Skip to content

Commit 0a88db4

Browse files
committed
[infra] Add cli to extract error codes independent of build
1 parent 6a3e097 commit 0a88db4

File tree

11 files changed

+368
-124
lines changed

11 files changed

+368
-124
lines changed

packages/babel-plugin-minify-errors/index.js

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,68 @@ function handleUnminifyableError(missingError, path) {
102102
}
103103
}
104104

105+
/**
106+
* @param {babel.types} t
107+
* @param {babel.NodePath<babel.types.NewExpression>} newExpressionPath
108+
* @param {{ detection: Options['detection']; missingError: MissingError}} param2
109+
* @returns {null | { messageNode: babel.types.Expression; messagePath: babel.NodePath<babel.types.ArgumentPlaceholder | babel.types.SpreadElement | babel.types.Expression>; message: { message: string; expressions: babel.types.Expression[] } }}
110+
*/
111+
function findMessageNode(t, newExpressionPath, { detection, missingError }) {
112+
if (!newExpressionPath.get('callee').isIdentifier({ name: 'Error' })) {
113+
return null;
114+
}
115+
116+
switch (detection) {
117+
case 'opt-in': {
118+
if (
119+
!newExpressionPath.node.leadingComments?.some((comment) =>
120+
comment.value.includes(COMMENT_OPT_IN_MARKER),
121+
)
122+
) {
123+
return null;
124+
}
125+
newExpressionPath.node.leadingComments = newExpressionPath.node.leadingComments.filter(
126+
(comment) => !comment.value.includes(COMMENT_OPT_IN_MARKER),
127+
);
128+
break;
129+
}
130+
case 'opt-out': {
131+
if (
132+
newExpressionPath.node.leadingComments?.some((comment) =>
133+
comment.value.includes(COMMENT_OPT_OUT_MARKER),
134+
)
135+
) {
136+
newExpressionPath.node.leadingComments = newExpressionPath.node.leadingComments.filter(
137+
(comment) => !comment.value.includes(COMMENT_OPT_OUT_MARKER),
138+
);
139+
return null;
140+
}
141+
142+
break;
143+
}
144+
default: {
145+
throw new Error(`Unknown detection option: ${detection}`);
146+
}
147+
}
148+
149+
const messagePath = newExpressionPath.get('arguments')[0];
150+
if (!messagePath) {
151+
return null;
152+
}
153+
154+
const messageNode = messagePath.node;
155+
if (t.isSpreadElement(messageNode) || t.isArgumentPlaceholder(messageNode)) {
156+
handleUnminifyableError(missingError, newExpressionPath);
157+
return null;
158+
}
159+
const message = extractMessage(t, messageNode);
160+
if (!message) {
161+
handleUnminifyableError(missingError, newExpressionPath);
162+
return null;
163+
}
164+
return { messagePath, messageNode, message };
165+
}
166+
105167
/**
106168
* Transforms the error message node.
107169
* @param {babel.types} t
@@ -261,59 +323,15 @@ module.exports = function plugin(
261323
name: '@mui/internal-babel-plugin-minify-errors',
262324
visitor: {
263325
NewExpression(newExpressionPath, state) {
264-
if (!newExpressionPath.get('callee').isIdentifier({ name: 'Error' })) {
265-
return;
266-
}
267-
268-
switch (detection) {
269-
case 'opt-in': {
270-
if (
271-
!newExpressionPath.node.leadingComments?.some((comment) =>
272-
comment.value.includes(COMMENT_OPT_IN_MARKER),
273-
)
274-
) {
275-
return;
276-
}
277-
newExpressionPath.node.leadingComments = newExpressionPath.node.leadingComments.filter(
278-
(comment) => !comment.value.includes(COMMENT_OPT_IN_MARKER),
279-
);
280-
break;
281-
}
282-
case 'opt-out': {
283-
if (
284-
newExpressionPath.node.leadingComments?.some((comment) =>
285-
comment.value.includes(COMMENT_OPT_OUT_MARKER),
286-
)
287-
) {
288-
newExpressionPath.node.leadingComments =
289-
newExpressionPath.node.leadingComments.filter(
290-
(comment) => !comment.value.includes(COMMENT_OPT_OUT_MARKER),
291-
);
292-
return;
293-
}
294-
295-
break;
296-
}
297-
default: {
298-
throw new Error(`Unknown detection option: ${detection}`);
299-
}
300-
}
301-
302-
const messagePath = newExpressionPath.get('arguments')[0];
303-
if (!messagePath) {
304-
return;
305-
}
306-
307-
const messageNode = messagePath.node;
308-
if (t.isSpreadElement(messageNode) || t.isArgumentPlaceholder(messageNode)) {
309-
handleUnminifyableError(missingError, newExpressionPath);
326+
const message = findMessageNode(t, newExpressionPath, { detection, missingError });
327+
if (!message) {
310328
return;
311329
}
312330

313331
const transformedMessage = transformMessage(
314332
t,
315333
newExpressionPath,
316-
messageNode,
334+
message.messageNode,
317335
state,
318336
errorCodesLookup,
319337
missingError,
@@ -322,7 +340,7 @@ module.exports = function plugin(
322340
);
323341

324342
if (transformedMessage) {
325-
messagePath.replaceWith(transformedMessage);
343+
message.messagePath.replaceWith(transformedMessage);
326344
}
327345
},
328346
},
@@ -336,3 +354,7 @@ module.exports = function plugin(
336354
},
337355
};
338356
};
357+
358+
module.exports.findMessageNode = findMessageNode;
359+
360+
exports.findMessageNode = findMessageNode;

packages/code-infra/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@argos-ci/core": "^4.1.0",
3030
"@babel/cli": "^7.28.3",
3131
"@babel/core": "^7.28.3",
32+
"@babel/plugin-syntax-jsx": "^7.27.1",
3233
"@babel/plugin-syntax-typescript": "^7.27.1",
3334
"@babel/plugin-transform-runtime": "^7.28.3",
3435
"@babel/preset-env": "^7.28.3",

packages/code-infra/src/cli/babel.mjs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { globby } from 'globby';
66
import * as fs from 'node:fs/promises';
77
import * as path from 'node:path';
88
import { $ } from 'execa';
9+
import { BASE_IGNORES } from '../utils/build.mjs';
910

1011
const TO_TRANSFORM_EXTENSIONS = ['.js', '.ts', '.tsx'];
1112

@@ -64,18 +65,6 @@ export async function cjsCopy({ from, to }) {
6465
* @property {string} [runtimeModule] - The runtime module to replace the errors with.
6566
*/
6667

67-
const BASE_IGNORES = [
68-
'**/*.test.js',
69-
'**/*.test.ts',
70-
'**/*.test.tsx',
71-
'**/*.spec.js',
72-
'**/*.spec.ts',
73-
'**/*.spec.tsx',
74-
'**/*.d.ts',
75-
'**/*.test/*.*',
76-
'**/test-cases/*.*',
77-
];
78-
7968
/**
8069
* @param {Object} options
8170
* @param {boolean} [options.verbose=false] - Whether to enable verbose logging.

packages/code-infra/src/cli/cmdCopyFiles.mjs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
22
import { globby } from 'globby';
33
import fs from 'node:fs/promises';
44
import path from 'node:path';
5+
import { wrapInWorker } from '../utils/build.mjs';
56

67
/**
78
* @typedef {Object} Args
@@ -105,20 +106,12 @@ async function processGlobs({ globs, cwd, silent = true, buildDir }) {
105106
});
106107
});
107108

108-
const concurrency = filesToProcess.length > 100 ? 100 : filesToProcess.length;
109-
const iterator = filesToProcess[Symbol.iterator]();
110-
const workers = [];
111-
for (let i = 0; i < concurrency; i += 1) {
112-
workers.push(
113-
Promise.resolve().then(async () => {
114-
for (const file of iterator) {
115-
// eslint-disable-next-line no-await-in-loop
116-
await recursiveCopy({ source: file.sourcePath, target: file.targetPath, silent });
117-
}
118-
}),
119-
);
120-
}
121-
await Promise.all(workers);
109+
await wrapInWorker(
110+
async (file) => {
111+
await recursiveCopy({ source: file.sourcePath, target: file.targetPath, silent });
112+
},
113+
{ items: filesToProcess },
114+
);
122115
return filesToProcess.length;
123116
}
124117

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* eslint-disable no-console */
2+
3+
import { measurePerf } from '../utils/build.mjs';
4+
5+
/**
6+
* @typedef {import('../utils/extractErrorCodes.mjs').Args} Args
7+
*/
8+
9+
export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
10+
command: 'extract-error-codes',
11+
describe: 'Extracts error codes from package(s).',
12+
builder(yargs) {
13+
return yargs
14+
.option('errorCodeFile', {
15+
type: 'string',
16+
describe: 'The output path to a json file to write the extracted error codes.',
17+
demandOption: true,
18+
})
19+
.option('skip', {
20+
type: 'array',
21+
describe: 'List of package names to skip.',
22+
default: [],
23+
});
24+
},
25+
async handler(args) {
26+
console.log(`🔨 Extracting error codes`);
27+
const duration = await measurePerf(/** @type {string} */ (args._[0]), async () => {
28+
const module = await import('../utils/extractErrorCodes.mjs');
29+
await module.default(args);
30+
});
31+
console.log(`✅ Extracted error codes in ${(duration / 1000.0).toFixed(3)}s`);
32+
},
33+
});

packages/code-infra/src/cli/cmdJsonLint.mjs

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import chalk from 'chalk';
44
import fs from 'node:fs/promises';
55
import { globby } from 'globby';
66
import path from 'node:path';
7+
import { wrapInWorker } from '../utils/build.mjs';
78

89
/**
910
* @typedef {Object} Args
@@ -42,33 +43,24 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
4243
followSymbolicLinks: false,
4344
});
4445

45-
const fileIterator = filenames[Symbol.iterator]();
46-
const concurrency = Math.min(20, filenames.length);
4746
let passed = true;
48-
const workers = [];
4947

50-
for (let i = 0; i < concurrency; i += 1) {
51-
// eslint-disable-next-line @typescript-eslint/no-loop-func
52-
const worker = Promise.resolve().then(async () => {
53-
for (const filename of fileIterator) {
54-
// eslint-disable-next-line no-await-in-loop
55-
const content = await fs.readFile(path.join(cwd, filename), { encoding: 'utf8' });
56-
try {
57-
JSON.parse(content);
58-
if (!args.silent) {
59-
// eslint-disable-next-line no-console
60-
console.log(passMessage(filename));
61-
}
62-
} catch (error) {
63-
passed = false;
64-
console.error(failMessage(`Error parsing ${filename}:\n\n${String(error)}`));
48+
await wrapInWorker(
49+
async (filename) => {
50+
const content = await fs.readFile(path.join(cwd, filename), { encoding: 'utf8' });
51+
try {
52+
JSON.parse(content);
53+
if (!args.silent) {
54+
// eslint-disable-next-line no-console
55+
console.log(passMessage(filename));
6556
}
57+
} catch (error) {
58+
passed = false;
59+
console.error(failMessage(`Error parsing ${filename}:\n\n${String(error)}`));
6660
}
67-
});
68-
workers.push(worker);
69-
}
70-
71-
await Promise.allSettled(workers);
61+
},
62+
{ items: filenames, defaultConcurrency: 20, promiseMethod: 'allSettled' },
63+
);
7264
if (!passed) {
7365
throw new Error('❌ At least one file did not pass. Check the console output');
7466
}

packages/code-infra/src/cli/index.mjs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,21 @@ import cmdListWorkspaces from './cmdListWorkspaces.mjs';
1010
import cmdPublish from './cmdPublish.mjs';
1111
import cmdPublishCanary from './cmdPublishCanary.mjs';
1212
import cmdSetVersionOverrides from './cmdSetVersionOverrides.mjs';
13+
import cmdExtractErrorCodes from './cmdExtractErrorCodes.mjs';
1314

1415
const pkgJson = createRequire(import.meta.url)('../../package.json');
1516

1617
yargs()
1718
.scriptName('code-infra')
19+
.command(cmdArgosPush)
20+
.command(cmdBuild)
21+
.command(cmdCopyFiles)
22+
.command(cmdExtractErrorCodes)
23+
.command(cmdJsonLint)
24+
.command(cmdListWorkspaces)
1825
.command(cmdPublish)
1926
.command(cmdPublishCanary)
20-
.command(cmdListWorkspaces)
21-
.command(cmdJsonLint)
22-
.command(cmdArgosPush)
2327
.command(cmdSetVersionOverrides)
24-
.command(cmdCopyFiles)
25-
.command(cmdBuild)
2628
.demandCommand(1, 'You need at least one command before moving on')
2729
.strict()
2830
.help()

packages/code-infra/src/untyped-plugins.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ declare module '@babel/plugin-transform-runtime' {
102102
export default plugin;
103103
}
104104

105+
declare module '@babel/plugin-syntax-jsx' {
106+
import type { PluginItem } from '@babel/core';
107+
108+
declare const plugin: PluginItem;
109+
export default plugin;
110+
}
111+
105112
declare module '@babel/plugin-syntax-typescript' {
106113
import type { PluginItem } from '@babel/core';
107114

0 commit comments

Comments
 (0)