@kb-labs/release-manager-core 2.114.0 → 2.116.11

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
@@ -132,7 +132,7 @@ interface FlowConfig {
132
132
  packages?: PackagesFilter;
133
133
  /** Replaces global versioningStrategy. */
134
134
  versioningStrategy?: 'lockstep' | 'independent' | 'adaptive';
135
- /** If set, replaces global checks for this flow. */
135
+ /** If set, adds to global checks; matching ids override the global check. */
136
136
  checks?: CustomCheckConfig[];
137
137
  /**
138
138
  * Git tag template for this flow's stable releases. Tokens: `{flow}`
@@ -207,10 +207,10 @@ interface ReleaseConfig {
207
207
  build?: BuildConfig;
208
208
  /** Filter which packages are discovered and released. */
209
209
  packages?: PackagesFilter;
210
- /** Per-scope overrides — packages filter merged with global, checks replace global entirely. */
210
+ /** Per-scope overrides — packages filter merged with global, checks are additive. */
211
211
  scopes?: Record<string, {
212
212
  packages?: PackagesFilter;
213
- /** If set, replaces global `checks` for this scope. */
213
+ /** If set, adds to global `checks`; matching ids override the global check. */
214
214
  checks?: CustomCheckConfig[];
215
215
  /** If set, overrides global versioningStrategy for this scope. */
216
216
  versioningStrategy?: 'lockstep' | 'independent' | 'adaptive';
@@ -336,7 +336,7 @@ interface PipelineOptions {
336
336
  scopeCwd: string;
337
337
  /** Original scope name for display/reporting only */
338
338
  scope?: string;
339
- /** Named flow — selects a release config profile. Completely replaces global packages/versioning/checks. */
339
+ /** Named flow — selects a release config profile. Packages/versioning replace global values; checks are additive. */
340
340
  flow?: string;
341
341
  config: ReleaseConfig;
342
342
  dryRun?: boolean;
@@ -376,7 +376,7 @@ interface PlannerOptions {
376
376
  cwd: string;
377
377
  config: ReleaseConfig;
378
378
  scope?: string;
379
- /** Named flow — selects a release config profile. Completely replaces global packages/versioning/checks. */
379
+ /** Named flow — selects a release config profile. Packages/versioning replace global values; checks are additive. */
380
380
  flow?: string;
381
381
  bumpOverride?: VersionBump;
382
382
  /** Release track. Defaults to 'stable'. See ReleaseChannel. */
@@ -384,7 +384,9 @@ interface PlannerOptions {
384
384
  }
385
385
  /**
386
386
  * Merge base config with a named flow's config.
387
- * Flow completely REPLACES packages, versioningStrategy, and checks no array merging.
387
+ * Flow replaces packages/versioningStrategy and adds to checks. Matching check
388
+ * ids override the global definition so a flow can make one gate stricter
389
+ * without accidentally removing the global release contract.
388
390
  * All other config fields (registry, publish, rollback, git, changelog) remain from global.
389
391
  */
390
392
  declare function mergeConfigWithFlow(config: ReleaseConfig, flowName: string): ReleaseConfig;
@@ -719,6 +721,23 @@ declare function verifyPackage(packagePath: string, packageName?: string): Verif
719
721
  * "verify before publish" and "verify after publish" use identical checks.
720
722
  */
721
723
  declare function verifyExtractedTarball(extractedDir: string): string[];
724
+ /**
725
+ * These protocols are workspace-only and cannot be consumed from a public
726
+ * registry. Checking the extracted package (rather than the source manifest)
727
+ * makes this a release-artifact contract and catches the exact bytes a user
728
+ * would install.
729
+ */
730
+ declare function findForbiddenDependencyProtocols(pkg: PkgJson): string[];
731
+ type PkgJson = Record<string, unknown> & {
732
+ exports?: Record<string, Record<string, string> | string>;
733
+ module?: string;
734
+ main?: string;
735
+ types?: string;
736
+ dependencies?: Record<string, string>;
737
+ optionalDependencies?: Record<string, string>;
738
+ peerDependencies?: Record<string, string>;
739
+ devDependencies?: Record<string, string>;
740
+ };
722
741
 
723
742
  /**
724
743
  * Registry-side artifact verification — confirms a package actually landed
@@ -753,6 +772,31 @@ interface VerifyAgainstRegistryOptions {
753
772
  */
754
773
  declare function verifyAgainstRegistry(packages: PublishablePackage[], options: VerifyAgainstRegistryOptions): Promise<VerifyResult[]>;
755
774
 
775
+ /**
776
+ * Verify a packed tarball actually installs — and imports — in a clean,
777
+ * outside-the-workspace consumer. This is the strongest guarantee available:
778
+ * it catches everything the static findForbiddenDependencyProtocols() check
779
+ * can (a literal workspace:/link:/file: protocol) PLUS classes that check
780
+ * can't, most importantly an already-published PEER dependency that is
781
+ * itself broken (npm auto-installs peers, so a bad manifest several levels
782
+ * deep in someone else's graph fails this package's install too).
783
+ *
784
+ * The package manager is configurable. pnpm is the platform default and is
785
+ * used for the monorepo release flow; npm remains available for projects that
786
+ * explicitly publish with npm.
787
+ */
788
+ interface CleanInstallResult {
789
+ ok: boolean;
790
+ /** Human-readable reason, always populated when ok is false. */
791
+ error?: string;
792
+ }
793
+ /**
794
+ * Install `tarballPath` into a throwaway consumer project (outside the
795
+ * monorepo workspace, so pnpm's workspace resolution can't mask a problem)
796
+ * and confirm `packageName` can actually be imported afterward.
797
+ */
798
+ declare function verifyCleanInstall(tarballPath: string, packageName: string, additionalTarballs?: string[], packageManager?: 'pnpm' | 'npm'): Promise<CleanInstallResult>;
799
+
756
800
  /**
757
801
  * Scope utilities — resolve scope name to filesystem path.
758
802
  *
@@ -777,4 +821,4 @@ declare function verifyAgainstRegistry(packages: PublishablePackage[], options:
777
821
  */
778
822
  declare function resolveScopePath(repoRoot: string, scope: string): Promise<string>;
779
823
 
780
- export { type AuditSummary, type BuildConfig, 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 };
824
+ export { type AuditSummary, type BuildConfig, type BuildResult, type ChangelogGenerator, type CheckId, type CheckResult, type CheckResultDetails, type CleanInstallResult, 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, findForbiddenDependencyProtocols, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolveFlowFromTag, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyCleanInstall, verifyExtractedTarball, verifyPackage, verifyPackages };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile, mkdir, rm, rename, cp } from 'fs/promises';
2
- import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, rmSync, statSync, unlinkSync } from 'fs';
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, rmSync, statSync, mkdtempSync, unlinkSync } from 'fs';
3
3
  import { join, relative, resolve, dirname } from 'path';
4
4
  import simpleGit from 'simple-git';
5
5
  import semver2 from 'semver';
@@ -24,6 +24,14 @@ function applyLockstep(packages) {
24
24
  if (packages.length === 0) {
25
25
  return packages;
26
26
  }
27
+ const alreadyPublished = packages.filter((p) => p.isPublished);
28
+ if (alreadyPublished.length > 0) {
29
+ const sharedVersion = alreadyPublished.reduce(
30
+ (max, p) => semver2.gt(p.nextVersion, max) ? p.nextVersion : max,
31
+ alreadyPublished[0].nextVersion
32
+ );
33
+ return packages.map((pkg) => ({ ...pkg, nextVersion: sharedVersion }));
34
+ }
27
35
  const maxBump = getMaxBump(packages);
28
36
  const maxVersion = packages.reduce((max, pkg) => {
29
37
  return semver2.gt(pkg.currentVersion, max) ? pkg.currentVersion : max;
@@ -65,9 +73,41 @@ function getMaxBump(packages) {
65
73
  return maxBump;
66
74
  }
67
75
 
76
+ // src/tag.ts
77
+ var DEFAULT_TAG_PATTERN = "{flow}-v{version}";
78
+ function escapeRegex(str) {
79
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
80
+ }
81
+ var SEMVER_SRC = "\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?";
82
+ function buildReleaseTag(flowName, version, tagPattern) {
83
+ const pattern = tagPattern ?? DEFAULT_TAG_PATTERN;
84
+ return pattern.replace("{flow}", flowName).replace("{version}", version);
85
+ }
86
+ function buildTagRegex(flowName, tagPattern) {
87
+ const escapedPattern = escapeRegex(tagPattern);
88
+ const src = escapedPattern.replace(escapeRegex("{flow}"), escapeRegex(flowName)).replace(escapeRegex("{version}"), `(${SEMVER_SRC})`);
89
+ return new RegExp(`^${src}$`);
90
+ }
91
+ function resolveFlowFromTag(config, tag) {
92
+ const flows = config.flows ?? {};
93
+ for (const [flowName, flowConfig] of Object.entries(flows)) {
94
+ const regex = buildTagRegex(flowName, flowConfig.tagPattern ?? DEFAULT_TAG_PATTERN);
95
+ if (regex.test(tag)) {
96
+ return { flowName, channel: "stable" };
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+
68
102
  // src/planner.ts
69
- async function findLastReleaseTag(git, pkgName) {
103
+ async function findLastReleaseTag(git, pkgName, flowTagGlob) {
70
104
  try {
105
+ if (flowTagGlob) {
106
+ const flowTags = await git.tags(["--sort=-version:refname", "--list", flowTagGlob]);
107
+ if (flowTags.all.length > 0) {
108
+ return flowTags.all[0];
109
+ }
110
+ }
71
111
  const tags = await git.tags(["--sort=-version:refname", `--list`, `${pkgName}@*`]);
72
112
  if (tags.all.length > 0) {
73
113
  return tags.all[0];
@@ -81,8 +121,8 @@ async function findLastReleaseTag(git, pkgName) {
81
121
  return null;
82
122
  }
83
123
  }
84
- async function getCommitsSinceTag(git, pkgName, relPath) {
85
- const lastTag = await findLastReleaseTag(git, pkgName);
124
+ async function getCommitsSinceTag(git, pkgName, relPath, flowTagGlob) {
125
+ const lastTag = await findLastReleaseTag(git, pkgName, flowTagGlob);
86
126
  try {
87
127
  const args = ["log", "--format=%s", ...lastTag ? [`${lastTag}..HEAD`] : [], "--", relPath];
88
128
  const raw = await git.raw(args);
@@ -100,7 +140,9 @@ function mergeConfigWithFlow(config, flowName) {
100
140
  ...config,
101
141
  ...flow.packages !== void 0 && { packages: flow.packages },
102
142
  ...flow.versioningStrategy && { versioningStrategy: flow.versioningStrategy },
103
- ...flow.checks !== void 0 && { checks: flow.checks },
143
+ ...flow.checks !== void 0 && {
144
+ checks: [...new Map([...config.checks ?? [], ...flow.checks].map((check) => [check.id, check])).values()]
145
+ },
104
146
  ...flow.build !== void 0 && { build: flow.build }
105
147
  };
106
148
  }
@@ -139,6 +181,7 @@ async function discoverCurrentPackages(cwd, scope, config) {
139
181
  async function planRelease(options) {
140
182
  const { cwd, scope, bumpOverride, channel = "stable" } = options;
141
183
  const config = options.flow ? mergeConfigWithFlow(options.config, options.flow) : options.config;
184
+ const flowTagGlob = options.flow ? buildReleaseTag(options.flow, "*", config.flows?.[options.flow]?.tagPattern) : void 0;
142
185
  const packages = await discoverCurrentPackages(cwd, scope, config);
143
186
  const isWorkspaceRoot = existsSync(join(cwd, ".gitmodules")) && !scope;
144
187
  let modifiedPackages;
@@ -148,7 +191,7 @@ async function planRelease(options) {
148
191
  modifiedPackages = packages;
149
192
  } else {
150
193
  const git = simpleGit(cwd, { timeout: { block: 6e4 } });
151
- modifiedPackages = await detectModifiedPackages(git, packages, cwd);
194
+ modifiedPackages = await detectModifiedPackages(git, packages, cwd, flowTagGlob);
152
195
  }
153
196
  const registry = config.registry ?? "https://registry.npmjs.org";
154
197
  let planPackages = [];
@@ -188,7 +231,8 @@ async function planRelease(options) {
188
231
  bump,
189
232
  git,
190
233
  gitCwd,
191
- pkg.name
234
+ pkg.name,
235
+ flowTagGlob
192
236
  );
193
237
  planPackages.push({
194
238
  ...pkg,
@@ -373,7 +417,7 @@ async function discoverSubRepoPackages(workspaceRoot, config) {
373
417
  }
374
418
  return packages;
375
419
  }
376
- async function detectModifiedPackages(git, packages, cwd) {
420
+ async function detectModifiedPackages(git, packages, cwd, flowTagGlob) {
377
421
  const status = await git.status();
378
422
  const changedPaths = status.files.map((file) => file.path);
379
423
  return (await Promise.all(packages.map(async (pkg) => {
@@ -382,21 +426,21 @@ async function detectModifiedPackages(git, packages, cwd) {
382
426
  if (hasUncommitted) {
383
427
  return pkg;
384
428
  }
385
- const commits = await getCommitsSinceTag(git, pkg.name, pkgRel);
429
+ const commits = await getCommitsSinceTag(git, pkg.name, pkgRel, flowTagGlob);
386
430
  return commits.length > 0 ? pkg : null;
387
431
  }))).filter((pkg) => pkg !== null);
388
432
  }
389
- async function computeNextVersion(packagePath, currentVersion, bump, git, gitCwd, pkgName) {
433
+ async function computeNextVersion(packagePath, currentVersion, bump, git, gitCwd, pkgName, flowTagGlob) {
390
434
  if (bump === "auto") {
391
- const detectedBump = await detectVersionFromCommits(git, packagePath, gitCwd, pkgName);
435
+ const detectedBump = await detectVersionFromCommits(git, packagePath, gitCwd, pkgName, flowTagGlob);
392
436
  return semver2.inc(currentVersion, detectedBump) || currentVersion;
393
437
  }
394
438
  return semver2.inc(currentVersion, bump) || currentVersion;
395
439
  }
396
- async function detectVersionFromCommits(git, packagePath, gitCwd, pkgName) {
440
+ async function detectVersionFromCommits(git, packagePath, gitCwd, pkgName, flowTagGlob) {
397
441
  try {
398
442
  const relPath = relative(resolve(gitCwd), resolve(packagePath));
399
- const messages = await getCommitsSinceTag(git, pkgName, relPath);
443
+ const messages = await getCommitsSinceTag(git, pkgName, relPath, flowTagGlob);
400
444
  let hasMinor = false;
401
445
  let hasBreaking = false;
402
446
  for (const message of messages) {
@@ -494,32 +538,6 @@ function isCheckpointResumable(checkpoint, flow, version) {
494
538
  return checkpoint.publishedPackages.length > 0 && Object.values(checkpoint.gitRoots).some((s) => !s.pushed);
495
539
  }
496
540
 
497
- // src/tag.ts
498
- var DEFAULT_TAG_PATTERN = "{flow}-v{version}";
499
- function escapeRegex(str) {
500
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
501
- }
502
- var SEMVER_SRC = "\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?";
503
- function buildReleaseTag(flowName, version, tagPattern) {
504
- const pattern = tagPattern ?? DEFAULT_TAG_PATTERN;
505
- return pattern.replace("{flow}", flowName).replace("{version}", version);
506
- }
507
- function buildTagRegex(flowName, tagPattern) {
508
- const escapedPattern = escapeRegex(tagPattern);
509
- const src = escapedPattern.replace(escapeRegex("{flow}"), escapeRegex(flowName)).replace(escapeRegex("{version}"), `(${SEMVER_SRC})`);
510
- return new RegExp(`^${src}$`);
511
- }
512
- function resolveFlowFromTag(config, tag) {
513
- const flows = config.flows ?? {};
514
- for (const [flowName, flowConfig] of Object.entries(flows)) {
515
- const regex = buildTagRegex(flowName, flowConfig.tagPattern ?? DEFAULT_TAG_PATTERN);
516
- if (regex.test(tag)) {
517
- return { flowName, channel: "stable" };
518
- }
519
- }
520
- return null;
521
- }
522
-
523
541
  // src/publisher.ts
524
542
  async function updatePackageVersion(pkg) {
525
543
  const packageJsonPath = join(pkg.path, "package.json");
@@ -659,6 +677,22 @@ function createPackageChangelog(pkg, changelog) {
659
677
  }
660
678
  return changelog.substring(startIdx, endIdx).trim();
661
679
  }
680
+ async function assertTagVersionsMatchDisk(pkgs) {
681
+ const mismatches = [];
682
+ for (const pkg of pkgs) {
683
+ const packageJsonPath = join(pkg.path, "package.json");
684
+ const onDiskVersion = JSON.parse(await readFile(packageJsonPath, "utf-8")).version;
685
+ if (onDiskVersion !== pkg.nextVersion) {
686
+ mismatches.push(`${pkg.name}: tag would say ${pkg.nextVersion}, package.json says ${onDiskVersion ?? "<missing>"}`);
687
+ }
688
+ }
689
+ if (mismatches.length > 0) {
690
+ throw new Error(
691
+ `Refusing to create a git tag that doesn't match the committed package.json version(s):
692
+ ${mismatches.join("\n ")}`
693
+ );
694
+ }
695
+ }
662
696
  async function commitAndTagRelease(options) {
663
697
  const { cwd, plan, dryRun, noVerify = false, repoRoot, checkpointGitRoots, changelogOutputPath, flowName = "release", tagPattern } = options;
664
698
  const simpleGit2 = (await import('simple-git')).default;
@@ -729,6 +763,7 @@ async function commitAndTagRelease(options) {
729
763
  result.committed = true;
730
764
  }
731
765
  if (rootTagged.length === 0) {
766
+ await assertTagVersionsMatchDisk(pkgs);
732
767
  if (singleVersionAcrossPlan) {
733
768
  const tagName = buildReleaseTag(flowName, plan.packages[0].nextVersion, tagPattern);
734
769
  await rootGit.addTag(tagName);
@@ -1334,6 +1369,7 @@ function verifyExtractedTarball(extractedDir) {
1334
1369
  issues.push(`Test files in dist/: ${testFiles.slice(0, 3).join(", ")}`);
1335
1370
  }
1336
1371
  const extractedPkg = JSON.parse(readFileSync(join(extractedDir, "package.json"), "utf-8"));
1372
+ issues.push(...findForbiddenDependencyProtocols(extractedPkg));
1337
1373
  for (const field of ["main", "module", "types"]) {
1338
1374
  const val = extractedPkg[field];
1339
1375
  if (val && !existsSync(join(extractedDir, val))) {
@@ -1366,6 +1402,25 @@ function verifyExtractedTarball(extractedDir) {
1366
1402
  }
1367
1403
  return issues;
1368
1404
  }
1405
+ function findForbiddenDependencyProtocols(pkg) {
1406
+ const issues = [];
1407
+ for (const section of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"]) {
1408
+ const deps = pkg[section];
1409
+ if (!deps || typeof deps !== "object") {
1410
+ continue;
1411
+ }
1412
+ for (const [name, value] of Object.entries(deps)) {
1413
+ if (typeof value !== "string") {
1414
+ continue;
1415
+ }
1416
+ const protocol = ["workspace:", "link:", "file:"].find((prefix) => value.startsWith(prefix));
1417
+ if (protocol) {
1418
+ issues.push(`${section}.${name} uses forbidden ${protocol} dependency protocol (${value})`);
1419
+ }
1420
+ }
1421
+ }
1422
+ return issues;
1423
+ }
1369
1424
  function resolveEsmEntry(pkg) {
1370
1425
  const dotExport = pkg.exports?.["."];
1371
1426
  const importEntry = dotExport && typeof dotExport === "object" ? dotExport["import"] : void 0;
@@ -1933,6 +1988,84 @@ function buildReport(stage, plan, repoRoot, dryRun, startTime, result) {
1933
1988
  result: { ...result, timingMs: result.timingMs ?? Date.now() - startTime }
1934
1989
  };
1935
1990
  }
1991
+ async function verifyCleanInstall(tarballPath, packageName, additionalTarballs = [], packageManager = "npm") {
1992
+ const consumerDir = mkdtempSync(join(tmpdir(), "kb-clean-install-"));
1993
+ try {
1994
+ if (packageManager === "pnpm") {
1995
+ const stagedTarballs = [tarballPath, ...additionalTarballs];
1996
+ const targetName = readPackedPackageName(tarballPath);
1997
+ if (!targetName) {
1998
+ return { ok: false, error: `install failed: cannot read package name from ${tarballPath}` };
1999
+ }
2000
+ const dependencies = { [targetName]: `file:${tarballPath}` };
2001
+ const overrides = {};
2002
+ for (const stagedTarball of stagedTarballs) {
2003
+ const name = readPackedPackageName(stagedTarball);
2004
+ if (!name) {
2005
+ return { ok: false, error: `install failed: cannot read package name from ${stagedTarball}` };
2006
+ }
2007
+ overrides[name] = `file:${stagedTarball}`;
2008
+ }
2009
+ writeFileSync(
2010
+ join(consumerDir, "package.json"),
2011
+ JSON.stringify({ name: "kb-release-consumer", private: true, dependencies, pnpm: { overrides } }, null, 2) + "\n"
2012
+ );
2013
+ const install = spawnSync(
2014
+ "pnpm",
2015
+ ["install", "--ignore-scripts", "--no-lockfile", "--config.auto-install-peers=true"],
2016
+ { cwd: consumerDir, stdio: "pipe", timeout: 12e4 }
2017
+ );
2018
+ if (install.status !== 0) {
2019
+ return { ok: false, error: `install failed: ${describeProcessFailure(install)}` };
2020
+ }
2021
+ } else {
2022
+ writeFileSync(join(consumerDir, "package.json"), JSON.stringify({ name: "kb-release-consumer", private: true }) + "\n");
2023
+ const { Arborist } = await import('@npmcli/arborist');
2024
+ const arb = new Arborist({ path: consumerDir, ignoreScripts: true });
2025
+ try {
2026
+ await arb.reify({ add: [tarballPath, ...additionalTarballs], save: false });
2027
+ } catch (err) {
2028
+ return { ok: false, error: `install failed: ${describeArboristError(err)}` };
2029
+ }
2030
+ }
2031
+ const importCheck = spawnSync(
2032
+ "node",
2033
+ ["--input-type=module", "-e", "await import(process.argv[1])", packageName],
2034
+ { cwd: consumerDir, stdio: "pipe", timeout: 15e3 }
2035
+ );
2036
+ if (importCheck.status !== 0) {
2037
+ const stderr = importCheck.stderr?.toString().trim();
2038
+ return { ok: false, error: `clean consumer cannot import ${packageName}${stderr ? `: ${stderr}` : ""}` };
2039
+ }
2040
+ return { ok: true };
2041
+ } finally {
2042
+ rmSync(consumerDir, { recursive: true, force: true });
2043
+ }
2044
+ }
2045
+ function readPackedPackageName(tarballPath) {
2046
+ const result = spawnSync("tar", ["xOf", tarballPath, "package/package.json"], { stdio: "pipe" });
2047
+ if (result.status !== 0) {
2048
+ return void 0;
2049
+ }
2050
+ try {
2051
+ const manifest = JSON.parse(result.stdout.toString());
2052
+ return typeof manifest.name === "string" ? manifest.name : void 0;
2053
+ } catch {
2054
+ return void 0;
2055
+ }
2056
+ }
2057
+ function describeArboristError(err) {
2058
+ if (err instanceof Error) {
2059
+ const code = err.code;
2060
+ return code ? `[${code}] ${err.message}` : err.message;
2061
+ }
2062
+ return String(err);
2063
+ }
2064
+ function describeProcessFailure(result) {
2065
+ const stderr = result.stderr?.toString().trim();
2066
+ const stdout = result.stdout?.toString().trim();
2067
+ return stderr || stdout || `package manager exited with code ${result.status ?? "unknown"}`;
2068
+ }
1936
2069
  async function resolveScopePath(repoRoot, scope) {
1937
2070
  if (!scope || scope === "root") {
1938
2071
  return repoRoot;
@@ -1957,6 +2090,6 @@ async function resolveScopePath(repoRoot, scope) {
1957
2090
  return join(repoRoot, scope);
1958
2091
  }
1959
2092
 
1960
- 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 };
2093
+ export { DEFAULT_ROOT_CHANGELOG_PATH, DEFAULT_TAG_PATTERN, applyCanarySuffix, applyVersionStrategy, buildPackages, buildReleaseTag, commitAndTagRelease, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, findForbiddenDependencyProtocols, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolveFlowFromTag, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, spawnCommand, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyCleanInstall, verifyExtractedTarball, verifyPackage, verifyPackages };
1961
2094
  //# sourceMappingURL=index.js.map
1962
2095
  //# sourceMappingURL=index.js.map