@varlock/bumpy 0.0.2 → 1.1.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.
Files changed (43) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/config-schema.json +327 -0
  3. package/dist/add-BmNL5VwL.mjs +323 -0
  4. package/dist/{ai-CQhUyHAG.mjs → ai-sMYUf3lP.mjs} +21 -4
  5. package/dist/{apply-release-plan-D6TSrcwX.mjs → apply-release-plan-0kH62jhu.mjs} +35 -26
  6. package/dist/bump-file-DVqR3k67.mjs +157 -0
  7. package/dist/{changelog-github-Du62krXi.mjs → changelog-github-DkACMj0j.mjs} +23 -21
  8. package/dist/check-BjWF6SJm.mjs +65 -0
  9. package/dist/{ci-D6LQbR38.mjs → ci-DY58ugIi.mjs} +138 -91
  10. package/dist/{ci-setup-C6FlOfW5.mjs → ci-setup-BQwktQEe.mjs} +3 -3
  11. package/dist/cli.mjs +36 -41
  12. package/dist/commit-message-BwsowSds.mjs +23 -0
  13. package/dist/{config-BkwIEaQg.mjs → config-B-Qg3DZH.mjs} +30 -24
  14. package/dist/fs-DYR2XuFE.mjs +81 -0
  15. package/dist/generate-DX46X-rW.mjs +186 -0
  16. package/dist/{git-CGHVXXKw.mjs → git-YDedMddc.mjs} +54 -2
  17. package/dist/index.d.mts +68 -39
  18. package/dist/index.mjs +9 -9
  19. package/dist/init-DkTPs_WQ.mjs +196 -0
  20. package/dist/{names-Ck8cun7B.mjs → names-C-TuOPbd.mjs} +1 -1
  21. package/dist/{js-yaml-DpZfOoD4.mjs → package-manager-Clsmr-9r.mjs} +79 -1
  22. package/dist/picomatch-DMmqYjgq.mjs +1870 -0
  23. package/dist/{publish-D_7RqEYL.mjs → publish-CGB4TIKD.mjs} +26 -25
  24. package/dist/{publish-pipeline-ChnqW8nR.mjs → publish-pipeline-CXuqce1N.mjs} +24 -19
  25. package/dist/release-plan-JNir7bSM.mjs +264 -0
  26. package/dist/{semver-BTzYh8vc.mjs → semver-BJzWIuRz.mjs} +13 -3
  27. package/dist/{shell-Dj7JRD_q.mjs → shell-CY7OD48z.mjs} +20 -2
  28. package/dist/{status--Q8yAxQ4.mjs → status-EGYqULJg.mjs} +26 -22
  29. package/dist/{version-cAUkfYPx.mjs → version-BcfidiVX.mjs} +23 -22
  30. package/dist/{workspace-CxEKakDm.mjs → workspace-DWXlwcH4.mjs} +3 -3
  31. package/package.json +16 -1
  32. package/skills/add-change/SKILL.md +18 -14
  33. package/dist/add-BjyVIUlr.mjs +0 -175
  34. package/dist/changeset-UCZdSRDv.mjs +0 -108
  35. package/dist/check-jIwike9F.mjs +0 -51
  36. package/dist/fs-0AtnPUUe.mjs +0 -51
  37. package/dist/generate-Btrsn1qi.mjs +0 -177
  38. package/dist/init-B0q3wEQW.mjs +0 -22
  39. package/dist/migrate-CfQNwD0T.mjs +0 -121
  40. package/dist/package-manager-DcI5TdDE.mjs +0 -80
  41. package/dist/release-plan-BEzwApuK.mjs +0 -173
  42. /package/dist/{clack-CDRCHrC-.mjs → clack-C6bVkGxf.mjs} +0 -0
  43. /package/dist/{dep-graph-E-9-eQ2J.mjs → dep-graph-DiLeAhl9.mjs} +0 -0
@@ -1,177 +0,0 @@
1
- import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
2
- import { t as ensureDir } from "./fs-0AtnPUUe.mjs";
3
- import { a as loadConfig, r as getBumpyDir } from "./config-BkwIEaQg.mjs";
4
- import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
5
- import { o as tryRunArgs } from "./shell-Dj7JRD_q.mjs";
6
- import { i as writeChangeset } from "./changeset-UCZdSRDv.mjs";
7
- import { n as slugify, t as randomName } from "./names-Ck8cun7B.mjs";
8
- //#region src/commands/generate.ts
9
- const BUMP_MAP = {
10
- feat: "minor",
11
- fix: "patch",
12
- perf: "patch",
13
- refactor: "patch",
14
- docs: "patch",
15
- style: "patch",
16
- test: "patch",
17
- build: "patch",
18
- ci: "patch",
19
- chore: "patch"
20
- };
21
- async function generateCommand(rootDir, opts) {
22
- const config = await loadConfig(rootDir);
23
- const packages = await discoverPackages(rootDir, config);
24
- const from = opts.from || findLastVersionTag(rootDir);
25
- if (!from) {
26
- log.error("Could not detect last version tag. Use --from <ref> to specify.");
27
- process.exit(1);
28
- }
29
- log.step(`Scanning commits from ${colorize(from, "cyan")}...`);
30
- const rawLog = tryRunArgs([
31
- "git",
32
- "log",
33
- `${from}..HEAD`,
34
- "--format=%H%n%s%n%b%n---END---"
35
- ], { cwd: rootDir });
36
- if (!rawLog) {
37
- log.info("No commits found since " + from);
38
- return;
39
- }
40
- const conventional = parseGitLog(rawLog).map(parseConventionalCommit).filter((c) => c !== null);
41
- if (conventional.length === 0) {
42
- log.info("No conventional commits found. Commits must follow the format: type(scope): description");
43
- return;
44
- }
45
- log.dim(` Found ${conventional.length} conventional commit(s)`);
46
- const scopeMap = buildScopeMap(packages, config);
47
- const releaseMap = /* @__PURE__ */ new Map();
48
- for (const commit of conventional) {
49
- const bump = commit.breaking ? "major" : BUMP_MAP[commit.type] || "patch";
50
- let pkgNames = [];
51
- if (commit.scope) {
52
- const resolved = resolveScope(commit.scope, scopeMap, packages);
53
- if (resolved.length > 0) pkgNames = resolved;
54
- else {
55
- log.dim(` Skipping: unknown scope "${commit.scope}" in: ${commit.description}`);
56
- continue;
57
- }
58
- } else {
59
- log.dim(` Skipping (no scope): ${commit.type}: ${commit.description}`);
60
- continue;
61
- }
62
- for (const name of pkgNames) {
63
- const existing = releaseMap.get(name);
64
- if (existing) {
65
- if (bumpPriority(bump) > bumpPriority(existing.type)) existing.type = bump;
66
- existing.messages.push(commit.description);
67
- } else releaseMap.set(name, {
68
- type: bump,
69
- messages: [commit.description]
70
- });
71
- }
72
- }
73
- if (releaseMap.size === 0) {
74
- log.info("No package bumps detected from conventional commits.");
75
- return;
76
- }
77
- const releases = [];
78
- const summaryLines = [];
79
- for (const [name, info] of releaseMap) {
80
- releases.push({
81
- name,
82
- type: info.type
83
- });
84
- for (const msg of info.messages) summaryLines.push(`- ${name}: ${msg}`);
85
- }
86
- if (opts.dryRun) {
87
- log.bold("Would create changeset:");
88
- for (const r of releases) console.log(` ${r.name}: ${colorize(r.type, r.type === "major" ? "red" : r.type === "minor" ? "yellow" : "green")}`);
89
- console.log();
90
- log.dim("Summary:");
91
- for (const line of summaryLines) log.dim(` ${line}`);
92
- return;
93
- }
94
- await ensureDir(getBumpyDir(rootDir));
95
- const filename = opts.name ? slugify(opts.name) : randomName();
96
- await writeChangeset(rootDir, filename, releases, summaryLines.join("\n"));
97
- log.success(`Created changeset: .bumpy/${filename}.md`);
98
- for (const r of releases) log.dim(` ${r.name}: ${r.type}`);
99
- }
100
- /** Parse raw git log output into individual commits */
101
- function parseGitLog(raw) {
102
- const commits = [];
103
- const entries = raw.split("---END---").filter((e) => e.trim());
104
- for (const entry of entries) {
105
- const lines = entry.trim().split("\n");
106
- if (lines.length < 2) continue;
107
- commits.push({
108
- hash: lines[0].trim(),
109
- subject: lines[1].trim(),
110
- body: lines.slice(2).join("\n").trim()
111
- });
112
- }
113
- return commits;
114
- }
115
- /** Parse a commit subject into conventional commit format */
116
- function parseConventionalCommit(commit) {
117
- const match = commit.subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?\s*:\s*(.+)$/);
118
- if (!match) return null;
119
- const [, type, bang1, scope, bang2, description] = match;
120
- const breaking = !!bang1 || !!bang2 || commit.body.includes("BREAKING CHANGE");
121
- return {
122
- hash: commit.hash,
123
- type: type.toLowerCase(),
124
- scope: scope || null,
125
- breaking,
126
- description: description.trim(),
127
- body: commit.body
128
- };
129
- }
130
- /** Build a map of scope aliases → package names from config and package names */
131
- function buildScopeMap(packages, _config) {
132
- const map = /* @__PURE__ */ new Map();
133
- for (const [name, pkg] of packages) {
134
- const shortName = name.includes("/") ? name.split("/").pop() : name;
135
- if (!map.has(shortName)) map.set(shortName, []);
136
- map.get(shortName).push(name);
137
- if (!map.has(name)) map.set(name, []);
138
- map.get(name).push(name);
139
- const dirName = pkg.relativeDir.split("/").pop();
140
- if (dirName !== shortName) {
141
- if (!map.has(dirName)) map.set(dirName, []);
142
- map.get(dirName).push(name);
143
- }
144
- }
145
- return map;
146
- }
147
- /** Resolve a scope string to package name(s) */
148
- function resolveScope(scope, scopeMap, packages) {
149
- const mapped = scopeMap.get(scope);
150
- if (mapped) return [...new Set(mapped)];
151
- for (const [key, value] of scopeMap) if (key.toLowerCase() === scope.toLowerCase()) return [...new Set(value)];
152
- if (packages.has(scope)) return [scope];
153
- return [];
154
- }
155
- function bumpPriority(type) {
156
- return type === "major" ? 2 : type === "minor" ? 1 : 0;
157
- }
158
- /** Find the most recent version tag in the repo */
159
- function findLastVersionTag(rootDir) {
160
- return tryRunArgs([
161
- "git",
162
- "describe",
163
- "--tags",
164
- "--abbrev=0",
165
- "--match",
166
- "v*"
167
- ], { cwd: rootDir }) || tryRunArgs([
168
- "git",
169
- "describe",
170
- "--tags",
171
- "--abbrev=0",
172
- "--match",
173
- "*@*"
174
- ], { cwd: rootDir }) || null;
175
- }
176
- //#endregion
177
- export { generateCommand };
@@ -1,22 +0,0 @@
1
- import { n as log } from "./logger-C2dEe5Su.mjs";
2
- import { c as writeJson, l as writeText, n as exists, t as ensureDir } from "./fs-0AtnPUUe.mjs";
3
- import { resolve } from "node:path";
4
- //#region src/commands/init.ts
5
- async function initCommand(rootDir) {
6
- const bumpyDir = resolve(rootDir, ".bumpy");
7
- if (await exists(resolve(bumpyDir, "_config.json"))) {
8
- log.warn(".bumpy/_config.json already exists");
9
- return;
10
- }
11
- await ensureDir(bumpyDir);
12
- await writeJson(resolve(bumpyDir, "_config.json"), {
13
- baseBranch: "main",
14
- changelog: "default"
15
- });
16
- await writeText(resolve(bumpyDir, "README.md"), `# 🐸 Bumpy\n\nThis directory is used by [bumpy](https://github.com/dmno-dev/bumpy) to manage versioning.\n\nChangeset files (\`.md\`) in this directory describe pending version bumps.\nRun \`bumpy add\` to create a new changeset.\n`);
17
- log.success("Initialized .bumpy/ directory");
18
- log.dim(" Created .bumpy/_config.json");
19
- log.dim(" Created .bumpy/README.md");
20
- }
21
- //#endregion
22
- export { initCommand };
@@ -1,121 +0,0 @@
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-0AtnPUUe.mjs";
3
- import { r as getBumpyDir } from "./config-BkwIEaQg.mjs";
4
- import { i as writeChangeset } from "./changeset-UCZdSRDv.mjs";
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-B0q3wEQW.mjs";
7
- import { resolve } from "node:path";
8
- import { readdir } from "node:fs/promises";
9
- //#region src/commands/migrate.ts
10
- var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
11
- async function migrateCommand(rootDir, opts) {
12
- const changesetDir = resolve(rootDir, ".changeset");
13
- if (!await exists(changesetDir)) {
14
- log.error("No .changeset/ directory found. Nothing to migrate.");
15
- process.exit(1);
16
- }
17
- const bumpyDir = getBumpyDir(rootDir);
18
- if (!await exists(resolve(bumpyDir, "_config.json"))) {
19
- log.step("Initializing .bumpy/ directory...");
20
- await initCommand(rootDir);
21
- }
22
- const changesetConfigPath = resolve(changesetDir, "config.json");
23
- if (await exists(changesetConfigPath)) {
24
- log.step("Migrating config from .changeset/config.json...");
25
- await migrateConfig(changesetConfigPath, bumpyDir);
26
- }
27
- const mdFiles = (await readdir(changesetDir)).filter((f) => f.endsWith(".md") && f !== "README.md");
28
- if (mdFiles.length > 0) {
29
- log.step(`Migrating ${mdFiles.length} pending changeset(s)...`);
30
- let migrated = 0;
31
- for (const file of mdFiles) {
32
- const result = parseChangesetFile(await readText(resolve(changesetDir, file)));
33
- if (!result) {
34
- log.warn(` Skipped ${file} (could not parse)`);
35
- continue;
36
- }
37
- const name = file.replace(/\.md$/, "");
38
- if (await exists(resolve(bumpyDir, file))) {
39
- log.dim(` Skipped ${file} (already exists in .bumpy/)`);
40
- continue;
41
- }
42
- await writeChangeset(rootDir, name, result.releases, result.summary);
43
- migrated++;
44
- log.dim(` Migrated ${file}`);
45
- }
46
- log.success(`Migrated ${migrated} changeset(s)`);
47
- } else log.info("No pending changesets to migrate.");
48
- if (!opts.force) {
49
- mt(import_picocolors.default.bgCyan(import_picocolors.default.black(" bumpy migrate ")));
50
- if (unwrap(await ot({
51
- message: "Remove .changeset/ directory?",
52
- initialValue: false
53
- }))) {
54
- const spin = fe();
55
- spin.start("Removing .changeset/");
56
- const { rm } = await import("node:fs/promises");
57
- await rm(changesetDir, { recursive: true });
58
- spin.stop("Removed .changeset/ directory");
59
- } else O.info("Keeping .changeset/ — you can remove it manually when ready.");
60
- gt(import_picocolors.default.green("Cleanup complete"));
61
- }
62
- console.log();
63
- log.success("Migration complete!");
64
- log.dim("Review .bumpy/_config.json and adjust settings as needed.");
65
- log.dim("Key differences from changesets:");
66
- log.dim(" - peerDependency bumps only propagate on major (not minor)");
67
- log.dim(" - Use 'patch-isolated'/'minor-isolated' to skip propagation");
68
- log.dim(" - Per-package config goes in package.json[\"bumpy\"]");
69
- }
70
- async function migrateConfig(changesetConfigPath, bumpyDir) {
71
- const csConfig = await readJson(changesetConfigPath);
72
- const bumpyConfigPath = resolve(bumpyDir, "_config.json");
73
- let bumpyConfig = {};
74
- if (await exists(bumpyConfigPath)) bumpyConfig = await readJson(bumpyConfigPath);
75
- for (const field of [
76
- "baseBranch",
77
- "access",
78
- "commit",
79
- "fixed",
80
- "linked",
81
- "ignore",
82
- "updateInternalDependencies",
83
- "privatePackages"
84
- ]) if (csConfig[field] !== void 0) bumpyConfig[field] = csConfig[field];
85
- const { writeJson } = await import("./fs-0AtnPUUe.mjs").then((n) => n.r);
86
- await writeJson(bumpyConfigPath, bumpyConfig);
87
- log.dim(" Migrated config fields: " + Object.keys(bumpyConfig).filter((k) => k !== "baseBranch" || bumpyConfig[k] !== "main").join(", "));
88
- }
89
- /** Parse a changesets-format markdown file */
90
- function parseChangesetFile(content) {
91
- const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
92
- if (!match) return null;
93
- const frontmatter = match[1].trim();
94
- const summary = match[2].trim();
95
- if (!frontmatter) return null;
96
- const releases = [];
97
- for (const line of frontmatter.split("\n")) {
98
- const trimmed = line.trim();
99
- if (!trimmed) continue;
100
- const lineMatch = trimmed.match(/^"?([^"]+)"?\s*:\s*(.+)$/);
101
- if (!lineMatch) continue;
102
- const name = lineMatch[1].trim();
103
- const type = lineMatch[2].trim();
104
- if (type === "none") continue;
105
- if ([
106
- "major",
107
- "minor",
108
- "patch"
109
- ].includes(type)) releases.push({
110
- name,
111
- type
112
- });
113
- }
114
- if (releases.length === 0 && !summary) return null;
115
- return {
116
- releases,
117
- summary
118
- };
119
- }
120
- //#endregion
121
- export { migrateCommand };
@@ -1,80 +0,0 @@
1
- import { a as readJson, n as exists, o as readText } from "./fs-0AtnPUUe.mjs";
2
- import { t as jsYaml } from "./js-yaml-DpZfOoD4.mjs";
3
- import { resolve } from "node:path";
4
- //#region src/utils/package-manager.ts
5
- /** Detect the package manager, extract workspace globs, and load catalogs */
6
- async function detectWorkspaces(rootDir) {
7
- const pm = await detectPackageManager(rootDir);
8
- return {
9
- packageManager: pm,
10
- globs: await getWorkspaceGlobs(rootDir, pm),
11
- catalogs: await loadCatalogs(rootDir, pm)
12
- };
13
- }
14
- async function detectPackageManager(rootDir) {
15
- if (await exists(resolve(rootDir, "bun.lock")) || await exists(resolve(rootDir, "bun.lockb"))) return "bun";
16
- if (await exists(resolve(rootDir, "pnpm-lock.yaml"))) return "pnpm";
17
- if (await exists(resolve(rootDir, "yarn.lock"))) return "yarn";
18
- try {
19
- const pkg = await readJson(resolve(rootDir, "package.json"));
20
- if (typeof pkg.packageManager === "string") {
21
- const name = pkg.packageManager.split("@")[0];
22
- if (name === "pnpm" || name === "yarn" || name === "bun") return name;
23
- }
24
- } catch {}
25
- return "npm";
26
- }
27
- async function getWorkspaceGlobs(rootDir, pm) {
28
- if (pm === "pnpm") {
29
- const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
30
- if (await exists(wsFile)) {
31
- const content = await readText(wsFile);
32
- const parsed = jsYaml.load(content);
33
- if (parsed?.packages) return parsed.packages;
34
- }
35
- }
36
- try {
37
- const workspaces = (await readJson(resolve(rootDir, "package.json"))).workspaces;
38
- if (Array.isArray(workspaces)) return workspaces;
39
- if (workspaces && typeof workspaces === "object" && "packages" in workspaces) {
40
- const pkgs = workspaces.packages;
41
- if (Array.isArray(pkgs)) return pkgs;
42
- }
43
- } catch {}
44
- return [];
45
- }
46
- /** Load catalog definitions from pnpm-workspace.yaml or root package.json */
47
- async function loadCatalogs(rootDir, pm) {
48
- const catalogs = /* @__PURE__ */ new Map();
49
- if (pm === "pnpm") {
50
- const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
51
- if (await exists(wsFile)) {
52
- const content = await readText(wsFile);
53
- const parsed = jsYaml.load(content);
54
- if (parsed?.catalog) catalogs.set("", parsed.catalog);
55
- if (parsed?.catalogs) for (const [name, deps] of Object.entries(parsed.catalogs)) catalogs.set(name, deps);
56
- }
57
- }
58
- try {
59
- const pkg = await readJson(resolve(rootDir, "package.json"));
60
- if (pkg.catalog && typeof pkg.catalog === "object") catalogs.set("", pkg.catalog);
61
- if (pkg.catalogs && typeof pkg.catalogs === "object") for (const [name, deps] of Object.entries(pkg.catalogs)) catalogs.set(name, deps);
62
- const workspaces = pkg.workspaces;
63
- if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
64
- const ws = workspaces;
65
- if (ws.catalog && typeof ws.catalog === "object") catalogs.set("", ws.catalog);
66
- if (ws.catalogs && typeof ws.catalogs === "object") for (const [name, deps] of Object.entries(ws.catalogs)) catalogs.set(name, deps);
67
- }
68
- } catch {}
69
- return catalogs;
70
- }
71
- /** Resolve a specific dependency's catalog: reference */
72
- function resolveCatalogDep(depName, range, catalogs) {
73
- if (!range.startsWith("catalog:")) return null;
74
- const catalogName = range.slice(8).trim() || "";
75
- const catalog = catalogs.get(catalogName);
76
- if (!catalog) return null;
77
- return catalog[depName] ?? null;
78
- }
79
- //#endregion
80
- export { detectWorkspaces as n, resolveCatalogDep as r, detectPackageManager as t };
@@ -1,173 +0,0 @@
1
- import { g as parseIsolatedBump, h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, p as bumpLevel, s as matchGlob } from "./config-BkwIEaQg.mjs";
2
- import { n as satisfies, r as stripProtocol, t as bumpVersion } from "./semver-BTzYh8vc.mjs";
3
- //#region src/core/release-plan.ts
4
- /**
5
- * Build a release plan from pending changesets, the dependency graph, and config.
6
- * This is the core algorithm of bumpy.
7
- */
8
- function assembleReleasePlan(changesets, packages, depGraph, config) {
9
- if (changesets.length === 0) return {
10
- changesets: [],
11
- releases: []
12
- };
13
- const planned = /* @__PURE__ */ new Map();
14
- const cascadeOverrides = /* @__PURE__ */ new Map();
15
- for (const cs of changesets) for (const release of cs.releases) {
16
- if (!packages.has(release.name)) continue;
17
- const { bump, isolated } = parseIsolatedBump(release.type);
18
- const existing = planned.get(release.name);
19
- if (existing) {
20
- existing.type = maxBump(existing.type, bump);
21
- if (!isolated) existing.isolated = false;
22
- existing.changesets.add(cs.id);
23
- } else planned.set(release.name, {
24
- type: bump,
25
- isolated,
26
- isDependencyBump: false,
27
- isCascadeBump: false,
28
- changesets: new Set([cs.id])
29
- });
30
- if (hasCascade(release)) {
31
- if (!cascadeOverrides.has(release.name)) cascadeOverrides.set(release.name, /* @__PURE__ */ new Map());
32
- const overrides = cascadeOverrides.get(release.name);
33
- for (const [pattern, bumpType] of Object.entries(release.cascade)) {
34
- const existing = overrides.get(pattern);
35
- overrides.set(pattern, maxBump(existing, bumpType));
36
- }
37
- }
38
- }
39
- for (const group of config.fixed) {
40
- let groupBump;
41
- let groupIsolated = true;
42
- for (const nameOrGlob of group) for (const [name, bump] of planned) if (matchGlob(name, nameOrGlob)) {
43
- groupBump = maxBump(groupBump, bump.type);
44
- if (!bump.isolated) groupIsolated = false;
45
- }
46
- if (!groupBump) continue;
47
- for (const nameOrGlob of group) for (const [name] of packages) {
48
- if (!matchGlob(name, nameOrGlob)) continue;
49
- const existing = planned.get(name);
50
- if (existing) {
51
- existing.type = groupBump;
52
- existing.isolated = groupIsolated;
53
- } else planned.set(name, {
54
- type: groupBump,
55
- isolated: groupIsolated,
56
- isDependencyBump: false,
57
- isCascadeBump: false,
58
- changesets: /* @__PURE__ */ new Set()
59
- });
60
- }
61
- }
62
- for (const group of config.linked) {
63
- let groupBump;
64
- for (const nameOrGlob of group) for (const [name, bump] of planned) if (matchGlob(name, nameOrGlob)) groupBump = maxBump(groupBump, bump.type);
65
- if (!groupBump) continue;
66
- for (const nameOrGlob of group) for (const [name] of packages) {
67
- if (!matchGlob(name, nameOrGlob)) continue;
68
- const existing = planned.get(name);
69
- if (existing) existing.type = groupBump;
70
- }
71
- }
72
- let changed = true;
73
- let iterations = 0;
74
- const MAX_ITERATIONS = 100;
75
- while (changed && iterations < MAX_ITERATIONS) {
76
- changed = false;
77
- iterations++;
78
- for (const [pkgName, bump] of planned) {
79
- if (bump.isolated) continue;
80
- const csOverrides = cascadeOverrides.get(pkgName);
81
- if (csOverrides) for (const [pattern, cascadeBumpType] of csOverrides) for (const [targetName] of packages) {
82
- if (!matchGlob(targetName, pattern)) continue;
83
- if (applyBump(planned, targetName, cascadeBumpType, false, true, bump.changesets)) changed = true;
84
- }
85
- const cascadeTo = packages.get(pkgName)?.bumpy?.cascadeTo;
86
- if (cascadeTo) for (const [pattern, rule] of Object.entries(cascadeTo)) {
87
- if (!shouldTrigger(bump.type, rule.trigger)) continue;
88
- const cascadeBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
89
- for (const [targetName] of packages) {
90
- if (!matchGlob(targetName, pattern)) continue;
91
- if (applyBump(planned, targetName, cascadeBump, false, true, bump.changesets)) changed = true;
92
- }
93
- }
94
- const dependents = depGraph.getDependents(pkgName);
95
- for (const dep of dependents) {
96
- const rule = resolveRule(dep.name, pkgName, dep.depType, packages, config);
97
- if (!shouldTrigger(bump.type, rule.trigger)) continue;
98
- if (config.updateInternalDependencies === "out-of-range") {
99
- if (satisfies(bumpVersion(packages.get(pkgName).version, bump.type), stripProtocol(dep.versionRange))) continue;
100
- }
101
- if (config.updateInternalDependencies === "none") continue;
102
- if (config.updateInternalDependencies === "minor" && bumpLevel(bump.type) < bumpLevel("minor")) continue;
103
- const depBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
104
- if (applyBump(planned, dep.name, depBump, true, false, bump.changesets)) changed = true;
105
- }
106
- }
107
- }
108
- const releases = [];
109
- for (const [name, bump] of planned) {
110
- const pkg = packages.get(name);
111
- if (!pkg) continue;
112
- const newVersion = bumpVersion(pkg.version, bump.type);
113
- releases.push({
114
- name,
115
- type: bump.type,
116
- oldVersion: pkg.version,
117
- newVersion,
118
- changesets: [...bump.changesets],
119
- isDependencyBump: bump.isDependencyBump,
120
- isCascadeBump: bump.isCascadeBump
121
- });
122
- }
123
- releases.sort((a, b) => a.name.localeCompare(b.name));
124
- return {
125
- changesets,
126
- releases
127
- };
128
- }
129
- /** Apply a bump to a package, upgrading if already planned. Returns true if anything changed. */
130
- function applyBump(planned, name, type, isDependencyBump, isCascadeBump, sourceChangesets) {
131
- const existing = planned.get(name);
132
- if (existing) {
133
- const newType = maxBump(existing.type, type);
134
- if (newType === existing.type) return false;
135
- existing.type = newType;
136
- if (isDependencyBump) existing.isDependencyBump = true;
137
- if (isCascadeBump) existing.isCascadeBump = true;
138
- for (const cs of sourceChangesets) existing.changesets.add(cs);
139
- return true;
140
- }
141
- planned.set(name, {
142
- type,
143
- isolated: false,
144
- isDependencyBump,
145
- isCascadeBump,
146
- changesets: new Set(sourceChangesets)
147
- });
148
- return true;
149
- }
150
- /** Check if a bump level meets the trigger threshold */
151
- function shouldTrigger(bumpType, trigger) {
152
- if (trigger === "none") return false;
153
- return bumpLevel(bumpType) >= bumpLevel(trigger);
154
- }
155
- /**
156
- * Resolve the dependency bump rule for a specific dependent/dependency pair.
157
- * Priority: specificDependencyRules > per-package depType rules > global depType rules > defaults
158
- */
159
- function resolveRule(dependentName, dependencyName, depType, packages, config) {
160
- const dependent = packages.get(dependentName);
161
- if (dependent?.bumpy?.specificDependencyRules?.[dependencyName]) return dependent.bumpy.specificDependencyRules[dependencyName];
162
- if (dependent?.bumpy?.specificDependencyRules) {
163
- for (const [pattern, rule] of Object.entries(dependent.bumpy.specificDependencyRules)) if (matchGlob(dependencyName, pattern)) return rule;
164
- }
165
- if (dependent?.bumpy?.dependencyBumpRules?.[depType]) return dependent.bumpy.dependencyBumpRules[depType];
166
- if (config.dependencyBumpRules[depType]) return config.dependencyBumpRules[depType];
167
- return DEFAULT_BUMP_RULES[depType] || {
168
- trigger: "patch",
169
- bumpAs: "patch"
170
- };
171
- }
172
- //#endregion
173
- export { assembleReleasePlan as t };
File without changes