@varlock/bumpy 0.0.1 → 1.0.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/dist/add-CgCjs4d-.mjs +313 -0
- package/dist/{ai-B8ZL2x8z.mjs → ai-sMYUf3lP.mjs} +22 -5
- package/dist/{apply-release-plan-DtU3rVyL.mjs → apply-release-plan-CczGWJTk.mjs} +34 -25
- package/dist/bump-file-CCLXMLA8.mjs +143 -0
- package/dist/changelog-github-Cd8uJHZI.mjs +195 -0
- package/dist/{check-CkRubvuk.mjs → check-BOoxpWqk.mjs} +11 -17
- package/dist/ci-Bhx--Tj6.mjs +629 -0
- package/dist/ci-setup-qz4Y3v7T.mjs +211 -0
- package/dist/clack-CDRCHrC-.mjs +1216 -0
- package/dist/cli.mjs +37 -31
- package/dist/{config-CJ2orhTL.mjs → config-XZWUL3ma.mjs} +28 -23
- package/dist/fs-DYR2XuFE.mjs +81 -0
- package/dist/{generate-oOFD9ABC.mjs → generate-gYKTpvex.mjs} +31 -12
- package/dist/git-CGHVXXKw.mjs +78 -0
- package/dist/index.d.mts +63 -37
- package/dist/index.mjs +9 -9
- package/dist/{init-Blw2GfC_.mjs → init-lA9E5pEc.mjs} +3 -3
- package/dist/logger-C2dEe5Su.mjs +135 -0
- package/dist/{migrate-DvOrXSw0.mjs → migrate-DmOYgmfD.mjs} +23 -16
- package/dist/{names-C-u50ofE.mjs → names-9VubBmL0.mjs} +3 -2
- package/dist/package-manager-VCe10bjc.mjs +80 -0
- package/dist/{publish-DZ3m7qkX.mjs → publish-Cun-zQ1b.mjs} +90 -35
- package/dist/{publish-pipeline-1M5GmbdP.mjs → publish-pipeline-BwBuKCIk.mjs} +56 -65
- package/dist/release-plan-Bi5QNSEo.mjs +264 -0
- package/dist/{semver-DWO6NFKN.mjs → semver-DfQyVLM_.mjs} +14 -4
- package/dist/shell-Dj7JRD_q.mjs +92 -0
- package/dist/{status-DRpq_Mha.mjs → status-CfE63ti5.mjs} +27 -23
- package/dist/version-19vVt9dv.mjs +124 -0
- package/dist/workspace-C5ULTyUN.mjs +107 -0
- package/package.json +16 -2
- package/skills/add-change/SKILL.md +8 -12
- package/dist/add-u5V9V3L7.mjs +0 -131
- package/dist/changelog-github-n-3zV1p9.mjs +0 -59
- package/dist/changeset-ClCYsChu.mjs +0 -75
- package/dist/ci-8KWWhjXl.mjs +0 -224
- package/dist/fs-DbNNEyzq.mjs +0 -51
- package/dist/logger-ZqggsyGZ.mjs +0 -176
- package/dist/prompt-BP8toAOI.mjs +0 -46
- package/dist/release-plan-CFnutSHD.mjs +0 -173
- package/dist/shell-DPlltpzb.mjs +0 -44
- package/dist/version-CJwf8XIA.mjs +0 -81
- package/dist/workspace-mVjawG8g.mjs +0 -183
- /package/dist/{dep-graph-DiLeAhl9.mjs → dep-graph-E-9-eQ2J.mjs} +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { a as readJson, n as exists } from "./fs-DYR2XuFE.mjs";
|
|
2
|
+
import { i as isPackageManaged, o as loadPackageConfig } from "./config-XZWUL3ma.mjs";
|
|
3
|
+
import { n as detectWorkspaces } from "./package-manager-VCe10bjc.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,7 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@varlock/bumpy",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Modern monorepo versioning and changelog tool",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bump",
|
|
7
|
+
"changelog",
|
|
8
|
+
"changesets",
|
|
9
|
+
"monorepo",
|
|
10
|
+
"version",
|
|
11
|
+
"version management"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://bumpy.varlock.dev",
|
|
14
|
+
"bugs": "https://github.com/dmno-dev/bumpy/issues",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "dmno-dev",
|
|
5
17
|
"repository": {
|
|
6
18
|
"type": "git",
|
|
7
19
|
"url": "https://github.com/dmno-dev/bumpy",
|
|
@@ -24,14 +36,16 @@
|
|
|
24
36
|
},
|
|
25
37
|
"scripts": {
|
|
26
38
|
"build": "tsdown",
|
|
39
|
+
"check": "bun run tsc --noEmit",
|
|
27
40
|
"test": "bun test"
|
|
28
41
|
},
|
|
29
42
|
"devDependencies": {
|
|
43
|
+
"@clack/prompts": "^1.2.0",
|
|
30
44
|
"@types/bun": "latest",
|
|
31
45
|
"@types/js-yaml": "^4.0.9",
|
|
32
46
|
"@types/semver": "^7.7.0",
|
|
33
|
-
"ansis": "^4.2.0",
|
|
34
47
|
"js-yaml": "^4.1.0",
|
|
48
|
+
"picocolors": "^1.1.1",
|
|
35
49
|
"semver": "^7.7.2",
|
|
36
50
|
"tsdown": "^0.21.8"
|
|
37
51
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: add-change
|
|
3
|
-
description: Create a bumpy
|
|
3
|
+
description: Create a bumpy bump file describing which packages changed and how, for version bumping and changelog generation. Use when the user wants to record a change, add a bump file, or prepare packages for release.
|
|
4
4
|
argument-hint: '[description of changes]'
|
|
5
5
|
allowed-tools: Read Grep Glob Bash Edit Write
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Create a bumpy
|
|
8
|
+
# Create a bumpy bump file
|
|
9
9
|
|
|
10
|
-
You are helping the user create a **bumpy
|
|
10
|
+
You are helping the user create a **bumpy bump file** — a markdown file in `.bumpy/` that describes which packages changed and how. Bumpy uses these to bump versions and generate changelogs.
|
|
11
11
|
|
|
12
12
|
## Steps
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ First, understand what changed. Run these in parallel:
|
|
|
17
17
|
|
|
18
18
|
- `git diff --stat` — see which files changed
|
|
19
19
|
- `git diff --cached --stat` — see staged changes
|
|
20
|
-
- `bumpy status --json` — see if there are already pending
|
|
20
|
+
- `bumpy status --json` — see if there are already pending bump files
|
|
21
21
|
|
|
22
22
|
If the user provided a description via `$ARGUMENTS`, use that as additional context for understanding the change.
|
|
23
23
|
|
|
@@ -43,11 +43,7 @@ For each affected package, choose the appropriate bump level:
|
|
|
43
43
|
| **minor** | New features: added exports, new options, new functionality |
|
|
44
44
|
| **patch** | Bug fixes, internal refactors, documentation, dependency updates |
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- Internal refactors with no API changes
|
|
49
|
-
- Dev tooling / test changes
|
|
50
|
-
- Documentation-only changes
|
|
46
|
+
Use `none` in a bump file to suppress a bump on a package that would otherwise be included via propagation. If skipping would leave a broken range, bumpy throws an error.
|
|
51
47
|
|
|
52
48
|
### 4. Write a clear summary
|
|
53
49
|
|
|
@@ -57,7 +53,7 @@ Write a concise summary (1-3 sentences) describing **what** changed and **why**.
|
|
|
57
53
|
- Focus on user-facing impact, not implementation details
|
|
58
54
|
- Are specific enough to be useful months later
|
|
59
55
|
|
|
60
|
-
### 5. Create the
|
|
56
|
+
### 5. Create the bump file
|
|
61
57
|
|
|
62
58
|
Use the non-interactive CLI:
|
|
63
59
|
|
|
@@ -83,7 +79,7 @@ bumpy add \
|
|
|
83
79
|
|
|
84
80
|
## Advanced: cascading bumps
|
|
85
81
|
|
|
86
|
-
If a change in a core package should explicitly cascade to dependents with specific bump levels, write the
|
|
82
|
+
If a change in a core package should explicitly cascade to dependents with specific bump levels, write the bump file directly instead of using the CLI:
|
|
87
83
|
|
|
88
84
|
```bash
|
|
89
85
|
cat > .bumpy/<name>.md << 'EOF'
|
|
@@ -105,4 +101,4 @@ EOF
|
|
|
105
101
|
- Only include packages that have **actual code changes** — bumpy handles dependency propagation automatically
|
|
106
102
|
- If the user hasn't made any changes yet, ask what they're planning to change
|
|
107
103
|
- 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
|
|
104
|
+
- One bump file per logical change — don't combine unrelated changes
|
package/dist/add-u5V9V3L7.mjs
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { n as log, t as colorize } from "./logger-ZqggsyGZ.mjs";
|
|
2
|
-
import { n as exists, t as ensureDir } from "./fs-DbNNEyzq.mjs";
|
|
3
|
-
import { a as loadConfig, r as getBumpyDir, s as matchGlob } from "./config-CJ2orhTL.mjs";
|
|
4
|
-
import { t as discoverPackages } from "./workspace-mVjawG8g.mjs";
|
|
5
|
-
import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
|
|
6
|
-
import { i as writeChangeset } from "./changeset-ClCYsChu.mjs";
|
|
7
|
-
import { i as select, n as confirm, r as multiSelect, t as ask } from "./prompt-BP8toAOI.mjs";
|
|
8
|
-
import { n as slugify, t as randomName } from "./names-C-u50ofE.mjs";
|
|
9
|
-
import { resolve } from "node:path";
|
|
10
|
-
//#region src/commands/add.ts
|
|
11
|
-
const BUMP_CHOICES = [
|
|
12
|
-
{
|
|
13
|
-
label: "patch",
|
|
14
|
-
value: "patch"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
label: "minor",
|
|
18
|
-
value: "minor"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
label: "major",
|
|
22
|
-
value: "major"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
label: "patch (isolated - no cascade)",
|
|
26
|
-
value: "patch-isolated"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
label: "minor (isolated - no cascade)",
|
|
30
|
-
value: "minor-isolated"
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
|
-
async function addCommand(rootDir, opts) {
|
|
34
|
-
const config = await loadConfig(rootDir);
|
|
35
|
-
const bumpyDir = getBumpyDir(rootDir);
|
|
36
|
-
await ensureDir(bumpyDir);
|
|
37
|
-
if (opts.empty) {
|
|
38
|
-
const filename = opts.name ? slugify(opts.name) : randomName();
|
|
39
|
-
const filePath = resolve(bumpyDir, `${filename}.md`);
|
|
40
|
-
const { writeText } = await import("./fs-DbNNEyzq.mjs").then((n) => n.r);
|
|
41
|
-
await writeText(filePath, "---\n---\n");
|
|
42
|
-
log.success(`Created empty changeset: .bumpy/${filename}.md`);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
let releases;
|
|
46
|
-
let summary;
|
|
47
|
-
if (opts.packages) {
|
|
48
|
-
releases = parsePackagesFlag(opts.packages);
|
|
49
|
-
summary = opts.message || "";
|
|
50
|
-
} else {
|
|
51
|
-
const pkgs = await discoverPackages(rootDir, config);
|
|
52
|
-
const depGraph = new DependencyGraph(pkgs);
|
|
53
|
-
const selected = await multiSelect("Which packages should be included in this changeset?", [...pkgs.values()].map((p) => ({
|
|
54
|
-
label: `${p.name} (${p.version})`,
|
|
55
|
-
value: p.name
|
|
56
|
-
})));
|
|
57
|
-
if (selected.length === 0) {
|
|
58
|
-
log.warn("No packages selected. Aborting.");
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
releases = [];
|
|
62
|
-
for (const name of selected) {
|
|
63
|
-
const bumpType = await select(`Bump type for ${colorize(name, "cyan")}:`, BUMP_CHOICES);
|
|
64
|
-
const release = {
|
|
65
|
-
name,
|
|
66
|
-
type: bumpType
|
|
67
|
-
};
|
|
68
|
-
if (!bumpType.endsWith("-isolated")) {
|
|
69
|
-
const dependents = depGraph.getDependents(name);
|
|
70
|
-
const cascadeTargets = pkgs.get(name).bumpy?.cascadeTo;
|
|
71
|
-
if (dependents.length > 0 || cascadeTargets) {
|
|
72
|
-
if (await confirm(`${name} has ${dependents.length} dependents. Specify explicit cascades?`, false)) {
|
|
73
|
-
const allTargets = /* @__PURE__ */ new Set();
|
|
74
|
-
for (const d of dependents) allTargets.add(d.name);
|
|
75
|
-
if (cascadeTargets) {
|
|
76
|
-
for (const pattern of Object.keys(cascadeTargets)) for (const [pName] of pkgs) if (matchGlob(pName, pattern)) allTargets.add(pName);
|
|
77
|
-
}
|
|
78
|
-
const cascadeSelected = await multiSelect("Which packages should cascade?", [...allTargets].map((n) => ({
|
|
79
|
-
label: n,
|
|
80
|
-
value: n
|
|
81
|
-
})));
|
|
82
|
-
if (cascadeSelected.length > 0) {
|
|
83
|
-
const cascadeBump = await select("Cascade bump type:", [
|
|
84
|
-
{
|
|
85
|
-
label: "patch",
|
|
86
|
-
value: "patch"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
label: "minor",
|
|
90
|
-
value: "minor"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
label: "major",
|
|
94
|
-
value: "major"
|
|
95
|
-
}
|
|
96
|
-
]);
|
|
97
|
-
const cascade = {};
|
|
98
|
-
for (const target of cascadeSelected) cascade[target] = cascadeBump;
|
|
99
|
-
release.cascade = cascade;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
releases.push(release);
|
|
105
|
-
}
|
|
106
|
-
summary = await ask("Summary (what changed and why)");
|
|
107
|
-
}
|
|
108
|
-
let filename;
|
|
109
|
-
if (opts.name) filename = slugify(opts.name);
|
|
110
|
-
else if (opts.packages) filename = randomName();
|
|
111
|
-
else filename = slugify(await ask("Changeset name", randomName())) || randomName();
|
|
112
|
-
if (await exists(resolve(bumpyDir, `${filename}.md`))) filename = `${filename}-${Date.now()}`;
|
|
113
|
-
await writeChangeset(rootDir, filename, releases, summary);
|
|
114
|
-
log.success(`Created changeset: .bumpy/${filename}.md`);
|
|
115
|
-
for (const r of releases) {
|
|
116
|
-
const cascade = "cascade" in r && Object.keys(r.cascade).length > 0 ? ` (cascade: ${Object.entries(r.cascade).map(([k, v]) => `${k}:${v}`).join(", ")})` : "";
|
|
117
|
-
log.dim(` ${r.name}: ${r.type}${cascade}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function parsePackagesFlag(input) {
|
|
121
|
-
return input.split(",").map((entry) => {
|
|
122
|
-
const [name, type] = entry.trim().split(":");
|
|
123
|
-
if (!name || !type) throw new Error(`Invalid package format: "${entry}". Expected "name:bumpType"`);
|
|
124
|
-
return {
|
|
125
|
-
name: name.trim(),
|
|
126
|
-
type: type.trim()
|
|
127
|
-
};
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
//#endregion
|
|
131
|
-
export { addCommand };
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { i as tryRun } from "./shell-DPlltpzb.mjs";
|
|
2
|
-
//#region src/core/changelog-github.ts
|
|
3
|
-
/**
|
|
4
|
-
* GitHub-enhanced changelog formatter.
|
|
5
|
-
* Adds PR links and author attribution when git/gh info is available.
|
|
6
|
-
*
|
|
7
|
-
* Usage in config:
|
|
8
|
-
* "changelog": "github"
|
|
9
|
-
* "changelog": ["github", { "repo": "dmno-dev/bumpy" }]
|
|
10
|
-
*/
|
|
11
|
-
function createGithubFormatter(options = {}) {
|
|
12
|
-
return async (ctx) => {
|
|
13
|
-
const { release, changesets, date } = ctx;
|
|
14
|
-
const lines = [];
|
|
15
|
-
lines.push(`## ${release.newVersion}`);
|
|
16
|
-
lines.push("");
|
|
17
|
-
lines.push(`_${date}_`);
|
|
18
|
-
lines.push("");
|
|
19
|
-
const relevantChangesets = changesets.filter((cs) => release.changesets.includes(cs.id));
|
|
20
|
-
if (relevantChangesets.length > 0) for (const cs of relevantChangesets) {
|
|
21
|
-
if (!cs.summary) continue;
|
|
22
|
-
const firstLine = cs.summary.split("\n")[0];
|
|
23
|
-
const prInfo = await findPrForChangeset(cs.id, options.repo);
|
|
24
|
-
if (prInfo) lines.push(`- ${firstLine} ([#${prInfo.number}](${prInfo.url})) by @${prInfo.author}`);
|
|
25
|
-
else lines.push(`- ${firstLine}`);
|
|
26
|
-
const summaryLines = cs.summary.split("\n");
|
|
27
|
-
for (let i = 1; i < summaryLines.length; i++) if (summaryLines[i].trim()) lines.push(` ${summaryLines[i]}`);
|
|
28
|
-
}
|
|
29
|
-
if (release.isDependencyBump && relevantChangesets.length === 0) lines.push("- Updated dependencies");
|
|
30
|
-
if (release.isCascadeBump && !release.isDependencyBump && relevantChangesets.length === 0) lines.push("- Version bump via cascade rule");
|
|
31
|
-
lines.push("");
|
|
32
|
-
return lines.join("\n");
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Find the PR that introduced a changeset file by checking git log
|
|
37
|
-
* for the commit that added the file, then looking up the PR.
|
|
38
|
-
*/
|
|
39
|
-
async function findPrForChangeset(changesetId, repo) {
|
|
40
|
-
try {
|
|
41
|
-
const commitHash = tryRun(`git log --diff-filter=A --format="%H" -- ".bumpy/${changesetId}.md" ".changeset/${changesetId}.md"`);
|
|
42
|
-
if (!commitHash) return null;
|
|
43
|
-
const hash = commitHash.split("\n")[0].trim();
|
|
44
|
-
if (!hash) return null;
|
|
45
|
-
const prJson = tryRun(`gh pr list --search "${hash}" --state merged --json number,url,author --jq ".[0]" ${repo ? `--repo ${repo}` : ""}`);
|
|
46
|
-
if (!prJson) return null;
|
|
47
|
-
const pr = JSON.parse(prJson);
|
|
48
|
-
if (!pr.number) return null;
|
|
49
|
-
return {
|
|
50
|
-
number: pr.number,
|
|
51
|
-
url: pr.url,
|
|
52
|
-
author: pr.author?.login || "unknown"
|
|
53
|
-
};
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
export { createGithubFormatter };
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { i as listFiles, l as writeText, o as readText, s as removeFile } from "./fs-DbNNEyzq.mjs";
|
|
2
|
-
import { r as getBumpyDir } from "./config-CJ2orhTL.mjs";
|
|
3
|
-
import { t as jsYaml } from "./js-yaml-DpZfOoD4.mjs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
//#region src/core/changeset.ts
|
|
6
|
-
/** Read all changeset files from .bumpy/ directory */
|
|
7
|
-
async function readChangesets(rootDir) {
|
|
8
|
-
const dir = getBumpyDir(rootDir);
|
|
9
|
-
const files = await listFiles(dir, ".md");
|
|
10
|
-
const changesets = [];
|
|
11
|
-
for (const file of files) {
|
|
12
|
-
if (file === "README.md") continue;
|
|
13
|
-
const cs = await parseChangesetFile(resolve(dir, file));
|
|
14
|
-
if (cs) changesets.push(cs);
|
|
15
|
-
}
|
|
16
|
-
return changesets;
|
|
17
|
-
}
|
|
18
|
-
/** Parse a single changeset markdown file */
|
|
19
|
-
async function parseChangesetFile(filePath) {
|
|
20
|
-
return parseChangeset(await readText(filePath), fileToId(filePath));
|
|
21
|
-
}
|
|
22
|
-
/** Parse changeset content (for testing) */
|
|
23
|
-
function parseChangeset(content, id) {
|
|
24
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
25
|
-
if (!match) return null;
|
|
26
|
-
const frontmatter = match[1];
|
|
27
|
-
const summary = match[2].trim();
|
|
28
|
-
const parsed = jsYaml.load(frontmatter);
|
|
29
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
30
|
-
const releases = [];
|
|
31
|
-
for (const [name, value] of Object.entries(parsed)) if (typeof value === "string") releases.push({
|
|
32
|
-
name,
|
|
33
|
-
type: value
|
|
34
|
-
});
|
|
35
|
-
else if (value && typeof value === "object") {
|
|
36
|
-
const obj = value;
|
|
37
|
-
const release = {
|
|
38
|
-
name,
|
|
39
|
-
type: obj.bump,
|
|
40
|
-
cascade: obj.cascade || {}
|
|
41
|
-
};
|
|
42
|
-
releases.push(release);
|
|
43
|
-
}
|
|
44
|
-
if (releases.length === 0) return null;
|
|
45
|
-
return {
|
|
46
|
-
id,
|
|
47
|
-
releases,
|
|
48
|
-
summary
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/** Write a changeset file */
|
|
52
|
-
async function writeChangeset(rootDir, filename, releases, summary) {
|
|
53
|
-
const filePath = resolve(getBumpyDir(rootDir), `${filename}.md`);
|
|
54
|
-
const frontmatter = {};
|
|
55
|
-
for (const release of releases) if ("cascade" in release && Object.keys(release.cascade).length > 0) frontmatter[release.name] = {
|
|
56
|
-
bump: release.type,
|
|
57
|
-
cascade: release.cascade
|
|
58
|
-
};
|
|
59
|
-
else frontmatter[release.name] = release.type;
|
|
60
|
-
await writeText(filePath, `---\n${jsYaml.dump(frontmatter, {
|
|
61
|
-
lineWidth: -1,
|
|
62
|
-
quotingType: "\""
|
|
63
|
-
}).trim()}\n---\n\n${summary}\n`);
|
|
64
|
-
return filePath;
|
|
65
|
-
}
|
|
66
|
-
/** Delete consumed changeset files */
|
|
67
|
-
async function deleteChangesets(rootDir, ids) {
|
|
68
|
-
const dir = getBumpyDir(rootDir);
|
|
69
|
-
for (const id of ids) await removeFile(resolve(dir, `${id}.md`));
|
|
70
|
-
}
|
|
71
|
-
function fileToId(filePath) {
|
|
72
|
-
return filePath.split("/").pop().replace(/\.md$/, "");
|
|
73
|
-
}
|
|
74
|
-
//#endregion
|
|
75
|
-
export { writeChangeset as i, parseChangeset as n, readChangesets as r, deleteChangesets as t };
|