@kb-labs/release-manager-cli 2.103.0 → 2.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cli/commands/deliver.d.ts +18 -0
  2. package/dist/cli/commands/deliver.js +540 -0
  3. package/dist/cli/commands/deliver.js.map +1 -0
  4. package/dist/cli/commands/promote.d.ts +2 -0
  5. package/dist/cli/commands/promote.js +11 -5
  6. package/dist/cli/commands/promote.js.map +1 -1
  7. package/dist/cli/commands/publish.js +8 -3
  8. package/dist/cli/commands/publish.js.map +1 -1
  9. package/dist/cli/commands/run.d.ts +1 -0
  10. package/dist/cli/commands/run.js +35 -20
  11. package/dist/cli/commands/run.js.map +1 -1
  12. package/dist/cli/commands/stage.d.ts +17 -0
  13. package/dist/cli/commands/stage.js +171 -0
  14. package/dist/cli/commands/stage.js.map +1 -0
  15. package/dist/index.js +60 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/manifest.js +60 -1
  18. package/dist/manifest.js.map +1 -1
  19. package/dist/manifest.json +1137 -0
  20. package/dist/resolve-flow-CDGQOKOU.d.ts +14 -0
  21. package/dist/rest/handlers/run-handler.js +8 -3
  22. package/dist/rest/handlers/run-handler.js.map +1 -1
  23. package/dist/widgets/117.js +1 -1
  24. package/dist/widgets/165.js +1 -1
  25. package/dist/widgets/370.js +1 -1
  26. package/dist/widgets/46.js +1 -1
  27. package/dist/widgets/50.js +1 -1
  28. package/dist/widgets/546.js +1 -1
  29. package/dist/widgets/62.js +1 -1
  30. package/dist/widgets/669.js +1 -1
  31. package/dist/widgets/733.js +2 -2
  32. package/dist/widgets/775.js +1 -1
  33. package/dist/widgets/78.js +1 -1
  34. package/dist/widgets/__federation_expose_ReleasePage.js +1 -1
  35. package/dist/widgets/mf-manifest.json +12 -12
  36. package/dist/widgets/mf-stats.json +16 -16
  37. package/dist/widgets/remoteEntry.js +4 -4
  38. package/package.json +26 -26
@@ -0,0 +1,171 @@
1
+ import { createHash } from 'crypto';
2
+ import { spawnSync } from 'child_process';
3
+ import { mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { defineCommand, findRepoRoot, useConfig, useLoader } from '@kb-labs/sdk';
6
+ import { mergeConfigWithFlow, discoverCurrentPackages, resolveFlowFromTag } from '@kb-labs/release-manager-core';
7
+
8
+ // src/cli/commands/stage.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
+ }
47
+ function rewriteWorkspaceDeps(pkg, versionMap, packageManager) {
48
+ const pkgPath = pkg.path;
49
+ const pkgJsonPath = join(pkgPath, "package.json");
50
+ const original = readFileSync(pkgJsonPath, "utf-8");
51
+ const pkgJson = JSON.parse(original);
52
+ let modified = false;
53
+ if (pkgJson.version !== pkg.version) {
54
+ pkgJson.version = pkg.version;
55
+ modified = true;
56
+ }
57
+ for (const section of ["dependencies", "peerDependencies"]) {
58
+ const deps = pkgJson[section];
59
+ if (!deps) {
60
+ continue;
61
+ }
62
+ if (rewriteDepsSection(deps, pkgPath, versionMap, packageManager)) {
63
+ modified = true;
64
+ }
65
+ }
66
+ if (modified) {
67
+ writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
68
+ }
69
+ return () => writeFileSync(pkgJsonPath, original);
70
+ }
71
+ function resolveFlowName(config, flags) {
72
+ if (flags.flow) {
73
+ return flags.flow;
74
+ }
75
+ if (flags["release-tag"]) {
76
+ const resolved = resolveFlowFromTag(config, flags["release-tag"]);
77
+ if (!resolved) {
78
+ return { error: `Could not resolve a release.flows entry from tag "${flags["release-tag"]}"` };
79
+ }
80
+ return resolved.flowName;
81
+ }
82
+ return { error: "requires --flow or --release-tag" };
83
+ }
84
+
85
+ // src/cli/commands/stage.ts
86
+ var stage_default = defineCommand({
87
+ id: "release:stage",
88
+ description: "Pack the currently-committed package versions for a flow into real npm tarballs, once, for `release deliver` to ship",
89
+ handler: {
90
+ async execute(ctx, input) {
91
+ const { flags } = input;
92
+ const cwd = ctx.cwd || process.cwd();
93
+ const repoRoot = await findRepoRoot(cwd);
94
+ const fileConfig = await useConfig();
95
+ const baseConfig = fileConfig ?? {};
96
+ const flowResult = resolveFlowName(baseConfig, flags);
97
+ if (typeof flowResult !== "string") {
98
+ const msg = `release:stage ${flowResult.error}`;
99
+ if (flags.json) {
100
+ ctx.ui?.json?.({ error: msg });
101
+ } else {
102
+ ctx.ui?.error?.(msg);
103
+ }
104
+ return { exitCode: 1 };
105
+ }
106
+ const flowName = flowResult;
107
+ const config = mergeConfigWithFlow(baseConfig, flowName);
108
+ const outDir = join(repoRoot, flags["out-dir"] ?? ".kb/release/artifacts");
109
+ mkdirSync(outDir, { recursive: true });
110
+ const discoverLoader = useLoader(`Discovering currently-committed packages for flow "${flowName}"...`);
111
+ discoverLoader.start();
112
+ const discovered = await discoverCurrentPackages(repoRoot, void 0, config);
113
+ discoverLoader.succeed(`Found ${discovered.length} package(s) to stage`);
114
+ if (discovered.length === 0) {
115
+ const msg = `No packages found for flow "${flowName}"`;
116
+ if (flags.json) {
117
+ ctx.ui?.json?.({ outDir, artifacts: [] });
118
+ } else {
119
+ ctx.ui?.write?.(msg);
120
+ }
121
+ return { exitCode: 1 };
122
+ }
123
+ const versionMap = new Map(discovered.map((pkg) => [pkg.name, pkg.currentVersion]));
124
+ const artifacts = [];
125
+ const packLoader = useLoader("Packing tarballs...");
126
+ packLoader.start();
127
+ for (const pkg of discovered) {
128
+ const restore = rewriteWorkspaceDeps({ path: pkg.path, version: pkg.currentVersion }, versionMap, "npm");
129
+ try {
130
+ const packResult = spawnSync("npm", ["pack", "--pack-destination", outDir, "--json"], {
131
+ cwd: pkg.path,
132
+ stdio: "pipe"
133
+ });
134
+ if (packResult.status !== 0) {
135
+ const stderr = packResult.stderr?.toString().trim() || packResult.stdout?.toString().trim();
136
+ throw new Error(`npm pack failed for ${pkg.name}@${pkg.currentVersion}${stderr ? `: ${stderr}` : ""}`);
137
+ }
138
+ const parsed = JSON.parse(packResult.stdout.toString());
139
+ const filename = parsed[0]?.filename;
140
+ if (!filename) {
141
+ throw new Error(`npm pack produced no tarball for ${pkg.name}@${pkg.currentVersion}`);
142
+ }
143
+ const sha256 = createHash("sha256").update(readFileSync(join(outDir, filename))).digest("hex");
144
+ artifacts.push({ name: pkg.name, version: pkg.currentVersion, tarball: filename, sha256 });
145
+ } finally {
146
+ restore();
147
+ }
148
+ }
149
+ packLoader.succeed(`Packed ${artifacts.length} tarball(s) to ${outDir}`);
150
+ writeFileSync(join(outDir, "manifest.json"), JSON.stringify(artifacts, null, 2) + "\n", "utf-8");
151
+ const result = { exitCode: 0, outDir, artifacts };
152
+ if (flags.json) {
153
+ ctx.ui?.json?.(result);
154
+ return result;
155
+ }
156
+ ctx.ui?.sideBox?.({
157
+ title: "Stage",
158
+ sections: [{
159
+ header: `Staged to ${outDir}`,
160
+ items: artifacts.map((a) => `${ctx.ui.symbols.success} ${a.name}@${a.version} \u2192 ${a.tarball}`)
161
+ }],
162
+ status: "success"
163
+ });
164
+ return result;
165
+ }
166
+ }
167
+ });
168
+
169
+ export { stage_default as default };
170
+ //# sourceMappingURL=stage.js.map
171
+ //# sourceMappingURL=stage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/shared/dep-rewrite.ts","../../../src/shared/resolve-flow.ts","../../../src/cli/commands/stage.ts"],"names":["join","readFileSync","writeFileSync"],"mappings":";;;;;;;;AAyBA,SAAS,cAAA,CACP,IAAA,EACA,OAAA,EACA,GAAA,EACA,SACA,UAAA,EACM;AACN,EAAA,MAAM,MAAA,GAAS,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AACrC,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,IAAA,CAAK,OAAO,CAAA,GAAI,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAC1B,IAAA;AAAA,EACF;AACA,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA;AACzC,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,IAAA,CAAK,SAAS,QAAA,EAAU,cAAc,CAAA,EAAG,OAAO,CAAC,CAAA;AACxF,IAAA,IAAA,CAAK,OAAO,CAAA,GAAI,CAAA,CAAA,EAAI,MAAA,CAAO,WAAW,GAAG,CAAA,CAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACN,IAAA,IAAA,CAAK,OAAO,CAAA,GAAI,GAAA;AAAA,EAClB;AACF;AAEA,SAAS,mBAAA,CACP,IAAA,EACA,OAAA,EACA,GAAA,EACA,UAAA,EACS;AACT,EAAA,MAAM,MAAA,GAAS,UAAA,CAAW,GAAA,CAAI,OAAO,CAAA;AACrC,EAAA,IAAI,CAAC,MAAA,EAAQ;AAAE,IAAA,OAAO,KAAA;AAAA,EAAO;AAC7B,EAAA,IAAA,CAAK,OAAO,CAAA,GAAI,GAAA,KAAQ,aAAA,GAAgB,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA,GAAK,GAAA,CAAI,OAAA,CAAQ,YAAA,EAAc,EAAE,CAAA;AACnF,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,kBAAA,CACP,IAAA,EACA,OAAA,EACA,UAAA,EACA,cAAA,EACS;AACT,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,KAAA,MAAW,OAAA,IAAW,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACvC,IAAA,MAAM,GAAA,GAAM,KAAK,OAAO,CAAA;AACxB,IAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAAE,MAAA;AAAA,IAAU;AACzC,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,OAAO,CAAA,EAAG;AAC3B,MAAA,cAAA,CAAe,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,OAAA,EAAS,UAAU,CAAA;AACtD,MAAA,QAAA,GAAW,IAAA;AAAA,IACb,CAAA,MAAA,IAAW,GAAA,CAAI,UAAA,CAAW,YAAY,CAAA,IAAK,cAAA,KAAmB,MAAA,IAAU,mBAAA,CAAoB,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,UAAU,CAAA,EAAG;AAC3H,MAAA,QAAA,GAAW,IAAA;AAAA,IACb;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT;AAEO,SAAS,oBAAA,CACd,GAAA,EACA,UAAA,EACA,cAAA,EACY;AACZ,EAAA,MAAM,UAAU,GAAA,CAAI,IAAA;AACpB,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AAChD,EAAA,MAAM,QAAA,GAAW,YAAA,CAAa,WAAA,EAAa,OAAO,CAAA;AAClD,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,QAAQ,CAAA;AACnC,EAAA,IAAI,QAAA,GAAW,KAAA;AAEf,EAAA,IAAI,OAAA,CAAQ,OAAA,KAAY,GAAA,CAAI,OAAA,EAAS;AACnC,IAAA,OAAA,CAAQ,UAAU,GAAA,CAAI,OAAA;AACtB,IAAA,QAAA,GAAW,IAAA;AAAA,EACb;AAEA,EAAA,KAAA,MAAW,OAAA,IAAW,CAAC,cAAA,EAAgB,kBAAkB,CAAA,EAAY;AACnE,IAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,IAAA,IAAI,CAAC,IAAA,EAAM;AAAE,MAAA;AAAA,IAAU;AACvB,IAAA,IAAI,kBAAA,CAAmB,IAAA,EAAM,OAAA,EAAS,UAAA,EAAY,cAAc,CAAA,EAAG;AAAE,MAAA,QAAA,GAAW,IAAA;AAAA,IAAM;AAAA,EACxF;AAEA,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,aAAA,CAAc,aAAa,IAAA,CAAK,SAAA,CAAU,SAAS,IAAA,EAAM,CAAC,IAAI,IAAI,CAAA;AAAA,EACpE;AAEA,EAAA,OAAO,MAAM,aAAA,CAAc,WAAA,EAAa,QAAQ,CAAA;AAClD;AC1FO,SAAS,eAAA,CAAgB,QAAuB,KAAA,EAAwD;AAC7G,EAAA,IAAI,MAAM,IAAA,EAAM;AAAE,IAAA,OAAO,KAAA,CAAM,IAAA;AAAA,EAAM;AACrC,EAAA,IAAI,KAAA,CAAM,aAAa,CAAA,EAAG;AACxB,IAAA,MAAM,QAAA,GAAW,kBAAA,CAAmB,MAAA,EAAQ,KAAA,CAAM,aAAa,CAAC,CAAA;AAChE,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,OAAO,EAAE,KAAA,EAAO,CAAA,kDAAA,EAAqD,KAAA,CAAM,aAAa,CAAC,CAAA,CAAA,CAAA,EAAI;AAAA,IAC/F;AACA,IAAA,OAAO,QAAA,CAAS,QAAA;AAAA,EAClB;AACA,EAAA,OAAO,EAAE,OAAO,kCAAA,EAAmC;AACrD;;;ACyBA,IAAO,gBAAQ,aAAA,CAAc;AAAA,EAC3B,EAAA,EAAI,eAAA;AAAA,EACJ,WAAA,EAAa,sHAAA;AAAA,EAEb,OAAA,EAAS;AAAA,IACP,MAAM,OAAA,CAAQ,GAAA,EAAsB,KAAA,EAAmD;AACrF,MAAA,MAAM,EAAE,OAAM,GAAI,KAAA;AAClB,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,GAAA,IAAO,OAAA,CAAQ,GAAA,EAAI;AACnC,MAAA,MAAM,QAAA,GAAW,MAAM,YAAA,CAAa,GAAG,CAAA;AAEvC,MAAA,MAAM,UAAA,GAAa,MAAM,SAAA,EAAyB;AAClD,MAAA,MAAM,UAAA,GAA4B,cAAc,EAAC;AAEjD,MAAA,MAAM,UAAA,GAAa,eAAA,CAAgB,UAAA,EAAY,KAAK,CAAA;AACpD,MAAA,IAAI,OAAO,eAAe,QAAA,EAAU;AAClC,QAAA,MAAM,GAAA,GAAM,CAAA,cAAA,EAAiB,UAAA,CAAW,KAAK,CAAA,CAAA;AAC7C,QAAA,IAAI,MAAM,IAAA,EAAM;AAAE,UAAA,GAAA,CAAI,EAAA,EAAI,IAAA,GAAO,EAAE,KAAA,EAAO,KAAK,CAAA;AAAA,QAAG,CAAA,MAAO;AAAE,UAAA,GAAA,CAAI,EAAA,EAAI,QAAQ,GAAG,CAAA;AAAA,QAAG;AACjF,QAAA,OAAO,EAAE,UAAU,CAAA,EAAE;AAAA,MACvB;AACA,MAAA,MAAM,QAAA,GAAW,UAAA;AAEjB,MAAA,MAAM,MAAA,GAAwB,mBAAA,CAAoB,UAAA,EAAY,QAAQ,CAAA;AACtE,MAAA,MAAM,SAASA,IAAAA,CAAK,QAAA,EAAU,KAAA,CAAM,SAAS,KAAK,uBAAuB,CAAA;AACzE,MAAA,SAAA,CAAU,MAAA,EAAQ,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAErC,MAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,CAAA,mDAAA,EAAsD,QAAQ,CAAA,IAAA,CAAM,CAAA;AACrG,MAAA,cAAA,CAAe,KAAA,EAAM;AACrB,MAAA,MAAM,UAAA,GAAa,MAAM,uBAAA,CAAwB,QAAA,EAAU,QAAW,MAAM,CAAA;AAC5E,MAAA,cAAA,CAAe,OAAA,CAAQ,CAAA,MAAA,EAAS,UAAA,CAAW,MAAM,CAAA,oBAAA,CAAsB,CAAA;AAEvE,MAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,QAAA,MAAM,GAAA,GAAM,+BAA+B,QAAQ,CAAA,CAAA,CAAA;AACnD,QAAA,IAAI,MAAM,IAAA,EAAM;AAAE,UAAA,GAAA,CAAI,IAAI,IAAA,GAAO,EAAE,QAAQ,SAAA,EAAW,IAAI,CAAA;AAAA,QAAG,CAAA,MAAO;AAAE,UAAA,GAAA,CAAI,EAAA,EAAI,QAAQ,GAAG,CAAA;AAAA,QAAG;AAC5F,QAAA,OAAO,EAAE,UAAU,CAAA,EAAE;AAAA,MACvB;AAEA,MAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAA,GAAA,KAAO,CAAC,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,cAAc,CAAC,CAAC,CAAA;AAChF,MAAA,MAAM,YAA8B,EAAC;AAErC,MAAA,MAAM,UAAA,GAAa,UAAU,qBAAqB,CAAA;AAClD,MAAA,UAAA,CAAW,KAAA,EAAM;AACjB,MAAA,KAAA,MAAW,OAAO,UAAA,EAAY;AAC5B,QAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,EAAE,IAAA,EAAM,GAAA,CAAI,IAAA,EAAM,OAAA,EAAS,GAAA,CAAI,cAAA,EAAe,EAAG,UAAA,EAAY,KAAK,CAAA;AACvG,QAAA,IAAI;AACF,UAAA,MAAM,UAAA,GAAa,UAAU,KAAA,EAAO,CAAC,QAAQ,oBAAA,EAAsB,MAAA,EAAQ,QAAQ,CAAA,EAAG;AAAA,YACpF,KAAK,GAAA,CAAI,IAAA;AAAA,YACT,KAAA,EAAO;AAAA,WACR,CAAA;AACD,UAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,YAAA,MAAM,MAAA,GAAS,UAAA,CAAW,MAAA,EAAQ,QAAA,EAAS,CAAE,IAAA,EAAK,IAAK,UAAA,CAAW,MAAA,EAAQ,QAAA,EAAS,CAAE,IAAA,EAAK;AAC1F,YAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,GAAA,CAAI,IAAI,CAAA,CAAA,EAAI,GAAA,CAAI,cAAc,CAAA,EAAG,MAAA,GAAS,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA,GAAK,EAAE,CAAA,CAAE,CAAA;AAAA,UACvG;AACA,UAAA,MAAM,SAAS,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,MAAA,CAAO,UAAU,CAAA;AACtD,UAAA,MAAM,QAAA,GAAW,MAAA,CAAO,CAAC,CAAA,EAAG,QAAA;AAC5B,UAAA,IAAI,CAAC,QAAA,EAAU;AACb,YAAA,MAAM,IAAI,MAAM,CAAA,iCAAA,EAAoC,GAAA,CAAI,IAAI,CAAA,CAAA,EAAI,GAAA,CAAI,cAAc,CAAA,CAAE,CAAA;AAAA,UACtF;AACA,UAAA,MAAM,MAAA,GAAS,UAAA,CAAW,QAAQ,CAAA,CAAE,MAAA,CAAOC,YAAAA,CAAaD,IAAAA,CAAK,MAAA,EAAQ,QAAQ,CAAC,CAAC,CAAA,CAAE,OAAO,KAAK,CAAA;AAC7F,UAAA,SAAA,CAAU,IAAA,CAAK,EAAE,IAAA,EAAM,GAAA,CAAI,IAAA,EAAM,OAAA,EAAS,GAAA,CAAI,cAAA,EAAgB,OAAA,EAAS,QAAA,EAAU,MAAA,EAAQ,CAAA;AAAA,QAC3F,CAAA,SAAE;AACA,UAAA,OAAA,EAAQ;AAAA,QACV;AAAA,MACF;AACA,MAAA,UAAA,CAAW,QAAQ,CAAA,OAAA,EAAU,SAAA,CAAU,MAAM,CAAA,eAAA,EAAkB,MAAM,CAAA,CAAE,CAAA;AAEvE,MAAAE,aAAAA,CAAcF,IAAAA,CAAK,MAAA,EAAQ,eAAe,CAAA,EAAG,IAAA,CAAK,SAAA,CAAU,SAAA,EAAW,IAAA,EAAM,CAAC,CAAA,GAAI,IAAA,EAAM,OAAO,CAAA;AAE/F,MAAA,MAAM,MAAA,GAAsB,EAAE,QAAA,EAAU,CAAA,EAAG,QAAQ,SAAA,EAAU;AAC7D,MAAA,IAAI,MAAM,IAAA,EAAM;AACd,QAAA,GAAA,CAAI,EAAA,EAAI,OAAO,MAAM,CAAA;AACrB,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,GAAA,CAAI,IAAI,OAAA,GAAU;AAAA,QAChB,KAAA,EAAO,OAAA;AAAA,QACP,UAAU,CAAC;AAAA,UACT,MAAA,EAAQ,aAAa,MAAM,CAAA,CAAA;AAAA,UAC3B,OAAO,SAAA,CAAU,GAAA,CAAI,OAAK,CAAA,EAAG,GAAA,CAAI,GAAG,OAAA,CAAQ,OAAO,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,CAAA,CAAE,OAAO,CAAA,QAAA,EAAM,CAAA,CAAE,OAAO,CAAA,CAAE;AAAA,SAC5F,CAAA;AAAA,QACD,MAAA,EAAQ;AAAA,OACT,CAAA;AAED,MAAA,OAAO,MAAA;AAAA,IACT;AAAA;AAEJ,CAAC","file":"stage.js","sourcesContent":["/**\n * Rewrite a package's own version and workspace:/link: dependency references\n * before `pnpm publish`, so the tarball on disk matches what's about to be\n * published even when that version was never persisted to package.json.\n *\n * - Own version: stable releases already have the target version on disk\n * (written by updatePackageVersions() before this runs), so this is a\n * no-op for them. Canary releases compute nextVersion in-memory only —\n * never committed to package.json/git — so without this, `pnpm publish`\n * would read the OLD on-disk version, and either fail outright or (worse)\n * get misclassified as \"already published\" by the retry/idempotency\n * patterns since that old version genuinely IS already on the registry.\n * - pnpm handles `workspace:*` natively during `pnpm publish` — no rewrite needed.\n * - npm/yarn do NOT — we must replace with `^version` before publish.\n * - `link:` references are never valid on the npm registry — always rewrite.\n *\n * Returns a restore function that reverts package.json to original content.\n * Call it in a finally block to guarantee cleanup.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n// ── rewriteWorkspaceDeps helpers ──────────────────────────────────────────\n\nfunction rewriteLinkDep(\n deps: Record<string, string>,\n depName: string,\n val: string,\n pkgPath: string,\n versionMap: Map<string, string>,\n): void {\n const pinned = versionMap.get(depName);\n if (pinned) {\n deps[depName] = `^${pinned}`;\n return;\n }\n try {\n const linkPath = val.slice('link:'.length);\n const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, 'package.json'), 'utf-8')) as { version?: string };\n deps[depName] = `^${linked.version ?? '*'}`;\n } catch {\n deps[depName] = '*';\n }\n}\n\nfunction rewriteWorkspaceDep(\n deps: Record<string, string>,\n depName: string,\n val: string,\n versionMap: Map<string, string>,\n): boolean {\n const pinned = versionMap.get(depName);\n if (!pinned) { return false; }\n deps[depName] = val === 'workspace:*' ? `^${pinned}` : val.replace('workspace:', '');\n return true;\n}\n\nfunction rewriteDepsSection(\n deps: Record<string, string>,\n pkgPath: string,\n versionMap: Map<string, string>,\n packageManager: string,\n): boolean {\n let modified = false;\n for (const depName of Object.keys(deps)) {\n const val = deps[depName];\n if (typeof val !== 'string') { continue; }\n if (val.startsWith('link:')) {\n rewriteLinkDep(deps, depName, val, pkgPath, versionMap);\n modified = true;\n } else if (val.startsWith('workspace:') && packageManager !== 'pnpm' && rewriteWorkspaceDep(deps, depName, val, versionMap)) {\n modified = true;\n }\n }\n return modified;\n}\n\nexport function rewriteWorkspaceDeps(\n pkg: { path: string; version: string },\n versionMap: Map<string, string>,\n packageManager: string,\n): () => void {\n const pkgPath = pkg.path;\n const pkgJsonPath = join(pkgPath, 'package.json');\n const original = readFileSync(pkgJsonPath, 'utf-8');\n const pkgJson = JSON.parse(original) as Record<string, unknown> & { version?: string };\n let modified = false;\n\n if (pkgJson.version !== pkg.version) {\n pkgJson.version = pkg.version;\n modified = true;\n }\n\n for (const section of ['dependencies', 'peerDependencies'] as const) {\n const deps = pkgJson[section] as Record<string, string> | undefined;\n if (!deps) { continue; }\n if (rewriteDepsSection(deps, pkgPath, versionMap, packageManager)) { modified = true; }\n }\n\n if (modified) {\n writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\\n');\n }\n\n return () => writeFileSync(pkgJsonPath, original);\n}\n\n// ── topoSort helpers ──────────────────────────────────────────────────────\n\n/**\n * Build a simple topological sort of packages for publish order.\n *\n * Packages are grouped into \"waves\": wave 0 has no intra-release deps,\n * wave 1 depends only on wave 0, etc. Within each wave publish order\n * is arbitrary (safe to parallelise).\n *\n * Cycles (should not exist in a well-formed monorepo) fall into a\n * \"remainder\" group appended at the end.\n */\nexport interface TopoPackage {\n name: string;\n path: string;\n version: string;\n}\n\nfunction buildIntraDeps(packages: TopoPackage[], nameSet: Set<string>): Map<string, Set<string>> {\n const deps = new Map<string, Set<string>>();\n for (const pkg of packages) {\n const intra = new Set<string>();\n try {\n const pkgJson = JSON.parse(readFileSync(join(pkg.path, 'package.json'), 'utf-8')) as {\n dependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n };\n for (const section of ['dependencies', 'peerDependencies'] as const) {\n for (const depName of Object.keys(pkgJson[section] ?? {})) {\n if (nameSet.has(depName)) { intra.add(depName); }\n }\n }\n } catch {\n // If we can't read package.json, treat as no deps\n }\n deps.set(pkg.name, intra);\n }\n return deps;\n}\n\nfunction buildDependantsMap(packages: TopoPackage[], deps: Map<string, Set<string>>): Map<string, Set<string>> {\n const dependants = new Map<string, Set<string>>();\n for (const pkg of packages) {\n for (const dep of deps.get(pkg.name) ?? []) {\n if (!dependants.has(dep)) { dependants.set(dep, new Set()); }\n dependants.get(dep)!.add(pkg.name);\n }\n }\n return dependants;\n}\n\nfunction computeWaves(\n packages: TopoPackage[],\n deps: Map<string, Set<string>>,\n dependants: Map<string, Set<string>>,\n): TopoPackage[][] {\n const byName = new Map(packages.map(p => [p.name, p]));\n const remaining = new Map<string, number>(\n packages.map(p => [p.name, (deps.get(p.name) ?? new Set()).size])\n );\n const waves: TopoPackage[][] = [];\n\n while (remaining.size > 0) {\n const wave = [...remaining.entries()]\n .filter(([, count]) => count === 0)\n .map(([name]) => byName.get(name)!);\n\n if (wave.length === 0) {\n waves.push([...remaining.keys()].map(n => byName.get(n)!));\n break;\n }\n\n waves.push(wave);\n for (const pkg of wave) {\n remaining.delete(pkg.name);\n for (const dependant of dependants.get(pkg.name) ?? []) {\n if (remaining.has(dependant)) {\n remaining.set(dependant, (remaining.get(dependant) ?? 1) - 1);\n }\n }\n }\n }\n\n return waves.filter(w => w.length > 0);\n}\n\nexport function topoSort(packages: TopoPackage[], _packageManager: string): TopoPackage[][] {\n const nameSet = new Set(packages.map(p => p.name));\n const deps = buildIntraDeps(packages, nameSet);\n const dependants = buildDependantsMap(packages, deps);\n return computeWaves(packages, deps, dependants);\n}\n","/**\n * Shared `--release-tag` / `--flow` resolution for commands that operate\n * on an already-decided release (`stage`, `deliver`) — mirrors the tag\n * grammar in `@kb-labs/release-manager-core`'s `resolveFlowFromTag` so CI\n * can pass just the tag and the plugin decides the flow, per the\n * \"plugin prepares, CI delivers\" release plan.\n */\n\nimport { resolveFlowFromTag, type ReleaseConfig } from '@kb-labs/release-manager-core';\n\nexport interface FlowResolvableFlags {\n flow?: string;\n 'release-tag'?: string;\n}\n\nexport function resolveFlowName(config: ReleaseConfig, flags: FlowResolvableFlags): string | { error: string } {\n if (flags.flow) { return flags.flow; }\n if (flags['release-tag']) {\n const resolved = resolveFlowFromTag(config, flags['release-tag']);\n if (!resolved) {\n return { error: `Could not resolve a release.flows entry from tag \"${flags['release-tag']}\"` };\n }\n return resolved.flowName;\n }\n return { error: 'requires --flow or --release-tag' };\n}\n","/**\n * Standalone stage command — packs the flow's currently-committed package\n * versions into real npm tarballs, once, so every downstream `release\n * deliver` job ships the exact same bytes instead of re-packing\n * independently from source. See plugins/release/docs/adr/0001-* and the\n * \"plugin prepares, CI delivers\" release plan.\n *\n * Deliberately does NOT call planRelease() — same reasoning as promote.ts:\n * versions are already fixed by the prepare step that already ran (version\n * bump, changelog, git commit/tag/push). This command's only job is\n * \"pack exactly what's already on disk, unchanged.\"\n *\n * Not to be confused with `release:pack` (`pack.ts`) — that command\n * verifies *proposed* packages via `npm pack` + static checks before a\n * release is decided; this one produces the *actual* tarball artifact for\n * an already-decided release.\n */\n\nimport { createHash } from 'node:crypto';\nimport { spawnSync } from 'node:child_process';\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { defineCommand, type CLIInput, type PluginContextV3, useLoader, useConfig } from '@kb-labs/sdk';\nimport {\n discoverCurrentPackages,\n mergeConfigWithFlow,\n type ReleaseConfig,\n} from '@kb-labs/release-manager-core';\nimport { findRepoRoot } from '../../shared/utils';\nimport { rewriteWorkspaceDeps } from '../../shared/dep-rewrite';\nimport { resolveFlowName, type FlowResolvableFlags } from '../../shared/resolve-flow';\n\ninterface StageFlags extends FlowResolvableFlags {\n 'out-dir'?: string;\n json?: boolean;\n}\n\nexport interface StagedArtifact {\n name: string;\n version: string;\n tarball: string;\n sha256: string;\n}\n\ninterface StageResult {\n exitCode: number;\n outDir?: string;\n artifacts?: StagedArtifact[];\n}\n\nexport default defineCommand({\n id: 'release:stage',\n description: 'Pack the currently-committed package versions for a flow into real npm tarballs, once, for `release deliver` to ship',\n\n handler: {\n async execute(ctx: PluginContextV3, input: CLIInput<StageFlags>): Promise<StageResult> {\n const { flags } = input;\n const cwd = ctx.cwd || process.cwd();\n const repoRoot = await findRepoRoot(cwd);\n\n const fileConfig = await useConfig<ReleaseConfig>();\n const baseConfig: ReleaseConfig = fileConfig ?? {};\n\n const flowResult = resolveFlowName(baseConfig, flags);\n if (typeof flowResult !== 'string') {\n const msg = `release:stage ${flowResult.error}`;\n if (flags.json) { ctx.ui?.json?.({ error: msg }); } else { ctx.ui?.error?.(msg); }\n return { exitCode: 1 };\n }\n const flowName = flowResult;\n\n const config: ReleaseConfig = mergeConfigWithFlow(baseConfig, flowName);\n const outDir = join(repoRoot, flags['out-dir'] ?? '.kb/release/artifacts');\n mkdirSync(outDir, { recursive: true });\n\n const discoverLoader = useLoader(`Discovering currently-committed packages for flow \"${flowName}\"...`);\n discoverLoader.start();\n const discovered = await discoverCurrentPackages(repoRoot, undefined, config);\n discoverLoader.succeed(`Found ${discovered.length} package(s) to stage`);\n\n if (discovered.length === 0) {\n const msg = `No packages found for flow \"${flowName}\"`;\n if (flags.json) { ctx.ui?.json?.({ outDir, artifacts: [] }); } else { ctx.ui?.write?.(msg); }\n return { exitCode: 1 };\n }\n\n const versionMap = new Map(discovered.map(pkg => [pkg.name, pkg.currentVersion]));\n const artifacts: StagedArtifact[] = [];\n\n const packLoader = useLoader('Packing tarballs...');\n packLoader.start();\n for (const pkg of discovered) {\n const restore = rewriteWorkspaceDeps({ path: pkg.path, version: pkg.currentVersion }, versionMap, 'npm');\n try {\n const packResult = spawnSync('npm', ['pack', '--pack-destination', outDir, '--json'], {\n cwd: pkg.path,\n stdio: 'pipe',\n });\n if (packResult.status !== 0) {\n const stderr = packResult.stderr?.toString().trim() || packResult.stdout?.toString().trim();\n throw new Error(`npm pack failed for ${pkg.name}@${pkg.currentVersion}${stderr ? `: ${stderr}` : ''}`);\n }\n const parsed = JSON.parse(packResult.stdout.toString()) as Array<{ filename: string }>;\n const filename = parsed[0]?.filename;\n if (!filename) {\n throw new Error(`npm pack produced no tarball for ${pkg.name}@${pkg.currentVersion}`);\n }\n const sha256 = createHash('sha256').update(readFileSync(join(outDir, filename))).digest('hex');\n artifacts.push({ name: pkg.name, version: pkg.currentVersion, tarball: filename, sha256 });\n } finally {\n restore();\n }\n }\n packLoader.succeed(`Packed ${artifacts.length} tarball(s) to ${outDir}`);\n\n writeFileSync(join(outDir, 'manifest.json'), JSON.stringify(artifacts, null, 2) + '\\n', 'utf-8');\n\n const result: StageResult = { exitCode: 0, outDir, artifacts };\n if (flags.json) {\n ctx.ui?.json?.(result);\n return result;\n }\n\n ctx.ui?.sideBox?.({\n title: 'Stage',\n sections: [{\n header: `Staged to ${outDir}`,\n items: artifacts.map(a => `${ctx.ui.symbols.success} ${a.name}@${a.version} → ${a.tarball}`),\n }],\n status: 'success',\n });\n\n return result;\n },\n },\n});\n"]}
package/dist/index.js CHANGED
@@ -121,6 +121,10 @@ var manifest = {
121
121
  "skip-checks": { type: "boolean", description: "Skip pre-release checks" },
122
122
  "skip-build": { type: "boolean", description: "Skip build step" },
123
123
  "skip-verify": { type: "boolean", description: "Skip artifact verification (npm pack check)" },
124
+ "skip-publish": {
125
+ type: "boolean",
126
+ description: "Prepare-only: build, version, changelog, git commit/tag \u2014 never publish to npm. No npm credentials required. Pair with a tag-triggered CI job running `kb release promote`."
127
+ },
124
128
  "no-verify": { type: "boolean", description: "Pass --no-verify to git push (bypasses pre-push hooks)" },
125
129
  yes: { type: "boolean", description: "Skip confirmation prompt \u2014 for CI/headless mode" },
126
130
  json: { type: "boolean", description: "Print result as JSON" }
@@ -134,7 +138,8 @@ var manifest = {
134
138
  "kb release run --scope @my-org/core",
135
139
  "kb release run --skip-checks --skip-build",
136
140
  "kb release run --strict --json",
137
- "kb release run --channel canary --yes"
141
+ "kb release run --channel canary --yes",
142
+ "kb release run --flow platform --skip-publish --yes"
138
143
  ]
139
144
  },
140
145
  // release:publish - Publish packages to npm
@@ -166,6 +171,26 @@ var manifest = {
166
171
  "kb release publish --tag next --access public"
167
172
  ]
168
173
  },
174
+ // release:stage - Pack the currently-committed versions into real tarballs, once
175
+ {
176
+ path: "release stage",
177
+ category: "Publish",
178
+ describe: "Pack the currently-committed package versions for a flow into real npm tarballs, once, for `release deliver` to ship",
179
+ operationType: "execute",
180
+ longDescription: "Produces the actual npm tarball artifacts for a flow's already-committed package.json versions \u2014 no re-bump, no rebuild. Writes a manifest.json (name/version/tarball/sha256) alongside the tarballs. Intended to run once in CI right after checking out a release tag; every `release deliver` target then ships these exact bytes instead of re-packing independently. Not the same command as `release pack` (that one verifies proposed packages before a release is decided; this one packs an already-decided one).",
181
+ handler: "./cli/commands/stage.js#default",
182
+ flags: defineCommandFlags({
183
+ "release-tag": { type: "string", description: "Git tag to resolve {flow, channel} from (via release.flows[*].tagPattern) \u2014 alternative to --flow" },
184
+ flow: { type: "string", description: "Named flow \u2014 selects packages the same way `release run --flow` does (e.g. excludes sdk from platform)" },
185
+ "out-dir": { type: "string", description: "Output directory for tarballs + manifest.json (default: .kb/release/artifacts)" },
186
+ json: { type: "boolean", description: "Output in JSON format" }
187
+ }),
188
+ examples: [
189
+ "kb release stage --flow platform",
190
+ "kb release stage --release-tag platform-v2.105.0",
191
+ "kb release stage --flow sdk --out-dir .kb/release/artifacts"
192
+ ]
193
+ },
169
194
  // release:promote - Promote an already-released version to npm
170
195
  {
171
196
  path: "release promote",
@@ -176,6 +201,7 @@ var manifest = {
176
201
  handler: "./cli/commands/promote.js#default",
177
202
  flags: defineCommandFlags({
178
203
  scope: { type: "string", description: "Package scope (glob pattern)" },
204
+ flow: { type: "string", description: "Named flow \u2014 selects packages the same way `release run --flow` does (e.g. excludes sdk from platform)" },
179
205
  tag: { type: "string", description: 'npm dist-tag override (default: config.publish.stableTag, falls back to "latest")' },
180
206
  registry: { type: "string", description: "Registry override (default: config.publish.npmRegistry, falls back to real npm)" },
181
207
  otp: { type: "string", description: "One-time password (optional, will prompt if needed)" },
@@ -190,11 +216,44 @@ var manifest = {
190
216
  }),
191
217
  examples: [
192
218
  "kb release promote",
219
+ "kb release promote --flow platform",
220
+ "kb release promote --flow sdk",
193
221
  "kb release promote --scope @kb-labs/core",
194
222
  "kb release promote --dry-run",
195
223
  "kb release promote --tag next"
196
224
  ]
197
225
  },
226
+ // release:deliver - Ship a `release stage`d artifact to a target (npm)
227
+ {
228
+ path: "release deliver",
229
+ category: "Publish",
230
+ describe: "Ship the tarballs `release stage` already packed to a target \u2014 no packing, no rebuild",
231
+ operationType: "execute",
232
+ longDescription: 'CI-side half of the "plugin prepares, CI delivers" release flow: reads manifest.json written by `release stage` and ships those exact tarballs to --target (only "npm" is implemented this pass). Resolves {flow, channel} from --release-tag via release.flows[*].tagPattern so CI never needs to guess the flow itself \u2014 just pass the tag. Verifies the delivery against the real registry afterwards (with retry, since real npm has propagation lag); never attempts npm unpublish on a verification failure \u2014 that is a human decision.',
233
+ handler: "./cli/commands/deliver.js#default",
234
+ flags: defineCommandFlags({
235
+ "release-tag": { type: "string", description: "Git tag to resolve {flow, channel} from (via release.flows[*].tagPattern) \u2014 alternative to --flow" },
236
+ flow: { type: "string", description: "Named flow \u2014 alternative to --release-tag" },
237
+ target: { type: "string", choices: ["npm"], description: "Delivery target (default: npm \u2014 the only target implemented this pass)" },
238
+ "artifacts-dir": { type: "string", description: "Where `release stage` wrote tarballs + manifest.json (default: .kb/release/artifacts)" },
239
+ tag: { type: "string", description: 'npm dist-tag override (default: config.publish.stableTag, falls back to "latest")' },
240
+ registry: { type: "string", description: "Registry override (default: config.publish.npmRegistry, falls back to real npm)" },
241
+ otp: { type: "string", description: "One-time password (optional, will prompt if needed)" },
242
+ "dry-run": { type: "boolean", description: "Simulate delivery without actually publishing" },
243
+ access: {
244
+ type: "string",
245
+ choices: ["public", "restricted"],
246
+ description: "Package access level"
247
+ },
248
+ token: { type: "string", description: "NPM auth token (overrides NPM_TOKEN env)" },
249
+ json: { type: "boolean", description: "Output in JSON format" }
250
+ }),
251
+ examples: [
252
+ "kb release deliver --release-tag platform-v2.105.0 --target npm",
253
+ "kb release deliver --flow sdk --target npm",
254
+ "kb release deliver --release-tag sdk-v3.2.0 --dry-run"
255
+ ]
256
+ },
198
257
  // release:rollback - Rollback last release
199
258
  {
200
259
  path: "release rollback",