@mui/internal-code-infra 0.1.1-canary.23 → 0.1.1-canary.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,6 +35,10 @@ export type Args = {
35
35
  * - Whether to skip checking for main field in package.json.
36
36
  */
37
37
  skipMainCheck: boolean;
38
+ /**
39
+ * - Whether to skip validating the output directory with publint.
40
+ */
41
+ skipPublint: boolean;
38
42
  /**
39
43
  * - Globs to be ignored by Babel.
40
44
  */
@@ -1,4 +1,5 @@
1
1
  export * from './baseConfig.mjs';
2
2
  export * from './docsConfig.mjs';
3
+ export * from './nodeSupportConfig.mjs';
3
4
  export * from './testConfig.mjs';
4
5
  export * from './extensions.mjs';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Reports syntax, ECMAScript built-ins and Node.js APIs in package sources that the Node.js
3
+ * versions allowed by the package's "engines.node" don't support. The ESM bundle is compiled for
4
+ * the "stable" browserslist environment rather than for "engines.node", and nothing polyfills
5
+ * built-ins, so all of them reach Node.js consumers as written.
6
+ * Only packages that declare "engines.node", meaning they're intended to run in Node.js, are
7
+ * checked.
8
+ * @param {string} baseDirectory - The repository root. File patterns are resolved against it.
9
+ * @returns {import('eslint').Linter.Config[]}
10
+ */
11
+ export declare function createNodeSupportConfig(baseDirectory: string): import('eslint').Linter.Config[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.1.1-canary.23",
3
+ "version": "0.1.1-canary.25",
4
4
  "author": "MUI Team",
5
5
  "description": "Infra scripts and configs to be used across MUI repos.",
6
6
  "license": "MIT",
@@ -101,6 +101,7 @@
101
101
  "eslint-plugin-import": "^2.32.0",
102
102
  "eslint-plugin-jsx-a11y": "^6.10.2",
103
103
  "eslint-plugin-mdx": "^3.8.2",
104
+ "eslint-plugin-n": "^18.5.0",
104
105
  "eslint-plugin-package-json": "^1.10.1",
105
106
  "eslint-plugin-react": "^7.37.5",
106
107
  "eslint-plugin-react-compiler": "^19.1.0-rc.2",
@@ -115,6 +116,7 @@
115
116
  "node-html-parser": "^9.0.5",
116
117
  "open": "^11.0.4",
117
118
  "postcss-styled-syntax": "^0.7.4",
119
+ "publint": "^0.3.25",
118
120
  "regexp.escape": "^2.0.1",
119
121
  "rehype-slug": "^6.0.0",
120
122
  "rehype-stringify": "^10.0.1",
@@ -190,7 +192,7 @@
190
192
  "publishConfig": {
191
193
  "access": "public"
192
194
  },
193
- "gitSha": "8d779945c318e7e2dada1296b64de1e618a99942",
195
+ "gitSha": "80d3ec0d49901ddda4af788aeb3b1b51e21c07cc",
194
196
  "scripts": {
195
197
  "build": "tsc -p tsconfig.build.json",
196
198
  "typescript": "tsc -noEmit",
@@ -28,6 +28,7 @@ import { readPnpmConfig } from '../utils/pnpm.mjs';
28
28
  * @property {boolean} skipBabelRuntimeCheck - Whether to skip checking for Babel runtime dependencies in the package.
29
29
  * @property {boolean} skipPackageJson - Whether to skip generating the package.json file in the bundle output.
30
30
  * @property {boolean} skipMainCheck - Whether to skip checking for main field in package.json.
31
+ * @property {boolean} skipPublint - Whether to skip validating the output directory with publint.
31
32
  * @property {string[]} ignore - Globs to be ignored by Babel.
32
33
  * @property {string[]} [copy] - Files/Directories to be copied. Can be a glob pattern.
33
34
  * @property {boolean} [enableReactCompiler] - Whether to use the React compiler.
@@ -216,6 +217,11 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
216
217
  default: false,
217
218
  description: 'Skip checking for main field in package.json.',
218
219
  })
220
+ .option('skipPublint', {
221
+ type: 'boolean',
222
+ default: false,
223
+ description: 'Skip validating the output directory with publint.',
224
+ })
219
225
  .option('copy', {
220
226
  type: 'string',
221
227
  array: true,
@@ -392,9 +398,48 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
392
398
  buildDir,
393
399
  verbose: args.verbose,
394
400
  });
401
+
402
+ if (!args.skipPublint) {
403
+ await validateWithPublint(buildDir);
404
+ }
395
405
  },
396
406
  });
397
407
 
408
+ /**
409
+ * Lints the output directory with publint. Errors fail the build, warnings are only reported.
410
+ * @param {string} buildDir - The build directory to validate.
411
+ * @returns {Promise<void>}
412
+ */
413
+ async function validateWithPublint(buildDir) {
414
+ const [{ publint }, { formatMessage }] = await Promise.all([
415
+ import('publint'),
416
+ import('publint/utils'),
417
+ ]);
418
+ // Everything in the output directory is published, so there is nothing to pack.
419
+ const result = await publint({ pkgDir: buildDir, pack: false, level: 'warning' });
420
+ const { pkg } = result;
421
+ // TODO: remove this filter and bump publint to >=0.3.25, which ships the fix from
422
+ // https://github.com/publint/publint/pull/265 (publint checks `browser` values
423
+ // that point at another package as local files).
424
+ const messages = result.messages.filter((message) => {
425
+ if (message.code !== 'FILE_DOES_NOT_EXIST' || message.path[0] !== 'browser') {
426
+ return true;
427
+ }
428
+ const value = message.path.length === 2 ? pkg.browser?.[message.path[1]] : undefined;
429
+ return typeof value !== 'string' || value.startsWith('.');
430
+ });
431
+
432
+ for (const message of messages) {
433
+ const log = message.type === 'error' ? console.error : console.warn;
434
+ log(`publint ${message.type}: ${formatMessage(message, pkg)}`);
435
+ }
436
+
437
+ const errorCount = messages.filter((message) => message.type === 'error').length;
438
+ if (errorCount > 0) {
439
+ throw new Error(`publint found ${errorCount} error(s) in "${buildDir}".`);
440
+ }
441
+ }
442
+
398
443
  /**
399
444
  * @param {Object} param0
400
445
  * @param {string} param0.cwd - The current working directory.
@@ -3,6 +3,7 @@ import eslintJs from '@eslint/js';
3
3
  import { defineConfig } from 'eslint/config';
4
4
  import prettier from 'eslint-config-prettier/flat';
5
5
  import compatPlugin from 'eslint-plugin-compat';
6
+ import nPlugin from 'eslint-plugin-n';
6
7
  import importPlugin from 'eslint-plugin-import';
7
8
  import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
8
9
  import reactPlugin from 'eslint-plugin-react';
@@ -17,6 +18,7 @@ import { createCoreConfig } from './mui/config.mjs';
17
18
  import muiPlugin from './mui/index.mjs';
18
19
  import { EXTENSION_TS } from './extensions.mjs';
19
20
  import { createJsonConfig } from './jsonConfig.mjs';
21
+ import { createNodeSupportConfig } from './nodeSupportConfig.mjs';
20
22
  import { createPackageJsonConfig } from './packageJsonConfig.mjs';
21
23
 
22
24
  /**
@@ -59,6 +61,9 @@ export function createBaseConfig({
59
61
  },
60
62
  plugins: {
61
63
  mui: muiPlugin,
64
+ // Registered once for all files, so consumers can enable `n/*` rules without registering
65
+ // a second, possibly different, instance of the plugin.
66
+ n: nPlugin,
62
67
  },
63
68
  settings: {
64
69
  react: {
@@ -74,6 +79,7 @@ export function createBaseConfig({
74
79
  includeIgnoreIfExists(path.join(baseDirectory, '.gitignore'), `Ignore rules from .gitignore`),
75
80
  includeIgnoreIfExists(path.join(baseDirectory, '.lintignore'), `Ignore rules from .lintignore`),
76
81
  createJsonConfig(),
82
+ createNodeSupportConfig(baseDirectory),
77
83
  createPackageJsonConfig(),
78
84
  prettier,
79
85
  // Markdown + MDX linting via eslint-plugin-mdx. Severities for markdown
@@ -3,5 +3,6 @@
3
3
 
4
4
  export * from './baseConfig.mjs';
5
5
  export * from './docsConfig.mjs';
6
+ export * from './nodeSupportConfig.mjs';
6
7
  export * from './testConfig.mjs';
7
8
  export * from './extensions.mjs';
@@ -0,0 +1,70 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { defineConfig } from 'eslint/config';
4
+ import nPlugin from 'eslint-plugin-n';
5
+ import { globbySync } from 'globby';
6
+ import { EXTENSION_TEST_FILE, EXTENSION_TS } from './extensions.mjs';
7
+
8
+ /**
9
+ * Finds the packages in the `packages*` directories of the repository that declare the Node.js
10
+ * versions they support in "engines.node". Packages without it, like development tooling, run
11
+ * on the repository's own Node.js version.
12
+ * @param {string} baseDirectory
13
+ * @returns {string[]} Package directories, relative to `baseDirectory`.
14
+ */
15
+ function findPackageDirsWithEnginesNode(baseDirectory) {
16
+ return globbySync('packages*/*/package.json', { cwd: baseDirectory })
17
+ .filter((packageJsonPath) => {
18
+ const packageJson = JSON.parse(
19
+ fs.readFileSync(path.join(baseDirectory, packageJsonPath), 'utf8'),
20
+ );
21
+ return typeof packageJson.engines?.node === 'string';
22
+ })
23
+ .map((packageJsonPath) => path.posix.dirname(packageJsonPath));
24
+ }
25
+
26
+ /**
27
+ * Reports syntax, ECMAScript built-ins and Node.js APIs in package sources that the Node.js
28
+ * versions allowed by the package's "engines.node" don't support. The ESM bundle is compiled for
29
+ * the "stable" browserslist environment rather than for "engines.node", and nothing polyfills
30
+ * built-ins, so all of them reach Node.js consumers as written.
31
+ * Only packages that declare "engines.node", meaning they're intended to run in Node.js, are
32
+ * checked.
33
+ * @param {string} baseDirectory - The repository root. File patterns are resolved against it.
34
+ * @returns {import('eslint').Linter.Config[]}
35
+ */
36
+ export function createNodeSupportConfig(baseDirectory) {
37
+ const packageDirs = findPackageDirsWithEnginesNode(baseDirectory);
38
+ if (packageDirs.length === 0) {
39
+ return [];
40
+ }
41
+ return defineConfig([
42
+ {
43
+ name: 'Node.js support of package sources',
44
+ basePath: baseDirectory,
45
+ files: packageDirs.map((packageDir) => `${packageDir}/src/**/*${EXTENSION_TS}`),
46
+ ignores: [`**/*${EXTENSION_TEST_FILE}`, `**/*.spec${EXTENSION_TS}`],
47
+ plugins: { n: nPlugin },
48
+ rules: {
49
+ 'n/no-unsupported-features/es-builtins': 'error',
50
+ 'n/no-unsupported-features/es-syntax': 'error',
51
+ 'n/no-unsupported-features/node-builtins': [
52
+ 'error',
53
+ // Browser APIs that Node.js also exposes, behind an experimental flag. In packages that
54
+ // combine Node.js tooling with browser code, these refer to the browser API.
55
+ {
56
+ ignores: [
57
+ 'localStorage',
58
+ 'sessionStorage',
59
+ 'Storage',
60
+ 'navigator',
61
+ 'Navigator',
62
+ 'Worker',
63
+ 'ErrorEvent',
64
+ ],
65
+ },
66
+ ],
67
+ },
68
+ },
69
+ ]);
70
+ }