@varlock/bumpy 0.0.0 → 0.0.2
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-BjyVIUlr.mjs +175 -0
- package/dist/ai-CQhUyHAG.mjs +82 -0
- package/dist/apply-release-plan-D6TSrcwX.mjs +137 -0
- package/dist/changelog-github-Du62krXi.mjs +193 -0
- package/dist/changeset-UCZdSRDv.mjs +108 -0
- package/dist/check-jIwike9F.mjs +51 -0
- package/dist/ci-D6LQbR38.mjs +585 -0
- package/dist/ci-setup-C6FlOfW5.mjs +211 -0
- package/dist/clack-CDRCHrC-.mjs +1216 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +214 -0
- package/dist/config-BkwIEaQg.mjs +215 -0
- package/dist/dep-graph-E-9-eQ2J.mjs +64 -0
- package/dist/fs-0AtnPUUe.mjs +51 -0
- package/dist/generate-Btrsn1qi.mjs +177 -0
- package/dist/git-CGHVXXKw.mjs +78 -0
- package/dist/index.d.mts +262 -0
- package/dist/index.mjs +9 -0
- package/dist/init-B0q3wEQW.mjs +22 -0
- package/dist/js-yaml-DpZfOoD4.mjs +2031 -0
- package/dist/logger-C2dEe5Su.mjs +135 -0
- package/dist/migrate-CfQNwD0T.mjs +121 -0
- package/dist/names-Ck8cun7B.mjs +144 -0
- package/dist/package-manager-DcI5TdDE.mjs +80 -0
- package/dist/publish-D_7RqEYL.mjs +251 -0
- package/dist/publish-pipeline-ChnqW8nR.mjs +277 -0
- package/dist/release-plan-BEzwApuK.mjs +173 -0
- package/dist/semver-BTzYh8vc.mjs +1360 -0
- package/dist/shell-Dj7JRD_q.mjs +92 -0
- package/dist/status--Q8yAxQ4.mjs +106 -0
- package/dist/version-cAUkfYPx.mjs +120 -0
- package/dist/workspace-CxEKakDm.mjs +107 -0
- package/package.json +38 -6
- package/skills/add-change/SKILL.md +108 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { exec, execFile, execFileSync } from "node:child_process";
|
|
3
|
+
//#region src/utils/shell.ts
|
|
4
|
+
var shell_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
runArgs: () => runArgs,
|
|
6
|
+
runArgsAsync: () => runArgsAsync,
|
|
7
|
+
runAsync: () => runAsync,
|
|
8
|
+
sq: () => sq,
|
|
9
|
+
tryRunArgs: () => tryRunArgs
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Escape a value for safe interpolation inside a single-quoted shell string.
|
|
13
|
+
* Works by ending the current single-quote, inserting an escaped single-quote,
|
|
14
|
+
* and re-opening the single-quote: "it's" → 'it'\''s'
|
|
15
|
+
*/
|
|
16
|
+
function sq(value) {
|
|
17
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
18
|
+
}
|
|
19
|
+
function checkIntercept(args, opts) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function runAsync(cmd, opts) {
|
|
23
|
+
const result = checkIntercept(cmd.split(/\s+/), opts);
|
|
24
|
+
if (result?.intercepted) {
|
|
25
|
+
if ("error" in result) return Promise.reject(new Error(result.error));
|
|
26
|
+
return Promise.resolve(result.result);
|
|
27
|
+
}
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const child = exec(cmd, {
|
|
30
|
+
cwd: opts?.cwd,
|
|
31
|
+
encoding: "utf-8"
|
|
32
|
+
}, (err, stdout, stderr) => {
|
|
33
|
+
if (err) reject(/* @__PURE__ */ new Error(`Command failed: ${cmd}\n${stderr}`));
|
|
34
|
+
else resolve(stdout.trim());
|
|
35
|
+
});
|
|
36
|
+
if (opts?.input) {
|
|
37
|
+
child.stdin?.write(opts.input);
|
|
38
|
+
child.stdin?.end();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Run a command with an argument array — bypasses the shell entirely */
|
|
43
|
+
function runArgs(args, opts) {
|
|
44
|
+
const result = checkIntercept(args, opts);
|
|
45
|
+
if (result?.intercepted) {
|
|
46
|
+
if ("error" in result) throw new Error(result.error);
|
|
47
|
+
return result.result;
|
|
48
|
+
}
|
|
49
|
+
const [cmd, ...rest] = args;
|
|
50
|
+
return execFileSync(cmd, rest, {
|
|
51
|
+
cwd: opts?.cwd,
|
|
52
|
+
input: opts?.input,
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
stdio: [
|
|
55
|
+
opts?.input ? "pipe" : "pipe",
|
|
56
|
+
"pipe",
|
|
57
|
+
"pipe"
|
|
58
|
+
]
|
|
59
|
+
}).trim();
|
|
60
|
+
}
|
|
61
|
+
/** Async version of runArgs */
|
|
62
|
+
function runArgsAsync(args, opts) {
|
|
63
|
+
const result = checkIntercept(args, opts);
|
|
64
|
+
if (result?.intercepted) {
|
|
65
|
+
if ("error" in result) return Promise.reject(new Error(result.error));
|
|
66
|
+
return Promise.resolve(result.result);
|
|
67
|
+
}
|
|
68
|
+
const [cmd, ...rest] = args;
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const child = execFile(cmd, rest, {
|
|
71
|
+
cwd: opts?.cwd,
|
|
72
|
+
encoding: "utf-8"
|
|
73
|
+
}, (err, stdout, stderr) => {
|
|
74
|
+
if (err) reject(/* @__PURE__ */ new Error(`Command failed: ${args.join(" ")}\n${stderr}`));
|
|
75
|
+
else resolve(stdout.trim());
|
|
76
|
+
});
|
|
77
|
+
if (opts?.input) {
|
|
78
|
+
child.stdin?.write(opts.input);
|
|
79
|
+
child.stdin?.end();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** tryRun equivalent for argument arrays */
|
|
84
|
+
function tryRunArgs(args, opts) {
|
|
85
|
+
try {
|
|
86
|
+
return runArgs(args, opts);
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { sq as a, shell_exports as i, runArgsAsync as n, tryRunArgs as o, runAsync as r, runArgs as t };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-BkwIEaQg.mjs";
|
|
3
|
+
import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
|
|
4
|
+
import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
|
|
5
|
+
import { r as readChangesets } from "./changeset-UCZdSRDv.mjs";
|
|
6
|
+
import { t as assembleReleasePlan } from "./release-plan-BEzwApuK.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-BkwIEaQg.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,120 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-BkwIEaQg.mjs";
|
|
3
|
+
import { n as detectWorkspaces } from "./package-manager-DcI5TdDE.mjs";
|
|
4
|
+
import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
|
|
5
|
+
import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
|
|
6
|
+
import { o as tryRunArgs, t as runArgs } from "./shell-Dj7JRD_q.mjs";
|
|
7
|
+
import { r as readChangesets } from "./changeset-UCZdSRDv.mjs";
|
|
8
|
+
import { t as assembleReleasePlan } from "./release-plan-BEzwApuK.mjs";
|
|
9
|
+
import { t as applyReleasePlan } from "./apply-release-plan-D6TSrcwX.mjs";
|
|
10
|
+
//#region src/commands/version.ts
|
|
11
|
+
async function versionCommand(rootDir) {
|
|
12
|
+
const config = await loadConfig(rootDir);
|
|
13
|
+
const packages = await discoverPackages(rootDir, config);
|
|
14
|
+
const depGraph = new DependencyGraph(packages);
|
|
15
|
+
const changesets = await readChangesets(rootDir);
|
|
16
|
+
if (changesets.length === 0) {
|
|
17
|
+
log.info("No pending changesets.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const plan = assembleReleasePlan(changesets, packages, depGraph, config);
|
|
21
|
+
if (plan.releases.length === 0) {
|
|
22
|
+
log.warn("Changesets found but no packages would be released.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
log.step("Applying version bumps:");
|
|
26
|
+
for (const r of plan.releases) {
|
|
27
|
+
const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
|
|
28
|
+
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${tag}`);
|
|
29
|
+
}
|
|
30
|
+
await applyReleasePlan(plan, packages, rootDir, config);
|
|
31
|
+
log.success(`Updated ${plan.releases.length} package(s)`);
|
|
32
|
+
log.dim(` Deleted ${changesets.length} changeset file(s)`);
|
|
33
|
+
await updateLockfile(rootDir);
|
|
34
|
+
if (config.commit) try {
|
|
35
|
+
runArgs([
|
|
36
|
+
"git",
|
|
37
|
+
"add",
|
|
38
|
+
"-A",
|
|
39
|
+
".bumpy/"
|
|
40
|
+
], { cwd: rootDir });
|
|
41
|
+
for (const r of plan.releases) {
|
|
42
|
+
const pkg = packages.get(r.name);
|
|
43
|
+
runArgs([
|
|
44
|
+
"git",
|
|
45
|
+
"add",
|
|
46
|
+
"--",
|
|
47
|
+
`${pkg.relativeDir}/package.json`
|
|
48
|
+
], { cwd: rootDir });
|
|
49
|
+
runArgs([
|
|
50
|
+
"git",
|
|
51
|
+
"add",
|
|
52
|
+
"--",
|
|
53
|
+
`${pkg.relativeDir}/CHANGELOG.md`
|
|
54
|
+
], { cwd: rootDir });
|
|
55
|
+
}
|
|
56
|
+
for (const lockfile of [
|
|
57
|
+
"bun.lock",
|
|
58
|
+
"bun.lockb",
|
|
59
|
+
"pnpm-lock.yaml",
|
|
60
|
+
"yarn.lock",
|
|
61
|
+
"package-lock.json"
|
|
62
|
+
]) tryRunArgs([
|
|
63
|
+
"git",
|
|
64
|
+
"add",
|
|
65
|
+
"--",
|
|
66
|
+
lockfile
|
|
67
|
+
], { cwd: rootDir });
|
|
68
|
+
runArgs([
|
|
69
|
+
"git",
|
|
70
|
+
"commit",
|
|
71
|
+
"-F",
|
|
72
|
+
"-"
|
|
73
|
+
], {
|
|
74
|
+
cwd: rootDir,
|
|
75
|
+
input: [
|
|
76
|
+
"Version packages",
|
|
77
|
+
"",
|
|
78
|
+
...plan.releases.map((r) => `${r.name}@${r.newVersion}`)
|
|
79
|
+
].join("\n")
|
|
80
|
+
});
|
|
81
|
+
log.success("Created git commit");
|
|
82
|
+
} catch (e) {
|
|
83
|
+
log.warn(`Git commit failed: ${e}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Run the package manager's install to update the lockfile */
|
|
87
|
+
async function updateLockfile(rootDir) {
|
|
88
|
+
const { packageManager } = await detectWorkspaces(rootDir);
|
|
89
|
+
const installArgs = getInstallArgs(packageManager);
|
|
90
|
+
log.step(`Updating lockfile (${installArgs.join(" ")})...`);
|
|
91
|
+
try {
|
|
92
|
+
runArgs(installArgs, { cwd: rootDir });
|
|
93
|
+
log.dim(" Lockfile updated");
|
|
94
|
+
} catch (err) {
|
|
95
|
+
log.warn(` Lockfile update failed: ${err instanceof Error ? err.message : err}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function getInstallArgs(pm) {
|
|
99
|
+
switch (pm) {
|
|
100
|
+
case "pnpm": return [
|
|
101
|
+
"pnpm",
|
|
102
|
+
"install",
|
|
103
|
+
"--lockfile-only"
|
|
104
|
+
];
|
|
105
|
+
case "bun": return ["bun", "install"];
|
|
106
|
+
case "yarn": return [
|
|
107
|
+
"yarn",
|
|
108
|
+
"install",
|
|
109
|
+
"--mode",
|
|
110
|
+
"update-lockfile"
|
|
111
|
+
];
|
|
112
|
+
default: return [
|
|
113
|
+
"npm",
|
|
114
|
+
"install",
|
|
115
|
+
"--package-lock-only"
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { versionCommand };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { a as readJson, n as exists } from "./fs-0AtnPUUe.mjs";
|
|
2
|
+
import { i as isPackageManaged, o as loadPackageConfig } from "./config-BkwIEaQg.mjs";
|
|
3
|
+
import { n as detectWorkspaces } from "./package-manager-DcI5TdDE.mjs";
|
|
4
|
+
import { relative, resolve } from "node:path";
|
|
5
|
+
import { readdir, stat } from "node:fs/promises";
|
|
6
|
+
//#region src/core/workspace.ts
|
|
7
|
+
/** Discover all workspace packages and catalogs in a monorepo */
|
|
8
|
+
async function discoverWorkspace(rootDir, config) {
|
|
9
|
+
const { globs, catalogs } = await detectWorkspaces(rootDir);
|
|
10
|
+
if (globs.length === 0) throw new Error("No workspace globs found. Is this a monorepo?");
|
|
11
|
+
const packages = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const glob of globs) {
|
|
13
|
+
const dirs = await resolveGlob(rootDir, glob);
|
|
14
|
+
for (const dir of dirs) {
|
|
15
|
+
const pkg = await loadWorkspacePackage(dir, rootDir, config);
|
|
16
|
+
if (pkg) {
|
|
17
|
+
if (!isPackageManaged(pkg.name, pkg.private, config, pkg.bumpy)) continue;
|
|
18
|
+
packages.set(pkg.name, pkg);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
packages,
|
|
24
|
+
catalogs
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Convenience wrapper that returns just packages (backwards compat) */
|
|
28
|
+
async function discoverPackages(rootDir, config) {
|
|
29
|
+
const { packages } = await discoverWorkspace(rootDir, config);
|
|
30
|
+
return packages;
|
|
31
|
+
}
|
|
32
|
+
/** Resolve a workspace glob pattern to directories containing package.json */
|
|
33
|
+
async function resolveGlob(rootDir, pattern) {
|
|
34
|
+
return expandGlob(rootDir, pattern.split("/"));
|
|
35
|
+
}
|
|
36
|
+
async function expandGlob(baseDir, parts) {
|
|
37
|
+
if (parts.length === 0) {
|
|
38
|
+
if (await exists(resolve(baseDir, "package.json"))) return [baseDir];
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const [current, ...rest] = parts;
|
|
42
|
+
if (current === "*") {
|
|
43
|
+
const entries = await safeReaddir(baseDir);
|
|
44
|
+
const results = [];
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const entryPath = resolve(baseDir, entry);
|
|
47
|
+
if (await isDirectory(entryPath)) results.push(...await expandGlob(entryPath, rest));
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
} else if (current === "**") {
|
|
51
|
+
const results = [];
|
|
52
|
+
results.push(...await expandGlob(baseDir, rest));
|
|
53
|
+
const entries = await safeReaddir(baseDir);
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
56
|
+
const entryPath = resolve(baseDir, entry);
|
|
57
|
+
if (await isDirectory(entryPath)) results.push(...await expandGlob(entryPath, parts));
|
|
58
|
+
}
|
|
59
|
+
return results;
|
|
60
|
+
} else {
|
|
61
|
+
const next = resolve(baseDir, current);
|
|
62
|
+
if (await isDirectory(next)) return expandGlob(next, rest);
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function safeReaddir(dir) {
|
|
67
|
+
try {
|
|
68
|
+
return await readdir(dir);
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function isDirectory(path) {
|
|
74
|
+
try {
|
|
75
|
+
return (await stat(path)).isDirectory();
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function loadWorkspacePackage(dir, rootDir, config) {
|
|
81
|
+
const pkgPath = resolve(dir, "package.json");
|
|
82
|
+
if (!await exists(pkgPath)) return null;
|
|
83
|
+
let pkg;
|
|
84
|
+
try {
|
|
85
|
+
pkg = await readJson(pkgPath);
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const name = pkg.name;
|
|
90
|
+
if (!name) return null;
|
|
91
|
+
const bumpy = await loadPackageConfig(dir, config, name);
|
|
92
|
+
return {
|
|
93
|
+
name,
|
|
94
|
+
version: pkg.version || "0.0.0",
|
|
95
|
+
dir: resolve(dir),
|
|
96
|
+
relativeDir: relative(rootDir, dir),
|
|
97
|
+
packageJson: pkg,
|
|
98
|
+
private: !!pkg.private,
|
|
99
|
+
dependencies: pkg.dependencies || {},
|
|
100
|
+
devDependencies: pkg.devDependencies || {},
|
|
101
|
+
peerDependencies: pkg.peerDependencies || {},
|
|
102
|
+
optionalDependencies: pkg.optionalDependencies || {},
|
|
103
|
+
bumpy
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { discoverWorkspace as n, discoverPackages as t };
|
package/package.json
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@varlock/bumpy",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
"version": "0.0.2",
|
|
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
|
+
"check": "bun run tsc --noEmit",
|
|
28
|
+
"test": "bun test"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@clack/prompts": "^1.2.0",
|
|
32
|
+
"@types/bun": "latest",
|
|
33
|
+
"@types/js-yaml": "^4.0.9",
|
|
34
|
+
"@types/semver": "^7.7.0",
|
|
35
|
+
"js-yaml": "^4.1.0",
|
|
36
|
+
"picocolors": "^1.1.1",
|
|
37
|
+
"semver": "^7.7.2",
|
|
38
|
+
"tsdown": "^0.21.8"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -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
|