Skip to content

Commit 1a309e5

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

File tree

9 files changed

+376
-76
lines changed

9 files changed

+376
-76
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.
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/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

packages/code-infra/src/utils/build.mjs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,60 @@ export function validatePkgJson(packageJson, options = {}) {
6868
throw error;
6969
}
7070
}
71+
72+
/**
73+
* Measures the performance of a function.
74+
* @param {string} label - The label for the performance measurement.
75+
* @param {Function} fn - The function to measure.
76+
* @returns {Promise<number>} - The duration of the function execution in milliseconds.
77+
*/
78+
export async function measurePerf(label, fn) {
79+
performance.mark(`${label}-start`);
80+
await Promise.resolve(fn());
81+
performance.mark(`${label}-end`);
82+
const measurement = performance.measure(label, `${label}-start`, `${label}-end`);
83+
return measurement.duration;
84+
}
85+
86+
export const BASE_IGNORES = [
87+
'**/*.test.js',
88+
'**/*.test.ts',
89+
'**/*.test.tsx',
90+
'**/*.spec.js',
91+
'**/*.spec.ts',
92+
'**/*.spec.tsx',
93+
'**/*.d.ts',
94+
'**/*.test/*.*',
95+
'**/test-cases/*.*',
96+
];
97+
98+
/**
99+
* @function
100+
* @template T
101+
* @param {(item: T) => Promise<void>} fn
102+
* @param {Object} options
103+
* @param {number} options.defaultConcurrency
104+
* @param {T[]} options.items
105+
* @returns {Promise<void>}
106+
*/
107+
export async function wrapInWorker(
108+
fn,
109+
{ defaultConcurrency, items } = { defaultConcurrency: 20, items: [] },
110+
) {
111+
if (items.length === 0) {
112+
return;
113+
}
114+
const itemIterator = items[Symbol.iterator]();
115+
const concurrency = Math.min(defaultConcurrency, items.length);
116+
const workers = [];
117+
for (let i = 0; i < concurrency; i += 1) {
118+
const worker = Promise.resolve().then(async () => {
119+
for (const item of itemIterator) {
120+
// eslint-disable-next-line no-await-in-loop
121+
await fn(item);
122+
}
123+
});
124+
workers.push(worker);
125+
}
126+
await Promise.all(workers);
127+
}

0 commit comments

Comments
 (0)