@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.d.ts +106 -61
- package/dist/index.js +235 -331
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
1616
|
-
report: buildReport("planning",
|
|
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
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
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,
|
|
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
|