openai-node

Форк
0
/
postprocess-files.cjs 
165 строк · 5.3 Кб
1
const fs = require('fs');
2
const path = require('path');
3
const { parse } = require('@typescript-eslint/parser');
4

5
const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? 'openai/'
6

7
const distDir =
8
  process.env['DIST_PATH'] ?
9
    path.resolve(process.env['DIST_PATH'])
10
  : path.resolve(__dirname, '..', 'dist');
11
const distSrcDir = path.join(distDir, 'src');
12

13
/**
14
 * Quick and dirty AST traversal
15
 */
16
function traverse(node, visitor) {
17
  if (!node || typeof node.type !== 'string') return;
18
  visitor.node?.(node);
19
  visitor[node.type]?.(node);
20
  for (const key in node) {
21
    const value = node[key];
22
    if (Array.isArray(value)) {
23
      for (const elem of value) traverse(elem, visitor);
24
    } else if (value instanceof Object) {
25
      traverse(value, visitor);
26
    }
27
  }
28
}
29

30
/**
31
 * Helper method for replacing arbitrary ranges of text in input code.
32
 *
33
 * The `replacer` is a function that will be called with a mini-api.  For example:
34
 *
35
 * replaceRanges('foobar', ({ replace }) => replace([0, 3], 'baz')) // 'bazbar'
36
 *
37
 * The replaced ranges must not be overlapping.
38
 */
39
function replaceRanges(code, replacer) {
40
  const replacements = [];
41
  replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) });
42

43
  if (!replacements.length) return code;
44
  replacements.sort((a, b) => a.range[0] - b.range[0]);
45
  const overlapIndex = replacements.findIndex(
46
    (r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0],
47
  );
48
  if (overlapIndex >= 0) {
49
    throw new Error(
50
      `replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify(
51
        replacements[overlapIndex],
52
      )}`,
53
    );
54
  }
55

56
  const parts = [];
57
  let end = 0;
58
  for (const {
59
    range: [from, to],
60
    replacement,
61
  } of replacements) {
62
    if (from > end) parts.push(code.substring(end, from));
63
    parts.push(replacement);
64
    end = to;
65
  }
66
  if (end < code.length) parts.push(code.substring(end));
67
  return parts.join('');
68
}
69

70
/**
71
 * Like calling .map(), where the iteratee is called on the path in every import or export from statement.
72
 * @returns the transformed code
73
 */
74
function mapModulePaths(code, iteratee) {
75
  const ast = parse(code, { range: true });
76
  return replaceRanges(code, ({ replace }) =>
77
    traverse(ast, {
78
      node(node) {
79
        switch (node.type) {
80
          case 'ImportDeclaration':
81
          case 'ExportNamedDeclaration':
82
          case 'ExportAllDeclaration':
83
          case 'ImportExpression':
84
            if (node.source) {
85
              const { range, value } = node.source;
86
              const transformed = iteratee(value);
87
              if (transformed !== value) {
88
                replace(range, JSON.stringify(transformed));
89
              }
90
            }
91
        }
92
      },
93
    }),
94
  );
95
}
96

97
async function* walk(dir) {
98
  for await (const d of await fs.promises.opendir(dir)) {
99
    const entry = path.join(dir, d.name);
100
    if (d.isDirectory()) yield* walk(entry);
101
    else if (d.isFile()) yield entry;
102
  }
103
}
104

105
async function postprocess() {
106
  for await (const file of walk(path.resolve(__dirname, '..', 'dist'))) {
107
    if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue;
108

109
    const code = await fs.promises.readFile(file, 'utf8');
110

111
    let transformed = mapModulePaths(code, (importPath) => {
112
      if (file.startsWith(distSrcDir)) {
113
        if (importPath.startsWith(pkgImportPath)) {
114
          // convert self-references in dist/src to relative paths
115
          let relativePath = path.relative(
116
            path.dirname(file),
117
            path.join(distSrcDir, importPath.substring(pkgImportPath.length)),
118
          );
119
          if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
120
          return relativePath;
121
        }
122
        return importPath;
123
      }
124
      if (importPath.startsWith('.')) {
125
        // add explicit file extensions to relative imports
126
        const { dir, name } = path.parse(importPath);
127
        const ext = /\.mjs$/.test(file) ? '.mjs' : '.js';
128
        return `${dir}/${name}${ext}`;
129
      }
130
      return importPath;
131
    });
132

133
    if (file.startsWith(distSrcDir) && !file.endsWith('_shims/index.d.ts')) {
134
      // strip out `unknown extends Foo ? never :` shim guards in dist/src
135
      // to prevent errors from appearing in Go To Source
136
      transformed = transformed.replace(
137
        new RegExp('unknown extends (typeof )?\\S+ \\? \\S+ :\\s*'.replace(/\s+/, '\\s+'), 'gm'),
138
        // replace with same number of characters to avoid breaking source maps
139
        (match) => ' '.repeat(match.length),
140
      );
141
    }
142

143
    if (file.endsWith('.d.ts')) {
144
      // work around bad tsc behavior
145
      // if we have `import { type Readable } from 'openai/_shims/index'`,
146
      // tsc sometimes replaces `Readable` with `import("stream").Readable` inline
147
      // in the output .d.ts
148
      transformed = transformed.replace(/import\("stream"\).Readable/g, 'Readable');
149
    }
150

151
    // strip out lib="dom" and types="node" references; these are needed at build time,
152
    // but would pollute the user's TS environment
153
    transformed = transformed.replace(
154
      /^ *\/\/\/ *<reference +(lib="dom"|types="node").*?\n/gm,
155
      // replace with same number of characters to avoid breaking source maps
156
      (match) => ' '.repeat(match.length - 1) + '\n',
157
    );
158

159
    if (transformed !== code) {
160
      await fs.promises.writeFile(file, transformed, 'utf8');
161
      console.error(`wrote ${path.relative(process.cwd(), file)}`);
162
    }
163
  }
164
}
165
postprocess();
166

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.