@kb-labs/release-manager-core 2.112.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 +51 -7
- package/dist/index.js +173 -40
- package/dist/index.js.map +1 -1
- package/package.json +19 -17
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,
|
|
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
|
|
210
|
+
/** Per-scope overrides — packages filter merged with global, checks are additive. */
|
|
211
211
|
scopes?: Record<string, {
|
|
212
212
|
packages?: PackagesFilter;
|
|
213
|
-
/** If set,
|
|
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.
|
|
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.
|
|
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
|
|
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 && {
|
|
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
|