Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 70 additions & 48 deletions packages/babel-plugin-minify-errors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,68 @@ function handleUnminifyableError(missingError, path) {
}
}

/**
* @param {babel.types} t
* @param {babel.NodePath<babel.types.NewExpression>} newExpressionPath
* @param {{ detection: Options['detection']; missingError: MissingError}} param2
* @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[] } }}
*/
function findMessageNode(t, newExpressionPath, { detection, missingError }) {
if (!newExpressionPath.get('callee').isIdentifier({ name: 'Error' })) {
return null;
}

switch (detection) {
case 'opt-in': {
if (
!newExpressionPath.node.leadingComments?.some((comment) =>
comment.value.includes(COMMENT_OPT_IN_MARKER),
)
) {
return null;
}
newExpressionPath.node.leadingComments = newExpressionPath.node.leadingComments.filter(
(comment) => !comment.value.includes(COMMENT_OPT_IN_MARKER),
);
break;
}
case 'opt-out': {
if (
newExpressionPath.node.leadingComments?.some((comment) =>
comment.value.includes(COMMENT_OPT_OUT_MARKER),
)
) {
newExpressionPath.node.leadingComments = newExpressionPath.node.leadingComments.filter(
(comment) => !comment.value.includes(COMMENT_OPT_OUT_MARKER),
);
return null;
}

break;
}
default: {
throw new Error(`Unknown detection option: ${detection}`);
}
}

const messagePath = newExpressionPath.get('arguments')[0];
if (!messagePath) {
return null;
}

const messageNode = messagePath.node;
if (t.isSpreadElement(messageNode) || t.isArgumentPlaceholder(messageNode)) {
handleUnminifyableError(missingError, newExpressionPath);
return null;
}
const message = extractMessage(t, messageNode);
if (!message) {
handleUnminifyableError(missingError, newExpressionPath);
return null;
}
return { messagePath, messageNode, message };
}

/**
* Transforms the error message node.
* @param {babel.types} t
Expand Down Expand Up @@ -261,59 +323,15 @@ module.exports = function plugin(
name: '@mui/internal-babel-plugin-minify-errors',
visitor: {
NewExpression(newExpressionPath, state) {
if (!newExpressionPath.get('callee').isIdentifier({ name: 'Error' })) {
return;
}

switch (detection) {
case 'opt-in': {
if (
!newExpressionPath.node.leadingComments?.some((comment) =>
comment.value.includes(COMMENT_OPT_IN_MARKER),
)
) {
return;
}
newExpressionPath.node.leadingComments = newExpressionPath.node.leadingComments.filter(
(comment) => !comment.value.includes(COMMENT_OPT_IN_MARKER),
);
break;
}
case 'opt-out': {
if (
newExpressionPath.node.leadingComments?.some((comment) =>
comment.value.includes(COMMENT_OPT_OUT_MARKER),
)
) {
newExpressionPath.node.leadingComments =
newExpressionPath.node.leadingComments.filter(
(comment) => !comment.value.includes(COMMENT_OPT_OUT_MARKER),
);
return;
}

break;
}
default: {
throw new Error(`Unknown detection option: ${detection}`);
}
}

const messagePath = newExpressionPath.get('arguments')[0];
if (!messagePath) {
return;
}

const messageNode = messagePath.node;
if (t.isSpreadElement(messageNode) || t.isArgumentPlaceholder(messageNode)) {
handleUnminifyableError(missingError, newExpressionPath);
const message = findMessageNode(t, newExpressionPath, { detection, missingError });
if (!message) {
return;
}

const transformedMessage = transformMessage(
t,
newExpressionPath,
messageNode,
message.messageNode,
state,
errorCodesLookup,
missingError,
Expand All @@ -322,7 +340,7 @@ module.exports = function plugin(
);

if (transformedMessage) {
messagePath.replaceWith(transformedMessage);
message.messagePath.replaceWith(transformedMessage);
}
},
},
Expand All @@ -336,3 +354,7 @@ module.exports = function plugin(
},
};
};

module.exports.findMessageNode = findMessageNode;

exports.findMessageNode = findMessageNode;
1 change: 1 addition & 0 deletions packages/code-infra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@argos-ci/core": "^4.1.2",
"@babel/cli": "^7.28.3",
"@babel/core": "^7.28.4",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-syntax-typescript": "^7.27.1",
"@babel/plugin-transform-runtime": "^7.28.3",
"@babel/preset-env": "^7.28.3",
Expand Down
13 changes: 1 addition & 12 deletions packages/code-infra/src/cli/babel.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { globby } from 'globby';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { $ } from 'execa';
import { BASE_IGNORES } from '../utils/build.mjs';

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

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

const BASE_IGNORES = [
'**/*.test.js',
'**/*.test.ts',
'**/*.test.tsx',
'**/*.spec.js',
'**/*.spec.ts',
'**/*.spec.tsx',
'**/*.d.ts',
'**/*.test/*.*',
'**/test-cases/*.*',
];

/**
* @param {Object} options
* @param {boolean} [options.verbose=false] - Whether to enable verbose logging.
Expand Down
22 changes: 8 additions & 14 deletions packages/code-infra/src/cli/cmdCopyFiles.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
import { globby } from 'globby';
import fs from 'node:fs/promises';
import path from 'node:path';
import { mapConcurrently } from '../utils/build.mjs';

/**
* @typedef {Object} Args
Expand Down Expand Up @@ -105,20 +106,13 @@ async function processGlobs({ globs, cwd, silent = true, buildDir }) {
});
});

const concurrency = filesToProcess.length > 100 ? 100 : filesToProcess.length;
const iterator = filesToProcess[Symbol.iterator]();
const workers = [];
for (let i = 0; i < concurrency; i += 1) {
workers.push(
Promise.resolve().then(async () => {
for (const file of iterator) {
// eslint-disable-next-line no-await-in-loop
await recursiveCopy({ source: file.sourcePath, target: file.targetPath, silent });
}
}),
);
}
await Promise.all(workers);
await mapConcurrently(
filesToProcess,
async (file) => {
await recursiveCopy({ source: file.sourcePath, target: file.targetPath, silent });
},
50,
);
return filesToProcess.length;
}

Expand Down
41 changes: 41 additions & 0 deletions packages/code-infra/src/cli/cmdExtractErrorCodes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable no-console */

import { markFn, measureFn } from '../utils/build.mjs';

/**
* @typedef {import('../utils/extractErrorCodes.mjs').Args} Args
*/

export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
command: 'extract-error-codes',
describe: 'Extracts error codes from package(s).',
builder(yargs) {
return yargs
.option('errorCodesPath', {
type: 'string',
describe: 'The output path to a json file to write the extracted error codes.',
demandOption: true,
})
.option('detection', {
type: 'string',
describe: 'The detection strategy to use when extracting error codes.',
choices: ['opt-in', 'opt-out'],
default: 'opt-in',
})
.option('skip', {
type: 'array',
describe: 'List of package names to skip.',
default: [],
});
},
async handler(args) {
const commandName = /** @type {string} */ (args._[0]);
await markFn(commandName, async () => {
const module = await import('../utils/extractErrorCodes.mjs');
await module.default(args);
});
console.log(
`✅ Extracted error codes in ${(measureFn(commandName).duration / 1000.0).toFixed(3)}s`,
);
},
});
39 changes: 16 additions & 23 deletions packages/code-infra/src/cli/cmdJsonLint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chalk from 'chalk';
import fs from 'node:fs/promises';
import { globby } from 'globby';
import path from 'node:path';
import { mapConcurrently } from '../utils/build.mjs';

/**
* @typedef {Object} Args
Expand Down Expand Up @@ -42,33 +43,25 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
followSymbolicLinks: false,
});

const fileIterator = filenames[Symbol.iterator]();
const concurrency = Math.min(20, filenames.length);
let passed = true;
const workers = [];

for (let i = 0; i < concurrency; i += 1) {
// eslint-disable-next-line @typescript-eslint/no-loop-func
const worker = Promise.resolve().then(async () => {
for (const filename of fileIterator) {
// eslint-disable-next-line no-await-in-loop
const content = await fs.readFile(path.join(cwd, filename), { encoding: 'utf8' });
try {
JSON.parse(content);
if (!args.silent) {
// eslint-disable-next-line no-console
console.log(passMessage(filename));
}
} catch (error) {
passed = false;
console.error(failMessage(`Error parsing ${filename}:\n\n${String(error)}`));
await mapConcurrently(
filenames,
async (filename) => {
const content = await fs.readFile(path.join(cwd, filename), { encoding: 'utf8' });
try {
JSON.parse(content);
if (!args.silent) {
// eslint-disable-next-line no-console
console.log(passMessage(filename));
}
} catch (error) {
passed = false;
console.error(failMessage(`Error parsing ${filename}:\n\n${String(error)}`));
}
});
workers.push(worker);
}

await Promise.allSettled(workers);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like allSettled wasn't wanted here, the JSON.parse is already wrapped, and the fs.readFile we would want to propagate its errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that we'd want to know about all the erroneous json files in the same run instead of just failing on the first wrong file.
This way, devs can fix the files together instead of running the command multiple times.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would still work, the JSON.parse is wrapped in try/catch already, and the catch just logs. as far as I can see, the allSettled is not doing anything, except from hiding errors from fs.readFile, which is undesired.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I figured later. Removed the option.

},
20,
);
if (!passed) {
throw new Error('❌ At least one file did not pass. Check the console output');
}
Expand Down
13 changes: 8 additions & 5 deletions packages/code-infra/src/cli/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { hideBin } from 'yargs/helpers';
import cmdArgosPush from './cmdArgosPush.mjs';
import cmdBuild from './cmdBuild.mjs';
import cmdCopyFiles from './cmdCopyFiles.mjs';
import cmdExtractErrorCodes from './cmdExtractErrorCodes.mjs';
import cmdJsonLint from './cmdJsonLint.mjs';
import cmdListWorkspaces from './cmdListWorkspaces.mjs';
import cmdPublish from './cmdPublish.mjs';
Expand All @@ -15,14 +16,16 @@ const pkgJson = createRequire(import.meta.url)('../../package.json');

yargs()
.scriptName('code-infra')
.usage('$0 <command> [args]')
.command(cmdArgosPush)
.command(cmdBuild)
.command(cmdCopyFiles)
.command(cmdExtractErrorCodes)
.command(cmdJsonLint)
.command(cmdListWorkspaces)
.command(cmdPublish)
.command(cmdPublishCanary)
.command(cmdListWorkspaces)
.command(cmdJsonLint)
.command(cmdArgosPush)
.command(cmdSetVersionOverrides)
.command(cmdCopyFiles)
.command(cmdBuild)
.demandCommand(1, 'You need at least one command before moving on')
.strict()
.help()
Expand Down
7 changes: 7 additions & 0 deletions packages/code-infra/src/untyped-plugins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ declare module '@babel/plugin-transform-runtime' {
export default plugin;
}

declare module '@babel/plugin-syntax-jsx' {
import type { PluginItem } from '@babel/core';

declare const plugin: PluginItem;
export default plugin;
}

declare module '@babel/plugin-syntax-typescript' {
import type { PluginItem } from '@babel/core';

Expand Down
Loading
Loading