@mui/internal-code-infra 0.0.4-canary.4 ā 0.0.4-canary.40
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 +44 -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/baseConfig.d.mts +3 -1
- 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-guarded-throw.d.mts +31 -0
- package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +2 -2
- package/build/eslint/mui/rules/nodeEnvUtils.d.mts +18 -0
- 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/remark/config.d.mts +43 -0
- package/build/remark/createLintTester.d.mts +10 -0
- package/build/remark/firstBlockHeading.d.mts +4 -0
- package/build/remark/gitDiff.d.mts +2 -0
- package/build/remark/noSpaceInLinks.d.mts +2 -0
- package/build/remark/straightQuotes.d.mts +2 -0
- package/build/remark/tableAlignment.d.mts +2 -0
- package/build/remark/terminalLanguage.d.mts +2 -0
- 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 +59 -32
- 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 +200 -0
- package/src/brokenLinksChecker/index.mjs +213 -164
- package/src/brokenLinksChecker/index.test.ts +63 -13
- package/src/changelog/categorizeCommits.test.ts +5 -5
- package/src/changelog/fetchChangelogs.mjs +6 -2
- package/src/changelog/parseCommitLabels.test.ts +5 -5
- package/src/changelog/renderChangelog.mjs +1 -1
- 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 +45 -20
- package/src/eslint/docsConfig.mjs +2 -1
- package/src/eslint/jsonConfig.mjs +2 -1
- package/src/eslint/mui/config.mjs +20 -1
- package/src/eslint/mui/index.mjs +2 -0
- package/src/eslint/mui/rules/no-guarded-throw.mjs +115 -0
- package/src/eslint/mui/rules/no-guarded-throw.test.mjs +206 -0
- package/src/eslint/mui/rules/nodeEnvUtils.mjs +52 -0
- package/src/eslint/mui/rules/require-dev-wrapper.mjs +25 -40
- package/src/eslint/testConfig.mjs +2 -1
- package/src/estree-typescript.d.ts +1 -1
- package/src/remark/config.mjs +157 -0
- package/src/remark/createLintTester.mjs +19 -0
- package/src/remark/firstBlockHeading.mjs +87 -0
- package/src/remark/firstBlockHeading.test.mjs +107 -0
- package/src/remark/gitDiff.mjs +43 -0
- package/src/remark/gitDiff.test.mjs +45 -0
- package/src/remark/noSpaceInLinks.mjs +42 -0
- package/src/remark/noSpaceInLinks.test.mjs +22 -0
- package/src/remark/straightQuotes.mjs +31 -0
- package/src/remark/straightQuotes.test.mjs +25 -0
- package/src/remark/tableAlignment.mjs +23 -0
- package/src/remark/tableAlignment.test.mjs +28 -0
- package/src/remark/terminalLanguage.mjs +19 -0
- package/src/remark/terminalLanguage.test.mjs +17 -0
- 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
|
@@ -210,11 +210,11 @@ describe('parseCommitLabels', () => {
|
|
|
210
210
|
|
|
211
211
|
describe('category overrides', () => {
|
|
212
212
|
it('should detect category override labels', () => {
|
|
213
|
-
const commit = createCommit({ labels: ['all components'] });
|
|
213
|
+
const commit = createCommit({ labels: ['scope: all components'] });
|
|
214
214
|
const config: LabelConfig = {
|
|
215
215
|
...baseLabelConfig,
|
|
216
216
|
categoryOverrides: {
|
|
217
|
-
'all components': 'General changes',
|
|
217
|
+
'scope: all components': 'General changes',
|
|
218
218
|
},
|
|
219
219
|
};
|
|
220
220
|
|
|
@@ -224,11 +224,11 @@ describe('parseCommitLabels', () => {
|
|
|
224
224
|
});
|
|
225
225
|
|
|
226
226
|
it('should use the last category override when multiple are present', () => {
|
|
227
|
-
const commit = createCommit({ labels: ['all components', 'docs'] });
|
|
227
|
+
const commit = createCommit({ labels: ['scope: all components', 'docs'] });
|
|
228
228
|
const config: LabelConfig = {
|
|
229
229
|
...baseLabelConfig,
|
|
230
230
|
categoryOverrides: {
|
|
231
|
-
'all components': 'General changes',
|
|
231
|
+
'scope: all components': 'General changes',
|
|
232
232
|
docs: 'Documentation',
|
|
233
233
|
},
|
|
234
234
|
};
|
|
@@ -243,7 +243,7 @@ describe('parseCommitLabels', () => {
|
|
|
243
243
|
const config: LabelConfig = {
|
|
244
244
|
...baseLabelConfig,
|
|
245
245
|
categoryOverrides: {
|
|
246
|
-
'all components': 'General changes',
|
|
246
|
+
'scope: all components': 'General changes',
|
|
247
247
|
},
|
|
248
248
|
};
|
|
249
249
|
|
|
@@ -438,7 +438,7 @@ function renderContributors(contributors, config, lines) {
|
|
|
438
438
|
const template = getTemplateString(
|
|
439
439
|
config.contributors?.message?.contributors,
|
|
440
440
|
allContributors.length,
|
|
441
|
-
`${allContributors.length !== 1 ? 'All contributors of this release in alphabetical order' : 'Contributor of this release'}
|
|
441
|
+
`${allContributors.length !== 1 ? 'All contributors of this release in alphabetical order' : 'Contributor of this release'}: {{contributors}}`,
|
|
442
442
|
);
|
|
443
443
|
const contributorsMessage = templateString(template, {
|
|
444
444
|
contributors: renderContributorsList(allContributors),
|
package/src/changelog/types.ts
CHANGED
|
@@ -233,7 +233,7 @@ export interface IntroConfig {
|
|
|
233
233
|
* - {{teamCount}}: Number of team members
|
|
234
234
|
* - {{communityCount}}: Number of community contributors
|
|
235
235
|
*
|
|
236
|
-
* Example: "
|
|
236
|
+
* Example: "A big thanks to the {{contributorCount}} contributors who made this release possible."
|
|
237
237
|
*
|
|
238
238
|
* Set to `false` or omit to disable the thank you message.
|
|
239
239
|
*/
|
|
@@ -15,6 +15,7 @@ import { getWorkspacePackages } from '../utils/pnpm.mjs';
|
|
|
15
15
|
* @property {boolean} [publicOnly] - Whether to filter to only public packages
|
|
16
16
|
* @property {'json'|'path'|'name'|'publish-dir'} [output] - Output format (name, path, or json)
|
|
17
17
|
* @property {string} [sinceRef] - Git reference to filter changes since
|
|
18
|
+
* @property {string[]} [filter] - Same as filtering packages with --filter in pnpm. Only include packages matching the filter. See https://pnpm.io/filtering.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
@@ -37,13 +38,19 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
37
38
|
.option('since-ref', {
|
|
38
39
|
type: 'string',
|
|
39
40
|
description: 'Filter packages changed since git reference',
|
|
41
|
+
})
|
|
42
|
+
.option('filter', {
|
|
43
|
+
type: 'string',
|
|
44
|
+
array: true,
|
|
45
|
+
description:
|
|
46
|
+
'Same as filtering packages with --filter in pnpm. Only include packages matching the filter. See https://pnpm.io/filtering.',
|
|
40
47
|
});
|
|
41
48
|
},
|
|
42
49
|
handler: async (argv) => {
|
|
43
|
-
const { publicOnly = false, output = 'name', sinceRef } = argv;
|
|
50
|
+
const { publicOnly = false, output = 'name', sinceRef, filter = [] } = argv;
|
|
44
51
|
|
|
45
52
|
// Get packages using our helper function
|
|
46
|
-
const packages = await getWorkspacePackages({ sinceRef, publicOnly });
|
|
53
|
+
const packages = await getWorkspacePackages({ sinceRef, publicOnly, filter });
|
|
47
54
|
|
|
48
55
|
if (output === 'json') {
|
|
49
56
|
// Serialize packages to JSON
|
|
@@ -12,93 +12,7 @@ import * as fs from 'node:fs/promises';
|
|
|
12
12
|
import * as path from 'node:path';
|
|
13
13
|
import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
|
|
14
14
|
import { toPosixPath } from '../utils/path.mjs';
|
|
15
|
-
import { getWorkspacePackages } from '../utils/pnpm.mjs';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get all workspace dependencies (direct and transitive) from a package
|
|
19
|
-
* @param {string} packageName - Package name
|
|
20
|
-
* @param {Map<string, string>} workspaceMap - Map of workspace name to path
|
|
21
|
-
* @param {Map<string, Promise<Set<string>>>} cache - Cache of package resolution promises
|
|
22
|
-
* @returns {Promise<Set<string>>} Set of workspace package names (dependencies only, not including the package itself)
|
|
23
|
-
*/
|
|
24
|
-
async function getWorkspaceDependenciesRecursive(packageName, workspaceMap, cache) {
|
|
25
|
-
// Check cache first
|
|
26
|
-
const cached = cache.get(packageName);
|
|
27
|
-
if (cached) {
|
|
28
|
-
return cached;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Create the resolution promise
|
|
32
|
-
const promise = (async () => {
|
|
33
|
-
const packagePath = workspaceMap.get(packageName);
|
|
34
|
-
if (!packagePath) {
|
|
35
|
-
throw new Error(`Workspace "${packageName}" not found in the repository`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
39
|
-
const content = await fs.readFile(packageJsonPath, 'utf8');
|
|
40
|
-
const packageJson = JSON.parse(content);
|
|
41
|
-
|
|
42
|
-
// Collect all dependency names
|
|
43
|
-
/** @type {Set<string>} */
|
|
44
|
-
const allDeps = new Set();
|
|
45
|
-
if (packageJson.dependencies) {
|
|
46
|
-
Object.keys(packageJson.dependencies).forEach((dep) => allDeps.add(dep));
|
|
47
|
-
}
|
|
48
|
-
if (packageJson.devDependencies) {
|
|
49
|
-
Object.keys(packageJson.devDependencies).forEach((dep) => allDeps.add(dep));
|
|
50
|
-
}
|
|
51
|
-
if (packageJson.peerDependencies) {
|
|
52
|
-
Object.keys(packageJson.peerDependencies).forEach((dep) => allDeps.add(dep));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Filter to only workspace dependencies
|
|
56
|
-
const workspaceDeps = Array.from(allDeps).filter((dep) => workspaceMap.has(dep));
|
|
57
|
-
|
|
58
|
-
// Recursively process workspace dependencies in parallel
|
|
59
|
-
const recursiveResults = await Promise.all(
|
|
60
|
-
workspaceDeps.map(async (dep) => {
|
|
61
|
-
return getWorkspaceDependenciesRecursive(dep, workspaceMap, cache);
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Merge all results using flatMap
|
|
66
|
-
return new Set(recursiveResults.flatMap((result) => Array.from(result)).concat(workspaceDeps));
|
|
67
|
-
})();
|
|
68
|
-
|
|
69
|
-
// Store in cache before returning
|
|
70
|
-
cache.set(packageName, promise);
|
|
71
|
-
|
|
72
|
-
return promise;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get transitive workspace dependencies for a list of workspace names
|
|
77
|
-
* @param {string[]} workspaceNames - Array of workspace names
|
|
78
|
-
* @param {Map<string, string>} workspaceMap - Map of workspace name to path
|
|
79
|
-
* @returns {Promise<Set<string>>} Set of workspace package names (including requested packages and all their dependencies)
|
|
80
|
-
*/
|
|
81
|
-
async function getTransitiveDependencies(workspaceNames, workspaceMap) {
|
|
82
|
-
// Shared cache for all workspace dependency resolution
|
|
83
|
-
const cache = new Map();
|
|
84
|
-
|
|
85
|
-
// Validate all workspace names exist
|
|
86
|
-
for (const workspaceName of workspaceNames) {
|
|
87
|
-
if (!workspaceMap.has(workspaceName)) {
|
|
88
|
-
throw new Error(`Workspace "${workspaceName}" not found in the repository`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Process each requested workspace in parallel
|
|
93
|
-
const workspaceResults = await Promise.all(
|
|
94
|
-
workspaceNames.map((workspaceName) =>
|
|
95
|
-
getWorkspaceDependenciesRecursive(workspaceName, workspaceMap, cache),
|
|
96
|
-
),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Merge all results using flatMap and add the original package names
|
|
100
|
-
return new Set(workspaceNames.concat(workspaceResults.flatMap((result) => Array.from(result))));
|
|
101
|
-
}
|
|
15
|
+
import { getTransitiveDependencies, getWorkspacePackages } from '../utils/pnpm.mjs';
|
|
102
16
|
|
|
103
17
|
/**
|
|
104
18
|
* Generate the ignore command string for netlify.toml
|
|
@@ -220,7 +134,9 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
220
134
|
console.log(`Processing ${workspaceName}...`);
|
|
221
135
|
|
|
222
136
|
// Get transitive dependencies for this specific workspace
|
|
223
|
-
const dependencyNames = await getTransitiveDependencies([workspaceName],
|
|
137
|
+
const dependencyNames = await getTransitiveDependencies([workspaceName], {
|
|
138
|
+
workspacePathByName: workspaceMap,
|
|
139
|
+
});
|
|
224
140
|
|
|
225
141
|
// Convert package names to relative paths (normalize to POSIX separators for git)
|
|
226
142
|
const relativePaths = Array.from(dependencyNames)
|
package/src/cli/cmdPublish.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {import('../utils/pnpm.mjs').PublicPackage} PublicPackage
|
|
7
7
|
* @typedef {import('../utils/pnpm.mjs').PublishOptions} PublishOptions
|
|
8
|
+
* @typedef {import('../utils/pnpm.mjs').PublishSummaryEntry} PublishSummaryEntry
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import select from '@inquirer/select';
|
|
@@ -17,7 +18,11 @@ import * as fs from 'node:fs/promises';
|
|
|
17
18
|
import * as semver from 'semver';
|
|
18
19
|
|
|
19
20
|
import { persistentAuthStrategy } from '../utils/github.mjs';
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
getWorkspacePackages,
|
|
23
|
+
publishPackages,
|
|
24
|
+
validatePublishDependencies,
|
|
25
|
+
} from '../utils/pnpm.mjs';
|
|
21
26
|
import { getCurrentGitSha, getRepositoryInfo } from '../utils/git.mjs';
|
|
22
27
|
|
|
23
28
|
const isCI = envCI().isCi;
|
|
@@ -33,6 +38,7 @@ function getOctokit() {
|
|
|
33
38
|
* @property {string} tag NPM dist tag to publish to
|
|
34
39
|
* @property {boolean} ci Runs in CI environment
|
|
35
40
|
* @property {string} [sha] Git SHA to use for the GitHub release workflow (local only)
|
|
41
|
+
* @property {string[]} [filter] Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.
|
|
36
42
|
*/
|
|
37
43
|
|
|
38
44
|
/**
|
|
@@ -189,18 +195,11 @@ async function validateGitHubRelease(version) {
|
|
|
189
195
|
* Publish packages to npm
|
|
190
196
|
* @param {PublicPackage[]} packages - Packages to publish
|
|
191
197
|
* @param {PublishOptions} options - Publishing options
|
|
192
|
-
* @returns {Promise<
|
|
198
|
+
* @returns {Promise<PublishSummaryEntry[]>}
|
|
193
199
|
*/
|
|
194
200
|
async function publishToNpm(packages, options) {
|
|
195
|
-
console.log('\nš¦ Publishing packages to npm...');
|
|
196
|
-
console.log(`š Found ${packages.length} packages:`);
|
|
197
|
-
packages.forEach((pkg) => {
|
|
198
|
-
console.log(` ⢠${pkg.name}@${pkg.version}`);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
201
|
// Use pnpm's built-in duplicate checking - no need to check versions ourselves
|
|
202
|
-
|
|
203
|
-
console.log('ā
Successfully published to npm');
|
|
202
|
+
return publishPackages(packages, options);
|
|
204
203
|
}
|
|
205
204
|
|
|
206
205
|
/**
|
|
@@ -260,10 +259,16 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
260
259
|
.option('sha', {
|
|
261
260
|
type: 'string',
|
|
262
261
|
description: 'Git SHA to use for the GitHub release workflow (local only)',
|
|
262
|
+
})
|
|
263
|
+
.option('filter', {
|
|
264
|
+
type: 'string',
|
|
265
|
+
array: true,
|
|
266
|
+
description:
|
|
267
|
+
'Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.',
|
|
263
268
|
});
|
|
264
269
|
},
|
|
265
270
|
handler: async (argv) => {
|
|
266
|
-
const { dryRun = false, githubRelease = false, tag = 'latest', sha } = argv;
|
|
271
|
+
const { dryRun = false, githubRelease = false, tag = 'latest', sha, filter = [] } = argv;
|
|
267
272
|
|
|
268
273
|
if (isCI && !argv.ci) {
|
|
269
274
|
console.error(
|
|
@@ -290,13 +295,34 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
290
295
|
// Get all packages
|
|
291
296
|
console.log('š Discovering all workspace packages...');
|
|
292
297
|
|
|
293
|
-
const allPackages = await getWorkspacePackages({ publicOnly: true });
|
|
298
|
+
const allPackages = await getWorkspacePackages({ publicOnly: true, filter });
|
|
294
299
|
|
|
295
300
|
if (allPackages.length === 0) {
|
|
296
|
-
console.log(
|
|
301
|
+
console.log(
|
|
302
|
+
`ā ļø No publishable packages found in workspace${filter.length > 0 ? ` matching filter "${filter.join(', ')}"` : ''}`,
|
|
303
|
+
);
|
|
297
304
|
return;
|
|
298
305
|
}
|
|
299
306
|
|
|
307
|
+
if (filter.length > 0) {
|
|
308
|
+
console.log('š Validating workspace dependencies for filtered packages...');
|
|
309
|
+
|
|
310
|
+
const { issues } = await validatePublishDependencies(allPackages);
|
|
311
|
+
|
|
312
|
+
if (issues.length > 0) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Invalid dependencies structure of packages to be published -
|
|
315
|
+
${issues.join('\n ')}
|
|
316
|
+
`,
|
|
317
|
+
{
|
|
318
|
+
cause: issues,
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log('ā
All workspace dependency requirements satisfied');
|
|
324
|
+
}
|
|
325
|
+
|
|
300
326
|
// Get version from root package.json
|
|
301
327
|
const version = await getReleaseVersion();
|
|
302
328
|
|
|
@@ -323,7 +349,18 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
323
349
|
|
|
324
350
|
// Publish to npm (pnpm handles duplicate checking automatically)
|
|
325
351
|
// No git checks, we'll do our own
|
|
326
|
-
|
|
352
|
+
console.log('\nš¦ Publishing packages to npm...');
|
|
353
|
+
const publishedPackages = await publishToNpm(allPackages, { dryRun, noGitChecks: true, tag });
|
|
354
|
+
|
|
355
|
+
if (publishedPackages.length === 0) {
|
|
356
|
+
console.log('ā¹ļø No packages were published (all may already be up to date on npm)');
|
|
357
|
+
console.log('\nš Nothing to publish, skipping git tag and GitHub release.');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
publishedPackages.forEach((pkg) => {
|
|
362
|
+
console.log(`ā
Published ${pkg.name}@${pkg.version}`);
|
|
363
|
+
});
|
|
327
364
|
|
|
328
365
|
await createGitTag(version, dryRun);
|
|
329
366
|
|
|
@@ -16,10 +16,12 @@ import * as semver from 'semver';
|
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
18
|
getPackageVersionInfo,
|
|
19
|
+
getTransitiveDependencies,
|
|
19
20
|
getWorkspacePackages,
|
|
20
21
|
publishPackages,
|
|
21
22
|
readPackageJson,
|
|
22
23
|
semverMax,
|
|
24
|
+
validatePublishDependencies,
|
|
23
25
|
writePackageJson,
|
|
24
26
|
} from '../utils/pnpm.mjs';
|
|
25
27
|
import { getCurrentGitSha, getRepositoryInfo } from '../utils/git.mjs';
|
|
@@ -28,6 +30,7 @@ import { getCurrentGitSha, getRepositoryInfo } from '../utils/git.mjs';
|
|
|
28
30
|
* @typedef {Object} Args
|
|
29
31
|
* @property {boolean} [dryRun] - Whether to run in dry-run mode
|
|
30
32
|
* @property {boolean} [githubRelease] - Whether to create GitHub releases for canary packages
|
|
33
|
+
* @property {string[]} [filter] - Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.
|
|
31
34
|
*/
|
|
32
35
|
|
|
33
36
|
const CANARY_TAG = 'canary';
|
|
@@ -118,80 +121,6 @@ function cleanupCommitMessage(message) {
|
|
|
118
121
|
return `${prefix}${msg}`.trim();
|
|
119
122
|
}
|
|
120
123
|
|
|
121
|
-
async function getPackageToDependencyMap() {
|
|
122
|
-
/**
|
|
123
|
-
* @type {(PublicPackage & { dependencies: Record<string, unknown>; private: boolean; })[]}
|
|
124
|
-
*/
|
|
125
|
-
const packagesWithDeps = JSON.parse(
|
|
126
|
-
(await $`pnpm ls -r --json --exclude-peers --only-projects --prod`).stdout,
|
|
127
|
-
);
|
|
128
|
-
/** @type {Record<string, string[]>} */
|
|
129
|
-
const directPkgDependencies = packagesWithDeps
|
|
130
|
-
.filter((pkg) => !pkg.private)
|
|
131
|
-
.reduce((acc, pkg) => {
|
|
132
|
-
if (!pkg.name) {
|
|
133
|
-
return acc;
|
|
134
|
-
}
|
|
135
|
-
const deps = Object.keys(pkg.dependencies || {});
|
|
136
|
-
if (!deps.length) {
|
|
137
|
-
return acc;
|
|
138
|
-
}
|
|
139
|
-
acc[pkg.name] = deps;
|
|
140
|
-
return acc;
|
|
141
|
-
}, /** @type {Record<string, string[]>} */ ({}));
|
|
142
|
-
return directPkgDependencies;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* @param {Record<string, string[]>} pkgGraph
|
|
147
|
-
*/
|
|
148
|
-
function resolveTransitiveDependencies(pkgGraph = {}) {
|
|
149
|
-
// Compute transitive (nested) dependencies limited to workspace packages and avoid cycles.
|
|
150
|
-
const workspacePkgNames = new Set(Object.keys(pkgGraph));
|
|
151
|
-
const nestedMap = /** @type {Record<string, string[]>} */ ({});
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
*
|
|
155
|
-
* @param {string} pkgName
|
|
156
|
-
* @returns {string[]}
|
|
157
|
-
*/
|
|
158
|
-
const getTransitiveDeps = (pkgName) => {
|
|
159
|
-
/**
|
|
160
|
-
* @type {Set<string>}
|
|
161
|
-
*/
|
|
162
|
-
const seen = new Set();
|
|
163
|
-
const stack = (pkgGraph[pkgName] || []).slice();
|
|
164
|
-
|
|
165
|
-
while (stack.length) {
|
|
166
|
-
const dep = stack.pop();
|
|
167
|
-
if (!dep || seen.has(dep)) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
// Only consider workspace packages for transitive expansion
|
|
171
|
-
if (!workspacePkgNames.has(dep)) {
|
|
172
|
-
// still record external deps as direct deps but don't traverse into them
|
|
173
|
-
seen.add(dep);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
seen.add(dep);
|
|
177
|
-
const children = pkgGraph[dep] || [];
|
|
178
|
-
for (const c of children) {
|
|
179
|
-
if (!seen.has(c)) {
|
|
180
|
-
stack.push(c);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return Array.from(seen);
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
for (const name of Object.keys(pkgGraph)) {
|
|
189
|
-
nestedMap[name] = getTransitiveDeps(name);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return nestedMap;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
124
|
/**
|
|
196
125
|
* Prepare changelog data for packages using GitHub API
|
|
197
126
|
* @param {PublicPackage[]} packagesToPublish - Packages that will be published
|
|
@@ -224,14 +153,23 @@ async function prepareChangelogsFromGitCli(packagesToPublish, allPackages, canar
|
|
|
224
153
|
}
|
|
225
154
|
}),
|
|
226
155
|
);
|
|
227
|
-
// Second pass: check for dependency updates in other packages not part of git history
|
|
228
|
-
const
|
|
229
|
-
const
|
|
156
|
+
// Second pass: check for dependency updates in other packages not part of git history.
|
|
157
|
+
const workspacePathByName = new Map(allPackages.map((pkg) => [pkg.name, pkg.path]));
|
|
158
|
+
const publishedNames = new Set(packagesToPublish.map((p) => p.name));
|
|
159
|
+
|
|
160
|
+
const transitiveDepSets = await Promise.all(
|
|
161
|
+
allPackages.map((pkg) =>
|
|
162
|
+
getTransitiveDependencies([pkg.name], {
|
|
163
|
+
includeDev: false,
|
|
164
|
+
workspacePathByName,
|
|
165
|
+
}),
|
|
166
|
+
),
|
|
167
|
+
);
|
|
230
168
|
|
|
231
169
|
for (let i = 0; i < allPackages.length; i += 1) {
|
|
232
170
|
const pkg = allPackages[i];
|
|
233
|
-
const depsToPublish =
|
|
234
|
-
|
|
171
|
+
const depsToPublish = [...transitiveDepSets[i]].filter(
|
|
172
|
+
(dep) => dep !== pkg.name && publishedNames.has(dep),
|
|
235
173
|
);
|
|
236
174
|
if (depsToPublish.length === 0) {
|
|
237
175
|
continue;
|
|
@@ -290,6 +228,41 @@ async function prepareChangelogsForPackages(packagesToPublish, allPackages, cana
|
|
|
290
228
|
return changelogs;
|
|
291
229
|
}
|
|
292
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Create or replace a GitHub release. Attempts to create the release first,
|
|
233
|
+
* and if it already exists (422), deletes the existing one and retries.
|
|
234
|
+
*
|
|
235
|
+
* @param {InstanceType<typeof Octokit>} octokit
|
|
236
|
+
* @param {NonNullable<Parameters<Octokit['repos']['createRelease']>[0]>} params
|
|
237
|
+
*/
|
|
238
|
+
async function upsertGitHubRelease(octokit, params) {
|
|
239
|
+
try {
|
|
240
|
+
return await octokit.repos.createRelease(params);
|
|
241
|
+
} catch (/** @type {any} */ error) {
|
|
242
|
+
const isAlreadyExists =
|
|
243
|
+
error.status === 422 &&
|
|
244
|
+
error.response?.data?.errors?.some(
|
|
245
|
+
(/** @type {any} */ err) => err.code === 'already_exists' && err.field === 'tag_name',
|
|
246
|
+
);
|
|
247
|
+
if (!isAlreadyExists) {
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Release already exists ā delete and recreate
|
|
253
|
+
const existing = await octokit.repos.getReleaseByTag({
|
|
254
|
+
owner: params.owner,
|
|
255
|
+
repo: params.repo,
|
|
256
|
+
tag: params.tag_name,
|
|
257
|
+
});
|
|
258
|
+
await octokit.repos.deleteRelease({
|
|
259
|
+
owner: params.owner,
|
|
260
|
+
repo: params.repo,
|
|
261
|
+
release_id: existing.data.id,
|
|
262
|
+
});
|
|
263
|
+
return octokit.repos.createRelease(params);
|
|
264
|
+
}
|
|
265
|
+
|
|
293
266
|
/**
|
|
294
267
|
* Create GitHub releases and tags for published packages
|
|
295
268
|
* @param {PublicPackage[]} publishedPackages - Packages that were published
|
|
@@ -342,15 +315,20 @@ async function createGitHubReleasesForPackages(
|
|
|
342
315
|
GIT_COMMITTER_NAME: 'Code infra',
|
|
343
316
|
GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
|
|
344
317
|
},
|
|
345
|
-
})`git tag -
|
|
318
|
+
})`git tag -fa ${tagName} -m ${`Canary release ${pkg.name}@${version}`}`;
|
|
346
319
|
|
|
320
|
+
// Force-push to handle retries where the tag already exists from a previous
|
|
321
|
+
// failed publish. The npm registry is the source of truth for published
|
|
322
|
+
// versions, so it's safe to rewrite a tag even if it points to a different
|
|
323
|
+
// commit ā it just means the prior publish for this version failed partway
|
|
324
|
+
// through and the GitHub release needs to be recreated.
|
|
347
325
|
// eslint-disable-next-line no-await-in-loop
|
|
348
|
-
await $`git push origin ${tagName}`;
|
|
326
|
+
await $`git push --force origin ${tagName}`;
|
|
349
327
|
console.log(`ā
Created and pushed git tag: ${tagName}`);
|
|
350
328
|
|
|
351
329
|
// Create GitHub release
|
|
352
330
|
// eslint-disable-next-line no-await-in-loop
|
|
353
|
-
const res = await octokit
|
|
331
|
+
const res = await upsertGitHubRelease(octokit, {
|
|
354
332
|
owner: repoInfo.owner,
|
|
355
333
|
repo: repoInfo.repo,
|
|
356
334
|
tag_name: tagName,
|
|
@@ -380,7 +358,14 @@ async function getLastCanaryTag() {
|
|
|
380
358
|
// Tag might not exist locally, which is fine
|
|
381
359
|
}
|
|
382
360
|
|
|
383
|
-
|
|
361
|
+
try {
|
|
362
|
+
await $`git fetch origin tag ${CANARY_TAG}`;
|
|
363
|
+
} catch (err) {
|
|
364
|
+
// Tag might not exist on the remote yet (first canary run), which is fine
|
|
365
|
+
if (!(/** @type {Error} */ (err).message?.includes("couldn't find remote ref"))) {
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
384
369
|
const { stdout: remoteCanaryTag } = await $`git ls-remote --tags origin ${CANARY_TAG}`;
|
|
385
370
|
return remoteCanaryTag.trim() ? CANARY_TAG : null;
|
|
386
371
|
}
|
|
@@ -492,16 +477,23 @@ async function publishCanaryVersions(
|
|
|
492
477
|
}
|
|
493
478
|
|
|
494
479
|
// Third pass: publish only the changed packages using recursive publish
|
|
495
|
-
|
|
480
|
+
/** @type {Set<string>} */
|
|
481
|
+
const publishedNames = new Set();
|
|
496
482
|
try {
|
|
497
483
|
console.log(`š¤ Publishing ${packagesToPublish.length} canary versions...`);
|
|
498
|
-
await publishPackages(packagesToPublish, {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
console.log(`ā
Published ${pkg.name}@${canaryVersion}`);
|
|
484
|
+
const publishedPackages = await publishPackages(packagesToPublish, {
|
|
485
|
+
dryRun: options.dryRun,
|
|
486
|
+
noGitChecks: true,
|
|
487
|
+
tag: CANARY_TAG,
|
|
503
488
|
});
|
|
504
|
-
|
|
489
|
+
|
|
490
|
+
// Only use package names from the report summary, not versions.
|
|
491
|
+
// pnpm-publish-summary.json reports the version from the workspace
|
|
492
|
+
// package.json, which is wrong for packages with publishConfig.directory.
|
|
493
|
+
for (const { name } of publishedPackages) {
|
|
494
|
+
publishedNames.add(name);
|
|
495
|
+
console.log(`ā
Published ${name}@${canaryVersions.get(name)}`);
|
|
496
|
+
}
|
|
505
497
|
} finally {
|
|
506
498
|
// Always restore original package.json files in parallel
|
|
507
499
|
console.log('\nš Restoring original package.json files...');
|
|
@@ -515,16 +507,26 @@ async function publishCanaryVersions(
|
|
|
515
507
|
await Promise.all(restorePromises);
|
|
516
508
|
}
|
|
517
509
|
|
|
518
|
-
if (
|
|
519
|
-
// Create
|
|
520
|
-
await createCanaryTag(options.dryRun);
|
|
521
|
-
|
|
522
|
-
// Create GitHub releases if requested
|
|
510
|
+
if (publishedNames.size > 0) {
|
|
511
|
+
// Create GitHub releases only for actually published packages
|
|
523
512
|
if (options.githubRelease) {
|
|
524
|
-
|
|
513
|
+
const actuallyPublished = packagesToPublish.filter((pkg) => publishedNames.has(pkg.name));
|
|
514
|
+
await createGitHubReleasesForPackages(actuallyPublished, canaryVersions, changelogs, options);
|
|
525
515
|
}
|
|
526
516
|
|
|
527
|
-
|
|
517
|
+
// Only advance the canary tag if all expected packages were published.
|
|
518
|
+
// Otherwise the tag would skip over unpublished packages and they'd
|
|
519
|
+
// never be retried on the next run.
|
|
520
|
+
const missing = packagesToPublish.filter((pkg) => !publishedNames.has(pkg.name));
|
|
521
|
+
if (missing.length === 0) {
|
|
522
|
+
await createCanaryTag(options.dryRun);
|
|
523
|
+
console.log('\nš All canary versions published successfully!');
|
|
524
|
+
} else {
|
|
525
|
+
const missingNames = missing.map((pkg) => pkg.name).join(', ');
|
|
526
|
+
console.warn(
|
|
527
|
+
`\nā ļø Canary tag not advanced, some packages failed to publish: ${missingNames}`,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
528
530
|
}
|
|
529
531
|
}
|
|
530
532
|
|
|
@@ -542,10 +544,16 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
542
544
|
type: 'boolean',
|
|
543
545
|
default: false,
|
|
544
546
|
description: 'Create GitHub releases for published packages',
|
|
547
|
+
})
|
|
548
|
+
.option('filter', {
|
|
549
|
+
type: 'string',
|
|
550
|
+
array: true,
|
|
551
|
+
description:
|
|
552
|
+
'Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.',
|
|
545
553
|
});
|
|
546
554
|
},
|
|
547
555
|
handler: async (argv) => {
|
|
548
|
-
const { dryRun = false, githubRelease = false } = argv;
|
|
556
|
+
const { dryRun = false, githubRelease = false, filter = [] } = argv;
|
|
549
557
|
|
|
550
558
|
const options = { dryRun, githubRelease };
|
|
551
559
|
|
|
@@ -557,22 +565,46 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
557
565
|
console.log('š GitHub releases will be created for published packages\n');
|
|
558
566
|
}
|
|
559
567
|
|
|
560
|
-
//
|
|
568
|
+
// All public packages ā needed by publishCanaryVersions to bump versions and update
|
|
569
|
+
// package.json across the entire workspace, even for packages not being published.
|
|
561
570
|
console.log('š Discovering all workspace packages...');
|
|
562
|
-
const
|
|
571
|
+
const filteredPackages = await getWorkspacePackages({ publicOnly: true, filter });
|
|
563
572
|
|
|
564
|
-
if (
|
|
565
|
-
console.log(
|
|
573
|
+
if (filteredPackages.length === 0) {
|
|
574
|
+
console.log(
|
|
575
|
+
`ā ļø No publishable packages found in workspace${filter.length > 0 ? ` matching filter "${filter.join(', ')}"` : ''}`,
|
|
576
|
+
);
|
|
566
577
|
return;
|
|
567
578
|
}
|
|
568
579
|
|
|
569
|
-
|
|
580
|
+
if (filter.length > 0) {
|
|
581
|
+
console.log('š Validating workspace dependencies for filtered packages...');
|
|
582
|
+
|
|
583
|
+
const { issues } = await validatePublishDependencies(filteredPackages);
|
|
584
|
+
|
|
585
|
+
if (issues.length > 0) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`Invalid dependencies structure of packages to be published -
|
|
588
|
+
${issues.join('\n ')}
|
|
589
|
+
`,
|
|
590
|
+
{
|
|
591
|
+
cause: issues,
|
|
592
|
+
},
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
console.log('ā
All workspace dependency requirements satisfied');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Check for canary tag to determine selective publishing.
|
|
600
|
+
// --filter is applied on top of sinceRef: publish only packages that have
|
|
601
|
+
// changed since the last canary tag AND match the filter.
|
|
570
602
|
const canaryTag = await getLastCanaryTag();
|
|
571
603
|
|
|
572
604
|
console.log('š Checking for packages changed since canary tag...');
|
|
573
605
|
const packages = canaryTag
|
|
574
|
-
? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true })
|
|
575
|
-
:
|
|
606
|
+
? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true, filter })
|
|
607
|
+
: filteredPackages;
|
|
576
608
|
|
|
577
609
|
console.log(`š Found ${packages.length} packages(s) for canary publishing:`);
|
|
578
610
|
packages.forEach((pkg) => {
|
|
@@ -581,7 +613,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
581
613
|
|
|
582
614
|
// Fetch version info for all packages in parallel
|
|
583
615
|
console.log('\nš Fetching package version information...');
|
|
584
|
-
const versionInfoPromises =
|
|
616
|
+
const versionInfoPromises = filteredPackages.map(async (pkg) => {
|
|
585
617
|
const versionInfo = await getPackageVersionInfo(pkg.name, pkg.version);
|
|
586
618
|
return { packageName: pkg.name, versionInfo };
|
|
587
619
|
});
|
|
@@ -593,7 +625,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
593
625
|
packageVersionInfo.set(packageName, versionInfo);
|
|
594
626
|
}
|
|
595
627
|
|
|
596
|
-
await publishCanaryVersions(packages,
|
|
628
|
+
await publishCanaryVersions(packages, filteredPackages, packageVersionInfo, options);
|
|
597
629
|
|
|
598
630
|
console.log('\nš Publishing complete!');
|
|
599
631
|
},
|