argsbarg 6.1.10 → 6.2.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/CHANGELOG.md +10 -1
- package/README.md +43 -41
- package/docs/README.md +3 -2
- package/docs/ai-skills.md +36 -23
- package/docs/cli-program.md +12 -11
- package/docs/config-schema.md +3 -3
- package/docs/configure.md +12 -16
- package/docs/developing.md +6 -6
- package/docs/mcp.md +21 -57
- package/docs/output-schema.md +2 -2
- package/examples/formats.ts +5 -6
- package/examples/full-example/README.md +7 -69
- package/examples/full-example/docs/cli-schema.json +1 -1659
- package/examples/full-example/docs/cli.md +2 -1538
- package/examples/full-example/docs/http.md +0 -7
- package/examples/full-example/docs/mcp.md +23 -59
- package/examples/full-example/docs/openapi.json +2 -782
- package/examples/full-example/docs/skill.md +18 -14
- package/examples/full-example/justfile +5 -17
- package/examples/full-example/scripts/create-identity.ts +2 -1
- package/examples/full-example/src/commands/status/command.test.ts +2 -2
- package/examples/full-example/src/commands/status/command.ts +7 -6
- package/examples/full-example/src/program.ts +4 -10
- package/examples/full-example-json/Formula/.gitkeep +0 -0
- package/examples/full-example-json/Formula/full-example-json.rb +35 -0
- package/examples/full-example-json/README.md +27 -0
- package/examples/full-example-json/biome.json +22 -0
- package/examples/full-example-json/bun.lock +48 -0
- package/examples/full-example-json/docs/README.md +27 -0
- package/examples/full-example-json/docs/cli-schema.json +2145 -0
- package/examples/full-example-json/docs/cli.md +1990 -0
- package/examples/full-example-json/docs/http.md +92 -0
- package/examples/full-example-json/docs/mcp.md +116 -0
- package/examples/full-example-json/docs/openapi.json +1246 -0
- package/examples/full-example-json/docs/skill.md +57 -0
- package/examples/full-example-json/justfile +171 -0
- package/examples/full-example-json/package.json +22 -0
- package/examples/full-example-json/scripts/create-identity.ts +12 -0
- package/examples/full-example-json/scripts/dev-formula.ts +97 -0
- package/examples/full-example-json/scripts/formula-shared.test.ts +68 -0
- package/examples/full-example-json/scripts/formula-shared.ts +170 -0
- package/examples/full-example-json/scripts/print-identity.ts +28 -0
- package/examples/full-example-json/scripts/release.ts +212 -0
- package/examples/full-example-json/src/commands/echo/command.ts +26 -0
- package/examples/full-example-json/src/commands/status/command.test.ts +10 -0
- package/examples/full-example-json/src/commands/status/command.ts +28 -0
- package/examples/full-example-json/src/index.ts +10 -0
- package/examples/full-example-json/src/program.ts +33 -0
- package/examples/full-example-json/src/types/md.d.ts +4 -0
- package/examples/full-example-json/tsconfig.json +17 -0
- package/examples/minimal.ts +17 -17
- package/examples/nested.ts +10 -10
- package/examples/option-required.ts +13 -13
- package/examples/servers.ts +10 -10
- package/index.d.ts +17 -43
- package/package.json +1 -1
- package/src/cli-tool/create.test.ts +44 -68
- package/src/cli-tool/create.ts +81 -17
- package/src/cli-tool/full-example-capabilities.test.ts +33 -18
- package/src/cli-tool/post-create.ts +31 -17
- package/src/cli-tool/program.ts +16 -7
- package/src/cli-tool/prompt.ts +27 -0
- package/src/cli-tool/run-create.ts +19 -7
- package/src/cli-tool/schemagen/schemagen.test.ts +3 -3
- package/src/configure/artifacts/install-validate.test.ts +20 -33
- package/src/configure/artifacts/paths.ts +9 -53
- package/src/configure/artifacts/status.test.ts +13 -16
- package/src/configure/artifacts/status.ts +5 -22
- package/src/configure/artifacts/target-base.ts +6 -15
- package/src/configure/artifacts/target-effective.ts +16 -54
- package/src/configure/artifacts/target-mcp-json.ts +2 -5
- package/src/configure/artifacts/target-registry.ts +0 -7
- package/src/configure/artifacts/target-scope.ts +7 -17
- package/src/configure/artifacts/target-skill.ts +6 -15
- package/src/configure/artifacts/target-types.ts +6 -54
- package/src/configure/artifacts/targets/agents-mcp.ts +11 -0
- package/src/configure/artifacts/targets/configure.ts +1 -5
- package/src/configure/artifacts/targets/index.ts +4 -44
- package/src/configure/artifacts/targets/skill.ts +12 -0
- package/src/configure/artifacts/targets.test.ts +21 -59
- package/src/configure/configure.test.ts +35 -46
- package/src/configure/index.ts +19 -19
- package/src/configure/prompt.ts +2 -12
- package/src/core/parse.test.ts +21 -32
- package/src/core/types.ts +18 -44
- package/src/core/validate.ts +28 -45
- package/src/docs/docs.test.ts +4 -4
- package/src/docs/mcp-guide.ts +41 -71
- package/src/docs/resolve.ts +1 -1
- package/src/exports/cli.ts +1 -1
- package/src/index.ts +1 -1
- package/src/skill/generate.ts +26 -45
- package/src/skill/install.ts +18 -38
- package/src/skill/naming.ts +3 -27
- package/src/test/integration/config.test.ts +3 -3
- package/src/test/integration/mcp.test.ts +4 -4
- package/{examples/mcp-test.ts → src/test/mcp-integration-fixture.ts} +20 -22
- package/src/configure/artifacts/target-mcp-cli.ts +0 -127
- package/src/configure/artifacts/targets/chatgpt-mcp.ts +0 -12
- package/src/configure/artifacts/targets/claude-code-mcp.ts +0 -15
- package/src/configure/artifacts/targets/claude-desktop-mcp.ts +0 -12
- package/src/configure/artifacts/targets/claude-skill.ts +0 -16
- package/src/configure/artifacts/targets/codex-mcp.ts +0 -25
- package/src/configure/artifacts/targets/codex-skill.ts +0 -14
- package/src/configure/artifacts/targets/cursor-mcp.ts +0 -15
- package/src/configure/artifacts/targets/cursor-skill.ts +0 -16
- package/src/configure/artifacts/targets/openclaw-mcp.ts +0 -25
- package/src/configure/artifacts/targets/openclaw-skill.ts +0 -17
- package/src/configure/artifacts/targets/opencode-mcp.ts +0 -96
- package/src/configure/artifacts/targets/opencode-skill.ts +0 -15
- /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/RenderJsonInputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/command.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/command.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/render-json/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/StatusJsonOutputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/status/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/__generated__/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/command.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/commands/workspaces/types.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/index.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/index.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrate.test.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrate.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/db/migrations/001_workspaces.sql +0 -0
- /package/examples/{full-example → full-example-json}/src/db/tables/workspaces.ts +0 -0
- /package/examples/{full-example → full-example-json}/src/types/argsbarg.d.ts +0 -0
|
@@ -8,6 +8,7 @@ import { tmpdir } from "node:os";
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import {
|
|
10
10
|
applyCreate,
|
|
11
|
+
type CreateOptions,
|
|
11
12
|
classNameFromKey,
|
|
12
13
|
diffCreate,
|
|
13
14
|
diffCreateDetails,
|
|
@@ -16,25 +17,39 @@ import {
|
|
|
16
17
|
substituteTemplateContent,
|
|
17
18
|
} from "./create.ts";
|
|
18
19
|
|
|
20
|
+
function baseOpts(overrides: Partial<CreateOptions> = {}): CreateOptions {
|
|
21
|
+
return {
|
|
22
|
+
templateId: "cli",
|
|
23
|
+
key: "testapp",
|
|
24
|
+
className: "Testapp",
|
|
25
|
+
tap: "local/testapp",
|
|
26
|
+
homepage: "https://example.com",
|
|
27
|
+
releaseRepo: "example/testapp",
|
|
28
|
+
desc: "Test",
|
|
29
|
+
force: false,
|
|
30
|
+
dryRun: false,
|
|
31
|
+
check: false,
|
|
32
|
+
diff: false,
|
|
33
|
+
yes: false,
|
|
34
|
+
devTemplate: false,
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
/** Tests for argsbarg create. */
|
|
20
40
|
describe("argsbarg create", () => {
|
|
21
|
-
/** Tests that substitutes {key} tokens. */
|
|
22
41
|
test("substitutes {key} tokens", () => {
|
|
23
42
|
const out = substituteTemplateContent(
|
|
24
43
|
"key={key} class={className} env={envPrefix}_API_TOKEN tap={tap} org={tapOrg}",
|
|
25
44
|
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
check: false,
|
|
35
|
-
diff: false,
|
|
36
|
-
yes: false,
|
|
37
|
-
devTemplate: false,
|
|
45
|
+
...baseOpts({
|
|
46
|
+
key: "my-cli",
|
|
47
|
+
className: "MyCli",
|
|
48
|
+
tap: "org/my-cli",
|
|
49
|
+
homepage: "https://github.com/org/my-cli",
|
|
50
|
+
releaseRepo: "org/my-cli",
|
|
51
|
+
desc: "My CLI",
|
|
52
|
+
}),
|
|
38
53
|
},
|
|
39
54
|
);
|
|
40
55
|
expect(out).toContain("my-cli");
|
|
@@ -50,7 +65,6 @@ describe("argsbarg create", () => {
|
|
|
50
65
|
expect(classNameFromKey("1password")).toBe("App1password");
|
|
51
66
|
});
|
|
52
67
|
|
|
53
|
-
/** ResolveCreateOptions derives identity defaults from key. */
|
|
54
68
|
test("resolveCreateOptions derives identity defaults from key", () => {
|
|
55
69
|
expect(resolveCreateOptions({ key: "1password", releaseRepo: "org/1password" }).className).toBe("App1password");
|
|
56
70
|
expect(resolveCreateOptions({ key: "my-cli", className: "Custom", releaseRepo: "org/my-cli" }).className).toBe(
|
|
@@ -58,83 +72,45 @@ describe("argsbarg create", () => {
|
|
|
58
72
|
);
|
|
59
73
|
const opts = resolveCreateOptions({ key: "at1", releaseRepo: "bdombro/at1" });
|
|
60
74
|
expect(opts.tap).toBe("bdombro/at1");
|
|
61
|
-
expect(opts.
|
|
62
|
-
expect(opts.homepage).toBe("https://github.com/bdombro/at1");
|
|
63
|
-
expect(opts.desc).toBe("At1 CLI");
|
|
75
|
+
expect(opts.templateId).toBe("cli");
|
|
64
76
|
});
|
|
65
77
|
|
|
66
78
|
test("resolveCreateOptions requires release repo", () => {
|
|
67
79
|
expect(() => resolveCreateOptions({ key: "at1" })).toThrow(/release repo/i);
|
|
68
80
|
});
|
|
69
81
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const tree = renderCreateTree({
|
|
73
|
-
key: "testapp",
|
|
74
|
-
className: "Testapp",
|
|
75
|
-
tap: "local/testapp",
|
|
76
|
-
homepage: "https://example.com",
|
|
77
|
-
releaseRepo: "example/testapp",
|
|
78
|
-
desc: "Test",
|
|
79
|
-
force: false,
|
|
80
|
-
dryRun: false,
|
|
81
|
-
check: false,
|
|
82
|
-
diff: false,
|
|
83
|
-
yes: false,
|
|
84
|
-
devTemplate: false,
|
|
85
|
-
});
|
|
82
|
+
test("renderCreateTree includes justfile and create-identity for cli template", () => {
|
|
83
|
+
const tree = renderCreateTree(baseOpts({ key: "testapp" }));
|
|
86
84
|
expect(tree.has("justfile")).toBe(true);
|
|
87
|
-
expect(tree.has("biome.json")).toBe(true);
|
|
88
|
-
expect(tree.has(".cursor/rules/code.mdc")).toBe(true);
|
|
89
85
|
expect(tree.has("scripts/create-identity.ts")).toBe(true);
|
|
90
86
|
const identity = tree.get("scripts/create-identity.ts");
|
|
91
87
|
expect(identity).toContain('key: "testapp"');
|
|
92
|
-
|
|
93
|
-
expect(
|
|
88
|
+
expect(identity).toContain('template: "cli"');
|
|
89
|
+
expect(tree.has("src/commands/render-json/command.ts")).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("renderCreateTree json template includes schemagen commands", () => {
|
|
93
|
+
const tree = renderCreateTree(baseOpts({ templateId: "json", key: "testapp" }));
|
|
94
|
+
expect(tree.has("src/commands/render-json/command.ts")).toBe(true);
|
|
95
|
+
expect(tree.has("src/db/index.ts")).toBe(true);
|
|
96
|
+
const identity = tree.get("scripts/create-identity.ts");
|
|
97
|
+
expect(identity).toContain('template: "json"');
|
|
94
98
|
});
|
|
95
99
|
|
|
96
|
-
/** Tests that --check detects drift. */
|
|
97
100
|
test("--check detects drift", () => {
|
|
98
101
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
|
|
99
102
|
try {
|
|
100
|
-
applyCreate(dir, {
|
|
101
|
-
|
|
102
|
-
className: "Testapp",
|
|
103
|
-
tap: "local/testapp",
|
|
104
|
-
homepage: "https://example.com",
|
|
105
|
-
releaseRepo: "example/testapp",
|
|
106
|
-
desc: "Test",
|
|
107
|
-
force: true,
|
|
108
|
-
dryRun: false,
|
|
109
|
-
check: false,
|
|
110
|
-
diff: false,
|
|
111
|
-
yes: false,
|
|
112
|
-
devTemplate: false,
|
|
113
|
-
});
|
|
114
|
-
expect(diffCreate(dir, { key: "testapp", className: "Testapp" })).toEqual([]);
|
|
103
|
+
applyCreate(dir, baseOpts({ force: true }));
|
|
104
|
+
expect(diffCreate(dir, { key: "testapp", className: "Testapp", templateId: "cli" })).toEqual([]);
|
|
115
105
|
} finally {
|
|
116
106
|
rmSync(dir, { recursive: true, force: true });
|
|
117
107
|
}
|
|
118
108
|
});
|
|
119
109
|
|
|
120
|
-
/** Tests that --check infers options from create-identity.ts. */
|
|
121
110
|
test("--check infers options from create-identity.ts", () => {
|
|
122
111
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
|
|
123
112
|
try {
|
|
124
|
-
applyCreate(dir, {
|
|
125
|
-
key: "testapp",
|
|
126
|
-
className: "Testapp",
|
|
127
|
-
tap: "local/testapp",
|
|
128
|
-
homepage: "https://example.com",
|
|
129
|
-
releaseRepo: "example/testapp",
|
|
130
|
-
desc: "Test",
|
|
131
|
-
force: true,
|
|
132
|
-
dryRun: false,
|
|
133
|
-
check: false,
|
|
134
|
-
diff: false,
|
|
135
|
-
yes: false,
|
|
136
|
-
devTemplate: false,
|
|
137
|
-
});
|
|
113
|
+
applyCreate(dir, baseOpts({ force: true }));
|
|
138
114
|
expect(diffCreate(dir, { check: true })).toEqual([]);
|
|
139
115
|
} finally {
|
|
140
116
|
rmSync(dir, { recursive: true, force: true });
|
package/src/cli-tool/create.ts
CHANGED
|
@@ -1,11 +1,48 @@
|
|
|
1
|
-
/** `argsbarg create` — copy npm-shipped
|
|
1
|
+
/** `argsbarg create` — copy npm-shipped example templates with substitutions. */
|
|
2
2
|
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import pkg from "../../package.json" with { type: "json" };
|
|
7
7
|
|
|
8
|
+
export type CreateTemplateId = "cli" | "json";
|
|
9
|
+
|
|
10
|
+
export interface CreateTemplateSpec {
|
|
11
|
+
id: CreateTemplateId;
|
|
12
|
+
dirName: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const CREATE_TEMPLATES: CreateTemplateSpec[] = [
|
|
18
|
+
{
|
|
19
|
+
id: "cli",
|
|
20
|
+
dirName: "full-example",
|
|
21
|
+
displayName: "full-example",
|
|
22
|
+
description: "Production CLI with MCP, HTTP, configure, and skills. Options and flags only; no schemagen.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "json",
|
|
26
|
+
dirName: "full-example-json",
|
|
27
|
+
displayName: "full-example-json",
|
|
28
|
+
description: "Same shell plus @sg schemagen, input/outputSchema validation, JSON HTTP leaves, and REST CRUD demo.",
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function normalizeCreateTemplateId(value: string | undefined): CreateTemplateId {
|
|
33
|
+
return value === "json" ? "json" : "cli";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function templateDirFor(templateId: CreateTemplateId): string {
|
|
37
|
+
const spec = CREATE_TEMPLATES.find((t) => t.id === templateId);
|
|
38
|
+
if (!spec) {
|
|
39
|
+
throw new Error(`Unknown create template: ${templateId}`);
|
|
40
|
+
}
|
|
41
|
+
return join(packageRoot(), "examples", spec.dirName);
|
|
42
|
+
}
|
|
43
|
+
|
|
8
44
|
export interface CreateOptions {
|
|
45
|
+
templateId: CreateTemplateId;
|
|
9
46
|
key: string;
|
|
10
47
|
className: string;
|
|
11
48
|
tap: string;
|
|
@@ -29,12 +66,25 @@ export function packageRoot(): string {
|
|
|
29
66
|
return resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
30
67
|
}
|
|
31
68
|
|
|
32
|
-
export function templateDir(): string {
|
|
33
|
-
return
|
|
69
|
+
export function templateDir(templateId: CreateTemplateId = "cli"): string {
|
|
70
|
+
return templateDirFor(templateId);
|
|
34
71
|
}
|
|
35
72
|
|
|
36
|
-
export function
|
|
37
|
-
|
|
73
|
+
export function devTemplateIdForDir(baseDir: string): CreateTemplateId | undefined {
|
|
74
|
+
const resolved = resolve(baseDir);
|
|
75
|
+
for (const spec of CREATE_TEMPLATES) {
|
|
76
|
+
if (resolved === resolve(templateDirFor(spec.id))) {
|
|
77
|
+
return spec.id;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isDevTemplateDir(baseDir: string, templateId?: CreateTemplateId): boolean {
|
|
84
|
+
if (templateId) {
|
|
85
|
+
return resolve(baseDir) === resolve(templateDirFor(templateId));
|
|
86
|
+
}
|
|
87
|
+
return devTemplateIdForDir(baseDir) !== undefined;
|
|
38
88
|
}
|
|
39
89
|
|
|
40
90
|
export function keyToEnvPrefix(key: string): string {
|
|
@@ -70,10 +120,13 @@ function tapLibraryParts(tap: string): { org: string; repo: string } {
|
|
|
70
120
|
}
|
|
71
121
|
|
|
72
122
|
/** Parse `scripts/create-identity.ts` for inference and template defaults. */
|
|
73
|
-
export function parseCreateIdentityFile(
|
|
123
|
+
export function parseCreateIdentityFile(
|
|
124
|
+
path: string,
|
|
125
|
+
): Partial<CreateOptions> & { envPrefix?: string; template?: string } {
|
|
74
126
|
if (!existsSync(path)) return {};
|
|
75
127
|
const text = readFileSync(path, "utf8");
|
|
76
128
|
const pick = (field: string) => text.match(new RegExp(`${field}:\\s*"([^"]*)"`))?.[1];
|
|
129
|
+
const templateRaw = text.match(/template:\s*"(cli|json)"/)?.[1];
|
|
77
130
|
return {
|
|
78
131
|
key: pick("key"),
|
|
79
132
|
className: pick("className"),
|
|
@@ -82,10 +135,13 @@ export function parseCreateIdentityFile(path: string): Partial<CreateOptions> &
|
|
|
82
135
|
releaseRepo: pick("releaseRepo"),
|
|
83
136
|
desc: pick("desc"),
|
|
84
137
|
envPrefix: pick("envPrefix"),
|
|
138
|
+
template: templateRaw,
|
|
139
|
+
templateId: templateRaw ? normalizeCreateTemplateId(templateRaw) : undefined,
|
|
85
140
|
};
|
|
86
141
|
}
|
|
87
142
|
|
|
88
|
-
export function templateIdentity(): {
|
|
143
|
+
export function templateIdentity(templateId: CreateTemplateId = "cli"): {
|
|
144
|
+
templateId: CreateTemplateId;
|
|
89
145
|
key: string;
|
|
90
146
|
className: string;
|
|
91
147
|
tap: string;
|
|
@@ -94,15 +150,16 @@ export function templateIdentity(): {
|
|
|
94
150
|
desc: string;
|
|
95
151
|
envPrefix: string;
|
|
96
152
|
} {
|
|
97
|
-
const parsed = parseCreateIdentityFile(join(
|
|
98
|
-
const key = parsed.key ?? "full-example";
|
|
153
|
+
const parsed = parseCreateIdentityFile(join(templateDirFor(templateId), CREATE_IDENTITY_REL));
|
|
154
|
+
const key = parsed.key ?? CREATE_TEMPLATES.find((t) => t.id === templateId)?.displayName ?? "full-example";
|
|
99
155
|
return {
|
|
156
|
+
templateId,
|
|
100
157
|
key,
|
|
101
158
|
className: parsed.className ?? classNameFromKey(key),
|
|
102
159
|
tap: parsed.tap ?? `local/${key}`,
|
|
103
160
|
homepage: parsed.homepage ?? "https://github.com/bdombro/bun-argsbarg",
|
|
104
161
|
releaseRepo: parsed.releaseRepo ?? "bdombro/bun-argsbarg",
|
|
105
|
-
desc: parsed.desc ?? "Argsbarg
|
|
162
|
+
desc: parsed.desc ?? "Argsbarg copy template",
|
|
106
163
|
envPrefix: parsed.envPrefix ?? keyToEnvPrefix(key),
|
|
107
164
|
};
|
|
108
165
|
}
|
|
@@ -113,6 +170,7 @@ export function inferCreateOptions(baseDir: string, partial: Partial<CreateOptio
|
|
|
113
170
|
}
|
|
114
171
|
const fromIdentity = parseCreateIdentityFile(join(baseDir, CREATE_IDENTITY_REL));
|
|
115
172
|
return {
|
|
173
|
+
templateId: partial.templateId ?? fromIdentity.templateId,
|
|
116
174
|
key: partial.key ?? fromIdentity.key,
|
|
117
175
|
className: partial.className ?? fromIdentity.className,
|
|
118
176
|
desc: partial.desc ?? fromIdentity.desc,
|
|
@@ -136,7 +194,10 @@ function assertReleaseRepoFormat(releaseRepo: string): void {
|
|
|
136
194
|
|
|
137
195
|
export function resolveCreateOptions(partial: Partial<CreateOptions>, baseDir?: string): CreateOptions {
|
|
138
196
|
const merged = baseDir ? inferCreateOptions(baseDir, partial) : partial;
|
|
139
|
-
const
|
|
197
|
+
const templateId = normalizeCreateTemplateId(
|
|
198
|
+
merged.templateId ?? (baseDir ? devTemplateIdForDir(baseDir) : undefined),
|
|
199
|
+
);
|
|
200
|
+
const tmpl = templateIdentity(templateId);
|
|
140
201
|
const key = merged.key ?? tmpl.key ?? "full-example";
|
|
141
202
|
const className = merged.className ?? classNameFromKey(key);
|
|
142
203
|
const releaseRepo = merged.releaseRepo;
|
|
@@ -144,8 +205,9 @@ export function resolveCreateOptions(partial: Partial<CreateOptions>, baseDir?:
|
|
|
144
205
|
throw new Error("GitHub release repo (org/repo) is required. Pass --release-repo or use the interactive wizard.");
|
|
145
206
|
}
|
|
146
207
|
assertReleaseRepoFormat(releaseRepo);
|
|
147
|
-
const devTemplate = merged.devTemplate ?? (baseDir ? isDevTemplateDir(baseDir) : false);
|
|
208
|
+
const devTemplate = merged.devTemplate ?? (baseDir ? isDevTemplateDir(baseDir, templateId) : false);
|
|
148
209
|
return {
|
|
210
|
+
templateId,
|
|
149
211
|
key,
|
|
150
212
|
className,
|
|
151
213
|
tap: merged.tap ?? releaseRepo,
|
|
@@ -173,6 +235,7 @@ export const createIdentity = {
|
|
|
173
235
|
releaseRepo: "${opts.releaseRepo}",
|
|
174
236
|
desc: "${opts.desc}",
|
|
175
237
|
envPrefix: "${envPrefix}",
|
|
238
|
+
template: "${opts.templateId}",
|
|
176
239
|
} as const;
|
|
177
240
|
`;
|
|
178
241
|
}
|
|
@@ -195,7 +258,7 @@ function tokenMap(opts: CreateOptions): Record<string, string> {
|
|
|
195
258
|
|
|
196
259
|
/** Substitute \`{key}\`-style placeholders; also replace template identity literals. */
|
|
197
260
|
export function substituteTemplateContent(content: string, opts: CreateOptions): string {
|
|
198
|
-
const tmpl = templateIdentity();
|
|
261
|
+
const tmpl = templateIdentity(opts.templateId);
|
|
199
262
|
const tokens = tokenMap(opts);
|
|
200
263
|
const argsbargVersion = pkg.version;
|
|
201
264
|
|
|
@@ -245,8 +308,8 @@ function shouldExcludeRel(rel: string): boolean {
|
|
|
245
308
|
return EXCLUDE_REL.has(rel);
|
|
246
309
|
}
|
|
247
310
|
|
|
248
|
-
function listTemplateFiles(): string[] {
|
|
249
|
-
const root =
|
|
311
|
+
function listTemplateFiles(templateId: CreateTemplateId): string[] {
|
|
312
|
+
const root = templateDirFor(templateId);
|
|
250
313
|
if (!existsSync(root)) return [];
|
|
251
314
|
const out: string[] = [];
|
|
252
315
|
const walk = (dir: string, prefix: string) => {
|
|
@@ -266,12 +329,12 @@ function listTemplateFiles(): string[] {
|
|
|
266
329
|
|
|
267
330
|
export function renderCreateTree(opts: CreateOptions): Map<string, string> {
|
|
268
331
|
const files = new Map<string, string>();
|
|
269
|
-
for (const rel of listTemplateFiles()) {
|
|
332
|
+
for (const rel of listTemplateFiles(opts.templateId)) {
|
|
270
333
|
if (rel === CREATE_IDENTITY_REL) {
|
|
271
334
|
files.set(rel, renderCreateIdentitySource(opts));
|
|
272
335
|
continue;
|
|
273
336
|
}
|
|
274
|
-
const src = join(
|
|
337
|
+
const src = join(templateDirFor(opts.templateId), rel);
|
|
275
338
|
const raw = readFileSync(src, "utf8");
|
|
276
339
|
files.set(rel, substituteTemplateContent(raw, opts));
|
|
277
340
|
}
|
|
@@ -374,6 +437,7 @@ export function parseCreateArgv(rest: string[]): ParsedCreateArgv {
|
|
|
374
437
|
else if (a === "--homepage" && rest[i + 1]) opts.homepage = rest[++i];
|
|
375
438
|
else if (a === "--release-repo" && rest[i + 1]) opts.releaseRepo = rest[++i];
|
|
376
439
|
else if (a === "--desc" && rest[i + 1]) opts.desc = rest[++i];
|
|
440
|
+
else if (a === "--template" && rest[i + 1]) opts.templateId = normalizeCreateTemplateId(rest[++i]);
|
|
377
441
|
else if (a?.startsWith("--")) {
|
|
378
442
|
throw new Error(`Unknown option: ${a}`);
|
|
379
443
|
} else if (a) {
|
|
@@ -8,14 +8,16 @@ import { join } from "node:path";
|
|
|
8
8
|
import { CliOptionKind, type CliProgram } from "../core/types.ts";
|
|
9
9
|
import { resolveCapabilities } from "../runtime/capabilities.ts";
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const cliExampleRoot = join(import.meta.dir, "../../examples/full-example");
|
|
12
|
+
const jsonExampleRoot = join(import.meta.dir, "../../examples/full-example-json");
|
|
13
|
+
const cliProgramSource = readFileSync(join(cliExampleRoot, "src/program.ts"), "utf8");
|
|
14
|
+
const jsonProgramSource = readFileSync(join(jsonExampleRoot, "src/program.ts"), "utf8");
|
|
13
15
|
|
|
14
|
-
/** Mirror
|
|
16
|
+
/** Mirror capability flags for copy templates (in-repo types only). */
|
|
15
17
|
const sinkProgram = {
|
|
16
18
|
key: "full-example",
|
|
17
19
|
version: "1.0.0",
|
|
18
|
-
description: "
|
|
20
|
+
description: "Copy template reference.",
|
|
19
21
|
docs: {
|
|
20
22
|
topics: { readme: { text: "# readme\n" } },
|
|
21
23
|
},
|
|
@@ -44,25 +46,22 @@ const sinkProgram = {
|
|
|
44
46
|
],
|
|
45
47
|
} satisfies CliProgram;
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
describe("full-example template", () => {
|
|
49
|
-
/** Tests that program source enables every builtin flag. */
|
|
49
|
+
describe("full-example cli template", () => {
|
|
50
50
|
test("program source enables every builtin flag", () => {
|
|
51
|
-
expect(
|
|
52
|
-
expect(
|
|
53
|
-
expect(
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
51
|
+
expect(cliProgramSource).toContain("mcpServer: {");
|
|
52
|
+
expect(cliProgramSource).toContain("httpServer: {");
|
|
53
|
+
expect(cliProgramSource).toContain("docs:");
|
|
54
|
+
expect(cliProgramSource).not.toMatch(/docs:\s*\{[^}]*enabled:\s*true/s);
|
|
55
|
+
expect(cliProgramSource).not.toContain("appConfig:");
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
test("status command
|
|
59
|
-
const statusSource = readFileSync(join(
|
|
60
|
-
expect(statusSource).toMatch(/outputSchema[,:]/);
|
|
61
|
-
expect(statusSource).toContain("
|
|
62
|
-
expect(statusSource).toContain('from "./__generated__"');
|
|
58
|
+
test("status command has no outputSchema", () => {
|
|
59
|
+
const statusSource = readFileSync(join(cliExampleRoot, "src/commands/status/command.ts"), "utf8");
|
|
60
|
+
expect(statusSource).not.toMatch(/outputSchema[,:]/);
|
|
61
|
+
expect(statusSource).not.toContain("__generated__");
|
|
63
62
|
});
|
|
64
63
|
|
|
65
|
-
test("resolveCapabilities matches
|
|
64
|
+
test("resolveCapabilities matches sink shape", () => {
|
|
66
65
|
expect(resolveCapabilities(sinkProgram)).toEqual({
|
|
67
66
|
http: true,
|
|
68
67
|
completion: true,
|
|
@@ -73,3 +72,19 @@ describe("full-example template", () => {
|
|
|
73
72
|
});
|
|
74
73
|
});
|
|
75
74
|
});
|
|
75
|
+
|
|
76
|
+
describe("full-example-json template", () => {
|
|
77
|
+
test("program source enables every builtin flag", () => {
|
|
78
|
+
expect(jsonProgramSource).toContain("mcpServer: {");
|
|
79
|
+
expect(jsonProgramSource).toContain("httpServer: {");
|
|
80
|
+
expect(jsonProgramSource).toContain("docs:");
|
|
81
|
+
expect(jsonProgramSource).not.toContain("appConfig:");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("status command defines outputSchema", () => {
|
|
85
|
+
const statusSource = readFileSync(join(jsonExampleRoot, "src/commands/status/command.ts"), "utf8");
|
|
86
|
+
expect(statusSource).toMatch(/outputSchema[,:]/);
|
|
87
|
+
expect(statusSource).toContain("StatusJsonOutputSchema");
|
|
88
|
+
expect(statusSource).toContain('from "./__generated__"');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
+
import type { CreateTemplateId } from "./create.ts";
|
|
5
6
|
|
|
6
7
|
export function isInsideGitWorkTree(dir: string): boolean {
|
|
7
8
|
try {
|
|
@@ -25,7 +26,11 @@ export function shouldSkipGitBootstrap(targetDir: string): boolean {
|
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export async function runPostCreate(
|
|
29
|
+
export async function runPostCreate(
|
|
30
|
+
targetDir: string,
|
|
31
|
+
dryRun: boolean,
|
|
32
|
+
templateId: CreateTemplateId = "cli",
|
|
33
|
+
): Promise<void> {
|
|
29
34
|
const abs = resolve(targetDir);
|
|
30
35
|
const steps: Array<{ label: string; run: () => Promise<void> | void }> = [
|
|
31
36
|
{
|
|
@@ -40,18 +45,22 @@ export async function runPostCreate(targetDir: string, dryRun: boolean): Promise
|
|
|
40
45
|
if (proc.exitCode !== 0) throw new Error("bun install failed");
|
|
41
46
|
},
|
|
42
47
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
...(templateId === "json"
|
|
49
|
+
? [
|
|
50
|
+
{
|
|
51
|
+
label: "argsbarg schemagen",
|
|
52
|
+
run: () => {
|
|
53
|
+
if (dryRun) return;
|
|
54
|
+
const proc = Bun.spawnSync(["argsbarg", "schemagen"], {
|
|
55
|
+
cwd: abs,
|
|
56
|
+
stdout: "inherit",
|
|
57
|
+
stderr: "inherit",
|
|
58
|
+
});
|
|
59
|
+
if (proc.exitCode !== 0) throw new Error("schemagen failed");
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
]
|
|
63
|
+
: []),
|
|
55
64
|
{
|
|
56
65
|
label: "bun test",
|
|
57
66
|
run: () => {
|
|
@@ -100,10 +109,15 @@ export async function runPostCreate(targetDir: string, dryRun: boolean): Promise
|
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
export function printPostCreatePlan(): void {
|
|
112
|
+
export function printPostCreatePlan(templateId: CreateTemplateId = "cli"): void {
|
|
104
113
|
process.stderr.write("Post-create steps:\n");
|
|
105
114
|
process.stderr.write(" 1. bun install\n");
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
if (templateId === "json") {
|
|
116
|
+
process.stderr.write(" 2. argsbarg schemagen\n");
|
|
117
|
+
process.stderr.write(" 3. bun test\n");
|
|
118
|
+
process.stderr.write(" 4. git init + Initial commit (skipped inside existing git work tree)\n");
|
|
119
|
+
} else {
|
|
120
|
+
process.stderr.write(" 2. bun test\n");
|
|
121
|
+
process.stderr.write(" 3. git init + Initial commit (skipped inside existing git work tree)\n");
|
|
122
|
+
}
|
|
109
123
|
}
|
package/src/cli-tool/program.ts
CHANGED
|
@@ -4,21 +4,22 @@ Argsbarg developer tools — bootstrap consumer CLIs via `create`.
|
|
|
4
4
|
|
|
5
5
|
import pkg from "../../package.json" with { type: "json" };
|
|
6
6
|
import { CliOptionKind, type CliProgram } from "../index.ts";
|
|
7
|
+
import { normalizeCreateTemplateId } from "./create.ts";
|
|
7
8
|
import { runCreate } from "./run-create.ts";
|
|
8
9
|
import { runSchemagenCli } from "./run-schemagen.ts";
|
|
9
10
|
|
|
10
11
|
export const program = {
|
|
11
|
-
key: "argsbarg",
|
|
12
|
-
version: pkg.version,
|
|
13
|
-
description: "Argsbarg developer tools — bootstrap CLIs from the full-example template.",
|
|
14
|
-
completion: { enabled: false },
|
|
15
|
-
configure: { enabled: false },
|
|
16
|
-
docs: { enabled: false },
|
|
17
12
|
commands: [
|
|
18
13
|
{
|
|
19
14
|
key: "create",
|
|
20
|
-
description: "Copy
|
|
15
|
+
description: "Copy a CLI template into a directory with substitutions.",
|
|
21
16
|
options: [
|
|
17
|
+
{
|
|
18
|
+
name: "template",
|
|
19
|
+
description: "Template: cli (default) or json (schema-first).",
|
|
20
|
+
kind: CliOptionKind.Enum,
|
|
21
|
+
choices: ["cli", "json"],
|
|
22
|
+
},
|
|
22
23
|
{ name: "key", description: "CLI binary name.", kind: CliOptionKind.String },
|
|
23
24
|
{
|
|
24
25
|
name: "class-name",
|
|
@@ -77,8 +78,10 @@ export const program = {
|
|
|
77
78
|
},
|
|
78
79
|
],
|
|
79
80
|
handler: async (ctx) => {
|
|
81
|
+
const templateRaw = ctx.stringOpt("template");
|
|
80
82
|
const code = await runCreate({
|
|
81
83
|
dir: ctx.args[0],
|
|
84
|
+
...(templateRaw !== undefined ? { templateId: normalizeCreateTemplateId(templateRaw) } : {}),
|
|
82
85
|
key: ctx.stringOpt("key"),
|
|
83
86
|
className: ctx.stringOpt("class-name"),
|
|
84
87
|
tap: ctx.stringOpt("tap"),
|
|
@@ -130,4 +133,10 @@ export const program = {
|
|
|
130
133
|
},
|
|
131
134
|
},
|
|
132
135
|
],
|
|
136
|
+
completion: { enabled: false },
|
|
137
|
+
configure: { enabled: false },
|
|
138
|
+
description: "Argsbarg developer tools — bootstrap CLIs from copy templates.",
|
|
139
|
+
docs: { enabled: false },
|
|
140
|
+
key: "argsbarg",
|
|
141
|
+
version: pkg.version,
|
|
133
142
|
} satisfies CliProgram;
|
package/src/cli-tool/prompt.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Interactive prompts for `argsbarg create`. */
|
|
2
2
|
|
|
3
3
|
import { readPromptLine as readStdinLine } from "../prompt.ts";
|
|
4
|
+
import { CREATE_TEMPLATES, type CreateTemplateId, normalizeCreateTemplateId } from "./create.ts";
|
|
4
5
|
|
|
5
6
|
export function readPromptLine(prompt: string): string {
|
|
6
7
|
process.stderr.write(prompt);
|
|
@@ -26,3 +27,29 @@ export function promptRequired(label: string, current?: string): string {
|
|
|
26
27
|
process.stderr.write(" (required)\n");
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
/** A/B picker for create template (returns cli or json). */
|
|
32
|
+
export function promptTemplateChoice(): CreateTemplateId {
|
|
33
|
+
const cli = CREATE_TEMPLATES.find((t) => t.id === "cli");
|
|
34
|
+
const json = CREATE_TEMPLATES.find((t) => t.id === "json");
|
|
35
|
+
if (!cli || !json) {
|
|
36
|
+
throw new Error("CREATE_TEMPLATES must include cli and json entries");
|
|
37
|
+
}
|
|
38
|
+
process.stderr.write("\nSelect a template:\n\n");
|
|
39
|
+
process.stderr.write(`A) ${cli.displayName} — ${cli.description}\n`);
|
|
40
|
+
process.stderr.write(`B) ${json.displayName} — ${json.description}\n\n`);
|
|
41
|
+
while (true) {
|
|
42
|
+
const ans = readPromptLine("Choice: ").toLowerCase();
|
|
43
|
+
if (ans === "a") return "cli";
|
|
44
|
+
if (ans === "b") return "json";
|
|
45
|
+
process.stderr.write(" Enter A or B.\n");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseTemplateChoiceInput(value: string | undefined): CreateTemplateId | undefined {
|
|
50
|
+
if (!value) return undefined;
|
|
51
|
+
const normalized = value.trim().toLowerCase();
|
|
52
|
+
if (normalized === "a" || normalized === "cli") return "cli";
|
|
53
|
+
if (normalized === "b" || normalized === "json") return "json";
|
|
54
|
+
return normalizeCreateTemplateId(value);
|
|
55
|
+
}
|