@kb-labs/release-manager-core 2.100.0 → 2.101.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/index.js CHANGED
@@ -43,6 +43,15 @@ function applyAdaptive(packages) {
43
43
  }
44
44
  return packages;
45
45
  }
46
+ function applyCanarySuffix(packages, shortSha) {
47
+ if (!shortSha) {
48
+ throw new Error("applyCanarySuffix: shortSha is required to build a canary version");
49
+ }
50
+ return packages.map((pkg) => ({
51
+ ...pkg,
52
+ nextVersion: `${pkg.nextVersion}-canary.${shortSha}`
53
+ }));
54
+ }
46
55
  function getMaxBump(packages) {
47
56
  let maxBump = "patch";
48
57
  for (const pkg of packages) {
@@ -114,32 +123,32 @@ async function isVersionPublished(name, version, registry) {
114
123
  return false;
115
124
  }
116
125
  }
117
- async function planRelease(options) {
118
- const { cwd, scope, bumpOverride } = options;
119
- const config = options.flow ? mergeConfigWithFlow(options.config, options.flow) : options.config;
126
+ async function discoverCurrentPackages(cwd, scope, config) {
120
127
  const allPackages = await discoverPackages(cwd, config);
121
- let packages;
122
- if (scope && scope !== "root") {
123
- const isWorkspace = existsSync(join(cwd, ".gitmodules"));
124
- if (isWorkspace) {
125
- const matchedRoots = filterByScope(allPackages, scope, config);
126
- const innerPackages = [];
127
- for (const root of matchedRoots) {
128
- innerPackages.push(root);
129
- const inner = await discoverPackages(root.path, config);
130
- for (const pkg of inner) {
131
- if (!innerPackages.some((p) => p.name === pkg.name)) {
132
- innerPackages.push(pkg);
133
- }
128
+ if (!scope || scope === "root") {
129
+ return allPackages;
130
+ }
131
+ const isWorkspace = existsSync(join(cwd, ".gitmodules"));
132
+ if (isWorkspace) {
133
+ const matchedRoots = filterByScope(allPackages, scope, config);
134
+ const innerPackages = [];
135
+ for (const root of matchedRoots) {
136
+ innerPackages.push(root);
137
+ const inner = await discoverPackages(root.path, config);
138
+ for (const pkg of inner) {
139
+ if (!innerPackages.some((p) => p.name === pkg.name)) {
140
+ innerPackages.push(pkg);
134
141
  }
135
142
  }
136
- packages = innerPackages;
137
- } else {
138
- packages = filterByScope(allPackages, scope, config);
139
143
  }
140
- } else {
141
- packages = allPackages;
144
+ return innerPackages;
142
145
  }
146
+ return filterByScope(allPackages, scope, config);
147
+ }
148
+ async function planRelease(options) {
149
+ const { cwd, scope, bumpOverride, channel = "stable" } = options;
150
+ const config = options.flow ? mergeConfigWithFlow(options.config, options.flow) : options.config;
151
+ const packages = await discoverCurrentPackages(cwd, scope, config);
143
152
  const isWorkspaceRoot = existsSync(join(cwd, ".gitmodules")) && !scope;
144
153
  let modifiedPackages;
145
154
  if (isWorkspaceRoot) {
@@ -202,11 +211,17 @@ async function planRelease(options) {
202
211
  const versionStrategy = mapBumpStrategyToVersionStrategy(bumpStrategy);
203
212
  planPackages = applyVersionStrategy(planPackages, {
204
213
  strategy: versionStrategy});
214
+ if (channel === "canary") {
215
+ const git = simpleGit(cwd, { timeout: { block: 6e4 } });
216
+ const shortSha = (await git.revparse(["--short", "HEAD"])).trim();
217
+ planPackages = applyCanarySuffix(planPackages, shortSha);
218
+ }
205
219
  return {
206
220
  packages: planPackages,
207
221
  strategy: config.strategy || "semver",
208
222
  registry: config.registry || "https://registry.npmjs.org",
209
- rollbackEnabled: config.rollback?.enabled ?? true
223
+ rollbackEnabled: config.rollback?.enabled ?? true,
224
+ channel
210
225
  };
211
226
  }
212
227
  function mapBumpStrategyToVersionStrategy(bumpStrategy) {
@@ -438,91 +453,6 @@ function matchesPackagePattern(pkgName, relativePath, patterns) {
438
453
  }
439
454
  return false;
440
455
  }
441
- function createExecaShellAdapter() {
442
- return {
443
- async exec(command, args, options) {
444
- try {
445
- const result = await execa(command, args || [], {
446
- cwd: options?.cwd,
447
- timeout: options?.timeout,
448
- preferLocal: true,
449
- env: options?.env || process.env
450
- });
451
- return {
452
- ok: result.exitCode === 0,
453
- code: result.exitCode ?? 0,
454
- stdout: result.stdout || "",
455
- stderr: result.stderr || ""
456
- };
457
- } catch (error) {
458
- const e = error;
459
- return {
460
- ok: false,
461
- code: e.exitCode || 1,
462
- stdout: e.stdout || "",
463
- stderr: e.stderr || e.message || ""
464
- };
465
- }
466
- }
467
- };
468
- }
469
- function rewriteLinkDep(deps, depName, val, pkgPath, versionMap) {
470
- const pinned = versionMap.get(depName);
471
- if (pinned) {
472
- deps[depName] = `^${pinned}`;
473
- return;
474
- }
475
- try {
476
- const linkPath = val.slice("link:".length);
477
- const linked = JSON.parse(readFileSync(join(pkgPath, linkPath, "package.json"), "utf-8"));
478
- deps[depName] = `^${linked.version ?? "*"}`;
479
- } catch {
480
- deps[depName] = "*";
481
- }
482
- }
483
- function rewriteWorkspaceDep(deps, depName, val, versionMap) {
484
- const pinned = versionMap.get(depName);
485
- if (!pinned) {
486
- return false;
487
- }
488
- deps[depName] = val === "workspace:*" ? `^${pinned}` : val.replace("workspace:", "");
489
- return true;
490
- }
491
- function rewriteDepsSection(deps, pkgPath, versionMap, packageManager) {
492
- let modified = false;
493
- for (const depName of Object.keys(deps)) {
494
- const val = deps[depName];
495
- if (typeof val !== "string") {
496
- continue;
497
- }
498
- if (val.startsWith("link:")) {
499
- rewriteLinkDep(deps, depName, val, pkgPath, versionMap);
500
- modified = true;
501
- } else if (val.startsWith("workspace:") && packageManager !== "pnpm" && rewriteWorkspaceDep(deps, depName, val, versionMap)) {
502
- modified = true;
503
- }
504
- }
505
- return modified;
506
- }
507
- function rewriteWorkspaceDeps(pkgPath, versionMap, packageManager) {
508
- const pkgJsonPath = join(pkgPath, "package.json");
509
- const original = readFileSync(pkgJsonPath, "utf-8");
510
- const pkgJson = JSON.parse(original);
511
- let modified = false;
512
- for (const section of ["dependencies", "peerDependencies"]) {
513
- const deps = pkgJson[section];
514
- if (!deps) {
515
- continue;
516
- }
517
- if (rewriteDepsSection(deps, pkgPath, versionMap, packageManager)) {
518
- modified = true;
519
- }
520
- }
521
- if (modified) {
522
- writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
523
- }
524
- return () => writeFileSync(pkgJsonPath, original);
525
- }
526
456
  function checkpointPath(repoRoot) {
527
457
  return join(repoRoot, ".kb", "release", "checkpoint.json");
528
458
  }
@@ -578,76 +508,6 @@ function isCheckpointResumable(checkpoint, flow, version) {
578
508
  }
579
509
 
580
510
  // src/publisher.ts
581
- async function publishPackages(options) {
582
- const { plan, dryRun, shell } = options;
583
- const shellApi = shell || createExecaShellAdapter();
584
- const result = {
585
- published: [],
586
- skipped: [],
587
- errors: [],
588
- versionUpdates: []
589
- };
590
- if (dryRun) {
591
- for (const pkg of plan.packages) {
592
- result.skipped.push(`${pkg.name}@${pkg.nextVersion} (dry-run)`);
593
- result.versionUpdates.push({
594
- package: pkg.name,
595
- from: pkg.currentVersion || "unknown",
596
- to: pkg.nextVersion || "unknown",
597
- updated: false
598
- });
599
- }
600
- return result;
601
- }
602
- const pm = options.config?.publish?.packageManager ?? "pnpm";
603
- const versionMap = new Map(plan.packages.map((p) => [p.name, p.nextVersion]));
604
- for (const pkg of plan.packages) {
605
- try {
606
- const registry = plan.registry || "https://registry.npmjs.org";
607
- try {
608
- await updatePackageVersion(pkg);
609
- result.versionUpdates.push({
610
- package: pkg.name,
611
- from: pkg.currentVersion || "unknown",
612
- to: pkg.nextVersion || "unknown",
613
- updated: true
614
- });
615
- } catch (versionError) {
616
- const msg = `Failed to update version for ${pkg.name}: ${versionError instanceof Error ? versionError.message : String(versionError)}`;
617
- result.errors.push(msg);
618
- result.versionUpdates.push({
619
- package: pkg.name,
620
- from: pkg.currentVersion || "unknown",
621
- to: pkg.nextVersion || "unknown",
622
- updated: false
623
- });
624
- continue;
625
- }
626
- const restoreDeps = rewriteWorkspaceDeps(pkg.path, versionMap, pm);
627
- const access = options.config?.publish?.access ?? "public";
628
- let publishResult;
629
- try {
630
- publishResult = await shellApi.exec(
631
- pm,
632
- ["publish", "--access", access, "--registry", registry],
633
- { cwd: pkg.path, timeout: 6e4 }
634
- );
635
- } finally {
636
- restoreDeps();
637
- }
638
- if (publishResult.ok) {
639
- result.published.push(`${pkg.name}@${pkg.nextVersion}`);
640
- } else {
641
- const errorDetails = publishResult.stderr || publishResult.stdout || "Unknown error";
642
- result.errors.push(`Failed to publish ${pkg.name}: ${errorDetails}`);
643
- }
644
- } catch (error) {
645
- const msg = `Failed to publish ${pkg.name}: ${error instanceof Error ? error.message : String(error)}`;
646
- result.errors.push(msg);
647
- }
648
- }
649
- return result;
650
- }
651
511
  async function updatePackageVersion(pkg) {
652
512
  const packageJsonPath = join(pkg.path, "package.json");
653
513
  const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
@@ -677,48 +537,14 @@ async function updatePackageVersions(plan) {
677
537
  }
678
538
  return results;
679
539
  }
680
- async function generateChangelog(options) {
681
- const { cwd, plan } = options;
682
- const changelogPath = join(cwd, "CHANGELOG.md");
683
- let existingChangelog = "";
684
- try {
685
- existingChangelog = await readFile(changelogPath, "utf-8");
686
- } catch {
687
- }
688
- const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
689
- const header = `## [${date}] Release
690
-
691
- `;
692
- const entries = [];
693
- for (const pkg of plan.packages) {
694
- entries.push(`- **${pkg.name}**: ${pkg.currentVersion} \u2192 ${pkg.nextVersion}`);
695
- }
696
- const newEntry = header + entries.join("\n") + "\n\n";
697
- const updatedChangelog = newEntry + existingChangelog;
698
- try {
699
- await mkdir(join(cwd, ".kb", "release"), { recursive: true });
700
- await writeFile(changelogPath, updatedChangelog, "utf-8");
701
- } catch (error) {
702
- console.warn(`Failed to write changelog: ${error instanceof Error ? error.message : String(error)}`);
703
- }
704
- return newEntry;
705
- }
706
- async function generateEnhancedChangelog(options) {
707
- const simpleChangelog = await generateChangelog({
708
- cwd: options.cwd,
709
- plan: options.plan
710
- });
711
- return {
712
- changelog: simpleChangelog,
713
- manifest: null
714
- };
715
- }
716
540
  async function copyChangelogToPackages(options) {
717
541
  const { plan, changelog } = options;
542
+ const uniqueVersions = new Set(plan.packages.map((p) => p.nextVersion));
543
+ const isLockstep = plan.packages.length > 1 && uniqueVersions.size === 1;
718
544
  for (const pkg of plan.packages) {
719
545
  try {
720
546
  let packageChangelog;
721
- if (plan.packages.length === 1) {
547
+ if (plan.packages.length === 1 || isLockstep) {
722
548
  packageChangelog = changelog;
723
549
  } else {
724
550
  packageChangelog = createPackageChangelog(pkg, changelog);
@@ -733,10 +559,11 @@ async function copyChangelogToPackages(options) {
733
559
  existingChangelog = await readFile(changelogPath, "utf-8");
734
560
  } catch {
735
561
  }
736
- const versionPattern = new RegExp(
562
+ const versionPattern = isLockstep ? new RegExp(`^##\\s+\\[${pkg.nextVersion.replace(/\./g, "\\.")}\\]`, "m") : new RegExp(
737
563
  `^##\\s+${pkg.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s+${pkg.nextVersion.replace(/\./g, "\\.")}`,
738
564
  "m"
739
565
  );
566
+ const nextSectionPattern = /^##\s+((@[\w-]+\/)?[\w-]+\s+\d|\[\d)/;
740
567
  let updatedChangelog;
741
568
  if (existingChangelog && versionPattern.test(existingChangelog)) {
742
569
  const lines = existingChangelog.split("\n");
@@ -746,7 +573,7 @@ async function copyChangelogToPackages(options) {
746
573
  const line = lines[i];
747
574
  if (line && versionPattern.test(line)) {
748
575
  startIdx = i;
749
- } else if (startIdx !== -1 && line && /^##\s+@?[\w-]+/.test(line)) {
576
+ } else if (startIdx !== -1 && line && nextSectionPattern.test(line)) {
750
577
  endIdx = i;
751
578
  break;
752
579
  }
@@ -941,58 +768,6 @@ async function restoreSnapshot(cwd) {
941
768
  async function cleanupOldSnapshots(_snapshotDir) {
942
769
  }
943
770
 
944
- // src/runner.ts
945
- async function runRelease(options) {
946
- const {
947
- config,
948
- runChecks,
949
- executePlan,
950
- onStageChange
951
- } = options;
952
- const startTime = Date.now();
953
- const errors = [];
954
- let checks;
955
- try {
956
- onStageChange?.("planning");
957
- if (config.verify && config.verify.length > 0 && runChecks) {
958
- onStageChange?.("checking");
959
- checks = await runChecks("checking");
960
- const failedChecks = Object.entries(checks).filter(([_, result]) => result && !result.ok).map(([id]) => id);
961
- if (failedChecks.length > 0) {
962
- errors.push(`Pre-release checks failed: ${failedChecks.join(", ")}`);
963
- if (config.strict) {
964
- return {
965
- ok: false,
966
- timingMs: Date.now() - startTime,
967
- errors,
968
- checks
969
- };
970
- }
971
- }
972
- }
973
- onStageChange?.("publishing");
974
- if (executePlan) {
975
- await executePlan();
976
- }
977
- onStageChange?.("verifying");
978
- return {
979
- ok: errors.length === 0,
980
- timingMs: Date.now() - startTime,
981
- errors: errors.length > 0 ? errors : void 0,
982
- checks
983
- };
984
- } catch (error) {
985
- onStageChange?.("rollback");
986
- errors.push(error instanceof Error ? error.message : String(error));
987
- return {
988
- ok: false,
989
- timingMs: Date.now() - startTime,
990
- errors,
991
- checks
992
- };
993
- }
994
- }
995
-
996
771
  // src/reporters/json.ts
997
772
  function renderJson(report) {
998
773
  return JSON.stringify(report, null, 2);
@@ -1134,6 +909,49 @@ function formatTiming2(ms) {
1134
909
  }
1135
910
  return `${(ms / 6e4).toFixed(1)}m`;
1136
911
  }
912
+ function createExecaShellAdapter() {
913
+ return {
914
+ async exec(command, args, options) {
915
+ try {
916
+ const result = await execa(command, args || [], {
917
+ cwd: options?.cwd,
918
+ timeout: options?.timeout,
919
+ preferLocal: true,
920
+ env: options?.env || process.env
921
+ });
922
+ return {
923
+ ok: result.exitCode === 0,
924
+ code: result.exitCode ?? 0,
925
+ stdout: result.stdout || "",
926
+ stderr: result.stderr || ""
927
+ };
928
+ } catch (error) {
929
+ const e = error;
930
+ return {
931
+ ok: false,
932
+ code: e.exitCode || 1,
933
+ stdout: e.stdout || "",
934
+ stderr: e.stderr || e.message || ""
935
+ };
936
+ }
937
+ }
938
+ };
939
+ }
940
+
941
+ // src/channel.ts
942
+ var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
943
+ function resolvePublishTag(config, channel) {
944
+ if (channel === "canary") {
945
+ return config.publish?.canaryTag ?? "canary";
946
+ }
947
+ return config.publish?.stableTag ?? "latest";
948
+ }
949
+ function resolvePublishRegistry(config, channel) {
950
+ if (channel === "canary") {
951
+ return config.publish?.npmRegistry ?? DEFAULT_NPM_REGISTRY;
952
+ }
953
+ return config.registry ?? DEFAULT_NPM_REGISTRY;
954
+ }
1137
955
  async function buildPackages(packages, options) {
1138
956
  const results = [];
1139
957
  for (const pkg of packages) {
@@ -1388,44 +1206,7 @@ function verifyPackage(packagePath, packageName) {
1388
1206
  }
1389
1207
  spawnSync("tar", ["xzf", tgzFile], { cwd: tmpDir, stdio: "pipe" });
1390
1208
  const extractedDir = join(tmpDir, "package");
1391
- const testFiles = findFiles(
1392
- join(extractedDir, "dist"),
1393
- (f) => f.includes(".spec.") || f.includes(".test.") || f.includes("__tests__")
1394
- );
1395
- if (testFiles.length > 0) {
1396
- issues.push(`Test files in dist/: ${testFiles.slice(0, 3).join(", ")}`);
1397
- }
1398
- const extractedPkg = JSON.parse(readFileSync(join(extractedDir, "package.json"), "utf-8"));
1399
- for (const field of ["main", "module", "types"]) {
1400
- const val = extractedPkg[field];
1401
- if (val && !existsSync(join(extractedDir, val))) {
1402
- issues.push(`${field}: ${val} does not exist in published package`);
1403
- }
1404
- }
1405
- if (extractedPkg.exports) {
1406
- checkExportsExist(extractedPkg.exports, extractedDir, "exports", issues);
1407
- }
1408
- const esmEntry = resolveEsmEntry(extractedPkg);
1409
- if (esmEntry) {
1410
- const esmPath = join(extractedDir, esmEntry);
1411
- if (existsSync(esmPath)) {
1412
- checkDirectoryImports(esmPath, join(extractedDir, "dist"), issues);
1413
- const esmCheck = spawnSync("node", ["--check", esmPath], { stdio: "pipe", timeout: 1e4 });
1414
- if (esmCheck.status !== 0) {
1415
- issues.push(`ESM syntax error in ${esmEntry}`);
1416
- }
1417
- }
1418
- }
1419
- const cjsEntry = resolveCjsEntry(extractedPkg);
1420
- if (cjsEntry) {
1421
- const cjsPath = join(extractedDir, cjsEntry);
1422
- if (existsSync(cjsPath)) {
1423
- const cjsCheck = spawnSync("node", ["--check", cjsPath], { stdio: "pipe", timeout: 1e4 });
1424
- if (cjsCheck.status !== 0) {
1425
- issues.push(`CJS syntax error in ${cjsEntry}`);
1426
- }
1427
- }
1428
- }
1209
+ issues.push(...verifyExtractedTarball(extractedDir));
1429
1210
  } catch (err) {
1430
1211
  issues.push(`Verification error: ${err instanceof Error ? err.message : String(err)}`);
1431
1212
  } finally {
@@ -1433,6 +1214,48 @@ function verifyPackage(packagePath, packageName) {
1433
1214
  }
1434
1215
  return { name, success: issues.length === 0, issues };
1435
1216
  }
1217
+ function verifyExtractedTarball(extractedDir) {
1218
+ const issues = [];
1219
+ const testFiles = findFiles(
1220
+ join(extractedDir, "dist"),
1221
+ (f) => f.includes(".spec.") || f.includes(".test.") || f.includes("__tests__")
1222
+ );
1223
+ if (testFiles.length > 0) {
1224
+ issues.push(`Test files in dist/: ${testFiles.slice(0, 3).join(", ")}`);
1225
+ }
1226
+ const extractedPkg = JSON.parse(readFileSync(join(extractedDir, "package.json"), "utf-8"));
1227
+ for (const field of ["main", "module", "types"]) {
1228
+ const val = extractedPkg[field];
1229
+ if (val && !existsSync(join(extractedDir, val))) {
1230
+ issues.push(`${field}: ${val} does not exist in published package`);
1231
+ }
1232
+ }
1233
+ if (extractedPkg.exports) {
1234
+ checkExportsExist(extractedPkg.exports, extractedDir, "exports", issues);
1235
+ }
1236
+ const esmEntry = resolveEsmEntry(extractedPkg);
1237
+ if (esmEntry) {
1238
+ const esmPath = join(extractedDir, esmEntry);
1239
+ if (existsSync(esmPath)) {
1240
+ checkDirectoryImports(esmPath, join(extractedDir, "dist"), issues);
1241
+ const esmCheck = spawnSync("node", ["--check", esmPath], { stdio: "pipe", timeout: 1e4 });
1242
+ if (esmCheck.status !== 0) {
1243
+ issues.push(`ESM syntax error in ${esmEntry}`);
1244
+ }
1245
+ }
1246
+ }
1247
+ const cjsEntry = resolveCjsEntry(extractedPkg);
1248
+ if (cjsEntry) {
1249
+ const cjsPath = join(extractedDir, cjsEntry);
1250
+ if (existsSync(cjsPath)) {
1251
+ const cjsCheck = spawnSync("node", ["--check", cjsPath], { stdio: "pipe", timeout: 1e4 });
1252
+ if (cjsCheck.status !== 0) {
1253
+ issues.push(`CJS syntax error in ${cjsEntry}`);
1254
+ }
1255
+ }
1256
+ }
1257
+ return issues;
1258
+ }
1436
1259
  function resolveEsmEntry(pkg) {
1437
1260
  const dotExport = pkg.exports?.["."];
1438
1261
  const importEntry = dotExport && typeof dotExport === "object" ? dotExport["import"] : void 0;
@@ -1502,6 +1325,55 @@ function findFiles(dir, predicate) {
1502
1325
  walk(dir);
1503
1326
  return results;
1504
1327
  }
1328
+ var DEFAULT_TIMEOUT_MS = 3e4;
1329
+ async function verifyAgainstRegistry(packages, options) {
1330
+ const { registry, timeout = DEFAULT_TIMEOUT_MS, logger } = options;
1331
+ const results = [];
1332
+ for (const pkg of packages) {
1333
+ results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, logger));
1334
+ }
1335
+ return results;
1336
+ }
1337
+ async function verifyOneAgainstRegistry(pkg, registry, timeout, logger) {
1338
+ const published = await isVersionPublished(pkg.name, pkg.version, registry);
1339
+ if (!published) {
1340
+ return { name: pkg.name, success: false, issues: [`${pkg.name}@${pkg.version} was not found on ${registry} after publish`] };
1341
+ }
1342
+ logger?.info?.(`${pkg.name}@${pkg.version} confirmed on ${registry}`);
1343
+ const tmpDir = join(tmpdir(), `kb-verdaccio-verify-${randomBytes(6).toString("hex")}`);
1344
+ const issues = [];
1345
+ try {
1346
+ mkdirSync(tmpDir, { recursive: true });
1347
+ const spec = `${pkg.name}@${pkg.version}`;
1348
+ const packResult = spawnSync(
1349
+ "npm",
1350
+ ["pack", spec, "--registry", registry, "--pack-destination", tmpDir],
1351
+ { stdio: "pipe", timeout }
1352
+ );
1353
+ if (packResult.status !== 0) {
1354
+ const stderr = packResult.stderr?.toString().trim();
1355
+ issues.push(`Could not pull ${spec} back from ${registry} for verification${stderr ? `: ${stderr}` : ""}`);
1356
+ return { name: pkg.name, success: false, issues };
1357
+ }
1358
+ const tgzFile = readdirSync(tmpDir).find((f) => f.endsWith(".tgz"));
1359
+ if (!tgzFile) {
1360
+ issues.push(`npm pack produced no tarball for ${spec} from ${registry}`);
1361
+ return { name: pkg.name, success: false, issues };
1362
+ }
1363
+ spawnSync("tar", ["xzf", tgzFile], { cwd: tmpDir, stdio: "pipe" });
1364
+ const extractedDir = join(tmpDir, "package");
1365
+ if (!existsSync(extractedDir)) {
1366
+ issues.push(`Failed to extract tarball for ${spec}`);
1367
+ return { name: pkg.name, success: false, issues };
1368
+ }
1369
+ issues.push(...verifyExtractedTarball(extractedDir));
1370
+ } catch (err) {
1371
+ issues.push(`Registry verification error: ${err instanceof Error ? err.message : String(err)}`);
1372
+ } finally {
1373
+ rmSync(tmpDir, { recursive: true, force: true });
1374
+ }
1375
+ return { name: pkg.name, success: issues.length === 0, issues };
1376
+ }
1505
1377
  function lockPath(repoRoot) {
1506
1378
  return join(repoRoot, ".kb", "release", "release.lock");
1507
1379
  }
@@ -1606,14 +1478,18 @@ async function _runPipeline(ctx) {
1606
1478
  startTime,
1607
1479
  progress
1608
1480
  } = ctx;
1481
+ const channel = config.channel ?? "stable";
1482
+ const publishTag = resolvePublishTag(config, channel);
1483
+ const publishRegistry = resolvePublishRegistry(config, channel);
1609
1484
  if (!dryRun) {
1610
- const registry = config.registry ?? "https://registry.npmjs.org";
1485
+ const registry = publishRegistry;
1611
1486
  const authError = await verifyNpmAuth(registry);
1612
1487
  if (authError) {
1488
+ const emptyPlan = { packages: [], strategy: "semver", registry, rollbackEnabled: false, channel };
1613
1489
  return {
1614
1490
  success: false,
1615
- plan: { packages: [], strategy: "semver", registry, rollbackEnabled: false },
1616
- report: buildReport("planning", { packages: [], strategy: "semver", registry, rollbackEnabled: false }, repoRoot, dryRun, startTime, {
1491
+ plan: emptyPlan,
1492
+ report: buildReport("planning", emptyPlan, repoRoot, dryRun, startTime, {
1617
1493
  ok: false,
1618
1494
  errors: [`npm auth check failed: ${authError}`],
1619
1495
  timingMs: Date.now() - startTime
@@ -1628,7 +1504,8 @@ async function _runPipeline(ctx) {
1628
1504
  scope,
1629
1505
  flow,
1630
1506
  // already includes defaultFlow fallback
1631
- bumpOverride: config.bump
1507
+ bumpOverride: config.bump,
1508
+ channel
1632
1509
  });
1633
1510
  if (plan.packages.length === 0) {
1634
1511
  return {
@@ -1758,26 +1635,28 @@ async function _runPipeline(ctx) {
1758
1635
  }
1759
1636
  progress("verifying", "Package artifacts verified");
1760
1637
  }
1761
- progress("versioning", "Updating package versions...");
1762
- if (!dryRun) {
1763
- const versionUpdates = await updatePackageVersions(plan);
1764
- const failedUpdates = versionUpdates.filter((u) => !u.updated);
1765
- if (failedUpdates.length > 0) {
1766
- await restoreSnapshot(repoRoot);
1767
- return {
1768
- success: false,
1769
- plan,
1770
- report: buildReport("versioning", plan, repoRoot, dryRun, startTime, {
1771
- ok: false,
1772
- errors: failedUpdates.map((u) => `Version update failed: ${u.package}`),
1773
- versionUpdates,
1774
- timingMs: Date.now() - startTime
1775
- })
1776
- };
1638
+ if (channel === "stable") {
1639
+ progress("versioning", "Updating package versions...");
1640
+ if (!dryRun) {
1641
+ const versionUpdates = await updatePackageVersions(plan);
1642
+ const failedUpdates = versionUpdates.filter((u) => !u.updated);
1643
+ if (failedUpdates.length > 0) {
1644
+ await restoreSnapshot(repoRoot);
1645
+ return {
1646
+ success: false,
1647
+ plan,
1648
+ report: buildReport("versioning", plan, repoRoot, dryRun, startTime, {
1649
+ ok: false,
1650
+ errors: failedUpdates.map((u) => `Version update failed: ${u.package}`),
1651
+ versionUpdates,
1652
+ timingMs: Date.now() - startTime
1653
+ })
1654
+ };
1655
+ }
1777
1656
  }
1778
1657
  }
1779
1658
  let changelogMd = "";
1780
- if (changelogGen) {
1659
+ if (changelogGen && channel === "stable") {
1781
1660
  progress("versioning", "Generating changelog...");
1782
1661
  try {
1783
1662
  changelogMd = await changelogGen.generate(plan, { repoRoot, gitCwd: scopeCwd, config });
@@ -1799,7 +1678,9 @@ async function _runPipeline(ctx) {
1799
1678
  }));
1800
1679
  const publishResult = await publisher.publish(packagesToPublish, {
1801
1680
  dryRun,
1802
- access: "public"
1681
+ access: config.publish?.access ?? "public",
1682
+ tag: publishTag,
1683
+ registry: publishRegistry
1803
1684
  });
1804
1685
  const publishFailed = publishResult.failed.length > 0;
1805
1686
  if (publishFailed) {
@@ -1818,7 +1699,7 @@ async function _runPipeline(ctx) {
1818
1699
  })
1819
1700
  };
1820
1701
  }
1821
- if (!dryRun) {
1702
+ if (!dryRun && channel === "stable") {
1822
1703
  const uniqueVersions = new Set(plan.packages.map((p) => p.nextVersion));
1823
1704
  const cpVersion = uniqueVersions.size === 1 ? plan.packages[0].nextVersion : "independent";
1824
1705
  writeCheckpoint(repoRoot, {
@@ -1833,8 +1714,31 @@ async function _runPipeline(ctx) {
1833
1714
  gitRoots: {}
1834
1715
  });
1835
1716
  }
1717
+ if (!dryRun && channel === "stable") {
1718
+ progress("verifying", "Verifying published artifacts against the registry...");
1719
+ const registryVerifyResults = await verifyAgainstRegistry(packagesToPublish, {
1720
+ registry: publishRegistry,
1721
+ timeout: config.publish?.verifyRegistryTimeoutMs,
1722
+ logger
1723
+ });
1724
+ const registryVerifyFailed = registryVerifyResults.filter((r) => !r.success);
1725
+ if (registryVerifyFailed.length > 0) {
1726
+ const allIssues = registryVerifyFailed.flatMap((r) => r.issues.map((i) => `${r.name}: ${i}`));
1727
+ return {
1728
+ success: false,
1729
+ plan,
1730
+ report: buildReport("verifying", plan, repoRoot, dryRun, startTime, {
1731
+ ok: false,
1732
+ published: publishResult.published,
1733
+ errors: [`Registry verification failed after publish \u2014 git commit/tag skipped:
1734
+ ${allIssues.join("\n ")}`],
1735
+ timingMs: Date.now() - startTime
1736
+ })
1737
+ };
1738
+ }
1739
+ }
1836
1740
  let gitResult;
1837
- if (!dryRun) {
1741
+ if (!dryRun && channel === "stable") {
1838
1742
  progress("verifying", "Committing and tagging release...");
1839
1743
  gitResult = await commitAndTagRelease({ cwd: scopeCwd, plan, dryRun, noVerify, repoRoot });
1840
1744
  deleteCheckpoint(repoRoot);
@@ -1906,6 +1810,6 @@ async function resolveScopePath(repoRoot, scope) {
1906
1810
  return join(repoRoot, scope);
1907
1811
  }
1908
1812
 
1909
- export { applyVersionStrategy, buildPackages, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, generateChangelog, generateEnhancedChangelog, isBuildCommand, matchesPackagePattern, mergeConfigWithFlow, planRelease, publishPackages, renderJson, renderMarkdown, renderText, resolveScopePath, restoreSnapshot, runRelease, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyPackage, verifyPackages };
1813
+ export { applyCanarySuffix, applyVersionStrategy, buildPackages, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, planRelease, renderJson, renderMarkdown, renderText, resolvePublishRegistry, resolvePublishTag, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyExtractedTarball, verifyPackage, verifyPackages };
1910
1814
  //# sourceMappingURL=index.js.map
1911
1815
  //# sourceMappingURL=index.js.map