@varlock/bumpy 0.0.2 → 1.0.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/.claude-plugin/plugin.json +2 -2
- package/dist/add-CgCjs4d-.mjs +313 -0
- package/dist/{ai-CQhUyHAG.mjs → ai-sMYUf3lP.mjs} +21 -4
- package/dist/{apply-release-plan-D6TSrcwX.mjs → apply-release-plan-CczGWJTk.mjs} +28 -24
- package/dist/bump-file-CCLXMLA8.mjs +143 -0
- package/dist/{changelog-github-Du62krXi.mjs → changelog-github-Cd8uJHZI.mjs} +22 -20
- package/dist/{check-jIwike9F.mjs → check-BOoxpWqk.mjs} +9 -9
- package/dist/{ci-D6LQbR38.mjs → ci-Bhx--Tj6.mjs} +116 -72
- package/dist/{ci-setup-C6FlOfW5.mjs → ci-setup-qz4Y3v7T.mjs} +1 -1
- package/dist/cli.mjs +32 -30
- package/dist/{config-BkwIEaQg.mjs → config-XZWUL3ma.mjs} +27 -22
- package/dist/fs-DYR2XuFE.mjs +81 -0
- package/dist/{generate-Btrsn1qi.mjs → generate-gYKTpvex.mjs} +8 -8
- package/dist/index.d.mts +55 -37
- package/dist/index.mjs +8 -8
- package/dist/{init-B0q3wEQW.mjs → init-lA9E5pEc.mjs} +2 -2
- package/dist/{migrate-CfQNwD0T.mjs → migrate-DmOYgmfD.mjs} +10 -10
- package/dist/{names-Ck8cun7B.mjs → names-9VubBmL0.mjs} +1 -1
- package/dist/{package-manager-DcI5TdDE.mjs → package-manager-VCe10bjc.mjs} +1 -1
- package/dist/{publish-D_7RqEYL.mjs → publish-Cun-zQ1b.mjs} +21 -20
- package/dist/{publish-pipeline-ChnqW8nR.mjs → publish-pipeline-BwBuKCIk.mjs} +22 -17
- package/dist/release-plan-Bi5QNSEo.mjs +264 -0
- package/dist/{semver-BTzYh8vc.mjs → semver-DfQyVLM_.mjs} +13 -3
- package/dist/{status--Q8yAxQ4.mjs → status-CfE63ti5.mjs} +25 -21
- package/dist/{version-cAUkfYPx.mjs → version-19vVt9dv.mjs} +16 -12
- package/dist/{workspace-CxEKakDm.mjs → workspace-C5ULTyUN.mjs} +3 -3
- package/package.json +13 -1
- package/skills/add-change/SKILL.md +8 -12
- package/dist/add-BjyVIUlr.mjs +0 -175
- package/dist/changeset-UCZdSRDv.mjs +0 -108
- package/dist/fs-0AtnPUUe.mjs +0 -51
- package/dist/release-plan-BEzwApuK.mjs +0 -173
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as log, o as __toESM, r as require_picocolors } from "./logger-C2dEe5Su.mjs";
|
|
2
|
-
import { a as readJson, n as exists, o as readText } from "./fs-
|
|
3
|
-
import { r as getBumpyDir } from "./config-
|
|
4
|
-
import { i as
|
|
2
|
+
import { a as readJson, n as exists, o as readText } from "./fs-DYR2XuFE.mjs";
|
|
3
|
+
import { r as getBumpyDir } from "./config-XZWUL3ma.mjs";
|
|
4
|
+
import { i as writeBumpFile } from "./bump-file-CCLXMLA8.mjs";
|
|
5
5
|
import { a as fe, c as ot, n as O, o as gt, s as mt, t as unwrap } from "./clack-CDRCHrC-.mjs";
|
|
6
|
-
import { initCommand } from "./init-
|
|
6
|
+
import { initCommand } from "./init-lA9E5pEc.mjs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { readdir } from "node:fs/promises";
|
|
9
9
|
//#region src/commands/migrate.ts
|
|
@@ -26,7 +26,7 @@ async function migrateCommand(rootDir, opts) {
|
|
|
26
26
|
}
|
|
27
27
|
const mdFiles = (await readdir(changesetDir)).filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
28
28
|
if (mdFiles.length > 0) {
|
|
29
|
-
log.step(`Migrating ${mdFiles.length} pending changeset(s)...`);
|
|
29
|
+
log.step(`Migrating ${mdFiles.length} pending changeset(s) to bump files...`);
|
|
30
30
|
let migrated = 0;
|
|
31
31
|
for (const file of mdFiles) {
|
|
32
32
|
const result = parseChangesetFile(await readText(resolve(changesetDir, file)));
|
|
@@ -39,11 +39,11 @@ async function migrateCommand(rootDir, opts) {
|
|
|
39
39
|
log.dim(` Skipped ${file} (already exists in .bumpy/)`);
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
|
-
await
|
|
42
|
+
await writeBumpFile(rootDir, name, result.releases, result.summary);
|
|
43
43
|
migrated++;
|
|
44
44
|
log.dim(` Migrated ${file}`);
|
|
45
45
|
}
|
|
46
|
-
log.success(`Migrated ${migrated}
|
|
46
|
+
log.success(`Migrated ${migrated} bump file(s)`);
|
|
47
47
|
} else log.info("No pending changesets to migrate.");
|
|
48
48
|
if (!opts.force) {
|
|
49
49
|
mt(import_picocolors.default.bgCyan(import_picocolors.default.black(" bumpy migrate ")));
|
|
@@ -63,8 +63,8 @@ async function migrateCommand(rootDir, opts) {
|
|
|
63
63
|
log.success("Migration complete!");
|
|
64
64
|
log.dim("Review .bumpy/_config.json and adjust settings as needed.");
|
|
65
65
|
log.dim("Key differences from changesets:");
|
|
66
|
-
log.dim(" -
|
|
67
|
-
log.dim(" - Use '
|
|
66
|
+
log.dim(" - Out-of-range peer dep bumps match the triggering bump level (not always major)");
|
|
67
|
+
log.dim(" - Use 'none' in a bump file to suppress a propagated bump");
|
|
68
68
|
log.dim(" - Per-package config goes in package.json[\"bumpy\"]");
|
|
69
69
|
}
|
|
70
70
|
async function migrateConfig(changesetConfigPath, bumpyDir) {
|
|
@@ -82,7 +82,7 @@ async function migrateConfig(changesetConfigPath, bumpyDir) {
|
|
|
82
82
|
"updateInternalDependencies",
|
|
83
83
|
"privatePackages"
|
|
84
84
|
]) if (csConfig[field] !== void 0) bumpyConfig[field] = csConfig[field];
|
|
85
|
-
const { writeJson } = await import("./fs-
|
|
85
|
+
const { writeJson } = await import("./fs-DYR2XuFE.mjs").then((n) => n.r);
|
|
86
86
|
await writeJson(bumpyConfigPath, bumpyConfig);
|
|
87
87
|
log.dim(" Migrated config fields: " + Object.keys(bumpyConfig).filter((k) => k !== "baseBranch" || bumpyConfig[k] !== "main").join(", "));
|
|
88
88
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/utils/names.ts
|
|
2
|
-
/** Generate a random adjective-noun name for
|
|
2
|
+
/** Generate a random adjective-noun name for bump files */
|
|
3
3
|
function randomName() {
|
|
4
4
|
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
5
5
|
return `${pick(ADJECTIVES)}-${pick(ADJECTIVES)}-${pick(NOUNS)}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as readJson, n as exists, o as readText } from "./fs-
|
|
1
|
+
import { a as readJson, n as exists, o as readText } from "./fs-DYR2XuFE.mjs";
|
|
2
2
|
import { t as jsYaml } from "./js-yaml-DpZfOoD4.mjs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
//#region src/utils/package-manager.ts
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
-
import { a as loadConfig } from "./config-
|
|
3
|
-
import { n as detectWorkspaces } from "./package-manager-
|
|
4
|
-
import { n as discoverWorkspace } from "./workspace-
|
|
2
|
+
import { a as loadConfig } from "./config-XZWUL3ma.mjs";
|
|
3
|
+
import { n as detectWorkspaces } from "./package-manager-VCe10bjc.mjs";
|
|
4
|
+
import { n as discoverWorkspace } from "./workspace-C5ULTyUN.mjs";
|
|
5
5
|
import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
|
|
6
6
|
import { n as runArgsAsync, o as tryRunArgs } from "./shell-Dj7JRD_q.mjs";
|
|
7
7
|
import { a as pushWithTags, i as listTags, r as hasUncommittedChanges } from "./git-CGHVXXKw.mjs";
|
|
8
|
-
import { t as publishPackages } from "./publish-pipeline-
|
|
8
|
+
import { t as publishPackages } from "./publish-pipeline-BwBuKCIk.mjs";
|
|
9
9
|
//#region src/core/github-release.ts
|
|
10
10
|
/** Get the current HEAD commit SHA */
|
|
11
11
|
function getHeadSha(rootDir) {
|
|
@@ -16,7 +16,7 @@ function getHeadSha(rootDir) {
|
|
|
16
16
|
], { cwd: rootDir });
|
|
17
17
|
}
|
|
18
18
|
/** Create individual GitHub releases for each published package */
|
|
19
|
-
async function createIndividualReleases(releases,
|
|
19
|
+
async function createIndividualReleases(releases, bumpFiles, rootDir, opts = {}) {
|
|
20
20
|
if (!isGhAvailable()) {
|
|
21
21
|
log.dim(" gh CLI not found — skipping GitHub releases");
|
|
22
22
|
return;
|
|
@@ -24,7 +24,7 @@ async function createIndividualReleases(releases, changesets, rootDir, opts = {}
|
|
|
24
24
|
const headSha = getHeadSha(rootDir);
|
|
25
25
|
for (const release of releases) {
|
|
26
26
|
const tag = `${release.name}@${release.newVersion}`;
|
|
27
|
-
const body = buildReleaseBody(release,
|
|
27
|
+
const body = buildReleaseBody(release, bumpFiles);
|
|
28
28
|
const title = `${release.name} v${release.newVersion}`;
|
|
29
29
|
if (opts.dryRun) {
|
|
30
30
|
log.dim(` Would create GitHub release: ${title}`);
|
|
@@ -50,7 +50,7 @@ async function createIndividualReleases(releases, changesets, rootDir, opts = {}
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
/** Create a single aggregated GitHub release for all published packages */
|
|
53
|
-
async function createAggregateRelease(releases,
|
|
53
|
+
async function createAggregateRelease(releases, bumpFiles, rootDir, opts = {}) {
|
|
54
54
|
if (!isGhAvailable()) {
|
|
55
55
|
log.dim(" gh CLI not found — skipping GitHub release");
|
|
56
56
|
return;
|
|
@@ -58,7 +58,7 @@ async function createAggregateRelease(releases, changesets, rootDir, opts = {})
|
|
|
58
58
|
if (releases.length === 0) return;
|
|
59
59
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
60
60
|
const { tag, title } = resolveAggregateTagAndTitle(date, listTags(`release-${date}*`, { cwd: rootDir }), opts.title);
|
|
61
|
-
const body = buildAggregateBody(releases,
|
|
61
|
+
const body = buildAggregateBody(releases, bumpFiles);
|
|
62
62
|
if (opts.dryRun) {
|
|
63
63
|
log.dim(` Would create aggregate GitHub release: ${title}`);
|
|
64
64
|
log.dim(` Tag: ${tag}`);
|
|
@@ -88,16 +88,16 @@ async function createAggregateRelease(releases, changesets, rootDir, opts = {})
|
|
|
88
88
|
log.warn(`Failed to create aggregate GitHub release: ${err instanceof Error ? err.message : err}`);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
function buildReleaseBody(release,
|
|
91
|
+
function buildReleaseBody(release, bumpFiles) {
|
|
92
92
|
const lines = [];
|
|
93
|
-
const relevant =
|
|
93
|
+
const relevant = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
|
|
94
94
|
if (relevant.length > 0) {
|
|
95
|
-
for (const
|
|
95
|
+
for (const bf of relevant) if (bf.summary) lines.push(`- ${bf.summary.split("\n")[0]}`);
|
|
96
96
|
}
|
|
97
97
|
if (release.isDependencyBump && relevant.length === 0) lines.push("- Updated dependencies");
|
|
98
98
|
return lines.join("\n") || "No changelog entries.";
|
|
99
99
|
}
|
|
100
|
-
function buildAggregateBody(releases,
|
|
100
|
+
function buildAggregateBody(releases, bumpFiles) {
|
|
101
101
|
const lines = [];
|
|
102
102
|
const groups = [
|
|
103
103
|
["Major Changes", releases.filter((r) => r.type === "major")],
|
|
@@ -109,9 +109,9 @@ function buildAggregateBody(releases, changesets) {
|
|
|
109
109
|
lines.push(`## ${heading}\n`);
|
|
110
110
|
for (const release of group) {
|
|
111
111
|
lines.push(`### ${release.name} v${release.newVersion}\n`);
|
|
112
|
-
const relevant =
|
|
112
|
+
const relevant = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
|
|
113
113
|
if (relevant.length > 0) {
|
|
114
|
-
for (const
|
|
114
|
+
for (const bf of relevant) if (bf.summary) lines.push(`- ${bf.summary.split("\n")[0]}`);
|
|
115
115
|
} else if (release.isDependencyBump) lines.push("- Updated dependencies");
|
|
116
116
|
else if (release.isCascadeBump) lines.push("- Version bump via cascade rule");
|
|
117
117
|
lines.push("");
|
|
@@ -148,7 +148,7 @@ async function publishCommand(rootDir, opts) {
|
|
|
148
148
|
}
|
|
149
149
|
let toPublish = await findUnpublishedPackages(packages, config);
|
|
150
150
|
if (opts.filter) {
|
|
151
|
-
const { matchGlob } = await import("./config-
|
|
151
|
+
const { matchGlob } = await import("./config-XZWUL3ma.mjs").then((n) => n.t);
|
|
152
152
|
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
153
153
|
toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
154
154
|
}
|
|
@@ -157,8 +157,9 @@ async function publishCommand(rootDir, opts) {
|
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
const releasePlan = {
|
|
160
|
-
|
|
161
|
-
releases: toPublish
|
|
160
|
+
bumpFiles: [],
|
|
161
|
+
releases: toPublish,
|
|
162
|
+
warnings: []
|
|
162
163
|
};
|
|
163
164
|
if (opts.dryRun) log.bold("Dry run — would publish:");
|
|
164
165
|
else log.bold("Publishing:");
|
|
@@ -186,11 +187,11 @@ async function publishCommand(rootDir, opts) {
|
|
|
186
187
|
const aggConfig = config.aggregateRelease;
|
|
187
188
|
const isAggregate = aggConfig === true || typeof aggConfig === "object" && aggConfig.enabled;
|
|
188
189
|
const aggTitle = typeof aggConfig === "object" ? aggConfig.title : void 0;
|
|
189
|
-
if (isAggregate) await createAggregateRelease(publishedReleases, releasePlan.
|
|
190
|
+
if (isAggregate) await createAggregateRelease(publishedReleases, releasePlan.bumpFiles, rootDir, {
|
|
190
191
|
dryRun: opts.dryRun,
|
|
191
192
|
title: aggTitle
|
|
192
193
|
});
|
|
193
|
-
else await createIndividualReleases(publishedReleases, releasePlan.
|
|
194
|
+
else await createIndividualReleases(publishedReleases, releasePlan.bumpFiles, rootDir, { dryRun: opts.dryRun });
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
/**
|
|
@@ -211,7 +212,7 @@ async function findUnpublishedPackages(packages, _config) {
|
|
|
211
212
|
type: "patch",
|
|
212
213
|
oldVersion: pkg.version,
|
|
213
214
|
newVersion: pkg.version,
|
|
214
|
-
|
|
215
|
+
bumpFiles: [],
|
|
215
216
|
isDependencyBump: false,
|
|
216
217
|
isCascadeBump: false
|
|
217
218
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
-
import { a as readJson,
|
|
3
|
-
import { r as resolveCatalogDep } from "./package-manager-
|
|
2
|
+
import { a as readJson, l as updateJsonNestedField } from "./fs-DYR2XuFE.mjs";
|
|
3
|
+
import { r as resolveCatalogDep } from "./package-manager-VCe10bjc.mjs";
|
|
4
4
|
import { a as sq, n as runArgsAsync, o as tryRunArgs, r as runAsync } from "./shell-Dj7JRD_q.mjs";
|
|
5
|
-
import { r as stripProtocol } from "./semver-
|
|
5
|
+
import { r as stripProtocol } from "./semver-DfQyVLM_.mjs";
|
|
6
6
|
import { o as tagExists, t as createTag } from "./git-CGHVXXKw.mjs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { unlink } from "node:fs/promises";
|
|
@@ -160,7 +160,7 @@ async function packThenPublish(pkg, pkgConfig, config, packManager, opts) {
|
|
|
160
160
|
log.dim(` Would publish with: ${publishArgs.join(" ")}`);
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
|
-
const tarball = parseTarballPath(await runArgsAsync(packArgs, { cwd: pkg.dir }), pkg.dir);
|
|
163
|
+
const tarball = parseTarballPath(await runArgsAsync(packArgs, { cwd: pkg.dir }), pkg.dir, packManager);
|
|
164
164
|
try {
|
|
165
165
|
const publishArgs = buildPublishArgs(pkg, pkgConfig, config, opts, tarball);
|
|
166
166
|
log.dim(` Publishing: ${publishArgs.join(" ")}`);
|
|
@@ -179,14 +179,22 @@ async function npmPublishDirect(pkg, pkgConfig, config, opts) {
|
|
|
179
179
|
}
|
|
180
180
|
function getPackArgs(pm) {
|
|
181
181
|
switch (pm) {
|
|
182
|
-
case "pnpm": return [
|
|
182
|
+
case "pnpm": return [
|
|
183
|
+
"pnpm",
|
|
184
|
+
"pack",
|
|
185
|
+
"--json"
|
|
186
|
+
];
|
|
183
187
|
case "bun": return [
|
|
184
188
|
"bun",
|
|
185
189
|
"pm",
|
|
186
190
|
"pack"
|
|
187
191
|
];
|
|
188
192
|
case "yarn": return ["yarn", "pack"];
|
|
189
|
-
default: return [
|
|
193
|
+
default: return [
|
|
194
|
+
"npm",
|
|
195
|
+
"pack",
|
|
196
|
+
"--json"
|
|
197
|
+
];
|
|
190
198
|
}
|
|
191
199
|
}
|
|
192
200
|
function buildPublishArgs(pkg, pkgConfig, config, opts, tarball) {
|
|
@@ -204,12 +212,14 @@ function buildPublishArgs(pkg, pkgConfig, config, opts, tarball) {
|
|
|
204
212
|
}
|
|
205
213
|
/**
|
|
206
214
|
* Parse the tarball path from pack command output.
|
|
207
|
-
*
|
|
208
|
-
* npm/pnpm: tarball filename on the last line
|
|
209
|
-
* bun: tarball filename mid-output, summary lines after
|
|
210
|
-
* yarn: 'success Wrote tarball to "/path/to/foo.tgz".'
|
|
215
|
+
* npm/pnpm use --json for structured output; bun/yarn fall back to regex parsing.
|
|
211
216
|
*/
|
|
212
|
-
function parseTarballPath(output, cwd) {
|
|
217
|
+
function parseTarballPath(output, cwd, pm) {
|
|
218
|
+
if (pm === "npm" || pm === "pnpm") try {
|
|
219
|
+
const parsed = JSON.parse(output);
|
|
220
|
+
const entry = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
221
|
+
if (entry?.filename) return resolve(cwd, entry.filename);
|
|
222
|
+
} catch {}
|
|
213
223
|
const tgzMatch = output.match(/(?:^|["'\s])([^\s"']*\.tgz)/m);
|
|
214
224
|
if (tgzMatch) {
|
|
215
225
|
const tarball = tgzMatch[1];
|
|
@@ -239,7 +249,6 @@ function createGitTag(release, rootDir, opts) {
|
|
|
239
249
|
async function resolveProtocolsInPlace(pkg, packages, releasePlan, catalogs) {
|
|
240
250
|
const pkgJsonPath = resolve(pkg.dir, "package.json");
|
|
241
251
|
const pkgJson = await readJson(pkgJsonPath);
|
|
242
|
-
let modified = false;
|
|
243
252
|
const releaseMap = new Map(releasePlan.releases.map((r) => [r.name, r]));
|
|
244
253
|
for (const depField of [
|
|
245
254
|
"dependencies",
|
|
@@ -265,13 +274,9 @@ async function resolveProtocolsInPlace(pkg, packages, releasePlan, catalogs) {
|
|
|
265
274
|
resolved = `${cleanRange === "*" ? "^" : cleanRange}${version}`;
|
|
266
275
|
} else resolved = cleanRange;
|
|
267
276
|
}
|
|
268
|
-
if (resolved)
|
|
269
|
-
deps[depName] = resolved;
|
|
270
|
-
modified = true;
|
|
271
|
-
}
|
|
277
|
+
if (resolved) await updateJsonNestedField(pkgJsonPath, depField, depName, resolved);
|
|
272
278
|
}
|
|
273
279
|
}
|
|
274
|
-
if (modified) await writeJson(pkgJsonPath, pkgJson);
|
|
275
280
|
}
|
|
276
281
|
//#endregion
|
|
277
282
|
export { publishPackages as t };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, p as bumpLevel, s as matchGlob } from "./config-XZWUL3ma.mjs";
|
|
2
|
+
import { n as satisfies, t as bumpVersion } from "./semver-DfQyVLM_.mjs";
|
|
3
|
+
//#region src/core/release-plan.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build a release plan from pending bump files, the dependency graph, and config.
|
|
6
|
+
* This is the core algorithm of bumpy.
|
|
7
|
+
*
|
|
8
|
+
* The propagation loop runs three phases until stable:
|
|
9
|
+
* Phase A — fix out-of-range dependencies (always runs)
|
|
10
|
+
* Phase B — enforce fixed/linked group constraints
|
|
11
|
+
* Phase C — apply cascades and proactive propagation rules
|
|
12
|
+
*/
|
|
13
|
+
function assembleReleasePlan(bumpFiles, packages, depGraph, config) {
|
|
14
|
+
if (bumpFiles.length === 0) return {
|
|
15
|
+
bumpFiles: [],
|
|
16
|
+
releases: [],
|
|
17
|
+
warnings: []
|
|
18
|
+
};
|
|
19
|
+
const planned = /* @__PURE__ */ new Map();
|
|
20
|
+
const warnings = [];
|
|
21
|
+
const cascadeOverrides = /* @__PURE__ */ new Map();
|
|
22
|
+
const suppressedPackages = /* @__PURE__ */ new Set();
|
|
23
|
+
for (const bf of bumpFiles) for (const release of bf.releases) {
|
|
24
|
+
if (!packages.has(release.name)) continue;
|
|
25
|
+
const bump = release.type;
|
|
26
|
+
if (bump === "none") {
|
|
27
|
+
suppressedPackages.add(release.name);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const existing = planned.get(release.name);
|
|
31
|
+
if (existing) {
|
|
32
|
+
existing.type = maxBump(existing.type, bump);
|
|
33
|
+
existing.bumpFiles.add(bf.id);
|
|
34
|
+
} else planned.set(release.name, {
|
|
35
|
+
type: bump,
|
|
36
|
+
suppressed: false,
|
|
37
|
+
isDependencyBump: false,
|
|
38
|
+
isCascadeBump: false,
|
|
39
|
+
bumpFiles: new Set([bf.id])
|
|
40
|
+
});
|
|
41
|
+
if (hasCascade(release)) {
|
|
42
|
+
if (!cascadeOverrides.has(release.name)) cascadeOverrides.set(release.name, /* @__PURE__ */ new Map());
|
|
43
|
+
const overrides = cascadeOverrides.get(release.name);
|
|
44
|
+
for (const [pattern, bumpType] of Object.entries(release.cascade)) {
|
|
45
|
+
const existing = overrides.get(pattern);
|
|
46
|
+
overrides.set(pattern, maxBump(existing, bumpType));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const name of suppressedPackages) if (!planned.has(name)) planned.set(name, {
|
|
51
|
+
type: "patch",
|
|
52
|
+
suppressed: true,
|
|
53
|
+
isDependencyBump: false,
|
|
54
|
+
isCascadeBump: false,
|
|
55
|
+
bumpFiles: /* @__PURE__ */ new Set()
|
|
56
|
+
});
|
|
57
|
+
let changed = true;
|
|
58
|
+
let iterations = 0;
|
|
59
|
+
const MAX_ITERATIONS = 100;
|
|
60
|
+
while (changed && iterations < MAX_ITERATIONS) {
|
|
61
|
+
changed = false;
|
|
62
|
+
iterations++;
|
|
63
|
+
for (const [pkgName, bump] of planned) {
|
|
64
|
+
if (bump.suppressed) continue;
|
|
65
|
+
const pkg = packages.get(pkgName);
|
|
66
|
+
const newVersion = bumpVersion(pkg.version, bump.type);
|
|
67
|
+
const dependents = depGraph.getDependents(pkgName);
|
|
68
|
+
for (const dep of dependents) {
|
|
69
|
+
if (dep.depType === "devDependencies") continue;
|
|
70
|
+
const currentVersion = pkg.version;
|
|
71
|
+
if (satisfies(newVersion, dep.versionRange, currentVersion)) continue;
|
|
72
|
+
let depBump;
|
|
73
|
+
if (dep.depType === "peerDependencies") depBump = bump.type;
|
|
74
|
+
else depBump = "patch";
|
|
75
|
+
if (planned.get(dep.name)?.suppressed) throw new Error(`Cannot suppress bump for '${dep.name}' (via 'none' in bump file) — '${pkgName}' is bumping to ${newVersion} which breaks the declared range '${dep.versionRange}'. Either widen the range or remove the 'none' entry.`);
|
|
76
|
+
if (dep.depType === "peerDependencies" && depBump !== "patch") {
|
|
77
|
+
let resolvedRange = dep.versionRange.replace(/^workspace:/, "");
|
|
78
|
+
if (resolvedRange === "^" || resolvedRange === "~") resolvedRange = `${resolvedRange}${pkg.version}`;
|
|
79
|
+
if (/^\^0(\.|$)/.test(resolvedRange)) warnings.push(`${dep.name} gets a ${depBump} bump because ${pkgName}@${newVersion} is out of range for its peer dep "${dep.versionRange}" (resolves to ${resolvedRange}). npm treats ^ on 0.x as minor-breaking. Consider using >=0.x ranges for pre-1.0 peer deps.`);
|
|
80
|
+
}
|
|
81
|
+
if (applyBump(planned, dep.name, depBump, true, false, bump.bumpFiles)) changed = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const group of config.fixed) {
|
|
85
|
+
let groupBump;
|
|
86
|
+
for (const nameOrGlob of group) for (const [name, bump] of planned) {
|
|
87
|
+
if (bump.suppressed) continue;
|
|
88
|
+
if (matchGlob(name, nameOrGlob)) groupBump = maxBump(groupBump, bump.type);
|
|
89
|
+
}
|
|
90
|
+
if (!groupBump) continue;
|
|
91
|
+
for (const nameOrGlob of group) for (const [name] of packages) {
|
|
92
|
+
if (!matchGlob(name, nameOrGlob)) continue;
|
|
93
|
+
const existing = planned.get(name);
|
|
94
|
+
if (existing && existing.suppressed) continue;
|
|
95
|
+
if (existing) {
|
|
96
|
+
const newType = maxBump(existing.type, groupBump);
|
|
97
|
+
if (newType !== existing.type) {
|
|
98
|
+
existing.type = newType;
|
|
99
|
+
changed = true;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
planned.set(name, {
|
|
103
|
+
type: groupBump,
|
|
104
|
+
suppressed: false,
|
|
105
|
+
isDependencyBump: false,
|
|
106
|
+
isCascadeBump: false,
|
|
107
|
+
bumpFiles: /* @__PURE__ */ new Set()
|
|
108
|
+
});
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const group of config.linked) {
|
|
114
|
+
let groupBump;
|
|
115
|
+
for (const nameOrGlob of group) for (const [name, bump] of planned) {
|
|
116
|
+
if (bump.suppressed) continue;
|
|
117
|
+
if (matchGlob(name, nameOrGlob)) groupBump = maxBump(groupBump, bump.type);
|
|
118
|
+
}
|
|
119
|
+
if (!groupBump) continue;
|
|
120
|
+
for (const nameOrGlob of group) for (const [name] of packages) {
|
|
121
|
+
if (!matchGlob(name, nameOrGlob)) continue;
|
|
122
|
+
const existing = planned.get(name);
|
|
123
|
+
if (!existing || existing.suppressed) continue;
|
|
124
|
+
const newType = maxBump(existing.type, groupBump);
|
|
125
|
+
if (newType !== existing.type) {
|
|
126
|
+
existing.type = newType;
|
|
127
|
+
changed = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (config.updateInternalDependencies !== "out-of-range") for (const [pkgName, bump] of planned) {
|
|
132
|
+
if (bump.suppressed) continue;
|
|
133
|
+
if (config.updateInternalDependencies === "minor" && bumpLevel(bump.type) < bumpLevel("minor")) continue;
|
|
134
|
+
const bfOverrides = cascadeOverrides.get(pkgName);
|
|
135
|
+
if (bfOverrides) for (const [pattern, cascadeBumpType] of bfOverrides) for (const [targetName] of packages) {
|
|
136
|
+
if (!matchGlob(targetName, pattern)) continue;
|
|
137
|
+
if (planned.get(targetName)?.suppressed) continue;
|
|
138
|
+
if (applyBump(planned, targetName, cascadeBumpType, false, true, bump.bumpFiles)) changed = true;
|
|
139
|
+
}
|
|
140
|
+
const cascadeTo = packages.get(pkgName)?.bumpy?.cascadeTo;
|
|
141
|
+
if (cascadeTo) for (const [pattern, rule] of Object.entries(cascadeTo)) {
|
|
142
|
+
if (!shouldTrigger(bump.type, rule.trigger)) continue;
|
|
143
|
+
const cascadeBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
|
|
144
|
+
for (const [targetName] of packages) {
|
|
145
|
+
if (!matchGlob(targetName, pattern)) continue;
|
|
146
|
+
if (planned.get(targetName)?.suppressed) continue;
|
|
147
|
+
if (applyBump(planned, targetName, cascadeBump, false, true, bump.bumpFiles)) changed = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const dependents = depGraph.getDependents(pkgName);
|
|
151
|
+
for (const dep of dependents) {
|
|
152
|
+
const rule = resolveRule(dep.name, dep.depType, packages, config);
|
|
153
|
+
if (!rule) continue;
|
|
154
|
+
if (!shouldTrigger(bump.type, rule.trigger)) continue;
|
|
155
|
+
if (planned.get(dep.name)?.suppressed) continue;
|
|
156
|
+
const depBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
|
|
157
|
+
if (applyBump(planned, dep.name, depBump, true, false, bump.bumpFiles)) changed = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else for (const [pkgName, bump] of planned) {
|
|
161
|
+
if (bump.suppressed) continue;
|
|
162
|
+
const bfOverrides = cascadeOverrides.get(pkgName);
|
|
163
|
+
if (bfOverrides) for (const [pattern, cascadeBumpType] of bfOverrides) for (const [targetName] of packages) {
|
|
164
|
+
if (!matchGlob(targetName, pattern)) continue;
|
|
165
|
+
if (planned.get(targetName)?.suppressed) continue;
|
|
166
|
+
if (applyBump(planned, targetName, cascadeBumpType, false, true, bump.bumpFiles)) changed = true;
|
|
167
|
+
}
|
|
168
|
+
const cascadeTo = packages.get(pkgName)?.bumpy?.cascadeTo;
|
|
169
|
+
if (cascadeTo) for (const [pattern, rule] of Object.entries(cascadeTo)) {
|
|
170
|
+
if (!shouldTrigger(bump.type, rule.trigger)) continue;
|
|
171
|
+
const cascadeBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
|
|
172
|
+
for (const [targetName] of packages) {
|
|
173
|
+
if (!matchGlob(targetName, pattern)) continue;
|
|
174
|
+
if (planned.get(targetName)?.suppressed) continue;
|
|
175
|
+
if (applyBump(planned, targetName, cascadeBump, false, true, bump.bumpFiles)) changed = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const [name, bump] of planned) {
|
|
181
|
+
if (!bump.suppressed) continue;
|
|
182
|
+
const pkg = packages.get(name);
|
|
183
|
+
for (const [depName, depBump] of planned) {
|
|
184
|
+
if (depBump.suppressed) continue;
|
|
185
|
+
const depPkg = packages.get(depName);
|
|
186
|
+
const newDepVersion = bumpVersion(depPkg.version, depBump.type);
|
|
187
|
+
for (const depType of [
|
|
188
|
+
"dependencies",
|
|
189
|
+
"peerDependencies",
|
|
190
|
+
"optionalDependencies"
|
|
191
|
+
]) {
|
|
192
|
+
const range = pkg[depType]?.[depName];
|
|
193
|
+
if (!range) continue;
|
|
194
|
+
if (!satisfies(newDepVersion, range, depPkg.version)) throw new Error(`Cannot suppress bump for '${name}' (via 'none' in bump file) — '${depName}' is bumping to ${newDepVersion} which breaks the declared range '${range}'. Either widen the range or remove the 'none' entry.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const releases = [];
|
|
199
|
+
for (const [name, bump] of planned) {
|
|
200
|
+
if (bump.suppressed) continue;
|
|
201
|
+
const pkg = packages.get(name);
|
|
202
|
+
if (!pkg) continue;
|
|
203
|
+
const newVersion = bumpVersion(pkg.version, bump.type);
|
|
204
|
+
releases.push({
|
|
205
|
+
name,
|
|
206
|
+
type: bump.type,
|
|
207
|
+
oldVersion: pkg.version,
|
|
208
|
+
newVersion,
|
|
209
|
+
bumpFiles: [...bump.bumpFiles],
|
|
210
|
+
isDependencyBump: bump.isDependencyBump,
|
|
211
|
+
isCascadeBump: bump.isCascadeBump
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
releases.sort((a, b) => a.name.localeCompare(b.name));
|
|
215
|
+
for (const [name, pkg] of packages) for (const [depName, range] of Object.entries(pkg.peerDependencies)) if (range === "workspace:*" && packages.has(depName)) warnings.push(`${name} has peer dep "${depName}": "workspace:*" — this will be published as a fixed range which may not match your intent. Consider using "workspace:^" instead.`);
|
|
216
|
+
return {
|
|
217
|
+
bumpFiles,
|
|
218
|
+
releases,
|
|
219
|
+
warnings
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** Apply a bump to a package, upgrading if already planned. Returns true if anything changed. */
|
|
223
|
+
function applyBump(planned, name, type, isDependencyBump, isCascadeBump, sourceBumpFiles) {
|
|
224
|
+
const existing = planned.get(name);
|
|
225
|
+
if (existing) {
|
|
226
|
+
if (existing.suppressed) return false;
|
|
227
|
+
const newType = maxBump(existing.type, type);
|
|
228
|
+
if (newType === existing.type) return false;
|
|
229
|
+
existing.type = newType;
|
|
230
|
+
if (isDependencyBump) existing.isDependencyBump = true;
|
|
231
|
+
if (isCascadeBump) existing.isCascadeBump = true;
|
|
232
|
+
for (const bf of sourceBumpFiles) existing.bumpFiles.add(bf);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
planned.set(name, {
|
|
236
|
+
type,
|
|
237
|
+
suppressed: false,
|
|
238
|
+
isDependencyBump,
|
|
239
|
+
isCascadeBump,
|
|
240
|
+
bumpFiles: new Set(sourceBumpFiles)
|
|
241
|
+
});
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
/** Check if a bump level meets the trigger threshold */
|
|
245
|
+
function shouldTrigger(bumpType, trigger) {
|
|
246
|
+
return bumpLevel(bumpType) >= bumpLevel(trigger);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Resolve the dependency bump rule for a specific dependent + dep type.
|
|
250
|
+
* Priority: per-package depType rules > global depType rules > defaults
|
|
251
|
+
* Returns false if the rule is disabled.
|
|
252
|
+
*/
|
|
253
|
+
function resolveRule(dependentName, depType, packages, config) {
|
|
254
|
+
const dependent = packages.get(dependentName);
|
|
255
|
+
if (dependent?.bumpy?.dependencyBumpRules && depType in dependent.bumpy.dependencyBumpRules) return dependent.bumpy.dependencyBumpRules[depType];
|
|
256
|
+
if (depType in config.dependencyBumpRules) return config.dependencyBumpRules[depType];
|
|
257
|
+
const defaultRule = DEFAULT_BUMP_RULES[depType];
|
|
258
|
+
return defaultRule !== void 0 ? defaultRule : {
|
|
259
|
+
trigger: "patch",
|
|
260
|
+
bumpAs: "patch"
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
export { assembleReleasePlan as t };
|
|
@@ -1341,11 +1341,21 @@ function bumpVersion(version, type) {
|
|
|
1341
1341
|
if (!result) throw new Error(`Failed to bump ${version} by ${type}`);
|
|
1342
1342
|
return result;
|
|
1343
1343
|
}
|
|
1344
|
-
/**
|
|
1345
|
-
|
|
1344
|
+
/**
|
|
1345
|
+
* Check if a version satisfies a range.
|
|
1346
|
+
* @param version - The version to check
|
|
1347
|
+
* @param range - The version range (may include workspace: or catalog: protocol)
|
|
1348
|
+
* @param currentVersion - The dependency's current version, used to resolve workspace:^ and workspace:~
|
|
1349
|
+
*/
|
|
1350
|
+
function satisfies(version, range, currentVersion) {
|
|
1346
1351
|
if (range.startsWith("workspace:")) {
|
|
1347
1352
|
const cleanRange = range.slice(10);
|
|
1348
|
-
if (!cleanRange || cleanRange === "*"
|
|
1353
|
+
if (!cleanRange || cleanRange === "*") return true;
|
|
1354
|
+
if (cleanRange === "^" || cleanRange === "~") {
|
|
1355
|
+
if (!currentVersion) return true;
|
|
1356
|
+
const resolved = `${cleanRange}${currentVersion}`;
|
|
1357
|
+
return import_semver.default.satisfies(version, resolved);
|
|
1358
|
+
}
|
|
1349
1359
|
return import_semver.default.satisfies(version, cleanRange);
|
|
1350
1360
|
}
|
|
1351
1361
|
if (range.startsWith("catalog:")) return true;
|
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
-
import { a as loadConfig } from "./config-
|
|
3
|
-
import { t as discoverPackages } from "./workspace-
|
|
2
|
+
import { a as loadConfig } from "./config-XZWUL3ma.mjs";
|
|
3
|
+
import { t as discoverPackages } from "./workspace-C5ULTyUN.mjs";
|
|
4
4
|
import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
|
|
5
|
-
import { r as
|
|
6
|
-
import { t as assembleReleasePlan } from "./release-plan-
|
|
5
|
+
import { r as readBumpFiles } from "./bump-file-CCLXMLA8.mjs";
|
|
6
|
+
import { t as assembleReleasePlan } from "./release-plan-Bi5QNSEo.mjs";
|
|
7
7
|
//#region src/commands/status.ts
|
|
8
8
|
async function statusCommand(rootDir, opts) {
|
|
9
9
|
const config = await loadConfig(rootDir);
|
|
10
10
|
const packages = await discoverPackages(rootDir, config);
|
|
11
11
|
const depGraph = new DependencyGraph(packages);
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
12
|
+
const bumpFiles = await readBumpFiles(rootDir);
|
|
13
|
+
if (bumpFiles.length === 0) {
|
|
14
14
|
if (opts.json) console.log(JSON.stringify({
|
|
15
|
-
|
|
15
|
+
bumpFiles: [],
|
|
16
16
|
releases: [],
|
|
17
17
|
packageNames: []
|
|
18
18
|
}, null, 2));
|
|
19
|
-
else if (!opts.packagesOnly) log.info("No pending
|
|
19
|
+
else if (!opts.packagesOnly) log.info("No pending bump files.");
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
|
-
const plan = assembleReleasePlan(
|
|
22
|
+
const plan = assembleReleasePlan(bumpFiles, packages, depGraph, config);
|
|
23
23
|
let releases = plan.releases;
|
|
24
24
|
if (opts.bumpType) {
|
|
25
25
|
const types = opts.bumpType.split(",").map((t) => t.trim());
|
|
26
26
|
releases = releases.filter((r) => types.includes(r.type));
|
|
27
27
|
}
|
|
28
28
|
if (opts.filter) {
|
|
29
|
-
const { matchGlob } = await import("./config-
|
|
29
|
+
const { matchGlob } = await import("./config-XZWUL3ma.mjs").then((n) => n.t);
|
|
30
30
|
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
31
31
|
releases = releases.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
32
32
|
}
|
|
33
33
|
if (opts.json) {
|
|
34
34
|
const jsonOutput = {
|
|
35
|
-
|
|
36
|
-
id:
|
|
37
|
-
summary:
|
|
38
|
-
releases:
|
|
35
|
+
bumpFiles: plan.bumpFiles.map((bf) => ({
|
|
36
|
+
id: bf.id,
|
|
37
|
+
summary: bf.summary,
|
|
38
|
+
releases: bf.releases.map((r) => ({
|
|
39
39
|
name: r.name,
|
|
40
40
|
type: r.type
|
|
41
41
|
}))
|
|
@@ -46,7 +46,7 @@ async function statusCommand(rootDir, opts) {
|
|
|
46
46
|
oldVersion: r.oldVersion,
|
|
47
47
|
newVersion: r.newVersion,
|
|
48
48
|
dir: packages.get(r.name)?.relativeDir,
|
|
49
|
-
|
|
49
|
+
bumpFiles: r.bumpFiles,
|
|
50
50
|
isDependencyBump: r.isDependencyBump,
|
|
51
51
|
isCascadeBump: r.isCascadeBump
|
|
52
52
|
})),
|
|
@@ -59,7 +59,7 @@ async function statusCommand(rootDir, opts) {
|
|
|
59
59
|
for (const r of releases) console.log(r.name);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
log.bold(`${
|
|
62
|
+
log.bold(`${bumpFiles.length} bump file(s) pending\n`);
|
|
63
63
|
if (releases.length === 0) {
|
|
64
64
|
log.warn("No packages match the current filters.");
|
|
65
65
|
return;
|
|
@@ -87,12 +87,16 @@ async function statusCommand(rootDir, opts) {
|
|
|
87
87
|
for (const r of group) printRelease(r, packages);
|
|
88
88
|
console.log();
|
|
89
89
|
}
|
|
90
|
+
if (plan.warnings.length > 0) {
|
|
91
|
+
for (const w of plan.warnings) log.warn(w);
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
90
94
|
if (opts.verbose) {
|
|
91
|
-
log.bold("
|
|
92
|
-
for (const
|
|
93
|
-
console.log(` ${colorize(
|
|
94
|
-
for (const r of
|
|
95
|
-
if (
|
|
95
|
+
log.bold("Bump files:");
|
|
96
|
+
for (const bf of plan.bumpFiles) {
|
|
97
|
+
console.log(` ${colorize(bf.id, "cyan")}`);
|
|
98
|
+
for (const r of bf.releases) console.log(` ${r.name}: ${r.type}`);
|
|
99
|
+
if (bf.summary) console.log(` ${colorize(bf.summary.split("\n")[0], "dim")}`);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
}
|