@varlock/bumpy 0.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -2
- package/config-schema.json +327 -0
- package/dist/add-BmNL5VwL.mjs +323 -0
- package/dist/{ai-CQhUyHAG.mjs → ai-sMYUf3lP.mjs} +21 -4
- package/dist/{apply-release-plan-D6TSrcwX.mjs → apply-release-plan-0kH62jhu.mjs} +35 -26
- package/dist/bump-file-DVqR3k67.mjs +157 -0
- package/dist/{changelog-github-Du62krXi.mjs → changelog-github-DkACMj0j.mjs} +23 -21
- package/dist/check-BjWF6SJm.mjs +65 -0
- package/dist/{ci-D6LQbR38.mjs → ci-DY58ugIi.mjs} +138 -91
- package/dist/{ci-setup-C6FlOfW5.mjs → ci-setup-BQwktQEe.mjs} +3 -3
- package/dist/cli.mjs +36 -41
- package/dist/commit-message-BwsowSds.mjs +23 -0
- package/dist/{config-BkwIEaQg.mjs → config-B-Qg3DZH.mjs} +30 -24
- package/dist/fs-DYR2XuFE.mjs +81 -0
- package/dist/generate-DX46X-rW.mjs +186 -0
- package/dist/{git-CGHVXXKw.mjs → git-YDedMddc.mjs} +54 -2
- package/dist/index.d.mts +68 -39
- package/dist/index.mjs +9 -9
- package/dist/init-DkTPs_WQ.mjs +196 -0
- package/dist/{names-Ck8cun7B.mjs → names-C-TuOPbd.mjs} +1 -1
- package/dist/{js-yaml-DpZfOoD4.mjs → package-manager-Clsmr-9r.mjs} +79 -1
- package/dist/picomatch-DMmqYjgq.mjs +1870 -0
- package/dist/{publish-D_7RqEYL.mjs → publish-CGB4TIKD.mjs} +26 -25
- package/dist/{publish-pipeline-ChnqW8nR.mjs → publish-pipeline-CXuqce1N.mjs} +24 -19
- package/dist/release-plan-JNir7bSM.mjs +264 -0
- package/dist/{semver-BTzYh8vc.mjs → semver-BJzWIuRz.mjs} +13 -3
- package/dist/{shell-Dj7JRD_q.mjs → shell-CY7OD48z.mjs} +20 -2
- package/dist/{status--Q8yAxQ4.mjs → status-EGYqULJg.mjs} +26 -22
- package/dist/{version-cAUkfYPx.mjs → version-BcfidiVX.mjs} +23 -22
- package/dist/{workspace-CxEKakDm.mjs → workspace-DWXlwcH4.mjs} +3 -3
- package/package.json +16 -1
- package/skills/add-change/SKILL.md +18 -14
- package/dist/add-BjyVIUlr.mjs +0 -175
- package/dist/changeset-UCZdSRDv.mjs +0 -108
- package/dist/check-jIwike9F.mjs +0 -51
- package/dist/fs-0AtnPUUe.mjs +0 -51
- package/dist/generate-Btrsn1qi.mjs +0 -177
- package/dist/init-B0q3wEQW.mjs +0 -22
- package/dist/migrate-CfQNwD0T.mjs +0 -121
- package/dist/package-manager-DcI5TdDE.mjs +0 -80
- package/dist/release-plan-BEzwApuK.mjs +0 -173
- /package/dist/{clack-CDRCHrC-.mjs → clack-C6bVkGxf.mjs} +0 -0
- /package/dist/{dep-graph-E-9-eQ2J.mjs → dep-graph-DiLeAhl9.mjs} +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
//#region src/core/commit-message.ts
|
|
3
|
+
/** Build the default version commit message */
|
|
4
|
+
function defaultCommitMessage(plan) {
|
|
5
|
+
return [
|
|
6
|
+
"Version packages",
|
|
7
|
+
"",
|
|
8
|
+
...plan.releases.map((r) => `${r.name}@${r.newVersion}`)
|
|
9
|
+
].join("\n");
|
|
10
|
+
}
|
|
11
|
+
/** Resolve the commit message from config, falling back to the default */
|
|
12
|
+
async function resolveCommitMessage(config, plan, rootDir) {
|
|
13
|
+
if (!config) return defaultCommitMessage(plan);
|
|
14
|
+
if (config.startsWith("./") || config.startsWith("../")) {
|
|
15
|
+
const mod = await import(resolve(rootDir, config));
|
|
16
|
+
const fn = mod.default ?? mod;
|
|
17
|
+
if (typeof fn !== "function") throw new Error(`versionCommitMessage module "${config}" must export a function`);
|
|
18
|
+
return fn(plan);
|
|
19
|
+
}
|
|
20
|
+
return config;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { resolveCommitMessage as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
|
|
2
|
-
import { a as readJson, n as exists } from "./fs-
|
|
2
|
+
import { a as readJson, n as exists } from "./fs-DYR2XuFE.mjs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
//#region src/types.ts
|
|
5
5
|
const BUMP_LEVELS = {
|
|
@@ -10,16 +10,6 @@ const BUMP_LEVELS = {
|
|
|
10
10
|
function bumpLevel(type) {
|
|
11
11
|
return BUMP_LEVELS[type];
|
|
12
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
13
|
function maxBump(a, b) {
|
|
24
14
|
if (!a) return b;
|
|
25
15
|
return bumpLevel(a) >= bumpLevel(b) ? a : b;
|
|
@@ -31,12 +21,9 @@ const DEFAULT_BUMP_RULES = {
|
|
|
31
21
|
},
|
|
32
22
|
peerDependencies: {
|
|
33
23
|
trigger: "major",
|
|
34
|
-
bumpAs: "
|
|
35
|
-
},
|
|
36
|
-
devDependencies: {
|
|
37
|
-
trigger: "none",
|
|
38
|
-
bumpAs: "patch"
|
|
24
|
+
bumpAs: "match"
|
|
39
25
|
},
|
|
26
|
+
devDependencies: false,
|
|
40
27
|
optionalDependencies: {
|
|
41
28
|
trigger: "minor",
|
|
42
29
|
bumpAs: "patch"
|
|
@@ -57,7 +44,8 @@ const DEFAULT_PUBLISH_CONFIG = {
|
|
|
57
44
|
const DEFAULT_CONFIG = {
|
|
58
45
|
baseBranch: "main",
|
|
59
46
|
access: "public",
|
|
60
|
-
|
|
47
|
+
versionCommitMessage: void 0,
|
|
48
|
+
changedFilePatterns: ["**"],
|
|
61
49
|
changelog: "default",
|
|
62
50
|
fixed: [],
|
|
63
51
|
linked: [],
|
|
@@ -69,6 +57,7 @@ const DEFAULT_CONFIG = {
|
|
|
69
57
|
version: false,
|
|
70
58
|
tag: false
|
|
71
59
|
},
|
|
60
|
+
allowCustomCommands: false,
|
|
72
61
|
packages: {},
|
|
73
62
|
publish: { ...DEFAULT_PUBLISH_CONFIG },
|
|
74
63
|
aggregateRelease: false,
|
|
@@ -80,9 +69,9 @@ const DEFAULT_CONFIG = {
|
|
|
80
69
|
title: "🐸 Versioned release",
|
|
81
70
|
branch: "bumpy/version-packages",
|
|
82
71
|
preamble: [
|
|
83
|
-
`<a href="https://
|
|
72
|
+
`<a href="https://bumpy.varlock.dev"><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
73
|
"",
|
|
85
|
-
`This PR was created and will be kept in sync by [bumpy](https://
|
|
74
|
+
`This PR was created and will be kept in sync by [bumpy](https://bumpy.varlock.dev) based on your .bumpy bump files. Merge it when you are ready to release the packages listed below:`,
|
|
86
75
|
"<br clear=\"left\" />"
|
|
87
76
|
].join("\n")
|
|
88
77
|
}
|
|
@@ -131,6 +120,20 @@ async function loadPackageConfig(pkgDir, rootConfig, pkgName) {
|
|
|
131
120
|
const pkg = await readJson(resolve(pkgDir, "package.json"));
|
|
132
121
|
if (pkg.bumpy && typeof pkg.bumpy === "object") pkgJsonConfig = pkg.bumpy;
|
|
133
122
|
} catch {}
|
|
123
|
+
const disallowedKeys = [
|
|
124
|
+
"buildCommand",
|
|
125
|
+
"publishCommand",
|
|
126
|
+
"checkPublished"
|
|
127
|
+
].filter((k) => pkgJsonConfig[k] != null);
|
|
128
|
+
if (disallowedKeys.length > 0 && !isCustomCommandAllowed(pkgName, rootConfig)) {
|
|
129
|
+
const fields = disallowedKeys.map((k) => `"${k}"`).join(", ");
|
|
130
|
+
throw new Error(`Package "${pkgName}" defines custom command(s) (${fields}) in its package.json "bumpy" config, but the root config does not allow this.
|
|
131
|
+
Custom commands execute shell commands during publishing and must be explicitly enabled.
|
|
132
|
+
|
|
133
|
+
To fix this, either:
|
|
134
|
+
1. Move the command(s) to .bumpy/_config.json under "packages" (always trusted)
|
|
135
|
+
2. Add "allowCustomCommands": true (or ["${pkgName}"]) to .bumpy/_config.json`);
|
|
136
|
+
}
|
|
134
137
|
return mergePackageConfig(rootPkgConfig, pkgJsonConfig);
|
|
135
138
|
}
|
|
136
139
|
/** Find a package config from the root config, supporting glob patterns */
|
|
@@ -175,10 +178,6 @@ function mergePackageConfig(...configs) {
|
|
|
175
178
|
...result.dependencyBumpRules,
|
|
176
179
|
...cfg.dependencyBumpRules
|
|
177
180
|
};
|
|
178
|
-
if (cfg.specificDependencyRules) result.specificDependencyRules = {
|
|
179
|
-
...result.specificDependencyRules,
|
|
180
|
-
...cfg.specificDependencyRules
|
|
181
|
-
};
|
|
182
181
|
if (cfg.cascadeTo) result.cascadeTo = {
|
|
183
182
|
...result.cascadeTo,
|
|
184
183
|
...cfg.cascadeTo
|
|
@@ -186,6 +185,13 @@ function mergePackageConfig(...configs) {
|
|
|
186
185
|
}
|
|
187
186
|
return result;
|
|
188
187
|
}
|
|
188
|
+
/** Check if a package is allowed to define custom commands via package.json */
|
|
189
|
+
function isCustomCommandAllowed(pkgName, config) {
|
|
190
|
+
const { allowCustomCommands } = config;
|
|
191
|
+
if (allowCustomCommands === true) return true;
|
|
192
|
+
if (Array.isArray(allowCustomCommands)) return allowCustomCommands.some((pattern) => matchGlob(pkgName, pattern));
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
189
195
|
function getBumpyDir(rootDir) {
|
|
190
196
|
return resolve(rootDir, BUMPY_DIR);
|
|
191
197
|
}
|
|
@@ -212,4 +218,4 @@ function isPackageManaged(pkgName, isPrivate, config, pkgBumpy) {
|
|
|
212
218
|
return true;
|
|
213
219
|
}
|
|
214
220
|
//#endregion
|
|
215
|
-
export { loadConfig as a, BUMP_LEVELS as c, DEFAULT_PUBLISH_CONFIG as d, DEP_TYPES as f,
|
|
221
|
+
export { loadConfig as a, BUMP_LEVELS as c, DEFAULT_PUBLISH_CONFIG as d, DEP_TYPES as f, 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 };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
//#region src/utils/fs.ts
|
|
4
|
+
var fs_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
ensureDir: () => ensureDir,
|
|
6
|
+
exists: () => exists,
|
|
7
|
+
listFiles: () => listFiles,
|
|
8
|
+
readJson: () => readJson,
|
|
9
|
+
readText: () => readText,
|
|
10
|
+
removeFile: () => removeFile,
|
|
11
|
+
updateJsonFields: () => updateJsonFields,
|
|
12
|
+
updateJsonNestedField: () => updateJsonNestedField,
|
|
13
|
+
writeJson: () => writeJson,
|
|
14
|
+
writeText: () => writeText
|
|
15
|
+
});
|
|
16
|
+
async function readJson(filePath) {
|
|
17
|
+
const content = await readFile(filePath, "utf-8");
|
|
18
|
+
return JSON.parse(content);
|
|
19
|
+
}
|
|
20
|
+
async function writeJson(filePath, data, indent = 2) {
|
|
21
|
+
await writeFile(filePath, JSON.stringify(data, null, indent) + "\n", "utf-8");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Update specific top-level string fields in a JSON file without reformatting.
|
|
25
|
+
* Reads the raw text, does targeted regex replacements, and writes it back.
|
|
26
|
+
*/
|
|
27
|
+
async function updateJsonFields(filePath, updates) {
|
|
28
|
+
let content = await readFile(filePath, "utf-8");
|
|
29
|
+
for (const [key, newValue] of Object.entries(updates)) {
|
|
30
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31
|
+
const pattern = new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`);
|
|
32
|
+
content = content.replace(pattern, `$1"${newValue}"`);
|
|
33
|
+
}
|
|
34
|
+
await writeFile(filePath, content, "utf-8");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Update a nested string field inside a top-level object in a JSON file without reformatting.
|
|
38
|
+
* e.g., updateJsonNestedField(path, 'dependencies', 'core', '^2.0.0')
|
|
39
|
+
*/
|
|
40
|
+
async function updateJsonNestedField(filePath, parentKey, childKey, newValue) {
|
|
41
|
+
let content = await readFile(filePath, "utf-8");
|
|
42
|
+
const parentEscaped = parentKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43
|
+
const childEscaped = childKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
44
|
+
const parentPattern = new RegExp(`("${parentEscaped}"\\s*:\\s*\\{)([^}]*)\\}`, "s");
|
|
45
|
+
content = content.replace(parentPattern, (match, prefix, body) => {
|
|
46
|
+
const childPattern = new RegExp(`("${childEscaped}"\\s*:\\s*)"[^"]*"`);
|
|
47
|
+
return `${prefix}${body.replace(childPattern, `$1"${newValue}"`)}}`;
|
|
48
|
+
});
|
|
49
|
+
await writeFile(filePath, content, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
async function readText(filePath) {
|
|
52
|
+
return readFile(filePath, "utf-8");
|
|
53
|
+
}
|
|
54
|
+
async function writeText(filePath, content) {
|
|
55
|
+
await writeFile(filePath, content, "utf-8");
|
|
56
|
+
}
|
|
57
|
+
async function exists(filePath) {
|
|
58
|
+
try {
|
|
59
|
+
await access(filePath);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function listFiles(dir, ext) {
|
|
66
|
+
try {
|
|
67
|
+
const entries = await readdir(dir);
|
|
68
|
+
if (ext) return entries.filter((e) => e.endsWith(ext));
|
|
69
|
+
return entries;
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function removeFile(filePath) {
|
|
75
|
+
await unlink(filePath);
|
|
76
|
+
}
|
|
77
|
+
async function ensureDir(dir) {
|
|
78
|
+
await mkdir(dir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { readJson as a, updateJsonFields as c, writeText as d, listFiles as i, updateJsonNestedField as l, exists as n, readText as o, fs_exports as r, removeFile as s, ensureDir as t, writeJson as u };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { t as ensureDir } from "./fs-DYR2XuFE.mjs";
|
|
3
|
+
import { a as loadConfig, r as getBumpyDir } from "./config-B-Qg3DZH.mjs";
|
|
4
|
+
import { t as discoverPackages } from "./workspace-DWXlwcH4.mjs";
|
|
5
|
+
import { s as tryRunArgs } from "./shell-CY7OD48z.mjs";
|
|
6
|
+
import { i as writeBumpFile } from "./bump-file-DVqR3k67.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";
|
|
10
|
+
//#region src/commands/generate.ts
|
|
11
|
+
const BUMP_MAP = {
|
|
12
|
+
feat: "minor",
|
|
13
|
+
fix: "patch",
|
|
14
|
+
perf: "patch",
|
|
15
|
+
refactor: "patch",
|
|
16
|
+
docs: "patch",
|
|
17
|
+
style: "patch",
|
|
18
|
+
test: "patch",
|
|
19
|
+
build: "patch",
|
|
20
|
+
ci: "patch",
|
|
21
|
+
chore: "patch"
|
|
22
|
+
};
|
|
23
|
+
async function generateCommand(rootDir, opts) {
|
|
24
|
+
const config = await loadConfig(rootDir);
|
|
25
|
+
const packages = await discoverPackages(rootDir, config);
|
|
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);
|
|
43
|
+
}
|
|
44
|
+
if (commits.length === 0) {
|
|
45
|
+
log.info("No commits found on this branch.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
log.dim(` Found ${commits.length} commit(s)`);
|
|
49
|
+
const scopeMap = buildScopeMap(packages, config);
|
|
50
|
+
const releaseMap = /* @__PURE__ */ new Map();
|
|
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);
|
|
65
|
+
continue;
|
|
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}`);
|
|
70
|
+
} else {
|
|
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}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (ccCount > 0) log.dim(` ${ccCount} conventional commit(s)`);
|
|
79
|
+
if (fileBasedCount > 0) log.dim(` ${fileBasedCount} commit(s) detected via changed files`);
|
|
80
|
+
if (releaseMap.size === 0) {
|
|
81
|
+
log.info("No package bumps detected from commits.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const releases = [];
|
|
85
|
+
const summaryLines = [];
|
|
86
|
+
for (const [name, info] of releaseMap) {
|
|
87
|
+
releases.push({
|
|
88
|
+
name,
|
|
89
|
+
type: info.type
|
|
90
|
+
});
|
|
91
|
+
for (const msg of info.messages) summaryLines.push(`- ${name}: ${msg}`);
|
|
92
|
+
}
|
|
93
|
+
if (opts.dryRun) {
|
|
94
|
+
log.bold("Would create bump file:");
|
|
95
|
+
for (const r of releases) console.log(` ${r.name}: ${colorize(r.type, r.type === "major" ? "red" : r.type === "minor" ? "yellow" : "green")}`);
|
|
96
|
+
console.log();
|
|
97
|
+
log.dim("Summary:");
|
|
98
|
+
for (const line of summaryLines) log.dim(` ${line}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await ensureDir(getBumpyDir(rootDir));
|
|
102
|
+
const filename = opts.name ? slugify(opts.name) : randomName();
|
|
103
|
+
await writeBumpFile(rootDir, filename, releases, summaryLines.join("\n"));
|
|
104
|
+
log.success(`🐸 Created bump file: .bumpy/${filename}.md`);
|
|
105
|
+
for (const r of releases) log.dim(` ${r.name}: ${r.type}`);
|
|
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
|
+
}
|
|
127
|
+
/** Parse raw git log output into individual commits */
|
|
128
|
+
function parseGitLog(raw) {
|
|
129
|
+
const commits = [];
|
|
130
|
+
const entries = raw.split("---END---").filter((e) => e.trim());
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
const lines = entry.trim().split("\n");
|
|
133
|
+
if (lines.length < 2) continue;
|
|
134
|
+
commits.push({
|
|
135
|
+
hash: lines[0].trim(),
|
|
136
|
+
subject: lines[1].trim(),
|
|
137
|
+
body: lines.slice(2).join("\n").trim()
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return commits;
|
|
141
|
+
}
|
|
142
|
+
/** Parse a commit subject into conventional commit format */
|
|
143
|
+
function parseConventionalCommit(commit) {
|
|
144
|
+
const match = commit.subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?\s*:\s*(.+)$/);
|
|
145
|
+
if (!match) return null;
|
|
146
|
+
const [, type, bang1, scope, bang2, description] = match;
|
|
147
|
+
const breaking = !!bang1 || !!bang2 || commit.body.includes("BREAKING CHANGE");
|
|
148
|
+
return {
|
|
149
|
+
hash: commit.hash,
|
|
150
|
+
type: type.toLowerCase(),
|
|
151
|
+
scope: scope || null,
|
|
152
|
+
breaking,
|
|
153
|
+
description: description.trim(),
|
|
154
|
+
body: commit.body
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Build a map of scope aliases → package names from config and package names */
|
|
158
|
+
function buildScopeMap(packages, _config) {
|
|
159
|
+
const map = /* @__PURE__ */ new Map();
|
|
160
|
+
for (const [name, pkg] of packages) {
|
|
161
|
+
const shortName = name.includes("/") ? name.split("/").pop() : name;
|
|
162
|
+
if (!map.has(shortName)) map.set(shortName, []);
|
|
163
|
+
map.get(shortName).push(name);
|
|
164
|
+
if (!map.has(name)) map.set(name, []);
|
|
165
|
+
map.get(name).push(name);
|
|
166
|
+
const dirName = pkg.relativeDir.split("/").pop();
|
|
167
|
+
if (dirName !== shortName) {
|
|
168
|
+
if (!map.has(dirName)) map.set(dirName, []);
|
|
169
|
+
map.get(dirName).push(name);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return map;
|
|
173
|
+
}
|
|
174
|
+
/** Resolve a scope string to package name(s) */
|
|
175
|
+
function resolveScope(scope, scopeMap, packages) {
|
|
176
|
+
const mapped = scopeMap.get(scope);
|
|
177
|
+
if (mapped) return [...new Set(mapped)];
|
|
178
|
+
for (const [key, value] of scopeMap) if (key.toLowerCase() === scope.toLowerCase()) return [...new Set(value)];
|
|
179
|
+
if (packages.has(scope)) return [scope];
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
function bumpPriority(type) {
|
|
183
|
+
return type === "major" ? 2 : type === "minor" ? 1 : 0;
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
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 };
|