argsbarg 3.3.2 → 3.3.3
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 +6 -3
- package/docs/bundled-docs.md +18 -0
- package/package.json +1 -1
- package/src/docs/builtin.ts +22 -3
- package/src/docs/docs.test.ts +68 -1
- package/src/docs/resolve.ts +19 -30
- package/src/docs/save.ts +60 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [3.3.
|
|
10
|
+
## [3.3.3] - 2026-06-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
11
13
|
|
|
14
|
+
- **`docs --save`** — write bundled docs to `./docs/`; `docs all --save` writes one file per topic instead of combined stdout output. Argsbarg-generated markdown (`mcp`, `api`, `skill`) is prefixed with a `Generated by … docs … --save` HTML comment.
|
|
12
15
|
|
|
13
16
|
## [3.3.2] - 2026-06-21
|
|
14
17
|
|
|
@@ -252,8 +255,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
252
255
|
- Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
|
|
253
256
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
254
257
|
|
|
255
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.
|
|
256
|
-
[3.3.
|
|
258
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.3...HEAD
|
|
259
|
+
[3.3.3]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.3
|
|
257
260
|
[3.3.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.2
|
|
258
261
|
[3.3.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.1
|
|
259
262
|
[3.3.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.0
|
package/docs/bundled-docs.md
CHANGED
|
@@ -32,6 +32,8 @@ myapp docs api # command tree as markdown
|
|
|
32
32
|
myapp docs skill # generated Cursor SKILL.md
|
|
33
33
|
myapp docs all # all user topics; includes auto mcp when MCP enabled
|
|
34
34
|
myapp docs mcp # auto-generated when mcpServer.enabled
|
|
35
|
+
myapp docs readme --save # write ./docs/readme.md
|
|
36
|
+
myapp docs all --save # write ./docs/<topic>.md for each bundled topic
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
## Configuration
|
|
@@ -89,3 +91,19 @@ All `docs` subcommands are hidden from MCP `tools/list` (`mcpTool: { enabled: fa
|
|
|
89
91
|
| `mcp` | Callable tools + schema resource |
|
|
90
92
|
|
|
91
93
|
Do not declare a top-level command named **`docs`** when `docs.enabled` is `true` — it is reserved.
|
|
94
|
+
|
|
95
|
+
## Save to disk (`--save`)
|
|
96
|
+
|
|
97
|
+
Pass **`--save`** on `docs` or any docs subcommand to write files under **`./docs/`** (relative to the current working directory). Each saved path is printed on stdout.
|
|
98
|
+
|
|
99
|
+
| Command | Output |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `docs readme --save` | `./docs/readme.md` |
|
|
102
|
+
| `docs schema --save` | `./docs/schema.json` |
|
|
103
|
+
| `docs api --save` | `./docs/api.md` |
|
|
104
|
+
| `docs skill --save` | `./docs/skill.md` |
|
|
105
|
+
| `docs all --save` | one file per bundled topic (`readme.md`, `mcp.md`, …) — not a combined file |
|
|
106
|
+
|
|
107
|
+
`docs all --save` uses the same topic set as stdout `docs all` (user topics plus auto `mcp` when enabled). It does not include `schema`, `api`, or `skill` unless you invoke those subcommands separately.
|
|
108
|
+
|
|
109
|
+
Argsbarg-generated markdown (`mcp`, `api`, `skill`) is prefixed with an HTML comment: `<!-- Generated by myapp docs api --save; do not edit. -->`. Consumer topic files and `schema.json` are written as-is.
|
package/package.json
CHANGED
package/src/docs/builtin.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CliFallbackMode, type CliLeaf, type CliProgram, type CliRouter } from "../types.ts";
|
|
1
|
+
import { CliFallbackMode, CliOptionKind, type CliLeaf, type CliOption, type CliProgram, type CliRouter } from "../types.ts";
|
|
2
2
|
import {
|
|
3
3
|
DOCS_ROUTER_DESCRIPTION,
|
|
4
4
|
docsEffectiveDefaultTopic,
|
|
@@ -8,14 +8,32 @@ import {
|
|
|
8
8
|
docsUserTopicKeys,
|
|
9
9
|
printDocsTopic,
|
|
10
10
|
} from "./resolve.ts";
|
|
11
|
+
import { saveDocsTopics } from "./save.ts";
|
|
12
|
+
|
|
13
|
+
const DOCS_SAVE_OPTION: CliOption = {
|
|
14
|
+
name: "save",
|
|
15
|
+
description: "Write documentation to ./docs/ (`docs all` writes one file per topic).",
|
|
16
|
+
kind: CliOptionKind.Presence,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function runDocsTopic(program: CliProgram, topic: string, ctx: { hasFlag(name: string): boolean }): void {
|
|
20
|
+
if (ctx.hasFlag("save")) {
|
|
21
|
+
for (const path of saveDocsTopics(program, topic)) {
|
|
22
|
+
process.stdout.write(`${path}\n`);
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
printDocsTopic(program, topic);
|
|
27
|
+
}
|
|
11
28
|
|
|
12
29
|
function docsLeaf(program: CliProgram, key: string, description: string): CliLeaf {
|
|
13
30
|
return {
|
|
14
31
|
key,
|
|
15
32
|
description,
|
|
33
|
+
options: [DOCS_SAVE_OPTION],
|
|
16
34
|
mcpTool: { enabled: false },
|
|
17
|
-
handler: () => {
|
|
18
|
-
|
|
35
|
+
handler: (ctx) => {
|
|
36
|
+
runDocsTopic(program, key, ctx);
|
|
19
37
|
},
|
|
20
38
|
};
|
|
21
39
|
}
|
|
@@ -46,6 +64,7 @@ export function cliBuiltinDocsGroup(program: CliProgram): CliRouter {
|
|
|
46
64
|
return {
|
|
47
65
|
key: "docs",
|
|
48
66
|
description: docs.description ?? DOCS_ROUTER_DESCRIPTION,
|
|
67
|
+
options: [DOCS_SAVE_OPTION],
|
|
49
68
|
fallbackCommand: docsEffectiveDefaultTopic(docs),
|
|
50
69
|
fallbackMode: CliFallbackMode.MissingOnly,
|
|
51
70
|
commands: leaves,
|
package/src/docs/docs.test.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
2
5
|
import { cliPresentationRoot } from "../builtins/presentation.ts";
|
|
3
6
|
import { completionBashScript } from "../completion.ts";
|
|
4
7
|
import { cliInvoke } from "../index.ts";
|
|
@@ -6,6 +9,21 @@ import type { CliProgram } from "../types.ts";
|
|
|
6
9
|
import { cliValidateProgram } from "../validate.ts";
|
|
7
10
|
import { combineAllDocs, docsEffectiveDefaultTopic } from "./resolve.ts";
|
|
8
11
|
import { generateMcpGuide } from "./mcp-guide.ts";
|
|
12
|
+
import { docsTopicsToSave, saveDocsTopics } from "./save.ts";
|
|
13
|
+
|
|
14
|
+
let workDir: string;
|
|
15
|
+
let prevCwd: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
workDir = mkdtempSync(join(tmpdir(), "argsbarg-docs-save-"));
|
|
19
|
+
prevCwd = process.cwd();
|
|
20
|
+
process.chdir(workDir);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
process.chdir(prevCwd);
|
|
25
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
9
27
|
|
|
10
28
|
function docsFixture(mcp = true): CliProgram {
|
|
11
29
|
return {
|
|
@@ -165,3 +183,52 @@ test("generateMcpGuide includes schema URI", () => {
|
|
|
165
183
|
const guide = generateMcpGuide(docsFixture(true));
|
|
166
184
|
expect(guide).toContain("myapp://schema");
|
|
167
185
|
});
|
|
186
|
+
|
|
187
|
+
test("docs --save writes topic file", async () => {
|
|
188
|
+
const result = await cliInvoke(docsFixture(), ["docs", "readme", "--save"]);
|
|
189
|
+
expect(result.exitCode).toBe(0);
|
|
190
|
+
expect(result.stdout.trim()).toBe("docs/readme.md");
|
|
191
|
+
const text = readFileSync(join(workDir, "docs/readme.md"), "utf8");
|
|
192
|
+
expect(text).toContain("Hello README");
|
|
193
|
+
expect(text).not.toContain("Generated by");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("docs api --save prepends generated hint", async () => {
|
|
197
|
+
const result = await cliInvoke(docsFixture(), ["docs", "api", "--save"]);
|
|
198
|
+
expect(result.exitCode).toBe(0);
|
|
199
|
+
const text = readFileSync(join(workDir, "docs/api.md"), "utf8");
|
|
200
|
+
expect(text.startsWith("<!-- Generated by myapp docs api --save; do not edit. -->\n\n")).toBe(
|
|
201
|
+
true,
|
|
202
|
+
);
|
|
203
|
+
expect(text).toContain("CLI API reference");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("docs schema --save writes JSON file", async () => {
|
|
207
|
+
const result = await cliInvoke(docsFixture(), ["docs", "schema", "--save"]);
|
|
208
|
+
expect(result.exitCode).toBe(0);
|
|
209
|
+
expect(result.stdout.trim()).toBe("docs/schema.json");
|
|
210
|
+
const text = readFileSync(join(workDir, "docs/schema.json"), "utf8");
|
|
211
|
+
expect(text).not.toContain("Generated by");
|
|
212
|
+
const schema = JSON.parse(text);
|
|
213
|
+
expect(schema.key).toBe("myapp");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("docs all --save writes separate topic files", async () => {
|
|
217
|
+
const program = docsFixture(true);
|
|
218
|
+
const result = await cliInvoke(program, ["docs", "all", "--save"]);
|
|
219
|
+
expect(result.exitCode).toBe(0);
|
|
220
|
+
const lines = result.stdout.trim().split("\n");
|
|
221
|
+
expect(lines).toEqual(docsTopicsToSave(program, "all").map((key) => `docs/${key}.md`));
|
|
222
|
+
expect(readFileSync(join(workDir, "docs/readme.md"), "utf8")).not.toContain("Generated by");
|
|
223
|
+
expect(readFileSync(join(workDir, "docs/mcp.md"), "utf8")).toContain(
|
|
224
|
+
"<!-- Generated by myapp docs mcp --save; do not edit. -->",
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("saveDocsTopics returns relative paths", () => {
|
|
229
|
+
const paths = saveDocsTopics(docsFixture(), "api");
|
|
230
|
+
expect(paths).toEqual(["docs/api.md"]);
|
|
231
|
+
const text = readFileSync(join(workDir, "docs/api.md"), "utf8");
|
|
232
|
+
expect(text).toContain("<!-- Generated by myapp docs api --save; do not edit. -->");
|
|
233
|
+
expect(text).toContain("CLI API reference");
|
|
234
|
+
});
|
package/src/docs/resolve.ts
CHANGED
|
@@ -77,6 +77,24 @@ export function docsTopicText(program: CliProgram, topic: string): string {
|
|
|
77
77
|
return entry.text;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/** Full file body for a docs topic (stdout or `--save`). */
|
|
81
|
+
export function docsTopicContent(program: CliProgram, topic: string): string {
|
|
82
|
+
if (topic === "schema") {
|
|
83
|
+
return cliSchemaJson(program);
|
|
84
|
+
}
|
|
85
|
+
if (topic === "api") {
|
|
86
|
+
return generateApiGuide(program);
|
|
87
|
+
}
|
|
88
|
+
if (topic === "skill") {
|
|
89
|
+
return `${generateSkillBundle(program, "cursor").skillMd}\n`;
|
|
90
|
+
}
|
|
91
|
+
if (topic === "all") {
|
|
92
|
+
return `${combineAllDocs(program)}\n`;
|
|
93
|
+
}
|
|
94
|
+
const text = docsTopicText(program, topic);
|
|
95
|
+
return text.endsWith("\n") ? text : `${text}\n`;
|
|
96
|
+
}
|
|
97
|
+
|
|
80
98
|
/** All bundled docs concatenated with horizontal rules. */
|
|
81
99
|
export function combineAllDocs(program: CliProgram): string {
|
|
82
100
|
return docsPrintOrder(program)
|
|
@@ -84,36 +102,7 @@ export function combineAllDocs(program: CliProgram): string {
|
|
|
84
102
|
.join("\n\n---\n\n");
|
|
85
103
|
}
|
|
86
104
|
|
|
87
|
-
/** Writes CLI schema JSON to stdout (`docs schema`). */
|
|
88
|
-
export function printDocsSchema(program: CliProgram): void {
|
|
89
|
-
process.stdout.write(cliSchemaJson(program));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Writes markdown API reference to stdout (`docs api`). */
|
|
93
|
-
export function printDocsApi(program: CliProgram): void {
|
|
94
|
-
process.stdout.write(generateApiGuide(program));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Writes generated Cursor SKILL.md to stdout (`docs skill`). */
|
|
98
|
-
export function printDocsSkill(program: CliProgram): void {
|
|
99
|
-
const bundle = generateSkillBundle(program, "cursor");
|
|
100
|
-
process.stdout.write(`${bundle.skillMd}\n`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
105
|
/** Writes one docs topic (or `all`) to stdout. */
|
|
104
106
|
export function printDocsTopic(program: CliProgram, topic: string): void {
|
|
105
|
-
|
|
106
|
-
printDocsSchema(program);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (topic === "api") {
|
|
110
|
-
printDocsApi(program);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (topic === "skill") {
|
|
114
|
-
printDocsSkill(program);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const content = topic === "all" ? combineAllDocs(program) : docsTopicText(program, topic);
|
|
118
|
-
process.stdout.write(`${content}\n`);
|
|
107
|
+
process.stdout.write(docsTopicContent(program, topic));
|
|
119
108
|
}
|
package/src/docs/save.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { CliProgram } from "../types.ts";
|
|
4
|
+
import { docsPrintOrder, docsTopicContent } from "./resolve.ts";
|
|
5
|
+
|
|
6
|
+
/** Relative output directory for `docs --save`. */
|
|
7
|
+
export const DOCS_SAVE_DIR = "docs";
|
|
8
|
+
|
|
9
|
+
/** Builtin docs topics generated by argsbarg (not consumer `docs.topics`). */
|
|
10
|
+
export const DOCS_GENERATED_SAVE_TOPICS = ["mcp", "api", "skill"] as const;
|
|
11
|
+
|
|
12
|
+
/** Whether `--save` should prepend a generated-file hint (argsbarg writers only). */
|
|
13
|
+
export function docsTopicIsGeneratedByArgsbarg(topic: string): boolean {
|
|
14
|
+
return (DOCS_GENERATED_SAVE_TOPICS as readonly string[]).includes(topic);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** HTML comment prepended to generated markdown saved with `--save`. */
|
|
18
|
+
export function docsSaveGeneratedHint(program: CliProgram, topic: string): string {
|
|
19
|
+
return `<!-- Generated by ${program.key} docs ${topic} --save; do not edit. -->\n\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** File body for `--save` (hint on argsbarg-generated markdown only). */
|
|
23
|
+
export function docsTopicContentForSave(program: CliProgram, topic: string): string {
|
|
24
|
+
const content = docsTopicContent(program, topic);
|
|
25
|
+
if (!docsTopicIsGeneratedByArgsbarg(topic)) {
|
|
26
|
+
return content;
|
|
27
|
+
}
|
|
28
|
+
return `${docsSaveGeneratedHint(program, topic)}${content}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Filename for a saved docs topic. */
|
|
32
|
+
export function docsSaveFilename(topic: string): string {
|
|
33
|
+
if (topic === "schema") {
|
|
34
|
+
return "schema.json";
|
|
35
|
+
}
|
|
36
|
+
return `${topic}.md`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Topic keys to write for one `docs` invocation (`all` expands to bundled topics). */
|
|
40
|
+
export function docsTopicsToSave(program: CliProgram, topic: string): string[] {
|
|
41
|
+
if (topic === "all") {
|
|
42
|
+
return docsPrintOrder(program);
|
|
43
|
+
}
|
|
44
|
+
return [topic];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Writes docs topic(s) under `./docs/`; returns relative paths written. */
|
|
48
|
+
export function saveDocsTopics(program: CliProgram, topic: string): string[] {
|
|
49
|
+
const dir = join(process.cwd(), DOCS_SAVE_DIR);
|
|
50
|
+
mkdirSync(dir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
const saved: string[] = [];
|
|
53
|
+
for (const key of docsTopicsToSave(program, topic)) {
|
|
54
|
+
const rel = join(DOCS_SAVE_DIR, docsSaveFilename(key));
|
|
55
|
+
const abs = join(process.cwd(), rel);
|
|
56
|
+
writeFileSync(abs, docsTopicContentForSave(program, key), "utf8");
|
|
57
|
+
saved.push(rel);
|
|
58
|
+
}
|
|
59
|
+
return saved;
|
|
60
|
+
}
|