@kb-labs/release-manager-cli 2.82.0 → 2.88.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.
- package/dist/cli/commands/build.d.ts +10 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/changelog.js +65 -57
- package/dist/cli/commands/changelog.js.map +1 -1
- package/dist/cli/commands/checks.d.ts +10 -0
- package/dist/cli/commands/checks.js +93 -0
- package/dist/cli/commands/checks.js.map +1 -0
- package/dist/cli/commands/git.d.ts +13 -0
- package/dist/cli/commands/git.js +93 -0
- package/dist/cli/commands/git.js.map +1 -0
- package/dist/cli/commands/pack.d.ts +10 -0
- package/dist/cli/commands/pack.js +83 -0
- package/dist/cli/commands/pack.js.map +1 -0
- package/dist/cli/commands/plan.js +40 -41
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/publish.js +225 -194
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/report.js +28 -31
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/rollback.js +0 -3
- package/dist/cli/commands/rollback.js.map +1 -1
- package/dist/cli/commands/run.js +387 -300
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/verify.js +29 -28
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/commands/version.d.ts +12 -0
- package/dist/cli/commands/version.js +111 -0
- package/dist/cli/commands/version.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +146 -13
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +9 -0
- package/dist/manifest.js +131 -0
- package/dist/manifest.js.map +1 -1
- package/dist/rest/handlers/build-handler.js +22 -20
- package/dist/rest/handlers/build-handler.js.map +1 -1
- package/dist/rest/handlers/changelog-generate-handler.js +75 -88
- package/dist/rest/handlers/changelog-generate-handler.js.map +1 -1
- package/dist/rest/handlers/changelog-handler.js +1 -4
- package/dist/rest/handlers/changelog-handler.js.map +1 -1
- package/dist/rest/handlers/changelog-save-handler.js +0 -2
- package/dist/rest/handlers/changelog-save-handler.js.map +1 -1
- package/dist/rest/handlers/checklist-handler.js +113 -99
- package/dist/rest/handlers/checklist-handler.js.map +1 -1
- package/dist/rest/handlers/generate-handler.js +0 -3
- package/dist/rest/handlers/generate-handler.js.map +1 -1
- package/dist/rest/handlers/git-timeline-handler.js.map +1 -1
- package/dist/rest/handlers/history-changelog-handler.js +0 -1
- package/dist/rest/handlers/history-changelog-handler.js.map +1 -1
- package/dist/rest/handlers/history-handler.js +2 -3
- package/dist/rest/handlers/history-handler.js.map +1 -1
- package/dist/rest/handlers/history-plan-handler.js +0 -1
- package/dist/rest/handlers/history-plan-handler.js.map +1 -1
- package/dist/rest/handlers/history-report-handler.js +0 -1
- package/dist/rest/handlers/history-report-handler.js.map +1 -1
- package/dist/rest/handlers/plan-handler.js +0 -3
- package/dist/rest/handlers/plan-handler.js.map +1 -1
- package/dist/rest/handlers/preview-handler.js +0 -1
- package/dist/rest/handlers/preview-handler.js.map +1 -1
- package/dist/rest/handlers/report-handler.js +1 -1
- package/dist/rest/handlers/report-handler.js.map +1 -1
- package/dist/rest/handlers/reset-handler.js +0 -1
- package/dist/rest/handlers/reset-handler.js.map +1 -1
- package/dist/rest/handlers/run-checks-handler.js +0 -1
- package/dist/rest/handlers/run-checks-handler.js.map +1 -1
- package/dist/rest/handlers/run-handler.js +72 -52
- package/dist/rest/handlers/run-handler.js.map +1 -1
- package/dist/rest/handlers/status-handler.js +0 -2
- package/dist/rest/handlers/status-handler.js.map +1 -1
- package/dist/setup/handler.js.map +1 -1
- package/package.json +9 -8
package/dist/cli/commands/run.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import * as readline from 'readline/promises';
|
|
2
|
-
import { defineCommand, findRepoRoot, useLoader, useConfig,
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
return {
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
519
|
-
if (
|
|
593
|
+
const outcome = outcomes[j];
|
|
594
|
+
if (outcome.result === null) {
|
|
520
595
|
remaining.push(batch[j]);
|
|
521
596
|
} else {
|
|
522
|
-
results.push(
|
|
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
|
-
|
|
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,174 @@ 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
|
+
const r = await publishPackagesProgrammatic({
|
|
650
|
+
packages,
|
|
651
|
+
packageManager,
|
|
652
|
+
dryRun: opts.dryRun,
|
|
653
|
+
access: opts.access ?? "public",
|
|
654
|
+
registry: config.registry,
|
|
655
|
+
token
|
|
656
|
+
});
|
|
657
|
+
return { published: r.published, alreadyPublished: r.alreadyPublished, failed: r.failed, skipped: r.skipped, errors: r.errors };
|
|
658
|
+
}
|
|
659
|
+
return publishPackagesWithOTP({
|
|
660
|
+
packages,
|
|
661
|
+
packageManager,
|
|
662
|
+
dryRun: opts.dryRun,
|
|
663
|
+
access: opts.access ?? "public",
|
|
664
|
+
ui: ctx.ui,
|
|
665
|
+
logger: ctx.platform?.logger
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function appendCheckDetails(c, checkItems) {
|
|
671
|
+
if (c.details?.packagePath) {
|
|
672
|
+
checkItems.push(` package: ${c.details.packagePath}`);
|
|
673
|
+
}
|
|
674
|
+
if (c.details?.error) {
|
|
675
|
+
checkItems.push(` reason: ${c.details.error}`);
|
|
676
|
+
}
|
|
677
|
+
const output = (c.details?.stderr || c.details?.stdout || "").trim();
|
|
678
|
+
if (output) {
|
|
679
|
+
checkItems.push(` output:`);
|
|
680
|
+
for (const line of output.split("\n").slice(0, 8)) {
|
|
681
|
+
checkItems.push(` ${line}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const failedPkgs = (c.packages ?? []).filter((p) => !p.ok);
|
|
685
|
+
if (failedPkgs.length) {
|
|
686
|
+
checkItems.push(` failed in ${failedPkgs.length}/${c.packages.length} package(s):`);
|
|
687
|
+
for (const pkg of failedPkgs.slice(0, 10)) {
|
|
688
|
+
checkItems.push(` - ${pkg.path}${pkg.details?.error ? `: ${pkg.details.error}` : ""}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function buildCheckItems(checkEntries, symbols) {
|
|
693
|
+
const checkItems = [];
|
|
694
|
+
for (const c of checkEntries) {
|
|
695
|
+
const sym = c.ok ? symbols.success : c.hint === "optional" ? symbols.warning : symbols.error;
|
|
696
|
+
const timing = c.timingMs ? ` (${(c.timingMs / 1e3).toFixed(1)}s)` : "";
|
|
697
|
+
checkItems.push(`${sym} ${c.id}${timing}`);
|
|
698
|
+
if (!c.ok && c.hint !== "optional") {
|
|
699
|
+
appendCheckDetails(c, checkItems);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return checkItems;
|
|
703
|
+
}
|
|
704
|
+
function buildReleaseSections(report, dryRun, ctx) {
|
|
705
|
+
const sections = [];
|
|
706
|
+
if (!dryRun && report.result.published?.length) {
|
|
707
|
+
sections.push({
|
|
708
|
+
header: "Published",
|
|
709
|
+
items: report.result.published.map((p) => `${ctx.ui.symbols.success} ${p}`)
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
if (!dryRun && report.result.alreadyPublished?.length) {
|
|
713
|
+
sections.push({
|
|
714
|
+
header: "Already published (skipped)",
|
|
715
|
+
items: report.result.alreadyPublished.map((p) => `${ctx.ui.symbols.info} ${p}`)
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
if (dryRun && report.result.skipped?.length) {
|
|
719
|
+
sections.push({
|
|
720
|
+
header: "Would publish (dry-run)",
|
|
721
|
+
items: report.result.skipped.map((p) => `${ctx.ui.symbols.info} ${p.replace(" (dry-run)", "")}`)
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (report.result.checks) {
|
|
725
|
+
const checkEntries = Object.values(report.result.checks).filter(Boolean);
|
|
726
|
+
const passed = checkEntries.filter((c) => c.ok);
|
|
727
|
+
const failed = checkEntries.filter((c) => !c.ok && c.hint !== "optional");
|
|
728
|
+
const skipped = checkEntries.filter((c) => !c.ok && c.hint === "optional");
|
|
729
|
+
if (checkEntries.length > 0) {
|
|
730
|
+
sections.push({
|
|
731
|
+
header: `Checks \u2014 ${passed.length} passed, ${failed.length} failed${skipped.length ? `, ${skipped.length} skipped` : ""}`,
|
|
732
|
+
items: buildCheckItems(checkEntries, ctx.ui.symbols)
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (report.result.errors?.length && !report.result.checks) {
|
|
737
|
+
sections.push({
|
|
738
|
+
header: "Errors",
|
|
739
|
+
items: report.result.errors.map((e) => `${ctx.ui.symbols.error} ${e}`)
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
if (report.result.git) {
|
|
743
|
+
const g = report.result.git;
|
|
744
|
+
sections.push({
|
|
745
|
+
header: "Git",
|
|
746
|
+
items: [
|
|
747
|
+
`Committed: ${g.committed}`,
|
|
748
|
+
`Tags: ${g.tagged?.join(", ") || "none"}`,
|
|
749
|
+
`Pushed: ${g.pushed}`
|
|
750
|
+
]
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return sections;
|
|
754
|
+
}
|
|
755
|
+
function resolveChecks(flags, config) {
|
|
756
|
+
if (flags.flow) {
|
|
757
|
+
return config.flows?.[flags.flow]?.checks ?? config.checks ?? [];
|
|
758
|
+
}
|
|
759
|
+
if (flags.scope) {
|
|
760
|
+
return config.scopes?.[flags.scope]?.checks ?? config.checks ?? [];
|
|
761
|
+
}
|
|
762
|
+
return config.checks ?? [];
|
|
763
|
+
}
|
|
764
|
+
async function confirmRelease() {
|
|
765
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
766
|
+
let answer;
|
|
767
|
+
try {
|
|
768
|
+
answer = await rl.question("\nProceed with release? [y/N] ");
|
|
769
|
+
} finally {
|
|
770
|
+
rl.close();
|
|
771
|
+
}
|
|
772
|
+
return answer.trim().toLowerCase() === "y";
|
|
773
|
+
}
|
|
774
|
+
function showPlanBox(ctx, flags, plan, config, dryRun) {
|
|
775
|
+
if (flags.json) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
ctx.ui.sideBox({
|
|
779
|
+
title: dryRun ? "Release Plan (dry-run)" : "Release Plan",
|
|
780
|
+
sections: [
|
|
781
|
+
{
|
|
782
|
+
header: `${plan.packages.length} package(s) \xB7 strategy: ${config.versioningStrategy ?? "independent"}${flags.flow ? ` \xB7 flow: ${flags.flow}` : ""}`,
|
|
783
|
+
items: buildPlanRows(plan.packages)
|
|
784
|
+
}
|
|
785
|
+
],
|
|
786
|
+
status: "info"
|
|
787
|
+
});
|
|
788
|
+
if (flags["no-verify"]) {
|
|
789
|
+
ctx.ui.write?.(` \u26A0\uFE0F --no-verify: git pre-push hooks will be skipped
|
|
790
|
+
`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function reportPipelineResult(ctx, flags, result, dryRun) {
|
|
794
|
+
if (flags.json) {
|
|
795
|
+
ctx.ui?.json?.(result.report);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
ctx.ui.sideBox({
|
|
799
|
+
title: "Release",
|
|
800
|
+
sections: buildReleaseSections(result.report, dryRun, ctx),
|
|
801
|
+
status: result.success ? "success" : "error",
|
|
802
|
+
timing: result.report.result.timingMs
|
|
803
|
+
});
|
|
804
|
+
}
|
|
620
805
|
var run_default = defineCommand({
|
|
621
806
|
id: "release:run",
|
|
622
807
|
description: "Execute release process (plan, check, publish)",
|
|
@@ -636,6 +821,37 @@ var run_default = defineCommand({
|
|
|
636
821
|
...flags.strict !== void 0 && { strict: flags.strict }
|
|
637
822
|
};
|
|
638
823
|
configLoader.succeed("Configuration loaded");
|
|
824
|
+
const token = useEnv("NPM_TOKEN") ?? useEnv("NODE_AUTH_TOKEN");
|
|
825
|
+
if (!dryRun) {
|
|
826
|
+
const preErrors = [];
|
|
827
|
+
const registry = config.registry ?? "https://registry.npmjs.org";
|
|
828
|
+
if (!token) {
|
|
829
|
+
preErrors.push("NPM_TOKEN or NODE_AUTH_TOKEN is not set");
|
|
830
|
+
} else {
|
|
831
|
+
try {
|
|
832
|
+
const res = await fetch(`${registry}/-/whoami`, {
|
|
833
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
834
|
+
signal: AbortSignal.timeout(8e3)
|
|
835
|
+
});
|
|
836
|
+
if (!res.ok) {
|
|
837
|
+
preErrors.push(`npm token invalid or expired (HTTP ${res.status} from ${registry})`);
|
|
838
|
+
}
|
|
839
|
+
} catch (e) {
|
|
840
|
+
preErrors.push(`npm registry unreachable: ${e instanceof Error ? e.message : String(e)}`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (flags.flow && config.flows && !(flags.flow in config.flows)) {
|
|
844
|
+
preErrors.push(`Flow "${flags.flow}" is not defined in release.flows config`);
|
|
845
|
+
}
|
|
846
|
+
if (preErrors.length > 0) {
|
|
847
|
+
ctx.ui.sideBox({
|
|
848
|
+
title: "Release \u2014 pre-flight failed",
|
|
849
|
+
sections: [{ items: preErrors.map((e) => `${ctx.ui.symbols.error} ${e}`) }],
|
|
850
|
+
status: "error"
|
|
851
|
+
});
|
|
852
|
+
return { exitCode: 1 };
|
|
853
|
+
}
|
|
854
|
+
}
|
|
639
855
|
const scopeCwd = await resolveScopePath(repoRoot, flags.scope || "root");
|
|
640
856
|
const planLoader = useLoader("Discovering packages...");
|
|
641
857
|
planLoader.start();
|
|
@@ -655,36 +871,10 @@ var run_default = defineCommand({
|
|
|
655
871
|
});
|
|
656
872
|
return { exitCode: 0 };
|
|
657
873
|
}
|
|
658
|
-
|
|
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
|
-
}
|
|
874
|
+
showPlanBox(ctx, flags, plan, config, dryRun);
|
|
679
875
|
if (!skipYes) {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
try {
|
|
683
|
-
answer = await rl.question("\nProceed with release? [y/N] ");
|
|
684
|
-
} finally {
|
|
685
|
-
rl.close();
|
|
686
|
-
}
|
|
687
|
-
if (answer.trim().toLowerCase() !== "y") {
|
|
876
|
+
const confirmed = await confirmRelease();
|
|
877
|
+
if (!confirmed) {
|
|
688
878
|
ctx.ui.sideBox({
|
|
689
879
|
title: "Release",
|
|
690
880
|
sections: [{ items: [`${ctx.ui.symbols.info} Cancelled`] }],
|
|
@@ -695,23 +885,7 @@ var run_default = defineCommand({
|
|
|
695
885
|
}
|
|
696
886
|
const llm = useLLM();
|
|
697
887
|
const changelog = createChangelogGenerator(config, llm ?? void 0);
|
|
698
|
-
const
|
|
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
|
-
};
|
|
888
|
+
const publisher = createPublisher(config, token, ctx);
|
|
715
889
|
const pipelineLoader = useLoader("Running release pipeline...");
|
|
716
890
|
pipelineLoader.start();
|
|
717
891
|
const pipelineStart = Date.now();
|
|
@@ -727,7 +901,7 @@ var run_default = defineCommand({
|
|
|
727
901
|
skipBuild: flags["skip-build"],
|
|
728
902
|
skipVerify: flags["skip-verify"],
|
|
729
903
|
noVerify: flags["no-verify"],
|
|
730
|
-
checks: (flags
|
|
904
|
+
checks: resolveChecks(flags, config),
|
|
731
905
|
publisher,
|
|
732
906
|
changelog,
|
|
733
907
|
logger: ctx.platform?.logger,
|
|
@@ -737,94 +911,7 @@ var run_default = defineCommand({
|
|
|
737
911
|
}
|
|
738
912
|
});
|
|
739
913
|
pipelineLoader.succeed(result.success ? "Release completed" : "Release failed");
|
|
740
|
-
|
|
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
|
-
}
|
|
914
|
+
reportPipelineResult(ctx, flags, result, dryRun);
|
|
828
915
|
return { exitCode: result.success ? 0 : 1, report: result.report };
|
|
829
916
|
}
|
|
830
917
|
}
|