@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,13 +1,49 @@
1
1
  import { defineCommand, useEnv, findRepoRoot, useLoader, useConfig, useLogger } from '@kb-labs/sdk';
2
2
  import { planRelease } from '@kb-labs/release-manager-core';
3
- import { join } from 'path';
4
- import 'fs/promises';
5
- import 'globby';
6
3
  import { spawn } from 'child_process';
7
4
  import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
5
+ import { join } from 'path';
8
6
  import * as readline from 'readline/promises';
9
7
 
10
8
  // src/cli/commands/publish.ts
9
+ function rewriteLinkDep(deps, depName, val, pkgPath, versionMap) {
10
+ const pinned = versionMap.get(depName);
11
+ if (pinned) {
12
+ deps[depName] = `^${pinned}`;
13
+ return;
14
+ }
15
+ try {
16
+ const linkPath = val.slice("link:".length);
17
+ const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, "package.json"), "utf-8"));
18
+ deps[depName] = `^${linked.version ?? "*"}`;
19
+ } catch {
20
+ deps[depName] = "*";
21
+ }
22
+ }
23
+ function rewriteWorkspaceDep(deps, depName, val, versionMap) {
24
+ const pinned = versionMap.get(depName);
25
+ if (!pinned) {
26
+ return false;
27
+ }
28
+ deps[depName] = val === "workspace:*" ? `^${pinned}` : val.replace("workspace:", "");
29
+ return true;
30
+ }
31
+ function rewriteDepsSection(deps, pkgPath, versionMap, packageManager) {
32
+ let modified = false;
33
+ for (const depName of Object.keys(deps)) {
34
+ const val = deps[depName];
35
+ if (typeof val !== "string") {
36
+ continue;
37
+ }
38
+ if (val.startsWith("link:")) {
39
+ rewriteLinkDep(deps, depName, val, pkgPath, versionMap);
40
+ modified = true;
41
+ } else if (val.startsWith("workspace:") && packageManager !== "pnpm" && rewriteWorkspaceDep(deps, depName, val, versionMap)) {
42
+ modified = true;
43
+ }
44
+ }
45
+ return modified;
46
+ }
11
47
  function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
12
48
  const pkgJsonPath = join(pkgPath, "package.json");
13
49
  const original = readFileSync(pkgJsonPath, "utf-8");
@@ -18,32 +54,8 @@ function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
18
54
  if (!deps) {
19
55
  continue;
20
56
  }
21
- for (const depName of Object.keys(deps)) {
22
- const val = deps[depName];
23
- if (typeof val !== "string") {
24
- continue;
25
- }
26
- if (val.startsWith("link:")) {
27
- const pinned = versionMap.get(depName);
28
- if (pinned) {
29
- deps[depName] = `^${pinned}`;
30
- } else {
31
- try {
32
- const linkPath = val.slice("link:".length);
33
- const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, "package.json"), "utf-8"));
34
- deps[depName] = `^${linked.version ?? "*"}`;
35
- } catch {
36
- deps[depName] = "*";
37
- }
38
- }
39
- modified = true;
40
- } else if (val.startsWith("workspace:") && packageManager !== "pnpm") {
41
- const pinned = versionMap.get(depName);
42
- if (pinned) {
43
- deps[depName] = val === "workspace:*" ? `^${pinned}` : val.replace("workspace:", "");
44
- modified = true;
45
- }
46
- }
57
+ if (rewriteDepsSection(deps, pkgPath, versionMap, packageManager)) {
58
+ modified = true;
47
59
  }
48
60
  }
49
61
  if (modified) {
@@ -51,8 +63,7 @@ function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
51
63
  }
52
64
  return () => writeFileSync(pkgJsonPath, original);
53
65
  }
54
- function topoSort(packages, packageManager) {
55
- const nameSet = new Set(packages.map((p) => p.name));
66
+ function buildIntraDeps(packages, nameSet) {
56
67
  const deps = /* @__PURE__ */ new Map();
57
68
  for (const pkg of packages) {
58
69
  const intra = /* @__PURE__ */ new Set();
@@ -69,12 +80,9 @@ function topoSort(packages, packageManager) {
69
80
  }
70
81
  deps.set(pkg.name, intra);
71
82
  }
72
- const inDegree = new Map(packages.map((p) => [p.name, 0]));
73
- for (const [, d] of deps) {
74
- for (const dep of d) {
75
- inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
76
- }
77
- }
83
+ return deps;
84
+ }
85
+ function buildDependantsMap(packages, deps) {
78
86
  const dependants = /* @__PURE__ */ new Map();
79
87
  for (const pkg of packages) {
80
88
  for (const dep of deps.get(pkg.name) ?? []) {
@@ -84,10 +92,13 @@ function topoSort(packages, packageManager) {
84
92
  dependants.get(dep).add(pkg.name);
85
93
  }
86
94
  }
95
+ return dependants;
96
+ }
97
+ function computeWaves(packages, deps, dependants) {
98
+ const byName = new Map(packages.map((p) => [p.name, p]));
87
99
  const remaining = new Map(
88
100
  packages.map((p) => [p.name, (deps.get(p.name) ?? /* @__PURE__ */ new Set()).size])
89
101
  );
90
- const byName = new Map(packages.map((p) => [p.name, p]));
91
102
  const waves = [];
92
103
  while (remaining.size > 0) {
93
104
  const wave = [...remaining.entries()].filter(([, count]) => count === 0).map(([name]) => byName.get(name));
@@ -107,6 +118,12 @@ function topoSort(packages, packageManager) {
107
118
  }
108
119
  return waves.filter((w) => w.length > 0);
109
120
  }
121
+ function topoSort(packages, _packageManager) {
122
+ const nameSet = new Set(packages.map((p) => p.name));
123
+ const deps = buildIntraDeps(packages, nameSet);
124
+ const dependants = buildDependantsMap(packages, deps);
125
+ return computeWaves(packages, deps, dependants);
126
+ }
110
127
 
111
128
  // src/shared/publish-programmatic.ts
112
129
  var MAX_RETRIES = 5;
@@ -248,7 +265,9 @@ async function publishOne(pkg, opts, logger) {
248
265
  logger.warn(
249
266
  `Transient error for ${pkg.name}@${pkg.version} (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${(delay / 1e3).toFixed(1)}s: ${message.split("\n")[0]}`
250
267
  );
251
- await new Promise((r) => setTimeout(r, delay));
268
+ await new Promise((r) => {
269
+ setTimeout(r, delay);
270
+ });
252
271
  continue;
253
272
  }
254
273
  logger.error(`Failed to publish ${pkg.name}@${pkg.version}`, void 0, { error: message });
@@ -265,6 +284,20 @@ async function publishOne(pkg, opts, logger) {
265
284
  restore();
266
285
  }
267
286
  }
287
+ function skipRemainingWaves(waves, waveIdx, waveFailed, logger, allResults) {
288
+ const remaining = waves.slice(waveIdx + 1).reduce((acc, w) => acc + w.length, 0);
289
+ if (remaining === 0) {
290
+ return;
291
+ }
292
+ logger.warn(
293
+ `Wave ${waveIdx + 1} had ${waveFailed.length} failure(s) \u2014 skipping ${remaining} package(s) in later waves to avoid broken dependency chain`
294
+ );
295
+ for (const laterWave of waves.slice(waveIdx + 1)) {
296
+ for (const pkg of laterWave) {
297
+ allResults.push({ name: pkg.name, version: pkg.version, success: false, error: "Skipped: dependency wave failed" });
298
+ }
299
+ }
300
+ }
268
301
  async function publishPackagesProgrammatic(options) {
269
302
  const {
270
303
  packages,
@@ -310,22 +343,7 @@ async function publishPackagesProgrammatic(options) {
310
343
  }
311
344
  const waveFailed = allResults.slice(-wave.length).filter((r) => !r.success);
312
345
  if (waveFailed.length > 0) {
313
- const remaining = waves.slice(waveIdx + 1).reduce((acc, w) => acc + w.length, 0);
314
- if (remaining > 0) {
315
- logger.warn(
316
- `Wave ${waveIdx + 1} had ${waveFailed.length} failure(s) \u2014 skipping ${remaining} package(s) in later waves to avoid broken dependency chain`
317
- );
318
- for (const laterWave of waves.slice(waveIdx + 1)) {
319
- for (const pkg of laterWave) {
320
- allResults.push({
321
- name: pkg.name,
322
- version: pkg.version,
323
- success: false,
324
- error: "Skipped: dependency wave failed"
325
- });
326
- }
327
- }
328
- }
346
+ skipRemainingWaves(waves, waveIdx, waveFailed, logger, allResults);
329
347
  break;
330
348
  }
331
349
  }
@@ -417,65 +435,125 @@ function publishSinglePackage(options) {
417
435
  child.on("error", (err) => reject(err));
418
436
  });
419
437
  }
420
- async function publishPackagesWithOTP(options) {
421
- const { packages, packageManager = "pnpm", dryRun, tag, access, ui, logger } = options;
422
- let otp = options.otp;
423
- const results = [];
424
- const versionMap = new Map(packages.map((p) => [p.name, p.version]));
425
- const CONCURRENCY = Number(useEnv("KB_PUBLISH_CONCURRENCY") ?? 4);
426
- const remaining = [];
427
- let otpFailureDetected = false;
428
- const fastPublishOne = async (pkg) => {
429
- const restore = rewriteWorkspaceDeps(pkg.path, versionMap, packageManager);
430
- try {
431
- for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
432
- try {
433
- await publishSinglePackage({ packagePath: pkg.path, packageManager, otp, dryRun, tag, access });
434
- logger?.info("Published (fast-path)", { name: pkg.name, version: pkg.version });
435
- return { name: pkg.name, version: pkg.version, success: true };
436
- } catch (err) {
437
- const msg = err instanceof Error ? err.message : String(err);
438
- if (isAlreadyPublished2(msg)) {
439
- logger?.info(`${pkg.name}@${pkg.version} already published \u2014 skipping`);
440
- return { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true };
441
- }
442
- if (msg.includes("EOTP") || msg.includes("one-time password")) {
443
- otpFailureDetected = true;
444
- return null;
445
- }
446
- if (isRetryable2(msg) && attempt < MAX_RETRIES2) {
447
- const delay = retryDelay2(attempt);
448
- logger?.warn(
449
- `Transient error for ${pkg.name} (attempt ${attempt + 1}/${MAX_RETRIES2}), retrying in ${(delay / 1e3).toFixed(1)}s`
450
- );
451
- await new Promise((r) => setTimeout(r, delay));
452
- continue;
438
+ async function fastPublishPkg(pkg, ctx, logger) {
439
+ const restore = rewriteWorkspaceDeps(pkg.path, ctx.versionMap, ctx.packageManager);
440
+ try {
441
+ for (let attempt = 0; attempt <= MAX_RETRIES2; attempt++) {
442
+ try {
443
+ await publishSinglePackage({ packagePath: pkg.path, packageManager: ctx.packageManager, otp: ctx.otp, dryRun: ctx.dryRun, tag: ctx.tag, access: ctx.access });
444
+ logger?.info("Published (fast-path)", { name: pkg.name, version: pkg.version });
445
+ return { result: { name: pkg.name, version: pkg.version, success: true }, otpFailed: false };
446
+ } catch (err) {
447
+ const msg = err instanceof Error ? err.message : String(err);
448
+ if (isAlreadyPublished2(msg)) {
449
+ logger?.info(`${pkg.name}@${pkg.version} already published \u2014 skipping`);
450
+ return { result: { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true }, otpFailed: false };
451
+ }
452
+ if (msg.includes("EOTP") || msg.includes("one-time password")) {
453
+ return { result: null, otpFailed: true };
454
+ }
455
+ if (isRetryable2(msg) && attempt < MAX_RETRIES2) {
456
+ const delay = retryDelay2(attempt);
457
+ logger?.warn(`Transient error for ${pkg.name} (attempt ${attempt + 1}/${MAX_RETRIES2}), retrying in ${(delay / 1e3).toFixed(1)}s`);
458
+ await new Promise((r) => {
459
+ setTimeout(r, delay);
460
+ });
461
+ continue;
462
+ }
463
+ logger?.error("Publish failed (fast-path)", void 0, { name: pkg.name, error: msg });
464
+ return { result: { name: pkg.name, version: pkg.version, success: false, error: msg }, otpFailed: false };
465
+ }
466
+ }
467
+ return { result: { name: pkg.name, version: pkg.version, success: false, error: `Publish failed after ${MAX_RETRIES2} retries (transient errors)` }, otpFailed: false };
468
+ } finally {
469
+ restore();
470
+ }
471
+ }
472
+ async function promptForOtp(ui, loader, pkgName) {
473
+ loader.succeed(`\u{1F510} 2FA required for ${pkgName}`);
474
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
475
+ try {
476
+ const entered = await rl.question(" Enter OTP code: ");
477
+ rl.close();
478
+ if (!entered || entered.trim().length !== 6) {
479
+ ui.write?.(" \u26A0\uFE0F Invalid OTP (must be 6 digits)\n");
480
+ return void 0;
481
+ }
482
+ return entered.trim();
483
+ } catch (e) {
484
+ rl.close();
485
+ throw e;
486
+ }
487
+ }
488
+ async function sequentialPublishPkg(pkg, ctx, otpState, ui, logger) {
489
+ const loader = useLoader(`Publishing ${pkg.name}@${pkg.version}...`);
490
+ loader.start();
491
+ const restore = rewriteWorkspaceDeps(pkg.path, ctx.versionMap, ctx.packageManager);
492
+ try {
493
+ const MAX_OTP_ATTEMPTS = 3;
494
+ for (let otpAttempts = 0; otpAttempts < MAX_OTP_ATTEMPTS; otpAttempts++) {
495
+ try {
496
+ await publishSinglePackage({ packagePath: pkg.path, packageManager: ctx.packageManager, otp: otpState.otp, dryRun: ctx.dryRun, tag: ctx.tag, access: ctx.access });
497
+ loader.succeed(ctx.dryRun ? `Dry-run: ${pkg.name}@${pkg.version}` : `Published ${pkg.name}@${pkg.version}`);
498
+ return { name: pkg.name, version: pkg.version, success: true };
499
+ } catch (err) {
500
+ const msg = err instanceof Error ? err.message : String(err);
501
+ if (isAlreadyPublished2(msg)) {
502
+ loader.succeed(`${pkg.name}@${pkg.version} already published`);
503
+ return { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true };
504
+ }
505
+ if (msg.includes("EOTP") || msg.includes("one-time password")) {
506
+ if (otpAttempts + 1 < MAX_OTP_ATTEMPTS) {
507
+ otpState.otp = await promptForOtp(ui, loader, pkg.name);
508
+ loader.update({ text: `Publishing ${pkg.name}@${pkg.version} with OTP...` });
509
+ loader.start();
510
+ } else {
511
+ loader.fail("Max OTP attempts reached");
512
+ return { name: pkg.name, version: pkg.version, success: false, error: "Max OTP attempts reached" };
453
513
  }
454
- logger?.error("Publish failed (fast-path)", { name: pkg.name, error: msg });
514
+ } else if (isRetryable2(msg)) {
515
+ const delay = retryDelay2(MAX_RETRIES2 - 1);
516
+ loader.update({ text: `Rate limited \u2014 waiting ${(delay / 1e3).toFixed(0)}s\u2026` });
517
+ loader.start();
518
+ await new Promise((r) => {
519
+ setTimeout(r, delay);
520
+ });
521
+ otpAttempts--;
522
+ } else {
523
+ loader.fail(`Failed: ${msg.split("\n")[0]}`);
455
524
  return { name: pkg.name, version: pkg.version, success: false, error: msg };
456
525
  }
457
526
  }
458
- return {
459
- name: pkg.name,
460
- version: pkg.version,
461
- success: false,
462
- error: `Publish failed after ${MAX_RETRIES2} retries (transient errors)`
463
- };
464
- } finally {
465
- restore();
466
527
  }
467
- };
528
+ return { name: pkg.name, version: pkg.version, success: false, error: "Publish failed after max OTP attempts" };
529
+ } finally {
530
+ restore();
531
+ }
532
+ }
533
+ async function publishPackagesWithOTP(options) {
534
+ const { packages, packageManager = "pnpm", dryRun, tag, access, ui, logger } = options;
535
+ const otpState = { otp: options.otp };
536
+ const versionMap = new Map(packages.map((p) => [p.name, p.version]));
537
+ const fastCtx = { packageManager, versionMap, otp: otpState.otp, dryRun, tag, access };
538
+ const seqCtx = { packageManager, versionMap, dryRun, tag, access };
539
+ const results = [];
540
+ const remaining = [];
541
+ const CONCURRENCY = Number(useEnv("KB_PUBLISH_CONCURRENCY") ?? 4);
542
+ let otpFailureDetected = false;
468
543
  ui.write?.(` Publishing ${packages.length} package(s) in parallel (concurrency=${CONCURRENCY})...
469
544
  `);
470
545
  for (let i = 0; i < packages.length; i += CONCURRENCY) {
471
546
  const batch = packages.slice(i, i + CONCURRENCY);
472
- const batchResults = await Promise.all(batch.map(fastPublishOne));
547
+ const outcomes = await Promise.all(batch.map((pkg) => fastPublishPkg(pkg, fastCtx, logger)));
473
548
  for (let j = 0; j < batch.length; j++) {
474
- const res = batchResults[j];
475
- if (res === null || res === void 0) {
549
+ const outcome = outcomes[j];
550
+ if (outcome.result === null) {
476
551
  remaining.push(batch[j]);
477
552
  } else {
478
- results.push(res);
553
+ results.push(outcome.result);
554
+ }
555
+ if (outcome.otpFailed) {
556
+ otpFailureDetected = true;
479
557
  }
480
558
  }
481
559
  if (otpFailureDetected) {
@@ -490,68 +568,7 @@ async function publishPackagesWithOTP(options) {
490
568
  `);
491
569
  for (const pkg of remaining) {
492
570
  logger?.info("Publishing package (sequential)", { name: pkg.name, version: pkg.version });
493
- const loader = useLoader(`Publishing ${pkg.name}@${pkg.version}...`);
494
- loader.start();
495
- const restore = rewriteWorkspaceDeps(pkg.path, versionMap, packageManager);
496
- try {
497
- let published = false;
498
- let otpAttempts = 0;
499
- const MAX_OTP_ATTEMPTS = 3;
500
- while (!published && otpAttempts < MAX_OTP_ATTEMPTS) {
501
- otpAttempts++;
502
- try {
503
- await publishSinglePackage({ packagePath: pkg.path, packageManager, otp, dryRun, tag, access });
504
- published = true;
505
- results.push({ name: pkg.name, version: pkg.version, success: true });
506
- loader.succeed(dryRun ? `Dry-run: ${pkg.name}@${pkg.version}` : `Published ${pkg.name}@${pkg.version}`);
507
- } catch (err) {
508
- const msg = err instanceof Error ? err.message : String(err);
509
- if (isAlreadyPublished2(msg)) {
510
- published = true;
511
- results.push({ name: pkg.name, version: pkg.version, success: true, alreadyPublished: true });
512
- loader.succeed(`${pkg.name}@${pkg.version} already published`);
513
- break;
514
- }
515
- if (msg.includes("EOTP") || msg.includes("one-time password")) {
516
- if (otpAttempts < MAX_OTP_ATTEMPTS) {
517
- loader.succeed(`\u{1F510} 2FA required for ${pkg.name}`);
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
- otp = void 0;
525
- } else {
526
- otp = entered.trim();
527
- }
528
- } catch (e) {
529
- rl.close();
530
- throw e;
531
- }
532
- loader.update({ text: `Publishing ${pkg.name}@${pkg.version} with OTP...` });
533
- loader.start();
534
- } else {
535
- loader.fail("Max OTP attempts reached");
536
- results.push({ name: pkg.name, version: pkg.version, success: false, error: "Max OTP attempts reached" });
537
- break;
538
- }
539
- } else if (isRetryable2(msg)) {
540
- const delay = retryDelay2(MAX_RETRIES2 - 1);
541
- loader.update({ text: `Rate limited \u2014 waiting ${(delay / 1e3).toFixed(0)}s\u2026` });
542
- loader.start();
543
- await new Promise((r) => setTimeout(r, delay));
544
- otpAttempts--;
545
- } else {
546
- loader.fail(`Failed: ${msg.split("\n")[0]}`);
547
- results.push({ name: pkg.name, version: pkg.version, success: false, error: msg });
548
- break;
549
- }
550
- }
551
- }
552
- } finally {
553
- restore();
554
- }
571
+ results.push(await sequentialPublishPkg(pkg, seqCtx, otpState, ui));
555
572
  }
556
573
  return buildResult(results, packages, dryRun);
557
574
  }
@@ -567,6 +584,40 @@ function buildResult(results, packages, dryRun) {
567
584
  }
568
585
 
569
586
  // src/cli/commands/publish.ts
587
+ function buildPublishSections(results, symbols) {
588
+ const sections = [];
589
+ const successful = results.filter((r) => r.success);
590
+ if (successful.length > 0) {
591
+ const items = [];
592
+ for (const r of successful) {
593
+ items.push(`${symbols.success} ${r.name}@${r.version}`);
594
+ items.push(` \u2514\u2500 https://www.npmjs.com/package/${r.name}`);
595
+ }
596
+ sections.push({ header: "Published", items });
597
+ }
598
+ const failed = results.filter((r) => !r.success);
599
+ if (failed.length > 0) {
600
+ sections.push({
601
+ header: "Failed",
602
+ items: failed.map((r) => `${symbols.error} ${r.name}@${r.version} - ${r.error}`)
603
+ });
604
+ }
605
+ return sections;
606
+ }
607
+ function toPublishResult(results) {
608
+ const successful = results.filter((r) => r.success).length;
609
+ const failed = results.filter((r) => !r.success).length;
610
+ return {
611
+ exitCode: failed === 0 ? 0 : 1,
612
+ published: results.filter((r) => r.success).map((r) => ({ name: r.name, version: r.version })),
613
+ failed: results.filter((r) => !r.success).map((r) => ({
614
+ name: r.name,
615
+ version: r.version,
616
+ error: r.error || "Unknown error"
617
+ })),
618
+ summary: { total: results.length, successful, failed }
619
+ };
620
+ }
570
621
  var publish_default = defineCommand({
571
622
  id: "release:publish",
572
623
  description: "Publish packages to npm registry",
@@ -598,9 +649,9 @@ var publish_default = defineCommand({
598
649
  }
599
650
  return { exitCode: 1, summary: { total: 0, successful: 0, failed: 0 } };
600
651
  }
601
- let result;
652
+ let rawResult;
602
653
  if (token) {
603
- result = await publishPackagesProgrammatic({
654
+ rawResult = await publishPackagesProgrammatic({
604
655
  packages,
605
656
  dryRun,
606
657
  otp: initialOtp,
@@ -609,7 +660,7 @@ var publish_default = defineCommand({
609
660
  token
610
661
  });
611
662
  } else {
612
- result = await publishPackagesWithOTP({
663
+ rawResult = await publishPackagesWithOTP({
613
664
  packages,
614
665
  dryRun,
615
666
  otp: initialOtp,
@@ -619,39 +670,19 @@ var publish_default = defineCommand({
619
670
  logger: ctx.platform?.logger
620
671
  });
621
672
  }
622
- const successful = result.results.filter((r) => r.success).length;
623
- const failed = result.results.filter((r) => !r.success).length;
624
- const publishResult = {
625
- exitCode: failed === 0 ? 0 : 1,
626
- published: result.results.filter((r) => r.success).map((r) => ({ name: r.name, version: r.version })),
627
- failed: result.results.filter((r) => !r.success).map((r) => ({
628
- name: r.name,
629
- version: r.version,
630
- error: r.error || "Unknown error"
631
- })),
632
- summary: { total: result.results.length, successful, failed }
633
- };
673
+ const publishResult = toPublishResult(rawResult.results);
674
+ console.log("::kb-output::" + JSON.stringify({
675
+ published: publishResult.published?.map((p) => `${p.name}@${p.version}`) ?? [],
676
+ failed: publishResult.failed?.map((p) => p.name) ?? []
677
+ }));
634
678
  if (json) {
635
679
  ctx.ui?.json?.(publishResult);
636
680
  return publishResult;
637
681
  }
638
- const sections = [];
639
- if (successful > 0) {
640
- const successItems = [];
641
- for (const r of result.results.filter((r2) => r2.success)) {
642
- successItems.push(`${ctx.ui.symbols.success} ${r.name}@${r.version}`);
643
- successItems.push(` \u2514\u2500 https://www.npmjs.com/package/${r.name}`);
644
- }
645
- sections.push({ header: "Published", items: successItems });
646
- }
647
- if (failed > 0) {
648
- const failItems = result.results.filter((r) => !r.success).map((r) => `${ctx.ui.symbols.error} ${r.name}@${r.version} - ${r.error}`);
649
- sections.push({ header: "Failed", items: failItems });
650
- }
651
682
  ctx.ui.sideBox({
652
683
  title: dryRun ? "Publish Dry-Run" : "Publish Summary",
653
- sections,
654
- status: failed === 0 ? "success" : "error"
684
+ sections: buildPublishSections(rawResult.results, ctx.ui.symbols),
685
+ status: publishResult.exitCode === 0 ? "success" : "error"
655
686
  });
656
687
  return publishResult;
657
688
  }