@mui/internal-code-infra 0.1.1-canary.2 → 0.1.1-canary.20

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.
Files changed (35) hide show
  1. package/README.md +3 -3
  2. package/build/changelog/fetchChangelogs.d.mts +9 -10
  3. package/build/changelog/index.d.mts +1 -0
  4. package/build/cli/cmdBaseline.d.mts +18 -0
  5. package/build/cli/cmdNetlifyIgnore.d.mts +1 -1
  6. package/build/eslint/mui/rules/no-constructor-assigned-field.d.mts +50 -0
  7. package/build/eslint/packageJsonConfig.d.mts +7 -0
  8. package/build/remark/config.d.mts +5 -14
  9. package/build/utils/build.d.mts +14 -0
  10. package/build/utils/credentials.d.mts +2 -2
  11. package/build/utils/git.d.mts +30 -9
  12. package/build/utils/github.d.mts +8 -0
  13. package/package.json +38 -35
  14. package/src/changelog/fetchChangelogs.mjs +66 -26
  15. package/src/changelog/index.mjs +3 -0
  16. package/src/cli/cmdBaseline.mjs +31 -0
  17. package/src/cli/cmdBuild.mjs +2 -0
  18. package/src/cli/cmdNetlifyIgnore.mjs +14 -7
  19. package/src/cli/index.mjs +2 -0
  20. package/src/eslint/baseConfig.mjs +2 -0
  21. package/src/eslint/docsConfig.mjs +28 -10
  22. package/src/eslint/jsonConfig.mjs +2 -1
  23. package/src/eslint/mui/config.mjs +16 -1
  24. package/src/eslint/mui/index.mjs +2 -0
  25. package/src/eslint/mui/rules/no-constructor-assigned-field.mjs +183 -0
  26. package/src/eslint/packageJsonConfig.mjs +50 -0
  27. package/src/eslint/testConfig.mjs +1 -2
  28. package/src/remark/config.mjs +60 -13
  29. package/src/utils/build.mjs +34 -0
  30. package/src/utils/credentials.mjs +4 -3
  31. package/src/utils/extractErrorCodes.mjs +1 -3
  32. package/src/utils/git.mjs +107 -46
  33. package/src/utils/github.mjs +27 -0
  34. package/vale/styles/MUI/GoogleLatin.yml +5 -2
  35. package/vale/styles/MUI/CorrectRererenceCased.yml +0 -14
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @typedef {Object} Args
7
- * @property {string[]} workspaces - List of workspace names to process
7
+ * @property {string[]} workspaces - Workspace names or root-relative paths to process
8
8
  * @property {boolean} [check] - Check mode - error if the generated content differs from current
9
9
  * @property {string} [baseBranch] - Branch to compare PRs against (default: master)
10
10
  */
@@ -100,7 +100,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
100
100
  .positional('workspaces', {
101
101
  type: 'string',
102
102
  array: true,
103
- describe: 'List of workspace names to process',
103
+ describe: 'Workspace names or root-relative paths to process',
104
104
  demandOption: true,
105
105
  })
106
106
  .option('check', {
@@ -115,6 +115,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
115
115
  "Branch to compare PRs against (the site's production branch on Netlify). Production and branch deploys always rebuild regardless of this value.",
116
116
  })
117
117
  .example('$0 netlify-ignore @mui/internal-docs-infra', 'Update netlify.toml for a workspace')
118
+ .example('$0 netlify-ignore ./apps/dashboard', 'Update an unnamed workspace by path')
118
119
  .example(
119
120
  '$0 netlify-ignore @mui/internal-docs-infra @mui/internal-code-infra',
120
121
  'Update netlify.toml for multiple workspaces',
@@ -135,11 +136,17 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
135
136
 
136
137
  // Get all workspace packages and create workspace map
137
138
  const allWorkspaces = await getWorkspacePackages({ cwd: workspaceRoot });
138
- const workspaceMap = new Map(
139
- allWorkspaces.flatMap((workspace) =>
140
- workspace.name ? [[workspace.name, workspace.path]] : [],
141
- ),
142
- );
139
+ /** @type {Map<string, string>} */
140
+ const workspaceMap = new Map();
141
+ for (const workspace of allWorkspaces) {
142
+ if (workspace.name) {
143
+ workspaceMap.set(workspace.name, workspace.path);
144
+ }
145
+
146
+ // Paths also identify private workspaces that have no package name.
147
+ const relativePath = toPosixPath(path.relative(workspaceRoot, workspace.path));
148
+ workspaceMap.set(`./${relativePath}`, workspace.path);
149
+ }
143
150
 
144
151
  // Process each workspace concurrently
145
152
  await Promise.all(
package/src/cli/index.mjs CHANGED
@@ -4,6 +4,7 @@ import yargs from 'yargs';
4
4
  import { hideBin } from 'yargs/helpers';
5
5
 
6
6
  import cmdArgosPush from './cmdArgosPush.mjs';
7
+ import cmdBaseline from './cmdBaseline.mjs';
7
8
  import cmdBuild from './cmdBuild.mjs';
8
9
  import cmdCopyFiles from './cmdCopyFiles.mjs';
9
10
  import cmdExtractErrorCodes from './cmdExtractErrorCodes.mjs';
@@ -38,6 +39,7 @@ await yargs(hideBin(process.argv))
38
39
  globalArgv = argv;
39
40
  }, true)
40
41
  .command(cmdArgosPush)
42
+ .command(cmdBaseline)
41
43
  .command(cmdBuild)
42
44
  .command(cmdCopyFiles)
43
45
  .command(cmdExtractErrorCodes)
@@ -17,6 +17,7 @@ import { createCoreConfig } from './mui/config.mjs';
17
17
  import muiPlugin from './mui/index.mjs';
18
18
  import { EXTENSION_TS } from './extensions.mjs';
19
19
  import { createJsonConfig } from './jsonConfig.mjs';
20
+ import { createPackageJsonConfig } from './packageJsonConfig.mjs';
20
21
 
21
22
  /**
22
23
  * @param {string} filePath
@@ -73,6 +74,7 @@ export function createBaseConfig({
73
74
  includeIgnoreIfExists(path.join(baseDirectory, '.gitignore'), `Ignore rules from .gitignore`),
74
75
  includeIgnoreIfExists(path.join(baseDirectory, '.lintignore'), `Ignore rules from .lintignore`),
75
76
  createJsonConfig(),
77
+ createPackageJsonConfig(),
76
78
  prettier,
77
79
  // Markdown + MDX linting via eslint-plugin-mdx. Severities for markdown
78
80
  // quality checks live in the project's `.remarkrc` (see
@@ -19,17 +19,35 @@ export function createDocsConfig() {
19
19
  ? nextjsAlias.flatConfig.recommended
20
20
  : nextjsAlias.configs.recommended;
21
21
 
22
- return defineConfig(recommendedConfig, {
23
- settings: {
24
- next: {
25
- rootDir: 'docs',
22
+ return defineConfig(
23
+ recommendedConfig,
24
+ {
25
+ settings: {
26
+ next: {
27
+ rootDir: 'docs',
28
+ },
29
+ },
30
+ files: [`**/*${EXTENSION_TS}`],
31
+ rules: {
32
+ 'compat/compat': 'off',
33
+ 'jsx-a11y/anchor-is-valid': 'off',
34
+ 'no-irregular-whitespace': ['error', { skipJSXText: true, skipStrings: true }],
26
35
  },
27
36
  },
28
- files: [`**/*${EXTENSION_TS}`],
29
- rules: {
30
- 'compat/compat': 'off',
31
- 'jsx-a11y/anchor-is-valid': 'off',
32
- 'no-irregular-whitespace': ['error', { skipJSXText: true, skipStrings: true }],
37
+ {
38
+ // Build configuration and maintenance scripts aren't documentation examples
39
+ // and are often intentionally outside the docs TypeScript project.
40
+ files: [`**/*${EXTENSION_TS}`],
41
+ ignores: ['**/*.config.*', '**/scripts/**'],
42
+ languageOptions: {
43
+ parserOptions: {
44
+ projectService: true,
45
+ tsconfigRootDir: process.cwd(),
46
+ },
47
+ },
48
+ rules: {
49
+ '@typescript-eslint/no-deprecated': 'error',
50
+ },
33
51
  },
34
- });
52
+ );
35
53
  }
@@ -9,7 +9,8 @@ export function createJsonConfig() {
9
9
  {
10
10
  name: 'JSON files',
11
11
  files: ['**/*.json'],
12
- ignores: ['package-lock.json'],
12
+ // package.json is linted by eslint-plugin-package-json, whose rules need the jsonc-eslint-parser AST.
13
+ ignores: ['package-lock.json', '**/package.json'],
13
14
  plugins: { json },
14
15
  language: 'json/json',
15
16
  extends: [json.configs.recommended],
@@ -1,5 +1,5 @@
1
1
  import { defineConfig } from 'eslint/config';
2
- import { EXTENSION_DTS } from '../extensions.mjs';
2
+ import { EXTENSION_DTS, EXTENSION_TS_ONLY } from '../extensions.mjs';
3
3
 
4
4
  const restrictedMethods = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'];
5
5
 
@@ -184,6 +184,14 @@ const typescriptOverrides = {
184
184
  // TypeScript equivalents of ESLint rules
185
185
  'default-param-last': 'off',
186
186
  '@typescript-eslint/default-param-last': 'error',
187
+ '@typescript-eslint/ban-ts-comment': [
188
+ 'error',
189
+ {
190
+ 'ts-expect-error': 'allow-with-description',
191
+ 'ts-ignore': true,
192
+ minimumDescriptionLength: 10,
193
+ },
194
+ ],
187
195
  'no-array-constructor': 'off',
188
196
  '@typescript-eslint/no-array-constructor': 'error',
189
197
  '@typescript-eslint/triple-slash-reference': 'off',
@@ -538,6 +546,13 @@ export function createCoreConfig(options = {}) {
538
546
  '@typescript-eslint/return-await': 'off',
539
547
  },
540
548
  },
549
+ {
550
+ name: 'mui-base/ts',
551
+ files: [`**/*${EXTENSION_TS_ONLY}`],
552
+ rules: {
553
+ 'mui/no-constructor-assigned-field': 'error',
554
+ },
555
+ },
541
556
  {
542
557
  name: 'mui-base/dts',
543
558
  files: [`**/*${EXTENSION_DTS}`],
@@ -14,6 +14,7 @@ import addUndefToOptional from './rules/add-undef-to-optional.mjs';
14
14
  import flattenParentheses from './rules/flatten-parentheses.mjs';
15
15
  import noPresentationRole from './rules/no-presentation-role.mjs';
16
16
  import noFloatingCleanup from './rules/no-floating-cleanup.mjs';
17
+ import noConstructorAssignedField from './rules/no-constructor-assigned-field.mjs';
17
18
 
18
19
  /** @type {import('eslint').ESLint.Plugin} */
19
20
  const muiPlugin = {
@@ -39,6 +40,7 @@ const muiPlugin = {
39
40
  'flatten-parentheses': /** @type {any} */ (flattenParentheses),
40
41
  'no-presentation-role': noPresentationRole,
41
42
  'no-floating-cleanup': /** @type {any} */ (noFloatingCleanup),
43
+ 'no-constructor-assigned-field': /** @type {any} */ (noConstructorAssignedField),
42
44
  },
43
45
  };
44
46
 
@@ -0,0 +1,183 @@
1
+ import { ASTUtils, ESLintUtils } from '@typescript-eslint/utils';
2
+
3
+ /**
4
+ * @typedef {import('@typescript-eslint/utils').TSESTree.PropertyDefinition} PropertyDefinition
5
+ * @typedef {import('@typescript-eslint/utils').TSESTree.Node} Node
6
+ */
7
+
8
+ const createRule = ESLintUtils.RuleCreator(
9
+ (name) =>
10
+ `https://github.com/mui/mui-public/blob/master/packages/code-infra/src/eslint/mui/rules/${name}.mjs`,
11
+ );
12
+
13
+ /**
14
+ * Returns the constructor whose own code contains `node`, or `null` when `node` is outside a
15
+ * constructor or inside a nested function or class.
16
+ * @param {Node} node
17
+ */
18
+ function getEnclosingConstructor(node) {
19
+ let current = node.parent;
20
+ while (current && !ASTUtils.isFunction(current)) {
21
+ if (current.type === 'ClassBody') {
22
+ return null;
23
+ }
24
+ current = current.parent;
25
+ }
26
+ const method = current?.parent;
27
+ return method && ASTUtils.isConstructor(method) ? method : null;
28
+ }
29
+
30
+ /**
31
+ * Returns the extra edits needed before `field` can be marked `declare`.
32
+ * @param {PropertyDefinition} field
33
+ */
34
+ function getDeclareHint(field) {
35
+ const edits = [];
36
+ if (field.value) {
37
+ edits.push('drop its initializer');
38
+ }
39
+ if (field.definite) {
40
+ edits.push('drop the `!`');
41
+ }
42
+ if (field.override) {
43
+ edits.push('drop `override`');
44
+ }
45
+ return edits.length > 0 ? ` (${edits.join(', ')})` : '';
46
+ }
47
+
48
+ /**
49
+ * ESLint rule that disallows assigning a class field in the constructor when it is also
50
+ * declared as a (non-`declare`) class field.
51
+ *
52
+ * Babel 8 only strips TypeScript fields marked `declare` (see mui/material-ui#49241), so a
53
+ * field without an initializer is emitted as a property definition that sets it to
54
+ * `undefined` before the constructor assigns it again. Either initialize the field at its
55
+ * declaration, or mark it `declare` so it only serves as a type annotation.
56
+ *
57
+ * `declare` fields are exempt from TypeScript's strict property initialization check
58
+ * (TS2564), so prefer initializing at the declaration where the value doesn't depend on
59
+ * constructor arguments. The `declare` suggestion is only offered when every path through the
60
+ * constructor assigns the field, i.e. for an unconditional top-level assignment.
61
+ *
62
+ * Only plain `=` assignments count: compound assignments read the field rather than
63
+ * re-initialize it. A field with an initializer is only reported for an unconditional
64
+ * top-level assignment, as a conditional one overrides a default. Private (`#`) and decorated
65
+ * fields are ignored: they are runtime declarations that `declare` can't replace.
66
+ *
67
+ * @example
68
+ * // Invalid
69
+ * class ModalManager {
70
+ * private modals: Modal[];
71
+ * constructor() {
72
+ * this.modals = [];
73
+ * }
74
+ * }
75
+ *
76
+ * @example
77
+ * // Valid - initialized at the declaration
78
+ * class ModalManager {
79
+ * private modals: Modal[] = [];
80
+ * }
81
+ *
82
+ * @example
83
+ * // Valid - declared only, assigned in the constructor
84
+ * class ModalManager {
85
+ * declare private modals: Modal[];
86
+ * constructor(modals: Modal[]) {
87
+ * this.modals = modals;
88
+ * }
89
+ * }
90
+ */
91
+ export default createRule({
92
+ name: 'no-constructor-assigned-field',
93
+ meta: {
94
+ type: 'suggestion',
95
+ docs: {
96
+ description:
97
+ 'Disallow assigning class fields in the constructor when they are also declared as class fields',
98
+ },
99
+ hasSuggestions: true,
100
+ messages: {
101
+ assignedInConstructor:
102
+ "Field '{{name}}' is declared as a class field and also assigned in the constructor. " +
103
+ 'Either initialize it at the declaration and remove the constructor assignment, ' +
104
+ 'or mark it `declare`{{declareHint}}.',
105
+ addDeclare: 'Mark the field `declare`.',
106
+ },
107
+ schema: [],
108
+ },
109
+ defaultOptions: [],
110
+ create(context) {
111
+ /**
112
+ * Fields assigned in a constructor, mapped to whether any of those assignments is an
113
+ * unconditional top-level statement.
114
+ * @type {Map<PropertyDefinition, boolean>}
115
+ */
116
+ const assignedFields = new Map();
117
+
118
+ return {
119
+ AssignmentExpression(node) {
120
+ if (
121
+ node.operator !== '=' ||
122
+ node.left.type !== 'MemberExpression' ||
123
+ node.left.object.type !== 'ThisExpression'
124
+ ) {
125
+ return;
126
+ }
127
+ const name = ASTUtils.getPropertyName(node.left);
128
+ if (name === null) {
129
+ return;
130
+ }
131
+ const constructor = getEnclosingConstructor(node);
132
+ if (!constructor) {
133
+ return;
134
+ }
135
+
136
+ const field = constructor.parent.body.find(
137
+ /** @returns {member is PropertyDefinition} */
138
+ (member) =>
139
+ member.type === 'PropertyDefinition' &&
140
+ !member.static &&
141
+ !member.declare &&
142
+ member.decorators.length === 0 &&
143
+ ASTUtils.getPropertyName(member) === name,
144
+ );
145
+ if (!field) {
146
+ return;
147
+ }
148
+
149
+ const statement = node.parent;
150
+ const unconditional =
151
+ statement.type === 'ExpressionStatement' && statement.parent === constructor.value.body;
152
+ assignedFields.set(field, Boolean(assignedFields.get(field)) || unconditional);
153
+ },
154
+ 'ClassBody:exit'(classBody) {
155
+ for (const member of classBody.body) {
156
+ if (member.type !== 'PropertyDefinition' || !assignedFields.has(member)) {
157
+ continue;
158
+ }
159
+ const unconditional = Boolean(assignedFields.get(member));
160
+ if (member.value && !unconditional) {
161
+ continue;
162
+ }
163
+
164
+ const declareHint = getDeclareHint(member);
165
+ context.report({
166
+ node: member,
167
+ messageId: 'assignedInConstructor',
168
+ data: { name: ASTUtils.getPropertyName(member), declareHint },
169
+ suggest:
170
+ unconditional && declareHint === ''
171
+ ? [
172
+ {
173
+ messageId: 'addDeclare',
174
+ fix: (fixer) => fixer.insertTextBefore(member, 'declare '),
175
+ },
176
+ ]
177
+ : [],
178
+ });
179
+ }
180
+ },
181
+ };
182
+ },
183
+ });
@@ -0,0 +1,50 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import packageJson from 'eslint-plugin-package-json';
3
+
4
+ /**
5
+ * Lints package.json files for publishing metadata. Published packages must
6
+ * declare an SPDX license, `"author": "MUI Team"` and a repository pointing at
7
+ * their own directory. Private packages must not carry publishing metadata.
8
+ * @returns {import('eslint').Linter.Config[]}
9
+ */
10
+ export function createPackageJsonConfig() {
11
+ return defineConfig([
12
+ {
13
+ name: 'package.json files',
14
+ files: ['**/package.json'],
15
+ ignores: ['**/__fixtures__/**'],
16
+ extends: [packageJson.configs.recommended],
17
+ rules: {
18
+ 'package-json/require-author': ['error', { ignorePrivate: true }],
19
+ 'package-json/require-description': ['error', { ignorePrivate: true }],
20
+ 'package-json/restrict-private-properties': [
21
+ 'error',
22
+ // `publishConfig.directory` is allowed: `code-infra build` reads it, also for private packages.
23
+ { blockedProperties: ['files', 'license'] },
24
+ ],
25
+ 'no-restricted-syntax': [
26
+ 'error',
27
+ {
28
+ selector:
29
+ 'Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value="author"][value.value!="MUI Team"]',
30
+ message: 'The "author" field must be "MUI Team".',
31
+ },
32
+ ],
33
+ // Condition order in "exports" is significant, and scripts are grouped by purpose.
34
+ 'package-json/sort-collections': 'off',
35
+ // Packages built with `code-infra build` get these fields in the generated package.json.
36
+ 'package-json/require-files': 'off',
37
+ 'package-json/require-sideEffects': 'off',
38
+ 'package-json/require-type': 'off',
39
+ // Peer dependencies are installed once at the workspace root.
40
+ 'package-json/specify-peers-locally': 'off',
41
+ // CLI-only packages have no "exports".
42
+ 'package-json/require-exports': 'off',
43
+ // Rejects the object form of "browser", which the browser field spec allows.
44
+ 'package-json/valid-browser': 'off',
45
+ // npm only auto-includes "main" when it has no "./" prefix; removing it from "files" can drop the entry point.
46
+ 'package-json/no-redundant-files': 'off',
47
+ },
48
+ },
49
+ ]);
50
+ }
@@ -21,7 +21,6 @@ export const baseSpecRules = {
21
21
  '@typescript-eslint/no-unused-expressions': 'off',
22
22
  '@typescript-eslint/no-unused-vars': 'off',
23
23
  '@typescript-eslint/no-use-before-define': 'off',
24
- '@typescript-eslint/ban-ts-comment': 'off',
25
24
 
26
25
  'import/prefer-default-export': 'off',
27
26
 
@@ -63,6 +62,7 @@ export function createTestConfig() {
63
62
  'import/named': 'off',
64
63
  'mui/disallow-active-element-as-key-event-target': 'error',
65
64
  'mui/consistent-production-guard': 'off',
65
+ 'mui/no-constructor-assigned-field': 'off',
66
66
 
67
67
  // disable eslint-plugin-jsx-a11y
68
68
  // tests are not driven by assistive technology
@@ -87,7 +87,6 @@ export function createTestConfig() {
87
87
  'react/no-unused-prop-types': 'off',
88
88
  // Part of the migration away from airbnb config. Turned of initially.
89
89
  '@typescript-eslint/no-empty-function': 'off',
90
- '@typescript-eslint/ban-ts-comment': 'off',
91
90
  'testing-library/no-node-access': 'off',
92
91
  '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
93
92
  // end migration
@@ -97,6 +97,49 @@ function withOverrides(name, plugin, baseSettings, overrideEntries) {
97
97
  return wrapper;
98
98
  }
99
99
 
100
+ /**
101
+ * Allows link-reference comments while retaining diagnostics for real links and definitions.
102
+ * Existing docs generators use these markers in both Markdown and MDX and parse them as metadata.
103
+ * Authored MDX can use JSX comments without this compatibility option.
104
+ *
105
+ * @param {import('unified').Plugin<any[], any, any>} plugin
106
+ */
107
+ function withDefinitionComments(plugin) {
108
+ /** @param {any[]} settings */
109
+ return function wrapper(...settings) {
110
+ const processor = unified().use(plugin, ...settings);
111
+ /** @type {import('unified').Transformer} */
112
+ return async function transformer(tree, file) {
113
+ const previousMessages = file.messages.length;
114
+ await processor.run(tree, file);
115
+ file.messages = file.messages.filter((message, index) => {
116
+ const node = message.ancestors?.at(-1);
117
+ return !(
118
+ index >= previousMessages &&
119
+ node?.type === 'definition' &&
120
+ 'identifier' in node &&
121
+ node.identifier === '//' &&
122
+ 'title' in node &&
123
+ typeof node.title === 'string' &&
124
+ node.title.length > 0
125
+ );
126
+ });
127
+ };
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Makes the processor output the source text unchanged. The preset only lints, and
133
+ * remark-stringify doesn't round-trip Markdown (it escapes GitHub alerts, rewrites bare
134
+ * URLs, ...). eslint-plugin-mdx offers the processed output as a whole-file fix for any
135
+ * remark message, so with this `eslint --fix` leaves Markdown alone.
136
+ *
137
+ * @this {import('unified').Processor}
138
+ */
139
+ function remarkPreserveSource() {
140
+ this.compiler = (_tree, file) => String(file);
141
+ }
142
+
100
143
  /**
101
144
  * Returns a remark preset wiring the MUI-authored remark-lint plugins together
102
145
  * with a curated set of community plugins. Drop this into `.remarkrc.mjs`:
@@ -120,9 +163,11 @@ function withOverrides(name, plugin, baseSettings, overrideEntries) {
120
163
  * ```
121
164
  *
122
165
  * @param {Object} [options]
166
+ * @param {boolean} [options.allowDefinitionComments=false] Allow titled `[//]` link-reference comments.
123
167
  * @param {Array<{ files: string | string[], rules: Record<string, false | unknown[]> }>} [options.overrides]
168
+ * @returns {import('unified').Preset}
124
169
  */
125
- export function createRemarkConfig({ overrides = [] } = {}) {
170
+ export function createRemarkConfig({ overrides = [], allowDefinitionComments = false } = {}) {
126
171
  for (const override of overrides) {
127
172
  const unknown = Object.keys(override.rules).filter((ruleName) => !(ruleName in RULES));
128
173
  if (unknown.length > 0) {
@@ -130,10 +175,14 @@ export function createRemarkConfig({ overrides = [] } = {}) {
130
175
  }
131
176
  }
132
177
 
178
+ /** @type {import('unified').PluggableList} */
133
179
  const entries = Object.entries(RULES).map(([name, entry]) => {
134
- const [plugin, baseSettings] = /** @type {[import('unified').Plugin<any[], any, any>, any]} */ (
135
- entry
136
- );
180
+ const [rulePlugin, baseSettings] =
181
+ /** @type {[import('unified').Plugin<any[], any, any>, any]} */ (entry);
182
+ const plugin =
183
+ allowDefinitionComments && (name === 'no-empty-url' || name === 'no-unused-definitions')
184
+ ? withDefinitionComments(rulePlugin)
185
+ : rulePlugin;
137
186
  const overrideEntries = overrides
138
187
  .filter((override) => name in override.rules)
139
188
  .map((override) => ({ files: override.files, settings: override.rules[name] }));
@@ -144,14 +193,12 @@ export function createRemarkConfig({ overrides = [] } = {}) {
144
193
  });
145
194
 
146
195
  return {
147
- settings: {
148
- bullet: '-',
149
- emphasis: '_',
150
- fence: '`',
151
- listItemIndent: 'one',
152
- resourceLink: true,
153
- rule: '-',
154
- },
155
- plugins: [[remarkFrontmatter, ['yaml', 'toml']], remarkGfm, remarkLint, ...entries],
196
+ plugins: [
197
+ [remarkFrontmatter, ['yaml', 'toml']],
198
+ remarkGfm,
199
+ remarkLint,
200
+ ...entries,
201
+ remarkPreserveSource,
202
+ ],
156
203
  };
157
204
  }
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import * as path from 'node:path';
3
+ import getTargets from '@babel/helper-compilation-targets';
3
4
  import { globby } from 'globby';
4
5
  import * as semver from 'semver';
5
6
 
@@ -867,6 +868,39 @@ export function validatePkgJson(packageJson, options = {}) {
867
868
  }
868
869
  }
869
870
 
871
+ /**
872
+ * Validates that "engines.node" in package.json doesn't allow Node.js versions older than the
873
+ * "node" browserslist environment, which the CommonJS bundle is compiled for. Otherwise the
874
+ * build can emit syntax that the oldest supported Node.js version can't run.
875
+ * Targets are resolved the same way `@babel/preset-env` resolves them during the build.
876
+ * @param {{ name?: string; engines?: { node?: string } }} packageJson
877
+ * @param {string} cwd - The package directory, used to find the browserslist config.
878
+ */
879
+ export function validateEnginesNode(packageJson, cwd) {
880
+ const enginesNode = packageJson.engines?.node;
881
+ if (!enginesNode) {
882
+ return;
883
+ }
884
+
885
+ const minEnginesVersion = semver.minVersion(enginesNode);
886
+ if (!minEnginesVersion) {
887
+ throw new Error(
888
+ `Unable to determine the minimum Node.js version from "engines.node" in "${packageJson.name}" package.json: "${enginesNode}".`,
889
+ );
890
+ }
891
+
892
+ const targetNode = getTargets(undefined, { configPath: cwd, browserslistEnv: 'node' }).node;
893
+ if (!targetNode) {
894
+ return;
895
+ }
896
+
897
+ if (semver.gt(targetNode, minEnginesVersion)) {
898
+ throw new Error(
899
+ `"engines.node" in "${packageJson.name}" package.json allows Node.js ${minEnginesVersion.version}, but the "node" browserslist environment compiles for Node.js ${targetNode} and up. Raise "engines.node" to at least ${targetNode}, or lower the "node" browserslist target.`,
900
+ );
901
+ }
902
+ }
903
+
870
904
  /**
871
905
  * Marks the start and end of a function execution for performance measurement.
872
906
  * Uses the Performance API to create marks and measure the duration.
@@ -23,11 +23,12 @@ const accessedCredentials = new Map();
23
23
 
24
24
  /**
25
25
  * @param {string} key
26
- * @returns {Promise<string | undefined>}
26
+ * @returns {Promise<string | null>}
27
27
  */
28
28
  export async function getPassword(key) {
29
- if (accessedCredentials.has(key)) {
30
- return accessedCredentials.get(key);
29
+ const cached = accessedCredentials.get(key);
30
+ if (cached) {
31
+ return cached;
31
32
  }
32
33
  let credential = credentials.get(key);
33
34
  if (!credential) {
@@ -99,9 +99,7 @@ export default async function extractErrorCodes(args) {
99
99
  const packages = basePackages.filter(
100
100
  (pkg) =>
101
101
  // Ignore obvious packages that do not have user-facing errors
102
- !pkg.name.startsWith('@mui/internal-') &&
103
- !pkg.name.startsWith('@mui-internal/') &&
104
- !skipPackages.includes(pkg.name),
102
+ !pkg.name.startsWith('@mui/internal-') && !skipPackages.includes(pkg.name),
105
103
  );
106
104
  const files = await Promise.all(packages.map((pkg) => getFilesForPackage(pkg)));
107
105
  packages.forEach((pkg, index) => {