@kb-labs/release-manager-core 2.118.2 → 2.119.0-canary.0c078654e

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
@@ -357,6 +357,14 @@ interface ChangelogGenerator {
357
357
  repoRoot: string;
358
358
  gitCwd: string;
359
359
  config: ReleaseConfig;
360
+ /** Named flow whose release tag pattern bounds the changelog. */
361
+ flow?: string;
362
+ /** Explicit git boundaries; these take precedence over the flow baseline. */
363
+ range?: {
364
+ from?: string;
365
+ to?: string;
366
+ sinceTag?: string;
367
+ };
360
368
  }): Promise<string>;
361
369
  }
362
370
  interface PipelineOptions {
@@ -744,12 +752,26 @@ declare function runReleaseChecks(checks: CustomCheckConfig[], options: CheckRun
744
752
  declare function verifyPackages(packages: PackageVersion[], options?: {
745
753
  logger?: Pick<PluginLogger, 'info'>;
746
754
  onProgress?: (pkg: string, result: VerifyResult) => void;
755
+ packageManager?: 'pnpm' | 'npm' | 'yarn';
747
756
  }): Promise<VerifyResult[]>;
748
757
  /**
749
758
  * Verify a single package is publishable.
750
- * npm pack → extract → check exports, directory imports, test leaks, syntax.
751
- */
752
- declare function verifyPackage(packagePath: string, packageName?: string): VerifyResult;
759
+ * pack → extract → check exports, directory imports, test leaks, syntax.
760
+ *
761
+ * Packing tool must match what the real publish step will actually use:
762
+ * `pnpm pack` resolves `workspace:`/`link:` protocol refs to real version
763
+ * ranges natively (confirmed: `pnpm pack` on a package with
764
+ * `peerDependencies: { "@kb-labs/sdk": "workspace:^" }` produces a tarball
765
+ * with `"@kb-labs/sdk": "^2.115.4"`). `npm pack` does not — it packs the
766
+ * manifest byte-for-byte, so an unrewritten `workspace:*`/`link:` ref
767
+ * survives into the tarball and gets correctly flagged by
768
+ * findForbiddenDependencyProtocols() below. Defaulting to 'npm' here when
769
+ * the caller doesn't say otherwise is deliberately the strict reading (catch
770
+ * it even if we don't know yet what will actually publish); passing 'pnpm'
771
+ * for a pipeline that really does pack with pnpm avoids false positives on
772
+ * refs pnpm already resolves for free.
773
+ */
774
+ declare function verifyPackage(packagePath: string, packageName?: string, packageManager?: 'pnpm' | 'npm' | 'yarn'): VerifyResult;
753
775
  /**
754
776
  * Run the static artifact checks (test-file leaks, exports existence,
755
777
  * directory-import detection, syntax validation) against an already
@@ -798,6 +820,13 @@ interface VerifyAgainstRegistryOptions {
798
820
  * Default: 0 (single attempt, matches pre-existing Verdaccio behavior).
799
821
  */
800
822
  retries?: number;
823
+ /**
824
+ * Total time to wait for npm metadata and tarballs to propagate after a
825
+ * publish. When provided this supersedes the legacy fixed retry count, so
826
+ * an idempotent re-run resumes verification of already-published tarballs
827
+ * instead of requiring a new publish or a workflow-owned sleep.
828
+ */
829
+ visibilityDeadlineMs?: number;
801
830
  retryDelaysMs?: readonly number[];
802
831
  logger?: Pick<PluginLogger, 'info' | 'warn'>;
803
832
  }
package/dist/index.js CHANGED
@@ -1287,13 +1287,13 @@ function evaluateParser(check, stdout, stderr, exitCode) {
1287
1287
  async function verifyPackages(packages, options) {
1288
1288
  const results = [];
1289
1289
  for (const pkg of packages) {
1290
- const result = verifyPackage(pkg.path, pkg.name);
1290
+ const result = verifyPackage(pkg.path, pkg.name, options?.packageManager);
1291
1291
  results.push(result);
1292
1292
  options?.onProgress?.(pkg.name, result);
1293
1293
  }
1294
1294
  return results;
1295
1295
  }
1296
- function verifyPackage(packagePath, packageName) {
1296
+ function verifyPackage(packagePath, packageName, packageManager = "npm") {
1297
1297
  const pkgJsonPath = join(packagePath, "package.json");
1298
1298
  if (!existsSync(pkgJsonPath)) {
1299
1299
  return { name: packageName ?? packagePath, success: true, issues: [] };
@@ -1310,23 +1310,26 @@ function verifyPackage(packagePath, packageName) {
1310
1310
  const tmpDir = join(tmpdir(), `kb-verify-${randomBytes(6).toString("hex")}`);
1311
1311
  try {
1312
1312
  mkdirSync(tmpDir, { recursive: true });
1313
+ const isPnpm = packageManager === "pnpm";
1313
1314
  const origPkg = readFileSync(pkgJsonPath, "utf-8");
1314
- const modPkg = JSON.parse(origPkg);
1315
- for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
1316
- const deps = modPkg[section];
1317
- if (!deps) {
1318
- continue;
1319
- }
1320
- for (const [k, v] of Object.entries(deps)) {
1321
- if (typeof v === "string" && v.startsWith("link:")) {
1322
- deps[k] = "*";
1315
+ if (!isPnpm) {
1316
+ const modPkg = JSON.parse(origPkg);
1317
+ for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
1318
+ const deps = modPkg[section];
1319
+ if (!deps) {
1320
+ continue;
1321
+ }
1322
+ for (const [k, v] of Object.entries(deps)) {
1323
+ if (typeof v === "string" && v.startsWith("link:")) {
1324
+ deps[k] = "*";
1325
+ }
1323
1326
  }
1324
1327
  }
1328
+ writeFileSync(pkgJsonPath, JSON.stringify(modPkg, null, 2) + "\n");
1325
1329
  }
1326
- writeFileSync(pkgJsonPath, JSON.stringify(modPkg, null, 2) + "\n");
1327
1330
  let tgzFile;
1328
1331
  try {
1329
- spawnSync("npm", ["pack", "--pack-destination", tmpDir], { cwd: packagePath, stdio: "pipe", timeout: 3e4 });
1332
+ spawnSync(isPnpm ? "pnpm" : "npm", ["pack", "--pack-destination", tmpDir], { cwd: packagePath, stdio: "pipe", timeout: 3e4 });
1330
1333
  const files = readdirSync(tmpDir).filter((f) => f.endsWith(".tgz"));
1331
1334
  tgzFile = files[0] ? join(tmpDir, files[0]) : void 0;
1332
1335
  } finally {
@@ -1483,31 +1486,35 @@ async function verifyAgainstRegistry(packages, options) {
1483
1486
  const { registry, timeout = DEFAULT_TIMEOUT_MS, retries = 0, retryDelaysMs = DEFAULT_POLL_RETRY_DELAYS_MS, logger } = options;
1484
1487
  const results = [];
1485
1488
  for (const pkg of packages) {
1486
- results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, logger));
1489
+ results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, options.visibilityDeadlineMs, logger));
1487
1490
  }
1488
1491
  return results;
1489
1492
  }
1490
- async function waitUntilPublished(pkg, registry, retries, retryDelaysMs, logger) {
1491
- for (let attempt = 0; attempt <= retries; attempt++) {
1493
+ async function waitUntilPublished(pkg, registry, retries, retryDelaysMs, visibilityDeadlineMs, logger) {
1494
+ const deadline = visibilityDeadlineMs === void 0 ? void 0 : Date.now() + visibilityDeadlineMs;
1495
+ for (let attempt = 0; deadline === void 0 ? attempt <= retries : Date.now() <= deadline; attempt++) {
1492
1496
  if (await isVersionPublished(pkg.name, pkg.version, registry)) {
1493
1497
  return true;
1494
1498
  }
1495
- if (attempt < retries) {
1499
+ if (deadline === void 0 ? attempt < retries : Date.now() < deadline) {
1496
1500
  const delay = retryDelaysMs[Math.min(attempt, retryDelaysMs.length - 1)];
1501
+ const remaining = deadline === void 0 ? void 0 : deadline - Date.now();
1502
+ const waitMs = remaining === void 0 ? delay : Math.min(delay, Math.max(0, remaining));
1497
1503
  logger?.warn?.(
1498
- `${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`
1504
+ `${pkg.name}@${pkg.version} not yet visible on ${registry} (attempt ${attempt + 1}${deadline === void 0 ? `/${retries + 1}` : ""}), retrying in ${(waitMs / 1e3).toFixed(0)}s${remaining === void 0 ? "" : `; ${(Math.max(0, remaining) / 1e3).toFixed(0)}s remain`} \u2014 likely registry propagation lag`
1499
1505
  );
1500
1506
  await new Promise((r) => {
1501
- setTimeout(r, delay);
1507
+ setTimeout(r, waitMs);
1502
1508
  });
1503
1509
  }
1504
1510
  }
1505
1511
  return false;
1506
1512
  }
1507
- async function verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, logger) {
1508
- const published = await waitUntilPublished(pkg, registry, retries, retryDelaysMs, logger);
1513
+ async function verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, visibilityDeadlineMs, logger) {
1514
+ const published = await waitUntilPublished(pkg, registry, retries, retryDelaysMs, visibilityDeadlineMs, logger);
1509
1515
  if (!published) {
1510
- 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"})`] };
1516
+ const waited = visibilityDeadlineMs === void 0 ? `waited through ${retries} retr${retries === 1 ? "y" : "ies"}` : `visibility deadline of ${(visibilityDeadlineMs / 1e3).toFixed(0)}s elapsed`;
1517
+ return { name: pkg.name, success: false, issues: [`${pkg.name}@${pkg.version} was not found on ${registry} after publish (${waited})`] };
1511
1518
  }
1512
1519
  logger?.info?.(`${pkg.name}@${pkg.version} confirmed on ${registry}`);
1513
1520
  const tmpDir = join(tmpdir(), `kb-verdaccio-verify-${randomBytes(6).toString("hex")}`);
@@ -1809,7 +1816,8 @@ async function _runPipeline(ctx) {
1809
1816
  }
1810
1817
  if (!skipVerify && !dryRun) {
1811
1818
  progress("verifying", "Verifying package artifacts...");
1812
- const verifyResults = await verifyPackages(plan.packages, { logger });
1819
+ const packageManager = config.workspace?.type ?? config.publish?.packageManager ?? "pnpm";
1820
+ const verifyResults = await verifyPackages(plan.packages, { logger, packageManager });
1813
1821
  const verifyFailed = verifyResults.filter((r) => !r.success);
1814
1822
  if (verifyFailed.length > 0) {
1815
1823
  await restoreSnapshot(repoRoot);
@@ -1851,7 +1859,7 @@ async function _runPipeline(ctx) {
1851
1859
  if (changelogGen && channel === "stable") {
1852
1860
  progress("versioning", "Generating changelog...");
1853
1861
  try {
1854
- changelogMd = await changelogGen.generate(plan, { repoRoot, gitCwd: scopeCwd, config });
1862
+ changelogMd = await changelogGen.generate(plan, { repoRoot, gitCwd: scopeCwd, config, flow });
1855
1863
  } catch (err) {
1856
1864
  logger?.warn?.(`Changelog generation failed: ${err instanceof Error ? err.message : String(err)}`);
1857
1865
  }