@kb-labs/release-manager-cli 2.104.0 → 2.106.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/deliver.d.ts +18 -0
- package/dist/cli/commands/deliver.js +597 -0
- package/dist/cli/commands/deliver.js.map +1 -0
- package/dist/cli/commands/promote.d.ts +2 -0
- package/dist/cli/commands/promote.js +29 -7
- package/dist/cli/commands/promote.js.map +1 -1
- package/dist/cli/commands/publish.js +26 -5
- package/dist/cli/commands/publish.js.map +1 -1
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +53 -22
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/stage.d.ts +17 -0
- package/dist/cli/commands/stage.js +171 -0
- package/dist/cli/commands/stage.js.map +1 -0
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.js +60 -1
- package/dist/manifest.js.map +1 -1
- package/dist/manifest.json +1137 -0
- package/dist/resolve-flow-CDGQOKOU.d.ts +14 -0
- package/dist/rest/handlers/run-handler.js +26 -5
- package/dist/rest/handlers/run-handler.js.map +1 -1
- package/dist/widgets/117.js +1 -1
- package/dist/widgets/165.js +1 -1
- package/dist/widgets/370.js +1 -1
- package/dist/widgets/46.js +1 -1
- package/dist/widgets/50.js +1 -1
- package/dist/widgets/546.js +1 -1
- package/dist/widgets/62.js +1 -1
- package/dist/widgets/669.js +1 -1
- package/dist/widgets/733.js +2 -2
- package/dist/widgets/775.js +1 -1
- package/dist/widgets/78.js +1 -1
- package/dist/widgets/__federation_expose_ReleasePage.js +1 -1
- package/dist/widgets/mf-manifest.json +12 -12
- package/dist/widgets/mf-stats.json +16 -16
- package/dist/widgets/remoteEntry.js +4 -4
- package/package.json +26 -26
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
3
|
+
import { F as FlowResolvableFlags } from '../../resolve-flow-CDGQOKOU.js';
|
|
4
|
+
|
|
5
|
+
interface DeliverFlags extends FlowResolvableFlags {
|
|
6
|
+
target?: string;
|
|
7
|
+
'artifacts-dir'?: string;
|
|
8
|
+
tag?: string;
|
|
9
|
+
registry?: string;
|
|
10
|
+
otp?: string;
|
|
11
|
+
access?: string;
|
|
12
|
+
token?: string;
|
|
13
|
+
'dry-run'?: boolean;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<DeliverFlags>, unknown>;
|
|
17
|
+
|
|
18
|
+
export { _default as default };
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import { readFileSync, appendFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { defineCommand, findRepoRoot, useConfig, useEnv, useLoader, useLogger } from '@kb-labs/sdk';
|
|
4
|
+
import { mergeConfigWithFlow, resolvePublishTag, resolvePublishRegistry, verifyAgainstRegistry, resolveFlowFromTag } from '@kb-labs/release-manager-core';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
|
|
7
|
+
// src/cli/commands/deliver.ts
|
|
8
|
+
function rewriteLinkDep(deps, depName, val, pkgPath, versionMap) {
|
|
9
|
+
const pinned = versionMap.get(depName);
|
|
10
|
+
if (pinned) {
|
|
11
|
+
deps[depName] = `^${pinned}`;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const linkPath = val.slice("link:".length);
|
|
16
|
+
const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, "package.json"), "utf-8"));
|
|
17
|
+
deps[depName] = `^${linked.version ?? "*"}`;
|
|
18
|
+
} catch {
|
|
19
|
+
deps[depName] = "*";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function rewriteWorkspaceDep(deps, depName, val, versionMap) {
|
|
23
|
+
const pinned = versionMap.get(depName);
|
|
24
|
+
if (!pinned) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
deps[depName] = val === "workspace:*" ? `^${pinned}` : val.replace("workspace:", "");
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function rewriteDepsSection(deps, pkgPath, versionMap, packageManager) {
|
|
31
|
+
let modified = false;
|
|
32
|
+
for (const depName of Object.keys(deps)) {
|
|
33
|
+
const val = deps[depName];
|
|
34
|
+
if (typeof val !== "string") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (val.startsWith("link:")) {
|
|
38
|
+
rewriteLinkDep(deps, depName, val, pkgPath, versionMap);
|
|
39
|
+
modified = true;
|
|
40
|
+
} else if (val.startsWith("workspace:") && packageManager !== "pnpm" && rewriteWorkspaceDep(deps, depName, val, versionMap)) {
|
|
41
|
+
modified = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return modified;
|
|
45
|
+
}
|
|
46
|
+
function rewriteWorkspaceDeps(pkg, versionMap, packageManager) {
|
|
47
|
+
const pkgPath = pkg.path;
|
|
48
|
+
const pkgJsonPath = join(pkgPath, "package.json");
|
|
49
|
+
const original = readFileSync(pkgJsonPath, "utf-8");
|
|
50
|
+
const pkgJson = JSON.parse(original);
|
|
51
|
+
let modified = false;
|
|
52
|
+
if (pkgJson.version !== pkg.version) {
|
|
53
|
+
pkgJson.version = pkg.version;
|
|
54
|
+
modified = true;
|
|
55
|
+
}
|
|
56
|
+
for (const section of ["dependencies", "peerDependencies"]) {
|
|
57
|
+
const deps = pkgJson[section];
|
|
58
|
+
if (!deps) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (rewriteDepsSection(deps, pkgPath, versionMap, packageManager)) {
|
|
62
|
+
modified = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (modified) {
|
|
66
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
|
|
67
|
+
}
|
|
68
|
+
return () => writeFileSync(pkgJsonPath, original);
|
|
69
|
+
}
|
|
70
|
+
function buildIntraDeps(packages, nameSet) {
|
|
71
|
+
const deps = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const pkg of packages) {
|
|
73
|
+
const intra = /* @__PURE__ */ new Set();
|
|
74
|
+
try {
|
|
75
|
+
const pkgJson = JSON.parse(readFileSync(join(pkg.path, "package.json"), "utf-8"));
|
|
76
|
+
for (const section of ["dependencies", "peerDependencies"]) {
|
|
77
|
+
for (const depName of Object.keys(pkgJson[section] ?? {})) {
|
|
78
|
+
if (nameSet.has(depName)) {
|
|
79
|
+
intra.add(depName);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
deps.set(pkg.name, intra);
|
|
86
|
+
}
|
|
87
|
+
return deps;
|
|
88
|
+
}
|
|
89
|
+
function buildDependantsMap(packages, deps) {
|
|
90
|
+
const dependants = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const pkg of packages) {
|
|
92
|
+
for (const dep of deps.get(pkg.name) ?? []) {
|
|
93
|
+
if (!dependants.has(dep)) {
|
|
94
|
+
dependants.set(dep, /* @__PURE__ */ new Set());
|
|
95
|
+
}
|
|
96
|
+
dependants.get(dep).add(pkg.name);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return dependants;
|
|
100
|
+
}
|
|
101
|
+
function computeWaves(packages, deps, dependants) {
|
|
102
|
+
const byName = new Map(packages.map((p) => [p.name, p]));
|
|
103
|
+
const remaining = new Map(
|
|
104
|
+
packages.map((p) => [p.name, (deps.get(p.name) ?? /* @__PURE__ */ new Set()).size])
|
|
105
|
+
);
|
|
106
|
+
const waves = [];
|
|
107
|
+
while (remaining.size > 0) {
|
|
108
|
+
const wave = [...remaining.entries()].filter(([, count]) => count === 0).map(([name]) => byName.get(name));
|
|
109
|
+
if (wave.length === 0) {
|
|
110
|
+
waves.push([...remaining.keys()].map((n) => byName.get(n)));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
waves.push(wave);
|
|
114
|
+
for (const pkg of wave) {
|
|
115
|
+
remaining.delete(pkg.name);
|
|
116
|
+
for (const dependant of dependants.get(pkg.name) ?? []) {
|
|
117
|
+
if (remaining.has(dependant)) {
|
|
118
|
+
remaining.set(dependant, (remaining.get(dependant) ?? 1) - 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return waves.filter((w) => w.length > 0);
|
|
124
|
+
}
|
|
125
|
+
function topoSort(packages, _packageManager) {
|
|
126
|
+
const nameSet = new Set(packages.map((p) => p.name));
|
|
127
|
+
const deps = buildIntraDeps(packages, nameSet);
|
|
128
|
+
const dependants = buildDependantsMap(packages, deps);
|
|
129
|
+
return computeWaves(packages, deps, dependants);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/shared/publish-programmatic.ts
|
|
133
|
+
var MAX_RETRIES = 5;
|
|
134
|
+
var RETRY_BASE_DELAYS_MS = [1e4, 2e4, 4e4, 8e4, 16e4];
|
|
135
|
+
var RETRYABLE_PATTERNS = [
|
|
136
|
+
"E429",
|
|
137
|
+
"429 Too Many Requests",
|
|
138
|
+
"ECONNRESET",
|
|
139
|
+
"ETIMEDOUT",
|
|
140
|
+
"ECONNREFUSED",
|
|
141
|
+
"ENOTFOUND",
|
|
142
|
+
"EAI_AGAIN",
|
|
143
|
+
"socket hang up",
|
|
144
|
+
"network timeout",
|
|
145
|
+
"read ECONNRESET"
|
|
146
|
+
];
|
|
147
|
+
var ALREADY_PUBLISHED_PATTERNS = [
|
|
148
|
+
"You cannot publish over the previously published versions",
|
|
149
|
+
"cannot publish over the previously published version",
|
|
150
|
+
"EPUBLISHCONFLICT"
|
|
151
|
+
// Some registries return 403 for conflicts
|
|
152
|
+
];
|
|
153
|
+
function isRetryable(message) {
|
|
154
|
+
return RETRYABLE_PATTERNS.some((p) => message.includes(p));
|
|
155
|
+
}
|
|
156
|
+
function isAlreadyPublished(message) {
|
|
157
|
+
return ALREADY_PUBLISHED_PATTERNS.some((p) => message.includes(p));
|
|
158
|
+
}
|
|
159
|
+
var NPM_ERROR_HINTS = {
|
|
160
|
+
E403: "npm rejected the publish (403). If other packages in the same run succeeded with the same token, this is almost certainly an npm-side restriction on this specific package/account entry (e.g. a security hold), not a token or code problem \u2014 check npmjs.com directly or contact npm support. Retrying will not help until that is resolved.",
|
|
161
|
+
E401: "Authentication failed (401) \u2014 NPM_TOKEN/NODE_AUTH_TOKEN is missing, expired, or invalid for this registry.",
|
|
162
|
+
E404: "Registry returned 404 \u2014 check the registry URL and that the package name/scope is correct and accessible with this token.",
|
|
163
|
+
EOTP: "npm requires a one-time password for this publish \u2014 programmatic (token-based) delivery cannot supply one; this package needs an interactive `npm publish` from a human, or a token exempt from OTP."
|
|
164
|
+
};
|
|
165
|
+
function extractNpmErrorCode(message) {
|
|
166
|
+
const match = message.match(/\bnpm error code (E[A-Z0-9]+)\b/);
|
|
167
|
+
return match?.[1];
|
|
168
|
+
}
|
|
169
|
+
function classifyPublishError(message) {
|
|
170
|
+
const code = extractNpmErrorCode(message);
|
|
171
|
+
const hint = code ? NPM_ERROR_HINTS[code] : void 0;
|
|
172
|
+
return { code, hint };
|
|
173
|
+
}
|
|
174
|
+
function retryDelay(attempt) {
|
|
175
|
+
const base = RETRY_BASE_DELAYS_MS[Math.min(attempt, RETRY_BASE_DELAYS_MS.length - 1)];
|
|
176
|
+
const jitter = 0.8 + Math.random() * 0.4;
|
|
177
|
+
return Math.round(base * jitter);
|
|
178
|
+
}
|
|
179
|
+
function resolveToken(token) {
|
|
180
|
+
return token ?? useEnv("NPM_TOKEN") ?? useEnv("NODE_AUTH_TOKEN");
|
|
181
|
+
}
|
|
182
|
+
function spawnPublish(options) {
|
|
183
|
+
const { packagePath, tarballPath, packageManager, token, otp, dryRun, tag, access, registry } = options;
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const args = ["publish"];
|
|
186
|
+
if (tarballPath) {
|
|
187
|
+
args.push(tarballPath);
|
|
188
|
+
}
|
|
189
|
+
if (packageManager === "pnpm" && !tarballPath) {
|
|
190
|
+
args.push("--no-git-checks");
|
|
191
|
+
}
|
|
192
|
+
if (dryRun) {
|
|
193
|
+
args.push("--dry-run");
|
|
194
|
+
}
|
|
195
|
+
if (tag) {
|
|
196
|
+
args.push(`--tag=${tag}`);
|
|
197
|
+
}
|
|
198
|
+
if (access) {
|
|
199
|
+
args.push(`--access=${access}`);
|
|
200
|
+
}
|
|
201
|
+
if (registry) {
|
|
202
|
+
args.push(`--registry=${registry}`);
|
|
203
|
+
}
|
|
204
|
+
if (otp) {
|
|
205
|
+
args.push(`--otp=${otp}`);
|
|
206
|
+
}
|
|
207
|
+
const env = { ...process.env };
|
|
208
|
+
if (token) {
|
|
209
|
+
env["NODE_AUTH_TOKEN"] = token;
|
|
210
|
+
}
|
|
211
|
+
const npmrcPath = join(packagePath, ".npmrc");
|
|
212
|
+
const npmrcExisted = existsSync(npmrcPath);
|
|
213
|
+
const npmrcBackup = npmrcExisted ? readFileSync(npmrcPath, "utf-8") : null;
|
|
214
|
+
const registryHost = registry ? new URL(registry).host : "registry.npmjs.org";
|
|
215
|
+
const authLine = `//${registryHost}/:_authToken=\${NODE_AUTH_TOKEN}
|
|
216
|
+
`;
|
|
217
|
+
if (token) {
|
|
218
|
+
const existing = npmrcExisted ? readFileSync(npmrcPath, "utf-8") : "";
|
|
219
|
+
if (!existing.includes("_authToken")) {
|
|
220
|
+
writeFileSync(npmrcPath, existing + authLine);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const restoreNpmrc = () => {
|
|
224
|
+
if (!token) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
if (npmrcBackup !== null) {
|
|
229
|
+
writeFileSync(npmrcPath, npmrcBackup);
|
|
230
|
+
} else if (existsSync(npmrcPath)) {
|
|
231
|
+
unlinkSync(npmrcPath);
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
const child = spawn(packageManager, args, {
|
|
237
|
+
cwd: packagePath,
|
|
238
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
239
|
+
shell: true,
|
|
240
|
+
env
|
|
241
|
+
});
|
|
242
|
+
let stdout = "";
|
|
243
|
+
let stderr = "";
|
|
244
|
+
child.stdout?.on("data", (d) => {
|
|
245
|
+
stdout += d.toString();
|
|
246
|
+
});
|
|
247
|
+
child.stderr?.on("data", (d) => {
|
|
248
|
+
stderr += d.toString();
|
|
249
|
+
});
|
|
250
|
+
child.on("close", (code) => {
|
|
251
|
+
restoreNpmrc();
|
|
252
|
+
if (code === 0) {
|
|
253
|
+
resolve();
|
|
254
|
+
} else {
|
|
255
|
+
reject(new Error(stderr || stdout || `${packageManager} publish exited with code ${code}`));
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
child.on("error", (err) => {
|
|
259
|
+
restoreNpmrc();
|
|
260
|
+
reject(err);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async function publishOne(pkg, opts, logger) {
|
|
265
|
+
const restore = pkg.tarballPath ? (() => {
|
|
266
|
+
}) : rewriteWorkspaceDeps(pkg, opts.versionMap, opts.packageManager);
|
|
267
|
+
try {
|
|
268
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
269
|
+
try {
|
|
270
|
+
await spawnPublish({
|
|
271
|
+
packagePath: pkg.path,
|
|
272
|
+
tarballPath: pkg.tarballPath,
|
|
273
|
+
packageManager: opts.packageManager,
|
|
274
|
+
token: opts.token,
|
|
275
|
+
otp: opts.otp,
|
|
276
|
+
dryRun: opts.dryRun,
|
|
277
|
+
tag: opts.tag,
|
|
278
|
+
access: opts.access,
|
|
279
|
+
registry: opts.registry
|
|
280
|
+
});
|
|
281
|
+
logger.info(`Published ${pkg.name}@${pkg.version}`);
|
|
282
|
+
return { name: pkg.name, version: pkg.version, success: true };
|
|
283
|
+
} catch (err) {
|
|
284
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
285
|
+
if (isAlreadyPublished(message)) {
|
|
286
|
+
logger.info(`${pkg.name}@${pkg.version} already published \u2014 skipping`);
|
|
287
|
+
return { name: pkg.name, version: pkg.version, success: true, alreadyPublished: true };
|
|
288
|
+
}
|
|
289
|
+
if (isRetryable(message) && attempt < MAX_RETRIES) {
|
|
290
|
+
const delay = retryDelay(attempt);
|
|
291
|
+
logger.warn(
|
|
292
|
+
`Transient error for ${pkg.name}@${pkg.version} (attempt ${attempt + 1}/${MAX_RETRIES}), retrying in ${(delay / 1e3).toFixed(1)}s: ${message.split("\n")[0]}`
|
|
293
|
+
);
|
|
294
|
+
await new Promise((r) => {
|
|
295
|
+
setTimeout(r, delay);
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const { code, hint } = classifyPublishError(message);
|
|
300
|
+
logger.error(`Failed to publish ${pkg.name}@${pkg.version}`, void 0, { error: message, errorCode: code });
|
|
301
|
+
return { name: pkg.name, version: pkg.version, success: false, error: message, errorCode: code, errorHint: hint };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
name: pkg.name,
|
|
306
|
+
version: pkg.version,
|
|
307
|
+
success: false,
|
|
308
|
+
error: `Publish failed after ${MAX_RETRIES} retries (transient errors)`
|
|
309
|
+
};
|
|
310
|
+
} finally {
|
|
311
|
+
restore();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function skipRemainingWaves(waves, waveIdx, waveFailed, logger, allResults) {
|
|
315
|
+
const remaining = waves.slice(waveIdx + 1).reduce((acc, w) => acc + w.length, 0);
|
|
316
|
+
if (remaining === 0) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
logger.warn(
|
|
320
|
+
`Wave ${waveIdx + 1} had ${waveFailed.length} failure(s) \u2014 skipping ${remaining} package(s) in later waves to avoid broken dependency chain`
|
|
321
|
+
);
|
|
322
|
+
for (const laterWave of waves.slice(waveIdx + 1)) {
|
|
323
|
+
for (const pkg of laterWave) {
|
|
324
|
+
allResults.push({ name: pkg.name, version: pkg.version, success: false, error: "Skipped: dependency wave failed" });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function publishPackagesProgrammatic(options) {
|
|
329
|
+
const {
|
|
330
|
+
packages,
|
|
331
|
+
packageManager = "pnpm",
|
|
332
|
+
dryRun,
|
|
333
|
+
otp,
|
|
334
|
+
tag,
|
|
335
|
+
access,
|
|
336
|
+
registry
|
|
337
|
+
} = options;
|
|
338
|
+
const logger = useLogger();
|
|
339
|
+
const token = resolveToken(options.token);
|
|
340
|
+
if (!token && !dryRun) {
|
|
341
|
+
const error = "No npm token found. Set NPM_TOKEN (or NODE_AUTH_TOKEN) in environment.";
|
|
342
|
+
logger.error(error);
|
|
343
|
+
return {
|
|
344
|
+
results: [],
|
|
345
|
+
published: [],
|
|
346
|
+
alreadyPublished: [],
|
|
347
|
+
failed: packages.map((p) => `${p.name}@${p.version}`),
|
|
348
|
+
skipped: [],
|
|
349
|
+
errors: [error]
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const versionMap = new Map(packages.map((p) => [p.name, p.version]));
|
|
353
|
+
const CONCURRENCY = Number(useEnv("KB_PUBLISH_CONCURRENCY") ?? 4);
|
|
354
|
+
const waves = topoSort(packages);
|
|
355
|
+
logger.info(
|
|
356
|
+
`Publishing ${packages.length} package(s) in ${waves.length} topological wave(s) (concurrency=${CONCURRENCY}, dryRun=${dryRun ?? false})`
|
|
357
|
+
);
|
|
358
|
+
const allResults = [];
|
|
359
|
+
for (let waveIdx = 0; waveIdx < waves.length; waveIdx++) {
|
|
360
|
+
const wave = waves[waveIdx];
|
|
361
|
+
logger.info(`Wave ${waveIdx + 1}/${waves.length}: publishing ${wave.length} package(s)`);
|
|
362
|
+
for (let i = 0; i < wave.length; i += CONCURRENCY) {
|
|
363
|
+
const batch = wave.slice(i, i + CONCURRENCY);
|
|
364
|
+
const batchResults = await Promise.all(
|
|
365
|
+
batch.map(
|
|
366
|
+
(pkg) => publishOne(pkg, { packageManager, token, versionMap, dryRun, otp, tag, access, registry }, logger)
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
allResults.push(...batchResults);
|
|
370
|
+
}
|
|
371
|
+
const waveFailed = allResults.slice(-wave.length).filter((r) => !r.success);
|
|
372
|
+
if (waveFailed.length > 0) {
|
|
373
|
+
skipRemainingWaves(waves, waveIdx, waveFailed, logger, allResults);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const published = allResults.filter((r) => r.success && !r.alreadyPublished).map((r) => `${r.name}@${r.version}`);
|
|
378
|
+
const alreadyPublished = allResults.filter((r) => r.alreadyPublished).map((r) => `${r.name}@${r.version}`);
|
|
379
|
+
const failed = allResults.filter((r) => !r.success).map((r) => `${r.name}@${r.version}`);
|
|
380
|
+
const errors = allResults.filter((r) => !r.success && r.error).map((r) => `${r.name}: ${r.error}`);
|
|
381
|
+
if (alreadyPublished.length > 0) {
|
|
382
|
+
logger.info(`${alreadyPublished.length} package(s) already published \u2014 counted as success`);
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
results: allResults,
|
|
386
|
+
published,
|
|
387
|
+
alreadyPublished,
|
|
388
|
+
failed,
|
|
389
|
+
skipped: dryRun ? packages.map((p) => `${p.name}@${p.version} (dry-run)`) : [],
|
|
390
|
+
errors
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function resolveFlowName(config, flags) {
|
|
394
|
+
if (flags.flow) {
|
|
395
|
+
return flags.flow;
|
|
396
|
+
}
|
|
397
|
+
if (flags["release-tag"]) {
|
|
398
|
+
const resolved = resolveFlowFromTag(config, flags["release-tag"]);
|
|
399
|
+
if (!resolved) {
|
|
400
|
+
return { error: `Could not resolve a release.flows entry from tag "${flags["release-tag"]}"` };
|
|
401
|
+
}
|
|
402
|
+
return resolved.flowName;
|
|
403
|
+
}
|
|
404
|
+
return { error: "requires --flow or --release-tag" };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/cli/commands/deliver.ts
|
|
408
|
+
var REGISTRY_VERIFY_RETRIES = 5;
|
|
409
|
+
function loadManifest(artifactsDir) {
|
|
410
|
+
const manifestPath = join(artifactsDir, "manifest.json");
|
|
411
|
+
try {
|
|
412
|
+
return JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
413
|
+
} catch {
|
|
414
|
+
return { error: `No manifest.json found at ${manifestPath} \u2014 run \`kb release stage\` first` };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function writeGithubStepSummary(markdown) {
|
|
418
|
+
const summaryPath = useEnv("GITHUB_STEP_SUMMARY");
|
|
419
|
+
if (!summaryPath) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
appendFileSync(summaryPath, markdown.endsWith("\n") ? markdown : `${markdown}
|
|
424
|
+
`);
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function buildFailureSummaryMarkdown(target, publishedCount, failed) {
|
|
429
|
+
const lines = [
|
|
430
|
+
`## \u274C \`release deliver --target ${target}\` \u2014 ${failed.length} package(s) failed`,
|
|
431
|
+
"",
|
|
432
|
+
`${publishedCount} package(s) delivered successfully; ${failed.length} did not.`,
|
|
433
|
+
""
|
|
434
|
+
];
|
|
435
|
+
for (const f of failed) {
|
|
436
|
+
lines.push(`### \`${f.name}@${f.version}\`${f.errorCode ? ` \u2014 \`${f.errorCode}\`` : ""}`);
|
|
437
|
+
if (f.errorHint) {
|
|
438
|
+
lines.push("", `**${f.errorHint}**`);
|
|
439
|
+
}
|
|
440
|
+
lines.push("", "```", f.error.slice(0, 2e3), "```", "");
|
|
441
|
+
}
|
|
442
|
+
return lines.join("\n");
|
|
443
|
+
}
|
|
444
|
+
var deliver_default = defineCommand({
|
|
445
|
+
id: "release:deliver",
|
|
446
|
+
description: "Ship the tarballs `release stage` already packed to a target (npm) \u2014 no packing, no rebuild",
|
|
447
|
+
handler: {
|
|
448
|
+
async execute(ctx, input) {
|
|
449
|
+
const { flags } = input;
|
|
450
|
+
const target = flags.target ?? "npm";
|
|
451
|
+
if (target !== "npm") {
|
|
452
|
+
const msg = `release:deliver --target ${target} is not implemented \u2014 only "npm" ships this pass (see release plan's out-of-scope section for github-release/mirror)`;
|
|
453
|
+
if (flags.json) {
|
|
454
|
+
ctx.ui?.json?.({ error: msg });
|
|
455
|
+
} else {
|
|
456
|
+
ctx.ui?.error?.(msg);
|
|
457
|
+
}
|
|
458
|
+
return { exitCode: 1 };
|
|
459
|
+
}
|
|
460
|
+
const cwd = ctx.cwd || process.cwd();
|
|
461
|
+
const repoRoot = await findRepoRoot(cwd);
|
|
462
|
+
const fileConfig = await useConfig();
|
|
463
|
+
const baseConfig = fileConfig ?? {};
|
|
464
|
+
const flowResult = resolveFlowName(baseConfig, flags);
|
|
465
|
+
if (typeof flowResult !== "string") {
|
|
466
|
+
const msg = `release:deliver ${flowResult.error}`;
|
|
467
|
+
if (flags.json) {
|
|
468
|
+
ctx.ui?.json?.({ error: msg });
|
|
469
|
+
} else {
|
|
470
|
+
ctx.ui?.error?.(msg);
|
|
471
|
+
}
|
|
472
|
+
return { exitCode: 1 };
|
|
473
|
+
}
|
|
474
|
+
const config = mergeConfigWithFlow(baseConfig, flowResult);
|
|
475
|
+
const artifactsDir = join(repoRoot, flags["artifacts-dir"] ?? ".kb/release/artifacts");
|
|
476
|
+
const manifest = loadManifest(artifactsDir);
|
|
477
|
+
if (!Array.isArray(manifest)) {
|
|
478
|
+
if (flags.json) {
|
|
479
|
+
ctx.ui?.json?.({ error: manifest.error });
|
|
480
|
+
} else {
|
|
481
|
+
ctx.ui?.error?.(manifest.error);
|
|
482
|
+
}
|
|
483
|
+
return { exitCode: 1 };
|
|
484
|
+
}
|
|
485
|
+
if (manifest.length === 0) {
|
|
486
|
+
const msg = `manifest.json at ${artifactsDir} lists no packages`;
|
|
487
|
+
if (flags.json) {
|
|
488
|
+
ctx.ui?.json?.({ error: msg });
|
|
489
|
+
} else {
|
|
490
|
+
ctx.ui?.error?.(msg);
|
|
491
|
+
}
|
|
492
|
+
return { exitCode: 1 };
|
|
493
|
+
}
|
|
494
|
+
const dryRun = flags["dry-run"];
|
|
495
|
+
const npmTag = flags.tag ?? resolvePublishTag(config, "stable");
|
|
496
|
+
const registry = flags.registry ?? resolvePublishRegistry(config, "stable");
|
|
497
|
+
const token = flags.token ?? useEnv("NPM_TOKEN") ?? useEnv("NODE_AUTH_TOKEN");
|
|
498
|
+
const packages = manifest.map((a) => ({
|
|
499
|
+
name: a.name,
|
|
500
|
+
version: a.version,
|
|
501
|
+
path: artifactsDir,
|
|
502
|
+
tarballPath: join(artifactsDir, a.tarball)
|
|
503
|
+
}));
|
|
504
|
+
const publishLoader = useLoader(`Delivering ${packages.length} package(s) to npm...`);
|
|
505
|
+
publishLoader.start();
|
|
506
|
+
const publishResult = await publishPackagesProgrammatic({
|
|
507
|
+
packages,
|
|
508
|
+
packageManager: "npm",
|
|
509
|
+
dryRun,
|
|
510
|
+
otp: flags.otp,
|
|
511
|
+
tag: npmTag,
|
|
512
|
+
access: flags.access ?? config.publish?.access,
|
|
513
|
+
registry,
|
|
514
|
+
token
|
|
515
|
+
});
|
|
516
|
+
const failed = publishResult.results.filter((r) => !r.success);
|
|
517
|
+
if (failed.length > 0) {
|
|
518
|
+
publishLoader.fail(`${failed.length} package(s) failed to publish`);
|
|
519
|
+
const publishedList = publishResult.results.filter((r) => r.success).map((r) => ({ name: r.name, version: r.version }));
|
|
520
|
+
const failedList = failed.map((r) => ({
|
|
521
|
+
name: r.name,
|
|
522
|
+
version: r.version,
|
|
523
|
+
error: r.error ?? "Unknown error",
|
|
524
|
+
errorCode: r.errorCode,
|
|
525
|
+
errorHint: r.errorHint
|
|
526
|
+
}));
|
|
527
|
+
const result2 = { exitCode: 1, target, published: publishedList, failed: failedList };
|
|
528
|
+
writeGithubStepSummary(buildFailureSummaryMarkdown(target, publishedList.length, failedList));
|
|
529
|
+
if (flags.json) {
|
|
530
|
+
ctx.ui?.json?.(result2);
|
|
531
|
+
} else {
|
|
532
|
+
ctx.ui?.sideBox?.({
|
|
533
|
+
title: "Deliver \u2014 npm",
|
|
534
|
+
sections: [{
|
|
535
|
+
header: `Failed (${publishedList.length} delivered ok)`,
|
|
536
|
+
items: failedList.map((r) => `${ctx.ui.symbols.error} ${r.name}@${r.version}${r.errorCode ? ` [${r.errorCode}]` : ""} \u2014 ${r.errorHint ?? r.error}`)
|
|
537
|
+
}],
|
|
538
|
+
status: "error"
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return result2;
|
|
542
|
+
}
|
|
543
|
+
publishLoader.succeed(`Delivered ${publishResult.results.length} package(s) to npm`);
|
|
544
|
+
if (dryRun) {
|
|
545
|
+
const result2 = { exitCode: 0, target, published: publishResult.results.map((r) => ({ name: r.name, version: r.version })) };
|
|
546
|
+
if (flags.json) {
|
|
547
|
+
ctx.ui?.json?.(result2);
|
|
548
|
+
}
|
|
549
|
+
return result2;
|
|
550
|
+
}
|
|
551
|
+
const verifyLoader = useLoader("Verifying delivery against the registry...");
|
|
552
|
+
verifyLoader.start();
|
|
553
|
+
const verifyTargets = manifest.map((a) => ({ name: a.name, version: a.version, path: artifactsDir }));
|
|
554
|
+
const verifyResults = await verifyAgainstRegistry(verifyTargets, {
|
|
555
|
+
registry,
|
|
556
|
+
retries: REGISTRY_VERIFY_RETRIES,
|
|
557
|
+
logger: ctx.platform?.logger
|
|
558
|
+
});
|
|
559
|
+
const verifyIssues = verifyResults.flatMap((r) => r.issues);
|
|
560
|
+
const result = {
|
|
561
|
+
exitCode: verifyIssues.length === 0 ? 0 : 1,
|
|
562
|
+
target,
|
|
563
|
+
published: publishResult.results.map((r) => ({ name: r.name, version: r.version })),
|
|
564
|
+
verifyIssues
|
|
565
|
+
};
|
|
566
|
+
if (verifyIssues.length > 0) {
|
|
567
|
+
verifyLoader.fail(`Delivery verification found ${verifyIssues.length} issue(s) \u2014 packages are published, verification failed`);
|
|
568
|
+
writeGithubStepSummary([
|
|
569
|
+
`## \u26A0\uFE0F \`release deliver --target ${target}\` \u2014 publish succeeded, verification found ${verifyIssues.length} issue(s)`,
|
|
570
|
+
"",
|
|
571
|
+
"Packages are already live on the registry \u2014 this is a verification problem, not a publish failure, and nothing was rolled back (npm unpublish is never attempted automatically).",
|
|
572
|
+
"",
|
|
573
|
+
...verifyIssues.map((i) => `- ${i}`)
|
|
574
|
+
].join("\n"));
|
|
575
|
+
} else {
|
|
576
|
+
verifyLoader.succeed("Delivery verified against the registry");
|
|
577
|
+
}
|
|
578
|
+
if (flags.json) {
|
|
579
|
+
ctx.ui?.json?.(result);
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
ctx.ui?.sideBox?.({
|
|
583
|
+
title: "Deliver \u2014 npm",
|
|
584
|
+
sections: [
|
|
585
|
+
{ header: `Delivered to ${registry} (tag: ${npmTag})`, items: publishResult.results.map((r) => `${ctx.ui.symbols.success} ${r.name}@${r.version}`) },
|
|
586
|
+
...verifyIssues.length > 0 ? [{ header: "Verification issues", items: verifyIssues.map((i) => `${ctx.ui.symbols.error} ${i}`) }] : []
|
|
587
|
+
],
|
|
588
|
+
status: verifyIssues.length === 0 ? "success" : "error"
|
|
589
|
+
});
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
export { deliver_default as default };
|
|
596
|
+
//# sourceMappingURL=deliver.js.map
|
|
597
|
+
//# sourceMappingURL=deliver.js.map
|