@mui/internal-code-infra 0.0.4-canary.7 → 0.0.4-canary.70

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 (120) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +19 -8
  3. package/build/babel-config.d.mts +11 -3
  4. package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
  5. package/build/brokenLinksChecker/index.d.mts +45 -2
  6. package/build/changelog/types.d.ts +1 -1
  7. package/build/cli/cmdArgosPush.d.mts +2 -2
  8. package/build/cli/cmdBuild.d.mts +3 -2
  9. package/build/cli/cmdCopyFiles.d.mts +2 -2
  10. package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
  11. package/build/cli/cmdGenerateChangelog.d.mts +2 -2
  12. package/build/cli/cmdGithubAuth.d.mts +2 -2
  13. package/build/cli/cmdListWorkspaces.d.mts +6 -4
  14. package/build/cli/cmdNetlifyIgnore.d.mts +3 -2
  15. package/build/cli/cmdPublish.d.mts +4 -2
  16. package/build/cli/cmdPublishCanary.d.mts +3 -3
  17. package/build/cli/cmdPublishNewPackage.d.mts +4 -2
  18. package/build/cli/cmdSetVersionOverrides.d.mts +2 -2
  19. package/build/cli/cmdVale.d.mts +46 -0
  20. package/build/cli/cmdValidateBuiltTypes.d.mts +2 -2
  21. package/build/eslint/baseConfig.d.mts +3 -1
  22. package/build/eslint/mui/rules/disallow-react-api-in-server-components.d.mts +2 -2
  23. package/build/eslint/mui/rules/docgen-ignore-before-comment.d.mts +2 -2
  24. package/build/eslint/mui/rules/no-floating-cleanup.d.mts +5 -0
  25. package/build/eslint/mui/rules/no-guarded-throw.d.mts +31 -0
  26. package/build/eslint/mui/rules/no-presentation-role.d.mts +5 -0
  27. package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +2 -2
  28. package/build/eslint/mui/rules/nodeEnvUtils.d.mts +18 -0
  29. package/build/remark/config.d.mts +43 -0
  30. package/build/remark/createLintTester.d.mts +10 -0
  31. package/build/remark/firstBlockHeading.d.mts +4 -0
  32. package/build/remark/gitDiff.d.mts +2 -0
  33. package/build/remark/noSpaceInLinks.d.mts +2 -0
  34. package/build/remark/straightQuotes.d.mts +2 -0
  35. package/build/remark/tableAlignment.d.mts +2 -0
  36. package/build/remark/terminalLanguage.d.mts +2 -0
  37. package/build/utils/babel.d.mts +1 -1
  38. package/build/utils/build.d.mts +56 -37
  39. package/build/utils/github.d.mts +1 -1
  40. package/build/utils/pnpm.d.mts +81 -2
  41. package/build/utils/testUtils.d.mts +7 -0
  42. package/build/utils/typescript.d.mts +6 -16
  43. package/package.json +60 -38
  44. package/src/babel-config.mjs +9 -3
  45. package/src/brokenLinksChecker/__fixtures__/static-site/index.html +1 -0
  46. package/src/brokenLinksChecker/__fixtures__/static-site/invalid-html.html +15 -0
  47. package/src/brokenLinksChecker/crawlWorker.mjs +217 -0
  48. package/src/brokenLinksChecker/index.mjs +263 -188
  49. package/src/brokenLinksChecker/index.test.ts +50 -13
  50. package/src/changelog/categorizeCommits.test.ts +5 -5
  51. package/src/changelog/fetchChangelogs.mjs +6 -2
  52. package/src/changelog/parseCommitLabels.test.ts +5 -5
  53. package/src/changelog/renderChangelog.mjs +1 -1
  54. package/src/changelog/types.ts +1 -1
  55. package/src/cli/cmdBuild.mjs +51 -85
  56. package/src/cli/cmdListWorkspaces.mjs +12 -27
  57. package/src/cli/cmdNetlifyIgnore.mjs +34 -93
  58. package/src/cli/cmdPublish.mjs +55 -15
  59. package/src/cli/cmdPublishCanary.mjs +128 -132
  60. package/src/cli/cmdPublishNewPackage.mjs +27 -6
  61. package/src/cli/cmdSetVersionOverrides.mjs +8 -9
  62. package/src/cli/cmdVale.mjs +513 -0
  63. package/src/cli/cmdVale.test.mjs +644 -0
  64. package/src/cli/index.mjs +2 -0
  65. package/src/cli/packageJson.d.ts +1 -1
  66. package/src/eslint/baseConfig.mjs +51 -19
  67. package/src/eslint/docsConfig.mjs +2 -1
  68. package/src/eslint/mui/config.mjs +22 -3
  69. package/src/eslint/mui/index.mjs +6 -0
  70. package/src/eslint/mui/rules/no-floating-cleanup.mjs +187 -0
  71. package/src/eslint/mui/rules/no-floating-cleanup.test.mjs +141 -0
  72. package/src/eslint/mui/rules/no-guarded-throw.mjs +115 -0
  73. package/src/eslint/mui/rules/no-guarded-throw.test.mjs +206 -0
  74. package/src/eslint/mui/rules/no-presentation-role.mjs +60 -0
  75. package/src/eslint/mui/rules/no-presentation-role.test.mjs +33 -0
  76. package/src/eslint/mui/rules/nodeEnvUtils.mjs +52 -0
  77. package/src/eslint/mui/rules/require-dev-wrapper.mjs +25 -40
  78. package/src/estree-typescript.d.ts +1 -1
  79. package/src/remark/config.mjs +157 -0
  80. package/src/remark/createLintTester.mjs +19 -0
  81. package/src/remark/firstBlockHeading.mjs +87 -0
  82. package/src/remark/firstBlockHeading.test.mjs +107 -0
  83. package/src/remark/gitDiff.mjs +43 -0
  84. package/src/remark/gitDiff.test.mjs +45 -0
  85. package/src/remark/noSpaceInLinks.mjs +42 -0
  86. package/src/remark/noSpaceInLinks.test.mjs +22 -0
  87. package/src/remark/straightQuotes.mjs +31 -0
  88. package/src/remark/straightQuotes.test.mjs +25 -0
  89. package/src/remark/tableAlignment.mjs +23 -0
  90. package/src/remark/tableAlignment.test.mjs +28 -0
  91. package/src/remark/terminalLanguage.mjs +19 -0
  92. package/src/remark/terminalLanguage.test.mjs +17 -0
  93. package/src/untyped-plugins.d.ts +11 -11
  94. package/src/utils/build.mjs +604 -270
  95. package/src/utils/build.test.mjs +1079 -521
  96. package/src/utils/git.mjs +16 -7
  97. package/src/utils/pnpm.mjs +255 -3
  98. package/src/utils/pnpm.test.mjs +731 -0
  99. package/src/utils/testUtils.mjs +18 -0
  100. package/src/utils/typescript.mjs +23 -42
  101. package/src/utils/typescript.test.mjs +234 -273
  102. package/vale/.vale.ini +1 -0
  103. package/vale/styles/MUI/CorrectReferenceAllCases.yml +43 -0
  104. package/vale/styles/MUI/CorrectRererenceCased.yml +14 -0
  105. package/vale/styles/MUI/GoogleLatin.yml +11 -0
  106. package/vale/styles/MUI/MuiBrandName.yml +22 -0
  107. package/vale/styles/MUI/NoBritish.yml +112 -0
  108. package/vale/styles/MUI/NoCompanyName.yml +17 -0
  109. package/build/markdownlint/duplicate-h1.d.mts +0 -56
  110. package/build/markdownlint/git-diff.d.mts +0 -11
  111. package/build/markdownlint/index.d.mts +0 -52
  112. package/build/markdownlint/straight-quotes.d.mts +0 -11
  113. package/build/markdownlint/table-alignment.d.mts +0 -11
  114. package/build/markdownlint/terminal-language.d.mts +0 -11
  115. package/src/markdownlint/duplicate-h1.mjs +0 -69
  116. package/src/markdownlint/git-diff.mjs +0 -31
  117. package/src/markdownlint/index.mjs +0 -62
  118. package/src/markdownlint/straight-quotes.mjs +0 -26
  119. package/src/markdownlint/table-alignment.mjs +0 -42
  120. package/src/markdownlint/terminal-language.mjs +0 -19
@@ -0,0 +1,731 @@
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 {
7
+ checkPublishDependencies,
8
+ readPackageJson,
9
+ writePackageJson,
10
+ writeOverridesToWorkspace,
11
+ } from './pnpm.mjs';
12
+
13
+ /**
14
+ * Write a package.json file to a temp subdirectory and return the directory path.
15
+ * @param {string} root - Root temp directory
16
+ * @param {string} name - Package subdirectory name
17
+ * @param {object} pkgJson - package.json contents
18
+ * @returns {Promise<string>} Path to the package directory
19
+ */
20
+ async function writePackage(root, name, pkgJson) {
21
+ const pkgDir = path.join(root, name);
22
+ await fs.mkdir(pkgDir, { recursive: true });
23
+ await fs.writeFile(path.join(pkgDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
24
+ return pkgDir;
25
+ }
26
+
27
+ /**
28
+ * @param {string} name
29
+ * @param {string} pkgPath
30
+ * @returns {import('./pnpm.mjs').PublicPackage}
31
+ */
32
+ function publicPkg(name, pkgPath) {
33
+ return { name, version: '1.0.0', path: pkgPath, isPrivate: false };
34
+ }
35
+
36
+ /**
37
+ * @param {string} name
38
+ * @param {string} pkgPath
39
+ * @returns {import('./pnpm.mjs').PrivatePackage}
40
+ */
41
+ function privatePkg(name, pkgPath) {
42
+ return { name, version: '1.0.0', path: pkgPath, isPrivate: true };
43
+ }
44
+
45
+ /**
46
+ * Build the workspace maps expected by checkPublishDependencies.
47
+ * @param {(import('./pnpm.mjs').PublicPackage | import('./pnpm.mjs').PrivatePackage)[]} allPkgs
48
+ */
49
+ function workspaceMaps(allPkgs) {
50
+ /** @type {Map<string, import('./pnpm.mjs').PublicPackage | import('./pnpm.mjs').PrivatePackage>} */
51
+ const byName = new Map(allPkgs.flatMap((p) => (p.name ? [[p.name, p]] : [])));
52
+ const pathByName = new Map(allPkgs.flatMap((p) => (p.name ? [[p.name, p.path]] : [])));
53
+ return { byName, pathByName };
54
+ }
55
+
56
+ describe('checkPublishDependencies', () => {
57
+ describe('workspace: protocol in dependencies', () => {
58
+ it('returns no issues when all workspace: dependencies are included in the publish set', async () => {
59
+ const root = await makeTempDir();
60
+ const aDir = await writePackage(root, 'pkg-a', {
61
+ name: '@scope/pkg-a',
62
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
63
+ });
64
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
65
+
66
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
67
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
68
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
69
+
70
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
71
+ expect(issues).toEqual([]);
72
+ });
73
+
74
+ it('reports an issue when a workspace: dependency is missing from the publish set', async () => {
75
+ const root = await makeTempDir();
76
+ const aDir = await writePackage(root, 'pkg-a', {
77
+ name: '@scope/pkg-a',
78
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
79
+ });
80
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
81
+
82
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
83
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
84
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
85
+
86
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
87
+ expect(issues).toHaveLength(1);
88
+ expect(issues[0]).toContain('@scope/pkg-b');
89
+ expect(issues[0]).toContain('Add them to the --filter list');
90
+ });
91
+
92
+ it('reports an issue when a workspace: dependency is private', async () => {
93
+ const root = await makeTempDir();
94
+ const aDir = await writePackage(root, 'pkg-a', {
95
+ name: '@scope/pkg-a',
96
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
97
+ });
98
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b', private: true });
99
+
100
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
101
+ const pkgB = privatePkg('@scope/pkg-b', bDir);
102
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
103
+
104
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
105
+ expect(issues).toHaveLength(1);
106
+ expect(issues[0]).toContain('@scope/pkg-b');
107
+ expect(issues[0]).toContain('private');
108
+ });
109
+
110
+ it('resolves transitive workspace: dependencies', async () => {
111
+ const root = await makeTempDir();
112
+ const aDir = await writePackage(root, 'pkg-a', {
113
+ name: '@scope/pkg-a',
114
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
115
+ });
116
+ const bDir = await writePackage(root, 'pkg-b', {
117
+ name: '@scope/pkg-b',
118
+ dependencies: { '@scope/pkg-c': 'workspace:*' },
119
+ });
120
+ const cDir = await writePackage(root, 'pkg-c', { name: '@scope/pkg-c' });
121
+
122
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
123
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
124
+ const pkgC = publicPkg('@scope/pkg-c', cDir);
125
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB, pkgC]);
126
+
127
+ // publishing only A and B — C is missing but transitively required
128
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
129
+ expect(issues).toHaveLength(1);
130
+ expect(issues[0]).toContain('@scope/pkg-c');
131
+
132
+ // publishing all three — no issues
133
+ const { issues: noIssues } = await checkPublishDependencies(
134
+ [pkgA, pkgB, pkgC],
135
+ byName,
136
+ pathByName,
137
+ );
138
+ expect(noIssues).toEqual([]);
139
+ });
140
+ });
141
+
142
+ describe('peerDependencies are never hard requirements', () => {
143
+ it('does not require a peer dependency even when using workspace: protocol', async () => {
144
+ const root = await makeTempDir();
145
+ const aDir = await writePackage(root, 'pkg-a', {
146
+ name: '@scope/pkg-a',
147
+ peerDependencies: { '@scope/pkg-b': 'workspace:*' },
148
+ });
149
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
150
+
151
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
152
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
153
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
154
+
155
+ // publishing only A — B is a workspace: peer dep but must NOT be required
156
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
157
+ expect(issues).toEqual([]);
158
+ });
159
+
160
+ it('does not require a peer dependency with a pinned version', async () => {
161
+ const root = await makeTempDir();
162
+ const aDir = await writePackage(root, 'pkg-a', {
163
+ name: '@scope/pkg-a',
164
+ peerDependencies: { '@scope/pkg-b': '^1.0.0' },
165
+ });
166
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
167
+
168
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
169
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
170
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
171
+
172
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
173
+ expect(issues).toEqual([]);
174
+ });
175
+
176
+ it('does not require a private peer dependency', async () => {
177
+ const root = await makeTempDir();
178
+ const aDir = await writePackage(root, 'pkg-a', {
179
+ name: '@scope/pkg-a',
180
+ peerDependencies: { '@scope/pkg-b': 'workspace:*' },
181
+ });
182
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b', private: true });
183
+
184
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
185
+ const pkgB = privatePkg('@scope/pkg-b', bDir);
186
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
187
+
188
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
189
+ expect(issues).toEqual([]);
190
+ });
191
+ });
192
+
193
+ describe('workspace:^ protocol in dependencies', () => {
194
+ it('requires a workspace:^ dependency that is missing from the publish set', async () => {
195
+ const root = await makeTempDir();
196
+ const aDir = await writePackage(root, 'pkg-a', {
197
+ name: '@scope/pkg-a',
198
+ dependencies: { '@scope/pkg-b': 'workspace:^' },
199
+ });
200
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
201
+
202
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
203
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
204
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
205
+
206
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
207
+ expect(issues).toHaveLength(1);
208
+ expect(issues[0]).toContain('@scope/pkg-b');
209
+ });
210
+
211
+ it('returns no issues when a workspace:^ dependency is included in the publish set', async () => {
212
+ const root = await makeTempDir();
213
+ const aDir = await writePackage(root, 'pkg-a', {
214
+ name: '@scope/pkg-a',
215
+ dependencies: { '@scope/pkg-b': 'workspace:^' },
216
+ });
217
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
218
+
219
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
220
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
221
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
222
+
223
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
224
+ expect(issues).toEqual([]);
225
+ });
226
+ });
227
+
228
+ describe('devDependencies are never hard requirements', () => {
229
+ it('does not require a workspace: devDependency missing from the publish set', async () => {
230
+ const root = await makeTempDir();
231
+ const aDir = await writePackage(root, 'pkg-a', {
232
+ name: '@scope/pkg-a',
233
+ devDependencies: { '@scope/pkg-b': 'workspace:*' },
234
+ });
235
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
236
+
237
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
238
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
239
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
240
+
241
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
242
+ expect(issues).toEqual([]);
243
+ });
244
+
245
+ it('does not require a workspace:^ devDependency missing from the publish set', async () => {
246
+ const root = await makeTempDir();
247
+ const aDir = await writePackage(root, 'pkg-a', {
248
+ name: '@scope/pkg-a',
249
+ devDependencies: { '@scope/pkg-b': 'workspace:^' },
250
+ });
251
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
252
+
253
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
254
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
255
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
256
+
257
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
258
+ expect(issues).toEqual([]);
259
+ });
260
+
261
+ it('does not require a private workspace: devDependency', async () => {
262
+ const root = await makeTempDir();
263
+ const aDir = await writePackage(root, 'pkg-a', {
264
+ name: '@scope/pkg-a',
265
+ devDependencies: { '@scope/pkg-b': 'workspace:*' },
266
+ });
267
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b', private: true });
268
+
269
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
270
+ const pkgB = privatePkg('@scope/pkg-b', bDir);
271
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
272
+
273
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
274
+ expect(issues).toEqual([]);
275
+ });
276
+ });
277
+
278
+ describe('pinned versions in dependencies are not hard requirements', () => {
279
+ it('does not require a workspace package referenced with a pinned version in dependencies', async () => {
280
+ const root = await makeTempDir();
281
+ const aDir = await writePackage(root, 'pkg-a', {
282
+ name: '@scope/pkg-a',
283
+ dependencies: { '@scope/pkg-b': '^1.0.0' },
284
+ });
285
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
286
+
287
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
288
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
289
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB]);
290
+
291
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
292
+ expect(issues).toEqual([]);
293
+ });
294
+ });
295
+
296
+ describe('mixed dependency types', () => {
297
+ it('requires workspace: dependencies but not workspace: peers from the same package', async () => {
298
+ const root = await makeTempDir();
299
+ const aDir = await writePackage(root, 'pkg-a', {
300
+ name: '@scope/pkg-a',
301
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
302
+ peerDependencies: { '@scope/pkg-c': 'workspace:*' },
303
+ });
304
+ const bDir = await writePackage(root, 'pkg-b', { name: '@scope/pkg-b' });
305
+ const cDir = await writePackage(root, 'pkg-c', { name: '@scope/pkg-c' });
306
+
307
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
308
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
309
+ const pkgC = publicPkg('@scope/pkg-c', cDir);
310
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB, pkgC]);
311
+
312
+ // B is required (workspace: dep), C is not (workspace: peer)
313
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
314
+ expect(issues).toEqual([]);
315
+
316
+ // Omitting B should flag it
317
+ const { issues: missingB } = await checkPublishDependencies([pkgA], byName, pathByName);
318
+ expect(missingB).toHaveLength(1);
319
+ expect(missingB[0]).toContain('@scope/pkg-b');
320
+ });
321
+
322
+ it('does not traverse peer deps when resolving transitive requirements', async () => {
323
+ const root = await makeTempDir();
324
+ // A depends on B (workspace:), B has C as a peer (workspace:)
325
+ // C should NOT be required just because B peers it
326
+ const aDir = await writePackage(root, 'pkg-a', {
327
+ name: '@scope/pkg-a',
328
+ dependencies: { '@scope/pkg-b': 'workspace:*' },
329
+ });
330
+ const bDir = await writePackage(root, 'pkg-b', {
331
+ name: '@scope/pkg-b',
332
+ peerDependencies: { '@scope/pkg-c': 'workspace:*' },
333
+ });
334
+ const cDir = await writePackage(root, 'pkg-c', { name: '@scope/pkg-c' });
335
+
336
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
337
+ const pkgB = publicPkg('@scope/pkg-b', bDir);
338
+ const pkgC = publicPkg('@scope/pkg-c', cDir);
339
+ const { byName, pathByName } = workspaceMaps([pkgA, pkgB, pkgC]);
340
+
341
+ const { issues } = await checkPublishDependencies([pkgA, pkgB], byName, pathByName);
342
+ expect(issues).toEqual([]);
343
+ });
344
+ });
345
+
346
+ describe('material-ui packages-internal workspace simulation', () => {
347
+ /**
348
+ * Mirrors the real package structure from material-ui packages-internal/* plus the
349
+ * packages/* that appear as workspace deps.
350
+ *
351
+ * Public packages-internal:
352
+ * @mui/internal-core-docs – deps: @mui/internal-markdown (workspace:^)
353
+ * – devDeps: @mui-internal/api-docs-builder (workspace:*),
354
+ * @mui/icons-material (workspace:*), @mui/material (workspace:*)
355
+ * – peers: @mui/material, @mui/icons-material, @mui/system, … (pinned ranges)
356
+ * @mui/internal-docs-utils – no workspace deps
357
+ * @mui/internal-markdown – no workspace deps
358
+ * @mui/internal-scripts – deps: @mui/internal-docs-utils (workspace:^)
359
+ *
360
+ * Private packages-internal:
361
+ * @mui-internal/api-docs-builder – deps: @mui/internal-docs-utils (workspace:^),
362
+ * @mui/internal-markdown (workspace:^)
363
+ * @mui-internal/api-docs-builder-core – deps: @mui-internal/api-docs-builder (workspace:^),
364
+ * @mui/internal-markdown (workspace:^)
365
+ * @mui/internal-waterfall – no workspace deps
366
+ *
367
+ * Public packages/* (not in the --filter set, resolve from registry):
368
+ * @mui/material, @mui/icons-material, @mui/system, @mui/utils, @mui/material-nextjs,
369
+ * @mui/stylis-plugin-rtl, @mui/core-downloads-tracker, @mui/types,
370
+ * @mui/material-pigment-css, @mui/private-theming, @mui/styled-engine
371
+ */
372
+ /** @param {string} root */
373
+ async function buildMaterialUiWorkspace(root) {
374
+ // packages-internal — public
375
+ const coreDocs = await writePackage(root, 'packages-internal/core-docs', {
376
+ name: '@mui/internal-core-docs',
377
+ version: '9.0.0-beta.1',
378
+ dependencies: {
379
+ '@babel/runtime': '^7.29.2',
380
+ '@mui/internal-markdown': 'workspace:^',
381
+ 'clipboard-copy': '^4.0.1',
382
+ clsx: '^2.1.1',
383
+ },
384
+ devDependencies: {
385
+ '@mui-internal/api-docs-builder': 'workspace:*',
386
+ '@mui/icons-material': 'workspace:*',
387
+ '@mui/material': 'workspace:*',
388
+ },
389
+ peerDependencies: {
390
+ '@mui/base': '^5.0.0 || ^7.0.0',
391
+ '@mui/icons-material': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
392
+ '@mui/material': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
393
+ '@mui/material-nextjs': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
394
+ '@mui/stylis-plugin-rtl': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
395
+ '@mui/system': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
396
+ '@mui/utils': '^5.0.0 || ^6.0.0 || ^7.0.0 || ^9.0.0',
397
+ react: '^17.0.0 || ^18.0.0 || ^19.0.0',
398
+ },
399
+ });
400
+ const docsUtils = await writePackage(root, 'packages-internal/docs-utils', {
401
+ name: '@mui/internal-docs-utils',
402
+ version: '3.0.2',
403
+ dependencies: { rimraf: '^6.1.3', typescript: '^5.9.3' },
404
+ });
405
+ const markdown = await writePackage(root, 'packages-internal/markdown', {
406
+ name: '@mui/internal-markdown',
407
+ version: '3.0.6',
408
+ dependencies: { '@babel/runtime': '^7.29.2', marked: '^17.0.5', prismjs: '^1.30.0' },
409
+ });
410
+ const scripts = await writePackage(root, 'packages-internal/scripts', {
411
+ name: '@mui/internal-scripts',
412
+ version: '3.0.5',
413
+ dependencies: {
414
+ '@mui/internal-docs-utils': 'workspace:^',
415
+ '@babel/core': '^7.29.0',
416
+ doctrine: '^3.0.0',
417
+ },
418
+ });
419
+
420
+ // packages-internal — private
421
+ const apiDocsBuilder = await writePackage(root, 'packages-internal/api-docs-builder', {
422
+ name: '@mui-internal/api-docs-builder',
423
+ version: '1.0.0',
424
+ private: true,
425
+ dependencies: {
426
+ '@mui/internal-docs-utils': 'workspace:^',
427
+ '@mui/internal-markdown': 'workspace:^',
428
+ '@babel/core': '^7.29.0',
429
+ },
430
+ });
431
+ const apiDocsBuilderCore = await writePackage(
432
+ root,
433
+ 'packages-internal/api-docs-builder-core',
434
+ {
435
+ name: '@mui-internal/api-docs-builder-core',
436
+ version: '1.0.0',
437
+ private: true,
438
+ dependencies: {
439
+ '@mui-internal/api-docs-builder': 'workspace:^',
440
+ '@mui/internal-markdown': 'workspace:^',
441
+ },
442
+ },
443
+ );
444
+ const waterfall = await writePackage(root, 'packages-internal/waterfall', {
445
+ name: '@mui/internal-waterfall',
446
+ version: '1.0.0',
447
+ private: true,
448
+ });
449
+
450
+ // packages/* — public, resolve from registry (not in the --filter set)
451
+ const material = await writePackage(root, 'packages/material', {
452
+ name: '@mui/material',
453
+ version: '9.0.0-beta.1',
454
+ });
455
+ const iconsM = await writePackage(root, 'packages/icons-material', {
456
+ name: '@mui/icons-material',
457
+ version: '9.0.0-beta.1',
458
+ });
459
+ const muiSystem = await writePackage(root, 'packages/system', {
460
+ name: '@mui/system',
461
+ version: '9.0.0-beta.1',
462
+ });
463
+ const muiUtils = await writePackage(root, 'packages/utils', {
464
+ name: '@mui/utils',
465
+ version: '9.0.0-beta.1',
466
+ });
467
+ const materialNextjs = await writePackage(root, 'packages/material-nextjs', {
468
+ name: '@mui/material-nextjs',
469
+ version: '9.0.0-beta.0',
470
+ });
471
+ const stylisPluginRtl = await writePackage(root, 'packages/stylis-plugin-rtl', {
472
+ name: '@mui/stylis-plugin-rtl',
473
+ version: '9.0.0-beta.0',
474
+ });
475
+
476
+ const publicInternalPkgs = [
477
+ publicPkg('@mui/internal-core-docs', coreDocs),
478
+ publicPkg('@mui/internal-docs-utils', docsUtils),
479
+ publicPkg('@mui/internal-markdown', markdown),
480
+ publicPkg('@mui/internal-scripts', scripts),
481
+ ];
482
+ const privateInternalPkgs = [
483
+ privatePkg('@mui-internal/api-docs-builder', apiDocsBuilder),
484
+ privatePkg('@mui-internal/api-docs-builder-core', apiDocsBuilderCore),
485
+ privatePkg('@mui/internal-waterfall', waterfall),
486
+ ];
487
+ const publicMainPkgs = [
488
+ publicPkg('@mui/material', material),
489
+ publicPkg('@mui/icons-material', iconsM),
490
+ publicPkg('@mui/system', muiSystem),
491
+ publicPkg('@mui/utils', muiUtils),
492
+ publicPkg('@mui/material-nextjs', materialNextjs),
493
+ publicPkg('@mui/stylis-plugin-rtl', stylisPluginRtl),
494
+ ];
495
+
496
+ const allPkgs = [...publicInternalPkgs, ...privateInternalPkgs, ...publicMainPkgs];
497
+ return { publicInternalPkgs, privateInternalPkgs, publicMainPkgs, allPkgs };
498
+ }
499
+
500
+ it('passes with no issues when publishing all public packages-internal packages', async () => {
501
+ const root = await makeTempDir();
502
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
503
+ const { byName, pathByName } = workspaceMaps(allPkgs);
504
+
505
+ const { issues } = await checkPublishDependencies(publicInternalPkgs, byName, pathByName);
506
+ expect(issues).toEqual([]);
507
+ });
508
+
509
+ it('passes when @mui/material and other pinned-range peers of core-docs are not in the publish set', async () => {
510
+ const root = await makeTempDir();
511
+ const { publicInternalPkgs, privateInternalPkgs } = await buildMaterialUiWorkspace(root);
512
+ // workspace without the packages/* — simulates --filter "./packages-internal/*"
513
+ const filteredWorkspace = [...publicInternalPkgs, ...privateInternalPkgs];
514
+ const { byName, pathByName } = workspaceMaps(filteredWorkspace);
515
+
516
+ const { issues } = await checkPublishDependencies(publicInternalPkgs, byName, pathByName);
517
+ expect(issues).toEqual([]);
518
+ });
519
+
520
+ it('passes when workspace:* devDependencies of core-docs are not in the publish set', async () => {
521
+ // core-docs has @mui-internal/api-docs-builder and @mui/icons-material as workspace:*
522
+ // devDependencies. They must NOT be required — devDeps are not installed on consumer devices.
523
+ const root = await makeTempDir();
524
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
525
+ const { byName, pathByName } = workspaceMaps(allPkgs);
526
+
527
+ const { issues } = await checkPublishDependencies(publicInternalPkgs, byName, pathByName);
528
+ expect(issues).toEqual([]);
529
+ });
530
+
531
+ it('flags @mui/internal-markdown as missing when core-docs is published without it', async () => {
532
+ const root = await makeTempDir();
533
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
534
+ const { byName, pathByName } = workspaceMaps(allPkgs);
535
+
536
+ const withoutMarkdown = publicInternalPkgs.filter((p) => p.name !== '@mui/internal-markdown');
537
+ const { issues } = await checkPublishDependencies(withoutMarkdown, byName, pathByName);
538
+ expect(issues).toHaveLength(1);
539
+ expect(issues[0]).toContain('@mui/internal-markdown');
540
+ });
541
+
542
+ it('flags @mui/internal-docs-utils as missing when scripts is published without it', async () => {
543
+ const root = await makeTempDir();
544
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
545
+ const { byName, pathByName } = workspaceMaps(allPkgs);
546
+
547
+ const withoutDocsUtils = publicInternalPkgs.filter(
548
+ (p) => p.name !== '@mui/internal-docs-utils',
549
+ );
550
+ const { issues } = await checkPublishDependencies(withoutDocsUtils, byName, pathByName);
551
+ expect(issues).toHaveLength(1);
552
+ expect(issues[0]).toContain('@mui/internal-docs-utils');
553
+ });
554
+
555
+ it('flags both missing workspace: deps when core-docs and scripts lack their deps', async () => {
556
+ const root = await makeTempDir();
557
+ const { publicInternalPkgs, allPkgs } = await buildMaterialUiWorkspace(root);
558
+ const { byName, pathByName } = workspaceMaps(allPkgs);
559
+
560
+ const onlyCoreDocs = publicInternalPkgs.filter(
561
+ (p) => p.name !== '@mui/internal-markdown' && p.name !== '@mui/internal-docs-utils',
562
+ );
563
+ const { issues } = await checkPublishDependencies(onlyCoreDocs, byName, pathByName);
564
+ expect(issues).toHaveLength(1); // single issue listing both missing packages
565
+ expect(issues[0]).toContain('@mui/internal-markdown');
566
+ expect(issues[0]).toContain('@mui/internal-docs-utils');
567
+ });
568
+ });
569
+
570
+ describe('packages not in the workspace', () => {
571
+ it('ignores dependencies that are not workspace packages', async () => {
572
+ const root = await makeTempDir();
573
+ const aDir = await writePackage(root, 'pkg-a', {
574
+ name: '@scope/pkg-a',
575
+ dependencies: { react: '^18.0.0', lodash: '^4.0.0' },
576
+ });
577
+
578
+ const pkgA = publicPkg('@scope/pkg-a', aDir);
579
+ const { byName, pathByName } = workspaceMaps([pkgA]);
580
+
581
+ const { issues } = await checkPublishDependencies([pkgA], byName, pathByName);
582
+ expect(issues).toEqual([]);
583
+ });
584
+ });
585
+ });
586
+
587
+ /**
588
+ * Set up a temp workspace with a package.json and optional pnpm-workspace.yaml.
589
+ * @param {object} packageJson - package.json contents
590
+ * @param {string} [workspaceYaml] - pnpm-workspace.yaml contents, omitted to skip the file
591
+ * @returns {Promise<string>} The workspace directory
592
+ */
593
+ async function makeWorkspace(packageJson, workspaceYaml) {
594
+ const cwd = await makeTempDir();
595
+ const writes = [writePackageJson(cwd, packageJson)];
596
+ if (workspaceYaml !== undefined) {
597
+ writes.push(fs.writeFile(path.join(cwd, 'pnpm-workspace.yaml'), workspaceYaml));
598
+ }
599
+ await Promise.all(writes);
600
+ return cwd;
601
+ }
602
+
603
+ /**
604
+ * @param {string} cwd
605
+ * @returns {Promise<string>}
606
+ */
607
+ function readWorkspaceYaml(cwd) {
608
+ return fs.readFile(path.join(cwd, 'pnpm-workspace.yaml'), 'utf8');
609
+ }
610
+
611
+ describe('writeOverridesToWorkspace', () => {
612
+ describe('writing to pnpm-workspace.yaml', () => {
613
+ it('creates the file with an overrides block when none exists', async () => {
614
+ const cwd = await makeWorkspace({ name: 'root' });
615
+
616
+ await writeOverridesToWorkspace(cwd, { foo: '1.2.3' });
617
+
618
+ expect(await readWorkspaceYaml(cwd)).toMatchInlineSnapshot(`
619
+ "overrides:
620
+ foo: 1.2.3
621
+ "
622
+ `);
623
+ });
624
+
625
+ it('merges into an existing block, preserving comments and quoting scoped names', async () => {
626
+ const cwd = await makeWorkspace(
627
+ { name: 'root' },
628
+ [
629
+ 'packages:',
630
+ " - 'packages/*'",
631
+ 'overrides:',
632
+ ' # keep this pin',
633
+ " bar: '2.0.0'",
634
+ '',
635
+ ].join('\n'),
636
+ );
637
+
638
+ await writeOverridesToWorkspace(cwd, { playwright: '1.49.1', '@playwright/test': '1.49.1' });
639
+
640
+ expect(await readWorkspaceYaml(cwd)).toMatchInlineSnapshot(`
641
+ "packages:
642
+ - 'packages/*'
643
+ overrides:
644
+ # keep this pin
645
+ bar: '2.0.0'
646
+ playwright: 1.49.1
647
+ "@playwright/test": 1.49.1
648
+ "
649
+ `);
650
+ // The package.json is left untouched.
651
+ expect(await readPackageJson(cwd)).toEqual({ name: 'root' });
652
+ });
653
+
654
+ it('overwrites a same-named override, keeping its quote style', async () => {
655
+ const cwd = await makeWorkspace(
656
+ { name: 'root' },
657
+ ['overrides:', " foo: '1.0.0'", ''].join('\n'),
658
+ );
659
+
660
+ await writeOverridesToWorkspace(cwd, { foo: '2.0.0' });
661
+
662
+ expect(await readWorkspaceYaml(cwd)).toMatchInlineSnapshot(`
663
+ "overrides:
664
+ foo: '2.0.0'
665
+ "
666
+ `);
667
+ });
668
+
669
+ it('prefers the workspace file when both manifests define overrides', async () => {
670
+ const cwd = await makeWorkspace(
671
+ { name: 'root', pnpm: { overrides: { baz: '3.0.0' } } },
672
+ ['overrides:', " bar: '2.0.0'", ''].join('\n'),
673
+ );
674
+
675
+ await writeOverridesToWorkspace(cwd, { foo: '1.2.3' });
676
+
677
+ expect(await readWorkspaceYaml(cwd)).toContain('foo: 1.2.3');
678
+ // package.json overrides are left where they were.
679
+ expect(await readPackageJson(cwd)).toEqual({
680
+ name: 'root',
681
+ pnpm: { overrides: { baz: '3.0.0' } },
682
+ });
683
+ });
684
+ });
685
+
686
+ describe('writing to package.json', () => {
687
+ it('honors the package.json location when no workspace overrides exist', async () => {
688
+ const cwd = await makeWorkspace({
689
+ name: 'root',
690
+ pnpm: { overrides: { foo: '1.0.0' }, packageExtensions: { thing: {} } },
691
+ });
692
+
693
+ await writeOverridesToWorkspace(cwd, { bar: '2.0.0' });
694
+
695
+ expect(await readPackageJson(cwd)).toEqual({
696
+ name: 'root',
697
+ pnpm: { overrides: { foo: '1.0.0', bar: '2.0.0' }, packageExtensions: { thing: {} } },
698
+ });
699
+ // No workspace file is created.
700
+ await expect(fs.access(path.join(cwd, 'pnpm-workspace.yaml'))).rejects.toThrow();
701
+ });
702
+
703
+ it('lets computed overrides win over an existing package.json override', async () => {
704
+ const cwd = await makeWorkspace({ name: 'root', pnpm: { overrides: { foo: '1.0.0' } } });
705
+
706
+ await writeOverridesToWorkspace(cwd, { foo: '2.0.0' });
707
+
708
+ expect(await readPackageJson(cwd)).toEqual({
709
+ name: 'root',
710
+ pnpm: { overrides: { foo: '2.0.0' } },
711
+ });
712
+ });
713
+ });
714
+
715
+ describe('rejecting resolutions', () => {
716
+ it('throws and writes nothing when package.json has a resolutions field', async () => {
717
+ const cwd = await makeWorkspace({ name: 'root', resolutions: { foo: '1.0.0' } });
718
+
719
+ await expect(writeOverridesToWorkspace(cwd, { bar: '2.0.0' })).rejects.toThrow(/resolutions/);
720
+ await expect(fs.access(path.join(cwd, 'pnpm-workspace.yaml'))).rejects.toThrow();
721
+ });
722
+
723
+ it('ignores an empty resolutions field', async () => {
724
+ const cwd = await makeWorkspace({ name: 'root', resolutions: {} });
725
+
726
+ await writeOverridesToWorkspace(cwd, { foo: '1.2.3' });
727
+
728
+ expect(await readWorkspaceYaml(cwd)).toContain('foo: 1.2.3');
729
+ });
730
+ });
731
+ });