@mui/internal-code-infra 0.0.4-canary.8 ā 0.0.4-canary.80
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 +19 -8
- package/build/babel-config.d.mts +5 -1
- package/build/brokenLinksChecker/crawlWorker.d.mts +1 -0
- package/build/brokenLinksChecker/index.d.mts +45 -2
- package/build/changelog/types.d.ts +1 -1
- package/build/cli/cmdArgosPush.d.mts +2 -2
- package/build/cli/cmdBuild.d.mts +3 -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 +6 -4
- package/build/cli/cmdNetlifyIgnore.d.mts +3 -2
- package/build/cli/cmdPublish.d.mts +4 -2
- package/build/cli/cmdPublishCanary.d.mts +3 -3
- 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-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 +2 -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 +1 -1
- package/build/utils/build.d.mts +56 -37
- package/build/utils/git.d.mts +7 -0
- package/build/utils/github.d.mts +1 -1
- package/build/utils/pnpm.d.mts +81 -2
- package/build/utils/testUtils.d.mts +7 -0
- package/build/utils/typescript.d.mts +6 -16
- package/package.json +65 -41
- package/src/babel-config.mjs +3 -1
- package/src/brokenLinksChecker/crawlWorker.mjs +240 -0
- package/src/brokenLinksChecker/index.mjs +263 -188
- package/src/changelog/fetchChangelogs.mjs +6 -2
- package/src/changelog/renderChangelog.mjs +1 -1
- package/src/changelog/types.ts +1 -1
- package/src/cli/cmdBuild.mjs +51 -85
- package/src/cli/cmdListWorkspaces.mjs +12 -27
- package/src/cli/cmdNetlifyIgnore.mjs +34 -93
- package/src/cli/cmdPublish.mjs +95 -20
- package/src/cli/cmdPublishCanary.mjs +128 -132
- package/src/cli/cmdPublishNewPackage.mjs +27 -6
- package/src/cli/cmdSetVersionOverrides.mjs +8 -9
- package/src/cli/cmdVale.mjs +513 -0
- package/src/cli/index.mjs +2 -0
- package/src/cli/packageJson.d.ts +1 -1
- 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/untyped-plugins.d.ts +11 -11
- package/src/utils/build.mjs +604 -270
- package/src/utils/git.mjs +27 -7
- package/src/utils/pnpm.mjs +277 -10
- package/src/utils/testUtils.mjs +18 -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,12 @@ 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
|
+
getWorkspacePackages,
|
|
23
|
+
publishPackages,
|
|
24
|
+
validatePublishDependencies,
|
|
25
|
+
} from '../utils/pnpm.mjs';
|
|
26
|
+
import { getCurrentGitSha, getRepositoryInfo, remoteGitTagExists } from '../utils/git.mjs';
|
|
22
27
|
|
|
23
28
|
const isCI = envCI().isCi;
|
|
24
29
|
|
|
@@ -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
|
/**
|
|
@@ -40,7 +46,10 @@ function getOctokit() {
|
|
|
40
46
|
* @returns {Promise<string | null>} Version string
|
|
41
47
|
*/
|
|
42
48
|
async function getReleaseVersion() {
|
|
43
|
-
|
|
49
|
+
// `--json` is required: pnpm 11 returns the raw value (e.g. `9.4.0`) for a
|
|
50
|
+
// single field without it, which is not valid JSON. The flag forces quoted
|
|
51
|
+
// output (`"9.4.0"`) across pnpm 9/10/11.
|
|
52
|
+
const result = await $`pnpm pkg get version --json`;
|
|
44
53
|
const version = JSON.parse(result.stdout.trim());
|
|
45
54
|
return semver.valid(version);
|
|
46
55
|
}
|
|
@@ -134,8 +143,19 @@ async function checkGitHubReleaseExists(owner, repo, version) {
|
|
|
134
143
|
*/
|
|
135
144
|
async function createGitTag(version, dryRun = false) {
|
|
136
145
|
const tagName = `v${version}`;
|
|
146
|
+
// The CI smoke-test runs with a read-only token that can't push. It sets this
|
|
147
|
+
// so we tolerate the push failure explicitly, instead of assuming any dry-run
|
|
148
|
+
// permission error is expected.
|
|
149
|
+
const isPublishTest = process.env.IS_PUBLISH_TEST === 'true';
|
|
137
150
|
|
|
138
151
|
try {
|
|
152
|
+
// Skip if the tag already exists locally. Tag creation is local and fails if it exists.
|
|
153
|
+
const { stdout: existingTag } = await $`git tag -l ${tagName}`;
|
|
154
|
+
if (existingTag.trim()) {
|
|
155
|
+
console.log(`š·ļø Git tag ${tagName} already exists, skipping`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
139
159
|
await $({
|
|
140
160
|
env: {
|
|
141
161
|
...process.env,
|
|
@@ -143,10 +163,28 @@ async function createGitTag(version, dryRun = false) {
|
|
|
143
163
|
GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
|
|
144
164
|
},
|
|
145
165
|
})`git tag -a ${tagName} -m ${`Version ${version}`}`;
|
|
146
|
-
const pushArgs = dryRun ? ['--dry-run'] : [];
|
|
147
|
-
await $({ stdio: 'inherit' })`git push origin ${tagName} ${pushArgs}`;
|
|
148
166
|
|
|
149
|
-
|
|
167
|
+
if (dryRun) {
|
|
168
|
+
// `git push --dry-run` still authenticates and needs push access. When the token
|
|
169
|
+
// can push, validate it for real; when it can't, only tolerate the failure if this
|
|
170
|
+
// is an explicit publish smoke-test and the error is a genuine permission denial.
|
|
171
|
+
try {
|
|
172
|
+
// Don't inherit stdio so stderr is captured and matchable below.
|
|
173
|
+
await $`git push origin ${tagName} --dry-run`;
|
|
174
|
+
console.log(`š·ļø Created git tag ${tagName} (dry-run)`);
|
|
175
|
+
} catch (/** @type {any} */ pushError) {
|
|
176
|
+
const message = pushError.stderr || pushError.message || '';
|
|
177
|
+
if (isPublishTest && /permission|403|denied/i.test(message)) {
|
|
178
|
+
console.log(`š·ļø Created git tag ${tagName} (dry-run, no push permission, skipped push)`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
throw pushError;
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await $({ stdio: 'inherit' })`git push origin ${tagName}`;
|
|
186
|
+
|
|
187
|
+
console.log(`š·ļø Created and pushed git tag ${tagName}`);
|
|
150
188
|
} catch (/** @type {any} */ error) {
|
|
151
189
|
throw new Error(`Failed to create git tag: ${error.message}`);
|
|
152
190
|
}
|
|
@@ -189,18 +227,11 @@ async function validateGitHubRelease(version) {
|
|
|
189
227
|
* Publish packages to npm
|
|
190
228
|
* @param {PublicPackage[]} packages - Packages to publish
|
|
191
229
|
* @param {PublishOptions} options - Publishing options
|
|
192
|
-
* @returns {Promise<
|
|
230
|
+
* @returns {Promise<PublishSummaryEntry[]>}
|
|
193
231
|
*/
|
|
194
232
|
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
233
|
// Use pnpm's built-in duplicate checking - no need to check versions ourselves
|
|
202
|
-
|
|
203
|
-
console.log('ā
Successfully published to npm');
|
|
234
|
+
return publishPackages(packages, options);
|
|
204
235
|
}
|
|
205
236
|
|
|
206
237
|
/**
|
|
@@ -260,10 +291,16 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
260
291
|
.option('sha', {
|
|
261
292
|
type: 'string',
|
|
262
293
|
description: 'Git SHA to use for the GitHub release workflow (local only)',
|
|
294
|
+
})
|
|
295
|
+
.option('filter', {
|
|
296
|
+
type: 'string',
|
|
297
|
+
array: true,
|
|
298
|
+
description:
|
|
299
|
+
'Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.',
|
|
263
300
|
});
|
|
264
301
|
},
|
|
265
302
|
handler: async (argv) => {
|
|
266
|
-
const { dryRun = false, githubRelease = false, tag = 'latest', sha } = argv;
|
|
303
|
+
const { dryRun = false, githubRelease = false, tag = 'latest', sha, filter = [] } = argv;
|
|
267
304
|
|
|
268
305
|
if (isCI && !argv.ci) {
|
|
269
306
|
console.error(
|
|
@@ -290,13 +327,34 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
290
327
|
// Get all packages
|
|
291
328
|
console.log('š Discovering all workspace packages...');
|
|
292
329
|
|
|
293
|
-
const allPackages = await getWorkspacePackages({ publicOnly: true });
|
|
330
|
+
const allPackages = await getWorkspacePackages({ publicOnly: true, filter });
|
|
294
331
|
|
|
295
332
|
if (allPackages.length === 0) {
|
|
296
|
-
console.log(
|
|
333
|
+
console.log(
|
|
334
|
+
`ā ļø No publishable packages found in workspace${filter.length > 0 ? ` matching filter "${filter.join(', ')}"` : ''}`,
|
|
335
|
+
);
|
|
297
336
|
return;
|
|
298
337
|
}
|
|
299
338
|
|
|
339
|
+
if (filter.length > 0) {
|
|
340
|
+
console.log('š Validating workspace dependencies for filtered packages...');
|
|
341
|
+
|
|
342
|
+
const { issues } = await validatePublishDependencies(allPackages);
|
|
343
|
+
|
|
344
|
+
if (issues.length > 0) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
`Invalid dependencies structure of packages to be published -
|
|
347
|
+
${issues.join('\n ')}
|
|
348
|
+
`,
|
|
349
|
+
{
|
|
350
|
+
cause: issues,
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
console.log('ā
All workspace dependency requirements satisfied');
|
|
356
|
+
}
|
|
357
|
+
|
|
300
358
|
// Get version from root package.json
|
|
301
359
|
const version = await getReleaseVersion();
|
|
302
360
|
|
|
@@ -323,9 +381,26 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
323
381
|
|
|
324
382
|
// Publish to npm (pnpm handles duplicate checking automatically)
|
|
325
383
|
// No git checks, we'll do our own
|
|
326
|
-
|
|
384
|
+
console.log('\nš¦ Publishing packages to npm...');
|
|
385
|
+
const publishedPackages = await publishToNpm(allPackages, { dryRun, noGitChecks: true, tag });
|
|
386
|
+
|
|
387
|
+
if (publishedPackages.length === 0) {
|
|
388
|
+
console.log('ā¹ļø No packages were published (all may already be up to date on npm)');
|
|
389
|
+
console.log('\nš Nothing to publish, skipping git tag and GitHub release.');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
327
392
|
|
|
328
|
-
|
|
393
|
+
publishedPackages.forEach((pkg) => {
|
|
394
|
+
console.log(`ā
Published ${pkg.name}@${pkg.version}`);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Tag the root version when it's new. Arbitrary package publishes that don't
|
|
398
|
+
// bump the root version don't create a new tag, so skip in that case.
|
|
399
|
+
if (await remoteGitTagExists(`v${version}`)) {
|
|
400
|
+
console.log(`ā¹ļø Git tag v${version} already exists, skipping tag creation.`);
|
|
401
|
+
} else {
|
|
402
|
+
await createGitTag(version, dryRun);
|
|
403
|
+
}
|
|
329
404
|
|
|
330
405
|
// Create GitHub release or git tag after successful npm publishing
|
|
331
406
|
if (githubRelease && githubReleaseData) {
|
|
@@ -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,7 +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
|
|
31
|
-
* @property {string[]} [
|
|
33
|
+
* @property {string[]} [filter] - Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.
|
|
32
34
|
*/
|
|
33
35
|
|
|
34
36
|
const CANARY_TAG = 'canary';
|
|
@@ -119,80 +121,6 @@ function cleanupCommitMessage(message) {
|
|
|
119
121
|
return `${prefix}${msg}`.trim();
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
async function getPackageToDependencyMap() {
|
|
123
|
-
/**
|
|
124
|
-
* @type {(PublicPackage & { dependencies: Record<string, unknown>; private: boolean; })[]}
|
|
125
|
-
*/
|
|
126
|
-
const packagesWithDeps = JSON.parse(
|
|
127
|
-
(await $`pnpm ls -r --json --exclude-peers --only-projects --prod`).stdout,
|
|
128
|
-
);
|
|
129
|
-
/** @type {Record<string, string[]>} */
|
|
130
|
-
const directPkgDependencies = packagesWithDeps
|
|
131
|
-
.filter((pkg) => !pkg.private)
|
|
132
|
-
.reduce((acc, pkg) => {
|
|
133
|
-
if (!pkg.name) {
|
|
134
|
-
return acc;
|
|
135
|
-
}
|
|
136
|
-
const deps = pkg.dependencies ? Object.keys(pkg.dependencies) : [];
|
|
137
|
-
if (!deps.length) {
|
|
138
|
-
return acc;
|
|
139
|
-
}
|
|
140
|
-
acc[pkg.name] = deps;
|
|
141
|
-
return acc;
|
|
142
|
-
}, /** @type {Record<string, string[]>} */ ({}));
|
|
143
|
-
return directPkgDependencies;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @param {Record<string, string[]>} pkgGraph
|
|
148
|
-
*/
|
|
149
|
-
function resolveTransitiveDependencies(pkgGraph = {}) {
|
|
150
|
-
// Compute transitive (nested) dependencies limited to workspace packages and avoid cycles.
|
|
151
|
-
const workspacePkgNames = new Set(Object.keys(pkgGraph));
|
|
152
|
-
const nestedMap = /** @type {Record<string, string[]>} */ ({});
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
*
|
|
156
|
-
* @param {string} pkgName
|
|
157
|
-
* @returns {string[]}
|
|
158
|
-
*/
|
|
159
|
-
const getTransitiveDeps = (pkgName) => {
|
|
160
|
-
/**
|
|
161
|
-
* @type {Set<string>}
|
|
162
|
-
*/
|
|
163
|
-
const seen = new Set();
|
|
164
|
-
const stack = (pkgGraph[pkgName] || []).slice();
|
|
165
|
-
|
|
166
|
-
while (stack.length) {
|
|
167
|
-
const dep = stack.pop();
|
|
168
|
-
if (!dep || seen.has(dep)) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
// Only consider workspace packages for transitive expansion
|
|
172
|
-
if (!workspacePkgNames.has(dep)) {
|
|
173
|
-
// still record external deps as direct deps but don't traverse into them
|
|
174
|
-
seen.add(dep);
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
seen.add(dep);
|
|
178
|
-
const children = pkgGraph[dep] || [];
|
|
179
|
-
for (const c of children) {
|
|
180
|
-
if (!seen.has(c)) {
|
|
181
|
-
stack.push(c);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return Array.from(seen);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
for (const name of Object.keys(pkgGraph)) {
|
|
190
|
-
nestedMap[name] = getTransitiveDeps(name);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return nestedMap;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
124
|
/**
|
|
197
125
|
* Prepare changelog data for packages using GitHub API
|
|
198
126
|
* @param {PublicPackage[]} packagesToPublish - Packages that will be published
|
|
@@ -225,14 +153,23 @@ async function prepareChangelogsFromGitCli(packagesToPublish, allPackages, canar
|
|
|
225
153
|
}
|
|
226
154
|
}),
|
|
227
155
|
);
|
|
228
|
-
// Second pass: check for dependency updates in other packages not part of git history
|
|
229
|
-
const
|
|
230
|
-
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
|
+
);
|
|
231
168
|
|
|
232
169
|
for (let i = 0; i < allPackages.length; i += 1) {
|
|
233
170
|
const pkg = allPackages[i];
|
|
234
|
-
const depsToPublish =
|
|
235
|
-
|
|
171
|
+
const depsToPublish = [...transitiveDepSets[i]].filter(
|
|
172
|
+
(dep) => dep !== pkg.name && publishedNames.has(dep),
|
|
236
173
|
);
|
|
237
174
|
if (depsToPublish.length === 0) {
|
|
238
175
|
continue;
|
|
@@ -291,6 +228,41 @@ async function prepareChangelogsForPackages(packagesToPublish, allPackages, cana
|
|
|
291
228
|
return changelogs;
|
|
292
229
|
}
|
|
293
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
|
+
|
|
294
266
|
/**
|
|
295
267
|
* Create GitHub releases and tags for published packages
|
|
296
268
|
* @param {PublicPackage[]} publishedPackages - Packages that were published
|
|
@@ -343,15 +315,20 @@ async function createGitHubReleasesForPackages(
|
|
|
343
315
|
GIT_COMMITTER_NAME: 'Code infra',
|
|
344
316
|
GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
|
|
345
317
|
},
|
|
346
|
-
})`git tag -
|
|
318
|
+
})`git tag -fa ${tagName} -m ${`Canary release ${pkg.name}@${version}`}`;
|
|
347
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.
|
|
348
325
|
// eslint-disable-next-line no-await-in-loop
|
|
349
|
-
await $`git push origin ${tagName}`;
|
|
326
|
+
await $`git push --force origin ${tagName}`;
|
|
350
327
|
console.log(`ā
Created and pushed git tag: ${tagName}`);
|
|
351
328
|
|
|
352
329
|
// Create GitHub release
|
|
353
330
|
// eslint-disable-next-line no-await-in-loop
|
|
354
|
-
const res = await octokit
|
|
331
|
+
const res = await upsertGitHubRelease(octokit, {
|
|
355
332
|
owner: repoInfo.owner,
|
|
356
333
|
repo: repoInfo.repo,
|
|
357
334
|
tag_name: tagName,
|
|
@@ -381,7 +358,14 @@ async function getLastCanaryTag() {
|
|
|
381
358
|
// Tag might not exist locally, which is fine
|
|
382
359
|
}
|
|
383
360
|
|
|
384
|
-
|
|
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
|
+
}
|
|
385
369
|
const { stdout: remoteCanaryTag } = await $`git ls-remote --tags origin ${CANARY_TAG}`;
|
|
386
370
|
return remoteCanaryTag.trim() ? CANARY_TAG : null;
|
|
387
371
|
}
|
|
@@ -493,20 +477,23 @@ async function publishCanaryVersions(
|
|
|
493
477
|
}
|
|
494
478
|
|
|
495
479
|
// Third pass: publish only the changed packages using recursive publish
|
|
496
|
-
|
|
480
|
+
/** @type {Set<string>} */
|
|
481
|
+
const publishedNames = new Set();
|
|
497
482
|
try {
|
|
498
483
|
console.log(`š¤ Publishing ${packagesToPublish.length} canary versions...`);
|
|
499
|
-
await publishPackages(packagesToPublish, {
|
|
484
|
+
const publishedPackages = await publishPackages(packagesToPublish, {
|
|
500
485
|
dryRun: options.dryRun,
|
|
501
486
|
noGitChecks: true,
|
|
502
487
|
tag: CANARY_TAG,
|
|
503
488
|
});
|
|
504
489
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
})
|
|
509
|
-
|
|
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
|
+
}
|
|
510
497
|
} finally {
|
|
511
498
|
// Always restore original package.json files in parallel
|
|
512
499
|
console.log('\nš Restoring original package.json files...');
|
|
@@ -520,16 +507,26 @@ async function publishCanaryVersions(
|
|
|
520
507
|
await Promise.all(restorePromises);
|
|
521
508
|
}
|
|
522
509
|
|
|
523
|
-
if (
|
|
524
|
-
// Create
|
|
525
|
-
await createCanaryTag(options.dryRun);
|
|
526
|
-
|
|
527
|
-
// Create GitHub releases if requested
|
|
510
|
+
if (publishedNames.size > 0) {
|
|
511
|
+
// Create GitHub releases only for actually published packages
|
|
528
512
|
if (options.githubRelease) {
|
|
529
|
-
|
|
513
|
+
const actuallyPublished = packagesToPublish.filter((pkg) => publishedNames.has(pkg.name));
|
|
514
|
+
await createGitHubReleasesForPackages(actuallyPublished, canaryVersions, changelogs, options);
|
|
530
515
|
}
|
|
531
516
|
|
|
532
|
-
|
|
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
|
+
}
|
|
533
530
|
}
|
|
534
531
|
}
|
|
535
532
|
|
|
@@ -548,14 +545,15 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
548
545
|
default: false,
|
|
549
546
|
description: 'Create GitHub releases for published packages',
|
|
550
547
|
})
|
|
551
|
-
.option('
|
|
548
|
+
.option('filter', {
|
|
552
549
|
type: 'string',
|
|
553
550
|
array: true,
|
|
554
|
-
description:
|
|
551
|
+
description:
|
|
552
|
+
'Same as filtering packages with --filter in pnpm. Only publish packages matching the filter. See https://pnpm.io/filtering.',
|
|
555
553
|
});
|
|
556
554
|
},
|
|
557
555
|
handler: async (argv) => {
|
|
558
|
-
const { dryRun = false, githubRelease = false,
|
|
556
|
+
const { dryRun = false, githubRelease = false, filter = [] } = argv;
|
|
559
557
|
|
|
560
558
|
const options = { dryRun, githubRelease };
|
|
561
559
|
|
|
@@ -567,48 +565,46 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
567
565
|
console.log('š GitHub releases will be created for published packages\n');
|
|
568
566
|
}
|
|
569
567
|
|
|
570
|
-
//
|
|
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.
|
|
571
570
|
console.log('š Discovering all workspace packages...');
|
|
572
|
-
const
|
|
571
|
+
const filteredPackages = await getWorkspacePackages({ publicOnly: true, filter });
|
|
573
572
|
|
|
574
|
-
if (
|
|
575
|
-
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
|
+
);
|
|
576
577
|
return;
|
|
577
578
|
}
|
|
578
579
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
deps.forEach((dep) => {
|
|
586
|
-
if (!explicitPackages.includes(dep)) {
|
|
587
|
-
missingDeps.add(dep);
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
if (missingDeps.size > 0) {
|
|
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) {
|
|
592
586
|
throw new Error(
|
|
593
|
-
`
|
|
594
|
-
${
|
|
595
|
-
|
|
587
|
+
`Invalid dependencies structure of packages to be published -
|
|
588
|
+
${issues.join('\n ')}
|
|
589
|
+
`,
|
|
590
|
+
{
|
|
591
|
+
cause: issues,
|
|
592
|
+
},
|
|
596
593
|
);
|
|
597
594
|
}
|
|
595
|
+
|
|
596
|
+
console.log('ā
All workspace dependency requirements satisfied');
|
|
598
597
|
}
|
|
599
598
|
|
|
600
|
-
// Check for canary tag to determine selective publishing
|
|
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.
|
|
601
602
|
const canaryTag = await getLastCanaryTag();
|
|
602
603
|
|
|
603
604
|
console.log('š Checking for packages changed since canary tag...');
|
|
604
|
-
|
|
605
|
-
? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true })
|
|
606
|
-
:
|
|
607
|
-
|
|
608
|
-
// If user provided package list, filter to only those in packageNames
|
|
609
|
-
if (explicitPackages.length > 0) {
|
|
610
|
-
packages = packages.filter((pkg) => explicitPackages.includes(pkg.name));
|
|
611
|
-
}
|
|
605
|
+
const packages = canaryTag
|
|
606
|
+
? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true, filter })
|
|
607
|
+
: filteredPackages;
|
|
612
608
|
|
|
613
609
|
console.log(`š Found ${packages.length} packages(s) for canary publishing:`);
|
|
614
610
|
packages.forEach((pkg) => {
|
|
@@ -617,7 +613,7 @@ Pass all workspace dependencies explicitly through the --package argument.`,
|
|
|
617
613
|
|
|
618
614
|
// Fetch version info for all packages in parallel
|
|
619
615
|
console.log('\nš Fetching package version information...');
|
|
620
|
-
const versionInfoPromises =
|
|
616
|
+
const versionInfoPromises = filteredPackages.map(async (pkg) => {
|
|
621
617
|
const versionInfo = await getPackageVersionInfo(pkg.name, pkg.version);
|
|
622
618
|
return { packageName: pkg.name, versionInfo };
|
|
623
619
|
});
|
|
@@ -629,7 +625,7 @@ Pass all workspace dependencies explicitly through the --package argument.`,
|
|
|
629
625
|
packageVersionInfo.set(packageName, versionInfo);
|
|
630
626
|
}
|
|
631
627
|
|
|
632
|
-
await publishCanaryVersions(packages,
|
|
628
|
+
await publishCanaryVersions(packages, filteredPackages, packageVersionInfo, options);
|
|
633
629
|
|
|
634
630
|
console.log('\nš Publishing complete!');
|
|
635
631
|
},
|
|
@@ -16,17 +16,34 @@ import { getWorkspacePackages } from '../utils/pnpm.mjs';
|
|
|
16
16
|
/**
|
|
17
17
|
* @typedef {Object} Args
|
|
18
18
|
* @property {boolean} [dryRun] If true, will only log the commands without executing them
|
|
19
|
+
* @property {string} [otp] 6 digit auth code to forward to npm for two-factor authentication
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
22
23
|
command: 'publish-new-package [pkg...]',
|
|
23
24
|
describe: 'Publish new empty package(s) to the npm registry.',
|
|
24
25
|
builder: (yargs) =>
|
|
25
|
-
yargs
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
yargs
|
|
27
|
+
.option('dryRun', {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
default: false,
|
|
30
|
+
description: 'If true, will only log the commands without executing them.',
|
|
31
|
+
})
|
|
32
|
+
.option('otp', {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: '6 digit auth code to forward to npm for two-factor authentication.',
|
|
35
|
+
coerce: (value) => {
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!/^\d{6}$/.test(value)) {
|
|
41
|
+
throw new Error('The --otp option must be a 6 digit number.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return value;
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
30
47
|
async handler(args) {
|
|
31
48
|
console.log(`š Detecting new packages to publish in workspace...`);
|
|
32
49
|
const newPackages = await getWorkspacePackages({ nonPublishedOnly: true });
|
|
@@ -62,7 +79,7 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
62
79
|
version: '0.0.1',
|
|
63
80
|
repository: {
|
|
64
81
|
type: 'git',
|
|
65
|
-
url: `git+https://github.com/${repo.owner}/${repo.
|
|
82
|
+
url: `git+https://github.com/${repo.owner}/${repo.repo}.git`,
|
|
66
83
|
directory: toPosixPath(path.relative(workspaceDir, pkg.path)),
|
|
67
84
|
},
|
|
68
85
|
};
|
|
@@ -78,8 +95,12 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
|
|
|
78
95
|
if (args.dryRun) {
|
|
79
96
|
publishArgs.push('--dry-run');
|
|
80
97
|
}
|
|
98
|
+
if (args.otp) {
|
|
99
|
+
publishArgs.push('--otp', args.otp);
|
|
100
|
+
}
|
|
81
101
|
await $({
|
|
82
102
|
cwd: newPkgDir,
|
|
103
|
+
stdio: 'inherit',
|
|
83
104
|
})`npm publish --access public --tag=canary ${publishArgs}`;
|
|
84
105
|
console.log(
|
|
85
106
|
`ā
${args.dryRun ? '[Dry run] ' : ''}Published ${chalk.bold(`${pkg.name}@${packageJson.version}`)} to npm registry.`,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
3
|
import * as semver from 'semver';
|
|
7
4
|
import { $ } from 'execa';
|
|
8
|
-
import {
|
|
5
|
+
import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
|
|
6
|
+
import {
|
|
7
|
+
resolveVersion,
|
|
8
|
+
findDependencyVersionFromSpec,
|
|
9
|
+
writeOverridesToWorkspace,
|
|
10
|
+
} from '../utils/pnpm.mjs';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* @typedef {Object} Args
|
|
@@ -103,11 +105,8 @@ async function handler(args) {
|
|
|
103
105
|
// eslint-disable-next-line no-console
|
|
104
106
|
console.log(`Using overrides: ${JSON.stringify(overrides, null, 2)}`);
|
|
105
107
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
packageJson.resolutions ??= {};
|
|
109
|
-
Object.assign(packageJson.resolutions, overrides);
|
|
110
|
-
await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}${os.EOL}`);
|
|
108
|
+
const workspaceDir = (await findWorkspaceDir(process.cwd())) ?? process.cwd();
|
|
109
|
+
await writeOverridesToWorkspace(workspaceDir, overrides);
|
|
111
110
|
|
|
112
111
|
await $({ stdio: 'inherit' })`pnpm dedupe`;
|
|
113
112
|
}
|