@varlock/bumpy 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/config-schema.json +327 -0
- package/dist/{add-CgCjs4d-.mjs → add-yP81c9_q.mjs} +23 -13
- package/dist/{ai-sMYUf3lP.mjs → ai-STKnq09z.mjs} +1 -1
- package/dist/{apply-release-plan-CczGWJTk.mjs → apply-release-plan-CPzu6JcF.mjs} +12 -7
- package/dist/{bump-file-CCLXMLA8.mjs → bump-file-Br2bTaWp.mjs} +36 -10
- package/dist/{changelog-github-Cd8uJHZI.mjs → changelog-github-DkACMj0j.mjs} +1 -1
- package/dist/check-D_0exKi6.mjs +87 -0
- package/dist/{ci-Bhx--Tj6.mjs → ci-CvaikKX1.mjs} +48 -33
- package/dist/{ci-setup-qz4Y3v7T.mjs → ci-setup-CARJFhcE.mjs} +3 -3
- package/dist/cli.mjs +31 -31
- package/dist/commit-message-BwsowSds.mjs +23 -0
- package/dist/{config-XZWUL3ma.mjs → config-D7Umr-fT.mjs} +6 -5
- package/dist/{fs-DYR2XuFE.mjs → fs-DnDogVn-.mjs} +16 -1
- package/dist/{generate-gYKTpvex.mjs → generate-BOLrTYWR.mjs} +74 -65
- package/dist/{git-CGHVXXKw.mjs → git-YDedMddc.mjs} +54 -2
- package/dist/index.d.mts +13 -2
- package/dist/index.mjs +8 -8
- package/dist/init-DJhMaceS.mjs +196 -0
- package/dist/{js-yaml-DpZfOoD4.mjs → package-manager-ByJ0wKYh.mjs} +79 -1
- package/dist/picomatch-DMmqYjgq.mjs +1870 -0
- package/dist/{publish-Cun-zQ1b.mjs → publish-CPZwqyWh.mjs} +10 -10
- package/dist/{publish-pipeline-BwBuKCIk.mjs → publish-pipeline-BFt96o_h.mjs} +5 -5
- package/dist/{release-plan-Bi5QNSEo.mjs → release-plan-CNOuSI-d.mjs} +2 -2
- package/dist/{shell-Dj7JRD_q.mjs → shell-CY7OD48z.mjs} +20 -2
- package/dist/{status-CfE63ti5.mjs → status-skGX8uU7.mjs} +6 -6
- package/dist/{version-19vVt9dv.mjs → version-CnXcbqi1.mjs} +13 -16
- package/dist/{workspace-C5ULTyUN.mjs → workspace-BHsAPUmC.mjs} +3 -3
- package/package.json +5 -1
- package/skills/add-change/SKILL.md +11 -3
- package/dist/check-BOoxpWqk.mjs +0 -51
- package/dist/init-lA9E5pEc.mjs +0 -22
- package/dist/migrate-DmOYgmfD.mjs +0 -121
- package/dist/package-manager-VCe10bjc.mjs +0 -80
- /package/dist/{clack-CDRCHrC-.mjs → clack-C6bVkGxf.mjs} +0 -0
- /package/dist/{dep-graph-E-9-eQ2J.mjs → dep-graph-DiLeAhl9.mjs} +0 -0
- /package/dist/{names-9VubBmL0.mjs → names-C-TuOPbd.mjs} +0 -0
- /package/dist/{semver-DfQyVLM_.mjs → semver-BJzWIuRz.mjs} +0 -0
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
|
|
2
2
|
import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
//#region src/utils/jsonc.ts
|
|
4
|
+
const stringOrCommentRe = /("(?:\\?[^])*?")|(\/\/.*)|(\/\*[^]*?\*\/)/g;
|
|
5
|
+
const stringOrTrailingCommaRe = /("(?:\\?[^])*?")|(,\s*)(?=]|})/g;
|
|
6
|
+
function parseJsonc(text) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text);
|
|
9
|
+
} catch {
|
|
10
|
+
return JSON.parse(text.replace(stringOrCommentRe, "$1").replace(stringOrTrailingCommaRe, "$1"));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
3
14
|
//#region src/utils/fs.ts
|
|
4
15
|
var fs_exports = /* @__PURE__ */ __exportAll({
|
|
5
16
|
ensureDir: () => ensureDir,
|
|
6
17
|
exists: () => exists,
|
|
7
18
|
listFiles: () => listFiles,
|
|
8
19
|
readJson: () => readJson,
|
|
20
|
+
readJsonc: () => readJsonc,
|
|
9
21
|
readText: () => readText,
|
|
10
22
|
removeFile: () => removeFile,
|
|
11
23
|
updateJsonFields: () => updateJsonFields,
|
|
@@ -17,6 +29,9 @@ async function readJson(filePath) {
|
|
|
17
29
|
const content = await readFile(filePath, "utf-8");
|
|
18
30
|
return JSON.parse(content);
|
|
19
31
|
}
|
|
32
|
+
async function readJsonc(filePath) {
|
|
33
|
+
return parseJsonc(await readFile(filePath, "utf-8"));
|
|
34
|
+
}
|
|
20
35
|
async function writeJson(filePath, data, indent = 2) {
|
|
21
36
|
await writeFile(filePath, JSON.stringify(data, null, indent) + "\n", "utf-8");
|
|
22
37
|
}
|
|
@@ -78,4 +93,4 @@ async function ensureDir(dir) {
|
|
|
78
93
|
await mkdir(dir, { recursive: true });
|
|
79
94
|
}
|
|
80
95
|
//#endregion
|
|
81
|
-
export { readJson as a,
|
|
96
|
+
export { readJson as a, removeFile as c, writeJson as d, writeText as f, listFiles as i, updateJsonFields as l, exists as n, readJsonc as o, fs_exports as r, readText as s, ensureDir as t, updateJsonNestedField as u };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
-
import { t as ensureDir } from "./fs-
|
|
3
|
-
import { a as loadConfig, r as getBumpyDir } from "./config-
|
|
4
|
-
import { t as discoverPackages } from "./workspace-
|
|
5
|
-
import {
|
|
6
|
-
import { i as writeBumpFile } from "./bump-file-
|
|
7
|
-
import {
|
|
2
|
+
import { t as ensureDir } from "./fs-DnDogVn-.mjs";
|
|
3
|
+
import { a as loadConfig, r as getBumpyDir } from "./config-D7Umr-fT.mjs";
|
|
4
|
+
import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
|
|
5
|
+
import { s as tryRunArgs } from "./shell-CY7OD48z.mjs";
|
|
6
|
+
import { i as writeBumpFile } from "./bump-file-Br2bTaWp.mjs";
|
|
7
|
+
import { i as getFilesChangedInCommit, n as getBranchCommits } from "./git-YDedMddc.mjs";
|
|
8
|
+
import { n as slugify, t as randomName } from "./names-C-TuOPbd.mjs";
|
|
9
|
+
import { relative } from "node:path";
|
|
8
10
|
//#region src/commands/generate.ts
|
|
9
11
|
const BUMP_MAP = {
|
|
10
12
|
feat: "minor",
|
|
@@ -21,57 +23,62 @@ const BUMP_MAP = {
|
|
|
21
23
|
async function generateCommand(rootDir, opts) {
|
|
22
24
|
const config = await loadConfig(rootDir);
|
|
23
25
|
const packages = await discoverPackages(rootDir, config);
|
|
24
|
-
|
|
25
|
-
if (
|
|
26
|
-
log.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
let commits;
|
|
27
|
+
if (opts.from) {
|
|
28
|
+
log.step(`Scanning commits from ${colorize(opts.from, "cyan")}...`);
|
|
29
|
+
const rawLog = tryRunArgs([
|
|
30
|
+
"git",
|
|
31
|
+
"log",
|
|
32
|
+
`${opts.from}..HEAD`,
|
|
33
|
+
"--format=%H%n%s%n%b%n---END---"
|
|
34
|
+
], { cwd: rootDir });
|
|
35
|
+
if (!rawLog) {
|
|
36
|
+
log.info("No commits found since " + opts.from);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
commits = parseGitLog(rawLog);
|
|
40
|
+
} else {
|
|
41
|
+
log.step(`Scanning commits on this branch (vs ${colorize(config.baseBranch, "cyan")})...`);
|
|
42
|
+
commits = getBranchCommits(rootDir, config.baseBranch);
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
log.info("No conventional commits found. Commits must follow the format: type(scope): description");
|
|
44
|
+
if (commits.length === 0) {
|
|
45
|
+
log.info("No commits found on this branch.");
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
|
-
log.dim(` Found ${
|
|
48
|
+
log.dim(` Found ${commits.length} commit(s)`);
|
|
46
49
|
const scopeMap = buildScopeMap(packages, config);
|
|
47
50
|
const releaseMap = /* @__PURE__ */ new Map();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
let ccCount = 0;
|
|
52
|
+
let fileBasedCount = 0;
|
|
53
|
+
for (const commit of commits) {
|
|
54
|
+
const cc = parseConventionalCommit(commit);
|
|
55
|
+
if (cc) {
|
|
56
|
+
ccCount++;
|
|
57
|
+
const bump = cc.breaking ? "major" : BUMP_MAP[cc.type] || "patch";
|
|
58
|
+
let pkgNames = [];
|
|
59
|
+
if (cc.scope) {
|
|
60
|
+
const resolved = resolveScope(cc.scope, scopeMap, packages);
|
|
61
|
+
if (resolved.length > 0) pkgNames = resolved;
|
|
62
|
+
}
|
|
63
|
+
if (pkgNames.length > 0) {
|
|
64
|
+
for (const name of pkgNames) mergeRelease(releaseMap, name, bump, cc.description);
|
|
56
65
|
continue;
|
|
57
66
|
}
|
|
67
|
+
const touchedPkgs = mapFilesToPackages(getFilesChangedInCommit(commit.hash, { cwd: rootDir }), packages, rootDir);
|
|
68
|
+
if (touchedPkgs.length > 0) for (const name of touchedPkgs) mergeRelease(releaseMap, name, bump, cc.description);
|
|
69
|
+
else log.dim(` Skipping CC (no matching packages): ${cc.type}: ${cc.description}`);
|
|
58
70
|
} else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
const touchedPkgs = mapFilesToPackages(getFilesChangedInCommit(commit.hash, { cwd: rootDir }), packages, rootDir);
|
|
72
|
+
if (touchedPkgs.length > 0) {
|
|
73
|
+
fileBasedCount++;
|
|
74
|
+
for (const name of touchedPkgs) mergeRelease(releaseMap, name, "patch", commit.subject);
|
|
75
|
+
} else log.dim(` Skipping (no matching packages): ${commit.subject}`);
|
|
71
76
|
}
|
|
72
77
|
}
|
|
78
|
+
if (ccCount > 0) log.dim(` ${ccCount} conventional commit(s)`);
|
|
79
|
+
if (fileBasedCount > 0) log.dim(` ${fileBasedCount} commit(s) detected via changed files`);
|
|
73
80
|
if (releaseMap.size === 0) {
|
|
74
|
-
log.info("No package bumps detected from
|
|
81
|
+
log.info("No package bumps detected from commits.");
|
|
75
82
|
return;
|
|
76
83
|
}
|
|
77
84
|
const releases = [];
|
|
@@ -94,9 +101,29 @@ async function generateCommand(rootDir, opts) {
|
|
|
94
101
|
await ensureDir(getBumpyDir(rootDir));
|
|
95
102
|
const filename = opts.name ? slugify(opts.name) : randomName();
|
|
96
103
|
await writeBumpFile(rootDir, filename, releases, summaryLines.join("\n"));
|
|
97
|
-
log.success(
|
|
104
|
+
log.success(`🐸 Created bump file: .bumpy/${filename}.md`);
|
|
98
105
|
for (const r of releases) log.dim(` ${r.name}: ${r.type}`);
|
|
99
106
|
}
|
|
107
|
+
/** Merge a bump into the release map, keeping the highest bump level */
|
|
108
|
+
function mergeRelease(releaseMap, name, bump, message) {
|
|
109
|
+
const existing = releaseMap.get(name);
|
|
110
|
+
if (existing) {
|
|
111
|
+
if (bumpPriority(bump) > bumpPriority(existing.type)) existing.type = bump;
|
|
112
|
+
existing.messages.push(message);
|
|
113
|
+
} else releaseMap.set(name, {
|
|
114
|
+
type: bump,
|
|
115
|
+
messages: [message]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** Map file paths to package names based on directory containment */
|
|
119
|
+
function mapFilesToPackages(files, packages, rootDir) {
|
|
120
|
+
const matched = /* @__PURE__ */ new Set();
|
|
121
|
+
for (const file of files) for (const [name, pkg] of packages) {
|
|
122
|
+
const pkgRelDir = relative(rootDir, pkg.dir);
|
|
123
|
+
if (file.startsWith(pkgRelDir + "/")) matched.add(name);
|
|
124
|
+
}
|
|
125
|
+
return [...matched];
|
|
126
|
+
}
|
|
100
127
|
/** Parse raw git log output into individual commits */
|
|
101
128
|
function parseGitLog(raw) {
|
|
102
129
|
const commits = [];
|
|
@@ -155,23 +182,5 @@ function resolveScope(scope, scopeMap, packages) {
|
|
|
155
182
|
function bumpPriority(type) {
|
|
156
183
|
return type === "major" ? 2 : type === "minor" ? 1 : 0;
|
|
157
184
|
}
|
|
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
185
|
//#endregion
|
|
177
186
|
export { generateCommand };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as runArgs, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
|
|
2
2
|
//#region src/core/git.ts
|
|
3
3
|
/** Create a git tag */
|
|
4
4
|
function createTag(tag, opts) {
|
|
@@ -63,6 +63,58 @@ function getChangedFiles(rootDir, baseBranch) {
|
|
|
63
63
|
if (!diff) return [];
|
|
64
64
|
return diff.split("\n").filter(Boolean);
|
|
65
65
|
}
|
|
66
|
+
/** Get commits on the current branch since it diverged from baseBranch */
|
|
67
|
+
function getBranchCommits(rootDir, baseBranch) {
|
|
68
|
+
if (!tryRunArgs([
|
|
69
|
+
"git",
|
|
70
|
+
"rev-parse",
|
|
71
|
+
"--verify",
|
|
72
|
+
`origin/${baseBranch}`
|
|
73
|
+
], { cwd: rootDir })) tryRunArgs([
|
|
74
|
+
"git",
|
|
75
|
+
"fetch",
|
|
76
|
+
"origin",
|
|
77
|
+
baseBranch,
|
|
78
|
+
"--depth=1"
|
|
79
|
+
], { cwd: rootDir });
|
|
80
|
+
const rawLog = tryRunArgs([
|
|
81
|
+
"git",
|
|
82
|
+
"log",
|
|
83
|
+
`${tryRunArgs([
|
|
84
|
+
"git",
|
|
85
|
+
"merge-base",
|
|
86
|
+
"HEAD",
|
|
87
|
+
`origin/${baseBranch}`
|
|
88
|
+
], { cwd: rootDir }) || `origin/${baseBranch}`}..HEAD`,
|
|
89
|
+
"--format=%H%n%s%n%b%n---END---"
|
|
90
|
+
], { cwd: rootDir });
|
|
91
|
+
if (!rawLog) return [];
|
|
92
|
+
const commits = [];
|
|
93
|
+
const entries = rawLog.split("---END---").filter((e) => e.trim());
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const lines = entry.trim().split("\n");
|
|
96
|
+
if (lines.length < 2) continue;
|
|
97
|
+
commits.push({
|
|
98
|
+
hash: lines[0].trim(),
|
|
99
|
+
subject: lines[1].trim(),
|
|
100
|
+
body: lines.slice(2).join("\n").trim()
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return commits;
|
|
104
|
+
}
|
|
105
|
+
/** Get files changed in a specific commit */
|
|
106
|
+
function getFilesChangedInCommit(hash, opts) {
|
|
107
|
+
const result = tryRunArgs([
|
|
108
|
+
"git",
|
|
109
|
+
"diff-tree",
|
|
110
|
+
"--no-commit-id",
|
|
111
|
+
"--name-only",
|
|
112
|
+
"-r",
|
|
113
|
+
hash
|
|
114
|
+
], opts);
|
|
115
|
+
if (!result) return [];
|
|
116
|
+
return result.split("\n").filter(Boolean);
|
|
117
|
+
}
|
|
66
118
|
/** Get all tags matching a pattern */
|
|
67
119
|
function listTags(pattern, opts) {
|
|
68
120
|
const result = tryRunArgs([
|
|
@@ -75,4 +127,4 @@ function listTags(pattern, opts) {
|
|
|
75
127
|
return result.split("\n").filter(Boolean);
|
|
76
128
|
}
|
|
77
129
|
//#endregion
|
|
78
|
-
export {
|
|
130
|
+
export { hasUncommittedChanges as a, tagExists as c, getFilesChangedInCommit as i, getBranchCommits as n, listTags as o, getChangedFiles as r, pushWithTags as s, createTag as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -32,10 +32,19 @@ interface PublishConfig {
|
|
|
32
32
|
interface BumpyConfig {
|
|
33
33
|
baseBranch: string;
|
|
34
34
|
access: 'public' | 'restricted';
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Customize the commit message used when versioning.
|
|
37
|
+
* A string starting with "./" is treated as a path to a module that exports
|
|
38
|
+
* a function receiving the release plan and returning a message string.
|
|
39
|
+
* Any other string is used as a static commit message.
|
|
40
|
+
* Omit to use the default: "Version packages\n\npkg@version..."
|
|
41
|
+
*/
|
|
42
|
+
versionCommitMessage?: string;
|
|
43
|
+
changelog: false | string | [string, Record<string, unknown>];
|
|
37
44
|
fixed: string[][];
|
|
38
45
|
linked: string[][];
|
|
46
|
+
/** Glob patterns to filter which changed files count toward marking a package as changed */
|
|
47
|
+
changedFilePatterns: string[];
|
|
39
48
|
/** Package names/globs to exclude from version management */
|
|
40
49
|
ignore: string[];
|
|
41
50
|
/** Package names/globs to explicitly include (overrides private + ignore) */
|
|
@@ -90,6 +99,8 @@ interface PackageConfig {
|
|
|
90
99
|
skipNpmPublish?: boolean;
|
|
91
100
|
/** Command to check if a version is already published. Should output the published version string. */
|
|
92
101
|
checkPublished?: string;
|
|
102
|
+
/** Glob patterns to filter which changed files count toward marking this package as changed */
|
|
103
|
+
changedFilePatterns?: string[];
|
|
93
104
|
dependencyBumpRules?: Partial<Record<DepType, DependencyBumpRule | false>>;
|
|
94
105
|
cascadeTo?: Record<string, DependencyBumpRule>;
|
|
95
106
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as loadConfig, c as BUMP_LEVELS, d as DEFAULT_PUBLISH_CONFIG, f as DEP_TYPES, h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, n as findRoot, p as bumpLevel, r as getBumpyDir, s as matchGlob, u as DEFAULT_CONFIG } from "./config-
|
|
2
|
-
import { t as discoverPackages } from "./workspace-
|
|
3
|
-
import { t as DependencyGraph } from "./dep-graph-
|
|
4
|
-
import { i as writeBumpFile, n as parseBumpFile, r as readBumpFiles } from "./bump-file-
|
|
5
|
-
import { n as satisfies, r as stripProtocol, t as bumpVersion } from "./semver-
|
|
6
|
-
import { t as assembleReleasePlan } from "./release-plan-
|
|
7
|
-
import { a as prependToChangelog, i as loadFormatter, n as defaultFormatter, r as generateChangelogEntry, t as applyReleasePlan } from "./apply-release-plan-
|
|
8
|
-
import { t as publishPackages } from "./publish-pipeline-
|
|
1
|
+
import { a as loadConfig, c as BUMP_LEVELS, d as DEFAULT_PUBLISH_CONFIG, f as DEP_TYPES, h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, n as findRoot, p as bumpLevel, r as getBumpyDir, s as matchGlob, u as DEFAULT_CONFIG } from "./config-D7Umr-fT.mjs";
|
|
2
|
+
import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
|
|
3
|
+
import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
|
|
4
|
+
import { i as writeBumpFile, n as parseBumpFile, r as readBumpFiles } from "./bump-file-Br2bTaWp.mjs";
|
|
5
|
+
import { n as satisfies, r as stripProtocol, t as bumpVersion } from "./semver-BJzWIuRz.mjs";
|
|
6
|
+
import { t as assembleReleasePlan } from "./release-plan-CNOuSI-d.mjs";
|
|
7
|
+
import { a as prependToChangelog, i as loadFormatter, n as defaultFormatter, r as generateChangelogEntry, t as applyReleasePlan } from "./apply-release-plan-CPzu6JcF.mjs";
|
|
8
|
+
import { t as publishPackages } from "./publish-pipeline-BFt96o_h.mjs";
|
|
9
9
|
export { BUMP_LEVELS, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DependencyGraph, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { n as log, o as __toESM, r as require_picocolors } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { a as readJson, d as writeJson, f as writeText, i as listFiles, n as exists, s as readText, t as ensureDir } from "./fs-DnDogVn-.mjs";
|
|
3
|
+
import { t as detectPackageManager } from "./package-manager-ByJ0wKYh.mjs";
|
|
4
|
+
import { t as run } from "./shell-CY7OD48z.mjs";
|
|
5
|
+
import { c as ot, o as gt, s as mt, t as unwrap } from "./clack-C6bVkGxf.mjs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { readdir, rename, rm } from "node:fs/promises";
|
|
8
|
+
//#region ../../.bumpy/README.md
|
|
9
|
+
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
10
|
+
var README_default = "# 🐸 Bumpy\n\nThis directory is used by [bumpy](https://bumpy.varlock.dev) to manage versioning and changelogs.\n\nBumpy is a modern versioning tool for JavaScript/TypeScript projects (monorepos and single packages). It uses **bump files** — small markdown files in this directory — to declare pending version changes. These files are consumed during the release process to compute version bumps, update changelogs, and publish packages.\n\n## How it works\n\n1. When you make a change that should trigger a release, create a bump file (typically one per PR)\n2. Bump files accumulate on your main branch until you're ready to release\n3. At release time, bumpy merges all pending bumps into a release plan, updates versions and changelogs, and publishes packages\n\n## Creating bump files\n\n### Interactive\n\n```bash\nbunx bumpy add\n```\n\n### Non-interactive (useful for AI-assisted development)\n\n```bash\nbunx bumpy add --packages \"package-name:minor,other-package:patch\" --message \"Description of changes\" --name \"my-change\"\n```\n\n### By hand\n\nCreate a `.md` file in this directory with YAML frontmatter mapping package names to bump levels (`major`, `minor`, `patch`, or `none`), and a markdown body for the changelog entry:\n\n```markdown\n---\n'package-name': minor\n---\n\nAdded a new feature.\n```\n\n### From conventional commits\n\n```bash\nbunx bumpy generate\n```\n\n### Empty bump files\n\nFor PRs that intentionally don't need a release (docs, CI, etc.):\n\n```bash\nbunx bumpy add --empty --name \"docs-update\"\n```\n\n## Keeping bump files up to date\n\nAs a PR evolves, make sure its bump file stays in sync. If the scope of changes grows (e.g., a patch becomes a new feature), update the bump level and description to match. Reviewers and AI assistants should treat the bump file as part of the PR — just like tests and docs.\n\n## Files in this directory\n\n- `_config.json` — bumpy configuration\n- `README.md` — this file\n- `*.md` (other than README.md) — pending bump files\n\n📖 Full documentation: https://bumpy.varlock.dev\n";
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/commands/init.ts
|
|
13
|
+
const PM_RUNNER = {
|
|
14
|
+
bun: "bunx bumpy",
|
|
15
|
+
pnpm: "pnpm bumpy",
|
|
16
|
+
yarn: "yarn bumpy",
|
|
17
|
+
npm: "npx bumpy"
|
|
18
|
+
};
|
|
19
|
+
const PM_ADD_DEV = {
|
|
20
|
+
bun: "bun add -d",
|
|
21
|
+
pnpm: "pnpm add -Dw",
|
|
22
|
+
yarn: "yarn add -D -W",
|
|
23
|
+
npm: "npm install -D"
|
|
24
|
+
};
|
|
25
|
+
const PM_REMOVE = {
|
|
26
|
+
bun: "bun remove",
|
|
27
|
+
pnpm: "pnpm remove -w",
|
|
28
|
+
yarn: "yarn remove -W",
|
|
29
|
+
npm: "npm uninstall"
|
|
30
|
+
};
|
|
31
|
+
async function initCommand(rootDir, opts = {}) {
|
|
32
|
+
const bumpyDir = resolve(rootDir, ".bumpy");
|
|
33
|
+
const changesetDir = resolve(rootDir, ".changeset");
|
|
34
|
+
const hasChangeset = await exists(changesetDir);
|
|
35
|
+
if (await exists(resolve(bumpyDir, "_config.json"))) {
|
|
36
|
+
log.info("🐸 Detected .bumpy/ directory - looks like we're ready to go!");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const pm = await detectPackageManager(rootDir);
|
|
40
|
+
if (!opts.force) mt(import_picocolors.default.bgCyan(import_picocolors.default.black(" bumpy init ")));
|
|
41
|
+
if (hasChangeset) {
|
|
42
|
+
log.step("🦋 Detected .changeset/ directory — migrating to .bumpy/ 🐸");
|
|
43
|
+
await rename(changesetDir, bumpyDir);
|
|
44
|
+
log.dim(" Renamed .changeset/ → .bumpy/");
|
|
45
|
+
const oldConfigPath = resolve(bumpyDir, "config.json");
|
|
46
|
+
if (await exists(oldConfigPath)) {
|
|
47
|
+
const bumpyConfig = migrateChangesetConfig(await readJson(oldConfigPath));
|
|
48
|
+
await writeJson(resolve(bumpyDir, "_config.json"), bumpyConfig);
|
|
49
|
+
await rm(oldConfigPath);
|
|
50
|
+
log.dim(" Migrated config.json → _config.json");
|
|
51
|
+
const migratedFields = Object.keys(bumpyConfig).filter((k) => k !== "$schema");
|
|
52
|
+
if (migratedFields.length > 0) log.dim(" Migrated fields: " + migratedFields.join(", "));
|
|
53
|
+
} else await writeJson(resolve(bumpyDir, "_config.json"), makeDefaultConfig());
|
|
54
|
+
const readmeContent = README_default.replaceAll("bunx bumpy", PM_RUNNER[pm] || "npx bumpy");
|
|
55
|
+
await writeText(resolve(bumpyDir, "README.md"), readmeContent);
|
|
56
|
+
log.dim(" Replaced README.md");
|
|
57
|
+
const pendingFiles = (await readdir(bumpyDir)).filter((f) => f.endsWith(".md") && f !== "README.md");
|
|
58
|
+
if (pendingFiles.length > 0) log.dim(` Kept ${pendingFiles.length} pending bump file(s)`);
|
|
59
|
+
if (await isPackageInstalled(rootDir, "@changesets/cli")) {
|
|
60
|
+
if (opts.force) await uninstallPackage(pm, "@changesets/cli", rootDir);
|
|
61
|
+
else if (unwrap(await ot({
|
|
62
|
+
message: "Uninstall @changesets/cli?",
|
|
63
|
+
initialValue: true
|
|
64
|
+
}))) await uninstallPackage(pm, "@changesets/cli", rootDir);
|
|
65
|
+
}
|
|
66
|
+
await warnChangesetWorkflows(rootDir, pm);
|
|
67
|
+
} else {
|
|
68
|
+
await ensureDir(bumpyDir);
|
|
69
|
+
await writeJson(resolve(bumpyDir, "_config.json"), makeDefaultConfig());
|
|
70
|
+
const readmeContent = README_default.replaceAll("bunx bumpy", PM_RUNNER[pm] || "npx bumpy");
|
|
71
|
+
await writeText(resolve(bumpyDir, "README.md"), readmeContent);
|
|
72
|
+
log.success("Initialized .bumpy/ directory");
|
|
73
|
+
log.dim(" Created .bumpy/_config.json");
|
|
74
|
+
log.dim(" Created .bumpy/README.md");
|
|
75
|
+
}
|
|
76
|
+
if (!await isPackageInstalled(rootDir, "@varlock/bumpy")) {
|
|
77
|
+
if (opts.force) await installPackage(pm, "@varlock/bumpy", rootDir);
|
|
78
|
+
else if (unwrap(await ot({
|
|
79
|
+
message: "Install @varlock/bumpy as a dev dependency?",
|
|
80
|
+
initialValue: true
|
|
81
|
+
}))) await installPackage(pm, "@varlock/bumpy", rootDir);
|
|
82
|
+
}
|
|
83
|
+
if (!opts.force) gt(import_picocolors.default.green("bumpy is ready!"));
|
|
84
|
+
else if (hasChangeset) log.success("Migration complete!");
|
|
85
|
+
}
|
|
86
|
+
function makeDefaultConfig() {
|
|
87
|
+
return {
|
|
88
|
+
$schema: "../node_modules/@varlock/bumpy/config-schema.json",
|
|
89
|
+
baseBranch: "main",
|
|
90
|
+
changelog: "default"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function migrateChangesetConfig(csConfig) {
|
|
94
|
+
const bumpyConfig = makeDefaultConfig();
|
|
95
|
+
for (const field of [
|
|
96
|
+
"baseBranch",
|
|
97
|
+
"access",
|
|
98
|
+
"fixed",
|
|
99
|
+
"linked",
|
|
100
|
+
"ignore",
|
|
101
|
+
"updateInternalDependencies",
|
|
102
|
+
"privatePackages"
|
|
103
|
+
]) if (csConfig[field] !== void 0) bumpyConfig[field] = csConfig[field];
|
|
104
|
+
return bumpyConfig;
|
|
105
|
+
}
|
|
106
|
+
async function isPackageInstalled(rootDir, pkgName) {
|
|
107
|
+
try {
|
|
108
|
+
const pkg = await readJson(resolve(rootDir, "package.json"));
|
|
109
|
+
return !!(pkg.devDependencies?.[pkgName] || pkg.dependencies?.[pkgName]);
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function installPackage(pm, pkgName, rootDir) {
|
|
115
|
+
const cmd = `${PM_ADD_DEV[pm] || "npm install -D"} ${pkgName}`;
|
|
116
|
+
log.step(`Installing ${pkgName}...`);
|
|
117
|
+
try {
|
|
118
|
+
run(cmd, { cwd: rootDir });
|
|
119
|
+
log.dim(` ${cmd}`);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
log.warn(`Failed to install ${pkgName}: ${err instanceof Error ? err.message : err}`);
|
|
122
|
+
log.dim(` Run manually: ${cmd}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function uninstallPackage(pm, pkgName, rootDir) {
|
|
126
|
+
const cmd = `${PM_REMOVE[pm] || "npm uninstall"} ${pkgName}`;
|
|
127
|
+
log.step(`Uninstalling ${pkgName}...`);
|
|
128
|
+
try {
|
|
129
|
+
run(cmd, { cwd: rootDir });
|
|
130
|
+
log.dim(` ${cmd}`);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
log.warn(`Failed to uninstall ${pkgName}: ${err instanceof Error ? err.message : err}`);
|
|
133
|
+
log.dim(` Run manually: ${cmd}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Patterns to detect in workflow files, with suggested replacements */
|
|
137
|
+
const CHANGESET_PATTERNS = [
|
|
138
|
+
{
|
|
139
|
+
pattern: /changesets\/action/,
|
|
140
|
+
replacement: "see https://bumpy.varlock.dev/ci for bumpy CI setup"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
pattern: /changeset publish/,
|
|
144
|
+
replacement: "bumpy publish"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
pattern: /changeset version/,
|
|
148
|
+
replacement: "bumpy version"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
pattern: /changeset status/,
|
|
152
|
+
replacement: "bumpy status"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
pattern: /@changesets\//,
|
|
156
|
+
replacement: "replace with @varlock/bumpy"
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
async function warnChangesetWorkflows(rootDir, pm) {
|
|
160
|
+
const workflowDir = resolve(rootDir, ".github", "workflows");
|
|
161
|
+
if (!await exists(workflowDir)) return;
|
|
162
|
+
const yamlFiles = (await listFiles(workflowDir)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
163
|
+
if (yamlFiles.length === 0) return;
|
|
164
|
+
const runner = PM_RUNNER[pm] || "npx bumpy";
|
|
165
|
+
const hits = [];
|
|
166
|
+
for (const file of yamlFiles) {
|
|
167
|
+
const lines = (await readText(resolve(workflowDir, file))).split("\n");
|
|
168
|
+
const fileMatches = [];
|
|
169
|
+
for (let i = 0; i < lines.length; i++) {
|
|
170
|
+
const line = lines[i];
|
|
171
|
+
for (const { pattern, replacement } of CHANGESET_PATTERNS) {
|
|
172
|
+
const match = line.match(pattern);
|
|
173
|
+
if (match) fileMatches.push({
|
|
174
|
+
line: i + 1,
|
|
175
|
+
found: match[0],
|
|
176
|
+
suggestion: replacement
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (fileMatches.length > 0) hits.push({
|
|
181
|
+
file,
|
|
182
|
+
matches: fileMatches
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (hits.length === 0) return;
|
|
186
|
+
console.log();
|
|
187
|
+
log.warn("Found changeset references in GitHub workflows:");
|
|
188
|
+
for (const { file, matches } of hits) {
|
|
189
|
+
log.dim(` .github/workflows/${file}`);
|
|
190
|
+
for (const { line, found, suggestion } of matches) log.dim(` L${line}: ${import_picocolors.default.red(found)} → ${import_picocolors.default.green(suggestion)}`);
|
|
191
|
+
}
|
|
192
|
+
console.log();
|
|
193
|
+
log.dim(` Run ${import_picocolors.default.cyan(`${runner} ci setup`)} for help configuring CI workflows.`);
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
export { initCommand };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { a as readJson, n as exists, s as readText } from "./fs-DnDogVn-.mjs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
1
3
|
//#region ../../node_modules/.bun/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
|
|
2
4
|
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
|
|
3
5
|
function isNothing(subject) {
|
|
@@ -2028,4 +2030,80 @@ var jsYaml = {
|
|
|
2028
2030
|
safeDump: renamed("safeDump", "dump")
|
|
2029
2031
|
};
|
|
2030
2032
|
//#endregion
|
|
2031
|
-
|
|
2033
|
+
//#region src/utils/package-manager.ts
|
|
2034
|
+
/** Detect the package manager, extract workspace globs, and load catalogs */
|
|
2035
|
+
async function detectWorkspaces(rootDir) {
|
|
2036
|
+
const pm = await detectPackageManager(rootDir);
|
|
2037
|
+
return {
|
|
2038
|
+
packageManager: pm,
|
|
2039
|
+
globs: await getWorkspaceGlobs(rootDir, pm),
|
|
2040
|
+
catalogs: await loadCatalogs(rootDir, pm)
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
async function detectPackageManager(rootDir) {
|
|
2044
|
+
if (await exists(resolve(rootDir, "bun.lock")) || await exists(resolve(rootDir, "bun.lockb"))) return "bun";
|
|
2045
|
+
if (await exists(resolve(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
2046
|
+
if (await exists(resolve(rootDir, "yarn.lock"))) return "yarn";
|
|
2047
|
+
try {
|
|
2048
|
+
const pkg = await readJson(resolve(rootDir, "package.json"));
|
|
2049
|
+
if (typeof pkg.packageManager === "string") {
|
|
2050
|
+
const name = pkg.packageManager.split("@")[0];
|
|
2051
|
+
if (name === "pnpm" || name === "yarn" || name === "bun") return name;
|
|
2052
|
+
}
|
|
2053
|
+
} catch {}
|
|
2054
|
+
return "npm";
|
|
2055
|
+
}
|
|
2056
|
+
async function getWorkspaceGlobs(rootDir, pm) {
|
|
2057
|
+
if (pm === "pnpm") {
|
|
2058
|
+
const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
|
|
2059
|
+
if (await exists(wsFile)) {
|
|
2060
|
+
const content = await readText(wsFile);
|
|
2061
|
+
const parsed = jsYaml.load(content);
|
|
2062
|
+
if (parsed?.packages) return parsed.packages;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
try {
|
|
2066
|
+
const workspaces = (await readJson(resolve(rootDir, "package.json"))).workspaces;
|
|
2067
|
+
if (Array.isArray(workspaces)) return workspaces;
|
|
2068
|
+
if (workspaces && typeof workspaces === "object" && "packages" in workspaces) {
|
|
2069
|
+
const pkgs = workspaces.packages;
|
|
2070
|
+
if (Array.isArray(pkgs)) return pkgs;
|
|
2071
|
+
}
|
|
2072
|
+
} catch {}
|
|
2073
|
+
return [];
|
|
2074
|
+
}
|
|
2075
|
+
/** Load catalog definitions from pnpm-workspace.yaml or root package.json */
|
|
2076
|
+
async function loadCatalogs(rootDir, pm) {
|
|
2077
|
+
const catalogs = /* @__PURE__ */ new Map();
|
|
2078
|
+
if (pm === "pnpm") {
|
|
2079
|
+
const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
|
|
2080
|
+
if (await exists(wsFile)) {
|
|
2081
|
+
const content = await readText(wsFile);
|
|
2082
|
+
const parsed = jsYaml.load(content);
|
|
2083
|
+
if (parsed?.catalog) catalogs.set("", parsed.catalog);
|
|
2084
|
+
if (parsed?.catalogs) for (const [name, deps] of Object.entries(parsed.catalogs)) catalogs.set(name, deps);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
try {
|
|
2088
|
+
const pkg = await readJson(resolve(rootDir, "package.json"));
|
|
2089
|
+
if (pkg.catalog && typeof pkg.catalog === "object") catalogs.set("", pkg.catalog);
|
|
2090
|
+
if (pkg.catalogs && typeof pkg.catalogs === "object") for (const [name, deps] of Object.entries(pkg.catalogs)) catalogs.set(name, deps);
|
|
2091
|
+
const workspaces = pkg.workspaces;
|
|
2092
|
+
if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
|
|
2093
|
+
const ws = workspaces;
|
|
2094
|
+
if (ws.catalog && typeof ws.catalog === "object") catalogs.set("", ws.catalog);
|
|
2095
|
+
if (ws.catalogs && typeof ws.catalogs === "object") for (const [name, deps] of Object.entries(ws.catalogs)) catalogs.set(name, deps);
|
|
2096
|
+
}
|
|
2097
|
+
} catch {}
|
|
2098
|
+
return catalogs;
|
|
2099
|
+
}
|
|
2100
|
+
/** Resolve a specific dependency's catalog: reference */
|
|
2101
|
+
function resolveCatalogDep(depName, range, catalogs) {
|
|
2102
|
+
if (!range.startsWith("catalog:")) return null;
|
|
2103
|
+
const catalogName = range.slice(8).trim() || "";
|
|
2104
|
+
const catalog = catalogs.get(catalogName);
|
|
2105
|
+
if (!catalog) return null;
|
|
2106
|
+
return catalog[depName] ?? null;
|
|
2107
|
+
}
|
|
2108
|
+
//#endregion
|
|
2109
|
+
export { jsYaml as i, detectWorkspaces as n, resolveCatalogDep as r, detectPackageManager as t };
|