@smoothbricks/cli 0.2.0 → 0.3.0

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.
Files changed (64) hide show
  1. package/README.md +27 -23
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +19 -3
  4. package/dist/generate/index.d.ts +20 -0
  5. package/dist/generate/index.d.ts.map +1 -0
  6. package/dist/generate/index.js +47 -0
  7. package/dist/lib/run.d.ts +1 -0
  8. package/dist/lib/run.d.ts.map +1 -1
  9. package/dist/lib/run.js +19 -0
  10. package/dist/lib/workspace.d.ts +10 -0
  11. package/dist/lib/workspace.d.ts.map +1 -1
  12. package/dist/lib/workspace.js +55 -16
  13. package/dist/monorepo/lockfile.d.ts.map +1 -1
  14. package/dist/monorepo/lockfile.js +9 -0
  15. package/dist/monorepo/package-policy.d.ts +6 -17
  16. package/dist/monorepo/package-policy.d.ts.map +1 -1
  17. package/dist/monorepo/package-policy.js +63 -1127
  18. package/dist/monorepo/publish-workflow.js +2 -3
  19. package/dist/nx/index.d.ts +1 -0
  20. package/dist/nx/index.d.ts.map +1 -1
  21. package/dist/nx/index.js +23 -5
  22. package/dist/release/candidates.js +1 -1
  23. package/dist/release/core.d.ts +3 -1
  24. package/dist/release/core.d.ts.map +1 -1
  25. package/dist/release/core.js +76 -9
  26. package/dist/release/github-release.d.ts +1 -0
  27. package/dist/release/github-release.d.ts.map +1 -1
  28. package/dist/release/github-release.js +1 -1
  29. package/dist/release/index.d.ts +9 -3
  30. package/dist/release/index.d.ts.map +1 -1
  31. package/dist/release/index.js +265 -39
  32. package/dist/release/orchestration.d.ts +10 -2
  33. package/dist/release/orchestration.d.ts.map +1 -1
  34. package/dist/release/orchestration.js +33 -3
  35. package/managed/templates/github/workflows/ci.yml +3 -2
  36. package/package.json +7 -12
  37. package/src/cli.ts +21 -4
  38. package/src/generate/index.ts +92 -0
  39. package/src/lib/run.ts +25 -0
  40. package/src/lib/workspace.ts +72 -16
  41. package/src/monorepo/lockfile.ts +9 -0
  42. package/src/monorepo/package-policy.test.ts +250 -22
  43. package/src/monorepo/package-policy.ts +88 -1351
  44. package/src/monorepo/publish-workflow.ts +2 -3
  45. package/src/nx/index.test.ts +9 -0
  46. package/src/nx/index.ts +25 -5
  47. package/src/release/__tests__/candidates.test.ts +53 -8
  48. package/src/release/__tests__/core-properties.test.ts +2 -2
  49. package/src/release/__tests__/core-scenarios.test.ts +12 -15
  50. package/src/release/__tests__/core.test.ts +50 -3
  51. package/src/release/__tests__/fixture-repo.test.ts +68 -18
  52. package/src/release/__tests__/github-release.test.ts +9 -16
  53. package/src/release/__tests__/orchestration.test.ts +91 -5
  54. package/src/release/__tests__/retag-unpublished.test.ts +9 -9
  55. package/src/release/__tests__/trust-publisher.test.ts +65 -33
  56. package/src/release/candidates.ts +1 -1
  57. package/src/release/core.ts +95 -10
  58. package/src/release/github-release.ts +1 -1
  59. package/src/release/index.ts +343 -49
  60. package/src/release/orchestration.ts +50 -5
  61. package/dist/nx-version-actions.cjs +0 -25
  62. package/dist/nx-version-actions.d.cts +0 -6
  63. package/dist/nx-version-actions.d.cts.map +0 -1
  64. package/src/nx-version-actions.cts +0 -36
@@ -6,15 +6,15 @@ import { Writable } from 'node:stream';
6
6
  import { $ } from 'bun';
7
7
  import { withDevenvEnv } from '../lib/devenv.js';
8
8
  import { isRecord, readJsonObject, stringProperty } from '../lib/json.js';
9
- import { decode, run, runResult, runStatus } from '../lib/run.js';
9
+ import { decode, run, runInteractiveStatus, runResult, runStatus } from '../lib/run.js';
10
10
  import { listReleasePackages, readPackageJson, repositoryInfo } from '../lib/workspace.js';
11
11
  import { readPackedPackageJson, validatePackedWorkspaceDependencies } from '../monorepo/packed-manifest.js';
12
12
  import { bootstrapNpmPackages, NPM_BOOTSTRAP_DIST_TAG, NPM_BOOTSTRAP_VERSION, } from './bootstrap-npm-packages.js';
13
13
  import { autoReleaseCandidatePackages } from './candidates.js';
14
- import { collectOwnedReleaseTagRecords, pendingReleaseTargets, releaseTag, } from './core.js';
15
- import { createOrUpdateGithubRelease, renderNxProjectChangelogContents } from './github-release.js';
14
+ import { collectOwnedReleaseTagRecords, pendingReleaseTargets, releaseTag, releaseTagAliases, } from './core.js';
15
+ import { createOrUpdateGithubRelease, renderNxProjectChangelogContents, withNxWorkspaceRoot, } from './github-release.js';
16
16
  import { publishWithAuthDiagnostics } from './npm-auth.js';
17
- import { completeReleaseAtHead as completeReleaseAtHeadWithShell, repairPendingTargets, runReleaseVersion, } from './orchestration.js';
17
+ import { bumpStableReleaseToNext, completeReleaseAtHead as completeReleaseAtHeadWithShell, repairPendingTargets, runReleaseVersion, } from './orchestration.js';
18
18
  import { retagUnpublished } from './retag-unpublished.js';
19
19
  export async function releaseVersion(root, options) {
20
20
  const bump = releaseBumpArg(options.bump);
@@ -33,6 +33,9 @@ export async function releaseVersion(root, options) {
33
33
  else if (result.status === 'no-release-needed') {
34
34
  console.log('Nx did not create a release commit; no release needed.');
35
35
  }
36
+ if (result.status === 'dry-run') {
37
+ await writeReleasePreviewSummary(root, packages, result.packages);
38
+ }
36
39
  await writeReleaseGithubOutput(options.githubOutput, result.packages, result.mode);
37
40
  }
38
41
  export async function releasePublish(root, options) {
@@ -40,15 +43,20 @@ export async function releasePublish(root, options) {
40
43
  const packages = await releasePackagesAtHead(root, releasePackages(root));
41
44
  if (packages.length === 0) {
42
45
  if (bump === 'auto') {
46
+ if (options.dryRun === true) {
47
+ console.log('No release tags found at HEAD because dry-run versioning does not create release commits or tags.');
48
+ return;
49
+ }
43
50
  console.log('No release tags found at HEAD; no release to publish.');
44
51
  const summary = {
45
52
  sha: await gitHead(root),
46
- dryRun: options.dryRun === true,
53
+ dryRun: false,
47
54
  packages,
48
55
  pushed: false,
49
56
  published: [],
50
57
  alreadyPublished: [],
51
58
  githubReleases: [],
59
+ githubReleaseLinks: [],
52
60
  rerunRequired: false,
53
61
  noRelease: true,
54
62
  };
@@ -59,6 +67,10 @@ export async function releasePublish(root, options) {
59
67
  }
60
68
  const summary = await completeReleaseAtHeadWithShell(releaseCompletionShell(root), packages, options.dryRun === true, await newerCommitsRemain(root));
61
69
  await writeReleaseSummary(summary);
70
+ const bumpedPackages = await bumpStableReleaseToNext(releaseNextShell(root), packages, options.dryRun === true, summary.rerunRequired);
71
+ if (bumpedPackages.length > 0) {
72
+ console.log(`Prepared next prerelease for ${packageSummary(bumpedPackages)}.`);
73
+ }
62
74
  }
63
75
  export async function releaseRepairPending(root, options) {
64
76
  const branch = await releaseBranch(root);
@@ -72,6 +84,9 @@ export async function releaseRepairPending(root, options) {
72
84
  console.log(targets.length === 0
73
85
  ? 'Repair pending releases: no pending durable state repairs found.'
74
86
  : `Repair pending releases: ${targets.length} release target${targets.length === 1 ? '' : 's'} need repair.`);
87
+ for (const target of targets) {
88
+ console.log(`Repair pending releases: target ${target.sha.slice(0, 12)} needs ${repairTargetSummary(target)}.`);
89
+ }
75
90
  const summaries = await repairPendingTargets(releaseRepairShell(root), targets, restoreRef, options.dryRun === true);
76
91
  await writeRepairSummary(summaries, options.dryRun === true);
77
92
  }
@@ -91,7 +106,6 @@ export async function releaseTrustPublisher(root, options) {
91
106
  promptOtp: (packageName) => promptForNpmOtp(packageName),
92
107
  log: (message) => console.log(message),
93
108
  }, bootstrapOptions),
94
- login: () => runLatestNpm(root, ['login', '--auth-type=web']),
95
109
  trustPublisher: (pkg, dryRun, env) => {
96
110
  const args = ['trust', 'github', pkg.name, '--file', workflow, '--repo', repository, '--yes'];
97
111
  if (dryRun) {
@@ -99,28 +113,27 @@ export async function releaseTrustPublisher(root, options) {
99
113
  }
100
114
  return runLatestNpmTrust(root, args, env);
101
115
  },
102
- promptOtp: (packageName) => promptForNpmOtp(packageName),
116
+ trustedPublishers: (pkg) => listTrustedPublishers(root, pkg),
103
117
  log: (message) => console.log(message),
104
118
  error: (message) => console.error(message),
105
119
  }, options);
106
120
  }
107
121
  export async function configureTrustedPublishers(shell, options) {
108
122
  const packages = shell.listReleasePackages();
109
- if (packages.length === 0) {
123
+ const selectedPackages = selectedTrustPublisherPackages(packages, options.packages ?? []);
124
+ if (selectedPackages.length === 0) {
110
125
  throw new Error('No owned release packages found.');
111
126
  }
112
- let bootstrapLoggedIn = false;
113
127
  if (options.bootstrap) {
114
- const bootstrapped = await shell.bootstrapNpmPackages({
128
+ await shell.bootstrapNpmPackages({
115
129
  dryRun: options.dryRun === true,
116
130
  skipLogin: options.skipLogin === true,
117
131
  otp: options.bootstrapOtp,
118
- packages: [],
132
+ packages: options.packages ?? [],
119
133
  });
120
- bootstrapLoggedIn = bootstrapped.length > 0 && !options.dryRun && !options.skipLogin;
121
134
  }
122
135
  if (!options.dryRun) {
123
- const packageStates = await Promise.all(packages.map(async (pkg) => ((await shell.packageExists(pkg.name)) ? null : pkg.name)));
136
+ const packageStates = await Promise.all(selectedPackages.map(async (pkg) => ((await shell.packageExists(pkg.name)) ? null : pkg.name)));
124
137
  const missingPackages = packageStates.filter((name) => name !== null);
125
138
  if (missingPackages.length > 0) {
126
139
  throw new Error('npm trusted publishing can only be configured after packages exist on the registry. ' +
@@ -128,16 +141,23 @@ export async function configureTrustedPublishers(shell, options) {
128
141
  `Missing packages: ${missingPackages.join(', ')}`);
129
142
  }
130
143
  }
131
- if (!options.dryRun && !options.skipLogin && !bootstrapLoggedIn) {
132
- await shell.login();
133
- }
134
144
  const failedPackages = [];
135
- for (const pkg of packages) {
145
+ for (const pkg of selectedPackages) {
146
+ if (!options.dryRun) {
147
+ const trustedPublishers = await shell.trustedPublishers(pkg);
148
+ if (hasMatchingGithubTrustedPublisher(trustedPublishers, shell.repository, shell.workflow)) {
149
+ shell.log(`${pkg.name}: npm trusted publisher is already configured; skipping.`);
150
+ continue;
151
+ }
152
+ if (trustedPublishers.length > 0) {
153
+ throw new Error(`${pkg.name}: npm trusted publisher exists but does not match GitHub Actions ${shell.repository}/${shell.workflow}. ` +
154
+ 'Use npm trust list to inspect it and npm trust revoke --id <trust-id> before reconfiguring.');
155
+ }
156
+ }
136
157
  shell.log(`${pkg.name}: trusting GitHub Actions ${shell.repository}/${shell.workflow}`);
137
158
  while (true) {
138
- const otp = options.dryRun ? undefined : (options.otp ?? (await shell.promptOtp(pkg.name)));
139
159
  try {
140
- const result = await shell.trustPublisher(pkg, options.dryRun === true, otp ? { NPM_CONFIG_OTP: otp } : undefined);
160
+ const result = await shell.trustPublisher(pkg, options.dryRun === true);
141
161
  if (result === 'already-configured') {
142
162
  shell.log(`${pkg.name}: npm trusted publisher is already configured; skipping.`);
143
163
  }
@@ -147,9 +167,9 @@ export async function configureTrustedPublishers(shell, options) {
147
167
  shell.error(`${pkg.name}: npm trusted publisher setup failed.`);
148
168
  shell.error(error instanceof Error ? error.message : String(error));
149
169
  if (!options.dryRun) {
150
- shell.error('npm OTP codes are single-use. Generate a fresh OTP before retrying this package.');
170
+ shell.error('npm owns trusted publishing authentication. Complete the npm browser challenge, then retry.');
151
171
  }
152
- if (options.otp || options.dryRun) {
172
+ if (options.dryRun) {
153
173
  failedPackages.push(pkg.name);
154
174
  break;
155
175
  }
@@ -159,10 +179,33 @@ export async function configureTrustedPublishers(shell, options) {
159
179
  }
160
180
  if (failedPackages.length > 0) {
161
181
  shell.error(`Trusted publishing was not configured for: ${failedPackages.join(', ')}`);
162
- if (options.otp) {
163
- shell.error('Rerun smoo release trust-publisher without --otp to enter a fresh OTP for each failed package.');
182
+ }
183
+ }
184
+ function selectedTrustPublisherPackages(packages, selections) {
185
+ if (selections.length === 0) {
186
+ return packages;
187
+ }
188
+ const byName = new Map(packages.map((pkg) => [pkg.name, pkg]));
189
+ const selected = [];
190
+ const unknown = [];
191
+ for (const name of selections) {
192
+ const pkg = byName.get(name);
193
+ if (pkg) {
194
+ selected.push(pkg);
195
+ }
196
+ else {
197
+ unknown.push(name);
164
198
  }
165
199
  }
200
+ if (unknown.length > 0) {
201
+ throw new Error(`Unknown owned release package selection: ${unknown.join(', ')}`);
202
+ }
203
+ return selected;
204
+ }
205
+ function hasMatchingGithubTrustedPublisher(trustedPublishers, repository, workflow) {
206
+ return trustedPublishers.some((trustedPublisher) => trustedPublisher.type === 'github' &&
207
+ trustedPublisher.repository === repository &&
208
+ trustedPublisher.file === workflow);
166
209
  }
167
210
  export async function releaseBootstrapNpmPackages(root, options) {
168
211
  await bootstrapNpmPackages({
@@ -198,6 +241,7 @@ export async function releaseRetagUnpublished(root, options) {
198
241
  export async function printReleaseState(root) {
199
242
  console.log(JSON.stringify(await getReleaseState(root), null, 2));
200
243
  }
244
+ const releasePreviewChangelogs = new Map();
201
245
  async function getReleaseState(root) {
202
246
  return getReleaseStateForPackages(root, listReleasePackages(root));
203
247
  }
@@ -301,6 +345,9 @@ async function releaseVersionPackages(root, packages, bump) {
301
345
  }, packages);
302
346
  }
303
347
  async function runNxReleaseVersion(root, projects, bump, dryRun) {
348
+ if (dryRun) {
349
+ return runNxReleaseVersionPreview(root, projects, bump);
350
+ }
304
351
  // Nx owns local release mutation: package versions, bun.lock updates, the
305
352
  // release commit, and annotated tags. smoo owns remote publication after the
306
353
  // workflow validates the exact release commit Nx produced.
@@ -309,10 +356,66 @@ async function runNxReleaseVersion(root, projects, bump, dryRun) {
309
356
  nxArgs.push(bump);
310
357
  }
311
358
  nxArgs.push(`--projects=${projects}`, '--git-commit=true', '--git-tag=true', '--git-push=false');
312
- if (dryRun) {
313
- nxArgs.push('--dry-run');
314
- }
315
359
  await run('nx', nxArgs, root);
360
+ return [];
361
+ }
362
+ async function runNxReleaseVersionPreview(root, projects, bump) {
363
+ return withNxWorkspaceRoot(root, async () => {
364
+ const { createAPI } = await import('nx/src/command-line/release/version.js');
365
+ const { createAPI: createChangelogAPI } = await import('nx/src/command-line/release/changelog.js');
366
+ const projectNames = projects.split(',').filter(Boolean);
367
+ const result = await createAPI({}, false)({
368
+ specifier: bump === 'auto' ? undefined : bump,
369
+ projects: projectNames,
370
+ gitCommit: true,
371
+ gitTag: true,
372
+ gitPush: false,
373
+ dryRun: true,
374
+ });
375
+ const changelogResult = await createChangelogAPI({
376
+ changelog: {
377
+ workspaceChangelog: false,
378
+ projectChangelogs: { createRelease: false, file: false },
379
+ },
380
+ }, false)({
381
+ projects: projectNames,
382
+ versionData: result.projectsVersionData,
383
+ releaseGraph: result.releaseGraph,
384
+ gitCommit: false,
385
+ gitTag: false,
386
+ gitPush: false,
387
+ stageChanges: false,
388
+ createRelease: false,
389
+ dryRun: true,
390
+ });
391
+ releasePreviewChangelogs.clear();
392
+ for (const [projectName, changelog] of Object.entries(changelogResult.projectChangelogs ?? {})) {
393
+ releasePreviewChangelogs.set(projectName, changelog.contents);
394
+ }
395
+ const packagesByProject = new Map(releasePackages(root).map((pkg) => [pkg.projectName, pkg]));
396
+ return Object.entries(result.projectsVersionData)
397
+ .map(([projectName, data]) => {
398
+ const pkg = packagesByProject.get(projectName);
399
+ if (!pkg || !data.newVersion) {
400
+ return null;
401
+ }
402
+ return { ...pkg, version: data.newVersion };
403
+ })
404
+ .filter((pkg) => pkg !== null);
405
+ });
406
+ }
407
+ async function runNxNextPrereleaseVersion(root, projects) {
408
+ await run('nx', [
409
+ 'release',
410
+ 'version',
411
+ 'prerelease',
412
+ `--projects=${projects}`,
413
+ '--preid=next',
414
+ '--git-commit=true',
415
+ '--git-tag=false',
416
+ '--git-push=false',
417
+ '--git-commit-message=chore(release): prepare next prerelease',
418
+ ], root);
316
419
  }
317
420
  async function releasePackagesAtHead(root, packages) {
318
421
  return releasePackagesAtRef(root, packages, 'HEAD');
@@ -381,7 +484,7 @@ async function gitTagsAtRef(root, ref) {
381
484
  .filter(Boolean);
382
485
  }
383
486
  async function previousReleaseTag(root, pkg, currentTag) {
384
- const result = await $ `git tag --list ${`${pkg.name}@*`} --sort=-v:refname --merged ${currentTag}`
487
+ const result = await $ `git tag --list ${`${pkg.projectName}@*`} --sort=-v:refname --merged ${currentTag}`
385
488
  .cwd(root)
386
489
  .quiet()
387
490
  .nothrow();
@@ -429,6 +532,11 @@ async function pushReleaseRefs(root, packages) {
429
532
  await run('git', ['push', '--atomic', remote, ...refspecs], root);
430
533
  return true;
431
534
  }
535
+ async function pushCurrentBranch(root) {
536
+ const branch = await releaseBranch(root);
537
+ const remote = await releaseRemote(root, branch);
538
+ await run('git', ['push', remote, `HEAD:refs/heads/${branch}`], root);
539
+ }
432
540
  function releaseCompletionShell(root) {
433
541
  return {
434
542
  gitHead: () => gitHead(root),
@@ -446,6 +554,14 @@ function releaseCompletionShell(root) {
446
554
  createGithubRelease: (pkg, dryRun) => createGithubRelease(root, pkg, dryRun),
447
555
  };
448
556
  }
557
+ function releaseNextShell(root) {
558
+ return {
559
+ bumpStablePackagesToNext: async (packages) => {
560
+ await runNxNextPrereleaseVersion(root, releasePackageProjects(packages));
561
+ await pushCurrentBranch(root);
562
+ },
563
+ };
564
+ }
449
565
  function releaseRepairShell(root) {
450
566
  return {
451
567
  ...releaseCompletionShell(root),
@@ -490,15 +606,23 @@ async function listOwnedReleaseTagRecords(root, ref) {
490
606
  }
491
607
  async function durableReleaseTagState(root, pkg, tag) {
492
608
  const packageVersion = `${pkg.name}@${pkg.version}`;
609
+ const githubReleaseTags = releaseTagAliases({ ...pkg, projectName: projectNameFromReleaseTag(tag) });
493
610
  const start = Date.now();
494
- console.log(`${packageVersion}: checking durable state (npm + GitHub Release ${tag}).`);
611
+ console.log(`${packageVersion}: checking durable state (npm + GitHub Release ${githubReleaseTags.join(' or ')}).`);
495
612
  const [npmPublished, githubReleasePresent] = await Promise.all([
496
613
  npmVersionExists(root, pkg.name, pkg.version),
497
- githubReleaseExists(root, tag),
614
+ anyGithubReleaseExists(root, githubReleaseTags),
498
615
  ]);
499
616
  console.log(`${packageVersion}: durable state npm=${yesNo(npmPublished)} github=${yesNo(githubReleasePresent)} (${Date.now() - start}ms).`);
500
617
  return { npmPublished, githubReleaseExists: githubReleasePresent };
501
618
  }
619
+ function projectNameFromReleaseTag(tag) {
620
+ const versionSeparator = tag.lastIndexOf('@');
621
+ if (versionSeparator <= 0) {
622
+ return tag;
623
+ }
624
+ return tag.slice(0, versionSeparator);
625
+ }
502
626
  async function gitReleaseTagsByCreatorDate(root) {
503
627
  const result = await $ `git for-each-ref --sort=-creatordate --format=${'%(refname:short)%09%(creatordate:unix)%09%(*objectname)%09%(objectname)'} refs/tags`
504
628
  .cwd(root)
@@ -537,7 +661,7 @@ async function createGithubRelease(root, pkg, dryRun) {
537
661
  const previousTag = await previousReleaseTag(root, pkg, currentTag);
538
662
  if (dryRun) {
539
663
  await renderNxProjectChangelogContents({ root, pkg, previousTag, dryRun });
540
- return;
664
+ return null;
541
665
  }
542
666
  const contents = await renderNxProjectChangelogContents({ root, pkg, previousTag, dryRun });
543
667
  await createOrUpdateGithubRelease(pkg, contents, {
@@ -545,6 +669,7 @@ async function createGithubRelease(root, pkg, dryRun) {
545
669
  runGhRelease: (args) => run('gh', args, root),
546
670
  log: (message) => console.log(message),
547
671
  });
672
+ return githubReleaseUrl(root, currentTag);
548
673
  }
549
674
  function envPresence(name) {
550
675
  return `${name}=${process.env[name] ? 'present' : 'missing'}`;
@@ -579,7 +704,7 @@ async function writeReleaseSummary(summary) {
579
704
  lines.push(`- Git refs pushed: ${summary.pushed ? 'yes' : 'already current'}`);
580
705
  lines.push(`- npm published: ${packageSummary(summary.published)}`);
581
706
  lines.push(`- npm already published: ${packageSummary(summary.alreadyPublished)}`);
582
- lines.push(`- GitHub Releases created/updated: ${packageSummary(summary.githubReleases)}`);
707
+ lines.push(`- GitHub Releases created/updated: ${githubReleaseSummary(summary)}`);
583
708
  }
584
709
  if (summary.rerunRequired) {
585
710
  const message = 'A previous incomplete release was repaired; newer commits remain. Run Publish again.';
@@ -593,6 +718,32 @@ async function writeReleaseSummary(summary) {
593
718
  await appendFile(summaryPath, `${text}\n`);
594
719
  }
595
720
  }
721
+ async function writeReleasePreviewSummary(root, currentPackages, previewPackages) {
722
+ const lines = ['## Release Preview', '', `- Commit: \`${(await gitHead(root)).slice(0, 12)}\``, '- Mode: dry run'];
723
+ if (previewPackages.length === 0) {
724
+ lines.push('- Result: no release needed');
725
+ }
726
+ else {
727
+ lines.push('- Packages:');
728
+ const currentByName = new Map(currentPackages.map((pkg) => [pkg.name, pkg]));
729
+ for (const pkg of previewPackages) {
730
+ lines.push(` - \`${pkg.name}\`: \`${currentByName.get(pkg.name)?.version ?? 'unknown'}\` -> \`${pkg.version}\``);
731
+ }
732
+ for (const pkg of previewPackages) {
733
+ const contents = releasePreviewChangelogs.get(pkg.projectName);
734
+ if (!contents) {
735
+ throw new Error(`Nx did not generate a project changelog for ${pkg.projectName}.`);
736
+ }
737
+ lines.push('', `### ${pkg.name} ${pkg.version}`, '', contents.trim());
738
+ }
739
+ }
740
+ const text = `${lines.join('\n')}\n`;
741
+ console.log(text.trimEnd());
742
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
743
+ if (summaryPath) {
744
+ await appendFile(summaryPath, `${text}\n`);
745
+ }
746
+ }
596
747
  async function writeRepairSummary(summaries, dryRun) {
597
748
  const lines = ['## Pending Release Repair', '', `- Mode: ${dryRun ? 'dry run' : 'release'}`];
598
749
  if (summaries.length === 0) {
@@ -600,7 +751,7 @@ async function writeRepairSummary(summaries, dryRun) {
600
751
  }
601
752
  else {
602
753
  for (const summary of summaries) {
603
- lines.push(`- Repaired \`${summary.sha.slice(0, 12)}\`: ${packageSummary(summary.packages)}`);
754
+ lines.push(`- ${dryRun ? 'Would repair' : 'Repaired'} \`${summary.sha.slice(0, 12)}\`: ${packageSummary(summary.packages)}`);
604
755
  }
605
756
  }
606
757
  const text = `${lines.join('\n')}\n`;
@@ -616,6 +767,27 @@ function packageSummary(packages) {
616
767
  }
617
768
  return packages.map((pkg) => `${pkg.name}@${pkg.version}`).join(', ');
618
769
  }
770
+ function githubReleaseSummary(summary) {
771
+ if (summary.githubReleaseLinks.length > 0) {
772
+ return summary.githubReleaseLinks.map(({ pkg, url }) => `[${pkg.name}@${pkg.version}](${url})`).join(', ');
773
+ }
774
+ return packageSummary(summary.githubReleases);
775
+ }
776
+ function repairTargetSummary(target) {
777
+ return target.packages
778
+ .map((pkg) => `${pkg.name}@${pkg.version} (${repairReasons(target, pkg).join(' + ')})`)
779
+ .join(', ');
780
+ }
781
+ function repairReasons(target, pkg) {
782
+ const reasons = [];
783
+ if (target.npmPackages.includes(pkg)) {
784
+ reasons.push('npm missing');
785
+ }
786
+ if (target.githubPackages.includes(pkg)) {
787
+ reasons.push('GitHub Release missing');
788
+ }
789
+ return reasons;
790
+ }
619
791
  async function fetchReleaseRefs(root, remote, branch) {
620
792
  await run('git', ['fetch', '--tags', remote, branch], root);
621
793
  }
@@ -806,6 +978,17 @@ async function pushRetaggedReleaseTags(root, remote, updates) {
806
978
  async function githubReleaseExists(root, tag) {
807
979
  return (await runStatus('gh', ['release', 'view', tag, '--json', 'tagName'], root, true)) === 0;
808
980
  }
981
+ function githubReleaseUrl(root, tag) {
982
+ return `https://github.com/${githubRepositoryFromRootPackage(root)}/releases/tag/${encodeURIComponent(tag)}`;
983
+ }
984
+ async function anyGithubReleaseExists(root, tags) {
985
+ for (const tag of tags) {
986
+ if (await githubReleaseExists(root, tag)) {
987
+ return true;
988
+ }
989
+ }
990
+ return false;
991
+ }
809
992
  async function assertRemoteTagExists(root, tag) {
810
993
  const branch = await releaseBranch(root);
811
994
  const remote = await releaseRemote(root, branch);
@@ -917,15 +1100,58 @@ async function runLatestNpm(root, npmArgs, env) {
917
1100
  await run('nix', ['shell', 'nixpkgs#nodejs_latest', '-c', 'npm', ...npmArgs], root, env);
918
1101
  }
919
1102
  async function runLatestNpmTrust(root, npmArgs, env) {
920
- const result = await runResult('nix', ['shell', 'nixpkgs#nodejs_latest', '-c', 'npm', ...npmArgs], root, env);
921
- if (result.exitCode === 0) {
1103
+ const status = await runInteractiveStatus('nix', ['shell', 'nixpkgs#nodejs_latest', '-c', 'npm', ...npmArgs], root, env);
1104
+ if (status === 0) {
922
1105
  return 'configured';
923
1106
  }
924
- const output = `${result.stdout}\n${result.stderr}`;
925
- if (/\bE409\b|\b409 Conflict\b/.test(output)) {
926
- return 'already-configured';
1107
+ throw new Error(`nix shell nixpkgs#nodejs_latest -c npm ${npmArgs.join(' ')} failed with exit code ${status}`);
1108
+ }
1109
+ async function listTrustedPublishers(root, pkg) {
1110
+ const args = ['trust', 'list', pkg.name, '--json'];
1111
+ let result = await runResult('nix', ['shell', 'nixpkgs#nodejs_latest', '-c', 'npm', ...args], root);
1112
+ if (result.exitCode !== 0 && /\bEOTP\b/.test(`${result.stdout}\n${result.stderr}`)) {
1113
+ const status = await runInteractiveStatus('nix', ['shell', 'nixpkgs#nodejs_latest', '-c', 'npm', ...args], root);
1114
+ if (status !== 0) {
1115
+ throw new Error(`nix shell nixpkgs#nodejs_latest -c npm ${args.join(' ')} failed with exit code ${status}`);
1116
+ }
1117
+ result = await runResult('nix', ['shell', 'nixpkgs#nodejs_latest', '-c', 'npm', ...args], root);
1118
+ }
1119
+ if (result.exitCode !== 0) {
1120
+ throw new Error(`nix shell nixpkgs#nodejs_latest -c npm ${args.join(' ')} failed with exit code ${result.exitCode}`);
1121
+ }
1122
+ return parseTrustedPublishers(result.stdout, pkg.name);
1123
+ }
1124
+ export function parseTrustedPublishers(stdout, packageName) {
1125
+ if (stdout.trim().length === 0) {
1126
+ return [];
1127
+ }
1128
+ const parsed = JSON.parse(stdout);
1129
+ if (parsed === null) {
1130
+ return [];
1131
+ }
1132
+ if (Array.isArray(parsed)) {
1133
+ return parsed.map((value) => parseTrustedPublisher(value, packageName));
1134
+ }
1135
+ if (isRecord(parsed)) {
1136
+ return [parseTrustedPublisher(parsed, packageName)];
927
1137
  }
928
- throw new Error(`nix shell nixpkgs#nodejs_latest -c npm ${npmArgs.join(' ')} failed with exit code ${result.exitCode}`);
1138
+ throw new Error(`${packageName}: npm trust list returned invalid JSON.`);
1139
+ }
1140
+ function parseTrustedPublisher(value, packageName) {
1141
+ if (!isRecord(value)) {
1142
+ throw new Error(`${packageName}: npm trust list returned invalid trusted publisher entry.`);
1143
+ }
1144
+ const id = stringProperty(value, 'id');
1145
+ const type = stringProperty(value, 'type');
1146
+ if (!id || !type) {
1147
+ throw new Error(`${packageName}: npm trust list returned a trusted publisher without id or type.`);
1148
+ }
1149
+ return {
1150
+ id,
1151
+ type,
1152
+ file: stringProperty(value, 'file') ?? undefined,
1153
+ repository: stringProperty(value, 'repository') ?? undefined,
1154
+ };
929
1155
  }
930
1156
  async function runLatestNpmPublish(root, npmArgs) {
931
1157
  await runLatestNpm(root, npmArgs, { NODE_AUTH_TOKEN: '', NPM_TOKEN: '' });
@@ -935,7 +1161,7 @@ function missingNpmPackagePublishGuidance(pkg) {
935
1161
  }
936
1162
  async function promptForNpmOtp(packageName) {
937
1163
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
938
- throw new Error(`npm trust requires a one-time password for ${packageName}. Pass --otp <code> in non-interactive shells.`);
1164
+ throw new Error(`npm requires a one-time password for ${packageName}. Pass --otp <code> in non-interactive shells.`);
939
1165
  }
940
1166
  const mutedOutput = new Writable({
941
1167
  write(_chunk, _encoding, callback) {
@@ -8,6 +8,10 @@ export interface ReleaseSummary<Package extends ReleasePackageInfo = ReleasePack
8
8
  published: Package[];
9
9
  alreadyPublished: Package[];
10
10
  githubReleases: Package[];
11
+ githubReleaseLinks: Array<{
12
+ pkg: Package;
13
+ url: string;
14
+ }>;
11
15
  rerunRequired: boolean;
12
16
  noRelease: boolean;
13
17
  }
@@ -18,7 +22,10 @@ export interface ReleaseCompletionShell<Package extends ReleasePackageInfo = Rel
18
22
  buildReleaseCandidate(packages: Package[]): Promise<void>;
19
23
  publishPackage(pkg: Package, distTag: string, dryRun: boolean): Promise<void>;
20
24
  listGithubMissingPackages(packages: Package[]): Promise<Package[]>;
21
- createGithubRelease(pkg: Package, dryRun: boolean): Promise<void>;
25
+ createGithubRelease(pkg: Package, dryRun: boolean): Promise<string | null>;
26
+ }
27
+ export interface ReleaseNextShell<Package extends ReleasePackageInfo = ReleasePackageInfo> {
28
+ bumpStablePackagesToNext(packages: Package[]): Promise<void>;
22
29
  }
23
30
  export interface ReleaseRepairShell<Package extends ReleasePackageInfo = ReleasePackageInfo> extends ReleaseCompletionShell<Package> {
24
31
  checkout(ref: string): Promise<void>;
@@ -31,7 +38,7 @@ export interface ReleaseVersionShell<Package extends ReleasePackageInfo = Releas
31
38
  releaseVersionPackages(bump: string): Promise<Package[]>;
32
39
  ensureLocalReleaseTags(packages: Package[]): Promise<void>;
33
40
  gitHead(): Promise<string>;
34
- runNxReleaseVersion(packages: Package[], bump: string, dryRun: boolean): Promise<void>;
41
+ runNxReleaseVersion(packages: Package[], bump: string, dryRun: boolean): Promise<Package[]>;
35
42
  assertCleanGitTree(): Promise<void>;
36
43
  }
37
44
  export interface ReleaseVersionResult<Package extends ReleasePackageInfo = ReleasePackageInfo> {
@@ -44,6 +51,7 @@ export declare function runReleaseVersion<Package extends ReleasePackageInfo>(sh
44
51
  dryRun: boolean;
45
52
  }): Promise<ReleaseVersionResult<Package>>;
46
53
  export declare function completeReleaseAtHead<Package extends ReleasePackageInfo>(shell: ReleaseCompletionShell<Package>, packages: Package[], dryRun: boolean, rerunRequired: boolean): Promise<ReleaseSummary<Package>>;
54
+ export declare function bumpStableReleaseToNext<Package extends ReleasePackageInfo>(shell: ReleaseNextShell<Package>, packages: Package[], dryRun: boolean, rerunRequired: boolean): Promise<Package[]>;
47
55
  export declare function repairPendingTargets<Package extends ReleasePackageInfo>(shell: ReleaseRepairShell<Package>, targets: Array<ReleaseTarget<Package>>, restoreRef: string, dryRun: boolean): Promise<Array<ReleaseSummary<Package>>>;
48
56
  export declare function completeRepairTargetAtHead<Package extends ReleasePackageInfo>(shell: ReleaseCompletionShell<Package>, target: ReleaseTarget<Package>, dryRun: boolean): Promise<ReleaseSummary<Package>>;
49
57
  //# sourceMappingURL=orchestration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"orchestration.d.ts","sourceRoot":"","sources":["../../src/release/orchestration.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"orchestration.d.ts","sourceRoot":"","sources":["../../src/release/orchestration.ts"],"names":[],"mappings":"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"}
@@ -16,9 +16,9 @@ export async function runReleaseVersion(shell, options) {
16
16
  return { mode: 'none', packages: [], status: 'no-release-needed' };
17
17
  }
18
18
  const headBeforeVersioning = await shell.gitHead();
19
- await shell.runNxReleaseVersion(versionPackages, options.bump, options.dryRun);
19
+ const dryRunPackages = await shell.runNxReleaseVersion(versionPackages, options.bump, options.dryRun);
20
20
  if (options.dryRun) {
21
- return { mode: 'none', packages: [], status: 'dry-run' };
21
+ return { mode: 'none', packages: dryRunPackages, status: 'dry-run' };
22
22
  }
23
23
  await shell.assertCleanGitTree();
24
24
  const headAfterVersioning = await shell.gitHead();
@@ -51,7 +51,18 @@ export async function completeReleaseAtHead(shell, packages, dryRun, rerunRequir
51
51
  rerunRequired,
52
52
  });
53
53
  }
54
+ export async function bumpStableReleaseToNext(shell, packages, dryRun, rerunRequired) {
55
+ const stablePackages = packages.filter((pkg) => !pkg.version.includes('-'));
56
+ if (dryRun || rerunRequired || stablePackages.length === 0) {
57
+ return [];
58
+ }
59
+ await shell.bumpStablePackagesToNext(stablePackages);
60
+ return stablePackages;
61
+ }
54
62
  export async function repairPendingTargets(shell, targets, restoreRef, dryRun) {
63
+ if (dryRun) {
64
+ return targets.map((target) => repairDryRunSummary(target));
65
+ }
55
66
  const summaries = [];
56
67
  try {
57
68
  for (const target of targets) {
@@ -73,6 +84,20 @@ export async function repairPendingTargets(shell, targets, restoreRef, dryRun) {
73
84
  }
74
85
  return summaries;
75
86
  }
87
+ function repairDryRunSummary(target) {
88
+ return {
89
+ sha: target.sha,
90
+ dryRun: true,
91
+ packages: target.packages,
92
+ pushed: false,
93
+ published: [],
94
+ alreadyPublished: target.packages.filter((pkg) => !target.npmPackages.includes(pkg)),
95
+ githubReleases: [],
96
+ githubReleaseLinks: [],
97
+ rerunRequired: false,
98
+ noRelease: false,
99
+ };
100
+ }
76
101
  export async function completeRepairTargetAtHead(shell, target, dryRun) {
77
102
  return completePlannedRelease(shell, {
78
103
  sha: await shell.gitHead(),
@@ -96,8 +121,12 @@ async function completePlannedRelease(shell, input) {
96
121
  for (const { pkg, distTag } of plan.publishPackages) {
97
122
  await shell.publishPackage(pkg, distTag, input.dryRun);
98
123
  }
124
+ const githubReleaseLinks = [];
99
125
  for (const pkg of plan.githubReleasePackages) {
100
- await shell.createGithubRelease(pkg, input.dryRun);
126
+ const url = await shell.createGithubRelease(pkg, input.dryRun);
127
+ if (url) {
128
+ githubReleaseLinks.push({ pkg, url });
129
+ }
101
130
  }
102
131
  return {
103
132
  sha: input.sha,
@@ -107,6 +136,7 @@ async function completePlannedRelease(shell, input) {
107
136
  published: input.dryRun ? [] : plan.publishPackages.map((action) => action.pkg),
108
137
  alreadyPublished: input.packages.filter((pkg) => !input.npmMissingPackages.includes(pkg)),
109
138
  githubReleases: input.dryRun ? [] : plan.githubReleasePackages,
139
+ githubReleaseLinks,
110
140
  rerunRequired: input.rerunRequired,
111
141
  noRelease: false,
112
142
  };