@varlock/bumpy 0.0.0 → 0.0.1
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 +13 -0
- package/dist/add-u5V9V3L7.mjs +131 -0
- package/dist/ai-B8ZL2x8z.mjs +82 -0
- package/dist/apply-release-plan-DtU3rVyL.mjs +132 -0
- package/dist/changelog-github-n-3zV1p9.mjs +59 -0
- package/dist/changeset-ClCYsChu.mjs +75 -0
- package/dist/check-CkRubvuk.mjs +57 -0
- package/dist/ci-8KWWhjXl.mjs +224 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +210 -0
- package/dist/config-CJ2orhTL.mjs +215 -0
- package/dist/dep-graph-DiLeAhl9.mjs +64 -0
- package/dist/fs-DbNNEyzq.mjs +51 -0
- package/dist/generate-oOFD9ABC.mjs +158 -0
- package/dist/index.d.mts +254 -0
- package/dist/index.mjs +9 -0
- package/dist/init-Blw2GfC_.mjs +22 -0
- package/dist/js-yaml-DpZfOoD4.mjs +2031 -0
- package/dist/logger-ZqggsyGZ.mjs +176 -0
- package/dist/migrate-DvOrXSw0.mjs +114 -0
- package/dist/names-C-u50ofE.mjs +143 -0
- package/dist/prompt-BP8toAOI.mjs +46 -0
- package/dist/publish-DZ3m7qkX.mjs +197 -0
- package/dist/publish-pipeline-1M5GmbdP.mjs +291 -0
- package/dist/release-plan-CFnutSHD.mjs +173 -0
- package/dist/semver-DWO6NFKN.mjs +1360 -0
- package/dist/shell-DPlltpzb.mjs +44 -0
- package/dist/status-DRpq_Mha.mjs +106 -0
- package/dist/version-CJwf8XIA.mjs +81 -0
- package/dist/workspace-mVjawG8g.mjs +183 -0
- package/package.json +36 -6
- package/skills/add-change/SKILL.md +108 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-ZqggsyGZ.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-CJ2orhTL.mjs";
|
|
3
|
+
import { n as discoverWorkspace } from "./workspace-mVjawG8g.mjs";
|
|
4
|
+
import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
|
|
5
|
+
import { r as readChangesets } from "./changeset-ClCYsChu.mjs";
|
|
6
|
+
import { t as assembleReleasePlan } from "./release-plan-CFnutSHD.mjs";
|
|
7
|
+
import { i as tryRun, n as runAsync, t as run } from "./shell-DPlltpzb.mjs";
|
|
8
|
+
//#region src/commands/ci.ts
|
|
9
|
+
/** Configure git identity for CI commits if not already set */
|
|
10
|
+
function ensureGitIdentity(rootDir, config) {
|
|
11
|
+
if (!tryRun("git config user.name", { cwd: rootDir })) {
|
|
12
|
+
const { name: gitName, email: gitEmail } = config.gitUser;
|
|
13
|
+
run(`git config user.name "${gitName}"`, { cwd: rootDir });
|
|
14
|
+
run(`git config user.email "${gitEmail}"`, { cwd: rootDir });
|
|
15
|
+
log.dim(` Using git identity: ${gitName} <${gitEmail}>`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* CI check: report on pending changesets.
|
|
20
|
+
* Designed for PR workflows — shows what would be released and optionally comments on the PR.
|
|
21
|
+
*/
|
|
22
|
+
async function ciCheckCommand(rootDir, opts) {
|
|
23
|
+
const config = await loadConfig(rootDir);
|
|
24
|
+
const { packages } = await discoverWorkspace(rootDir, config);
|
|
25
|
+
const depGraph = new DependencyGraph(packages);
|
|
26
|
+
const changesets = await readChangesets(rootDir);
|
|
27
|
+
const inCI = !!process.env.CI;
|
|
28
|
+
const shouldComment = opts.comment ?? inCI;
|
|
29
|
+
const prNumber = detectPrNumber();
|
|
30
|
+
if (changesets.length === 0) {
|
|
31
|
+
log.info("No changesets found in this PR.");
|
|
32
|
+
if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatNoChangesetsComment(), rootDir);
|
|
33
|
+
if (opts.failOnMissing) process.exit(1);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const plan = assembleReleasePlan(changesets, packages, depGraph, config);
|
|
37
|
+
log.bold(`${changesets.length} changeset(s) → ${plan.releases.length} package(s) to release\n`);
|
|
38
|
+
for (const r of plan.releases) {
|
|
39
|
+
const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
|
|
40
|
+
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${tag}`);
|
|
41
|
+
}
|
|
42
|
+
if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, changesets.length), rootDir);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* CI release: either auto-publish or create a version PR.
|
|
46
|
+
* Designed for merge-to-main workflows.
|
|
47
|
+
*/
|
|
48
|
+
async function ciReleaseCommand(rootDir, opts) {
|
|
49
|
+
const config = await loadConfig(rootDir);
|
|
50
|
+
ensureGitIdentity(rootDir, config);
|
|
51
|
+
const { packages } = await discoverWorkspace(rootDir, config);
|
|
52
|
+
const depGraph = new DependencyGraph(packages);
|
|
53
|
+
const changesets = await readChangesets(rootDir);
|
|
54
|
+
if (changesets.length === 0) {
|
|
55
|
+
log.info("No pending changesets — checking for unpublished packages...");
|
|
56
|
+
const { publishCommand } = await import("./publish-DZ3m7qkX.mjs");
|
|
57
|
+
await publishCommand(rootDir, { tag: opts.tag });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const plan = assembleReleasePlan(changesets, packages, depGraph, config);
|
|
61
|
+
if (plan.releases.length === 0) {
|
|
62
|
+
log.info("Changesets found but no packages would be released.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (opts.mode === "auto-publish") await autoPublish(rootDir, config, opts.tag);
|
|
66
|
+
else await createVersionPr(rootDir, plan, config, opts.branch);
|
|
67
|
+
}
|
|
68
|
+
async function autoPublish(rootDir, config, tag) {
|
|
69
|
+
log.step("Running bumpy version...");
|
|
70
|
+
const { versionCommand } = await import("./version-CJwf8XIA.mjs");
|
|
71
|
+
await versionCommand(rootDir);
|
|
72
|
+
log.step("Committing version changes...");
|
|
73
|
+
run("git add -A", { cwd: rootDir });
|
|
74
|
+
if (tryRun("git status --porcelain", { cwd: rootDir })) {
|
|
75
|
+
run("git commit -m \"Version packages\"", { cwd: rootDir });
|
|
76
|
+
run("git push", { cwd: rootDir });
|
|
77
|
+
}
|
|
78
|
+
log.step("Running bumpy publish...");
|
|
79
|
+
const { publishCommand } = await import("./publish-DZ3m7qkX.mjs");
|
|
80
|
+
await publishCommand(rootDir, { tag });
|
|
81
|
+
}
|
|
82
|
+
async function createVersionPr(rootDir, plan, config, branchName) {
|
|
83
|
+
const branch = branchName || config.versionPr.branch;
|
|
84
|
+
const baseBranch = tryRun("git rev-parse --abbrev-ref HEAD", { cwd: rootDir }) || "main";
|
|
85
|
+
const existingPr = tryRun(`gh pr list --head "${branch}" --json number --jq ".[0].number"`, { cwd: rootDir });
|
|
86
|
+
log.step(`Creating branch ${branch}...`);
|
|
87
|
+
if (tryRun(`git rev-parse --verify ${branch}`, { cwd: rootDir }) !== null) {
|
|
88
|
+
run(`git checkout ${branch}`, { cwd: rootDir });
|
|
89
|
+
run(`git reset --hard ${baseBranch}`, { cwd: rootDir });
|
|
90
|
+
} else run(`git checkout -b ${branch}`, { cwd: rootDir });
|
|
91
|
+
log.step("Running bumpy version...");
|
|
92
|
+
const { versionCommand } = await import("./version-CJwf8XIA.mjs");
|
|
93
|
+
await versionCommand(rootDir);
|
|
94
|
+
run("git add -A", { cwd: rootDir });
|
|
95
|
+
if (!tryRun("git status --porcelain", { cwd: rootDir })) {
|
|
96
|
+
log.info("No version changes to commit.");
|
|
97
|
+
run(`git checkout ${baseBranch}`, { cwd: rootDir });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
run("git commit -F -", {
|
|
101
|
+
cwd: rootDir,
|
|
102
|
+
input: [
|
|
103
|
+
"Version packages",
|
|
104
|
+
"",
|
|
105
|
+
...plan.releases.map((r) => `${r.name}@${r.newVersion}`)
|
|
106
|
+
].join("\n")
|
|
107
|
+
});
|
|
108
|
+
run(`git push -u origin ${branch} --force`, { cwd: rootDir });
|
|
109
|
+
const prBody = formatVersionPrBody(plan, config.versionPr.preamble);
|
|
110
|
+
if (existingPr) {
|
|
111
|
+
log.step(`Updating existing PR #${existingPr}...`);
|
|
112
|
+
await runAsync(`gh pr edit ${existingPr} --title "${config.versionPr.title}" --body-file -`, {
|
|
113
|
+
cwd: rootDir,
|
|
114
|
+
input: prBody
|
|
115
|
+
});
|
|
116
|
+
log.success(`Updated PR #${existingPr}`);
|
|
117
|
+
} else {
|
|
118
|
+
log.step("Creating version PR...");
|
|
119
|
+
const prTitle = config.versionPr.title;
|
|
120
|
+
const result = await runAsync(`gh pr create --title "${prTitle}" --body-file - --base "${baseBranch}" --head "${branch}"`, {
|
|
121
|
+
cwd: rootDir,
|
|
122
|
+
input: prBody
|
|
123
|
+
});
|
|
124
|
+
log.success(`Created PR: ${result}`);
|
|
125
|
+
}
|
|
126
|
+
run(`git checkout ${baseBranch}`, { cwd: rootDir });
|
|
127
|
+
}
|
|
128
|
+
function formatReleasePlanComment(plan, changesetCount) {
|
|
129
|
+
const lines = [];
|
|
130
|
+
lines.push("## 🐸 Bumpy Release Plan\n");
|
|
131
|
+
lines.push(`**${changesetCount}** changeset(s) → **${plan.releases.length}** package(s) to release\n`);
|
|
132
|
+
const groups = [
|
|
133
|
+
["🔴 Major", plan.releases.filter((r) => r.type === "major")],
|
|
134
|
+
["🟡 Minor", plan.releases.filter((r) => r.type === "minor")],
|
|
135
|
+
["🟢 Patch", plan.releases.filter((r) => r.type === "patch")]
|
|
136
|
+
];
|
|
137
|
+
for (const [label, group] of groups) {
|
|
138
|
+
if (group.length === 0) continue;
|
|
139
|
+
lines.push(`### ${label}\n`);
|
|
140
|
+
lines.push("| Package | Change |");
|
|
141
|
+
lines.push("|---------|--------|");
|
|
142
|
+
for (const r of group) {
|
|
143
|
+
const suffix = r.isDependencyBump ? " _(dep)_" : r.isCascadeBump ? " _(cascade)_" : "";
|
|
144
|
+
lines.push(`| \`${r.name}\` | ${r.oldVersion} → **${r.newVersion}**${suffix} |`);
|
|
145
|
+
}
|
|
146
|
+
lines.push("");
|
|
147
|
+
}
|
|
148
|
+
lines.push("---");
|
|
149
|
+
lines.push(`_This comment is maintained by [bumpy](https://github.com/dmno-dev/bumpy)._`);
|
|
150
|
+
return lines.join("\n");
|
|
151
|
+
}
|
|
152
|
+
function formatNoChangesetsComment() {
|
|
153
|
+
return [
|
|
154
|
+
"## 🐸 Bumpy Release Plan\n",
|
|
155
|
+
"No changesets found in this PR. If this PR should trigger a release, run:\n",
|
|
156
|
+
"```bash",
|
|
157
|
+
"bumpy add",
|
|
158
|
+
"```\n",
|
|
159
|
+
"---",
|
|
160
|
+
`_This comment is maintained by [bumpy](https://github.com/dmno-dev/bumpy)._`
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
const FROG_IMG_BASE = "https://raw.githubusercontent.com/dmno-dev/bumpy/main/images";
|
|
164
|
+
function bumpSectionHeader(type) {
|
|
165
|
+
return `### ${`<img src="${FROG_IMG_BASE}/frog-${type}.png" alt="${type}" width="52" style="image-rendering: pixelated;" align="right" />`} ${type.charAt(0).toUpperCase() + type.slice(1)} releases`;
|
|
166
|
+
}
|
|
167
|
+
function formatVersionPrBody(plan, preamble) {
|
|
168
|
+
const lines = [];
|
|
169
|
+
lines.push(preamble);
|
|
170
|
+
lines.push("");
|
|
171
|
+
const groups = {
|
|
172
|
+
major: [],
|
|
173
|
+
minor: [],
|
|
174
|
+
patch: []
|
|
175
|
+
};
|
|
176
|
+
for (const r of plan.releases) groups[r.type]?.push(r);
|
|
177
|
+
for (const type of [
|
|
178
|
+
"major",
|
|
179
|
+
"minor",
|
|
180
|
+
"patch"
|
|
181
|
+
]) {
|
|
182
|
+
const releases = groups[type];
|
|
183
|
+
if (!releases || releases.length === 0) continue;
|
|
184
|
+
lines.push(bumpSectionHeader(type));
|
|
185
|
+
lines.push("");
|
|
186
|
+
for (const r of releases) {
|
|
187
|
+
const suffix = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
|
|
188
|
+
lines.push(`- \`${r.name}\` ${r.oldVersion} → **${r.newVersion}**${suffix}`);
|
|
189
|
+
}
|
|
190
|
+
lines.push("");
|
|
191
|
+
}
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
const COMMENT_MARKER = "<!-- bumpy-release-plan -->";
|
|
195
|
+
async function postOrUpdatePrComment(prNumber, body, rootDir) {
|
|
196
|
+
const markedBody = `${COMMENT_MARKER}\n${body}`;
|
|
197
|
+
try {
|
|
198
|
+
const existingComment = tryRun(`gh pr view ${prNumber} --json comments --jq '.comments[] | select(.body | startswith("${COMMENT_MARKER}")) | .id' | head -1`, { cwd: rootDir });
|
|
199
|
+
if (existingComment) {
|
|
200
|
+
await runAsync(`gh api repos/{owner}/{repo}/issues/comments/${existingComment} -X PATCH -f body=@-`, {
|
|
201
|
+
cwd: rootDir,
|
|
202
|
+
input: markedBody
|
|
203
|
+
});
|
|
204
|
+
log.dim(" Updated PR comment");
|
|
205
|
+
} else {
|
|
206
|
+
await runAsync(`gh pr comment ${prNumber} --body-file -`, {
|
|
207
|
+
cwd: rootDir,
|
|
208
|
+
input: markedBody
|
|
209
|
+
});
|
|
210
|
+
log.dim(" Posted PR comment");
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
log.warn(` Failed to comment on PR: ${err instanceof Error ? err.message : err}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function detectPrNumber() {
|
|
217
|
+
if (process.env.GITHUB_EVENT_NAME === "pull_request") {
|
|
218
|
+
const match = process.env.GITHUB_REF?.match(/refs\/pull\/(\d+)\//);
|
|
219
|
+
if (match) return match[1];
|
|
220
|
+
}
|
|
221
|
+
return process.env.BUMPY_PR_NUMBER || process.env.PR_NUMBER || null;
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { ciCheckCommand, ciReleaseCommand };
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as log, t as colorize } from "./logger-ZqggsyGZ.mjs";
|
|
3
|
+
import { n as findRoot } from "./config-CJ2orhTL.mjs";
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const command = args[0];
|
|
7
|
+
function parseFlags(args) {
|
|
8
|
+
const flags = {};
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
const arg = args[i];
|
|
11
|
+
if (arg.startsWith("--")) {
|
|
12
|
+
const key = arg.slice(2);
|
|
13
|
+
const next = args[i + 1];
|
|
14
|
+
if (next && !next.startsWith("--")) {
|
|
15
|
+
flags[key] = next;
|
|
16
|
+
i++;
|
|
17
|
+
} else flags[key] = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return flags;
|
|
21
|
+
}
|
|
22
|
+
async function main() {
|
|
23
|
+
const flags = parseFlags(args.slice(1));
|
|
24
|
+
try {
|
|
25
|
+
switch (command) {
|
|
26
|
+
case "init": {
|
|
27
|
+
const rootDir = await findRoot();
|
|
28
|
+
const { initCommand } = await import("./init-Blw2GfC_.mjs");
|
|
29
|
+
await initCommand(rootDir);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "add": {
|
|
33
|
+
const rootDir = await findRoot();
|
|
34
|
+
const { addCommand } = await import("./add-u5V9V3L7.mjs");
|
|
35
|
+
await addCommand(rootDir, {
|
|
36
|
+
packages: flags.packages,
|
|
37
|
+
message: flags.message,
|
|
38
|
+
name: flags.name,
|
|
39
|
+
empty: flags.empty === true
|
|
40
|
+
});
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "status": {
|
|
44
|
+
const rootDir = await findRoot();
|
|
45
|
+
const { statusCommand } = await import("./status-DRpq_Mha.mjs");
|
|
46
|
+
await statusCommand(rootDir, {
|
|
47
|
+
json: flags.json === true,
|
|
48
|
+
packagesOnly: flags.packages === true,
|
|
49
|
+
bumpType: flags.bump,
|
|
50
|
+
filter: flags.filter,
|
|
51
|
+
verbose: flags.verbose === true
|
|
52
|
+
});
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case "version": {
|
|
56
|
+
const rootDir = await findRoot();
|
|
57
|
+
const { versionCommand } = await import("./version-CJwf8XIA.mjs");
|
|
58
|
+
await versionCommand(rootDir);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "generate": {
|
|
62
|
+
const rootDir = await findRoot();
|
|
63
|
+
const { generateCommand } = await import("./generate-oOFD9ABC.mjs");
|
|
64
|
+
await generateCommand(rootDir, {
|
|
65
|
+
from: flags.from,
|
|
66
|
+
dryRun: flags["dry-run"] === true,
|
|
67
|
+
name: flags.name
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "migrate": {
|
|
72
|
+
const rootDir = await findRoot();
|
|
73
|
+
const { migrateCommand } = await import("./migrate-DvOrXSw0.mjs");
|
|
74
|
+
await migrateCommand(rootDir, { force: flags.force === true });
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "check": {
|
|
78
|
+
const rootDir = await findRoot();
|
|
79
|
+
const { checkCommand } = await import("./check-CkRubvuk.mjs");
|
|
80
|
+
await checkCommand(rootDir);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case "ci": {
|
|
84
|
+
const rootDir = await findRoot();
|
|
85
|
+
const subcommand = args[1];
|
|
86
|
+
const ciFlags = parseFlags(args.slice(2));
|
|
87
|
+
if (subcommand === "check") {
|
|
88
|
+
const { ciCheckCommand } = await import("./ci-8KWWhjXl.mjs");
|
|
89
|
+
await ciCheckCommand(rootDir, {
|
|
90
|
+
comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
|
|
91
|
+
failOnMissing: ciFlags["fail-on-missing"] === true
|
|
92
|
+
});
|
|
93
|
+
} else if (subcommand === "release") {
|
|
94
|
+
const { ciReleaseCommand } = await import("./ci-8KWWhjXl.mjs");
|
|
95
|
+
await ciReleaseCommand(rootDir, {
|
|
96
|
+
mode: ciFlags["auto-publish"] === true ? "auto-publish" : "version-pr",
|
|
97
|
+
tag: ciFlags.tag,
|
|
98
|
+
branch: ciFlags.branch
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
log.error(`Unknown ci subcommand: ${subcommand}. Use "ci check" or "ci release".`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case "publish": {
|
|
107
|
+
const rootDir = await findRoot();
|
|
108
|
+
const { publishCommand } = await import("./publish-DZ3m7qkX.mjs");
|
|
109
|
+
await publishCommand(rootDir, {
|
|
110
|
+
dryRun: flags["dry-run"] === true,
|
|
111
|
+
tag: flags.tag,
|
|
112
|
+
noPush: flags["no-push"] === true,
|
|
113
|
+
filter: flags.filter
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "ai": {
|
|
118
|
+
const rootDir = await findRoot();
|
|
119
|
+
const subcommand = args[1];
|
|
120
|
+
const aiFlags = parseFlags(args.slice(2));
|
|
121
|
+
if (subcommand === "setup") {
|
|
122
|
+
const { aiSetupCommand } = await import("./ai-B8ZL2x8z.mjs");
|
|
123
|
+
await aiSetupCommand(rootDir, { target: aiFlags.target });
|
|
124
|
+
} else {
|
|
125
|
+
log.error(`Unknown ai subcommand: ${subcommand}. Use "ai setup".`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "--version":
|
|
131
|
+
case "-v":
|
|
132
|
+
console.log(`bumpy 0.0.1`);
|
|
133
|
+
break;
|
|
134
|
+
case "help":
|
|
135
|
+
case "--help":
|
|
136
|
+
case "-h":
|
|
137
|
+
case void 0:
|
|
138
|
+
printHelp();
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
log.error(`Unknown command: ${command}`);
|
|
142
|
+
printHelp();
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function printHelp() {
|
|
151
|
+
console.log(`
|
|
152
|
+
${colorize(`🐸 bumpy v0.0.1`, "bold")} - Modern monorepo versioning
|
|
153
|
+
|
|
154
|
+
Usage: bumpy <command> [options]
|
|
155
|
+
|
|
156
|
+
Commands:
|
|
157
|
+
init Initialize .bumpy/ directory
|
|
158
|
+
add Create a new changeset
|
|
159
|
+
generate Generate changeset from conventional commits
|
|
160
|
+
status Show pending releases
|
|
161
|
+
check Verify changed packages have changesets (for pre-push hooks)
|
|
162
|
+
version Apply changesets and bump versions
|
|
163
|
+
publish Publish versioned packages
|
|
164
|
+
ci check PR check — report pending releases, comment on PR
|
|
165
|
+
ci release Release — create version PR or auto-publish
|
|
166
|
+
migrate Migrate from .changeset/ to .bumpy/
|
|
167
|
+
ai setup Install AI skill for creating changesets
|
|
168
|
+
|
|
169
|
+
Add options:
|
|
170
|
+
--packages <list> Package bumps (e.g., "pkg-a:minor,pkg-b:patch")
|
|
171
|
+
--message <text> Changeset summary
|
|
172
|
+
--name <name> Changeset filename
|
|
173
|
+
--empty Create an empty changeset
|
|
174
|
+
|
|
175
|
+
Generate options:
|
|
176
|
+
--from <ref> Git ref to scan from (default: last version tag)
|
|
177
|
+
--dry-run Preview without creating a changeset
|
|
178
|
+
--name <name> Changeset filename
|
|
179
|
+
|
|
180
|
+
Status options:
|
|
181
|
+
--json Output as JSON (includes dirs, changesets, packageNames)
|
|
182
|
+
--packages Output only package names, one per line
|
|
183
|
+
--bump <types> Filter by bump type (e.g., "major", "minor,patch")
|
|
184
|
+
--filter <names> Filter by package name/glob (e.g., "@myorg/*")
|
|
185
|
+
--verbose Show changeset details
|
|
186
|
+
|
|
187
|
+
Publish options:
|
|
188
|
+
--dry-run Preview without publishing
|
|
189
|
+
--tag <tag> npm dist-tag (e.g., "next", "beta")
|
|
190
|
+
--no-push Skip pushing git tags to remote
|
|
191
|
+
--filter <names> Publish only matching packages (e.g., "@myorg/*")
|
|
192
|
+
|
|
193
|
+
CI check options:
|
|
194
|
+
--comment Force PR comment on/off (auto-detected in CI)
|
|
195
|
+
--fail-on-missing Exit 1 if no changesets found
|
|
196
|
+
|
|
197
|
+
CI release options:
|
|
198
|
+
--auto-publish Version + publish directly (default: create version PR)
|
|
199
|
+
--tag <tag> npm dist-tag for auto-publish
|
|
200
|
+
--branch <name> Branch name for version PR (default: bumpy/version-packages)
|
|
201
|
+
|
|
202
|
+
AI setup options:
|
|
203
|
+
--target <tool> Target AI tool: opencode, cursor, codex
|
|
204
|
+
|
|
205
|
+
${colorize("https://github.com/dmno-dev/bumpy", "dim")}
|
|
206
|
+
`);
|
|
207
|
+
}
|
|
208
|
+
main();
|
|
209
|
+
//#endregion
|
|
210
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { i as __exportAll } from "./logger-ZqggsyGZ.mjs";
|
|
2
|
+
import { a as readJson, n as exists } from "./fs-DbNNEyzq.mjs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
//#region src/types.ts
|
|
5
|
+
const BUMP_LEVELS = {
|
|
6
|
+
patch: 0,
|
|
7
|
+
minor: 1,
|
|
8
|
+
major: 2
|
|
9
|
+
};
|
|
10
|
+
function bumpLevel(type) {
|
|
11
|
+
return BUMP_LEVELS[type];
|
|
12
|
+
}
|
|
13
|
+
function parseIsolatedBump(type) {
|
|
14
|
+
if (type.endsWith("-isolated")) return {
|
|
15
|
+
bump: type.replace("-isolated", ""),
|
|
16
|
+
isolated: true
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
bump: type,
|
|
20
|
+
isolated: false
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function maxBump(a, b) {
|
|
24
|
+
if (!a) return b;
|
|
25
|
+
return bumpLevel(a) >= bumpLevel(b) ? a : b;
|
|
26
|
+
}
|
|
27
|
+
const DEFAULT_BUMP_RULES = {
|
|
28
|
+
dependencies: {
|
|
29
|
+
trigger: "patch",
|
|
30
|
+
bumpAs: "patch"
|
|
31
|
+
},
|
|
32
|
+
peerDependencies: {
|
|
33
|
+
trigger: "major",
|
|
34
|
+
bumpAs: "major"
|
|
35
|
+
},
|
|
36
|
+
devDependencies: {
|
|
37
|
+
trigger: "none",
|
|
38
|
+
bumpAs: "patch"
|
|
39
|
+
},
|
|
40
|
+
optionalDependencies: {
|
|
41
|
+
trigger: "minor",
|
|
42
|
+
bumpAs: "patch"
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const DEP_TYPES = [
|
|
46
|
+
"dependencies",
|
|
47
|
+
"devDependencies",
|
|
48
|
+
"peerDependencies",
|
|
49
|
+
"optionalDependencies"
|
|
50
|
+
];
|
|
51
|
+
const DEFAULT_PUBLISH_CONFIG = {
|
|
52
|
+
packManager: "auto",
|
|
53
|
+
publishManager: "npm",
|
|
54
|
+
publishArgs: [],
|
|
55
|
+
protocolResolution: "pack"
|
|
56
|
+
};
|
|
57
|
+
const DEFAULT_CONFIG = {
|
|
58
|
+
baseBranch: "main",
|
|
59
|
+
access: "public",
|
|
60
|
+
commit: false,
|
|
61
|
+
changelog: "default",
|
|
62
|
+
fixed: [],
|
|
63
|
+
linked: [],
|
|
64
|
+
ignore: [],
|
|
65
|
+
include: [],
|
|
66
|
+
updateInternalDependencies: "out-of-range",
|
|
67
|
+
dependencyBumpRules: {},
|
|
68
|
+
privatePackages: {
|
|
69
|
+
version: false,
|
|
70
|
+
tag: false
|
|
71
|
+
},
|
|
72
|
+
packages: {},
|
|
73
|
+
publish: { ...DEFAULT_PUBLISH_CONFIG },
|
|
74
|
+
aggregateRelease: false,
|
|
75
|
+
gitUser: {
|
|
76
|
+
name: "bumpy-bot",
|
|
77
|
+
email: "276066384+bumpy-bot@users.noreply.github.com"
|
|
78
|
+
},
|
|
79
|
+
versionPr: {
|
|
80
|
+
title: "🐸 Versioned release",
|
|
81
|
+
branch: "bumpy/version-packages",
|
|
82
|
+
preamble: [
|
|
83
|
+
`<a href="https://github.com/dmno-dev/bumpy"><img src="https://raw.githubusercontent.com/dmno-dev/bumpy/main/images/frog-talking.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a>`,
|
|
84
|
+
"",
|
|
85
|
+
`This PR was created and will be kept in sync by [bumpy](https://github.com/dmno-dev/bumpy) based on your .bumpy changeset files. Merge it when you are ready to release the packages listed below:`,
|
|
86
|
+
"<br clear=\"left\" />"
|
|
87
|
+
].join("\n")
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
function hasCascade(r) {
|
|
91
|
+
return "cascade" in r && Object.keys(r.cascade).length > 0;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/core/config.ts
|
|
95
|
+
var config_exports = /* @__PURE__ */ __exportAll({
|
|
96
|
+
findRoot: () => findRoot,
|
|
97
|
+
getBumpyDir: () => getBumpyDir,
|
|
98
|
+
isPackageManaged: () => isPackageManaged,
|
|
99
|
+
loadConfig: () => loadConfig,
|
|
100
|
+
loadPackageConfig: () => loadPackageConfig,
|
|
101
|
+
matchGlob: () => matchGlob
|
|
102
|
+
});
|
|
103
|
+
const BUMPY_DIR = ".bumpy";
|
|
104
|
+
const CONFIG_FILE = "_config.json";
|
|
105
|
+
/** Find the monorepo root by walking up from cwd looking for .bumpy/ */
|
|
106
|
+
async function findRoot(startDir = process.cwd()) {
|
|
107
|
+
let dir = resolve(startDir);
|
|
108
|
+
while (true) {
|
|
109
|
+
if (await exists(resolve(dir, BUMPY_DIR))) return dir;
|
|
110
|
+
if (await exists(resolve(dir, "package.json"))) try {
|
|
111
|
+
if ((await readJson(resolve(dir, "package.json"))).workspaces) return dir;
|
|
112
|
+
} catch {}
|
|
113
|
+
const parent = resolve(dir, "..");
|
|
114
|
+
if (parent === dir) break;
|
|
115
|
+
dir = parent;
|
|
116
|
+
}
|
|
117
|
+
return resolve(startDir);
|
|
118
|
+
}
|
|
119
|
+
/** Load the root bumpy config, merging with defaults */
|
|
120
|
+
async function loadConfig(rootDir) {
|
|
121
|
+
const configPath = resolve(rootDir, BUMPY_DIR, CONFIG_FILE);
|
|
122
|
+
let userConfig = {};
|
|
123
|
+
if (await exists(configPath)) userConfig = await readJson(configPath);
|
|
124
|
+
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
125
|
+
}
|
|
126
|
+
/** Load per-package bumpy config from package.json["bumpy"] or .bumpy.config.json */
|
|
127
|
+
async function loadPackageConfig(pkgDir, rootConfig, pkgName) {
|
|
128
|
+
const rootPkgConfig = findPackageConfig(rootConfig, pkgName);
|
|
129
|
+
let pkgJsonConfig = {};
|
|
130
|
+
try {
|
|
131
|
+
const pkg = await readJson(resolve(pkgDir, "package.json"));
|
|
132
|
+
if (pkg.bumpy && typeof pkg.bumpy === "object") pkgJsonConfig = pkg.bumpy;
|
|
133
|
+
} catch {}
|
|
134
|
+
return mergePackageConfig(rootPkgConfig, pkgJsonConfig);
|
|
135
|
+
}
|
|
136
|
+
/** Find a package config from the root config, supporting glob patterns */
|
|
137
|
+
function findPackageConfig(config, pkgName) {
|
|
138
|
+
if (config.packages[pkgName]) return config.packages[pkgName];
|
|
139
|
+
for (const [pattern, pkgConfig] of Object.entries(config.packages)) if (matchGlob(pkgName, pattern)) return pkgConfig;
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
/** Simple glob matching for package names (supports * and **) */
|
|
143
|
+
function matchGlob(name, pattern) {
|
|
144
|
+
if (name === pattern) return true;
|
|
145
|
+
const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{DOUBLE}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLE}}/g, ".*");
|
|
146
|
+
return new RegExp(`^${regexStr}$`).test(name);
|
|
147
|
+
}
|
|
148
|
+
function mergeConfig(defaults, user) {
|
|
149
|
+
return {
|
|
150
|
+
...defaults,
|
|
151
|
+
...user,
|
|
152
|
+
dependencyBumpRules: {
|
|
153
|
+
...defaults.dependencyBumpRules,
|
|
154
|
+
...user.dependencyBumpRules
|
|
155
|
+
},
|
|
156
|
+
privatePackages: {
|
|
157
|
+
...defaults.privatePackages,
|
|
158
|
+
...user.privatePackages
|
|
159
|
+
},
|
|
160
|
+
publish: {
|
|
161
|
+
...defaults.publish,
|
|
162
|
+
...user.publish
|
|
163
|
+
},
|
|
164
|
+
packages: {
|
|
165
|
+
...defaults.packages,
|
|
166
|
+
...user.packages
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function mergePackageConfig(...configs) {
|
|
171
|
+
const result = {};
|
|
172
|
+
for (const cfg of configs) {
|
|
173
|
+
Object.assign(result, cfg);
|
|
174
|
+
if (cfg.dependencyBumpRules) result.dependencyBumpRules = {
|
|
175
|
+
...result.dependencyBumpRules,
|
|
176
|
+
...cfg.dependencyBumpRules
|
|
177
|
+
};
|
|
178
|
+
if (cfg.specificDependencyRules) result.specificDependencyRules = {
|
|
179
|
+
...result.specificDependencyRules,
|
|
180
|
+
...cfg.specificDependencyRules
|
|
181
|
+
};
|
|
182
|
+
if (cfg.cascadeTo) result.cascadeTo = {
|
|
183
|
+
...result.cascadeTo,
|
|
184
|
+
...cfg.cascadeTo
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
function getBumpyDir(rootDir) {
|
|
190
|
+
return resolve(rootDir, BUMPY_DIR);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Determine if a package should be managed by bumpy.
|
|
194
|
+
* Resolution order:
|
|
195
|
+
* 1. Per-package `managed: false` → skip (explicit opt-out)
|
|
196
|
+
* 2. `config.ignore` glob match → skip
|
|
197
|
+
* 3. Per-package `managed: true` → include (explicit opt-in, overrides private)
|
|
198
|
+
* 4. `config.include` glob match → include (overrides private)
|
|
199
|
+
* 5. Private package + `config.privatePackages.version` false → skip
|
|
200
|
+
* 6. Otherwise → include
|
|
201
|
+
*/
|
|
202
|
+
function isPackageManaged(pkgName, isPrivate, config, pkgBumpy) {
|
|
203
|
+
if (pkgBumpy?.managed === false) return false;
|
|
204
|
+
if (config.ignore.some((pattern) => matchGlob(pkgName, pattern))) {
|
|
205
|
+
if (pkgBumpy?.managed === true) return true;
|
|
206
|
+
if (config.include.some((pattern) => matchGlob(pkgName, pattern))) return true;
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (pkgBumpy?.managed === true) return true;
|
|
210
|
+
if (config.include.some((pattern) => matchGlob(pkgName, pattern))) return true;
|
|
211
|
+
if (isPrivate && !config.privatePackages.version) return false;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
export { loadConfig as a, BUMP_LEVELS as c, DEFAULT_PUBLISH_CONFIG as d, DEP_TYPES as f, parseIsolatedBump as g, maxBump as h, isPackageManaged as i, DEFAULT_BUMP_RULES as l, hasCascade as m, findRoot as n, loadPackageConfig as o, bumpLevel as p, getBumpyDir as r, matchGlob as s, config_exports as t, DEFAULT_CONFIG as u };
|