@mui/internal-code-infra 0.0.4-canary.1 ā 0.0.4-canary.100
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/LICENSE +1 -1
- package/README.md +22 -8
- package/build/babel-config.d.mts +11 -3
- package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
- package/build/brokenLinksChecker/index.d.mts +192 -2
- package/build/changelog/fetchChangelogs.d.mts +3 -0
- package/build/changelog/types.d.ts +3 -1
- package/build/cli/cmdArgosPush.d.mts +8 -2
- package/build/cli/cmdBuild.d.mts +48 -2
- package/build/cli/cmdCopyFiles.d.mts +14 -2
- package/build/cli/cmdExtractErrorCodes.d.mts +2 -2
- package/build/cli/cmdGenerateChangelog.d.mts +17 -2
- package/build/cli/cmdGithubAuth.d.mts +2 -2
- package/build/cli/cmdListWorkspaces.d.mts +16 -2
- package/build/cli/cmdNetlifyIgnore.d.mts +12 -2
- package/build/cli/cmdPublish.d.mts +24 -4
- package/build/cli/cmdPublishCanary.d.mts +21 -2
- package/build/cli/cmdPublishNewPackage.d.mts +10 -2
- package/build/cli/cmdRenameScope.d.mts +18 -0
- package/build/cli/cmdSetVersionOverrides.d.mts +5 -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-floating-cleanup.d.mts +5 -0
- package/build/eslint/mui/rules/no-guarded-throw.d.mts +31 -0
- package/build/eslint/mui/rules/no-presentation-role.d.mts +5 -0
- package/build/eslint/mui/rules/no-restricted-resolved-imports.d.mts +8 -2
- package/build/eslint/mui/rules/nodeEnvUtils.d.mts +18 -0
- 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/babel.d.mts +7 -1
- package/build/utils/build.d.mts +59 -37
- package/build/utils/extractErrorCodes.d.mts +9 -0
- package/build/utils/git.d.mts +16 -0
- package/build/utils/github.d.mts +13 -1
- package/build/utils/pnpm.d.mts +200 -7
- package/build/utils/testUtils.d.mts +28 -0
- package/build/utils/typescript.d.mts +6 -16
- package/package.json +83 -56
- package/src/babel-config.mjs +9 -3
- package/src/brokenLinksChecker/crawlWorker.mjs +240 -0
- package/src/brokenLinksChecker/index.mjs +263 -188
- package/src/build-env.d.ts +13 -0
- package/src/changelog/fetchChangelogs.mjs +8 -2
- package/src/changelog/renderChangelog.mjs +1 -1
- package/src/changelog/types.ts +3 -1
- package/src/cli/cmdBuild.mjs +51 -85
- package/src/cli/cmdListWorkspaces.mjs +16 -3
- package/src/cli/cmdNetlifyIgnore.mjs +34 -93
- package/src/cli/cmdPublish.mjs +105 -36
- package/src/cli/cmdPublishCanary.mjs +145 -109
- package/src/cli/cmdPublishNewPackage.mjs +31 -8
- package/src/cli/cmdRenameScope.mjs +76 -0
- package/src/cli/cmdSetVersionOverrides.mjs +8 -9
- package/src/cli/cmdVale.mjs +513 -0
- package/src/cli/index.mjs +4 -0
- package/src/cli/packageJson.d.ts +2 -3
- package/src/eslint/baseConfig.mjs +51 -19
- package/src/eslint/docsConfig.mjs +2 -1
- package/src/eslint/mui/config.mjs +24 -4
- package/src/eslint/mui/index.mjs +6 -0
- package/src/eslint/mui/rules/no-floating-cleanup.mjs +187 -0
- package/src/eslint/mui/rules/no-guarded-throw.mjs +115 -0
- package/src/eslint/mui/rules/no-presentation-role.mjs +60 -0
- package/src/eslint/mui/rules/nodeEnvUtils.mjs +52 -0
- package/src/eslint/mui/rules/require-dev-wrapper.mjs +25 -40
- 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/gitDiff.mjs +43 -0
- package/src/remark/noSpaceInLinks.mjs +42 -0
- package/src/remark/straightQuotes.mjs +31 -0
- package/src/remark/tableAlignment.mjs +23 -0
- package/src/remark/terminalLanguage.mjs +19 -0
- package/src/stylelint/index.mjs +2 -2
- package/src/untyped-plugins.d.ts +14 -14
- package/src/utils/build.mjs +604 -270
- package/src/utils/git.mjs +27 -7
- package/src/utils/pnpm.mjs +549 -26
- package/src/utils/testUtils.mjs +53 -0
- package/src/utils/typescript.mjs +23 -42
- package/vale/.vale.ini +1 -0
- package/vale/styles/MUI/CorrectReferenceAllCases.yml +42 -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
- package/build/markdownlint/duplicate-h1.d.mts +0 -56
- package/build/markdownlint/git-diff.d.mts +0 -11
- package/build/markdownlint/index.d.mts +0 -52
- package/build/markdownlint/straight-quotes.d.mts +0 -11
- package/build/markdownlint/table-alignment.d.mts +0 -11
- package/build/markdownlint/terminal-language.d.mts +0 -11
- package/src/brokenLinksChecker/__fixtures__/static-site/broken-links.html +0 -20
- package/src/brokenLinksChecker/__fixtures__/static-site/broken-targets.html +0 -22
- package/src/brokenLinksChecker/__fixtures__/static-site/example.md +0 -20
- package/src/brokenLinksChecker/__fixtures__/static-site/external-links.html +0 -21
- package/src/brokenLinksChecker/__fixtures__/static-site/ignored-page.html +0 -17
- package/src/brokenLinksChecker/__fixtures__/static-site/index.html +0 -28
- package/src/brokenLinksChecker/__fixtures__/static-site/known-targets.json +0 -5
- package/src/brokenLinksChecker/__fixtures__/static-site/nested/page.html +0 -21
- package/src/brokenLinksChecker/__fixtures__/static-site/orphaned-page.html +0 -20
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-api-links.html +0 -20
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-custom-targets.html +0 -24
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-ignored-content.html +0 -28
- package/src/brokenLinksChecker/__fixtures__/static-site/page-with-known-target-links.html +0 -19
- package/src/brokenLinksChecker/__fixtures__/static-site/unclosed-tags.html +0 -1
- package/src/brokenLinksChecker/__fixtures__/static-site/valid.html +0 -20
- package/src/brokenLinksChecker/__fixtures__/static-site/with-anchors.html +0 -31
- package/src/brokenLinksChecker/index.test.ts +0 -261
- package/src/changelog/categorizeCommits.test.ts +0 -319
- package/src/changelog/filterCommits.test.ts +0 -363
- package/src/changelog/parseCommitLabels.test.ts +0 -509
- package/src/changelog/renderChangelog.test.ts +0 -378
- package/src/changelog/sortSections.test.ts +0 -199
- package/src/eslint/mui/rules/add-undef-to-optional.test.mjs +0 -361
- package/src/eslint/mui/rules/consistent-production-guard.test.mjs +0 -162
- package/src/eslint/mui/rules/disallow-active-elements-as-key-event-target.test.mjs +0 -66
- package/src/eslint/mui/rules/disallow-react-api-in-server-components.test.mjs +0 -305
- package/src/eslint/mui/rules/docgen-ignore-before-comment.test.mjs +0 -52
- package/src/eslint/mui/rules/flatten-parentheses.test.mjs +0 -245
- package/src/eslint/mui/rules/mui-name-matches-component-name.test.mjs +0 -247
- package/src/eslint/mui/rules/no-empty-box.test.mjs +0 -40
- package/src/eslint/mui/rules/no-styled-box.test.mjs +0 -73
- package/src/eslint/mui/rules/require-dev-wrapper.test.mjs +0 -265
- package/src/eslint/mui/rules/rules-of-use-theme-variants.test.mjs +0 -149
- package/src/eslint/mui/rules/straight-quotes.test.mjs +0 -67
- package/src/markdownlint/duplicate-h1.mjs +0 -69
- package/src/markdownlint/git-diff.mjs +0 -31
- package/src/markdownlint/index.mjs +0 -62
- package/src/markdownlint/straight-quotes.mjs +0 -26
- package/src/markdownlint/table-alignment.mjs +0 -42
- package/src/markdownlint/terminal-language.mjs +0 -19
- package/src/utils/build.test.mjs +0 -705
- package/src/utils/template.test.mjs +0 -133
- package/src/utils/typescript.test.mjs +0 -380
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,8 +18,14 @@ 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
|
-
|
|
21
|
+
import {
|
|
22
|
+
getPackagesNeedingManualPublish,
|
|
23
|
+
getReleaseVersion,
|
|
24
|
+
getWorkspacePackages,
|
|
25
|
+
publishPackages,
|
|
26
|
+
validatePublishDependencies,
|
|
27
|
+
} from '../utils/pnpm.mjs';
|
|
28
|
+
import { getCurrentGitSha, getRepositoryInfo, remoteGitTagExists } from '../utils/git.mjs';
|
|
22
29
|
|
|
23
30
|
const isCI = envCI().isCi;
|
|
24
31
|
|
|
@@ -33,18 +40,9 @@ function getOctokit() {
|
|
|
33
40
|
* @property {string} tag NPM dist tag to publish to
|
|
34
41
|
* @property {boolean} ci Runs in CI environment
|
|
35
42
|
* @property {string} [sha] Git SHA to use for the GitHub release workflow (local only)
|
|
43
|
+
* @property {string[]} [filter] Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.
|
|
36
44
|
*/
|
|
37
45
|
|
|
38
|
-
/**
|
|
39
|
-
* Get the version to release from the root package.json
|
|
40
|
-
* @returns {Promise<string | null>} Version string
|
|
41
|
-
*/
|
|
42
|
-
async function getReleaseVersion() {
|
|
43
|
-
const result = await $`pnpm pkg get version`;
|
|
44
|
-
const version = JSON.parse(result.stdout.trim());
|
|
45
|
-
return semver.valid(version);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
46
|
/**
|
|
49
47
|
* Parse changelog to extract content for a specific version
|
|
50
48
|
* @param {string} changelogPath - Path to CHANGELOG.md
|
|
@@ -134,8 +132,19 @@ async function checkGitHubReleaseExists(owner, repo, version) {
|
|
|
134
132
|
*/
|
|
135
133
|
async function createGitTag(version, dryRun = false) {
|
|
136
134
|
const tagName = `v${version}`;
|
|
135
|
+
// The CI smoke-test runs with a read-only token that can't push. It sets this
|
|
136
|
+
// so we tolerate the push failure explicitly, instead of assuming any dry-run
|
|
137
|
+
// permission error is expected.
|
|
138
|
+
const isPublishTest = process.env.IS_PUBLISH_TEST === 'true';
|
|
137
139
|
|
|
138
140
|
try {
|
|
141
|
+
// Skip if the tag already exists locally. Tag creation is local and fails if it exists.
|
|
142
|
+
const { stdout: existingTag } = await $`git tag -l ${tagName}`;
|
|
143
|
+
if (existingTag.trim()) {
|
|
144
|
+
console.log(`š·ļø Git tag ${tagName} already exists, skipping`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
139
148
|
await $({
|
|
140
149
|
env: {
|
|
141
150
|
...process.env,
|
|
@@ -143,10 +152,28 @@ async function createGitTag(version, dryRun = false) {
|
|
|
143
152
|
GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
|
|
144
153
|
},
|
|
145
154
|
})`git tag -a ${tagName} -m ${`Version ${version}`}`;
|
|
146
|
-
const pushArgs = dryRun ? ['--dry-run'] : [];
|
|
147
|
-
await $({ stdio: 'inherit' })`git push origin ${tagName} ${pushArgs}`;
|
|
148
155
|
|
|
149
|
-
|
|
156
|
+
if (dryRun) {
|
|
157
|
+
// `git push --dry-run` still authenticates and needs push access. When the token
|
|
158
|
+
// can push, validate it for real; when it can't, only tolerate the failure if this
|
|
159
|
+
// is an explicit publish smoke-test and the error is a genuine permission denial.
|
|
160
|
+
try {
|
|
161
|
+
// Don't inherit stdio so stderr is captured and matchable below.
|
|
162
|
+
await $`git push origin ${tagName} --dry-run`;
|
|
163
|
+
console.log(`š·ļø Created git tag ${tagName} (dry-run)`);
|
|
164
|
+
} catch (/** @type {any} */ pushError) {
|
|
165
|
+
const message = pushError.stderr || pushError.message || '';
|
|
166
|
+
if (isPublishTest && /permission|403|denied/i.test(message)) {
|
|
167
|
+
console.log(`š·ļø Created git tag ${tagName} (dry-run, no push permission, skipped push)`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
throw pushError;
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
await $({ stdio: 'inherit' })`git push origin ${tagName}`;
|
|
175
|
+
|
|
176
|
+
console.log(`š·ļø Created and pushed git tag ${tagName}`);
|
|
150
177
|
} catch (/** @type {any} */ error) {
|
|
151
178
|
throw new Error(`Failed to create git tag: ${error.message}`);
|
|
152
179
|
}
|
|
@@ -189,18 +216,11 @@ async function validateGitHubRelease(version) {
|
|
|
189
216
|
* Publish packages to npm
|
|
190
217
|
* @param {PublicPackage[]} packages - Packages to publish
|
|
191
218
|
* @param {PublishOptions} options - Publishing options
|
|
192
|
-
* @returns {Promise<
|
|
219
|
+
* @returns {Promise<PublishSummaryEntry[]>}
|
|
193
220
|
*/
|
|
194
221
|
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
222
|
// Use pnpm's built-in duplicate checking - no need to check versions ourselves
|
|
202
|
-
|
|
203
|
-
console.log('ā
Successfully published to npm');
|
|
223
|
+
return publishPackages(packages, options);
|
|
204
224
|
}
|
|
205
225
|
|
|
206
226
|
/**
|
|
@@ -260,10 +280,16 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
260
280
|
.option('sha', {
|
|
261
281
|
type: 'string',
|
|
262
282
|
description: 'Git SHA to use for the GitHub release workflow (local only)',
|
|
283
|
+
})
|
|
284
|
+
.option('filter', {
|
|
285
|
+
type: 'string',
|
|
286
|
+
array: true,
|
|
287
|
+
description:
|
|
288
|
+
'Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.',
|
|
263
289
|
});
|
|
264
290
|
},
|
|
265
291
|
handler: async (argv) => {
|
|
266
|
-
const { dryRun = false, githubRelease = false, tag = 'latest', sha } = argv;
|
|
292
|
+
const { dryRun = false, githubRelease = false, tag = 'latest', sha, filter = [] } = argv;
|
|
267
293
|
|
|
268
294
|
if (isCI && !argv.ci) {
|
|
269
295
|
console.error(
|
|
@@ -290,13 +316,34 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
290
316
|
// Get all packages
|
|
291
317
|
console.log('š Discovering all workspace packages...');
|
|
292
318
|
|
|
293
|
-
const
|
|
319
|
+
const filteredPackages = await getWorkspacePackages({ publicOnly: true, filter });
|
|
294
320
|
|
|
295
|
-
if (
|
|
296
|
-
console.log(
|
|
321
|
+
if (filteredPackages.length === 0) {
|
|
322
|
+
console.log(
|
|
323
|
+
`ā ļø No publishable packages found in workspace${filter.length > 0 ? ` matching filter "${filter.join(', ')}"` : ''}`,
|
|
324
|
+
);
|
|
297
325
|
return;
|
|
298
326
|
}
|
|
299
327
|
|
|
328
|
+
if (filter.length > 0) {
|
|
329
|
+
console.log('š Validating workspace dependencies for filtered packages...');
|
|
330
|
+
|
|
331
|
+
const { issues } = await validatePublishDependencies(filteredPackages);
|
|
332
|
+
|
|
333
|
+
if (issues.length > 0) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`Invalid dependencies structure of packages to be published -
|
|
336
|
+
${issues.join('\n ')}
|
|
337
|
+
`,
|
|
338
|
+
{
|
|
339
|
+
cause: issues,
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
console.log('ā
All workspace dependency requirements satisfied');
|
|
345
|
+
}
|
|
346
|
+
|
|
300
347
|
// Get version from root package.json
|
|
301
348
|
const version = await getReleaseVersion();
|
|
302
349
|
|
|
@@ -311,21 +358,41 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
311
358
|
githubReleaseData = await validateGitHubRelease(version);
|
|
312
359
|
}
|
|
313
360
|
|
|
314
|
-
const newPackages = await
|
|
361
|
+
const newPackages = await getPackagesNeedingManualPublish(filteredPackages);
|
|
315
362
|
|
|
316
363
|
if (newPackages.length > 0) {
|
|
364
|
+
const newPackageNames = newPackages.map((pkg) => pkg.name).join(', ');
|
|
317
365
|
throw new Error(
|
|
318
|
-
`The following packages are new and need to be published manually first: ${
|
|
319
|
-
', ',
|
|
320
|
-
)}. Read more about it here: https://github.com/mui/mui-public/blob/master/packages/code-infra/README.md#adding-and-publishing-new-packages`,
|
|
366
|
+
`The following packages are new and need to be published to npm manually first: ${newPackageNames}. Read more about it here: https://github.com/mui/mui-public/blob/master/packages/code-infra/README.md#adding-and-publishing-new-packages`,
|
|
321
367
|
);
|
|
322
368
|
}
|
|
323
369
|
|
|
324
370
|
// Publish to npm (pnpm handles duplicate checking automatically)
|
|
325
371
|
// No git checks, we'll do our own
|
|
326
|
-
|
|
372
|
+
console.log('\nš¦ Publishing packages to npm...');
|
|
373
|
+
const publishedPackages = await publishToNpm(filteredPackages, {
|
|
374
|
+
dryRun,
|
|
375
|
+
noGitChecks: true,
|
|
376
|
+
tag,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (publishedPackages.length === 0) {
|
|
380
|
+
console.log('ā¹ļø No packages were published (all may already be up to date on npm)');
|
|
381
|
+
console.log('\nš Nothing to publish, skipping git tag and GitHub release.');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
327
384
|
|
|
328
|
-
|
|
385
|
+
publishedPackages.forEach((pkg) => {
|
|
386
|
+
console.log(`ā
Published ${pkg.name}@${pkg.version}`);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Tag the root version when it's new. Arbitrary package publishes that don't
|
|
390
|
+
// bump the root version don't create a new tag, so skip in that case.
|
|
391
|
+
if (await remoteGitTagExists(`v${version}`)) {
|
|
392
|
+
console.log(`ā¹ļø Git tag v${version} already exists, skipping tag creation.`);
|
|
393
|
+
} else {
|
|
394
|
+
await createGitTag(version, dryRun);
|
|
395
|
+
}
|
|
329
396
|
|
|
330
397
|
// Create GitHub release or git tag after successful npm publishing
|
|
331
398
|
if (githubRelease && githubReleaseData) {
|
|
@@ -353,11 +420,13 @@ const PUBLISH_WORKFLOW_ID = `.github/${WORKFLOW_PATH}`;
|
|
|
353
420
|
*/
|
|
354
421
|
async function triggerLocalGithubPublishWorkflow(opts) {
|
|
355
422
|
console.log(`š Checking if there are new packages to publish in the workspace...`);
|
|
356
|
-
const newPackages = await
|
|
423
|
+
const newPackages = await getPackagesNeedingManualPublish(
|
|
424
|
+
await getWorkspacePackages({ publicOnly: true }),
|
|
425
|
+
);
|
|
357
426
|
if (newPackages.length) {
|
|
358
427
|
console.warn(
|
|
359
428
|
`ā ļø Found new packages that should be published to npm first before triggering a release:
|
|
360
|
-
|
|
429
|
+
${newPackages.map((pkg) => ` * ${pkg.name}`).join('\n')}
|
|
361
430
|
Please run the command "${chalk.bold('pnpm code-infra publish-new-package')}" first to publish and configure npm.`,
|
|
362
431
|
);
|
|
363
432
|
return;
|
|
@@ -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';
|
|
@@ -90,7 +93,11 @@ async function fetchCommitsForPackage({ packagePath }) {
|
|
|
90
93
|
return results;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
const AUTHOR_EXCLUDE_LIST =
|
|
96
|
+
const AUTHOR_EXCLUDE_LIST = new Set([
|
|
97
|
+
'renovate[bot]',
|
|
98
|
+
'dependabot[bot]',
|
|
99
|
+
'code-infra-renovate[bot]',
|
|
100
|
+
]);
|
|
94
101
|
|
|
95
102
|
/**
|
|
96
103
|
* @param {string} message
|
|
@@ -118,80 +125,6 @@ function cleanupCommitMessage(message) {
|
|
|
118
125
|
return `${prefix}${msg}`.trim();
|
|
119
126
|
}
|
|
120
127
|
|
|
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
128
|
/**
|
|
196
129
|
* Prepare changelog data for packages using GitHub API
|
|
197
130
|
* @param {PublicPackage[]} packagesToPublish - Packages that will be published
|
|
@@ -215,7 +148,7 @@ async function prepareChangelogsFromGitCli(packagesToPublish, allPackages, canar
|
|
|
215
148
|
// Exclude commits authored by bots
|
|
216
149
|
.filter(
|
|
217
150
|
// We want to allow commits from copilot or other AI tools, so only filter known bots
|
|
218
|
-
(commit) => !AUTHOR_EXCLUDE_LIST.
|
|
151
|
+
(commit) => !AUTHOR_EXCLUDE_LIST.has(commit.author),
|
|
219
152
|
)
|
|
220
153
|
.map((commit) => `- ${cleanupCommitMessage(commit.message)} by ${commit.author}`);
|
|
221
154
|
|
|
@@ -224,14 +157,23 @@ async function prepareChangelogsFromGitCli(packagesToPublish, allPackages, canar
|
|
|
224
157
|
}
|
|
225
158
|
}),
|
|
226
159
|
);
|
|
227
|
-
// Second pass: check for dependency updates in other packages not part of git history
|
|
228
|
-
const
|
|
229
|
-
const
|
|
160
|
+
// Second pass: check for dependency updates in other packages not part of git history.
|
|
161
|
+
const workspacePathByName = new Map(allPackages.map((pkg) => [pkg.name, pkg.path]));
|
|
162
|
+
const publishedNames = new Set(packagesToPublish.map((p) => p.name));
|
|
163
|
+
|
|
164
|
+
const transitiveDepSets = await Promise.all(
|
|
165
|
+
allPackages.map((pkg) =>
|
|
166
|
+
getTransitiveDependencies([pkg.name], {
|
|
167
|
+
includeDev: false,
|
|
168
|
+
workspacePathByName,
|
|
169
|
+
}),
|
|
170
|
+
),
|
|
171
|
+
);
|
|
230
172
|
|
|
231
173
|
for (let i = 0; i < allPackages.length; i += 1) {
|
|
232
174
|
const pkg = allPackages[i];
|
|
233
|
-
const depsToPublish =
|
|
234
|
-
|
|
175
|
+
const depsToPublish = [...transitiveDepSets[i]].filter(
|
|
176
|
+
(dep) => dep !== pkg.name && publishedNames.has(dep),
|
|
235
177
|
);
|
|
236
178
|
if (depsToPublish.length === 0) {
|
|
237
179
|
continue;
|
|
@@ -290,6 +232,41 @@ async function prepareChangelogsForPackages(packagesToPublish, allPackages, cana
|
|
|
290
232
|
return changelogs;
|
|
291
233
|
}
|
|
292
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Create or replace a GitHub release. Attempts to create the release first,
|
|
237
|
+
* and if it already exists (422), deletes the existing one and retries.
|
|
238
|
+
*
|
|
239
|
+
* @param {InstanceType<typeof Octokit>} octokit
|
|
240
|
+
* @param {NonNullable<Parameters<Octokit['repos']['createRelease']>[0]>} params
|
|
241
|
+
*/
|
|
242
|
+
async function upsertGitHubRelease(octokit, params) {
|
|
243
|
+
try {
|
|
244
|
+
return await octokit.repos.createRelease(params);
|
|
245
|
+
} catch (/** @type {any} */ error) {
|
|
246
|
+
const isAlreadyExists =
|
|
247
|
+
error.status === 422 &&
|
|
248
|
+
error.response?.data?.errors?.some(
|
|
249
|
+
(/** @type {any} */ err) => err.code === 'already_exists' && err.field === 'tag_name',
|
|
250
|
+
);
|
|
251
|
+
if (!isAlreadyExists) {
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Release already exists ā delete and recreate
|
|
257
|
+
const existing = await octokit.repos.getReleaseByTag({
|
|
258
|
+
owner: params.owner,
|
|
259
|
+
repo: params.repo,
|
|
260
|
+
tag: params.tag_name,
|
|
261
|
+
});
|
|
262
|
+
await octokit.repos.deleteRelease({
|
|
263
|
+
owner: params.owner,
|
|
264
|
+
repo: params.repo,
|
|
265
|
+
release_id: existing.data.id,
|
|
266
|
+
});
|
|
267
|
+
return octokit.repos.createRelease(params);
|
|
268
|
+
}
|
|
269
|
+
|
|
293
270
|
/**
|
|
294
271
|
* Create GitHub releases and tags for published packages
|
|
295
272
|
* @param {PublicPackage[]} publishedPackages - Packages that were published
|
|
@@ -342,15 +319,20 @@ async function createGitHubReleasesForPackages(
|
|
|
342
319
|
GIT_COMMITTER_NAME: 'Code infra',
|
|
343
320
|
GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
|
|
344
321
|
},
|
|
345
|
-
})`git tag -
|
|
322
|
+
})`git tag -fa ${tagName} -m ${`Canary release ${pkg.name}@${version}`}`;
|
|
346
323
|
|
|
324
|
+
// Force-push to handle retries where the tag already exists from a previous
|
|
325
|
+
// failed publish. The npm registry is the source of truth for published
|
|
326
|
+
// versions, so it's safe to rewrite a tag even if it points to a different
|
|
327
|
+
// commit ā it just means the prior publish for this version failed partway
|
|
328
|
+
// through and the GitHub release needs to be recreated.
|
|
347
329
|
// eslint-disable-next-line no-await-in-loop
|
|
348
|
-
await $`git push origin ${tagName}`;
|
|
330
|
+
await $`git push --force origin ${tagName}`;
|
|
349
331
|
console.log(`ā
Created and pushed git tag: ${tagName}`);
|
|
350
332
|
|
|
351
333
|
// Create GitHub release
|
|
352
334
|
// eslint-disable-next-line no-await-in-loop
|
|
353
|
-
const res = await octokit
|
|
335
|
+
const res = await upsertGitHubRelease(octokit, {
|
|
354
336
|
owner: repoInfo.owner,
|
|
355
337
|
repo: repoInfo.repo,
|
|
356
338
|
tag_name: tagName,
|
|
@@ -380,7 +362,14 @@ async function getLastCanaryTag() {
|
|
|
380
362
|
// Tag might not exist locally, which is fine
|
|
381
363
|
}
|
|
382
364
|
|
|
383
|
-
|
|
365
|
+
try {
|
|
366
|
+
await $`git fetch origin tag ${CANARY_TAG}`;
|
|
367
|
+
} catch (err) {
|
|
368
|
+
// Tag might not exist on the remote yet (first canary run), which is fine
|
|
369
|
+
if (!(/** @type {Error} */ (err).message?.includes("couldn't find remote ref"))) {
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
384
373
|
const { stdout: remoteCanaryTag } = await $`git ls-remote --tags origin ${CANARY_TAG}`;
|
|
385
374
|
return remoteCanaryTag.trim() ? CANARY_TAG : null;
|
|
386
375
|
}
|
|
@@ -492,16 +481,23 @@ async function publishCanaryVersions(
|
|
|
492
481
|
}
|
|
493
482
|
|
|
494
483
|
// Third pass: publish only the changed packages using recursive publish
|
|
495
|
-
|
|
484
|
+
/** @type {Set<string>} */
|
|
485
|
+
const publishedNames = new Set();
|
|
496
486
|
try {
|
|
497
487
|
console.log(`š¤ Publishing ${packagesToPublish.length} canary versions...`);
|
|
498
|
-
await publishPackages(packagesToPublish, {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
console.log(`ā
Published ${pkg.name}@${canaryVersion}`);
|
|
488
|
+
const publishedPackages = await publishPackages(packagesToPublish, {
|
|
489
|
+
dryRun: options.dryRun,
|
|
490
|
+
noGitChecks: true,
|
|
491
|
+
tag: CANARY_TAG,
|
|
503
492
|
});
|
|
504
|
-
|
|
493
|
+
|
|
494
|
+
// Only use package names from the report summary, not versions.
|
|
495
|
+
// pnpm-publish-summary.json reports the version from the workspace
|
|
496
|
+
// package.json, which is wrong for packages with publishConfig.directory.
|
|
497
|
+
for (const { name } of publishedPackages) {
|
|
498
|
+
publishedNames.add(name);
|
|
499
|
+
console.log(`ā
Published ${name}@${canaryVersions.get(name)}`);
|
|
500
|
+
}
|
|
505
501
|
} finally {
|
|
506
502
|
// Always restore original package.json files in parallel
|
|
507
503
|
console.log('\nš Restoring original package.json files...');
|
|
@@ -515,16 +511,26 @@ async function publishCanaryVersions(
|
|
|
515
511
|
await Promise.all(restorePromises);
|
|
516
512
|
}
|
|
517
513
|
|
|
518
|
-
if (
|
|
519
|
-
// Create
|
|
520
|
-
await createCanaryTag(options.dryRun);
|
|
521
|
-
|
|
522
|
-
// Create GitHub releases if requested
|
|
514
|
+
if (publishedNames.size > 0) {
|
|
515
|
+
// Create GitHub releases only for actually published packages
|
|
523
516
|
if (options.githubRelease) {
|
|
524
|
-
|
|
517
|
+
const actuallyPublished = packagesToPublish.filter((pkg) => publishedNames.has(pkg.name));
|
|
518
|
+
await createGitHubReleasesForPackages(actuallyPublished, canaryVersions, changelogs, options);
|
|
525
519
|
}
|
|
526
520
|
|
|
527
|
-
|
|
521
|
+
// Only advance the canary tag if all expected packages were published.
|
|
522
|
+
// Otherwise the tag would skip over unpublished packages and they'd
|
|
523
|
+
// never be retried on the next run.
|
|
524
|
+
const missing = packagesToPublish.filter((pkg) => !publishedNames.has(pkg.name));
|
|
525
|
+
if (missing.length === 0) {
|
|
526
|
+
await createCanaryTag(options.dryRun);
|
|
527
|
+
console.log('\nš All canary versions published successfully!');
|
|
528
|
+
} else {
|
|
529
|
+
const missingNames = missing.map((pkg) => pkg.name).join(', ');
|
|
530
|
+
console.warn(
|
|
531
|
+
`\nā ļø Canary tag not advanced, some packages failed to publish: ${missingNames}`,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
528
534
|
}
|
|
529
535
|
}
|
|
530
536
|
|
|
@@ -542,10 +548,16 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
542
548
|
type: 'boolean',
|
|
543
549
|
default: false,
|
|
544
550
|
description: 'Create GitHub releases for published packages',
|
|
551
|
+
})
|
|
552
|
+
.option('filter', {
|
|
553
|
+
type: 'string',
|
|
554
|
+
array: true,
|
|
555
|
+
description:
|
|
556
|
+
'Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.',
|
|
545
557
|
});
|
|
546
558
|
},
|
|
547
559
|
handler: async (argv) => {
|
|
548
|
-
const { dryRun = false, githubRelease = false } = argv;
|
|
560
|
+
const { dryRun = false, githubRelease = false, filter = [] } = argv;
|
|
549
561
|
|
|
550
562
|
const options = { dryRun, githubRelease };
|
|
551
563
|
|
|
@@ -557,22 +569,46 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
557
569
|
console.log('š GitHub releases will be created for published packages\n');
|
|
558
570
|
}
|
|
559
571
|
|
|
560
|
-
//
|
|
572
|
+
// All public packages ā needed by publishCanaryVersions to bump versions and update
|
|
573
|
+
// package.json across the entire workspace, even for packages not being published.
|
|
561
574
|
console.log('š Discovering all workspace packages...');
|
|
562
|
-
const
|
|
575
|
+
const filteredPackages = await getWorkspacePackages({ publicOnly: true, filter });
|
|
563
576
|
|
|
564
|
-
if (
|
|
565
|
-
console.log(
|
|
577
|
+
if (filteredPackages.length === 0) {
|
|
578
|
+
console.log(
|
|
579
|
+
`ā ļø No publishable packages found in workspace${filter.length > 0 ? ` matching filter "${filter.join(', ')}"` : ''}`,
|
|
580
|
+
);
|
|
566
581
|
return;
|
|
567
582
|
}
|
|
568
583
|
|
|
569
|
-
|
|
584
|
+
if (filter.length > 0) {
|
|
585
|
+
console.log('š Validating workspace dependencies for filtered packages...');
|
|
586
|
+
|
|
587
|
+
const { issues } = await validatePublishDependencies(filteredPackages);
|
|
588
|
+
|
|
589
|
+
if (issues.length > 0) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
`Invalid dependencies structure of packages to be published -
|
|
592
|
+
${issues.join('\n ')}
|
|
593
|
+
`,
|
|
594
|
+
{
|
|
595
|
+
cause: issues,
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
console.log('ā
All workspace dependency requirements satisfied');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Check for canary tag to determine selective publishing.
|
|
604
|
+
// --filter is applied on top of sinceRef: publish only packages that have
|
|
605
|
+
// changed since the last canary tag AND match the filter.
|
|
570
606
|
const canaryTag = await getLastCanaryTag();
|
|
571
607
|
|
|
572
608
|
console.log('š Checking for packages changed since canary tag...');
|
|
573
609
|
const packages = canaryTag
|
|
574
|
-
? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true })
|
|
575
|
-
:
|
|
610
|
+
? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true, filter })
|
|
611
|
+
: filteredPackages;
|
|
576
612
|
|
|
577
613
|
console.log(`š Found ${packages.length} packages(s) for canary publishing:`);
|
|
578
614
|
packages.forEach((pkg) => {
|
|
@@ -581,7 +617,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
581
617
|
|
|
582
618
|
// Fetch version info for all packages in parallel
|
|
583
619
|
console.log('\nš Fetching package version information...');
|
|
584
|
-
const versionInfoPromises =
|
|
620
|
+
const versionInfoPromises = filteredPackages.map(async (pkg) => {
|
|
585
621
|
const versionInfo = await getPackageVersionInfo(pkg.name, pkg.version);
|
|
586
622
|
return { packageName: pkg.name, versionInfo };
|
|
587
623
|
});
|
|
@@ -593,7 +629,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
593
629
|
packageVersionInfo.set(packageName, versionInfo);
|
|
594
630
|
}
|
|
595
631
|
|
|
596
|
-
await publishCanaryVersions(packages,
|
|
632
|
+
await publishCanaryVersions(packages, filteredPackages, packageVersionInfo, options);
|
|
597
633
|
|
|
598
634
|
console.log('\nš Publishing complete!');
|
|
599
635
|
},
|