bunset 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/package.json +25 -0
- package/src/changelog.test.ts +152 -0
- package/src/changelog.ts +96 -0
- package/src/cli.ts +199 -0
- package/src/commits.test.ts +265 -0
- package/src/commits.ts +87 -0
- package/src/config.ts +64 -0
- package/src/deps.ts +47 -0
- package/src/git.ts +65 -0
- package/src/index.ts +197 -0
- package/src/types.ts +50 -0
- package/src/version.test.ts +40 -0
- package/src/version.ts +32 -0
- package/src/workspace.ts +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bunset contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# bunset
|
|
2
|
+
|
|
3
|
+
A zero-dependency CLI tool that automates version bumping and changelog generation for Bun workspace monorepos and single packages.
|
|
4
|
+
|
|
5
|
+
It reads git commit messages since the last tag, categorizes them by type prefix (`[feat]`, `[fix]`, `[test]`), bumps semantic versions, updates `CHANGELOG.md` per package, and optionally commits and tags the result.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun src/index.ts [options]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or link it globally:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun link
|
|
23
|
+
bunset [options]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Options
|
|
27
|
+
|
|
28
|
+
| Flag | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `--patch` | Patch version bump (default) |
|
|
31
|
+
| `--minor` | Minor version bump |
|
|
32
|
+
| `--major` | Major version bump |
|
|
33
|
+
| `--all` | Update all workspace packages |
|
|
34
|
+
| `--changed` | Update only changed packages (default for workspaces) |
|
|
35
|
+
| `--no-commit` | Do not commit changes to git (commits by default) |
|
|
36
|
+
| `--tag` | Tag the commit with new version (default) |
|
|
37
|
+
| `--per-package-tags` | Use `pkg@1.2.3` tags instead of prefixed tags |
|
|
38
|
+
| `--tag-prefix` | Tag prefix (default: `v`, e.g. `v1.2.3`) |
|
|
39
|
+
| `--sections` | Comma-separated changelog sections (default: `feat,fix,perf`) |
|
|
40
|
+
| `--dry-run` | Preview changes without writing files, committing, or tagging |
|
|
41
|
+
| `--no-filter-by-package` | Include all commits in every package changelog (monorepo) |
|
|
42
|
+
|
|
43
|
+
When bump type or scope flags are omitted, interactive prompts will ask.
|
|
44
|
+
|
|
45
|
+
### Monorepo Per-Package Changelogs
|
|
46
|
+
|
|
47
|
+
In a monorepo, each package's changelog only includes commits that touched files within that package (enabled by default). A commit that modifies `packages/a/src/index.ts` will only appear in `packages/a/CHANGELOG.md`.
|
|
48
|
+
|
|
49
|
+
When `--per-package-tags` is set, packages with no matching commits are skipped entirely (no version bump, changelog, or tag). When `--per-package-tags` is not set, all packages are still bumped and get a changelog entry (which may be empty).
|
|
50
|
+
|
|
51
|
+
Use `--no-filter-by-package` to disable this and include all commits in every package's changelog.
|
|
52
|
+
|
|
53
|
+
### Commit Message Format
|
|
54
|
+
|
|
55
|
+
Commits are automatically matched against these patterns (case-insensitive):
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
[feat] Add user authentication
|
|
59
|
+
[fix]: Resolve crash on startup
|
|
60
|
+
test: Add unit tests for parser
|
|
61
|
+
feat(auth): Add login page
|
|
62
|
+
[fix(ui)]: Fix button alignment
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
All these styles work:
|
|
66
|
+
- `[type] description` — bracketed
|
|
67
|
+
- `[type]: description` — bracketed with colon
|
|
68
|
+
- `type: description` — conventional commits style
|
|
69
|
+
- `type(scope): description` — with optional scope
|
|
70
|
+
- `[type(scope)] description` — bracketed with optional scope
|
|
71
|
+
|
|
72
|
+
An optional scope groups commits under a `#### scope` sub-heading within their type section in the changelog.
|
|
73
|
+
|
|
74
|
+
Recognized type keywords:
|
|
75
|
+
- `feat`, `feature` — listed under **Features**
|
|
76
|
+
- `fix`, `bug`, `bugfix` — listed under **Bug Fixes**
|
|
77
|
+
- `refactor` — listed under **Refactors**
|
|
78
|
+
- `perf` — listed under **Performance**
|
|
79
|
+
- `style` — listed under **Style**
|
|
80
|
+
- `test` — listed under **Tests**
|
|
81
|
+
- `docs` — listed under **Documentation**
|
|
82
|
+
- `build` — listed under **Build**
|
|
83
|
+
- `ops` — listed under **Ops**
|
|
84
|
+
- `chore` — listed under **Chores**
|
|
85
|
+
|
|
86
|
+
Only sections listed in `--sections` (or the `sections` config option) appear in the changelog. The default is `feat,fix,perf`.
|
|
87
|
+
|
|
88
|
+
### Config File
|
|
89
|
+
|
|
90
|
+
Place a `.bunset.toml` in your project root to set persistent defaults so you don't have to pass the same flags every time:
|
|
91
|
+
|
|
92
|
+
```toml
|
|
93
|
+
bump = "patch" # "patch" | "minor" | "major"
|
|
94
|
+
scope = "changed" # "all" | "changed"
|
|
95
|
+
commit = true
|
|
96
|
+
tag = true
|
|
97
|
+
per-package-tags = false
|
|
98
|
+
sections = ["feat", "fix", "perf"] # changelog sections and order
|
|
99
|
+
dry-run = false # preview without writing
|
|
100
|
+
filter-by-package = true # per-package commit filtering (monorepo)
|
|
101
|
+
tag-prefix = "v" # prefix for version tags
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
All fields are optional. CLI flags always take priority over config values. If `bump` or `scope` is set in config, the interactive prompt for that option is skipped.
|
|
105
|
+
|
|
106
|
+
## Testing
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bun test
|
|
110
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bunset",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"module": "src/index.ts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bunset": "src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/bun": "latest"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"typescript": "^5"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"bun": "^1.3"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"packageManager": "bun@1.3"
|
|
25
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { buildChangelogEntry, writeChangelog } from "./changelog.ts";
|
|
3
|
+
import { COMMIT_TYPES } from "./commits.ts";
|
|
4
|
+
import type { GroupedCommits } from "./types.ts";
|
|
5
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
function emptyGroups(): GroupedCommits {
|
|
10
|
+
return {
|
|
11
|
+
feature: [],
|
|
12
|
+
bugfix: [],
|
|
13
|
+
refactor: [],
|
|
14
|
+
perf: [],
|
|
15
|
+
style: [],
|
|
16
|
+
test: [],
|
|
17
|
+
docs: [],
|
|
18
|
+
build: [],
|
|
19
|
+
ops: [],
|
|
20
|
+
chore: [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("buildChangelogEntry", () => {
|
|
25
|
+
test("builds entry with specified sections", () => {
|
|
26
|
+
const groups: GroupedCommits = {
|
|
27
|
+
...emptyGroups(),
|
|
28
|
+
feature: [
|
|
29
|
+
{ hash: "a", message: "", type: "feature", commitScope: null, files: [], description: "Add login" },
|
|
30
|
+
],
|
|
31
|
+
bugfix: [
|
|
32
|
+
{ hash: "b", message: "", type: "bugfix", commitScope: null, files: [], description: "Fix crash" },
|
|
33
|
+
],
|
|
34
|
+
test: [
|
|
35
|
+
{ hash: "c", message: "", type: "test", commitScope: null, files: [], description: "Add tests" },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
const entry = buildChangelogEntry("1.2.0", groups, [], COMMIT_TYPES);
|
|
39
|
+
expect(entry).toContain("## 1.2.0");
|
|
40
|
+
expect(entry).toContain("### Features");
|
|
41
|
+
expect(entry).toContain("- Add login");
|
|
42
|
+
expect(entry).toContain("### Bug Fixes");
|
|
43
|
+
expect(entry).toContain("- Fix crash");
|
|
44
|
+
expect(entry).toContain("### Tests");
|
|
45
|
+
expect(entry).toContain("- Add tests");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("only includes configured sections", () => {
|
|
49
|
+
const groups: GroupedCommits = {
|
|
50
|
+
...emptyGroups(),
|
|
51
|
+
feature: [
|
|
52
|
+
{ hash: "a", message: "", type: "feature", commitScope: null, files: [], description: "New thing" },
|
|
53
|
+
],
|
|
54
|
+
test: [
|
|
55
|
+
{ hash: "b", message: "", type: "test", commitScope: null, files: [], description: "Add tests" },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
const entry = buildChangelogEntry("1.0.0", groups);
|
|
59
|
+
expect(entry).toContain("### Features");
|
|
60
|
+
expect(entry).not.toContain("### Tests");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("omits empty sections", () => {
|
|
64
|
+
const groups: GroupedCommits = {
|
|
65
|
+
...emptyGroups(),
|
|
66
|
+
feature: [
|
|
67
|
+
{ hash: "a", message: "", type: "feature", commitScope: null, files: [], description: "New thing" },
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
const entry = buildChangelogEntry("1.0.0", groups);
|
|
71
|
+
expect(entry).toContain("### Features");
|
|
72
|
+
expect(entry).not.toContain("### Bug Fixes");
|
|
73
|
+
expect(entry).not.toContain("### Performance");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("groups scoped commits under sub-headings", () => {
|
|
77
|
+
const groups: GroupedCommits = {
|
|
78
|
+
...emptyGroups(),
|
|
79
|
+
feature: [
|
|
80
|
+
{ hash: "a", message: "", type: "feature", commitScope: null, files: [], description: "Global feature" },
|
|
81
|
+
{ hash: "b", message: "", type: "feature", commitScope: "auth", files: [], description: "Add login" },
|
|
82
|
+
{ hash: "c", message: "", type: "feature", commitScope: "auth", files: [], description: "Add logout" },
|
|
83
|
+
{ hash: "d", message: "", type: "feature", commitScope: "ui", files: [], description: "New dashboard" },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
const entry = buildChangelogEntry("1.0.0", groups);
|
|
87
|
+
expect(entry).toContain("### Features");
|
|
88
|
+
expect(entry).toContain("- Global feature");
|
|
89
|
+
expect(entry).toContain("#### auth");
|
|
90
|
+
expect(entry).toContain("- Add login");
|
|
91
|
+
expect(entry).toContain("- Add logout");
|
|
92
|
+
expect(entry).toContain("#### ui");
|
|
93
|
+
expect(entry).toContain("- New dashboard");
|
|
94
|
+
|
|
95
|
+
// Unscoped comes before scoped
|
|
96
|
+
const globalIdx = entry.indexOf("- Global feature");
|
|
97
|
+
const authIdx = entry.indexOf("#### auth");
|
|
98
|
+
expect(globalIdx).toBeLessThan(authIdx);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("renders only scoped commits when no unscoped", () => {
|
|
102
|
+
const groups: GroupedCommits = {
|
|
103
|
+
...emptyGroups(),
|
|
104
|
+
bugfix: [
|
|
105
|
+
{ hash: "a", message: "", type: "bugfix", commitScope: "parser", files: [], description: "Fix edge case" },
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
const entry = buildChangelogEntry("1.0.0", groups);
|
|
109
|
+
expect(entry).toContain("### Bug Fixes");
|
|
110
|
+
expect(entry).toContain("#### parser");
|
|
111
|
+
expect(entry).toContain("- Fix edge case");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("includes updated dependencies", () => {
|
|
115
|
+
const groups = emptyGroups();
|
|
116
|
+
const deps = [{ name: "lodash", newVersion: "4.18.0" }];
|
|
117
|
+
const entry = buildChangelogEntry("2.0.0", groups, deps);
|
|
118
|
+
expect(entry).toContain("### Updated Dependencies");
|
|
119
|
+
expect(entry).toContain("- `lodash`: 4.18.0");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("writeChangelog", () => {
|
|
124
|
+
let tmpDir: string;
|
|
125
|
+
|
|
126
|
+
beforeEach(async () => {
|
|
127
|
+
tmpDir = await mkdtemp(join(tmpdir(), "changelog-test-"));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
afterEach(async () => {
|
|
131
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("creates new changelog if missing", async () => {
|
|
135
|
+
await writeChangelog(tmpDir, "## 1.0.0\n\n### Features\n\n- Init\n\n");
|
|
136
|
+
const content = await Bun.file(join(tmpDir, "CHANGELOG.md")).text();
|
|
137
|
+
expect(content).toStartWith("# Changelog");
|
|
138
|
+
expect(content).toContain("## 1.0.0");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("prepends entry to existing changelog", async () => {
|
|
142
|
+
await Bun.write(
|
|
143
|
+
join(tmpDir, "CHANGELOG.md"),
|
|
144
|
+
"# Changelog\n\n## 0.1.0\n\n- Old entry\n",
|
|
145
|
+
);
|
|
146
|
+
await writeChangelog(tmpDir, "## 1.0.0\n\n### Features\n\n- New\n\n");
|
|
147
|
+
const content = await Bun.file(join(tmpDir, "CHANGELOG.md")).text();
|
|
148
|
+
const idx1 = content.indexOf("## 1.0.0");
|
|
149
|
+
const idx2 = content.indexOf("## 0.1.0");
|
|
150
|
+
expect(idx1).toBeLessThan(idx2);
|
|
151
|
+
});
|
|
152
|
+
});
|
package/src/changelog.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { CommitType, GroupedCommits, UpdatedDependency } from "./types.ts";
|
|
2
|
+
import { DEFAULT_SECTIONS } from "./commits.ts";
|
|
3
|
+
|
|
4
|
+
const SECTION_HEADINGS: Record<CommitType, string> = {
|
|
5
|
+
feature: "Features",
|
|
6
|
+
bugfix: "Bug Fixes",
|
|
7
|
+
refactor: "Refactors",
|
|
8
|
+
perf: "Performance",
|
|
9
|
+
style: "Style",
|
|
10
|
+
test: "Tests",
|
|
11
|
+
docs: "Documentation",
|
|
12
|
+
build: "Build",
|
|
13
|
+
ops: "Ops",
|
|
14
|
+
chore: "Chores",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function buildChangelogEntry(
|
|
18
|
+
version: string,
|
|
19
|
+
groups: GroupedCommits,
|
|
20
|
+
updatedDeps: UpdatedDependency[] = [],
|
|
21
|
+
sections: CommitType[] = DEFAULT_SECTIONS,
|
|
22
|
+
): string {
|
|
23
|
+
const lines: string[] = [`## ${version}`, ""];
|
|
24
|
+
|
|
25
|
+
for (const type of sections) {
|
|
26
|
+
const commits = groups[type];
|
|
27
|
+
if (commits.length === 0) continue;
|
|
28
|
+
|
|
29
|
+
lines.push(`### ${SECTION_HEADINGS[type]}`, "");
|
|
30
|
+
|
|
31
|
+
const unscoped = commits.filter((c) => !c.commitScope);
|
|
32
|
+
const scoped = new Map<string, typeof commits>();
|
|
33
|
+
for (const c of commits) {
|
|
34
|
+
if (c.commitScope) {
|
|
35
|
+
const list = scoped.get(c.commitScope);
|
|
36
|
+
if (list) list.push(c);
|
|
37
|
+
else scoped.set(c.commitScope, [c]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const c of unscoped) {
|
|
42
|
+
lines.push(`- ${c.description}`);
|
|
43
|
+
}
|
|
44
|
+
if (unscoped.length > 0 && scoped.size > 0) {
|
|
45
|
+
lines.push("");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [scope, scopeCommits] of scoped) {
|
|
49
|
+
lines.push(`#### ${scope}`, "");
|
|
50
|
+
for (const c of scopeCommits) {
|
|
51
|
+
lines.push(`- ${c.description}`);
|
|
52
|
+
}
|
|
53
|
+
lines.push("");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (unscoped.length > 0 && scoped.size === 0) {
|
|
57
|
+
lines.push("");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (updatedDeps.length > 0) {
|
|
62
|
+
lines.push("### Updated Dependencies", "");
|
|
63
|
+
for (const dep of updatedDeps) {
|
|
64
|
+
lines.push(`- \`${dep.name}\`: ${dep.newVersion}`);
|
|
65
|
+
}
|
|
66
|
+
lines.push("");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function writeChangelog(
|
|
73
|
+
dir: string,
|
|
74
|
+
entry: string,
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
const path = `${dir}/CHANGELOG.md`;
|
|
77
|
+
const file = Bun.file(path);
|
|
78
|
+
const exists = await file.exists();
|
|
79
|
+
|
|
80
|
+
if (!exists) {
|
|
81
|
+
await Bun.write(path, `# Changelog\n\n${entry}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const content = await file.text();
|
|
86
|
+
const headerEnd = content.indexOf("\n");
|
|
87
|
+
|
|
88
|
+
if (headerEnd === -1) {
|
|
89
|
+
await Bun.write(path, `${content}\n\n${entry}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const header = content.slice(0, headerEnd);
|
|
94
|
+
const rest = content.slice(headerEnd + 1);
|
|
95
|
+
await Bun.write(path, `${header}\n\n${entry}${rest}`);
|
|
96
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import type { BumpType, CliOptions, CommitType, PackageScope } from "./types.ts";
|
|
3
|
+
import { normalizeType, DEFAULT_SECTIONS } from "./commits.ts";
|
|
4
|
+
|
|
5
|
+
export function printHelp(): void {
|
|
6
|
+
console.log(`bunset - Version bumping and changelog generation for Bun projects
|
|
7
|
+
|
|
8
|
+
Usage: bunset [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--patch Bump patch version (x.y.Z)
|
|
12
|
+
--minor Bump minor version (x.Y.0)
|
|
13
|
+
--major Bump major version (X.0.0)
|
|
14
|
+
--all Update all packages (monorepo)
|
|
15
|
+
--changed Update only changed packages (monorepo)
|
|
16
|
+
--no-commit Do not commit the version bump and changelog
|
|
17
|
+
--no-tag Do not create git tags for released versions
|
|
18
|
+
--per-package-tags Use package-scoped tags (pkg@version) instead of prefixed
|
|
19
|
+
--tag-prefix <str> Tag prefix (default: "v", e.g. v1.2.3)
|
|
20
|
+
--sections <list> Comma-separated changelog sections (default: feat,fix,perf)
|
|
21
|
+
--dry-run Preview changes without writing files, committing, or tagging
|
|
22
|
+
--no-filter-by-package
|
|
23
|
+
Include all commits in every package changelog (monorepo)
|
|
24
|
+
--help, -h Show this help message
|
|
25
|
+
|
|
26
|
+
When --patch/--minor/--major is omitted, you will be prompted interactively.
|
|
27
|
+
In a monorepo, you will be prompted for --all or --changed if neither is given.
|
|
28
|
+
|
|
29
|
+
Commit format:
|
|
30
|
+
Commits are matched against these patterns (case-insensitive):
|
|
31
|
+
[type] description e.g. [feat] Add auth
|
|
32
|
+
[type]: description e.g. [feat]: Add auth
|
|
33
|
+
type: description e.g. feat: Add auth
|
|
34
|
+
type(scope): description e.g. feat(auth): Add login
|
|
35
|
+
[type(scope)] description e.g. [feat(auth)] Add login
|
|
36
|
+
|
|
37
|
+
An optional scope groups commits under a sub-heading in the changelog.
|
|
38
|
+
|
|
39
|
+
Recognized type keywords:
|
|
40
|
+
feat, feature → Features
|
|
41
|
+
fix, bug, bugfix → Bug Fixes
|
|
42
|
+
refactor → Refactors
|
|
43
|
+
perf → Performance
|
|
44
|
+
style → Style
|
|
45
|
+
test → Tests
|
|
46
|
+
docs → Documentation
|
|
47
|
+
build → Build
|
|
48
|
+
ops → Ops
|
|
49
|
+
chore → Chores
|
|
50
|
+
Only sections listed in --sections (or config) are included in the changelog.
|
|
51
|
+
Default sections: feat, fix, perf.
|
|
52
|
+
|
|
53
|
+
Config:
|
|
54
|
+
Place a .bunset.toml in your project root to set persistent defaults.
|
|
55
|
+
CLI flags always override config values. See README for format.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveOptions(
|
|
59
|
+
isWs: boolean,
|
|
60
|
+
config: Partial<CliOptions> = {},
|
|
61
|
+
): CliOptions | Promise<CliOptions> {
|
|
62
|
+
const { values } = parseArgs({
|
|
63
|
+
args: Bun.argv.slice(2),
|
|
64
|
+
options: {
|
|
65
|
+
all: { type: "boolean", default: false },
|
|
66
|
+
changed: { type: "boolean", default: false },
|
|
67
|
+
patch: { type: "boolean", default: false },
|
|
68
|
+
minor: { type: "boolean", default: false },
|
|
69
|
+
major: { type: "boolean", default: false },
|
|
70
|
+
commit: { type: "boolean", default: true },
|
|
71
|
+
tag: { type: "boolean", default: true },
|
|
72
|
+
"per-package-tags": { type: "boolean", default: false },
|
|
73
|
+
sections: { type: "string" },
|
|
74
|
+
"dry-run": { type: "boolean", default: false },
|
|
75
|
+
"filter-by-package": { type: "boolean", default: true },
|
|
76
|
+
"tag-prefix": { type: "string" },
|
|
77
|
+
help: { type: "boolean", short: "h", default: false },
|
|
78
|
+
},
|
|
79
|
+
strict: true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (values.help) {
|
|
83
|
+
printHelp();
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const cliBump = resolveBump(values);
|
|
88
|
+
const cliScope = resolveScope(values, isWs);
|
|
89
|
+
|
|
90
|
+
const bump = cliBump ?? config.bump ?? null;
|
|
91
|
+
const scope = cliScope ?? config.scope ?? (isWs ? null : "all");
|
|
92
|
+
|
|
93
|
+
const commit = values.commit === false ? false : (config.commit ?? true);
|
|
94
|
+
const tag = values.tag === false ? false : (config.tag ?? true);
|
|
95
|
+
const perPackageTags = values["per-package-tags"]
|
|
96
|
+
? true
|
|
97
|
+
: (config.perPackageTags ?? false);
|
|
98
|
+
|
|
99
|
+
const sections = parseSections(values.sections as string | undefined)
|
|
100
|
+
?? config.sections
|
|
101
|
+
?? DEFAULT_SECTIONS;
|
|
102
|
+
|
|
103
|
+
const dryRun = values["dry-run"] ? true : (config.dryRun ?? false);
|
|
104
|
+
|
|
105
|
+
const filterByPackage = values["filter-by-package"] === false
|
|
106
|
+
? false
|
|
107
|
+
: (config.filterByPackage ?? true);
|
|
108
|
+
|
|
109
|
+
const tagPrefix = values["tag-prefix"] as string | undefined
|
|
110
|
+
?? config.tagPrefix
|
|
111
|
+
?? "v";
|
|
112
|
+
|
|
113
|
+
if (bump && scope) {
|
|
114
|
+
return { scope, bump, commit, tag, perPackageTags, sections, dryRun, filterByPackage, tagPrefix };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return promptForMissing(
|
|
118
|
+
{ commit, tag, perPackageTags, sections, dryRun, filterByPackage, tagPrefix },
|
|
119
|
+
bump,
|
|
120
|
+
scope,
|
|
121
|
+
isWs,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveBump(values: Record<string, unknown>): BumpType | null {
|
|
126
|
+
if (values.major) return "major";
|
|
127
|
+
if (values.minor) return "minor";
|
|
128
|
+
if (values.patch) return "patch";
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveScope(
|
|
133
|
+
values: Record<string, unknown>,
|
|
134
|
+
isWs: boolean,
|
|
135
|
+
): PackageScope | null {
|
|
136
|
+
if (!isWs) return "all";
|
|
137
|
+
if (values.all) return "all";
|
|
138
|
+
if (values.changed) return "changed";
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseSections(raw: string | undefined): CommitType[] | null {
|
|
143
|
+
if (!raw) return null;
|
|
144
|
+
const result: CommitType[] = [];
|
|
145
|
+
for (const part of raw.split(",")) {
|
|
146
|
+
const type = normalizeType(part.trim());
|
|
147
|
+
if (type) result.push(type);
|
|
148
|
+
}
|
|
149
|
+
return result.length > 0 ? result : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface MergedDefaults {
|
|
153
|
+
commit: boolean;
|
|
154
|
+
tag: boolean;
|
|
155
|
+
perPackageTags: boolean;
|
|
156
|
+
sections: CommitType[];
|
|
157
|
+
dryRun: boolean;
|
|
158
|
+
filterByPackage: boolean;
|
|
159
|
+
tagPrefix: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function promptForMissing(
|
|
163
|
+
merged: MergedDefaults,
|
|
164
|
+
bump: BumpType | null,
|
|
165
|
+
scope: PackageScope | null,
|
|
166
|
+
isWs: boolean,
|
|
167
|
+
): Promise<CliOptions> {
|
|
168
|
+
if (!scope && isWs) {
|
|
169
|
+
process.stdout.write(
|
|
170
|
+
"Update packages: (1) ALL packages (2) Only CHANGED packages [default: 2]: ",
|
|
171
|
+
);
|
|
172
|
+
const answer = await readLine();
|
|
173
|
+
scope = answer.trim() === "1" ? "all" : "changed";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!bump) {
|
|
177
|
+
process.stdout.write(
|
|
178
|
+
"Version bump: (1) patch (2) minor (3) major [default: 1]: ",
|
|
179
|
+
);
|
|
180
|
+
const answer = await readLine();
|
|
181
|
+
const choice = answer.trim();
|
|
182
|
+
if (choice === "3") bump = "major";
|
|
183
|
+
else if (choice === "2") bump = "minor";
|
|
184
|
+
else bump = "patch";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
scope: scope ?? "all",
|
|
189
|
+
bump,
|
|
190
|
+
...merged,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function readLine(): Promise<string> {
|
|
195
|
+
for await (const line of console) {
|
|
196
|
+
return line;
|
|
197
|
+
}
|
|
198
|
+
return "";
|
|
199
|
+
}
|