@mui/internal-code-infra 0.0.4-canary.21 → 0.0.4-canary.23

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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  export type PublicPackage = import('../utils/pnpm.mjs').PublicPackage;
3
3
  export type PublishOptions = import('../utils/pnpm.mjs').PublishOptions;
4
+ export type PublishSummaryEntry = import('../utils/pnpm.mjs').PublishSummaryEntry;
4
5
  export type Args = {
5
6
  dry-run: boolean;
6
7
  github-release: boolean;
@@ -50,13 +50,22 @@ export declare function getWorkspacePackages(options?: GetWorkspacePackagesOptio
50
50
  * @returns {Promise<VersionInfo>} Version information
51
51
  */
52
52
  export declare function getPackageVersionInfo(packageName: string, baseVersion: string): Promise<VersionInfo>;
53
+ export type PublishSummaryEntry = {
54
+ name: string;
55
+ version: string;
56
+ };
57
+ /**
58
+ * @typedef {Object} PublishSummaryEntry
59
+ * @property {string} name
60
+ * @property {string} version
61
+ */
53
62
  /**
54
63
  * Publish packages with the given options
55
64
  * @param {PublicPackage[]} packages - Packages to publish
56
65
  * @param {PublishOptions} [options={}] - Publishing options
57
- * @returns {Promise<void>}
66
+ * @returns {Promise<PublishSummaryEntry[]>}
58
67
  */
59
- export declare function publishPackages(packages: PublicPackage[], options?: PublishOptions): Promise<void>;
68
+ export declare function publishPackages(packages: PublicPackage[], options?: PublishOptions): Promise<PublishSummaryEntry[]>;
60
69
  export type GetTransitiveDependenciesOptions = {
61
70
  workspacePathByName?: Map<string, string>;
62
71
  includeDev?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.0.4-canary.21",
3
+ "version": "0.0.4-canary.23",
4
4
  "author": "MUI Team",
5
5
  "description": "Infra scripts and configs to be used across MUI repos.",
6
6
  "license": "MIT",
@@ -166,7 +166,7 @@
166
166
  "publishConfig": {
167
167
  "access": "public"
168
168
  },
169
- "gitSha": "df5ed4a21b443278e1ebb858d592c01461ce1608",
169
+ "gitSha": "63e59979dc6273f97e9a27831ed341b8bc32f1cb",
170
170
  "scripts": {
171
171
  "build": "tsgo -p tsconfig.build.json",
172
172
  "typescript": "tsgo -noEmit",
@@ -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';
@@ -194,18 +195,11 @@ async function validateGitHubRelease(version) {
194
195
  * Publish packages to npm
195
196
  * @param {PublicPackage[]} packages - Packages to publish
196
197
  * @param {PublishOptions} options - Publishing options
197
- * @returns {Promise<void>}
198
+ * @returns {Promise<PublishSummaryEntry[]>}
198
199
  */
199
200
  async function publishToNpm(packages, options) {
200
- console.log('\nšŸ“¦ Publishing packages to npm...');
201
- console.log(`šŸ“‹ Found ${packages.length} packages:`);
202
- packages.forEach((pkg) => {
203
- console.log(` • ${pkg.name}@${pkg.version}`);
204
- });
205
-
206
201
  // Use pnpm's built-in duplicate checking - no need to check versions ourselves
207
- await publishPackages(packages, options);
208
- console.log('āœ… Successfully published to npm');
202
+ return publishPackages(packages, options);
209
203
  }
210
204
 
211
205
  /**
@@ -355,7 +349,18 @@ export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
355
349
 
356
350
  // Publish to npm (pnpm handles duplicate checking automatically)
357
351
  // No git checks, we'll do our own
358
- await publishToNpm(allPackages, { dryRun, noGitChecks: true, tag });
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
+ });
359
364
 
360
365
  await createGitTag(version, dryRun);
361
366
 
@@ -228,6 +228,41 @@ async function prepareChangelogsForPackages(packagesToPublish, allPackages, cana
228
228
  return changelogs;
229
229
  }
230
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
+
231
266
  /**
232
267
  * Create GitHub releases and tags for published packages
233
268
  * @param {PublicPackage[]} publishedPackages - Packages that were published
@@ -280,15 +315,20 @@ async function createGitHubReleasesForPackages(
280
315
  GIT_COMMITTER_NAME: 'Code infra',
281
316
  GIT_COMMITTER_EMAIL: 'code-infra@mui.com',
282
317
  },
283
- })`git tag -a ${tagName} -m ${`Canary release ${pkg.name}@${version}`}`;
318
+ })`git tag -fa ${tagName} -m ${`Canary release ${pkg.name}@${version}`}`;
284
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.
285
325
  // eslint-disable-next-line no-await-in-loop
286
- await $`git push origin ${tagName}`;
326
+ await $`git push --force origin ${tagName}`;
287
327
  console.log(`āœ… Created and pushed git tag: ${tagName}`);
288
328
 
289
329
  // Create GitHub release
290
330
  // eslint-disable-next-line no-await-in-loop
291
- const res = await octokit.repos.createRelease({
331
+ const res = await upsertGitHubRelease(octokit, {
292
332
  owner: repoInfo.owner,
293
333
  repo: repoInfo.repo,
294
334
  tag_name: tagName,
@@ -437,20 +477,23 @@ async function publishCanaryVersions(
437
477
  }
438
478
 
439
479
  // Third pass: publish only the changed packages using recursive publish
440
- let publishSuccess = false;
480
+ /** @type {Set<string>} */
481
+ const publishedNames = new Set();
441
482
  try {
442
483
  console.log(`šŸ“¤ Publishing ${packagesToPublish.length} canary versions...`);
443
- await publishPackages(packagesToPublish, {
484
+ const publishedPackages = await publishPackages(packagesToPublish, {
444
485
  dryRun: options.dryRun,
445
486
  noGitChecks: true,
446
487
  tag: CANARY_TAG,
447
488
  });
448
489
 
449
- packagesToPublish.forEach((pkg) => {
450
- const canaryVersion = canaryVersions.get(pkg.name);
451
- console.log(`āœ… Published ${pkg.name}@${canaryVersion}`);
452
- });
453
- publishSuccess = true;
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
+ }
454
497
  } finally {
455
498
  // Always restore original package.json files in parallel
456
499
  console.log('\nšŸ”„ Restoring original package.json files...');
@@ -464,16 +507,26 @@ async function publishCanaryVersions(
464
507
  await Promise.all(restorePromises);
465
508
  }
466
509
 
467
- if (publishSuccess) {
468
- // Create/update the canary tag after successful publish
469
- await createCanaryTag(options.dryRun);
470
-
471
- // Create GitHub releases if requested
510
+ if (publishedNames.size > 0) {
511
+ // Create GitHub releases only for actually published packages
472
512
  if (options.githubRelease) {
473
- await createGitHubReleasesForPackages(packagesToPublish, canaryVersions, changelogs, options);
513
+ const actuallyPublished = packagesToPublish.filter((pkg) => publishedNames.has(pkg.name));
514
+ await createGitHubReleasesForPackages(actuallyPublished, canaryVersions, changelogs, options);
474
515
  }
475
516
 
476
- console.log('\nšŸŽ‰ All canary versions published successfully!');
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
+ }
477
530
  }
478
531
  }
479
532
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
3
4
  import { $ } from 'execa';
4
5
  import * as fs from 'node:fs/promises';
5
6
  import * as path from 'node:path';
@@ -154,11 +155,17 @@ export async function getPackageVersionInfo(packageName, baseVersion) {
154
155
  }
155
156
  }
156
157
 
158
+ /**
159
+ * @typedef {Object} PublishSummaryEntry
160
+ * @property {string} name
161
+ * @property {string} version
162
+ */
163
+
157
164
  /**
158
165
  * Publish packages with the given options
159
166
  * @param {PublicPackage[]} packages - Packages to publish
160
167
  * @param {PublishOptions} [options={}] - Publishing options
161
- * @returns {Promise<void>}
168
+ * @returns {Promise<PublishSummaryEntry[]>}
162
169
  */
163
170
  export async function publishPackages(packages, options = {}) {
164
171
  const args = [];
@@ -178,10 +185,23 @@ export async function publishPackages(packages, options = {}) {
178
185
  args.push('--no-git-checks');
179
186
  }
180
187
 
188
+ const workspaceDir = await findWorkspaceDir(process.cwd());
189
+ if (!workspaceDir) {
190
+ throw new Error('Could not find pnpm workspace root');
191
+ }
192
+ const summaryPath = path.join(workspaceDir, 'pnpm-publish-summary.json');
193
+
194
+ // Clean up any leftover summary file from a previous run
195
+ await fs.rm(summaryPath, { force: true });
196
+
181
197
  await $({
182
198
  stdio: 'inherit',
183
199
  env: { npm_config_loglevel: 'warn' },
184
- })`pnpm -r publish --access public --tag=${tag} ${args}`;
200
+ })`pnpm -r publish --access public --tag=${tag} --report-summary ${args}`;
201
+
202
+ const summary = JSON.parse(await fs.readFile(summaryPath, 'utf-8'));
203
+ await fs.rm(summaryPath, { force: true });
204
+ return /** @type {PublishSummaryEntry[]} */ (summary.publishedPackages);
185
205
  }
186
206
 
187
207
  /**