@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.
- package/README.md +19 -8
- package/build/babel-config.d.mts +11 -3
- package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
- package/build/brokenLinksChecker/index.d.mts +35 -2
- package/build/changelog/types.d.ts +1 -1
- package/build/cli/cmdArgosPush.d.mts +2 -2
- package/build/cli/cmdBuild.d.mts +2 -2
- package/build/cli/cmdCopyFiles.d.mts +2 -2
- package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
- package/build/cli/cmdGenerateChangelog.d.mts +2 -2
- package/build/cli/cmdGithubAuth.d.mts +2 -2
- package/build/cli/cmdListWorkspaces.d.mts +4 -2
- package/build/cli/cmdNetlifyIgnore.d.mts +2 -2
- package/build/cli/cmdPublish.d.mts +4 -2
- package/build/cli/cmdPublishCanary.d.mts +3 -2
- package/build/cli/cmdPublishNewPackage.d.mts +4 -2
- package/build/cli/cmdSetVersionOverrides.d.mts +2 -2
- package/build/cli/cmdVale.d.mts +46 -0
- package/build/cli/cmdValidateBuiltTypes.d.mts +2 -2
- package/build/eslint/mui/rules/disallow-react-api-in-server-components.d.mts +2 -2
- package/build/eslint/mui/rules/docgen-ignore-before-comment.d.mts +2 -2
- package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +2 -2
- package/build/markdownlint/duplicate-h1.d.mts +1 -1
- package/build/markdownlint/git-diff.d.mts +1 -1
- package/build/markdownlint/index.d.mts +1 -1
- package/build/markdownlint/straight-quotes.d.mts +1 -1
- package/build/markdownlint/table-alignment.d.mts +1 -1
- package/build/markdownlint/terminal-language.d.mts +1 -1
- package/build/utils/build.d.mts +3 -3
- package/build/utils/github.d.mts +1 -1
- package/build/utils/pnpm.d.mts +68 -2
- package/build/utils/testUtils.d.mts +7 -0
- package/package.json +38 -31
- package/src/babel-config.mjs +9 -3
- package/src/brokenLinksChecker/__fixtures__/static-site/index.html +1 -0
- package/src/brokenLinksChecker/__fixtures__/static-site/invalid-html.html +15 -0
- package/src/brokenLinksChecker/crawlWorker.mjs +173 -0
- package/src/brokenLinksChecker/index.mjs +177 -164
- package/src/brokenLinksChecker/index.test.ts +55 -13
- package/src/build-env.d.ts +13 -0
- package/src/changelog/fetchChangelogs.mjs +6 -2
- package/src/changelog/types.ts +1 -1
- package/src/cli/cmdListWorkspaces.mjs +9 -2
- package/src/cli/cmdNetlifyIgnore.mjs +4 -88
- package/src/cli/cmdPublish.mjs +51 -14
- package/src/cli/cmdPublishCanary.mjs +139 -107
- package/src/cli/cmdPublishNewPackage.mjs +27 -6
- package/src/cli/cmdVale.mjs +513 -0
- package/src/cli/cmdVale.test.mjs +644 -0
- package/src/cli/index.mjs +2 -0
- package/src/eslint/baseConfig.mjs +2 -1
- package/src/eslint/docsConfig.mjs +2 -1
- package/src/eslint/jsonConfig.mjs +2 -1
- package/src/eslint/mui/config.mjs +11 -1
- package/src/eslint/testConfig.mjs +2 -1
- package/src/estree-typescript.d.ts +1 -1
- package/src/untyped-plugins.d.ts +11 -11
- package/src/utils/build.test.mjs +546 -575
- package/src/utils/pnpm.mjs +192 -3
- package/src/utils/pnpm.test.mjs +580 -0
- package/src/utils/testUtils.mjs +18 -0
- package/src/utils/typescript.test.mjs +249 -272
- package/vale/.vale.ini +1 -0
- package/vale/styles/MUI/CorrectReferenceAllCases.yml +43 -0
- package/vale/styles/MUI/CorrectRererenceCased.yml +14 -0
- package/vale/styles/MUI/GoogleLatin.yml +11 -0
- package/vale/styles/MUI/MuiBrandName.yml +22 -0
- package/vale/styles/MUI/NoBritish.yml +112 -0
- 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
|
+
}
|