@mui/internal-code-infra 0.0.4-canary.3 → 0.0.4-canary.31

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 (69) hide show
  1. package/README.md +19 -8
  2. package/build/babel-config.d.mts +11 -3
  3. package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
  4. package/build/brokenLinksChecker/index.d.mts +35 -2
  5. package/build/changelog/types.d.ts +1 -1
  6. package/build/cli/cmdArgosPush.d.mts +2 -2
  7. package/build/cli/cmdBuild.d.mts +2 -2
  8. package/build/cli/cmdCopyFiles.d.mts +2 -2
  9. package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
  10. package/build/cli/cmdGenerateChangelog.d.mts +2 -2
  11. package/build/cli/cmdGithubAuth.d.mts +2 -2
  12. package/build/cli/cmdListWorkspaces.d.mts +4 -2
  13. package/build/cli/cmdNetlifyIgnore.d.mts +2 -2
  14. package/build/cli/cmdPublish.d.mts +4 -2
  15. package/build/cli/cmdPublishCanary.d.mts +3 -2
  16. package/build/cli/cmdPublishNewPackage.d.mts +4 -2
  17. package/build/cli/cmdSetVersionOverrides.d.mts +2 -2
  18. package/build/cli/cmdVale.d.mts +46 -0
  19. package/build/cli/cmdValidateBuiltTypes.d.mts +2 -2
  20. package/build/eslint/mui/rules/disallow-react-api-in-server-components.d.mts +2 -2
  21. package/build/eslint/mui/rules/docgen-ignore-before-comment.d.mts +2 -2
  22. package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +2 -2
  23. package/build/markdownlint/duplicate-h1.d.mts +1 -1
  24. package/build/markdownlint/git-diff.d.mts +1 -1
  25. package/build/markdownlint/index.d.mts +1 -1
  26. package/build/markdownlint/straight-quotes.d.mts +1 -1
  27. package/build/markdownlint/table-alignment.d.mts +1 -1
  28. package/build/markdownlint/terminal-language.d.mts +1 -1
  29. package/build/utils/build.d.mts +3 -3
  30. package/build/utils/github.d.mts +1 -1
  31. package/build/utils/pnpm.d.mts +68 -2
  32. package/build/utils/testUtils.d.mts +7 -0
  33. package/package.json +38 -31
  34. package/src/babel-config.mjs +9 -3
  35. package/src/brokenLinksChecker/__fixtures__/static-site/index.html +1 -0
  36. package/src/brokenLinksChecker/__fixtures__/static-site/invalid-html.html +15 -0
  37. package/src/brokenLinksChecker/crawlWorker.mjs +173 -0
  38. package/src/brokenLinksChecker/index.mjs +177 -164
  39. package/src/brokenLinksChecker/index.test.ts +55 -13
  40. package/src/build-env.d.ts +13 -0
  41. package/src/changelog/fetchChangelogs.mjs +6 -2
  42. package/src/changelog/types.ts +1 -1
  43. package/src/cli/cmdListWorkspaces.mjs +9 -2
  44. package/src/cli/cmdNetlifyIgnore.mjs +4 -88
  45. package/src/cli/cmdPublish.mjs +51 -14
  46. package/src/cli/cmdPublishCanary.mjs +139 -107
  47. package/src/cli/cmdPublishNewPackage.mjs +27 -6
  48. package/src/cli/cmdVale.mjs +513 -0
  49. package/src/cli/cmdVale.test.mjs +644 -0
  50. package/src/cli/index.mjs +2 -0
  51. package/src/eslint/baseConfig.mjs +2 -1
  52. package/src/eslint/docsConfig.mjs +2 -1
  53. package/src/eslint/jsonConfig.mjs +2 -1
  54. package/src/eslint/mui/config.mjs +11 -1
  55. package/src/eslint/testConfig.mjs +2 -1
  56. package/src/estree-typescript.d.ts +1 -1
  57. package/src/untyped-plugins.d.ts +11 -11
  58. package/src/utils/build.test.mjs +546 -575
  59. package/src/utils/pnpm.mjs +192 -3
  60. package/src/utils/pnpm.test.mjs +580 -0
  61. package/src/utils/testUtils.mjs +18 -0
  62. package/src/utils/typescript.test.mjs +249 -272
  63. package/vale/.vale.ini +1 -0
  64. package/vale/styles/MUI/CorrectReferenceAllCases.yml +43 -0
  65. package/vale/styles/MUI/CorrectRererenceCased.yml +14 -0
  66. package/vale/styles/MUI/GoogleLatin.yml +11 -0
  67. package/vale/styles/MUI/MuiBrandName.yml +22 -0
  68. package/vale/styles/MUI/NoBritish.yml +112 -0
  69. package/vale/styles/MUI/NoCompanyName.yml +17 -0
@@ -0,0 +1,580 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { describe, it, expect } from 'vitest';
4
+
5
+ import { makeTempDir } from './testUtils.mjs';
6
+ import { checkPublishDependencies } from './pnpm.mjs';
7
+
8
+ /**
9
+ * Write a package.json file to a temp subdirectory and return the directory path.
10
+ * @param {string} root - Root temp directory
11
+ * @param {string} name - Package subdirectory name
12
+ * @param {object} pkgJson - package.json contents
13
+ * @returns {Promise<string>} Path to the package directory
14
+ */
15
+ async function writePackage(root, name, pkgJson) {
16
+ const pkgDir = path.join(root, name);
17
+ await fs.mkdir(pkgDir, { recursive: true });
18
+ await fs.writeFile(path.join(pkgDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
19
+ return pkgDir;
20
+ }
21
+
22
+ /**
23
+ * @param {string} name
24
+ * @param {string} pkgPath
25
+ * @returns {import('./pnpm.mjs').PublicPackage}
26
+ */
27
+ function publicPkg(name, pkgPath) {
28
+ return { name, version: '1.0.0', path: pkgPath, isPrivate: false };
29
+ }
30
+
31
+ /**
32
+ * @param {string} name
33
+ * @param {string} pkgPath
34
+ * @returns {import('./pnpm.mjs').PrivatePackage}
35
+ */
36
+ function privatePkg(name, pkgPath) {
37
+ return { name, version: '1.0.0', path: pkgPath, isPrivate: true };
38
+ }
39
+
40
+ /**
41
+ * Build the workspace maps expected by checkPublishDependencies.
42
+ * @param {(import('./pnpm.mjs').PublicPackage | import('./pnpm.mjs').PrivatePackage)[]} allPkgs
43
+ */
44
+ function workspaceMaps(allPkgs) {
45
+ /** @type {Map<string, import('./pnpm.mjs').PublicPackage | import('./pnpm.mjs').PrivatePackage>} */
46
+ const byName = new Map(allPkgs.flatMap((p) => (p.name ? [[p.name, p]] : [])));
47
+ const pathByName = new Map(allPkgs.flatMap((p) => (p.name ? [[p.name, p.path]] : [])));
48
+ return { byName, pathByName };
49
+ }
50
+
51
+ describe('checkPublishDependencies', () => {
52
+ describe('workspace: protocol in dependencies', () => {
53
+ it('returns no issues when all workspace: dependencies are included in the publish set', async () => {
54
+ const root = await makeTempDir();
55
+ const aDir = await writePackage(root, 'pkg-a', {
56
+ name: '@scope/pkg-a',
57
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
58
+ });
59
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
60
+
61
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
62
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
63
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
64
+
65
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
66
+ expect(issues).toEqual([]);
67
+ });
68
+
69
+ it('reports an issue when a workspace: dependency is missing from the publish set', async () => {
70
+ const root = await makeTempDir();
71
+ const aDir = await writePackage(root, 'pkg-a', {
72
+ name: '@scope/pkg-a',
73
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
74
+ });
75
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
76
+
77
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
78
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
79
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
80
+
81
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
82
+ expect(issues).toHaveLength(1);
83
+ expect(issues[0]).toContain('@scope/pkg-b');
84
+ expect(issues[0]).toContain('Add them to the --filter list');
85
+ });
86
+
87
+ it('reports an issue when a workspace: dependency is private', async () => {
88
+ const root = await makeTempDir();
89
+ const aDir = await writePackage(root, 'pkg-a', {
90
+ name: '@scope/pkg-a',
91
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
92
+ });
93
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b', private: true });
94
+
95
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
96
+ const pkgB = privatePkg('@scope/pkg-b', bDir);
97
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
98
+
99
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
100
+ expect(issues).toHaveLength(1);
101
+ expect(issues[0]).toContain('@scope/pkg-b');
102
+ expect(issues[0]).toContain('private');
103
+ });
104
+
105
+ it('resolves transitive workspace: dependencies', async () => {
106
+ const root = await makeTempDir();
107
+ const aDir = await writePackage(root, 'pkg-a', {
108
+ name: '@scope/pkg-a',
109
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
110
+ });
111
+ const bDir = await writePackage(root, 'pkg-b', {
112
+ name: '@scope/pkg-b',
113
+ dependencies: { '@scope/pkg-c': 'workspace:*' },
114
+ });
115
+ const cDir = await writePackage(root, 'pkg-c', { name: '@scope/pkg-c' });
116
+
117
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
118
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
119
+ const pkgC = publicPkg('@scope/pkg-c', cDir);
120
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB, pkgC]);
121
+
122
+ // publishing only A and B — C is missing but transitively required
123
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
124
+ expect(issues).toHaveLength(1);
125
+ expect(issues[0]).toContain('@scope/pkg-c');
126
+
127
+ // publishing all three — no issues
128
+ const { issues: noIssues } = await checkPublishDependencies(
129
+ [pkgA, pkgB, pkgC],
130
+ byName,
131
+ pathByName,
132
+ );
133
+ expect(noIssues).toEqual([]);
134
+ });
135
+ });
136
+
137
+ describe('peerDependencies are never hard requirements', () => {
138
+ it('does not require a peer dependency even when using workspace: protocol', async () => {
139
+ const root = await makeTempDir();
140
+ const aDir = await writePackage(root, 'pkg-a', {
141
+ name: '@scope/pkg-a',
142
+ peerDependencies: { '@scope/pkg-b': 'workspace:*' },
143
+ });
144
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
145
+
146
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
147
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
148
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
149
+
150
+ // publishing only A — B is a workspace: peer dep but must NOT be required
151
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
152
+ expect(issues).toEqual([]);
153
+ });
154
+
155
+ it('does not require a peer dependency with a pinned version', async () => {
156
+ const root = await makeTempDir();
157
+ const aDir = await writePackage(root, 'pkg-a', {
158
+ name: '@scope/pkg-a',
159
+ peerDependencies: { '@scope/pkg-b': '^1.0.0' },
160
+ });
161
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
162
+
163
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
164
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
165
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
166
+
167
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
168
+ expect(issues).toEqual([]);
169
+ });
170
+
171
+ it('does not require a private peer dependency', async () => {
172
+ const root = await makeTempDir();
173
+ const aDir = await writePackage(root, 'pkg-a', {
174
+ name: '@scope/pkg-a',
175
+ peerDependencies: { '@scope/pkg-b': 'workspace:*' },
176
+ });
177
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b', private: true });
178
+
179
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
180
+ const pkgB = privatePkg('@scope/pkg-b', bDir);
181
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
182
+
183
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
184
+ expect(issues).toEqual([]);
185
+ });
186
+ });
187
+
188
+ describe('workspace:^ protocol in dependencies', () => {
189
+ it('requires a workspace:^ dependency that is missing from the publish set', async () => {
190
+ const root = await makeTempDir();
191
+ const aDir = await writePackage(root, 'pkg-a', {
192
+ name: '@scope/pkg-a',
193
+ dependencies: { '@scope/pkg-b': 'workspace:^' },
194
+ });
195
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
196
+
197
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
198
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
199
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
200
+
201
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
202
+ expect(issues).toHaveLength(1);
203
+ expect(issues[0]).toContain('@scope/pkg-b');
204
+ });
205
+
206
+ it('returns no issues when a workspace:^ dependency is included in the publish set', async () => {
207
+ const root = await makeTempDir();
208
+ const aDir = await writePackage(root, 'pkg-a', {
209
+ name: '@scope/pkg-a',
210
+ dependencies: { '@scope/pkg-b': 'workspace:^' },
211
+ });
212
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
213
+
214
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
215
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
216
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
217
+
218
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
219
+ expect(issues).toEqual([]);
220
+ });
221
+ });
222
+
223
+ describe('devDependencies are never hard requirements', () => {
224
+ it('does not require a workspace: devDependency missing from the publish set', async () => {
225
+ const root = await makeTempDir();
226
+ const aDir = await writePackage(root, 'pkg-a', {
227
+ name: '@scope/pkg-a',
228
+ devDependencies: { '@scope/pkg-b': 'workspace:*' },
229
+ });
230
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
231
+
232
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
233
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
234
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
235
+
236
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
237
+ expect(issues).toEqual([]);
238
+ });
239
+
240
+ it('does not require a workspace:^ devDependency missing from the publish set', async () => {
241
+ const root = await makeTempDir();
242
+ const aDir = await writePackage(root, 'pkg-a', {
243
+ name: '@scope/pkg-a',
244
+ devDependencies: { '@scope/pkg-b': 'workspace:^' },
245
+ });
246
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
247
+
248
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
249
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
250
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
251
+
252
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
253
+ expect(issues).toEqual([]);
254
+ });
255
+
256
+ it('does not require a private workspace: devDependency', async () => {
257
+ const root = await makeTempDir();
258
+ const aDir = await writePackage(root, 'pkg-a', {
259
+ name: '@scope/pkg-a',
260
+ devDependencies: { '@scope/pkg-b': 'workspace:*' },
261
+ });
262
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b', private: true });
263
+
264
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
265
+ const pkgB = privatePkg('@scope/pkg-b', bDir);
266
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
267
+
268
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
269
+ expect(issues).toEqual([]);
270
+ });
271
+ });
272
+
273
+ describe('pinned versions in dependencies are not hard requirements', () => {
274
+ it('does not require a workspace package referenced with a pinned version in dependencies', async () => {
275
+ const root = await makeTempDir();
276
+ const aDir = await writePackage(root, 'pkg-a', {
277
+ name: '@scope/pkg-a',
278
+ dependencies: { '@scope/pkg-b': '^1.0.0' },
279
+ });
280
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
281
+
282
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
283
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
284
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
285
+
286
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
287
+ expect(issues).toEqual([]);
288
+ });
289
+ });
290
+
291
+ describe('mixed dependency types', () => {
292
+ it('requires workspace: dependencies but not workspace: peers from the same package', async () => {
293
+ const root = await makeTempDir();
294
+ const aDir = await writePackage(root, 'pkg-a', {
295
+ name: '@scope/pkg-a',
296
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
297
+ peerDependencies: { '@scope/pkg-c': 'workspace:*' },
298
+ });
299
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
300
+ const cDir = await writePackage(root, 'pkg-c', { name: '@scope/pkg-c' });
301
+
302
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
303
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
304
+ const pkgC = publicPkg('@scope/pkg-c', cDir);
305
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB, pkgC]);
306
+
307
+ // B is required (workspace: dep), C is not (workspace: peer)
308
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
309
+ expect(issues).toEqual([]);
310
+
311
+ // Omitting B should flag it
312
+ const { issues: missingB } = await checkPublishDependencies([pkgA], byName, pathByName);
313
+ expect(missingB).toHaveLength(1);
314
+ expect(missingB[0]).toContain('@scope/pkg-b');
315
+ });
316
+
317
+ it('does not traverse peer deps when resolving transitive requirements', async () => {
318
+ const root = await makeTempDir();
319
+ // A depends on B (workspace:), B has C as a peer (workspace:)
320
+ // C should NOT be required just because B peers it
321
+ const aDir = await writePackage(root, 'pkg-a', {
322
+ name: '@scope/pkg-a',
323
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
324
+ });
325
+ const bDir = await writePackage(root, 'pkg-b', {
326
+ name: '@scope/pkg-b',
327
+ peerDependencies: { '@scope/pkg-c': 'workspace:*' },
328
+ });
329
+ const cDir = await writePackage(root, 'pkg-c', { name: '@scope/pkg-c' });
330
+
331
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
332
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
333
+ const pkgC = publicPkg('@scope/pkg-c', cDir);
334
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB, pkgC]);
335
+
336
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
337
+ expect(issues).toEqual([]);
338
+ });
339
+ });
340
+
341
+ describe('material-ui packages-internal workspace simulation', () => {
342
+ /**
343
+ * Mirrors the real package structure from material-ui packages-internal/* plus the
344
+ * packages/* that appear as workspace deps.
345
+ *
346
+ * Public packages-internal:
347
+ * @mui/internal-core-docs – deps: @mui/internal-markdown (workspace:^)
348
+ * – devDeps: @mui-internal/api-docs-builder (workspace:*),
349
+ * @mui/icons-material (workspace:*), @mui/material (workspace:*)
350
+ * – peers: @mui/material, @mui/icons-material, @mui/system, … (pinned ranges)
351
+ * @mui/internal-docs-utils – no workspace deps
352
+ * @mui/internal-markdown – no workspace deps
353
+ * @mui/internal-scripts – deps: @mui/internal-docs-utils (workspace:^)
354
+ *
355
+ * Private packages-internal:
356
+ * @mui-internal/api-docs-builder – deps: @mui/internal-docs-utils (workspace:^),
357
+ * @mui/internal-markdown (workspace:^)
358
+ * @mui-internal/api-docs-builder-core – deps: @mui-internal/api-docs-builder (workspace:^),
359
+ * @mui/internal-markdown (workspace:^)
360
+ * @mui/internal-waterfall – no workspace deps
361
+ *
362
+ * Public packages/* (not in the --filter set, resolve from registry):
363
+ * @mui/material, @mui/icons-material, @mui/system, @mui/utils, @mui/material-nextjs,
364
+ * @mui/stylis-plugin-rtl, @mui/core-downloads-tracker, @mui/types,
365
+ * @mui/material-pigment-css, @mui/private-theming, @mui/styled-engine
366
+ */
367
+ /** @param {string} root */
368
+ async function buildMaterialUiWorkspace(root) {
369
+ // packages-internal — public
370
+ const coreDocs = await writePackage(root, 'packages-internal/core-docs', {
371
+ name: '@mui/internal-core-docs',
372
+ version: '9.0.0-beta.1',
373
+ dependencies: {
374
+ '@babel/runtime': '^7.29.2',
375
+ '@mui/internal-markdown': 'workspace:^',
376
+ 'clipboard-copy': '^4.0.1',
377
+ clsx: '^2.1.1',
378
+ },
379
+ devDependencies: {
380
+ '@mui-internal/api-docs-builder': 'workspace:*',
381
+ '@mui/icons-material': 'workspace:*',
382
+ '@mui/material': 'workspace:*',
383
+ },
384
+ peerDependencies: {
385
+ '@mui/base': '^5.0.0 || ^7.0.0',
386
+ '@mui/icons-material': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
387
+ '@mui/material': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
388
+ '@mui/material-nextjs': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
389
+ '@mui/stylis-plugin-rtl': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
390
+ '@mui/system': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
391
+ '@mui/utils': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
392
+ react: '^17.0.0 || ^18.0.0 || ^19.0.0',
393
+ },
394
+ });
395
+ const docsUtils = await writePackage(root, 'packages-internal/docs-utils', {
396
+ name: '@mui/internal-docs-utils',
397
+ version: '3.0.2',
398
+ dependencies: { rimraf: '^6.1.3', typescript: '^5.9.3' },
399
+ });
400
+ const markdown = await writePackage(root, 'packages-internal/markdown', {
401
+ name: '@mui/internal-markdown',
402
+ version: '3.0.6',
403
+ dependencies: { '@babel/runtime': '^7.29.2', marked: '^17.0.5', prismjs: '^1.30.0' },
404
+ });
405
+ const scripts = await writePackage(root, 'packages-internal/scripts', {
406
+ name: '@mui/internal-scripts',
407
+ version: '3.0.5',
408
+ dependencies: {
409
+ '@mui/internal-docs-utils': 'workspace:^',
410
+ '@babel/core': '^7.29.0',
411
+ doctrine: '^3.0.0',
412
+ },
413
+ });
414
+
415
+ // packages-internal — private
416
+ const apiDocsBuilder = await writePackage(root, 'packages-internal/api-docs-builder', {
417
+ name: '@mui-internal/api-docs-builder',
418
+ version: '1.0.0',
419
+ private: true,
420
+ dependencies: {
421
+ '@mui/internal-docs-utils': 'workspace:^',
422
+ '@mui/internal-markdown': 'workspace:^',
423
+ '@babel/core': '^7.29.0',
424
+ },
425
+ });
426
+ const apiDocsBuilderCore = await writePackage(
427
+ root,
428
+ 'packages-internal/api-docs-builder-core',
429
+ {
430
+ name: '@mui-internal/api-docs-builder-core',
431
+ version: '1.0.0',
432
+ private: true,
433
+ dependencies: {
434
+ '@mui-internal/api-docs-builder': 'workspace:^',
435
+ '@mui/internal-markdown': 'workspace:^',
436
+ },
437
+ },
438
+ );
439
+ const waterfall = await writePackage(root, 'packages-internal/waterfall', {
440
+ name: '@mui/internal-waterfall',
441
+ version: '1.0.0',
442
+ private: true,
443
+ });
444
+
445
+ // packages/* — public, resolve from registry (not in the --filter set)
446
+ const material = await writePackage(root, 'packages/material', {
447
+ name: '@mui/material',
448
+ version: '9.0.0-beta.1',
449
+ });
450
+ const iconsM = await writePackage(root, 'packages/icons-material', {
451
+ name: '@mui/icons-material',
452
+ version: '9.0.0-beta.1',
453
+ });
454
+ const muiSystem = await writePackage(root, 'packages/system', {
455
+ name: '@mui/system',
456
+ version: '9.0.0-beta.1',
457
+ });
458
+ const muiUtils = await writePackage(root, 'packages/utils', {
459
+ name: '@mui/utils',
460
+ version: '9.0.0-beta.1',
461
+ });
462
+ const materialNextjs = await writePackage(root, 'packages/material-nextjs', {
463
+ name: '@mui/material-nextjs',
464
+ version: '9.0.0-beta.0',
465
+ });
466
+ const stylisPluginRtl = await writePackage(root, 'packages/stylis-plugin-rtl', {
467
+ name: '@mui/stylis-plugin-rtl',
468
+ version: '9.0.0-beta.0',
469
+ });
470
+
471
+ const publicInternalPkgs = [
472
+ publicPkg('@mui/internal-core-docs', coreDocs),
473
+ publicPkg('@mui/internal-docs-utils', docsUtils),
474
+ publicPkg('@mui/internal-markdown', markdown),
475
+ publicPkg('@mui/internal-scripts', scripts),
476
+ ];
477
+ const privateInternalPkgs = [
478
+ privatePkg('@mui-internal/api-docs-builder', apiDocsBuilder),
479
+ privatePkg('@mui-internal/api-docs-builder-core', apiDocsBuilderCore),
480
+ privatePkg('@mui/internal-waterfall', waterfall),
481
+ ];
482
+ const publicMainPkgs = [
483
+ publicPkg('@mui/material', material),
484
+ publicPkg('@mui/icons-material', iconsM),
485
+ publicPkg('@mui/system', muiSystem),
486
+ publicPkg('@mui/utils', muiUtils),
487
+ publicPkg('@mui/material-nextjs', materialNextjs),
488
+ publicPkg('@mui/stylis-plugin-rtl', stylisPluginRtl),
489
+ ];
490
+
491
+ const allPkgs = [...publicInternalPkgs, ...privateInternalPkgs, ...publicMainPkgs];
492
+ return { publicInternalPkgs, privateInternalPkgs, publicMainPkgs, allPkgs };
493
+ }
494
+
495
+ it('passes with no issues when publishing all public packages-internal packages', async () => {
496
+ const root = await makeTempDir();
497
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
498
+ const { byName, pathByName } = workspaceMaps(allPkgs);
499
+
500
+ const { issues } = await checkPublishDependencies(publicInternalPkgs, byName, pathByName);
501
+ expect(issues).toEqual([]);
502
+ });
503
+
504
+ it('passes when @mui/material and other pinned-range peers of core-docs are not in the publish set', async () => {
505
+ const root = await makeTempDir();
506
+ const { publicInternalPkgs, privateInternalPkgs } = await buildMaterialUiWorkspace(root);
507
+ // workspace without the packages/* — simulates --filter "./packages-internal/*"
508
+ const filteredWorkspace = [...publicInternalPkgs, ...privateInternalPkgs];
509
+ const { byName, pathByName } = workspaceMaps(filteredWorkspace);
510
+
511
+ const { issues } = await checkPublishDependencies(publicInternalPkgs, byName, pathByName);
512
+ expect(issues).toEqual([]);
513
+ });
514
+
515
+ it('passes when workspace:* devDependencies of core-docs are not in the publish set', async () => {
516
+ // core-docs has @mui-internal/api-docs-builder and @mui/icons-material as workspace:*
517
+ // devDependencies. They must NOT be required — devDeps are not installed on consumer devices.
518
+ const root = await makeTempDir();
519
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
520
+ const { byName, pathByName } = workspaceMaps(allPkgs);
521
+
522
+ const { issues } = await checkPublishDependencies(publicInternalPkgs, byName, pathByName);
523
+ expect(issues).toEqual([]);
524
+ });
525
+
526
+ it('flags @mui/internal-markdown as missing when core-docs is published without it', async () => {
527
+ const root = await makeTempDir();
528
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
529
+ const { byName, pathByName } = workspaceMaps(allPkgs);
530
+
531
+ const withoutMarkdown = publicInternalPkgs.filter((p) => p.name !== '@mui/internal-markdown');
532
+ const { issues } = await checkPublishDependencies(withoutMarkdown, byName, pathByName);
533
+ expect(issues).toHaveLength(1);
534
+ expect(issues[0]).toContain('@mui/internal-markdown');
535
+ });
536
+
537
+ it('flags @mui/internal-docs-utils as missing when scripts is published without it', async () => {
538
+ const root = await makeTempDir();
539
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
540
+ const { byName, pathByName } = workspaceMaps(allPkgs);
541
+
542
+ const withoutDocsUtils = publicInternalPkgs.filter(
543
+ (p) => p.name !== '@mui/internal-docs-utils',
544
+ );
545
+ const { issues } = await checkPublishDependencies(withoutDocsUtils, byName, pathByName);
546
+ expect(issues).toHaveLength(1);
547
+ expect(issues[0]).toContain('@mui/internal-docs-utils');
548
+ });
549
+
550
+ it('flags both missing workspace: deps when core-docs and scripts lack their deps', async () => {
551
+ const root = await makeTempDir();
552
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
553
+ const { byName, pathByName } = workspaceMaps(allPkgs);
554
+
555
+ const onlyCoreDocs = publicInternalPkgs.filter(
556
+ (p) => p.name !== '@mui/internal-markdown' && p.name !== '@mui/internal-docs-utils',
557
+ );
558
+ const { issues } = await checkPublishDependencies(onlyCoreDocs, byName, pathByName);
559
+ expect(issues).toHaveLength(1); // single issue listing both missing packages
560
+ expect(issues[0]).toContain('@mui/internal-markdown');
561
+ expect(issues[0]).toContain('@mui/internal-docs-utils');
562
+ });
563
+ });
564
+
565
+ describe('packages not in the workspace', () => {
566
+ it('ignores dependencies that are not workspace packages', async () => {
567
+ const root = await makeTempDir();
568
+ const aDir = await writePackage(root, 'pkg-a', {
569
+ name: '@scope/pkg-a',
570
+ dependencies: { react: '^18.0.0', lodash: '^4.0.0' },
571
+ });
572
+
573
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
574
+ const { byName, pathByName } = workspaceMaps([pkgA]);
575
+
576
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
577
+ expect(issues).toEqual([]);
578
+ });
579
+ });
580
+ });
@@ -0,0 +1,18 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { onTestFinished } from 'vitest';
5
+
6
+ /**
7
+ * Creates a temporary directory and registers an `onTestFinished` hook to
8
+ * remove it automatically when the current test ends — even if the test throws.
9
+ *
10
+ * @returns {Promise<string>} The path of the created temporary directory.
11
+ */
12
+ export async function makeTempDir() {
13
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'code-infra-test-'));
14
+ onTestFinished(async () => {
15
+ await fs.rm(tmpDir, { recursive: true, force: true });
16
+ });
17
+ return tmpDir;
18
+ }