@kb-labs/release-manager-cli 2.82.0 → 2.87.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 (72) hide show
  1. package/dist/cli/commands/build.d.ts +10 -0
  2. package/dist/cli/commands/build.js +87 -0
  3. package/dist/cli/commands/build.js.map +1 -0
  4. package/dist/cli/commands/changelog.js +65 -57
  5. package/dist/cli/commands/changelog.js.map +1 -1
  6. package/dist/cli/commands/checks.d.ts +10 -0
  7. package/dist/cli/commands/checks.js +93 -0
  8. package/dist/cli/commands/checks.js.map +1 -0
  9. package/dist/cli/commands/git.d.ts +13 -0
  10. package/dist/cli/commands/git.js +93 -0
  11. package/dist/cli/commands/git.js.map +1 -0
  12. package/dist/cli/commands/pack.d.ts +10 -0
  13. package/dist/cli/commands/pack.js +83 -0
  14. package/dist/cli/commands/pack.js.map +1 -0
  15. package/dist/cli/commands/plan.js +40 -41
  16. package/dist/cli/commands/plan.js.map +1 -1
  17. package/dist/cli/commands/publish.js +225 -194
  18. package/dist/cli/commands/publish.js.map +1 -1
  19. package/dist/cli/commands/report.js +28 -31
  20. package/dist/cli/commands/report.js.map +1 -1
  21. package/dist/cli/commands/rollback.js +0 -3
  22. package/dist/cli/commands/rollback.js.map +1 -1
  23. package/dist/cli/commands/run.js +386 -300
  24. package/dist/cli/commands/run.js.map +1 -1
  25. package/dist/cli/commands/verify.js +29 -28
  26. package/dist/cli/commands/verify.js.map +1 -1
  27. package/dist/cli/commands/version.d.ts +12 -0
  28. package/dist/cli/commands/version.js +111 -0
  29. package/dist/cli/commands/version.js.map +1 -0
  30. package/dist/index.d.ts +6 -5
  31. package/dist/index.js +146 -13
  32. package/dist/index.js.map +1 -1
  33. package/dist/manifest.d.ts +9 -0
  34. package/dist/manifest.js +131 -0
  35. package/dist/manifest.js.map +1 -1
  36. package/dist/rest/handlers/build-handler.js +22 -20
  37. package/dist/rest/handlers/build-handler.js.map +1 -1
  38. package/dist/rest/handlers/changelog-generate-handler.js +75 -88
  39. package/dist/rest/handlers/changelog-generate-handler.js.map +1 -1
  40. package/dist/rest/handlers/changelog-handler.js +1 -4
  41. package/dist/rest/handlers/changelog-handler.js.map +1 -1
  42. package/dist/rest/handlers/changelog-save-handler.js +0 -2
  43. package/dist/rest/handlers/changelog-save-handler.js.map +1 -1
  44. package/dist/rest/handlers/checklist-handler.js +113 -99
  45. package/dist/rest/handlers/checklist-handler.js.map +1 -1
  46. package/dist/rest/handlers/generate-handler.js +0 -3
  47. package/dist/rest/handlers/generate-handler.js.map +1 -1
  48. package/dist/rest/handlers/git-timeline-handler.js.map +1 -1
  49. package/dist/rest/handlers/history-changelog-handler.js +0 -1
  50. package/dist/rest/handlers/history-changelog-handler.js.map +1 -1
  51. package/dist/rest/handlers/history-handler.js +2 -3
  52. package/dist/rest/handlers/history-handler.js.map +1 -1
  53. package/dist/rest/handlers/history-plan-handler.js +0 -1
  54. package/dist/rest/handlers/history-plan-handler.js.map +1 -1
  55. package/dist/rest/handlers/history-report-handler.js +0 -1
  56. package/dist/rest/handlers/history-report-handler.js.map +1 -1
  57. package/dist/rest/handlers/plan-handler.js +0 -3
  58. package/dist/rest/handlers/plan-handler.js.map +1 -1
  59. package/dist/rest/handlers/preview-handler.js +0 -1
  60. package/dist/rest/handlers/preview-handler.js.map +1 -1
  61. package/dist/rest/handlers/report-handler.js +1 -1
  62. package/dist/rest/handlers/report-handler.js.map +1 -1
  63. package/dist/rest/handlers/reset-handler.js +0 -1
  64. package/dist/rest/handlers/reset-handler.js.map +1 -1
  65. package/dist/rest/handlers/run-checks-handler.js +0 -1
  66. package/dist/rest/handlers/run-checks-handler.js.map +1 -1
  67. package/dist/rest/handlers/run-handler.js +72 -52
  68. package/dist/rest/handlers/run-handler.js.map +1 -1
  69. package/dist/rest/handlers/status-handler.js +0 -2
  70. package/dist/rest/handlers/status-handler.js.map +1 -1
  71. package/dist/setup/handler.js.map +1 -1
  72. package/package.json +9 -8
@@ -1,12 +1,10 @@
1
1
  import * as readline from 'readline/promises';
2
- import { defineCommand, findRepoRoot, useLoader, useConfig, useLLM, useEnv, useLogger } from '@kb-labs/sdk';
2
+ import { defineCommand, findRepoRoot, useLoader, useConfig, useEnv, useLLM, useLogger } from '@kb-labs/sdk';
3
3
  import { resolveScopePath, planRelease, runReleasePipeline } from '@kb-labs/release-manager-core';
4
- import { join } from 'path';
5
- import 'fs/promises';
6
- import 'globby';
7
4
  import { generateChangelog, generateSimpleChangelog } from '@kb-labs/release-manager-changelog';
8
5
  import { spawn } from 'child_process';
9
6
  import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
7
+ import { join } from 'path';
10
8
 
11
9
  // src/cli/commands/run.ts
12
10
  function createChangelogGenerator(config, llm) {
@@ -52,6 +50,44 @@ function createChangelogGenerator(config, llm) {
52
50
  }
53
51
  };
54
52
  }
53
+ function rewriteLinkDep(deps, depName, val, pkgPath, versionMap) {
54
+ const pinned = versionMap.get(depName);
55
+ if (pinned) {
56
+ deps[depName] = `^${pinned}`;
57
+ return;
58
+ }
59
+ try {
60
+ const linkPath = val.slice("link:".length);
61
+ const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, "package.json"), "utf-8"));
62
+ deps[depName] = `^${linked.version ?? "*"}`;
63
+ } catch {
64
+ deps[depName] = "*";
65
+ }
66
+ }
67
+ function rewriteWorkspaceDep(deps, depName, val, versionMap) {
68
+ const pinned = versionMap.get(depName);
69
+ if (!pinned) {
70
+ return false;
71
+ }
72
+ deps[depName] = val === "workspace:*" ? `^${pinned}` : val.replace("workspace:", "");
73
+ return true;
74
+ }
75
+ function rewriteDepsSection(deps, pkgPath, versionMap, packageManager) {
76
+ let modified = false;
77
+ for (const depName of Object.keys(deps)) {
78
+ const val = deps[depName];
79
+ if (typeof val !== "string") {
80
+ continue;
81
+ }
82
+ if (val.startsWith("link:")) {
83
+ rewriteLinkDep(deps, depName, val, pkgPath, versionMap);
84
+ modified = true;
85
+ } else if (val.startsWith("workspace:") && packageManager !== "pnpm" && rewriteWorkspaceDep(deps, depName, val, versionMap)) {
86
+ modified = true;
87
+ }
88
+ }
89
+ return modified;
90
+ }
55
91
  function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
56
92
  const pkgJsonPath = join(pkgPath, "package.json");
57
93
  const original = readFileSync(pkgJsonPath, "utf-8");
@@ -62,32 +98,8 @@ function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
62
98
  if (!deps) {
63
99
  continue;
64
100
  }
65
- for (const depName of Object.keys(deps)) {
66
- const val = deps[depName];
67
- if (typeof val !== "string") {
68
- continue;
69
- }
70
- if (val.startsWith("link:")) {
71
- const pinned = versionMap.get(depName);
72
- if (pinned) {
73
- deps[depName] = `^${pinned}`;
74
- } else {
75
- try {
76
- const linkPath = val.slice("link:".length);
77
- const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, "package.json"), "utf-8"));
78
- deps[depName] = `^${linked.version ?? "*"}`;
79
- } catch {
80
- deps[depName] = "*";
81
- }
82
- }
83
- modified = true;
84
- } else if (val.startsWith("workspace:") && packageManager !== "pnpm") {
85
- const pinned = versionMap.get(depName);
86
- if (pinned) {
87
- deps[depName] = val === "workspace:*" ? `^${pinned}` : val.replace("workspace:", "");
88
- modified = true;
89
- }
90
- }
101
+ if (rewriteDepsSection(deps, pkgPath, versionMap, packageManager)) {
102
+ modified = true;
91
103
  }
92
104
  }
93
105
  if (modified) {
@@ -95,8 +107,7 @@ function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
95
107
  }
96
108
  return () => writeFileSync(pkgJsonPath, original);
97
109
  }
98
- function topoSort(packages, packageManager) {
99
- const nameSet = new Set(packages.map((p) => p.name));
110
+ function buildIntraDeps(packages, nameSet) {
100
111
  const deps = /* @__PURE__ */ new Map();
101
112
  for (const pkg of packages) {
102
113
  const intra = /* @__PURE__ */ new Set();
@@ -113,12 +124,9 @@ function topoSort(packages, packageManager) {
113
124
  }
114
125
  deps.set(pkg.name, intra);
115
126
  }
116
- const inDegree = new Map(packages.map((p) => [p.name, 0]));
117
- for (const [, d] of deps) {
118
- for (const dep of d) {
119
- inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
120
- }
121
- }
127
+ return deps;
128
+ }
129
+ function buildDependantsMap(packages, deps) {
122
130
  const dependants = /* @__PURE__ */ new Map();
123
131
  for (const pkg of packages) {
124
132
  for (const dep of deps.get(pkg.name) ?? []) {
@@ -128,10 +136,13 @@ function topoSort(packages, packageManager) {
128
136
  dependants.get(dep).add(pkg.name);
129
137
  }
130
138
  }
139
+ return dependants;
140
+ }
141
+ function computeWaves(packages, deps, dependants) {
142
+ const byName = new Map(packages.map((p) => [p.name, p]));
131
143
  const remaining = new Map(
132
144
  packages.map((p) => [p.name, (deps.get(p.name) ?? /* @__PURE__ */ new Set()).size])
133
145
  );
134
- const byName = new Map(packages.map((p) => [p.name, p]));
135
146
  const waves = [];
136
147
  while (remaining.size > 0) {
137
148
  const wave = [...remaining.entries()].filter(([, count]) => count === 0).map(([name]) => byName.get(name));
@@ -151,6 +162,12 @@ function topoSort(packages, packageManager) {
151
162
  }
152
163
  return waves.filter((w) => w.length > 0);
153
164
  }
165
+ function topoSort(packages, _packageManager) {
166
+ const nameSet = new Set(packages.map((p) => p.name));
167
+ const deps = buildIntraDeps(packages, nameSet);
168
+ const dependants = buildDependantsMap(packages, deps);
169
+ return computeWaves(packages, deps, dependants);
170
+ }
154
171
 
155
172
  // src/shared/publish-programmatic.ts
156
173
  var MAX_RETRIES = 5;
@@ -292,7 +309,9 @@ async function publishOne(pkg, opts, logger) {
292
309
  logger.warn(
293
310
  `Transient error for ${pkg.name}@${pkg.version} (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${(delay / 1e3).toFixed(1)}s: ${message.split("\n")[0]}`
294
311
  );
295
- await new Promise((r) => setTimeout(r, delay));
312
+ await new Promise((r) => {
313
+ setTimeout(r, delay);
314
+ });
296
315
  continue;
297
316
  }
298
317
  logger.error(`Failed to publish ${pkg.name}@${pkg.version}`, void 0, { error: message });
@@ -309,6 +328,20 @@ async function publishOne(pkg, opts, logger) {
309
328
  restore();
310
329
  }
311
330
  }
331
+ function skipRemainingWaves(waves, waveIdx, waveFailed, logger, allResults) {
332
+ const remaining = waves.slice(waveIdx + 1).reduce((acc, w) => acc + w.length, 0);
333
+ if (remaining === 0) {
334
+ return;
335
+ }
336
+ logger.warn(
337
+ `Wave ${waveIdx + 1} had ${waveFailed.length} failure(s) \u2014 skipping ${remaining} package(s) in later waves to avoid broken dependency chain`
338
+ );
339
+ for (const laterWave of waves.slice(waveIdx + 1)) {
340
+ for (const pkg of laterWave) {
341
+ allResults.push({ name: pkg.name, version: pkg.version, success: false, error: "Skipped: dependency wave failed" });
342
+ }
343
+ }
344
+ }
312
345
  async function publishPackagesProgrammatic(options) {
313
346
  const {
314
347
  packages,
@@ -354,22 +387,7 @@ async function publishPackagesProgrammatic(options) {
354
387
  }
355
388
  const waveFailed = allResults.slice(-wave.length).filter((r) => !r.success);
356
389
  if (waveFailed.length > 0) {
357
- const remaining = waves.slice(waveIdx + 1).reduce((acc, w) => acc + w.length, 0);
358
- if (remaining > 0) {
359
- logger.warn(
360
- `Wave ${waveIdx + 1} had ${waveFailed.length} failure(s) \u2014 skipping ${remaining} package(s) in later waves to avoid broken dependency chain`
361
- );
362
- for (const laterWave of waves.slice(waveIdx + 1)) {
363
- for (const pkg of laterWave) {
364
- allResults.push({
365
- name: pkg.name,
366
- version: pkg.version,
367
- success: false,
368
- error: "Skipped: dependency wave failed"
369
- });
370
- }
371
- }
372
- }
390
+ skipRemainingWaves(waves, waveIdx, waveFailed, logger, allResults);
373
391
  break;
374
392
  }
375
393
  }
@@ -461,65 +479,125 @@ function publishSinglePackage(options) {
461
479
  child.on("error", (err) => reject(err));
462
480
  });
463
481
  }
464
- async function publishPackagesWithOTP(options) {
465
- const { packages, packageManager = "pnpm", dryRun, tag, access, ui, logger } = options;
466
- let otp = options.otp;
467
- const results = [];
468
- const versionMap = new Map(packages.map((p) => [p.name, p.version]));
469
- const CONCURRENCY = Number(useEnv("KB_PUBLISH_CONCURRENCY") ?? 4);
470
- const remaining = [];
471
- let otpFailureDetected = false;
472
- const fastPublishOne = async (pkg) => {
473
- const restore = rewriteWorkspaceDeps(pkg.path, versionMap, packageManager);
474
- try {
475
- for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
476
- try {
477
- await publishSinglePackage({ packagePath: pkg.path, packageManager, otp, dryRun, tag, access });
478
- logger?.info("Published (fast-path)", { name: pkg.name, version: pkg.version });
479
- return { name: pkg.name, version: pkg.version, success: true };
480
- } catch (err) {
481
- const msg = err instanceof Error ? err.message : String(err);
482
- if (isAlreadyPublished2(msg)) {
483
- logger?.info(`${pkg.name}@${pkg.version} already published \u2014 skipping`);
484
- return { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true };
485
- }
486
- if (msg.includes("EOTP") || msg.includes("one-time password")) {
487
- otpFailureDetected = true;
488
- return null;
489
- }
490
- if (isRetryable2(msg) && attempt < MAX_RETRIES2) {
491
- const delay = retryDelay2(attempt);
492
- logger?.warn(
493
- `Transient error for ${pkg.name} (attempt ${attempt + 1}/${MAX_RETRIES2}), retrying in ${(delay / 1e3).toFixed(1)}s`
494
- );
495
- await new Promise((r) => setTimeout(r, delay));
496
- continue;
482
+ async function fastPublishPkg(pkg, ctx, logger) {
483
+ const restore = rewriteWorkspaceDeps(pkg.path, ctx.versionMap, ctx.packageManager);
484
+ try {
485
+ for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
486
+ try {
487
+ await publishSinglePackage({ packagePath: pkg.path, packageManager: ctx.packageManager, otp: ctx.otp, dryRun: ctx.dryRun, tag: ctx.tag, access: ctx.access });
488
+ logger?.info("Published (fast-path)", { name: pkg.name, version: pkg.version });
489
+ return { result: { name: pkg.name, version: pkg.version, success: true }, otpFailed: false };
490
+ } catch (err) {
491
+ const msg = err instanceof Error ? err.message : String(err);
492
+ if (isAlreadyPublished2(msg)) {
493
+ logger?.info(`${pkg.name}@${pkg.version} already published \u2014 skipping`);
494
+ return { result: { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true }, otpFailed: false };
495
+ }
496
+ if (msg.includes("EOTP") || msg.includes("one-time password")) {
497
+ return { result: null, otpFailed: true };
498
+ }
499
+ if (isRetryable2(msg) && attempt < MAX_RETRIES2) {
500
+ const delay = retryDelay2(attempt);
501
+ logger?.warn(`Transient error for ${pkg.name} (attempt ${attempt + 1}/${MAX_RETRIES2}), retrying in ${(delay / 1e3).toFixed(1)}s`);
502
+ await new Promise((r) => {
503
+ setTimeout(r, delay);
504
+ });
505
+ continue;
506
+ }
507
+ logger?.error("Publish failed (fast-path)", void 0, { name: pkg.name, error: msg });
508
+ return { result: { name: pkg.name, version: pkg.version, success: false, error: msg }, otpFailed: false };
509
+ }
510
+ }
511
+ return { result: { name: pkg.name, version: pkg.version, success: false, error: `Publish failed after ${MAX_RETRIES2} retries (transient errors)` }, otpFailed: false };
512
+ } finally {
513
+ restore();
514
+ }
515
+ }
516
+ async function promptForOtp(ui, loader, pkgName) {
517
+ loader.succeed(`\u{1F510} 2FA required for ${pkgName}`);
518
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
519
+ try {
520
+ const entered = await rl.question(" Enter OTP code: ");
521
+ rl.close();
522
+ if (!entered || entered.trim().length !== 6) {
523
+ ui.write?.(" \u26A0\uFE0F Invalid OTP (must be 6 digits)\n");
524
+ return void 0;
525
+ }
526
+ return entered.trim();
527
+ } catch (e) {
528
+ rl.close();
529
+ throw e;
530
+ }
531
+ }
532
+ async function sequentialPublishPkg(pkg, ctx, otpState, ui, logger) {
533
+ const loader = useLoader(`Publishing ${pkg.name}@${pkg.version}...`);
534
+ loader.start();
535
+ const restore = rewriteWorkspaceDeps(pkg.path, ctx.versionMap, ctx.packageManager);
536
+ try {
537
+ const MAX_OTP_ATTEMPTS = 3;
538
+ for (let otpAttempts = 0; otpAttempts < MAX_OTP_ATTEMPTS; otpAttempts++) {
539
+ try {
540
+ await publishSinglePackage({ packagePath: pkg.path, packageManager: ctx.packageManager, otp: otpState.otp, dryRun: ctx.dryRun, tag: ctx.tag, access: ctx.access });
541
+ loader.succeed(ctx.dryRun ? `Dry-run: ${pkg.name}@${pkg.version}` : `Published ${pkg.name}@${pkg.version}`);
542
+ return { name: pkg.name, version: pkg.version, success: true };
543
+ } catch (err) {
544
+ const msg = err instanceof Error ? err.message : String(err);
545
+ if (isAlreadyPublished2(msg)) {
546
+ loader.succeed(`${pkg.name}@${pkg.version} already published`);
547
+ return { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true };
548
+ }
549
+ if (msg.includes("EOTP") || msg.includes("one-time password")) {
550
+ if (otpAttempts + 1 < MAX_OTP_ATTEMPTS) {
551
+ otpState.otp = await promptForOtp(ui, loader, pkg.name);
552
+ loader.update({ text: `Publishing ${pkg.name}@${pkg.version} with OTP...` });
553
+ loader.start();
554
+ } else {
555
+ loader.fail("Max OTP attempts reached");
556
+ return { name: pkg.name, version: pkg.version, success: false, error: "Max OTP attempts reached" };
497
557
  }
498
- logger?.error("Publish failed (fast-path)", { name: pkg.name, error: msg });
558
+ } else if (isRetryable2(msg)) {
559
+ const delay = retryDelay2(MAX_RETRIES2 - 1);
560
+ loader.update({ text: `Rate limited \u2014 waiting ${(delay / 1e3).toFixed(0)}s\u2026` });
561
+ loader.start();
562
+ await new Promise((r) => {
563
+ setTimeout(r, delay);
564
+ });
565
+ otpAttempts--;
566
+ } else {
567
+ loader.fail(`Failed: ${msg.split("\n")[0]}`);
499
568
  return { name: pkg.name, version: pkg.version, success: false, error: msg };
500
569
  }
501
570
  }
502
- return {
503
- name: pkg.name,
504
- version: pkg.version,
505
- success: false,
506
- error: `Publish failed after ${MAX_RETRIES2} retries (transient errors)`
507
- };
508
- } finally {
509
- restore();
510
571
  }
511
- };
572
+ return { name: pkg.name, version: pkg.version, success: false, error: "Publish failed after max OTP attempts" };
573
+ } finally {
574
+ restore();
575
+ }
576
+ }
577
+ async function publishPackagesWithOTP(options) {
578
+ const { packages, packageManager = "pnpm", dryRun, tag, access, ui, logger } = options;
579
+ const otpState = { otp: options.otp };
580
+ const versionMap = new Map(packages.map((p) => [p.name, p.version]));
581
+ const fastCtx = { packageManager, versionMap, otp: otpState.otp, dryRun, tag, access };
582
+ const seqCtx = { packageManager, versionMap, dryRun, tag, access };
583
+ const results = [];
584
+ const remaining = [];
585
+ const CONCURRENCY = Number(useEnv("KB_PUBLISH_CONCURRENCY") ?? 4);
586
+ let otpFailureDetected = false;
512
587
  ui.write?.(` Publishing ${packages.length} package(s) in parallel (concurrency=${CONCURRENCY})...
513
588
  `);
514
589
  for (let i = 0; i < packages.length; i += CONCURRENCY) {
515
590
  const batch = packages.slice(i, i + CONCURRENCY);
516
- const batchResults = await Promise.all(batch.map(fastPublishOne));
591
+ const outcomes = await Promise.all(batch.map((pkg) => fastPublishPkg(pkg, fastCtx, logger)));
517
592
  for (let j = 0; j < batch.length; j++) {
518
- const res = batchResults[j];
519
- if (res === null || res === void 0) {
593
+ const outcome = outcomes[j];
594
+ if (outcome.result === null) {
520
595
  remaining.push(batch[j]);
521
596
  } else {
522
- results.push(res);
597
+ results.push(outcome.result);
598
+ }
599
+ if (outcome.otpFailed) {
600
+ otpFailureDetected = true;
523
601
  }
524
602
  }
525
603
  if (otpFailureDetected) {
@@ -534,68 +612,7 @@ async function publishPackagesWithOTP(options) {
534
612
  `);
535
613
  for (const pkg of remaining) {
536
614
  logger?.info("Publishing package (sequential)", { name: pkg.name, version: pkg.version });
537
- const loader = useLoader(`Publishing ${pkg.name}@${pkg.version}...`);
538
- loader.start();
539
- const restore = rewriteWorkspaceDeps(pkg.path, versionMap, packageManager);
540
- try {
541
- let published = false;
542
- let otpAttempts = 0;
543
- const MAX_OTP_ATTEMPTS = 3;
544
- while (!published && otpAttempts < MAX_OTP_ATTEMPTS) {
545
- otpAttempts++;
546
- try {
547
- await publishSinglePackage({ packagePath: pkg.path, packageManager, otp, dryRun, tag, access });
548
- published = true;
549
- results.push({ name: pkg.name, version: pkg.version, success: true });
550
- loader.succeed(dryRun ? `Dry-run: ${pkg.name}@${pkg.version}` : `Published ${pkg.name}@${pkg.version}`);
551
- } catch (err) {
552
- const msg = err instanceof Error ? err.message : String(err);
553
- if (isAlreadyPublished2(msg)) {
554
- published = true;
555
- results.push({ name: pkg.name, version: pkg.version, success: true, alreadyPublished: true });
556
- loader.succeed(`${pkg.name}@${pkg.version} already published`);
557
- break;
558
- }
559
- if (msg.includes("EOTP") || msg.includes("one-time password")) {
560
- if (otpAttempts < MAX_OTP_ATTEMPTS) {
561
- loader.succeed(`\u{1F510} 2FA required for ${pkg.name}`);
562
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
563
- try {
564
- const entered = await rl.question(" Enter OTP code: ");
565
- rl.close();
566
- if (!entered || entered.trim().length !== 6) {
567
- ui.write?.(" \u26A0\uFE0F Invalid OTP (must be 6 digits)\n");
568
- otp = void 0;
569
- } else {
570
- otp = entered.trim();
571
- }
572
- } catch (e) {
573
- rl.close();
574
- throw e;
575
- }
576
- loader.update({ text: `Publishing ${pkg.name}@${pkg.version} with OTP...` });
577
- loader.start();
578
- } else {
579
- loader.fail("Max OTP attempts reached");
580
- results.push({ name: pkg.name, version: pkg.version, success: false, error: "Max OTP attempts reached" });
581
- break;
582
- }
583
- } else if (isRetryable2(msg)) {
584
- const delay = retryDelay2(MAX_RETRIES2 - 1);
585
- loader.update({ text: `Rate limited \u2014 waiting ${(delay / 1e3).toFixed(0)}s\u2026` });
586
- loader.start();
587
- await new Promise((r) => setTimeout(r, delay));
588
- otpAttempts--;
589
- } else {
590
- loader.fail(`Failed: ${msg.split("\n")[0]}`);
591
- results.push({ name: pkg.name, version: pkg.version, success: false, error: msg });
592
- break;
593
- }
594
- }
595
- }
596
- } finally {
597
- restore();
598
- }
615
+ results.push(await sequentialPublishPkg(pkg, seqCtx, otpState, ui));
599
616
  }
600
617
  return buildResult(results, packages, dryRun);
601
618
  }
@@ -617,6 +634,173 @@ var BUMP_SYMBOL = {
617
634
  patch: "\xB7",
618
635
  auto: "?"
619
636
  };
637
+ function buildPlanRows(packages) {
638
+ return packages.map((pkg) => {
639
+ const bump = pkg.bump ?? "auto";
640
+ const sym = BUMP_SYMBOL[bump] ?? "?";
641
+ return ` ${sym} ${pkg.name.padEnd(40)} ${pkg.currentVersion.padStart(8)} \u2192 ${pkg.nextVersion}`;
642
+ });
643
+ }
644
+ function createPublisher(config, token, ctx) {
645
+ const packageManager = config.workspace?.type ?? config.publish?.packageManager ?? "pnpm";
646
+ return {
647
+ async publish(packages, opts) {
648
+ if (token) {
649
+ return publishPackagesProgrammatic({
650
+ packages,
651
+ packageManager,
652
+ dryRun: opts.dryRun,
653
+ access: opts.access ?? "public",
654
+ registry: config.registry,
655
+ token
656
+ });
657
+ }
658
+ return publishPackagesWithOTP({
659
+ packages,
660
+ packageManager,
661
+ dryRun: opts.dryRun,
662
+ access: opts.access ?? "public",
663
+ ui: ctx.ui,
664
+ logger: ctx.platform?.logger
665
+ });
666
+ }
667
+ };
668
+ }
669
+ function appendCheckDetails(c, checkItems) {
670
+ if (c.details?.packagePath) {
671
+ checkItems.push(` package: ${c.details.packagePath}`);
672
+ }
673
+ if (c.details?.error) {
674
+ checkItems.push(` reason: ${c.details.error}`);
675
+ }
676
+ const output = (c.details?.stderr || c.details?.stdout || "").trim();
677
+ if (output) {
678
+ checkItems.push(` output:`);
679
+ for (const line of output.split("\n").slice(0, 8)) {
680
+ checkItems.push(` ${line}`);
681
+ }
682
+ }
683
+ const failedPkgs = (c.packages ?? []).filter((p) => !p.ok);
684
+ if (failedPkgs.length) {
685
+ checkItems.push(` failed in ${failedPkgs.length}/${c.packages.length} package(s):`);
686
+ for (const pkg of failedPkgs.slice(0, 10)) {
687
+ checkItems.push(` - ${pkg.path}${pkg.details?.error ? `: ${pkg.details.error}` : ""}`);
688
+ }
689
+ }
690
+ }
691
+ function buildCheckItems(checkEntries, symbols) {
692
+ const checkItems = [];
693
+ for (const c of checkEntries) {
694
+ const sym = c.ok ? symbols.success : c.hint === "optional" ? symbols.warning : symbols.error;
695
+ const timing = c.timingMs ? ` (${(c.timingMs / 1e3).toFixed(1)}s)` : "";
696
+ checkItems.push(`${sym} ${c.id}${timing}`);
697
+ if (!c.ok && c.hint !== "optional") {
698
+ appendCheckDetails(c, checkItems);
699
+ }
700
+ }
701
+ return checkItems;
702
+ }
703
+ function buildReleaseSections(report, dryRun, ctx) {
704
+ const sections = [];
705
+ if (!dryRun && report.result.published?.length) {
706
+ sections.push({
707
+ header: "Published",
708
+ items: report.result.published.map((p) => `${ctx.ui.symbols.success} ${p}`)
709
+ });
710
+ }
711
+ if (!dryRun && report.result.alreadyPublished?.length) {
712
+ sections.push({
713
+ header: "Already published (skipped)",
714
+ items: report.result.alreadyPublished.map((p) => `${ctx.ui.symbols.info} ${p}`)
715
+ });
716
+ }
717
+ if (dryRun && report.result.skipped?.length) {
718
+ sections.push({
719
+ header: "Would publish (dry-run)",
720
+ items: report.result.skipped.map((p) => `${ctx.ui.symbols.info} ${p.replace(" (dry-run)", "")}`)
721
+ });
722
+ }
723
+ if (report.result.checks) {
724
+ const checkEntries = Object.values(report.result.checks).filter(Boolean);
725
+ const passed = checkEntries.filter((c) => c.ok);
726
+ const failed = checkEntries.filter((c) => !c.ok && c.hint !== "optional");
727
+ const skipped = checkEntries.filter((c) => !c.ok && c.hint === "optional");
728
+ if (checkEntries.length > 0) {
729
+ sections.push({
730
+ header: `Checks \u2014 ${passed.length} passed, ${failed.length} failed${skipped.length ? `, ${skipped.length} skipped` : ""}`,
731
+ items: buildCheckItems(checkEntries, ctx.ui.symbols)
732
+ });
733
+ }
734
+ }
735
+ if (report.result.errors?.length && !report.result.checks) {
736
+ sections.push({
737
+ header: "Errors",
738
+ items: report.result.errors.map((e) => `${ctx.ui.symbols.error} ${e}`)
739
+ });
740
+ }
741
+ if (report.result.git) {
742
+ const g = report.result.git;
743
+ sections.push({
744
+ header: "Git",
745
+ items: [
746
+ `Committed: ${g.committed}`,
747
+ `Tags: ${g.tagged?.join(", ") || "none"}`,
748
+ `Pushed: ${g.pushed}`
749
+ ]
750
+ });
751
+ }
752
+ return sections;
753
+ }
754
+ function resolveChecks(flags, config) {
755
+ if (flags.flow) {
756
+ return config.flows?.[flags.flow]?.checks ?? config.checks ?? [];
757
+ }
758
+ if (flags.scope) {
759
+ return config.scopes?.[flags.scope]?.checks ?? config.checks ?? [];
760
+ }
761
+ return config.checks ?? [];
762
+ }
763
+ async function confirmRelease() {
764
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
765
+ let answer;
766
+ try {
767
+ answer = await rl.question("\nProceed with release? [y/N] ");
768
+ } finally {
769
+ rl.close();
770
+ }
771
+ return answer.trim().toLowerCase() === "y";
772
+ }
773
+ function showPlanBox(ctx, flags, plan, config, dryRun) {
774
+ if (flags.json) {
775
+ return;
776
+ }
777
+ ctx.ui.sideBox({
778
+ title: dryRun ? "Release Plan (dry-run)" : "Release Plan",
779
+ sections: [
780
+ {
781
+ header: `${plan.packages.length} package(s) \xB7 strategy: ${config.versioningStrategy ?? "independent"}${flags.flow ? ` \xB7 flow: ${flags.flow}` : ""}`,
782
+ items: buildPlanRows(plan.packages)
783
+ }
784
+ ],
785
+ status: "info"
786
+ });
787
+ if (flags["no-verify"]) {
788
+ ctx.ui.write?.(` \u26A0\uFE0F --no-verify: git pre-push hooks will be skipped
789
+ `);
790
+ }
791
+ }
792
+ function reportPipelineResult(ctx, flags, result, dryRun) {
793
+ if (flags.json) {
794
+ ctx.ui?.json?.(result.report);
795
+ return;
796
+ }
797
+ ctx.ui.sideBox({
798
+ title: "Release",
799
+ sections: buildReleaseSections(result.report, dryRun, ctx),
800
+ status: result.success ? "success" : "error",
801
+ timing: result.report.result.timingMs
802
+ });
803
+ }
620
804
  var run_default = defineCommand({
621
805
  id: "release:run",
622
806
  description: "Execute release process (plan, check, publish)",
@@ -636,6 +820,37 @@ var run_default = defineCommand({
636
820
  ...flags.strict !== void 0 && { strict: flags.strict }
637
821
  };
638
822
  configLoader.succeed("Configuration loaded");
823
+ const token = useEnv("NPM_TOKEN") ?? useEnv("NODE_AUTH_TOKEN");
824
+ if (!dryRun) {
825
+ const preErrors = [];
826
+ const registry = config.registry ?? "https://registry.npmjs.org";
827
+ if (!token) {
828
+ preErrors.push("NPM_TOKEN or NODE_AUTH_TOKEN is not set");
829
+ } else {
830
+ try {
831
+ const res = await fetch(`${registry}/-/whoami`, {
832
+ headers: { Authorization: `Bearer ${token}` },
833
+ signal: AbortSignal.timeout(8e3)
834
+ });
835
+ if (!res.ok) {
836
+ preErrors.push(`npm token invalid or expired (HTTP ${res.status} from ${registry})`);
837
+ }
838
+ } catch (e) {
839
+ preErrors.push(`npm registry unreachable: ${e instanceof Error ? e.message : String(e)}`);
840
+ }
841
+ }
842
+ if (flags.flow && config.flows && !(flags.flow in config.flows)) {
843
+ preErrors.push(`Flow "${flags.flow}" is not defined in release.flows config`);
844
+ }
845
+ if (preErrors.length > 0) {
846
+ ctx.ui.sideBox({
847
+ title: "Release \u2014 pre-flight failed",
848
+ sections: [{ items: preErrors.map((e) => `${ctx.ui.symbols.error} ${e}`) }],
849
+ status: "error"
850
+ });
851
+ return { exitCode: 1 };
852
+ }
853
+ }
639
854
  const scopeCwd = await resolveScopePath(repoRoot, flags.scope || "root");
640
855
  const planLoader = useLoader("Discovering packages...");
641
856
  planLoader.start();
@@ -655,36 +870,10 @@ var run_default = defineCommand({
655
870
  });
656
871
  return { exitCode: 0 };
657
872
  }
658
- if (!flags.json) {
659
- const rows = plan.packages.map((pkg) => {
660
- const bump = pkg.bump ?? "auto";
661
- const sym = BUMP_SYMBOL[bump] ?? "?";
662
- return ` ${sym} ${pkg.name.padEnd(40)} ${pkg.currentVersion.padStart(8)} \u2192 ${pkg.nextVersion}`;
663
- });
664
- ctx.ui.sideBox({
665
- title: dryRun ? "Release Plan (dry-run)" : "Release Plan",
666
- sections: [
667
- {
668
- header: `${plan.packages.length} package(s) \xB7 strategy: ${config.versioningStrategy ?? "independent"}${flags.flow ? ` \xB7 flow: ${flags.flow}` : ""}`,
669
- items: rows
670
- }
671
- ],
672
- status: "info"
673
- });
674
- if (flags["no-verify"]) {
675
- ctx.ui.write?.(` \u26A0\uFE0F --no-verify: git pre-push hooks will be skipped
676
- `);
677
- }
678
- }
873
+ showPlanBox(ctx, flags, plan, config, dryRun);
679
874
  if (!skipYes) {
680
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
681
- let answer;
682
- try {
683
- answer = await rl.question("\nProceed with release? [y/N] ");
684
- } finally {
685
- rl.close();
686
- }
687
- if (answer.trim().toLowerCase() !== "y") {
875
+ const confirmed = await confirmRelease();
876
+ if (!confirmed) {
688
877
  ctx.ui.sideBox({
689
878
  title: "Release",
690
879
  sections: [{ items: [`${ctx.ui.symbols.info} Cancelled`] }],
@@ -695,23 +884,7 @@ var run_default = defineCommand({
695
884
  }
696
885
  const llm = useLLM();
697
886
  const changelog = createChangelogGenerator(config, llm ?? void 0);
698
- const token = useEnv("NPM_TOKEN") ?? useEnv("NODE_AUTH_TOKEN");
699
- const packageManager = config.workspace?.type ?? config.publish?.packageManager ?? "pnpm";
700
- const publisher = {
701
- async publish(packages, opts) {
702
- if (token) {
703
- return publishPackagesProgrammatic({ packages, packageManager, dryRun: opts.dryRun });
704
- }
705
- return publishPackagesWithOTP({
706
- packages,
707
- packageManager,
708
- dryRun: opts.dryRun,
709
- access: opts.access ?? "public",
710
- ui: ctx.ui,
711
- logger: ctx.platform?.logger
712
- });
713
- }
714
- };
887
+ const publisher = createPublisher(config, token, ctx);
715
888
  const pipelineLoader = useLoader("Running release pipeline...");
716
889
  pipelineLoader.start();
717
890
  const pipelineStart = Date.now();
@@ -727,7 +900,7 @@ var run_default = defineCommand({
727
900
  skipBuild: flags["skip-build"],
728
901
  skipVerify: flags["skip-verify"],
729
902
  noVerify: flags["no-verify"],
730
- checks: (flags.flow ? config.flows?.[flags.flow]?.checks : void 0) ?? (flags.scope ? config.scopes?.[flags.scope]?.checks : void 0) ?? config.checks ?? [],
903
+ checks: resolveChecks(flags, config),
731
904
  publisher,
732
905
  changelog,
733
906
  logger: ctx.platform?.logger,
@@ -737,94 +910,7 @@ var run_default = defineCommand({
737
910
  }
738
911
  });
739
912
  pipelineLoader.succeed(result.success ? "Release completed" : "Release failed");
740
- if (flags.json) {
741
- ctx.ui?.json?.(result.report);
742
- } else {
743
- const report = result.report;
744
- const sections = [];
745
- if (!dryRun && report.result.published?.length) {
746
- sections.push({
747
- header: "Published",
748
- items: report.result.published.map((p) => `${ctx.ui.symbols.success} ${p}`)
749
- });
750
- }
751
- if (!dryRun && report.result.alreadyPublished?.length) {
752
- sections.push({
753
- header: "Already published (skipped)",
754
- items: report.result.alreadyPublished.map((p) => `${ctx.ui.symbols.info} ${p}`)
755
- });
756
- }
757
- if (dryRun && report.result.skipped?.length) {
758
- sections.push({
759
- header: "Would publish (dry-run)",
760
- items: report.result.skipped.map((p) => `${ctx.ui.symbols.info} ${p.replace(" (dry-run)", "")}`)
761
- });
762
- }
763
- if (report.result.checks) {
764
- const checkEntries = Object.values(report.result.checks).filter(Boolean);
765
- const passed = checkEntries.filter((c) => c.ok);
766
- const failed = checkEntries.filter((c) => !c.ok && c.hint !== "optional");
767
- const skipped = checkEntries.filter((c) => !c.ok && c.hint === "optional");
768
- if (checkEntries.length > 0) {
769
- const checkItems = [];
770
- for (const c of checkEntries) {
771
- const sym = c.ok ? ctx.ui.symbols.success : c.hint === "optional" ? ctx.ui.symbols.warning : ctx.ui.symbols.error;
772
- const timing = c.timingMs ? ` (${(c.timingMs / 1e3).toFixed(1)}s)` : "";
773
- checkItems.push(`${sym} ${c.id}${timing}`);
774
- if (!c.ok && c.hint !== "optional") {
775
- if (c.details?.packagePath) {
776
- checkItems.push(` package: ${c.details.packagePath}`);
777
- }
778
- if (c.details?.error) {
779
- checkItems.push(` reason: ${c.details.error}`);
780
- }
781
- const output = (c.details?.stderr || c.details?.stdout || "").trim();
782
- if (output) {
783
- const lines = output.split("\n").slice(0, 8);
784
- checkItems.push(` output:`);
785
- for (const line of lines) {
786
- checkItems.push(` ${line}`);
787
- }
788
- }
789
- if (c.packages?.filter((p) => !p.ok).length) {
790
- const failedPkgs = c.packages.filter((p) => !p.ok);
791
- checkItems.push(` failed in ${failedPkgs.length}/${c.packages.length} package(s):`);
792
- for (const pkg of failedPkgs.slice(0, 10)) {
793
- checkItems.push(` - ${pkg.path}${pkg.details?.error ? `: ${pkg.details.error}` : ""}`);
794
- }
795
- }
796
- }
797
- }
798
- sections.push({
799
- header: `Checks \u2014 ${passed.length} passed, ${failed.length} failed${skipped.length ? `, ${skipped.length} skipped` : ""}`,
800
- items: checkItems
801
- });
802
- }
803
- }
804
- if (report.result.errors?.length && !report.result.checks) {
805
- sections.push({
806
- header: "Errors",
807
- items: report.result.errors.map((e) => `${ctx.ui.symbols.error} ${e}`)
808
- });
809
- }
810
- if (report.result.git) {
811
- const g = report.result.git;
812
- sections.push({
813
- header: "Git",
814
- items: [
815
- `Committed: ${g.committed}`,
816
- `Tags: ${g.tagged?.join(", ") || "none"}`,
817
- `Pushed: ${g.pushed}`
818
- ]
819
- });
820
- }
821
- ctx.ui.sideBox({
822
- title: "Release",
823
- sections,
824
- status: result.success ? "success" : "error",
825
- timing: report.result.timingMs
826
- });
827
- }
913
+ reportPipelineResult(ctx, flags, result, dryRun);
828
914
  return { exitCode: result.success ? 0 : 1, report: result.report };
829
915
  }
830
916
  }