cloudflare/cloudflare-typescript

Public

mirrored from https://github.com/cloudflare/cloudflare-typescriptAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
generated-e3e8a04412

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/postprocess-files.cjs

165lines · modeblame

2d51afdcstainless-app[bot]2 years ago1const fs = require('fs');
2const path = require('path');
3const { parse } = require('@typescript-eslint/parser');
4
5const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? 'cloudflare/'
6
7const distDir =
8process.env['DIST_PATH'] ?
9path.resolve(process.env['DIST_PATH'])
10: path.resolve(__dirname, '..', 'dist');
11const distSrcDir = path.join(distDir, 'src');
12
13/**
14* Quick and dirty AST traversal
15*/
16function traverse(node, visitor) {
17if (!node || typeof node.type !== 'string') return;
18visitor.node?.(node);
19visitor[node.type]?.(node);
20for (const key in node) {
21const value = node[key];
22if (Array.isArray(value)) {
23for (const elem of value) traverse(elem, visitor);
24} else if (value instanceof Object) {
25traverse(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*/
39function replaceRanges(code, replacer) {
40const replacements = [];
41replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) });
42
43if (!replacements.length) return code;
44replacements.sort((a, b) => a.range[0] - b.range[0]);
45const overlapIndex = replacements.findIndex(
46(r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0],
47);
48if (overlapIndex >= 0) {
49throw new Error(
50`replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify(
51replacements[overlapIndex],
52)}`,
53);
54}
55
56const parts = [];
57let end = 0;
58for (const {
59range: [from, to],
60replacement,
61} of replacements) {
62if (from > end) parts.push(code.substring(end, from));
63parts.push(replacement);
64end = to;
65}
66if (end < code.length) parts.push(code.substring(end));
67return 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*/
74function mapModulePaths(code, iteratee) {
75const ast = parse(code, { range: true });
76return replaceRanges(code, ({ replace }) =>
77traverse(ast, {
78node(node) {
79switch (node.type) {
80case 'ImportDeclaration':
81case 'ExportNamedDeclaration':
82case 'ExportAllDeclaration':
83case 'ImportExpression':
84if (node.source) {
85const { range, value } = node.source;
86const transformed = iteratee(value);
87if (transformed !== value) {
88replace(range, JSON.stringify(transformed));
89}
90}
91}
92},
93}),
94);
95}
96
97async function* walk(dir) {
98for await (const d of await fs.promises.opendir(dir)) {
99const entry = path.join(dir, d.name);
100if (d.isDirectory()) yield* walk(entry);
101else if (d.isFile()) yield entry;
102}
103}
104
105async function postprocess() {
106for await (const file of walk(path.resolve(__dirname, '..', 'dist'))) {
107if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue;
108
109const code = await fs.promises.readFile(file, 'utf8');
110
111let transformed = mapModulePaths(code, (importPath) => {
112if (file.startsWith(distSrcDir)) {
113if (importPath.startsWith(pkgImportPath)) {
114// convert self-references in dist/src to relative paths
115let relativePath = path.relative(
116path.dirname(file),
117path.join(distSrcDir, importPath.substring(pkgImportPath.length)),
118);
119if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
120return relativePath;
121}
122return importPath;
123}
124if (importPath.startsWith('.')) {
125// add explicit file extensions to relative imports
126const { dir, name } = path.parse(importPath);
127const ext = /\.mjs$/.test(file) ? '.mjs' : '.js';
128return `${dir}/${name}${ext}`;
129}
130return importPath;
131});
132
133if (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
136transformed = transformed.replace(
137new 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
143if (file.endsWith('.d.ts')) {
144// work around bad tsc behavior
145// if we have `import { type Readable } from 'cloudflare/_shims/index'`,
146// tsc sometimes replaces `Readable` with `import("stream").Readable` inline
147// in the output .d.ts
148transformed = 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
153transformed = 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
159if (transformed !== code) {
160await fs.promises.writeFile(file, transformed, 'utf8');
161console.error(`wrote ${path.relative(process.cwd(), file)}`);
162}
163}
164}
165postprocess();