@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,44 @@
|
|
|
1
|
+
import { i as __exportAll } from "./logger-ZqggsyGZ.mjs";
|
|
2
|
+
import { exec, execSync } from "node:child_process";
|
|
3
|
+
//#region src/utils/shell.ts
|
|
4
|
+
var shell_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
run: () => run,
|
|
6
|
+
runAsync: () => runAsync,
|
|
7
|
+
tryRun: () => tryRun
|
|
8
|
+
});
|
|
9
|
+
function run(cmd, opts) {
|
|
10
|
+
return execSync(cmd, {
|
|
11
|
+
cwd: opts?.cwd,
|
|
12
|
+
input: opts?.input,
|
|
13
|
+
encoding: "utf-8",
|
|
14
|
+
stdio: [
|
|
15
|
+
opts?.input ? "pipe" : "pipe",
|
|
16
|
+
"pipe",
|
|
17
|
+
"pipe"
|
|
18
|
+
]
|
|
19
|
+
}).trim();
|
|
20
|
+
}
|
|
21
|
+
function runAsync(cmd, opts) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const child = exec(cmd, {
|
|
24
|
+
cwd: opts?.cwd,
|
|
25
|
+
encoding: "utf-8"
|
|
26
|
+
}, (err, stdout, stderr) => {
|
|
27
|
+
if (err) reject(/* @__PURE__ */ new Error(`Command failed: ${cmd}\n${stderr}`));
|
|
28
|
+
else resolve(stdout.trim());
|
|
29
|
+
});
|
|
30
|
+
if (opts?.input) {
|
|
31
|
+
child.stdin?.write(opts.input);
|
|
32
|
+
child.stdin?.end();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function tryRun(cmd, opts) {
|
|
37
|
+
try {
|
|
38
|
+
return run(cmd, opts);
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { tryRun as i, runAsync as n, shell_exports as r, run as t };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-ZqggsyGZ.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-CJ2orhTL.mjs";
|
|
3
|
+
import { t as discoverPackages } 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
|
+
//#region src/commands/status.ts
|
|
8
|
+
async function statusCommand(rootDir, opts) {
|
|
9
|
+
const config = await loadConfig(rootDir);
|
|
10
|
+
const packages = await discoverPackages(rootDir, config);
|
|
11
|
+
const depGraph = new DependencyGraph(packages);
|
|
12
|
+
const changesets = await readChangesets(rootDir);
|
|
13
|
+
if (changesets.length === 0) {
|
|
14
|
+
if (opts.json) console.log(JSON.stringify({
|
|
15
|
+
changesets: [],
|
|
16
|
+
releases: [],
|
|
17
|
+
packageNames: []
|
|
18
|
+
}, null, 2));
|
|
19
|
+
else if (!opts.packagesOnly) log.info("No pending changesets.");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const plan = assembleReleasePlan(changesets, packages, depGraph, config);
|
|
23
|
+
let releases = plan.releases;
|
|
24
|
+
if (opts.bumpType) {
|
|
25
|
+
const types = opts.bumpType.split(",").map((t) => t.trim());
|
|
26
|
+
releases = releases.filter((r) => types.includes(r.type));
|
|
27
|
+
}
|
|
28
|
+
if (opts.filter) {
|
|
29
|
+
const { matchGlob } = await import("./config-CJ2orhTL.mjs").then((n) => n.t);
|
|
30
|
+
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
31
|
+
releases = releases.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
32
|
+
}
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
const jsonOutput = {
|
|
35
|
+
changesets: plan.changesets.map((cs) => ({
|
|
36
|
+
id: cs.id,
|
|
37
|
+
summary: cs.summary,
|
|
38
|
+
releases: cs.releases.map((r) => ({
|
|
39
|
+
name: r.name,
|
|
40
|
+
type: r.type
|
|
41
|
+
}))
|
|
42
|
+
})),
|
|
43
|
+
releases: releases.map((r) => ({
|
|
44
|
+
name: r.name,
|
|
45
|
+
type: r.type,
|
|
46
|
+
oldVersion: r.oldVersion,
|
|
47
|
+
newVersion: r.newVersion,
|
|
48
|
+
dir: packages.get(r.name)?.relativeDir,
|
|
49
|
+
changesets: r.changesets,
|
|
50
|
+
isDependencyBump: r.isDependencyBump,
|
|
51
|
+
isCascadeBump: r.isCascadeBump
|
|
52
|
+
})),
|
|
53
|
+
packageNames: releases.map((r) => r.name)
|
|
54
|
+
};
|
|
55
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (opts.packagesOnly) {
|
|
59
|
+
for (const r of releases) console.log(r.name);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
log.bold(`${changesets.length} changeset(s) pending\n`);
|
|
63
|
+
if (releases.length === 0) {
|
|
64
|
+
log.warn("No packages match the current filters.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const groups = [
|
|
68
|
+
[
|
|
69
|
+
"Major",
|
|
70
|
+
"red",
|
|
71
|
+
releases.filter((r) => r.type === "major")
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
"Minor",
|
|
75
|
+
"yellow",
|
|
76
|
+
releases.filter((r) => r.type === "minor")
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
"Patch",
|
|
80
|
+
"green",
|
|
81
|
+
releases.filter((r) => r.type === "patch")
|
|
82
|
+
]
|
|
83
|
+
];
|
|
84
|
+
for (const [label, color, group] of groups) {
|
|
85
|
+
if (group.length === 0) continue;
|
|
86
|
+
log.bold(colorize(label, color));
|
|
87
|
+
for (const r of group) printRelease(r, packages);
|
|
88
|
+
console.log();
|
|
89
|
+
}
|
|
90
|
+
if (opts.verbose) {
|
|
91
|
+
log.bold("Changesets:");
|
|
92
|
+
for (const cs of plan.changesets) {
|
|
93
|
+
console.log(` ${colorize(cs.id, "cyan")}`);
|
|
94
|
+
for (const r of cs.releases) console.log(` ${r.name}: ${r.type}`);
|
|
95
|
+
if (cs.summary) console.log(` ${colorize(cs.summary.split("\n")[0], "dim")}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function printRelease(r, packages) {
|
|
100
|
+
const pkg = packages.get(r.name);
|
|
101
|
+
const dir = pkg ? colorize(` (${pkg.relativeDir})`, "dim") : "";
|
|
102
|
+
const suffix = r.isDependencyBump ? colorize(" ← dependency bump", "dim") : r.isCascadeBump ? colorize(" ← cascade", "dim") : "";
|
|
103
|
+
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${suffix}${dir}`);
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { statusCommand };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-ZqggsyGZ.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-CJ2orhTL.mjs";
|
|
3
|
+
import { r as detectWorkspaces, t as discoverPackages } 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 { t as applyReleasePlan } from "./apply-release-plan-DtU3rVyL.mjs";
|
|
8
|
+
import { i as tryRun, t as run } from "./shell-DPlltpzb.mjs";
|
|
9
|
+
//#region src/commands/version.ts
|
|
10
|
+
async function versionCommand(rootDir) {
|
|
11
|
+
const config = await loadConfig(rootDir);
|
|
12
|
+
const packages = await discoverPackages(rootDir, config);
|
|
13
|
+
const depGraph = new DependencyGraph(packages);
|
|
14
|
+
const changesets = await readChangesets(rootDir);
|
|
15
|
+
if (changesets.length === 0) {
|
|
16
|
+
log.info("No pending changesets.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const plan = assembleReleasePlan(changesets, packages, depGraph, config);
|
|
20
|
+
if (plan.releases.length === 0) {
|
|
21
|
+
log.warn("Changesets found but no packages would be released.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
log.step("Applying version bumps:");
|
|
25
|
+
for (const r of plan.releases) {
|
|
26
|
+
const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
|
|
27
|
+
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${tag}`);
|
|
28
|
+
}
|
|
29
|
+
await applyReleasePlan(plan, packages, rootDir, config);
|
|
30
|
+
log.success(`Updated ${plan.releases.length} package(s)`);
|
|
31
|
+
log.dim(` Deleted ${changesets.length} changeset file(s)`);
|
|
32
|
+
await updateLockfile(rootDir);
|
|
33
|
+
if (config.commit) try {
|
|
34
|
+
run("git add -A .bumpy/", { cwd: rootDir });
|
|
35
|
+
for (const r of plan.releases) {
|
|
36
|
+
const pkg = packages.get(r.name);
|
|
37
|
+
run(`git add "${pkg.relativeDir}/package.json"`, { cwd: rootDir });
|
|
38
|
+
run(`git add "${pkg.relativeDir}/CHANGELOG.md"`, { cwd: rootDir });
|
|
39
|
+
}
|
|
40
|
+
for (const lockfile of [
|
|
41
|
+
"bun.lock",
|
|
42
|
+
"bun.lockb",
|
|
43
|
+
"pnpm-lock.yaml",
|
|
44
|
+
"yarn.lock",
|
|
45
|
+
"package-lock.json"
|
|
46
|
+
]) tryRun(`git add "${lockfile}"`, { cwd: rootDir });
|
|
47
|
+
run("git commit -F -", {
|
|
48
|
+
cwd: rootDir,
|
|
49
|
+
input: [
|
|
50
|
+
"Version packages",
|
|
51
|
+
"",
|
|
52
|
+
...plan.releases.map((r) => `${r.name}@${r.newVersion}`)
|
|
53
|
+
].join("\n")
|
|
54
|
+
});
|
|
55
|
+
log.success("Created git commit");
|
|
56
|
+
} catch (e) {
|
|
57
|
+
log.warn(`Git commit failed: ${e}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Run the package manager's install to update the lockfile */
|
|
61
|
+
async function updateLockfile(rootDir) {
|
|
62
|
+
const { packageManager } = await detectWorkspaces(rootDir);
|
|
63
|
+
const installCmd = getInstallCommand(packageManager);
|
|
64
|
+
log.step(`Updating lockfile (${installCmd})...`);
|
|
65
|
+
try {
|
|
66
|
+
run(installCmd, { cwd: rootDir });
|
|
67
|
+
log.dim(" Lockfile updated");
|
|
68
|
+
} catch (err) {
|
|
69
|
+
log.warn(` Lockfile update failed: ${err instanceof Error ? err.message : err}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function getInstallCommand(pm) {
|
|
73
|
+
switch (pm) {
|
|
74
|
+
case "pnpm": return "pnpm install --lockfile-only";
|
|
75
|
+
case "bun": return "bun install";
|
|
76
|
+
case "yarn": return "yarn install --mode update-lockfile";
|
|
77
|
+
default: return "npm install --package-lock-only";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { versionCommand };
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { a as readJson, n as exists, o as readText } from "./fs-DbNNEyzq.mjs";
|
|
2
|
+
import { i as isPackageManaged, o as loadPackageConfig } from "./config-CJ2orhTL.mjs";
|
|
3
|
+
import { t as jsYaml } from "./js-yaml-DpZfOoD4.mjs";
|
|
4
|
+
import { relative, resolve } from "node:path";
|
|
5
|
+
import { readdir, stat } from "node:fs/promises";
|
|
6
|
+
//#region src/utils/package-manager.ts
|
|
7
|
+
/** Detect the package manager, extract workspace globs, and load catalogs */
|
|
8
|
+
async function detectWorkspaces(rootDir) {
|
|
9
|
+
const pm = await detectPackageManager(rootDir);
|
|
10
|
+
return {
|
|
11
|
+
packageManager: pm,
|
|
12
|
+
globs: await getWorkspaceGlobs(rootDir, pm),
|
|
13
|
+
catalogs: await loadCatalogs(rootDir, pm)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function detectPackageManager(rootDir) {
|
|
17
|
+
if (await exists(resolve(rootDir, "bun.lock")) || await exists(resolve(rootDir, "bun.lockb"))) return "bun";
|
|
18
|
+
if (await exists(resolve(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
19
|
+
if (await exists(resolve(rootDir, "yarn.lock"))) return "yarn";
|
|
20
|
+
try {
|
|
21
|
+
const pkg = await readJson(resolve(rootDir, "package.json"));
|
|
22
|
+
if (typeof pkg.packageManager === "string") {
|
|
23
|
+
const name = pkg.packageManager.split("@")[0];
|
|
24
|
+
if (name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
25
|
+
}
|
|
26
|
+
} catch {}
|
|
27
|
+
return "npm";
|
|
28
|
+
}
|
|
29
|
+
async function getWorkspaceGlobs(rootDir, pm) {
|
|
30
|
+
if (pm === "pnpm") {
|
|
31
|
+
const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
|
|
32
|
+
if (await exists(wsFile)) {
|
|
33
|
+
const content = await readText(wsFile);
|
|
34
|
+
const parsed = jsYaml.load(content);
|
|
35
|
+
if (parsed?.packages) return parsed.packages;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const workspaces = (await readJson(resolve(rootDir, "package.json"))).workspaces;
|
|
40
|
+
if (Array.isArray(workspaces)) return workspaces;
|
|
41
|
+
if (workspaces && typeof workspaces === "object" && "packages" in workspaces) {
|
|
42
|
+
const pkgs = workspaces.packages;
|
|
43
|
+
if (Array.isArray(pkgs)) return pkgs;
|
|
44
|
+
}
|
|
45
|
+
} catch {}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
/** Load catalog definitions from pnpm-workspace.yaml or root package.json */
|
|
49
|
+
async function loadCatalogs(rootDir, pm) {
|
|
50
|
+
const catalogs = /* @__PURE__ */ new Map();
|
|
51
|
+
if (pm === "pnpm") {
|
|
52
|
+
const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
|
|
53
|
+
if (await exists(wsFile)) {
|
|
54
|
+
const content = await readText(wsFile);
|
|
55
|
+
const parsed = jsYaml.load(content);
|
|
56
|
+
if (parsed?.catalog) catalogs.set("", parsed.catalog);
|
|
57
|
+
if (parsed?.catalogs) for (const [name, deps] of Object.entries(parsed.catalogs)) catalogs.set(name, deps);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const pkg = await readJson(resolve(rootDir, "package.json"));
|
|
62
|
+
if (pkg.catalog && typeof pkg.catalog === "object") catalogs.set("", pkg.catalog);
|
|
63
|
+
if (pkg.catalogs && typeof pkg.catalogs === "object") for (const [name, deps] of Object.entries(pkg.catalogs)) catalogs.set(name, deps);
|
|
64
|
+
const workspaces = pkg.workspaces;
|
|
65
|
+
if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
66
|
+
const ws = workspaces;
|
|
67
|
+
if (ws.catalog && typeof ws.catalog === "object") catalogs.set("", ws.catalog);
|
|
68
|
+
if (ws.catalogs && typeof ws.catalogs === "object") for (const [name, deps] of Object.entries(ws.catalogs)) catalogs.set(name, deps);
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
return catalogs;
|
|
72
|
+
}
|
|
73
|
+
/** Resolve a specific dependency's catalog: reference */
|
|
74
|
+
function resolveCatalogDep(depName, range, catalogs) {
|
|
75
|
+
if (!range.startsWith("catalog:")) return null;
|
|
76
|
+
const catalogName = range.slice(8).trim() || "";
|
|
77
|
+
const catalog = catalogs.get(catalogName);
|
|
78
|
+
if (!catalog) return null;
|
|
79
|
+
return catalog[depName] ?? null;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/core/workspace.ts
|
|
83
|
+
/** Discover all workspace packages and catalogs in a monorepo */
|
|
84
|
+
async function discoverWorkspace(rootDir, config) {
|
|
85
|
+
const { globs, catalogs } = await detectWorkspaces(rootDir);
|
|
86
|
+
if (globs.length === 0) throw new Error("No workspace globs found. Is this a monorepo?");
|
|
87
|
+
const packages = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const glob of globs) {
|
|
89
|
+
const dirs = await resolveGlob(rootDir, glob);
|
|
90
|
+
for (const dir of dirs) {
|
|
91
|
+
const pkg = await loadWorkspacePackage(dir, rootDir, config);
|
|
92
|
+
if (pkg) {
|
|
93
|
+
if (!isPackageManaged(pkg.name, pkg.private, config, pkg.bumpy)) continue;
|
|
94
|
+
packages.set(pkg.name, pkg);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
packages,
|
|
100
|
+
catalogs
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Convenience wrapper that returns just packages (backwards compat) */
|
|
104
|
+
async function discoverPackages(rootDir, config) {
|
|
105
|
+
const { packages } = await discoverWorkspace(rootDir, config);
|
|
106
|
+
return packages;
|
|
107
|
+
}
|
|
108
|
+
/** Resolve a workspace glob pattern to directories containing package.json */
|
|
109
|
+
async function resolveGlob(rootDir, pattern) {
|
|
110
|
+
return expandGlob(rootDir, pattern.split("/"));
|
|
111
|
+
}
|
|
112
|
+
async function expandGlob(baseDir, parts) {
|
|
113
|
+
if (parts.length === 0) {
|
|
114
|
+
if (await exists(resolve(baseDir, "package.json"))) return [baseDir];
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const [current, ...rest] = parts;
|
|
118
|
+
if (current === "*") {
|
|
119
|
+
const entries = await safeReaddir(baseDir);
|
|
120
|
+
const results = [];
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const entryPath = resolve(baseDir, entry);
|
|
123
|
+
if (await isDirectory(entryPath)) results.push(...await expandGlob(entryPath, rest));
|
|
124
|
+
}
|
|
125
|
+
return results;
|
|
126
|
+
} else if (current === "**") {
|
|
127
|
+
const results = [];
|
|
128
|
+
results.push(...await expandGlob(baseDir, rest));
|
|
129
|
+
const entries = await safeReaddir(baseDir);
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
132
|
+
const entryPath = resolve(baseDir, entry);
|
|
133
|
+
if (await isDirectory(entryPath)) results.push(...await expandGlob(entryPath, parts));
|
|
134
|
+
}
|
|
135
|
+
return results;
|
|
136
|
+
} else {
|
|
137
|
+
const next = resolve(baseDir, current);
|
|
138
|
+
if (await isDirectory(next)) return expandGlob(next, rest);
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function safeReaddir(dir) {
|
|
143
|
+
try {
|
|
144
|
+
return await readdir(dir);
|
|
145
|
+
} catch {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function isDirectory(path) {
|
|
150
|
+
try {
|
|
151
|
+
return (await stat(path)).isDirectory();
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function loadWorkspacePackage(dir, rootDir, config) {
|
|
157
|
+
const pkgPath = resolve(dir, "package.json");
|
|
158
|
+
if (!await exists(pkgPath)) return null;
|
|
159
|
+
let pkg;
|
|
160
|
+
try {
|
|
161
|
+
pkg = await readJson(pkgPath);
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const name = pkg.name;
|
|
166
|
+
if (!name) return null;
|
|
167
|
+
const bumpy = await loadPackageConfig(dir, config, name);
|
|
168
|
+
return {
|
|
169
|
+
name,
|
|
170
|
+
version: pkg.version || "0.0.0",
|
|
171
|
+
dir: resolve(dir),
|
|
172
|
+
relativeDir: relative(rootDir, dir),
|
|
173
|
+
packageJson: pkg,
|
|
174
|
+
private: !!pkg.private,
|
|
175
|
+
dependencies: pkg.dependencies || {},
|
|
176
|
+
devDependencies: pkg.devDependencies || {},
|
|
177
|
+
peerDependencies: pkg.peerDependencies || {},
|
|
178
|
+
optionalDependencies: pkg.optionalDependencies || {},
|
|
179
|
+
bumpy
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { resolveCatalogDep as i, discoverWorkspace as n, detectWorkspaces as r, discoverPackages as t };
|
package/package.json
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@varlock/bumpy",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Modern monorepo versioning and changelog tool",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/dmno-dev/bumpy",
|
|
8
|
+
"directory": "packages/bumpy"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"bumpy": "./dist/cli.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
".claude-plugin",
|
|
16
|
+
"skills"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/index.mjs",
|
|
22
|
+
"types": "./dist/index.d.mts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsdown",
|
|
27
|
+
"test": "bun test"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/bun": "latest",
|
|
31
|
+
"@types/js-yaml": "^4.0.9",
|
|
32
|
+
"@types/semver": "^7.7.0",
|
|
33
|
+
"ansis": "^4.2.0",
|
|
34
|
+
"js-yaml": "^4.1.0",
|
|
35
|
+
"semver": "^7.7.2",
|
|
36
|
+
"tsdown": "^0.21.8"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-change
|
|
3
|
+
description: Create a bumpy changeset describing which packages changed and how, for version bumping and changelog generation. Use when the user wants to record a change, add a changeset, or prepare packages for release.
|
|
4
|
+
argument-hint: '[description of changes]'
|
|
5
|
+
allowed-tools: Read Grep Glob Bash Edit Write
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Create a bumpy changeset
|
|
9
|
+
|
|
10
|
+
You are helping the user create a **bumpy changeset** — a markdown file in `.bumpy/` that describes which packages changed and how. Bumpy uses these to bump versions and generate changelogs.
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
### 1. Gather context
|
|
15
|
+
|
|
16
|
+
First, understand what changed. Run these in parallel:
|
|
17
|
+
|
|
18
|
+
- `git diff --stat` — see which files changed
|
|
19
|
+
- `git diff --cached --stat` — see staged changes
|
|
20
|
+
- `bumpy status --json` — see if there are already pending changesets
|
|
21
|
+
|
|
22
|
+
If the user provided a description via `$ARGUMENTS`, use that as additional context for understanding the change.
|
|
23
|
+
|
|
24
|
+
Review the diff output to understand the scope of changes.
|
|
25
|
+
|
|
26
|
+
### 2. Identify affected packages
|
|
27
|
+
|
|
28
|
+
Determine which workspace packages are affected by the changes. Map changed files to their packages based on directory structure.
|
|
29
|
+
|
|
30
|
+
If unsure which packages exist, run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bumpy status --packages 2>/dev/null || cat package.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Determine bump levels
|
|
37
|
+
|
|
38
|
+
For each affected package, choose the appropriate bump level:
|
|
39
|
+
|
|
40
|
+
| Level | When to use |
|
|
41
|
+
| --------- | --------------------------------------------------------------------------------------- |
|
|
42
|
+
| **major** | Breaking changes: removed/renamed exports, changed function signatures, dropped support |
|
|
43
|
+
| **minor** | New features: added exports, new options, new functionality |
|
|
44
|
+
| **patch** | Bug fixes, internal refactors, documentation, dependency updates |
|
|
45
|
+
|
|
46
|
+
Append `-isolated` (e.g., `patch-isolated`) if the change is purely internal and dependents should NOT be bumped. Use this for:
|
|
47
|
+
|
|
48
|
+
- Internal refactors with no API changes
|
|
49
|
+
- Dev tooling / test changes
|
|
50
|
+
- Documentation-only changes
|
|
51
|
+
|
|
52
|
+
### 4. Write a clear summary
|
|
53
|
+
|
|
54
|
+
Write a concise summary (1-3 sentences) describing **what** changed and **why**. This becomes the CHANGELOG entry. Good summaries:
|
|
55
|
+
|
|
56
|
+
- Start with a verb: "Added...", "Fixed...", "Refactored..."
|
|
57
|
+
- Focus on user-facing impact, not implementation details
|
|
58
|
+
- Are specific enough to be useful months later
|
|
59
|
+
|
|
60
|
+
### 5. Create the changeset
|
|
61
|
+
|
|
62
|
+
Use the non-interactive CLI:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bumpy add \
|
|
66
|
+
--packages "<pkg1>:<bump>,<pkg2>:<bump>" \
|
|
67
|
+
--message "<summary>" \
|
|
68
|
+
--name "<short-descriptive-name>"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `--name` should be a short kebab-case slug describing the change (e.g., `fix-auth-token-refresh`, `add-encryption-api`).
|
|
72
|
+
|
|
73
|
+
### Example
|
|
74
|
+
|
|
75
|
+
If the user fixed a bug in `@myorg/auth` that also required a type change in `@myorg/types`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
bumpy add \
|
|
79
|
+
--packages "@myorg/auth:patch,@myorg/types:patch" \
|
|
80
|
+
--message "Fixed token refresh failing silently when the refresh token has expired." \
|
|
81
|
+
--name "fix-token-refresh"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Advanced: cascading bumps
|
|
85
|
+
|
|
86
|
+
If a change in a core package should explicitly cascade to dependents with specific bump levels, write the changeset file directly instead of using the CLI:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cat > .bumpy/<name>.md << 'EOF'
|
|
90
|
+
---
|
|
91
|
+
"@myorg/core":
|
|
92
|
+
bump: minor
|
|
93
|
+
cascade:
|
|
94
|
+
"@myorg/plugin-*": patch
|
|
95
|
+
"@myorg/cli": minor
|
|
96
|
+
"@myorg/utils": patch
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
Added new encryption provider. Plugins need a patch bump for compatibility.
|
|
100
|
+
EOF
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Important notes
|
|
104
|
+
|
|
105
|
+
- Only include packages that have **actual code changes** — bumpy handles dependency propagation automatically
|
|
106
|
+
- If the user hasn't made any changes yet, ask what they're planning to change
|
|
107
|
+
- If the change doesn't affect any publishable packages (e.g., only root config files), suggest using `bumpy add --empty` to satisfy CI checks
|
|
108
|
+
- One changeset per logical change — don't combine unrelated changes
|