@kb-labs/release-manager-core 2.119.0-canary.e120bc0ee → 2.119.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 +84 -3
- package/dist/index.js +232 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -124,6 +124,16 @@ interface CustomCheckConfig {
|
|
|
124
124
|
* If omitted, check runs in each package directory (original behaviour).
|
|
125
125
|
*/
|
|
126
126
|
runIn?: 'repoRoot' | 'scopePath' | 'perPackage';
|
|
127
|
+
/**
|
|
128
|
+
* Package name patterns (same matcher as PackagesFilter.exclude) skipped
|
|
129
|
+
* by this check only — unlike `packages.exclude`, the package still gets
|
|
130
|
+
* discovered, packed, versioned, and published normally by the rest of
|
|
131
|
+
* the release. Use this for a check whose assumption doesn't hold for a
|
|
132
|
+
* specific package (e.g. `pack-install`'s bare top-level import against a
|
|
133
|
+
* config-only or test-harness-only package), not as a substitute for
|
|
134
|
+
* excluding the package from the release itself.
|
|
135
|
+
*/
|
|
136
|
+
skipPackages?: string[];
|
|
127
137
|
}
|
|
128
138
|
interface ReleaseResult {
|
|
129
139
|
ok: boolean;
|
|
@@ -664,6 +674,60 @@ declare function resolveFlowFromTag(config: ReleaseConfig, tag: string): {
|
|
|
664
674
|
channel: ReleaseChannel;
|
|
665
675
|
} | null;
|
|
666
676
|
|
|
677
|
+
/**
|
|
678
|
+
* Release status — a read-only "what is actually true right now" view for a
|
|
679
|
+
* flow, cross-checking the surfaces that the 2026-08-13 release-system audit
|
|
680
|
+
* found drifting apart independently: the latest stable git tag, and the
|
|
681
|
+
* real npm dist-tags packages currently resolve to.
|
|
682
|
+
*
|
|
683
|
+
* This does not replace the receipt/state-machine model from the breaking
|
|
684
|
+
* control-plane cutover plan — it is the cheap, non-breaking piece of that
|
|
685
|
+
* diagnosis that can ship today: make "a tag/npm entry exists" and "this was
|
|
686
|
+
* actually verified and promoted" visibly different without any new storage.
|
|
687
|
+
*/
|
|
688
|
+
|
|
689
|
+
interface NpmDistTagInfo {
|
|
690
|
+
name: string;
|
|
691
|
+
stableVersion: string | null;
|
|
692
|
+
canaryVersion: string | null;
|
|
693
|
+
error?: string;
|
|
694
|
+
}
|
|
695
|
+
interface StableTagInfo {
|
|
696
|
+
tag: string | null;
|
|
697
|
+
version: string | null;
|
|
698
|
+
commit: string | null;
|
|
699
|
+
committedAt: string | null;
|
|
700
|
+
}
|
|
701
|
+
interface FlowReleaseStatus {
|
|
702
|
+
flow: string;
|
|
703
|
+
packages: string[];
|
|
704
|
+
git: StableTagInfo;
|
|
705
|
+
npm: {
|
|
706
|
+
registry: string;
|
|
707
|
+
stableDistTag: string;
|
|
708
|
+
canaryDistTag: string;
|
|
709
|
+
perPackage: NpmDistTagInfo[];
|
|
710
|
+
/** Agreed version across all sampled packages, or null if they disagree or none resolved. */
|
|
711
|
+
stableVersion: string | null;
|
|
712
|
+
canaryVersion: string | null;
|
|
713
|
+
stableDrift: boolean;
|
|
714
|
+
canaryDrift: boolean;
|
|
715
|
+
};
|
|
716
|
+
verdict: {
|
|
717
|
+
ok: boolean;
|
|
718
|
+
warnings: string[];
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
interface ComputeFlowReleaseStatusOptions {
|
|
722
|
+
cwd: string;
|
|
723
|
+
config: ReleaseConfig;
|
|
724
|
+
flow: string;
|
|
725
|
+
shell: ShellAPI;
|
|
726
|
+
/** How many of the flow's packages to sample against npm. Lockstep flows only need enough to detect drift. Default 3. */
|
|
727
|
+
maxPackagesToCheck?: number;
|
|
728
|
+
}
|
|
729
|
+
declare function computeFlowReleaseStatus(opts: ComputeFlowReleaseStatusOptions): Promise<FlowReleaseStatus>;
|
|
730
|
+
|
|
667
731
|
/**
|
|
668
732
|
* Unified release pipeline — single orchestrator for CLI and REST.
|
|
669
733
|
*
|
|
@@ -733,8 +797,11 @@ interface CheckRunnerOptions {
|
|
|
733
797
|
* the tarball and runs a real `npm install` of it into a throwaway consumer
|
|
734
798
|
* per package. At higher concurrency those installs contend for CPU/disk/npm
|
|
735
799
|
* registry and start blowing their own per-check timeout under load.
|
|
800
|
+
*
|
|
801
|
+
* Overridable via KB_RELEASE_CHECKS_CONCURRENCY for profiling/tuning without
|
|
802
|
+
* a source edit + rebuild; the default (2) is unchanged when unset.
|
|
736
803
|
*/
|
|
737
|
-
declare const CHECKS_CONCURRENCY
|
|
804
|
+
declare const CHECKS_CONCURRENCY: number;
|
|
738
805
|
/**
|
|
739
806
|
* Run all configured checks against packages.
|
|
740
807
|
* Handles: parser evaluation, script path resolution, perPackage/scopePath/repoRoot routing.
|
|
@@ -820,6 +887,13 @@ interface VerifyAgainstRegistryOptions {
|
|
|
820
887
|
* Default: 0 (single attempt, matches pre-existing Verdaccio behavior).
|
|
821
888
|
*/
|
|
822
889
|
retries?: number;
|
|
890
|
+
/**
|
|
891
|
+
* Total time to wait for npm metadata and tarballs to propagate after a
|
|
892
|
+
* publish. When provided this supersedes the legacy fixed retry count, so
|
|
893
|
+
* an idempotent re-run resumes verification of already-published tarballs
|
|
894
|
+
* instead of requiring a new publish or a workflow-owned sleep.
|
|
895
|
+
*/
|
|
896
|
+
visibilityDeadlineMs?: number;
|
|
823
897
|
retryDelaysMs?: readonly number[];
|
|
824
898
|
logger?: Pick<PluginLogger, 'info' | 'warn'>;
|
|
825
899
|
}
|
|
@@ -853,8 +927,15 @@ interface CleanInstallResult {
|
|
|
853
927
|
* Install `tarballPath` into a throwaway consumer project (outside the
|
|
854
928
|
* monorepo workspace, so pnpm's workspace resolution can't mask a problem)
|
|
855
929
|
* and confirm `packageName` can actually be imported afterward.
|
|
930
|
+
*
|
|
931
|
+
* `registry`, when set, overrides where the consumer resolves ALL
|
|
932
|
+
* dependencies from (not just `packageName` itself) — used by the
|
|
933
|
+
* `pack-install` release gate to point at a local staging registry that
|
|
934
|
+
* carries the release plan's not-yet-published internal sibling versions.
|
|
935
|
+
* Third-party dependencies still resolve correctly through that registry's
|
|
936
|
+
* uplink to the real npm registry.
|
|
856
937
|
*/
|
|
857
|
-
declare function verifyCleanInstall(tarballPath: string, packageName: string, additionalTarballs?: string[], packageManager?: 'pnpm' | 'npm'): Promise<CleanInstallResult>;
|
|
938
|
+
declare function verifyCleanInstall(tarballPath: string, packageName: string, additionalTarballs?: string[], packageManager?: 'pnpm' | 'npm', registry?: string): Promise<CleanInstallResult>;
|
|
858
939
|
|
|
859
940
|
/**
|
|
860
941
|
* Scope utilities — resolve scope name to filesystem path.
|
|
@@ -880,4 +961,4 @@ declare function verifyCleanInstall(tarballPath: string, packageName: string, ad
|
|
|
880
961
|
*/
|
|
881
962
|
declare function resolveScopePath(repoRoot: string, scope: string): Promise<string>;
|
|
882
963
|
|
|
883
|
-
export { type AuditSummary, type BuildConfig, type BuildResult, CHECKS_CONCURRENCY, 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 ReleaseShell, 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, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyCleanInstall, verifyExtractedTarball, verifyPackage, verifyPackages };
|
|
964
|
+
export { type AuditSummary, type BuildConfig, type BuildResult, CHECKS_CONCURRENCY, type ChangelogGenerator, type CheckId, type CheckResult, type CheckResultDetails, type CleanInstallResult, type ComputeFlowReleaseStatusOptions, type CustomCheckConfig, DEFAULT_ROOT_CHANGELOG_PATH, DEFAULT_TAG_PATTERN, type FlowConfig, type FlowReleaseStatus, type NpmDistTagInfo, 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 ReleaseShell, type ReleaseStage, type RollbackSnapshot, type StableTagInfo, type StrategyOptions, type VerifyResult, type VersionBump, type VersionStrategy, applyCanarySuffix, applyVersionStrategy, buildPackages, buildReleaseTag, commitAndTagRelease, computeFlowReleaseStatus, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, findForbiddenDependencyProtocols, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolveFlowFromTag, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyCleanInstall, verifyExtractedTarball, verifyPackage, verifyPackages };
|
package/dist/index.js
CHANGED
|
@@ -1050,6 +1050,133 @@ function resolvePublishRegistry(config, channel) {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
return config.registry ?? DEFAULT_NPM_REGISTRY;
|
|
1052
1052
|
}
|
|
1053
|
+
|
|
1054
|
+
// src/status.ts
|
|
1055
|
+
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
|
|
1056
|
+
function compareSemver(a, b) {
|
|
1057
|
+
const ma = a.match(SEMVER_RE);
|
|
1058
|
+
const mb = b.match(SEMVER_RE);
|
|
1059
|
+
if (!ma || !mb) {
|
|
1060
|
+
return 0;
|
|
1061
|
+
}
|
|
1062
|
+
for (let i = 1; i <= 3; i++) {
|
|
1063
|
+
const diff = Number(ma[i]) - Number(mb[i]);
|
|
1064
|
+
if (diff !== 0) {
|
|
1065
|
+
return diff;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return 0;
|
|
1069
|
+
}
|
|
1070
|
+
function escapeRegex2(str) {
|
|
1071
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1072
|
+
}
|
|
1073
|
+
async function fetchDistTags(name, registry) {
|
|
1074
|
+
try {
|
|
1075
|
+
const encoded = name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : name;
|
|
1076
|
+
const url = `${registry.replace(/\/$/, "")}/${encoded}`;
|
|
1077
|
+
const res = await fetch(url, {
|
|
1078
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
1079
|
+
signal: AbortSignal.timeout(8e3)
|
|
1080
|
+
});
|
|
1081
|
+
if (!res.ok) {
|
|
1082
|
+
return { error: `HTTP ${res.status}` };
|
|
1083
|
+
}
|
|
1084
|
+
const body = await res.json();
|
|
1085
|
+
return body["dist-tags"] ?? {};
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
async function findLatestStableTag(shell, cwd, flowName, tagPattern) {
|
|
1091
|
+
const empty = { tag: null, version: null, commit: null, committedAt: null };
|
|
1092
|
+
const prefix = tagPattern.split("{version}")[0]?.replace("{flow}", flowName) ?? `${flowName}-v`;
|
|
1093
|
+
const listRes = await shell.exec("git", ["tag", "-l", `${prefix}*`], { cwd });
|
|
1094
|
+
const tags = listRes.stdout.split("\n").map((t) => t.trim()).filter(Boolean);
|
|
1095
|
+
if (tags.length === 0) {
|
|
1096
|
+
return empty;
|
|
1097
|
+
}
|
|
1098
|
+
const versionRe = new RegExp(`^${escapeRegex2(prefix)}(\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?)$`);
|
|
1099
|
+
const parsed = tags.map((t) => ({ tag: t, version: t.match(versionRe)?.[1] })).filter((t) => Boolean(t.version));
|
|
1100
|
+
if (parsed.length === 0) {
|
|
1101
|
+
return empty;
|
|
1102
|
+
}
|
|
1103
|
+
parsed.sort((a, b) => compareSemver(a.version, b.version));
|
|
1104
|
+
const latest = parsed[parsed.length - 1];
|
|
1105
|
+
const [shaRes, dateRes] = await Promise.all([
|
|
1106
|
+
shell.exec("git", ["rev-list", "-n", "1", latest.tag], { cwd }),
|
|
1107
|
+
shell.exec("git", ["log", "-1", "--format=%cI", latest.tag], { cwd })
|
|
1108
|
+
]);
|
|
1109
|
+
return {
|
|
1110
|
+
tag: latest.tag,
|
|
1111
|
+
version: latest.version,
|
|
1112
|
+
commit: shaRes.stdout.trim() || null,
|
|
1113
|
+
committedAt: dateRes.stdout.trim() || null
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
async function computeFlowReleaseStatus(opts) {
|
|
1117
|
+
const { cwd, config, flow, shell, maxPackagesToCheck = 3 } = opts;
|
|
1118
|
+
const flowConfig = mergeConfigWithFlow(config, flow);
|
|
1119
|
+
const tagPattern = config.flows?.[flow]?.tagPattern ?? DEFAULT_TAG_PATTERN;
|
|
1120
|
+
const packages = await discoverCurrentPackages(cwd, void 0, flowConfig);
|
|
1121
|
+
const registry = resolvePublishRegistry(config, "canary");
|
|
1122
|
+
const stableDistTag = resolvePublishTag(config, "stable");
|
|
1123
|
+
const canaryDistTag = resolvePublishTag(config, "canary");
|
|
1124
|
+
const sampled = packages.slice(0, maxPackagesToCheck);
|
|
1125
|
+
const perPackage = [];
|
|
1126
|
+
for (const pkg of sampled) {
|
|
1127
|
+
const result = await fetchDistTags(pkg.name, registry);
|
|
1128
|
+
if ("error" in result) {
|
|
1129
|
+
perPackage.push({ name: pkg.name, stableVersion: null, canaryVersion: null, error: result.error });
|
|
1130
|
+
} else {
|
|
1131
|
+
perPackage.push({
|
|
1132
|
+
name: pkg.name,
|
|
1133
|
+
stableVersion: result[stableDistTag] ?? null,
|
|
1134
|
+
canaryVersion: result[canaryDistTag] ?? null
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const stableVersions = new Set(perPackage.map((p) => p.stableVersion).filter((v) => Boolean(v)));
|
|
1139
|
+
const canaryVersions = new Set(perPackage.map((p) => p.canaryVersion).filter((v) => Boolean(v)));
|
|
1140
|
+
const git = await findLatestStableTag(shell, cwd, flow, tagPattern);
|
|
1141
|
+
const warnings = [];
|
|
1142
|
+
if (stableVersions.size > 1) {
|
|
1143
|
+
warnings.push(`Packages disagree on npm "${stableDistTag}" version: ${[...stableVersions].join(", ")}`);
|
|
1144
|
+
}
|
|
1145
|
+
if (canaryVersions.size > 1) {
|
|
1146
|
+
warnings.push(`Packages disagree on npm "${canaryDistTag}" version: ${[...canaryVersions].join(", ")}`);
|
|
1147
|
+
}
|
|
1148
|
+
const stableVersion = stableVersions.size === 1 ? [...stableVersions][0] : null;
|
|
1149
|
+
const canaryVersion = canaryVersions.size === 1 ? [...canaryVersions][0] : null;
|
|
1150
|
+
if (git.version && stableVersion && git.version !== stableVersion) {
|
|
1151
|
+
warnings.push(
|
|
1152
|
+
`Latest git tag ${git.tag} (${git.version}) does not match npm "${stableDistTag}" (${stableVersion})`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
if (stableVersion && canaryVersion && compareSemver(canaryVersion, stableVersion) > 0) {
|
|
1156
|
+
warnings.push(
|
|
1157
|
+
`npm "${canaryDistTag}" (${canaryVersion}) is ahead of "${stableDistTag}" (${stableVersion}) \u2014 treat as an unverified candidate, not a release, until it has a green delivery + smoke run and has been explicitly promoted`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
flow,
|
|
1162
|
+
packages: packages.map((p) => p.name),
|
|
1163
|
+
git,
|
|
1164
|
+
npm: {
|
|
1165
|
+
registry,
|
|
1166
|
+
stableDistTag,
|
|
1167
|
+
canaryDistTag,
|
|
1168
|
+
perPackage,
|
|
1169
|
+
stableVersion,
|
|
1170
|
+
canaryVersion,
|
|
1171
|
+
stableDrift: stableVersions.size > 1,
|
|
1172
|
+
canaryDrift: canaryVersions.size > 1
|
|
1173
|
+
},
|
|
1174
|
+
verdict: {
|
|
1175
|
+
ok: warnings.length === 0,
|
|
1176
|
+
warnings
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1053
1180
|
function buildIntraDeps(packages, nameSet) {
|
|
1054
1181
|
const deps = /* @__PURE__ */ new Map();
|
|
1055
1182
|
for (const pkg of packages) {
|
|
@@ -1198,7 +1325,7 @@ async function executeCommand(shell, command, args, cwd, timeoutMs, env) {
|
|
|
1198
1325
|
exitCode
|
|
1199
1326
|
};
|
|
1200
1327
|
}
|
|
1201
|
-
var CHECKS_CONCURRENCY = 2;
|
|
1328
|
+
var CHECKS_CONCURRENCY = Number(process.env.KB_RELEASE_CHECKS_CONCURRENCY) || 2;
|
|
1202
1329
|
async function runReleaseChecks(checks, options) {
|
|
1203
1330
|
const results = [];
|
|
1204
1331
|
for (const check of checks) {
|
|
@@ -1221,33 +1348,72 @@ async function runSingleCheck(check, options) {
|
|
|
1221
1348
|
} else {
|
|
1222
1349
|
pathsToRun = options.packagePaths.length > 0 ? options.packagePaths : [options.repoRoot];
|
|
1223
1350
|
}
|
|
1351
|
+
if (runIn === "perPackage" && check.skipPackages?.length) {
|
|
1352
|
+
const skipPatterns = check.skipPackages;
|
|
1353
|
+
pathsToRun = pathsToRun.filter((pkgPath) => {
|
|
1354
|
+
const name = readPackageName(pkgPath);
|
|
1355
|
+
const skip = name != null && matchesPackagePattern(name, pkgPath, skipPatterns);
|
|
1356
|
+
if (skip) {
|
|
1357
|
+
options.logger?.info?.(`Check ${check.id}: skipping ${name} (matches skipPackages)`);
|
|
1358
|
+
}
|
|
1359
|
+
return !skip;
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1224
1362
|
const resolvedArgs = (check.args ?? []).map(
|
|
1225
1363
|
(arg) => arg.match(/\.(sh|js|ts|mjs|cjs)$/) ? join(options.repoRoot, arg) : arg
|
|
1226
1364
|
);
|
|
1227
1365
|
const timeoutMs = check.timeoutMs ?? 12e4;
|
|
1228
|
-
async function runForPath(pkgPath) {
|
|
1366
|
+
async function runForPath(pkgPath, attempt = 1) {
|
|
1229
1367
|
const startedAt = Date.now();
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1368
|
+
try {
|
|
1369
|
+
const result = await options.shell.exec(check.command, resolvedArgs, { cwd: pkgPath, timeout: timeoutMs });
|
|
1370
|
+
const ok = evaluateParser(check, result.stdout, result.stderr, result.code);
|
|
1371
|
+
return {
|
|
1372
|
+
path: pkgPath,
|
|
1373
|
+
ok,
|
|
1374
|
+
durationMs: Date.now() - startedAt,
|
|
1375
|
+
details: {
|
|
1376
|
+
packagePath: pkgPath,
|
|
1377
|
+
stdout: result.stdout || void 0,
|
|
1378
|
+
stderr: result.stderr || void 0,
|
|
1379
|
+
exitCode: result.code,
|
|
1380
|
+
error: !ok ? `exit code ${result.code}` : void 0
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
} catch (error) {
|
|
1384
|
+
if (attempt === 1 && isTimeoutError(error)) {
|
|
1385
|
+
const partial2 = partialResultOf(error);
|
|
1386
|
+
options.logger?.warn?.(
|
|
1387
|
+
`Check ${check.id}: ${pkgPath} timed out, retrying once` + (partial2?.stdout || partial2?.stderr ? ` (captured ${partial2.stdout?.length ?? 0}B stdout / ${partial2.stderr?.length ?? 0}B stderr before kill)` : "")
|
|
1388
|
+
);
|
|
1389
|
+
return runForPath(pkgPath, attempt + 1);
|
|
1242
1390
|
}
|
|
1243
|
-
|
|
1391
|
+
const partial = partialResultOf(error);
|
|
1392
|
+
return {
|
|
1393
|
+
path: pkgPath,
|
|
1394
|
+
ok: false,
|
|
1395
|
+
durationMs: Date.now() - startedAt,
|
|
1396
|
+
details: {
|
|
1397
|
+
packagePath: pkgPath,
|
|
1398
|
+
stdout: partial?.stdout || void 0,
|
|
1399
|
+
stderr: partial?.stderr || void 0,
|
|
1400
|
+
exitCode: partial?.exitCode,
|
|
1401
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1244
1405
|
}
|
|
1245
1406
|
let pkgResults;
|
|
1246
|
-
if (
|
|
1407
|
+
if (pathsToRun.length === 0) {
|
|
1408
|
+
pkgResults = [];
|
|
1409
|
+
} else if (runIn === "perPackage" && pathsToRun.length > 1) {
|
|
1247
1410
|
pkgResults = [];
|
|
1248
1411
|
for (let i = 0; i < pathsToRun.length; i += CHECKS_CONCURRENCY) {
|
|
1249
1412
|
const batch = pathsToRun.slice(i, i + CHECKS_CONCURRENCY);
|
|
1250
1413
|
pkgResults.push(...await Promise.all(batch.map(runForPath)));
|
|
1414
|
+
options.logger?.info?.(
|
|
1415
|
+
`Check ${check.id}: ${Math.min(i + CHECKS_CONCURRENCY, pathsToRun.length)}/${pathsToRun.length} packages checked`
|
|
1416
|
+
);
|
|
1251
1417
|
}
|
|
1252
1418
|
} else {
|
|
1253
1419
|
pkgResults = [await runForPath(pathsToRun[0])];
|
|
@@ -1266,6 +1432,36 @@ async function runSingleCheck(check, options) {
|
|
|
1266
1432
|
packages: perPackage && perPackage.length > 0 ? perPackage : void 0
|
|
1267
1433
|
};
|
|
1268
1434
|
}
|
|
1435
|
+
function isTimeoutError(error) {
|
|
1436
|
+
return typeof error === "object" && error !== null && error.code === "PROCESS_TIMEOUT";
|
|
1437
|
+
}
|
|
1438
|
+
function partialResultOf(error) {
|
|
1439
|
+
if (typeof error !== "object" || error === null) {
|
|
1440
|
+
return void 0;
|
|
1441
|
+
}
|
|
1442
|
+
const details = error.details;
|
|
1443
|
+
if (typeof details !== "object" || details === null) {
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}
|
|
1446
|
+
const result = details.result;
|
|
1447
|
+
if (typeof result !== "object" || result === null) {
|
|
1448
|
+
return void 0;
|
|
1449
|
+
}
|
|
1450
|
+
const r = result;
|
|
1451
|
+
return {
|
|
1452
|
+
stdout: typeof r.stdout === "string" ? r.stdout : void 0,
|
|
1453
|
+
stderr: typeof r.stderr === "string" ? r.stderr : void 0,
|
|
1454
|
+
exitCode: typeof r.code === "number" ? r.code : void 0
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function readPackageName(pkgPath) {
|
|
1458
|
+
try {
|
|
1459
|
+
const pkg = JSON.parse(readFileSync(join(pkgPath, "package.json"), "utf8"));
|
|
1460
|
+
return typeof pkg.name === "string" ? pkg.name : void 0;
|
|
1461
|
+
} catch {
|
|
1462
|
+
return void 0;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1269
1465
|
function evaluateParser(check, stdout, stderr, exitCode) {
|
|
1270
1466
|
const parser = check.parser ?? "exitcode";
|
|
1271
1467
|
if (parser === "exitcode") {
|
|
@@ -1486,31 +1682,35 @@ async function verifyAgainstRegistry(packages, options) {
|
|
|
1486
1682
|
const { registry, timeout = DEFAULT_TIMEOUT_MS, retries = 0, retryDelaysMs = DEFAULT_POLL_RETRY_DELAYS_MS, logger } = options;
|
|
1487
1683
|
const results = [];
|
|
1488
1684
|
for (const pkg of packages) {
|
|
1489
|
-
results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, logger));
|
|
1685
|
+
results.push(await verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, options.visibilityDeadlineMs, logger));
|
|
1490
1686
|
}
|
|
1491
1687
|
return results;
|
|
1492
1688
|
}
|
|
1493
|
-
async function waitUntilPublished(pkg, registry, retries, retryDelaysMs, logger) {
|
|
1494
|
-
|
|
1689
|
+
async function waitUntilPublished(pkg, registry, retries, retryDelaysMs, visibilityDeadlineMs, logger) {
|
|
1690
|
+
const deadline = visibilityDeadlineMs === void 0 ? void 0 : Date.now() + visibilityDeadlineMs;
|
|
1691
|
+
for (let attempt = 0; deadline === void 0 ? attempt <= retries : Date.now() <= deadline; attempt++) {
|
|
1495
1692
|
if (await isVersionPublished(pkg.name, pkg.version, registry)) {
|
|
1496
1693
|
return true;
|
|
1497
1694
|
}
|
|
1498
|
-
if (attempt < retries) {
|
|
1695
|
+
if (deadline === void 0 ? attempt < retries : Date.now() < deadline) {
|
|
1499
1696
|
const delay = retryDelaysMs[Math.min(attempt, retryDelaysMs.length - 1)];
|
|
1697
|
+
const remaining = deadline === void 0 ? void 0 : deadline - Date.now();
|
|
1698
|
+
const waitMs = remaining === void 0 ? delay : Math.min(delay, Math.max(0, remaining));
|
|
1500
1699
|
logger?.warn?.(
|
|
1501
|
-
`${pkg.name}@${pkg.version} not yet visible on ${registry} (attempt ${attempt + 1}
|
|
1700
|
+
`${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`
|
|
1502
1701
|
);
|
|
1503
1702
|
await new Promise((r) => {
|
|
1504
|
-
setTimeout(r,
|
|
1703
|
+
setTimeout(r, waitMs);
|
|
1505
1704
|
});
|
|
1506
1705
|
}
|
|
1507
1706
|
}
|
|
1508
1707
|
return false;
|
|
1509
1708
|
}
|
|
1510
|
-
async function verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, logger) {
|
|
1511
|
-
const published = await waitUntilPublished(pkg, registry, retries, retryDelaysMs, logger);
|
|
1709
|
+
async function verifyOneAgainstRegistry(pkg, registry, timeout, retries, retryDelaysMs, visibilityDeadlineMs, logger) {
|
|
1710
|
+
const published = await waitUntilPublished(pkg, registry, retries, retryDelaysMs, visibilityDeadlineMs, logger);
|
|
1512
1711
|
if (!published) {
|
|
1513
|
-
|
|
1712
|
+
const waited = visibilityDeadlineMs === void 0 ? `waited through ${retries} retr${retries === 1 ? "y" : "ies"}` : `visibility deadline of ${(visibilityDeadlineMs / 1e3).toFixed(0)}s elapsed`;
|
|
1713
|
+
return { name: pkg.name, success: false, issues: [`${pkg.name}@${pkg.version} was not found on ${registry} after publish (${waited})`] };
|
|
1514
1714
|
}
|
|
1515
1715
|
logger?.info?.(`${pkg.name}@${pkg.version} confirmed on ${registry}`);
|
|
1516
1716
|
const tmpDir = join(tmpdir(), `kb-verdaccio-verify-${randomBytes(6).toString("hex")}`);
|
|
@@ -1995,7 +2195,7 @@ function buildReport(stage, plan, repoRoot, dryRun, startTime, result) {
|
|
|
1995
2195
|
result: { ...result, timingMs: result.timingMs ?? Date.now() - startTime }
|
|
1996
2196
|
};
|
|
1997
2197
|
}
|
|
1998
|
-
async function verifyCleanInstall(tarballPath, packageName, additionalTarballs = [], packageManager = "npm") {
|
|
2198
|
+
async function verifyCleanInstall(tarballPath, packageName, additionalTarballs = [], packageManager = "npm", registry) {
|
|
1999
2199
|
const consumerDir = mkdtempSync(join(tmpdir(), "kb-clean-install-"));
|
|
2000
2200
|
try {
|
|
2001
2201
|
if (packageManager === "pnpm") {
|
|
@@ -2017,6 +2217,10 @@ async function verifyCleanInstall(tarballPath, packageName, additionalTarballs =
|
|
|
2017
2217
|
join(consumerDir, "package.json"),
|
|
2018
2218
|
JSON.stringify({ name: "kb-release-consumer", private: true, dependencies, pnpm: { overrides } }, null, 2) + "\n"
|
|
2019
2219
|
);
|
|
2220
|
+
if (registry) {
|
|
2221
|
+
writeFileSync(join(consumerDir, ".npmrc"), `registry=${registry}
|
|
2222
|
+
`);
|
|
2223
|
+
}
|
|
2020
2224
|
const install = spawnSync(
|
|
2021
2225
|
"pnpm",
|
|
2022
2226
|
["install", "--ignore-scripts", "--no-lockfile", "--config.auto-install-peers=true"],
|
|
@@ -2028,7 +2232,7 @@ async function verifyCleanInstall(tarballPath, packageName, additionalTarballs =
|
|
|
2028
2232
|
} else {
|
|
2029
2233
|
writeFileSync(join(consumerDir, "package.json"), JSON.stringify({ name: "kb-release-consumer", private: true }) + "\n");
|
|
2030
2234
|
const { Arborist } = await import('@npmcli/arborist');
|
|
2031
|
-
const arb = new Arborist({ path: consumerDir, ignoreScripts: true });
|
|
2235
|
+
const arb = new Arborist({ path: consumerDir, ignoreScripts: true, audit: false, ...registry ? { registry } : {} });
|
|
2032
2236
|
try {
|
|
2033
2237
|
await arb.reify({ add: [tarballPath, ...additionalTarballs], save: false });
|
|
2034
2238
|
} catch (err) {
|
|
@@ -2097,6 +2301,6 @@ async function resolveScopePath(repoRoot, scope) {
|
|
|
2097
2301
|
return join(repoRoot, scope);
|
|
2098
2302
|
}
|
|
2099
2303
|
|
|
2100
|
-
export { CHECKS_CONCURRENCY, 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, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyCleanInstall, verifyExtractedTarball, verifyPackage, verifyPackages };
|
|
2304
|
+
export { CHECKS_CONCURRENCY, DEFAULT_ROOT_CHANGELOG_PATH, DEFAULT_TAG_PATTERN, applyCanarySuffix, applyVersionStrategy, buildPackages, buildReleaseTag, commitAndTagRelease, computeFlowReleaseStatus, copyChangelogToPackages, createExecaShellAdapter, discoverCurrentPackages, findForbiddenDependencyProtocols, isBuildCommand, isVersionPublished, matchesPackagePattern, mergeConfigWithFlow, mergeRootChangelog, planRelease, renderJson, renderMarkdown, renderText, resolveFlowFromTag, resolvePublishRegistry, resolvePublishTag, resolveRootChangelogRelPath, resolveScopePath, restoreSnapshot, runReleaseChecks, runReleasePipeline, runSafeBuild, saveSnapshot, updatePackageVersion, updatePackageVersions, verifyAgainstRegistry, verifyCleanInstall, verifyExtractedTarball, verifyPackage, verifyPackages };
|
|
2101
2305
|
//# sourceMappingURL=index.js.map
|
|
2102
2306
|
//# sourceMappingURL=index.js.map
|