@kb-labs/release-manager-core 2.105.0-canary.f6d35e31 → 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.
package/dist/index.d.ts CHANGED
@@ -134,6 +134,14 @@ interface FlowConfig {
134
134
  versioningStrategy?: 'lockstep' | 'independent' | 'adaptive';
135
135
  /** If set, replaces global checks for this flow. */
136
136
  checks?: CustomCheckConfig[];
137
+ /**
138
+ * Git tag template for this flow's stable releases. Tokens: `{flow}`
139
+ * (the flow's config key, e.g. "platform") and `{version}` (the release
140
+ * version). Default: `{flow}-v{version}` (e.g. `platform-v2.105.0`).
141
+ * Used both to generate the tag (`buildReleaseTag`) and to parse a tag
142
+ * back into a flow (`resolveFlowFromTag`) — see `./tag.ts`.
143
+ */
144
+ tagPattern?: string;
137
145
  }
138
146
  interface PackagesFilter {
139
147
  /** Glob dirs to scan, e.g. ['packages/*', 'apps/*'].
@@ -319,6 +327,14 @@ interface PipelineOptions {
319
327
  skipChecks?: boolean;
320
328
  skipBuild?: boolean;
321
329
  skipVerify?: boolean;
330
+ /**
331
+ * Prepare-only mode: run checks/build/verify/version-bump/changelog and
332
+ * commit+tag git, but never call the publisher. No npm credentials are
333
+ * required. Intended for a local/CI "prepare" step whose git tag is the
334
+ * trigger for a separate CI job that runs `kb release promote` to do the
335
+ * actual npm publish. See plugins/release/docs/adr/0001-*.
336
+ */
337
+ skipPublish?: boolean;
322
338
  /** Custom check configs from kb.config.json */
323
339
  checks?: CustomCheckConfig[];
324
340
  /** Injected publisher (CLI = interactive OTP, REST = programmatic token) */
@@ -452,6 +468,17 @@ declare function commitAndTagRelease(options: {
452
468
  }>;
453
469
  /** Repo-relative path to the consolidated root changelog. Defaults to DEFAULT_ROOT_CHANGELOG_PATH. */
454
470
  changelogOutputPath?: string;
471
+ /**
472
+ * The flow this release came from (e.g. "platform", "sdk"). Drives the
473
+ * tag grammar (`{flow}-v{version}`, see ./tag.ts). If omitted (ran
474
+ * without `--flow`), falls back to `"release"` and — for a genuinely
475
+ * divergent multi-version independent release — to the old one-tag-
476
+ * per-package behavior, since a single flow-level tag can't represent
477
+ * packages at different versions.
478
+ */
479
+ flowName?: string;
480
+ /** Per-flow tag template override, from `FlowConfig.tagPattern`. */
481
+ tagPattern?: string;
455
482
  }): Promise<{
456
483
  committed: boolean;
457
484
  tagged: string[];
@@ -550,6 +577,35 @@ declare function applyCanarySuffix(packages: PackageVersion[], shortSha: string)
550
577
  declare function resolvePublishTag(config: ReleaseConfig, channel: ReleaseChannel): string;
551
578
  declare function resolvePublishRegistry(config: ReleaseConfig, channel: ReleaseChannel): string;
552
579
 
580
+ /**
581
+ * Git tag grammar for release flows — one place that both generates a
582
+ * flow's release tag and parses a tag back into a flow. Keeping generation
583
+ * and resolution on the same template prevents them from drifting apart
584
+ * (previously tag-building was inlined ad hoc in `commitAndTagRelease` and
585
+ * nothing parsed a tag back into a flow at all — CI guessed via bash).
586
+ *
587
+ * Only stable-channel releases ever produce a git tag (canary is ephemeral,
588
+ * npm-only — see pipeline.ts), so a resolved tag always implies `channel:
589
+ * 'stable'`; it is never inferred from the tag itself.
590
+ */
591
+
592
+ declare const DEFAULT_TAG_PATTERN = "{flow}-v{version}";
593
+ /**
594
+ * Build the release tag for a flow. `tagPattern` defaults to
595
+ * `{flow}-v{version}` (e.g. `platform-v2.105.0`) when omitted.
596
+ */
597
+ declare function buildReleaseTag(flowName: string, version: string, tagPattern?: string): string;
598
+ /**
599
+ * Resolve a git tag back into the flow (and channel) that produced it, by
600
+ * matching it against every configured flow's tag pattern. Returns `null`
601
+ * if no configured flow's pattern matches — callers (e.g. `deliver`) must
602
+ * treat that as a hard failure, never a guess.
603
+ */
604
+ declare function resolveFlowFromTag(config: ReleaseConfig, tag: string): {
605
+ flowName: string;
606
+ channel: ReleaseChannel;
607
+ } | null;
608
+
553
609
  /**
554
610
  * Unified release pipeline — single orchestrator for CLI and REST.
555
611
  *
@@ -659,6 +715,13 @@ interface VerifyAgainstRegistryOptions {
659
715
  registry: string;
660
716
  /** Timeout (ms) for the registry HTTP check and the `npm pack` round-trip. Default: 30000. */
661
717
  timeout?: number;
718
+ /**
719
+ * Extra attempts for the "is it published yet" check before giving up —
720
+ * each retry waits `retryDelaysMs[attempt]` (capped at the last entry).
721
+ * Default: 0 (single attempt, matches pre-existing Verdaccio behavior).
722
+ */
723
+ retries?: number;
724
+ retryDelaysMs?: readonly number[];
662
725
  logger?: Pick<PluginLogger, 'info' | 'warn'>;
663
726
  }
664
727
  /**
@@ -693,4 +756,4 @@ declare function verifyAgainstRegistry(packages: PublishablePackage[], options:
693
756
  */
694
757
  declare function resolveScopePath(repoRoot: string, scope: string): Promise<string>;
695
758
 
696
- export { type AuditSummary, type BuildResult, type ChangelogGenerator, type CheckId, type CheckResult, type CheckResultDetails, type CustomCheckConfig, DEFAULT_ROOT_CHANGELOG_PATH, type FlowConfig, type PackagePublisher, type PackageVersion, type PackagesFilter, type PipelineOptions, type PipelineResult, type PlannerOptions, type PluginLogger, type PublishResult, type PublishablePackage, type ReleaseChannel, type ReleaseConfig, type ReleaseContext, type ReleasePlan, type ReleaseReport, type ReleaseResult, type ReleaseStage, type RollbackSnapshot, type StrategyOptions, type VerifyResult, type VersionBump, type VersionStrategy, applyCanarySuffix, applyVersionStrategy, buildPackages, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyExtractedTarball, verifyPackage, verifyPackages };
759
+ export { type AuditSummary, type BuildResult, type ChangelogGenerator, type CheckId, type CheckResult, type CheckResultDetails, type CustomCheckConfig, DEFAULT_ROOT_CHANGELOG_PATH, DEFAULT_TAG_PATTERN, type FlowConfig, type PackagePublisher, type PackageVersion, type PackagesFilter, type PipelineOptions, type PipelineResult, type PlannerOptions, type PluginLogger, type PublishResult, type PublishablePackage, type ReleaseChannel, type ReleaseConfig, type ReleaseContext, type ReleasePlan, type ReleaseReport, type ReleaseResult, type ReleaseStage, type RollbackSnapshot, type StrategyOptions, type VerifyResult, type VersionBump, type VersionStrategy, applyCanarySuffix, applyVersionStrategy, buildPackages, buildReleaseTag, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolveFlowFromTag, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyExtractedTarball, verifyPackage, verifyPackages };
package/dist/index.js CHANGED
@@ -507,6 +507,32 @@ function isCheckpointResumable(checkpoint, flow, version) {
507
507
  return checkpoint.publishedPackages.length > 0 && Object.values(checkpoint.gitRoots).some((s) => !s.pushed);
508
508
  }
509
509
 
510
+ // src/tag.ts
511
+ var DEFAULT_TAG_PATTERN = "{flow}-v{version}";
512
+ function escapeRegex(str) {
513
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
514
+ }
515
+ var SEMVER_SRC = "\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?";
516
+ function buildReleaseTag(flowName, version, tagPattern) {
517
+ const pattern = tagPattern ?? DEFAULT_TAG_PATTERN;
518
+ return pattern.replace("{flow}", flowName).replace("{version}", version);
519
+ }
520
+ function buildTagRegex(flowName, tagPattern) {
521
+ const escapedPattern = escapeRegex(tagPattern);
522
+ const src = escapedPattern.replace(escapeRegex("{flow}"), escapeRegex(flowName)).replace(escapeRegex("{version}"), `(${SEMVER_SRC})`);
523
+ return new RegExp(`^${src}$`);
524
+ }
525
+ function resolveFlowFromTag(config, tag) {
526
+ const flows = config.flows ?? {};
527
+ for (const [flowName, flowConfig] of Object.entries(flows)) {
528
+ const regex = buildTagRegex(flowName, flowConfig.tagPattern ?? DEFAULT_TAG_PATTERN);
529
+ if (regex.test(tag)) {
530
+ return { flowName, channel: "stable" };
531
+ }
532
+ }
533
+ return null;
534
+ }
535
+
510
536
  // src/publisher.ts
511
537
  async function updatePackageVersion(pkg) {
512
538
  const packageJsonPath = join(pkg.path, "package.json");
@@ -647,7 +673,7 @@ function createPackageChangelog(pkg, changelog) {
647
673
  return changelog.substring(startIdx, endIdx).trim();
648
674
  }
649
675
  async function commitAndTagRelease(options) {
650
- const { cwd, plan, dryRun, noVerify = false, repoRoot, checkpointGitRoots, changelogOutputPath } = options;
676
+ const { cwd, plan, dryRun, noVerify = false, repoRoot, checkpointGitRoots, changelogOutputPath, flowName = "release", tagPattern } = options;
651
677
  const simpleGit2 = (await import('simple-git')).default;
652
678
  const result = {
653
679
  committed: false,
@@ -671,7 +697,7 @@ async function commitAndTagRelease(options) {
671
697
  rootToPkgs.set(root, list);
672
698
  }
673
699
  const uniqueVersions = new Set(plan.packages.map((p) => p.nextVersion));
674
- const isLockstep = plan.packages.length > 1 && uniqueVersions.size === 1;
700
+ const singleVersionAcrossPlan = uniqueVersions.size === 1;
675
701
  const pushFlags = noVerify ? ["--no-verify"] : [];
676
702
  for (const [root, pkgs] of rootToPkgs) {
677
703
  const prior = checkpointGitRoots?.[root];
@@ -716,8 +742,8 @@ async function commitAndTagRelease(options) {
716
742
  result.committed = true;
717
743
  }
718
744
  if (rootTagged.length === 0) {
719
- if (isLockstep) {
720
- const tagName = `v${plan.packages[0].nextVersion}`;
745
+ if (singleVersionAcrossPlan) {
746
+ const tagName = buildReleaseTag(flowName, plan.packages[0].nextVersion, tagPattern);
721
747
  await rootGit.addTag(tagName);
722
748
  rootTagged = [tagName];
723
749
  } else {
@@ -1360,18 +1386,36 @@ function findFiles(dir, predicate) {
1360
1386
  return results;
1361
1387
  }
1362
1388
  var DEFAULT_TIMEOUT_MS = 3e4;
1389
+ var DEFAULT_POLL_RETRY_DELAYS_MS = [2e3, 4e3, 8e3, 16e3, 3e4];
1363
1390
  async function verifyAgainstRegistry(packages, options) {
1364
- const { registry, timeout = DEFAULT_TIMEOUT_MS, logger } = options;
1391
+ const { registry, timeout = DEFAULT_TIMEOUT_MS, retries = 0, retryDelaysMs = DEFAULT_POLL_RETRY_DELAYS_MS, logger } = options;
1365
1392
  const results = [];
1366
1393
  for (const pkg of packages) {
1367
- results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, logger));
1394
+ results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, logger));
1368
1395
  }
1369
1396
  return results;
1370
1397
  }
1371
- async function verifyOneAgainstRegistry(pkg, registry, timeout, logger) {
1372
- const published = await isVersionPublished(pkg.name, pkg.version, registry);
1398
+ async function waitUntilPublished(pkg, registry, retries, retryDelaysMs, logger) {
1399
+ for (let attempt = 0; attempt <= retries; attempt++) {
1400
+ if (await isVersionPublished(pkg.name, pkg.version, registry)) {
1401
+ return true;
1402
+ }
1403
+ if (attempt < retries) {
1404
+ const delay = retryDelaysMs[Math.min(attempt, retryDelaysMs.length - 1)];
1405
+ logger?.warn?.(
1406
+ `${pkg.name}@${pkg.version} not yet visible on ${registry} (attempt ${attempt + 1}/${retries + 1}), retrying in ${(delay / 1e3).toFixed(0)}s \u2014 likely registry propagation lag`
1407
+ );
1408
+ await new Promise((r) => {
1409
+ setTimeout(r, delay);
1410
+ });
1411
+ }
1412
+ }
1413
+ return false;
1414
+ }
1415
+ async function verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, logger) {
1416
+ const published = await waitUntilPublished(pkg, registry, retries, retryDelaysMs, logger);
1373
1417
  if (!published) {
1374
- return { name: pkg.name, success: false, issues: [`${pkg.name}@${pkg.version} was not found on ${registry} after publish`] };
1418
+ return { name: pkg.name, success: false, issues: [`${pkg.name}@${pkg.version} was not found on ${registry} after publish (waited through ${retries} retr${retries === 1 ? "y" : "ies"})`] };
1375
1419
  }
1376
1420
  logger?.info?.(`${pkg.name}@${pkg.version} confirmed on ${registry}`);
1377
1421
  const tmpDir = join(tmpdir(), `kb-verdaccio-verify-${randomBytes(6).toString("hex")}`);
@@ -1455,6 +1499,7 @@ async function runReleasePipeline(options) {
1455
1499
  skipChecks = false,
1456
1500
  skipBuild = false,
1457
1501
  skipVerify = false,
1502
+ skipPublish = false,
1458
1503
  noVerify = false,
1459
1504
  checks: checkConfigs,
1460
1505
  publisher,
@@ -1480,6 +1525,7 @@ async function runReleasePipeline(options) {
1480
1525
  skipChecks,
1481
1526
  skipBuild,
1482
1527
  skipVerify,
1528
+ skipPublish,
1483
1529
  noVerify,
1484
1530
  checkConfigs,
1485
1531
  publisher,
@@ -1504,6 +1550,7 @@ async function _runPipeline(ctx) {
1504
1550
  skipChecks,
1505
1551
  skipBuild,
1506
1552
  skipVerify,
1553
+ skipPublish,
1507
1554
  noVerify,
1508
1555
  checkConfigs,
1509
1556
  publisher,
@@ -1515,7 +1562,7 @@ async function _runPipeline(ctx) {
1515
1562
  const channel = config.channel ?? "stable";
1516
1563
  const publishTag = resolvePublishTag(config, channel);
1517
1564
  const publishRegistry = resolvePublishRegistry(config, channel);
1518
- if (!dryRun) {
1565
+ if (!dryRun && !skipPublish) {
1519
1566
  const registry = publishRegistry;
1520
1567
  const authError = await verifyNpmAuth(registry);
1521
1568
  if (authError) {
@@ -1553,7 +1600,7 @@ async function _runPipeline(ctx) {
1553
1600
  };
1554
1601
  }
1555
1602
  progress("planning", `Found ${plan.packages.length} package(s) to release`);
1556
- if (!dryRun) {
1603
+ if (!dryRun && !skipPublish) {
1557
1604
  const existingCheckpoint = loadCheckpoint(repoRoot);
1558
1605
  if (existingCheckpoint) {
1559
1606
  const uniqueVersions = new Set(plan.packages.map((p) => p.nextVersion));
@@ -1567,7 +1614,9 @@ async function _runPipeline(ctx) {
1567
1614
  noVerify,
1568
1615
  repoRoot,
1569
1616
  checkpointGitRoots: existingCheckpoint.gitRoots,
1570
- changelogOutputPath: config.changelog?.outputPath
1617
+ changelogOutputPath: config.changelog?.outputPath,
1618
+ flowName: flow,
1619
+ tagPattern: flow ? config.flows?.[flow]?.tagPattern : void 0
1571
1620
  });
1572
1621
  deleteCheckpoint(repoRoot);
1573
1622
  const resumeReport = buildReport("verifying", plan, repoRoot, dryRun, startTime, {
@@ -1703,13 +1752,19 @@ async function _runPipeline(ctx) {
1703
1752
  await copyChangelogToPackages({ plan, changelog: changelogMd });
1704
1753
  await mergeRootChangelog({ repoRoot, plan, changelog: changelogMd, outputPath: config.changelog?.outputPath });
1705
1754
  }
1706
- progress("publishing", dryRun ? "Simulating publish (dry-run)..." : "Publishing packages...");
1755
+ progress("publishing", dryRun ? "Simulating publish (dry-run)..." : skipPublish ? "Skipping publish (prepare-only)..." : "Publishing packages...");
1707
1756
  const packagesToPublish = plan.packages.map((pkg) => ({
1708
1757
  name: pkg.name,
1709
1758
  version: pkg.nextVersion,
1710
1759
  path: pkg.path
1711
1760
  }));
1712
- const publishResult = await publisher.publish(packagesToPublish, {
1761
+ const publishResult = skipPublish ? {
1762
+ published: [],
1763
+ alreadyPublished: [],
1764
+ failed: [],
1765
+ skipped: packagesToPublish.map((p) => `${p.name}@${p.version} (prepared, not published)`),
1766
+ errors: []
1767
+ } : await publisher.publish(packagesToPublish, {
1713
1768
  dryRun,
1714
1769
  access: config.publish?.access ?? "public",
1715
1770
  tag: publishTag,
@@ -1732,7 +1787,7 @@ async function _runPipeline(ctx) {
1732
1787
  })
1733
1788
  };
1734
1789
  }
1735
- if (!dryRun && channel === "stable") {
1790
+ if (!dryRun && !skipPublish && channel === "stable") {
1736
1791
  const uniqueVersions = new Set(plan.packages.map((p) => p.nextVersion));
1737
1792
  const cpVersion = uniqueVersions.size === 1 ? plan.packages[0].nextVersion : "independent";
1738
1793
  writeCheckpoint(repoRoot, {
@@ -1747,7 +1802,7 @@ async function _runPipeline(ctx) {
1747
1802
  gitRoots: {}
1748
1803
  });
1749
1804
  }
1750
- if (!dryRun && channel === "stable") {
1805
+ if (!dryRun && !skipPublish && channel === "stable") {
1751
1806
  progress("verifying", "Verifying published artifacts against the registry...");
1752
1807
  const registryVerifyResults = await verifyAgainstRegistry(packagesToPublish, {
1753
1808
  registry: publishRegistry,
@@ -1779,7 +1834,9 @@ async function _runPipeline(ctx) {
1779
1834
  dryRun,
1780
1835
  noVerify,
1781
1836
  repoRoot,
1782
- changelogOutputPath: config.changelog?.outputPath
1837
+ changelogOutputPath: config.changelog?.outputPath,
1838
+ flowName: flow,
1839
+ tagPattern: flow ? config.flows?.[flow]?.tagPattern : void 0
1783
1840
  });
1784
1841
  deleteCheckpoint(repoRoot);
1785
1842
  }
@@ -1850,6 +1907,6 @@ async function resolveScopePath(repoRoot, scope) {
1850
1907
  return join(repoRoot, scope);
1851
1908
  }
1852
1909
 
1853
- export { DEFAULT_ROOT_CHANGELOG_PATH, applyCanarySuffix, applyVersionStrategy, buildPackages, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyExtractedTarball, verifyPackage, verifyPackages };
1910
+ export { DEFAULT_ROOT_CHANGELOG_PATH, DEFAULT_TAG_PATTERN, applyCanarySuffix, applyVersionStrategy, buildPackages, buildReleaseTag, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolveFlowFromTag, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyExtractedTarball, verifyPackage, verifyPackages };
1854
1911
  //# sourceMappingURL=index.js.map
1855
1912
  //# sourceMappingURL=index.js.map