@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.
Files changed (43) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/config-schema.json +327 -0
  3. package/dist/add-BmNL5VwL.mjs +323 -0
  4. package/dist/{ai-CQhUyHAG.mjs → ai-sMYUf3lP.mjs} +21 -4
  5. package/dist/{apply-release-plan-D6TSrcwX.mjs → apply-release-plan-0kH62jhu.mjs} +35 -26
  6. package/dist/bump-file-DVqR3k67.mjs +157 -0
  7. package/dist/{changelog-github-Du62krXi.mjs → changelog-github-DkACMj0j.mjs} +23 -21
  8. package/dist/check-BjWF6SJm.mjs +65 -0
  9. package/dist/{ci-D6LQbR38.mjs → ci-DY58ugIi.mjs} +138 -91
  10. package/dist/{ci-setup-C6FlOfW5.mjs → ci-setup-BQwktQEe.mjs} +3 -3
  11. package/dist/cli.mjs +36 -41
  12. package/dist/commit-message-BwsowSds.mjs +23 -0
  13. package/dist/{config-BkwIEaQg.mjs → config-B-Qg3DZH.mjs} +30 -24
  14. package/dist/fs-DYR2XuFE.mjs +81 -0
  15. package/dist/generate-DX46X-rW.mjs +186 -0
  16. package/dist/{git-CGHVXXKw.mjs → git-YDedMddc.mjs} +54 -2
  17. package/dist/index.d.mts +68 -39
  18. package/dist/index.mjs +9 -9
  19. package/dist/init-DkTPs_WQ.mjs +196 -0
  20. package/dist/{names-Ck8cun7B.mjs → names-C-TuOPbd.mjs} +1 -1
  21. package/dist/{js-yaml-DpZfOoD4.mjs → package-manager-Clsmr-9r.mjs} +79 -1
  22. package/dist/picomatch-DMmqYjgq.mjs +1870 -0
  23. package/dist/{publish-D_7RqEYL.mjs → publish-CGB4TIKD.mjs} +26 -25
  24. package/dist/{publish-pipeline-ChnqW8nR.mjs → publish-pipeline-CXuqce1N.mjs} +24 -19
  25. package/dist/release-plan-JNir7bSM.mjs +264 -0
  26. package/dist/{semver-BTzYh8vc.mjs → semver-BJzWIuRz.mjs} +13 -3
  27. package/dist/{shell-Dj7JRD_q.mjs → shell-CY7OD48z.mjs} +20 -2
  28. package/dist/{status--Q8yAxQ4.mjs → status-EGYqULJg.mjs} +26 -22
  29. package/dist/{version-cAUkfYPx.mjs → version-BcfidiVX.mjs} +23 -22
  30. package/dist/{workspace-CxEKakDm.mjs → workspace-DWXlwcH4.mjs} +3 -3
  31. package/package.json +16 -1
  32. package/skills/add-change/SKILL.md +18 -14
  33. package/dist/add-BjyVIUlr.mjs +0 -175
  34. package/dist/changeset-UCZdSRDv.mjs +0 -108
  35. package/dist/check-jIwike9F.mjs +0 -51
  36. package/dist/fs-0AtnPUUe.mjs +0 -51
  37. package/dist/generate-Btrsn1qi.mjs +0 -177
  38. package/dist/init-B0q3wEQW.mjs +0 -22
  39. package/dist/migrate-CfQNwD0T.mjs +0 -121
  40. package/dist/package-manager-DcI5TdDE.mjs +0 -80
  41. package/dist/release-plan-BEzwApuK.mjs +0 -173
  42. /package/dist/{clack-CDRCHrC-.mjs → clack-C6bVkGxf.mjs} +0 -0
  43. /package/dist/{dep-graph-E-9-eQ2J.mjs → dep-graph-DiLeAhl9.mjs} +0 -0
@@ -1,37 +1,42 @@
1
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";
2
+ import { a as loadConfig } from "./config-B-Qg3DZH.mjs";
3
+ import { n as detectWorkspaces } from "./package-manager-Clsmr-9r.mjs";
4
+ import { t as discoverPackages } from "./workspace-DWXlwcH4.mjs";
5
+ import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
6
+ import { n as runArgs, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
7
+ import { r as readBumpFiles } from "./bump-file-DVqR3k67.mjs";
8
+ import { t as assembleReleasePlan } from "./release-plan-JNir7bSM.mjs";
9
+ import { t as applyReleasePlan } from "./apply-release-plan-0kH62jhu.mjs";
10
+ import { t as resolveCommitMessage } from "./commit-message-BwsowSds.mjs";
10
11
  //#region src/commands/version.ts
11
- async function versionCommand(rootDir) {
12
+ async function versionCommand(rootDir, opts = {}) {
12
13
  const config = await loadConfig(rootDir);
13
14
  const packages = await discoverPackages(rootDir, config);
14
15
  const depGraph = new DependencyGraph(packages);
15
- const changesets = await readChangesets(rootDir);
16
- if (changesets.length === 0) {
17
- log.info("No pending changesets.");
16
+ const bumpFiles = await readBumpFiles(rootDir);
17
+ if (bumpFiles.length === 0) {
18
+ log.info("No pending bump files.");
18
19
  return;
19
20
  }
20
- const plan = assembleReleasePlan(changesets, packages, depGraph, config);
21
+ const plan = assembleReleasePlan(bumpFiles, packages, depGraph, config);
21
22
  if (plan.releases.length === 0) {
22
- log.warn("Changesets found but no packages would be released.");
23
+ log.warn("Bump files found but no packages would be released.");
23
24
  return;
24
25
  }
26
+ if (plan.warnings.length > 0) {
27
+ for (const w of plan.warnings) log.warn(w);
28
+ console.log();
29
+ }
25
30
  log.step("Applying version bumps:");
26
31
  for (const r of plan.releases) {
27
32
  const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
28
33
  console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${tag}`);
29
34
  }
30
35
  await applyReleasePlan(plan, packages, rootDir, config);
31
- log.success(`Updated ${plan.releases.length} package(s)`);
32
- log.dim(` Deleted ${changesets.length} changeset file(s)`);
36
+ log.success(`🐸 Updated ${plan.releases.length} package(s)`);
37
+ log.dim(` Deleted ${bumpFiles.length} bump file(s)`);
33
38
  await updateLockfile(rootDir);
34
- if (config.commit) try {
39
+ if (opts.commit) try {
35
40
  runArgs([
36
41
  "git",
37
42
  "add",
@@ -72,11 +77,7 @@ async function versionCommand(rootDir) {
72
77
  "-"
73
78
  ], {
74
79
  cwd: rootDir,
75
- input: [
76
- "Version packages",
77
- "",
78
- ...plan.releases.map((r) => `${r.name}@${r.newVersion}`)
79
- ].join("\n")
80
+ input: await resolveCommitMessage(config.versionCommitMessage, plan, rootDir)
80
81
  });
81
82
  log.success("Created git commit");
82
83
  } catch (e) {
@@ -1,6 +1,6 @@
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";
1
+ import { a as readJson, n as exists } from "./fs-DYR2XuFE.mjs";
2
+ import { i as isPackageManaged, o as loadPackageConfig } from "./config-B-Qg3DZH.mjs";
3
+ import { n as detectWorkspaces } from "./package-manager-Clsmr-9r.mjs";
4
4
  import { relative, resolve } from "node:path";
5
5
  import { readdir, stat } from "node:fs/promises";
6
6
  //#region src/core/workspace.ts
package/package.json CHANGED
@@ -1,7 +1,19 @@
1
1
  {
2
2
  "name": "@varlock/bumpy",
3
- "version": "0.0.2",
3
+ "version": "1.1.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",
@@ -12,6 +24,7 @@
12
24
  },
13
25
  "files": [
14
26
  "dist",
27
+ "config-schema.json",
15
28
  ".claude-plugin",
16
29
  "skills"
17
30
  ],
@@ -31,9 +44,11 @@
31
44
  "@clack/prompts": "^1.2.0",
32
45
  "@types/bun": "latest",
33
46
  "@types/js-yaml": "^4.0.9",
47
+ "@types/picomatch": "^4.0.3",
34
48
  "@types/semver": "^7.7.0",
35
49
  "js-yaml": "^4.1.0",
36
50
  "picocolors": "^1.1.1",
51
+ "picomatch": "^4.0.4",
37
52
  "semver": "^7.7.2",
38
53
  "tsdown": "^0.21.8"
39
54
  }
@@ -1,13 +1,13 @@
1
1
  ---
2
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.
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 changeset
8
+ # Create a bumpy bump file
9
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.
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 changesets
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,23 +43,26 @@ 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
- 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
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
 
54
- Write a concise summary (1-3 sentences) describing **what** changed and **why**. This becomes the CHANGELOG entry. Good summaries:
50
+ Write a concise summary for the CHANGELOG entry. Keep it short ideally a single sentence, at most two. Good summaries:
55
51
 
56
52
  - Start with a verb: "Added...", "Fixed...", "Refactored..."
57
53
  - Focus on user-facing impact, not implementation details
58
54
  - Are specific enough to be useful months later
55
+ - Avoid filler, jargon, or restating the bump level
56
+ - Don't list every file changed — describe the logical change
57
+
58
+ Bad: "Updated the authentication module to fix an issue where the token refresh mechanism was not properly handling expired refresh tokens, causing silent failures in the auth flow."
59
+ Good: "Fixed token refresh failing silently on expired refresh tokens."
60
+
61
+ ### 5. Create or update the bump file
59
62
 
60
- ### 5. Create the changeset
63
+ Check if there are already bump files on this branch (from step 1's `bumpy status`). If one exists that covers the same logical change, **update it in place** by editing the `.bumpy/<name>.md` file directly — adjust the package list, bump levels, and summary to reflect the current state of the branch. Don't create a new bump file for every incremental change on the same branch.
61
64
 
62
- Use the non-interactive CLI:
65
+ If no relevant bump file exists yet, create one with the non-interactive CLI:
63
66
 
64
67
  ```bash
65
68
  bumpy add \
@@ -83,7 +86,7 @@ bumpy add \
83
86
 
84
87
  ## Advanced: cascading bumps
85
88
 
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:
89
+ 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
90
 
88
91
  ```bash
89
92
  cat > .bumpy/<name>.md << 'EOF'
@@ -105,4 +108,5 @@ EOF
105
108
  - Only include packages that have **actual code changes** — bumpy handles dependency propagation automatically
106
109
  - If the user hasn't made any changes yet, ask what they're planning to change
107
110
  - 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
111
+ - One bump file per logical change — don't combine unrelated changes
112
+ - **Keep bump files up to date** — as work continues on a branch, the bump file should reflect the final state of all changes, not just the first commit. If packages were added/removed or the bump level changed (e.g., a patch fix grew into a minor feature), update the existing bump file accordingly
@@ -1,175 +0,0 @@
1
- import { n as log, o as __toESM, r as require_picocolors } from "./logger-C2dEe5Su.mjs";
2
- import { n as exists, t as ensureDir } from "./fs-0AtnPUUe.mjs";
3
- import { a as loadConfig, r as getBumpyDir, s as matchGlob } from "./config-BkwIEaQg.mjs";
4
- import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
5
- import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
6
- import { i as writeChangeset } from "./changeset-UCZdSRDv.mjs";
7
- import { c as ot, d as yt, i as _t, l as pt, o as gt, r as Ot, s as mt, t as unwrap, u as wt } from "./clack-CDRCHrC-.mjs";
8
- import { n as slugify, t as randomName } from "./names-Ck8cun7B.mjs";
9
- import { resolve } from "node:path";
10
- //#region src/commands/add.ts
11
- var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
12
- const BUMP_CHOICES = [
13
- {
14
- label: "patch",
15
- value: "patch"
16
- },
17
- {
18
- label: "minor",
19
- value: "minor"
20
- },
21
- {
22
- label: "major",
23
- value: "major"
24
- },
25
- {
26
- label: "patch (isolated)",
27
- value: "patch-isolated",
28
- hint: "no cascade"
29
- },
30
- {
31
- label: "minor (isolated)",
32
- value: "minor-isolated",
33
- hint: "no cascade"
34
- }
35
- ];
36
- const CASCADE_CHOICES = [
37
- {
38
- label: "patch",
39
- value: "patch"
40
- },
41
- {
42
- label: "minor",
43
- value: "minor"
44
- },
45
- {
46
- label: "major",
47
- value: "major"
48
- }
49
- ];
50
- async function addCommand(rootDir, opts) {
51
- const config = await loadConfig(rootDir);
52
- const bumpyDir = getBumpyDir(rootDir);
53
- await ensureDir(bumpyDir);
54
- if (opts.empty) {
55
- const filename = opts.name ? slugify(opts.name) : randomName();
56
- const filePath = resolve(bumpyDir, `${filename}.md`);
57
- const { writeText } = await import("./fs-0AtnPUUe.mjs").then((n) => n.r);
58
- await writeText(filePath, "---\n---\n");
59
- log.success(`Created empty changeset: .bumpy/${filename}.md`);
60
- return;
61
- }
62
- let releases;
63
- let summary;
64
- let filename;
65
- if (opts.packages) {
66
- releases = parsePackagesFlag(opts.packages);
67
- summary = opts.message || "";
68
- filename = opts.name ? slugify(opts.name) : randomName();
69
- } else {
70
- mt(import_picocolors.default.bgCyan(import_picocolors.default.black(" bumpy add ")));
71
- const pkgs = await discoverPackages(rootDir, config);
72
- const depGraph = new DependencyGraph(pkgs);
73
- if (pkgs.size === 0) {
74
- pt("No managed packages found in this workspace.");
75
- process.exit(1);
76
- }
77
- const selected = unwrap(await yt({
78
- message: "Which packages should be included in this changeset?",
79
- options: [...pkgs.values()].map((pkg) => ({
80
- label: pkg.name,
81
- value: pkg.name,
82
- hint: pkg.version
83
- })),
84
- required: true
85
- }));
86
- releases = [];
87
- for (const name of selected) {
88
- const bumpType = unwrap(await _t({
89
- message: `Bump type for ${import_picocolors.default.cyan(name)}`,
90
- options: BUMP_CHOICES
91
- }));
92
- const release = {
93
- name,
94
- type: bumpType
95
- };
96
- if (!bumpType.endsWith("-isolated")) {
97
- const dependents = depGraph.getDependents(name);
98
- const cascadeTargets = pkgs.get(name).bumpy?.cascadeTo;
99
- if (dependents.length > 0 || cascadeTargets) {
100
- if (unwrap(await ot({
101
- message: `${import_picocolors.default.cyan(name)} has ${import_picocolors.default.bold(String(dependents.length))} dependents. Specify explicit cascades?`,
102
- initialValue: false
103
- }))) {
104
- const allTargets = /* @__PURE__ */ new Set();
105
- for (const d of dependents) allTargets.add(d.name);
106
- if (cascadeTargets) {
107
- for (const pattern of Object.keys(cascadeTargets)) for (const [pName] of pkgs) if (matchGlob(pName, pattern)) allTargets.add(pName);
108
- }
109
- const cascadeSelected = unwrap(await yt({
110
- message: "Which packages should cascade?",
111
- options: [...allTargets].map((n) => ({
112
- label: n,
113
- value: n
114
- })),
115
- required: false
116
- }));
117
- if (cascadeSelected.length > 0) {
118
- const cascadeBump = unwrap(await _t({
119
- message: "Cascade bump type",
120
- options: CASCADE_CHOICES
121
- }));
122
- const cascade = {};
123
- for (const target of cascadeSelected) cascade[target] = cascadeBump;
124
- release.cascade = cascade;
125
- }
126
- }
127
- }
128
- }
129
- releases.push(release);
130
- }
131
- summary = unwrap(await Ot({
132
- message: "Summary (what changed and why)",
133
- placeholder: "A short description of the change",
134
- validate: (value) => {
135
- if (!value || !value.trim()) return "Summary is required";
136
- }
137
- }));
138
- const defaultName = randomName();
139
- filename = slugify(unwrap(await Ot({
140
- message: "Changeset name",
141
- placeholder: defaultName,
142
- defaultValue: defaultName,
143
- validate: (value) => {
144
- if (!value) return void 0;
145
- if (!slugify(value)) return "Name must contain at least one alphanumeric character";
146
- }
147
- }))) || defaultName;
148
- }
149
- if (await exists(resolve(bumpyDir, `${filename}.md`))) filename = `${filename}-${Date.now()}`;
150
- await writeChangeset(rootDir, filename, releases, summary);
151
- if (opts.packages) {
152
- log.success(`Created changeset: .bumpy/${filename}.md`);
153
- for (const r of releases) log.dim(` ${r.name}: ${r.type}${formatCascade(r)}`);
154
- } else {
155
- wt(releases.map((r) => `${import_picocolors.default.cyan(r.name)} ${import_picocolors.default.dim("→")} ${import_picocolors.default.bold(r.type)}${formatCascade(r)}`).join("\n"), "Changeset");
156
- gt(import_picocolors.default.green(`Created .bumpy/${filename}.md`));
157
- }
158
- }
159
- function formatCascade(r) {
160
- if (!("cascade" in r) || Object.keys(r.cascade).length === 0) return "";
161
- const parts = Object.entries(r.cascade).map(([k, v]) => `${k}:${v}`);
162
- return import_picocolors.default.dim(` (cascade: ${parts.join(", ")})`);
163
- }
164
- function parsePackagesFlag(input) {
165
- return input.split(",").map((entry) => {
166
- const [name, type] = entry.trim().split(":");
167
- if (!name || !type) throw new Error(`Invalid package format: "${entry}". Expected "name:bumpType"`);
168
- return {
169
- name: name.trim(),
170
- type: type.trim()
171
- };
172
- });
173
- }
174
- //#endregion
175
- export { addCommand };
@@ -1,108 +0,0 @@
1
- import { i as listFiles, l as writeText, o as readText, s as removeFile } from "./fs-0AtnPUUe.mjs";
2
- import { r as getBumpyDir } from "./config-BkwIEaQg.mjs";
3
- import { t as jsYaml } from "./js-yaml-DpZfOoD4.mjs";
4
- import { o as tryRunArgs } from "./shell-Dj7JRD_q.mjs";
5
- import { resolve } from "node:path";
6
- //#region src/core/changeset.ts
7
- /** Read all changeset files from .bumpy/ directory, sorted by git creation order */
8
- async function readChangesets(rootDir) {
9
- const dir = getBumpyDir(rootDir);
10
- const files = await listFiles(dir, ".md");
11
- const changesets = [];
12
- for (const file of files) {
13
- if (file === "README.md") continue;
14
- const cs = await parseChangesetFile(resolve(dir, file));
15
- if (cs) changesets.push(cs);
16
- }
17
- const creationOrder = getChangesetCreationOrder(rootDir);
18
- if (creationOrder.size > 0) changesets.sort((a, b) => {
19
- return (creationOrder.get(a.id) ?? Infinity) - (creationOrder.get(b.id) ?? Infinity) || a.id.localeCompare(b.id);
20
- });
21
- return changesets;
22
- }
23
- /**
24
- * Use `git log` to get the commit timestamp when each changeset file was first added.
25
- * Returns a map of changeset ID → unix timestamp (seconds).
26
- */
27
- function getChangesetCreationOrder(rootDir) {
28
- const order = /* @__PURE__ */ new Map();
29
- const result = tryRunArgs([
30
- "git",
31
- "log",
32
- "--diff-filter=A",
33
- "--format=%at",
34
- "--name-only",
35
- "--",
36
- ".bumpy/*.md"
37
- ], { cwd: rootDir });
38
- if (!result) return order;
39
- let currentTimestamp = 0;
40
- for (const line of result.split("\n")) {
41
- const trimmed = line.trim();
42
- if (!trimmed) continue;
43
- if (/^\d+$/.test(trimmed)) currentTimestamp = parseInt(trimmed, 10);
44
- else if (trimmed.startsWith(".bumpy/") && trimmed.endsWith(".md")) {
45
- const id = trimmed.replace(/^\.bumpy\//, "").replace(/\.md$/, "");
46
- order.set(id, currentTimestamp);
47
- }
48
- }
49
- return order;
50
- }
51
- /** Parse a single changeset markdown file */
52
- async function parseChangesetFile(filePath) {
53
- return parseChangeset(await readText(filePath), fileToId(filePath));
54
- }
55
- /** Parse changeset content (for testing) */
56
- function parseChangeset(content, id) {
57
- const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
58
- if (!match) return null;
59
- const frontmatter = match[1];
60
- const summary = match[2].trim();
61
- const parsed = jsYaml.load(frontmatter);
62
- if (!parsed || typeof parsed !== "object") return null;
63
- const releases = [];
64
- for (const [name, value] of Object.entries(parsed)) if (typeof value === "string") releases.push({
65
- name,
66
- type: value
67
- });
68
- else if (value && typeof value === "object") {
69
- const obj = value;
70
- const release = {
71
- name,
72
- type: obj.bump,
73
- cascade: obj.cascade || {}
74
- };
75
- releases.push(release);
76
- }
77
- if (releases.length === 0) return null;
78
- return {
79
- id,
80
- releases,
81
- summary
82
- };
83
- }
84
- /** Write a changeset file */
85
- async function writeChangeset(rootDir, filename, releases, summary) {
86
- const filePath = resolve(getBumpyDir(rootDir), `${filename}.md`);
87
- const frontmatter = {};
88
- for (const release of releases) if ("cascade" in release && Object.keys(release.cascade).length > 0) frontmatter[release.name] = {
89
- bump: release.type,
90
- cascade: release.cascade
91
- };
92
- else frontmatter[release.name] = release.type;
93
- await writeText(filePath, `---\n${jsYaml.dump(frontmatter, {
94
- lineWidth: -1,
95
- quotingType: "\""
96
- }).trim()}\n---\n\n${summary}\n`);
97
- return filePath;
98
- }
99
- /** Delete consumed changeset files */
100
- async function deleteChangesets(rootDir, ids) {
101
- const dir = getBumpyDir(rootDir);
102
- for (const id of ids) await removeFile(resolve(dir, `${id}.md`));
103
- }
104
- function fileToId(filePath) {
105
- return filePath.split("/").pop().replace(/\.md$/, "");
106
- }
107
- //#endregion
108
- export { writeChangeset as i, parseChangeset as n, readChangesets as r, deleteChangesets as t };
@@ -1,51 +0,0 @@
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 discoverWorkspace } from "./workspace-CxEKakDm.mjs";
4
- import { r as readChangesets } from "./changeset-UCZdSRDv.mjs";
5
- import { n as getChangedFiles } from "./git-CGHVXXKw.mjs";
6
- import { relative } from "node:path";
7
- //#region src/commands/check.ts
8
- /**
9
- * Local check: detect which packages have changed on this branch
10
- * and verify they have corresponding changesets.
11
- * Designed for pre-push hooks — no GitHub API needed.
12
- */
13
- async function checkCommand(rootDir) {
14
- const config = await loadConfig(rootDir);
15
- const { packages } = await discoverWorkspace(rootDir, config);
16
- const changesets = await readChangesets(rootDir);
17
- const coveredPackages = /* @__PURE__ */ new Set();
18
- for (const cs of changesets) for (const release of cs.releases) coveredPackages.add(release.name);
19
- const baseBranch = config.baseBranch;
20
- const changedFiles = getChangedFiles(rootDir, baseBranch);
21
- if (changedFiles.length === 0) {
22
- log.info("No changed files detected.");
23
- return;
24
- }
25
- const changedPackages = findChangedPackages(changedFiles, packages, rootDir);
26
- if (changedPackages.length === 0) {
27
- log.info("No managed packages have changed.");
28
- return;
29
- }
30
- const missing = changedPackages.filter((name) => !coveredPackages.has(name));
31
- if (missing.length === 0) {
32
- log.success(`All ${changedPackages.length} changed package(s) have changesets.`);
33
- return;
34
- }
35
- log.warn(`${missing.length} changed package(s) missing changesets:\n`);
36
- for (const name of missing) console.log(` ${colorize(name, "yellow")}`);
37
- console.log();
38
- log.dim("Run `bumpy add` to create a changeset, or `bumpy add --empty` if no release is needed.");
39
- process.exit(1);
40
- }
41
- /** Map changed files to the packages they belong to */
42
- function findChangedPackages(changedFiles, packages, rootDir) {
43
- const changed = /* @__PURE__ */ new Set();
44
- for (const file of changedFiles) for (const [name, pkg] of packages) {
45
- const pkgRelDir = relative(rootDir, pkg.dir);
46
- if (file.startsWith(pkgRelDir + "/")) changed.add(name);
47
- }
48
- return [...changed];
49
- }
50
- //#endregion
51
- export { checkCommand };
@@ -1,51 +0,0 @@
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
- writeJson: () => writeJson,
12
- writeText: () => writeText
13
- });
14
- async function readJson(filePath) {
15
- const content = await readFile(filePath, "utf-8");
16
- return JSON.parse(content);
17
- }
18
- async function writeJson(filePath, data, indent = 2) {
19
- await writeFile(filePath, JSON.stringify(data, null, indent) + "\n", "utf-8");
20
- }
21
- async function readText(filePath) {
22
- return readFile(filePath, "utf-8");
23
- }
24
- async function writeText(filePath, content) {
25
- await writeFile(filePath, content, "utf-8");
26
- }
27
- async function exists(filePath) {
28
- try {
29
- await access(filePath);
30
- return true;
31
- } catch {
32
- return false;
33
- }
34
- }
35
- async function listFiles(dir, ext) {
36
- try {
37
- const entries = await readdir(dir);
38
- if (ext) return entries.filter((e) => e.endsWith(ext));
39
- return entries;
40
- } catch {
41
- return [];
42
- }
43
- }
44
- async function removeFile(filePath) {
45
- await unlink(filePath);
46
- }
47
- async function ensureDir(dir) {
48
- await mkdir(dir, { recursive: true });
49
- }
50
- //#endregion
51
- export { readJson as a, writeJson as c, listFiles as i, writeText as l, exists as n, readText as o, fs_exports as r, removeFile as s, ensureDir as t };