@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.
- 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 +386 -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
|
@@ -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
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
475
|
-
if (
|
|
549
|
+
const outcome = outcomes[j];
|
|
550
|
+
if (outcome.result === null) {
|
|
476
551
|
remaining.push(batch[j]);
|
|
477
552
|
} else {
|
|
478
|
-
results.push(
|
|
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
|
-
|
|
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
|
|
652
|
+
let rawResult;
|
|
602
653
|
if (token) {
|
|
603
|
-
|
|
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
|
-
|
|
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
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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:
|
|
684
|
+
sections: buildPublishSections(rawResult.results, ctx.ui.symbols),
|
|
685
|
+
status: publishResult.exitCode === 0 ? "success" : "error"
|
|
655
686
|
});
|
|
656
687
|
return publishResult;
|
|
657
688
|
}
|