argsbarg 3.5.0 → 3.6.1
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/CHANGELOG.md +25 -1
- package/README.md +24 -8
- package/docs/cli-program.md +75 -1
- package/docs/templates/cursor/rules/cli-program.mdc +15 -21
- package/index.d.ts +45 -0
- package/package.json +10 -1
- package/src/context.ts +91 -15
- package/src/docs/api-guide.ts +17 -3
- package/src/docs/mcp-guide.ts +3 -1
- package/src/formats.test.ts +35 -0
- package/src/formats.ts +135 -0
- package/src/index.test.ts +7 -7
- package/src/index.ts +13 -1
- package/src/mcp/tools.ts +67 -18
- package/src/parse.ts +33 -3
- package/src/types.ts +24 -0
- package/src/validate.ts +54 -0
- package/.cursor/plans/cliprogram_capabilities_refactor_081e1737.plan.md +0 -224
- package/.cursor/plans/mcp_v1.1_polish_e9656029.plan.md +0 -260
- package/.cursor/plans/mcp_v1.2_invocation_and_extensions_a4f82c1e.plan.md +0 -647
- package/.cursor/plans/v1.3_parser_ergonomics_b3e91f02.plan.md +0 -455
- package/.cursor/rules/code.mdc +0 -9
- package/.github/copilot-instructions.md.md +0 -8
- package/.github/pull_request_template.md +0 -13
- package/.private/scratch.md +0 -4
- package/CLAUDE.md +0 -8
- package/biome.json +0 -40
- package/bun.lock +0 -43
- package/justfile +0 -57
- package/logo.png +0 -0
- package/plan.md +0 -19
- package/scripts/release.ts +0 -266
- package/tsconfig.json +0 -12
package/scripts/release.ts
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Release workflow: bump semver; update CHANGELOG (trailing release reference links); commit, tag, push,
|
|
4
|
-
* GitHub release, npm publish.
|
|
5
|
-
*
|
|
6
|
-
* **Run `just test` (or the individual `just check-types`, `just lint`, `bun test`) before this
|
|
7
|
-
* script** — it does not typecheck, lint, or run tests. The `just release` recipe runs checks first.
|
|
8
|
-
*
|
|
9
|
-
* Usage: `bun scripts/release.ts <major|minor|patch>` (usually via `just release <bump>`.)
|
|
10
|
-
*
|
|
11
|
-
* Requires: `git`, `gh` (authenticated), `npm` logged in for publish. The release commit stages **all**
|
|
12
|
-
* repo changes (`git add -A`), not only the version and CHANGELOG edits from this script.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { unlink } from "node:fs/promises";
|
|
16
|
-
|
|
17
|
-
/** Returns the parent directory of an absolute file path. */
|
|
18
|
-
function parentDir(absolute: string): string {
|
|
19
|
-
const s = absolute.replace(/[/\\]+$/, "");
|
|
20
|
-
const i = Math.max(s.lastIndexOf("/"), s.lastIndexOf("\\"));
|
|
21
|
-
if (i <= 0) {
|
|
22
|
-
return s;
|
|
23
|
-
}
|
|
24
|
-
return s.slice(0, i);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Monorepo root: parent of `scripts/` (directory of this file). */
|
|
28
|
-
const repoRoot = parentDir(import.meta.dir);
|
|
29
|
-
|
|
30
|
-
/** Which semver segment to increment for the next release. */
|
|
31
|
-
type Bump = "major" | "minor" | "patch";
|
|
32
|
-
|
|
33
|
-
/** Prints usage to stderr and exits with status 1. */
|
|
34
|
-
function usage(): never {
|
|
35
|
-
console.error("Usage: bun scripts/release.ts <major|minor|patch>");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Parses the release bump level from argv; exits on invalid input. */
|
|
40
|
-
function parseBump(s: string | undefined): Bump {
|
|
41
|
-
if (s === "major" || s === "minor" || s === "patch") {
|
|
42
|
-
return s;
|
|
43
|
-
}
|
|
44
|
-
usage();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Returns the next `x.y.z` version for the given `major` | `minor` | `patch` segment. */
|
|
48
|
-
function bumpSemver(version: string, part: Bump): string {
|
|
49
|
-
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
|
|
50
|
-
if (!m) {
|
|
51
|
-
throw new Error(`package.json version must be semver x.y.z, got: ${JSON.stringify(version)}`);
|
|
52
|
-
}
|
|
53
|
-
let major = Number(m[1]);
|
|
54
|
-
let minor = Number(m[2]);
|
|
55
|
-
let patch = Number(m[3]);
|
|
56
|
-
if (part === "major") {
|
|
57
|
-
major += 1;
|
|
58
|
-
minor = 0;
|
|
59
|
-
patch = 0;
|
|
60
|
-
} else if (part === "minor") {
|
|
61
|
-
minor += 1;
|
|
62
|
-
patch = 0;
|
|
63
|
-
} else {
|
|
64
|
-
patch += 1;
|
|
65
|
-
}
|
|
66
|
-
return `${major}.${minor}.${patch}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Returns stdout from a successful subprocess, trimmed; throws if the command fails. */
|
|
70
|
-
function runCapture(cmd: string[], cwd: string = repoRoot): string {
|
|
71
|
-
const proc = Bun.spawnSync({ cmd, cwd, stdout: "pipe", stderr: "pipe" });
|
|
72
|
-
if (proc.exitCode !== 0) {
|
|
73
|
-
const err = new TextDecoder().decode(proc.stderr);
|
|
74
|
-
throw new Error(`Command failed: ${cmd.join(" ")}\n${err}`);
|
|
75
|
-
}
|
|
76
|
-
return new TextDecoder().decode(proc.stdout).trimEnd();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Parses `git remote get-url origin` into `https://github.com/owner/repo`, or null if not GitHub. */
|
|
80
|
-
function githubRepoBaseFromOrigin(origin: string): string | null {
|
|
81
|
-
const trimmed = origin.trim();
|
|
82
|
-
let path = trimmed.replace(/^git@[^:]+:/, "").replace(/^https?:\/\/[^/]+\//, "");
|
|
83
|
-
if (path.endsWith(".git")) {
|
|
84
|
-
path = path.slice(0, -4);
|
|
85
|
-
}
|
|
86
|
-
if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(path)) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
return `https://github.com/${path}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Collects released semver headings from changelog body in document order (newest first when
|
|
94
|
-
* headings follow Keep a Changelog order after each release).
|
|
95
|
-
*/
|
|
96
|
-
function releasedSemverVersionsFromChangelog(md: string): string[] {
|
|
97
|
-
const re = /^## \[(\d+\.\d+\.\d+)\] /gm;
|
|
98
|
-
const out: string[] = [];
|
|
99
|
-
let m = re.exec(md);
|
|
100
|
-
while (m !== null) {
|
|
101
|
-
const version = m[1];
|
|
102
|
-
if (version !== undefined) {
|
|
103
|
-
out.push(version);
|
|
104
|
-
}
|
|
105
|
-
m = re.exec(md);
|
|
106
|
-
}
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Strips optional legacy `## Links` heading and/or trailing Keep-a-Changelog reference link lines
|
|
112
|
-
* (`[...]: http...`) at the end of the file.
|
|
113
|
-
*/
|
|
114
|
-
function stripChangelogLinkDefinitions(md: string): string {
|
|
115
|
-
const s = md.replace(/\r?\n## Links\r?\n/, "\n");
|
|
116
|
-
const lines = s.split(/\r?\n/);
|
|
117
|
-
let i = lines.length;
|
|
118
|
-
while (i > 0 && lines[i - 1] === "") {
|
|
119
|
-
i -= 1;
|
|
120
|
-
}
|
|
121
|
-
const refLine = /^\[[^\]]+\]: .+$/;
|
|
122
|
-
while (i > 0) {
|
|
123
|
-
const line = lines[i - 1];
|
|
124
|
-
if (line === undefined || !refLine.test(line)) {
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
i -= 1;
|
|
128
|
-
}
|
|
129
|
-
while (i > 0 && lines[i - 1] === "") {
|
|
130
|
-
i -= 1;
|
|
131
|
-
}
|
|
132
|
-
if (i > 0 && lines[i - 1] === "## Links") {
|
|
133
|
-
i -= 1;
|
|
134
|
-
}
|
|
135
|
-
while (i > 0 && lines[i - 1] === "") {
|
|
136
|
-
i -= 1;
|
|
137
|
-
}
|
|
138
|
-
if (i === 0) {
|
|
139
|
-
return "";
|
|
140
|
-
}
|
|
141
|
-
return lines.slice(0, i).join("\n");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Appends reference link definitions (no visible heading): `[Unreleased]` → compare latest tag to `HEAD`,
|
|
146
|
-
* and each released `[x.y.z]` → release tag URL.
|
|
147
|
-
*/
|
|
148
|
-
function appendChangelogLinkDefinitions(md: string, repoBase: string): string {
|
|
149
|
-
const body = stripChangelogLinkDefinitions(md).trimEnd();
|
|
150
|
-
const versions = releasedSemverVersionsFromChangelog(body);
|
|
151
|
-
if (versions.length === 0) {
|
|
152
|
-
return `${body}\n`;
|
|
153
|
-
}
|
|
154
|
-
const newest = versions[0];
|
|
155
|
-
if (newest === undefined) {
|
|
156
|
-
return `${body}\n`;
|
|
157
|
-
}
|
|
158
|
-
const lines = [
|
|
159
|
-
"",
|
|
160
|
-
"",
|
|
161
|
-
`[Unreleased]: ${repoBase}/compare/v${newest}...HEAD`,
|
|
162
|
-
...versions.map((v) => `[${v}]: ${repoBase}/releases/tag/v${v}`),
|
|
163
|
-
"",
|
|
164
|
-
];
|
|
165
|
-
return `${body}${lines.join("\n")}`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Runs a subprocess, inheriting stdio, and exits the process on non-zero status. */
|
|
169
|
-
function run(label: string, cmd: string[], cwd: string = repoRoot): void {
|
|
170
|
-
console.log(`\n→ ${label}: ${cmd.join(" ")}`);
|
|
171
|
-
const proc = Bun.spawnSync({ cmd, cwd, stdout: "inherit", stderr: "inherit" });
|
|
172
|
-
if (proc.exitCode !== 0) {
|
|
173
|
-
console.error(`\n${label} failed (exit ${proc.exitCode})`);
|
|
174
|
-
process.exit(proc.exitCode ?? 1);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Moves `[Unreleased]` content under a new `## [version] - date` heading and leaves an empty Unreleased. */
|
|
179
|
-
function promoteChangelog(content: string, version: string, date: string): string {
|
|
180
|
-
const header = "## [Unreleased]";
|
|
181
|
-
const idx = content.indexOf(header);
|
|
182
|
-
if (idx === -1) {
|
|
183
|
-
throw new Error("CHANGELOG.md: missing ## [Unreleased] section");
|
|
184
|
-
}
|
|
185
|
-
const lineEnd = content.indexOf("\n", idx);
|
|
186
|
-
if (lineEnd === -1) {
|
|
187
|
-
throw new Error("CHANGELOG.md: malformed after [Unreleased]");
|
|
188
|
-
}
|
|
189
|
-
const bodyStart = lineEnd + 1;
|
|
190
|
-
const nextIdx = content.indexOf("\n## [", bodyStart);
|
|
191
|
-
const body =
|
|
192
|
-
nextIdx === -1
|
|
193
|
-
? content.slice(bodyStart).trimEnd()
|
|
194
|
-
: content.slice(bodyStart, nextIdx).trimEnd();
|
|
195
|
-
const tail = nextIdx === -1 ? "" : content.slice(nextIdx + 1);
|
|
196
|
-
const before = content.slice(0, idx);
|
|
197
|
-
const newBlock = `${header}\n\n## [${version}] - ${date}\n${body}\n\n`;
|
|
198
|
-
return before + newBlock + tail;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** Returns the markdown body of one version section for `gh release` notes. */
|
|
202
|
-
function extractReleaseNotes(nextChangelog: string, version: string, date: string): string {
|
|
203
|
-
const verHeader = `## [${version}] - ${date}`;
|
|
204
|
-
const ni = nextChangelog.indexOf(verHeader);
|
|
205
|
-
if (ni === -1) {
|
|
206
|
-
throw new Error("internal: promoted version header not found in CHANGELOG");
|
|
207
|
-
}
|
|
208
|
-
const nextHdrAt = nextChangelog.indexOf("\n## [", ni + verHeader.length);
|
|
209
|
-
if (nextHdrAt === -1) {
|
|
210
|
-
return nextChangelog.slice(ni).trimEnd();
|
|
211
|
-
}
|
|
212
|
-
return nextChangelog.slice(ni, nextHdrAt).trimEnd();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** Joins a root path and a file segment with a single path separator. */
|
|
216
|
-
function joinFile(root: string, segment: string): string {
|
|
217
|
-
const r = root.replace(/[/\\]+$/, "");
|
|
218
|
-
return `${r}/${segment}`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const bump = parseBump(process.argv[2]);
|
|
222
|
-
|
|
223
|
-
const pkgPath = joinFile(repoRoot, "package.json");
|
|
224
|
-
const pkgText = await Bun.file(pkgPath).text();
|
|
225
|
-
const pkg = JSON.parse(pkgText) as { name: string; version: string };
|
|
226
|
-
const nextVersion = bumpSemver(pkg.version, bump);
|
|
227
|
-
const releaseDate = new Date().toISOString().slice(0, 10);
|
|
228
|
-
|
|
229
|
-
pkg.version = nextVersion;
|
|
230
|
-
await Bun.write(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
231
|
-
|
|
232
|
-
const changelogPath = joinFile(repoRoot, "CHANGELOG.md");
|
|
233
|
-
const changelog = await Bun.file(changelogPath).text();
|
|
234
|
-
const promoted = promoteChangelog(changelog, nextVersion, releaseDate);
|
|
235
|
-
const origin = runCapture(["git", "remote", "get-url", "origin"], repoRoot);
|
|
236
|
-
const repoBase = githubRepoBaseFromOrigin(origin);
|
|
237
|
-
if (repoBase === null) {
|
|
238
|
-
throw new Error(`Could not parse GitHub repo URL from origin: ${JSON.stringify(origin)}`);
|
|
239
|
-
}
|
|
240
|
-
const nextChangelog = appendChangelogLinkDefinitions(promoted, repoBase);
|
|
241
|
-
await Bun.write(changelogPath, nextChangelog);
|
|
242
|
-
|
|
243
|
-
const notesPath = joinFile(repoRoot, ".release-notes.tmp.md");
|
|
244
|
-
const notesContent = extractReleaseNotes(nextChangelog, nextVersion, releaseDate);
|
|
245
|
-
await Bun.write(notesPath, `${notesContent}\n`);
|
|
246
|
-
|
|
247
|
-
const tag = `v${nextVersion}`;
|
|
248
|
-
const msg = `release ${tag}`;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
run("git add (all)", ["git", "add", "-A"], repoRoot);
|
|
252
|
-
run("git commit", ["git", "commit", "-m", msg], repoRoot);
|
|
253
|
-
run("git tag", ["git", "tag", "-a", tag, "-m", msg], repoRoot);
|
|
254
|
-
run("git push", ["git", "push"], repoRoot);
|
|
255
|
-
run("git push tags", ["git", "push", "--tags"], repoRoot);
|
|
256
|
-
run(
|
|
257
|
-
"gh release",
|
|
258
|
-
["gh", "release", "create", tag, "--title", tag, "--notes-file", notesPath],
|
|
259
|
-
repoRoot,
|
|
260
|
-
);
|
|
261
|
-
run("npm publish", ["npm", "publish"], repoRoot);
|
|
262
|
-
} finally {
|
|
263
|
-
await unlink(notesPath).catch(() => {});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
console.log(`\nDone: published ${pkg.name}@${nextVersion} (${tag}).`);
|
package/tsconfig.json
DELETED