argsbarg 5.0.1 → 5.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/CHANGELOG.md +8 -1
- package/docs/distribution-homebrew.md +2 -0
- package/examples/full-example/scripts/formula-shared.ts +1 -0
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +23 -0
- package/src/builtins/config.test.ts +8 -0
- package/src/cli-tool/cli-smoke.test.ts +8 -0
- package/src/cli-tool/create.test.ts +13 -0
- package/src/cli-tool/full-example-capabilities.test.ts +8 -0
- package/src/cli-tool/main.ts +0 -0
- package/src/config/context.test.ts +8 -0
- package/src/config/file.test.ts +12 -0
- package/src/config/resolve.test.ts +18 -0
- package/src/config/validate.test.ts +11 -0
- package/src/config.integration.test.ts +5 -0
- package/src/configure/configure.test.ts +22 -0
- package/src/configure/index.ts +14 -0
- package/src/configure/prompt.ts +7 -0
- package/src/docs/api-guide.test.ts +10 -0
- package/src/docs/docs.test.ts +27 -0
- package/src/docs/mcp-resources.test.ts +10 -0
- package/src/formats.test.ts +9 -0
- package/src/headless.test.ts +10 -0
- package/src/hidden-mcpb.test.ts +22 -0
- package/src/install/binary-placement.test.ts +14 -0
- package/src/install/gh-release-update.test.ts +9 -0
- package/src/install/install-validate.test.ts +10 -0
- package/src/install/mcp-codex.test.ts +7 -0
- package/src/install/mcp-openclaw.test.ts +6 -0
- package/src/install/mcp-opencode.test.ts +10 -0
- package/src/install/status.test.ts +9 -0
- package/src/install/targets.test.ts +19 -0
- package/src/invoke.test.ts +14 -0
- package/src/mcp/claude.test.ts +10 -0
- package/src/mcp/env.test.ts +12 -0
- package/src/mcp/zip.test.ts +5 -0
- package/src/mcp.integration.test.ts +39 -0
- package/src/parse.test.ts +71 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.0.2] - 2026-07-04
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **JSDoc style** — every `describe` and `test` block in `src/**/*.test.ts` now has a human-readable JSDoc; `configure` modules brought in line with file headers and symbol docs.
|
|
15
|
+
|
|
10
16
|
## [5.0.1] - 2026-07-04
|
|
11
17
|
|
|
12
18
|
|
|
@@ -567,7 +573,8 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
567
573
|
- 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`).
|
|
568
574
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
569
575
|
|
|
570
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.0.
|
|
576
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v5.0.2...HEAD
|
|
577
|
+
[5.0.2]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.2
|
|
571
578
|
[5.0.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.1
|
|
572
579
|
[5.0.0]: https://github.com/bdombro/bun-argsbarg/releases/tag/v5.0.0
|
|
573
580
|
[4.1.1]: https://github.com/bdombro/bun-argsbarg/releases/tag/v4.1.1
|
|
@@ -60,6 +60,8 @@ Completions require users to configure their shell per [Homebrew Shell Completio
|
|
|
60
60
|
|
|
61
61
|
**Why configure is separate from `post_install`:** the wizard is interactive (TTY + prompts for secrets). Formula `post_install` runs non-interactively during `brew install` and in CI (`brew test`). Apps with `appConfig` print a one-line configure hint in formula `caveats` instead.
|
|
62
62
|
|
|
63
|
+
**MCP hosts:** when `mcpServer.enabled` is true, add a caveats line that chat apps (Cursor, Claude Desktop, etc.) must be **restarted** after `brew install` / `brew upgrade` — `post_install` updates MCP config on disk, but hosts typically load it only at startup.
|
|
64
|
+
|
|
63
65
|
## Bootstrap CLI (`argsbarg create`)
|
|
64
66
|
|
|
65
67
|
Copy the shipped `examples/full-example` template into a new directory with identity substitutions, then run install, schemagen, tests, and git init (when appropriate):
|
|
@@ -17,6 +17,7 @@ export const formulaPostInstallRuby = `def post_install
|
|
|
17
17
|
export const formulaCaveatsRuby = `def caveats
|
|
18
18
|
<<~EOS
|
|
19
19
|
Run \`${key} configure\` to set up agent artifacts and app config (interactive).
|
|
20
|
+
Restart MCP chat apps (Cursor, Claude Desktop, etc.) after install or upgrade so they load the updated server.
|
|
20
21
|
EOS
|
|
21
22
|
end`;
|
|
22
23
|
|
package/package.json
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for builtins/builtins module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
3
7
|
import { cliBuiltinDocsGroup } from "../docs/builtin.ts";
|
|
@@ -31,7 +35,9 @@ const noMcp: CliProgram = {
|
|
|
31
35
|
commands: [{ key: "ping", description: "Ping.", handler: () => {} }],
|
|
32
36
|
};
|
|
33
37
|
|
|
38
|
+
/** Tests for builtins help copy. */
|
|
34
39
|
describe("builtins help copy", () => {
|
|
40
|
+
/** Configure command includes Homebrew-oriented description. */
|
|
35
41
|
test("configure command includes Homebrew-oriented description", () => {
|
|
36
42
|
const configure = cliBuiltinConfigureCommand(fixture);
|
|
37
43
|
expect(configure.description).toContain("agent skills");
|
|
@@ -45,6 +51,7 @@ describe("builtins help copy", () => {
|
|
|
45
51
|
expect(yesOpt?.shortName).toBe("y");
|
|
46
52
|
});
|
|
47
53
|
|
|
54
|
+
/** Configure copy omits MCP when mcpServer unset. */
|
|
48
55
|
test("configure copy omits MCP when mcpServer unset", () => {
|
|
49
56
|
const caps = resolveCapabilities(noMcp);
|
|
50
57
|
expect(configureCommandDescription(noMcp, caps)).toBe(
|
|
@@ -56,6 +63,7 @@ describe("builtins help copy", () => {
|
|
|
56
63
|
expect(configure.description).not.toContain("MCP");
|
|
57
64
|
});
|
|
58
65
|
|
|
66
|
+
/** Configure notes mention brew upgrade and interactive configure. */
|
|
59
67
|
test("configure notes mention brew upgrade and interactive configure", () => {
|
|
60
68
|
const configure = cliBuiltinConfigureCommand(fixture);
|
|
61
69
|
expect(configure.notes).toContain("brew upgrade");
|
|
@@ -71,6 +79,7 @@ describe("builtins help copy", () => {
|
|
|
71
79
|
expect(configureBuiltinOptions(withConfig).map((o) => o.name)).toContain("remove-config");
|
|
72
80
|
});
|
|
73
81
|
|
|
82
|
+
/** Configure -y parses as --yes. */
|
|
74
83
|
test("configure -y parses as --yes", () => {
|
|
75
84
|
const root = cliParseRoot(fixture);
|
|
76
85
|
const pr = postParseValidate(root, parse(root, ["configure", "-y", "--sync"]));
|
|
@@ -81,6 +90,7 @@ describe("builtins help copy", () => {
|
|
|
81
90
|
}
|
|
82
91
|
});
|
|
83
92
|
|
|
93
|
+
/** Mcp builtin description is user-facing. */
|
|
84
94
|
test("mcp builtin description is user-facing", () => {
|
|
85
95
|
const withDocs: CliProgram = {
|
|
86
96
|
...fixture,
|
|
@@ -93,7 +103,9 @@ describe("builtins help copy", () => {
|
|
|
93
103
|
});
|
|
94
104
|
});
|
|
95
105
|
|
|
106
|
+
/** Tests for presentation root. */
|
|
96
107
|
describe("presentation root", () => {
|
|
108
|
+
/** Includes mcp and configure when enabled. */
|
|
97
109
|
test("includes mcp and configure when enabled", () => {
|
|
98
110
|
const root = cliPresentationRoot(fixture);
|
|
99
111
|
const keys = root.commands?.map((c) => c.key) ?? [];
|
|
@@ -103,19 +115,23 @@ describe("presentation root", () => {
|
|
|
103
115
|
expect(keys).not.toContain("install");
|
|
104
116
|
});
|
|
105
117
|
|
|
118
|
+
/** Omits configure when configure.enabled is false. */
|
|
106
119
|
test("omits configure when configure.enabled is false", () => {
|
|
107
120
|
const disabled: CliProgram = { ...fixture, configure: { enabled: false } };
|
|
108
121
|
const root = cliPresentationRoot(disabled);
|
|
109
122
|
expect(root.commands?.map((c) => c.key)).not.toContain("configure");
|
|
110
123
|
});
|
|
111
124
|
|
|
125
|
+
/** Includes version builtin. */
|
|
112
126
|
test("includes version builtin", () => {
|
|
113
127
|
const root = cliPresentationRoot(fixture);
|
|
114
128
|
expect(root.commands?.map((c) => c.key)).toContain("version");
|
|
115
129
|
});
|
|
116
130
|
});
|
|
117
131
|
|
|
132
|
+
/** Tests for completion emitters. */
|
|
118
133
|
describe("completion emitters", () => {
|
|
134
|
+
/** Tests that fish script references app key and subcommands. */
|
|
119
135
|
test("fish script references app key and subcommands", () => {
|
|
120
136
|
const schema = cliPresentationRoot(fixture);
|
|
121
137
|
const fish = completionFishScript(schema);
|
|
@@ -124,6 +140,7 @@ describe("completion emitters", () => {
|
|
|
124
140
|
expect(fish).toContain("configure");
|
|
125
141
|
});
|
|
126
142
|
|
|
143
|
+
/** Tests that bash script includes configure flags. */
|
|
127
144
|
test("bash script includes configure flags", () => {
|
|
128
145
|
const schema = cliPresentationRoot(fixture);
|
|
129
146
|
const bash = completionBashScript(schema);
|
|
@@ -131,6 +148,7 @@ describe("completion emitters", () => {
|
|
|
131
148
|
expect(bash).toContain("--sync");
|
|
132
149
|
});
|
|
133
150
|
|
|
151
|
+
/** Tests that zsh script registers compdef. */
|
|
134
152
|
test("zsh script registers compdef", () => {
|
|
135
153
|
const schema = cliPresentationRoot({
|
|
136
154
|
key: "zapp",
|
|
@@ -144,7 +162,9 @@ describe("completion emitters", () => {
|
|
|
144
162
|
});
|
|
145
163
|
});
|
|
146
164
|
|
|
165
|
+
/** Tests for schema export builtins. */
|
|
147
166
|
describe("schema export builtins", () => {
|
|
167
|
+
/** ExportPresentationBuiltins includes config when appConfig set. */
|
|
148
168
|
test("exportPresentationBuiltins includes config when appConfig set", () => {
|
|
149
169
|
const withConfig: CliProgram = {
|
|
150
170
|
...fixture,
|
|
@@ -159,13 +179,16 @@ describe("schema export builtins", () => {
|
|
|
159
179
|
expect(builtins.map((b) => b.key)).toContain("configure");
|
|
160
180
|
});
|
|
161
181
|
|
|
182
|
+
/** ExportPresentationBuiltins omits hidden completion. */
|
|
162
183
|
test("exportPresentationBuiltins omits hidden completion", () => {
|
|
163
184
|
const builtins = exportPresentationBuiltins(fixture);
|
|
164
185
|
expect(builtins.map((b) => b.key)).not.toContain("completion");
|
|
165
186
|
});
|
|
166
187
|
});
|
|
167
188
|
|
|
189
|
+
/** Tests for docs skill topic copy. */
|
|
168
190
|
describe("docs skill topic copy", () => {
|
|
191
|
+
/** Tests that mentions configure when configure is enabled. */
|
|
169
192
|
test("mentions configure when configure is enabled", () => {
|
|
170
193
|
const withDocs: CliProgram = {
|
|
171
194
|
...noMcp,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for builtins/config module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -20,7 +24,9 @@ function configFixture(): CliProgram {
|
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
/** Tests for builtins/config. */
|
|
23
28
|
describe("builtins/config", () => {
|
|
29
|
+
/** Tests that config get redacts sensitive values. */
|
|
24
30
|
test("config get redacts sensitive values", async () => {
|
|
25
31
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
26
32
|
const prevHome = process.env.HOME;
|
|
@@ -43,6 +49,7 @@ describe("builtins/config", () => {
|
|
|
43
49
|
}
|
|
44
50
|
});
|
|
45
51
|
|
|
52
|
+
/** Tests that config get --json redacts sensitive as { set: true }. */
|
|
46
53
|
test("config get --json redacts sensitive as { set: true }", async () => {
|
|
47
54
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
48
55
|
const prevHome = process.env.HOME;
|
|
@@ -65,6 +72,7 @@ describe("builtins/config", () => {
|
|
|
65
72
|
}
|
|
66
73
|
});
|
|
67
74
|
|
|
75
|
+
/** Tests that config set writes and resolves without required exit. */
|
|
68
76
|
test("config set writes and resolves without required exit", async () => {
|
|
69
77
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
70
78
|
const prevHome = process.env.HOME;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for cli-tool/cli-smoke module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { spawnSync } from "node:child_process";
|
|
3
7
|
import { join } from "node:path";
|
|
4
8
|
|
|
5
9
|
const main = join(import.meta.dir, "main.ts");
|
|
6
10
|
|
|
11
|
+
/** Tests for argsbarg cli-tool. */
|
|
7
12
|
describe("argsbarg cli-tool", () => {
|
|
13
|
+
/** Tests that version subcommand prints version. */
|
|
8
14
|
test("version subcommand prints version", () => {
|
|
9
15
|
const proc = spawnSync("bun", [main, "version"], { encoding: "utf8" });
|
|
10
16
|
expect(proc.status).toBe(0);
|
|
11
17
|
expect(proc.stdout.trim().length).toBeGreaterThan(0);
|
|
12
18
|
});
|
|
13
19
|
|
|
20
|
+
/** Tests that help lists create and version only (no install, completion, mcp). */
|
|
14
21
|
test("help lists create and version only (no install, completion, mcp)", () => {
|
|
15
22
|
const proc = spawnSync("bun", [main, "--help"], { encoding: "utf8" });
|
|
16
23
|
expect(proc.status).toBe(0);
|
|
@@ -21,6 +28,7 @@ describe("argsbarg cli-tool", () => {
|
|
|
21
28
|
expect(proc.stdout).not.toContain("mcp");
|
|
22
29
|
});
|
|
23
30
|
|
|
31
|
+
/** Completion subcommand is disabled. */
|
|
24
32
|
test("completion subcommand is disabled", () => {
|
|
25
33
|
const proc = spawnSync("bun", [main, "completion", "bash"], { encoding: "utf8" });
|
|
26
34
|
expect(proc.status).toBe(1);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for cli-tool/create module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -12,7 +16,9 @@ import {
|
|
|
12
16
|
substituteTemplateContent,
|
|
13
17
|
} from "./create.ts";
|
|
14
18
|
|
|
19
|
+
/** Tests for argsbarg create. */
|
|
15
20
|
describe("argsbarg create", () => {
|
|
21
|
+
/** Tests that substitutes {key} tokens. */
|
|
16
22
|
test("substitutes {key} tokens", () => {
|
|
17
23
|
const out = substituteTemplateContent(
|
|
18
24
|
"key={key} class={className} env={envPrefix}_API_TOKEN tap={tap} org={tapOrg}",
|
|
@@ -38,12 +44,14 @@ describe("argsbarg create", () => {
|
|
|
38
44
|
expect(out).not.toContain("{key}");
|
|
39
45
|
});
|
|
40
46
|
|
|
47
|
+
/** Tests that classNameFromKey. */
|
|
41
48
|
test("classNameFromKey", () => {
|
|
42
49
|
expect(classNameFromKey("sqsp-i18n")).toBe("SqspI18n");
|
|
43
50
|
expect(classNameFromKey("at1")).toBe("At1");
|
|
44
51
|
expect(classNameFromKey("1password")).toBe("App1password");
|
|
45
52
|
});
|
|
46
53
|
|
|
54
|
+
/** ResolveCreateOptions derives identity defaults from key. */
|
|
47
55
|
test("resolveCreateOptions derives identity defaults from key", () => {
|
|
48
56
|
expect(resolveCreateOptions({ key: "1password", releaseRepo: "org/1password" }).className).toBe(
|
|
49
57
|
"App1password",
|
|
@@ -59,10 +67,12 @@ describe("argsbarg create", () => {
|
|
|
59
67
|
expect(opts.desc).toBe("At1 CLI");
|
|
60
68
|
});
|
|
61
69
|
|
|
70
|
+
/** ResolveCreateOptions requires release repo. */
|
|
62
71
|
test("resolveCreateOptions requires release repo", () => {
|
|
63
72
|
expect(() => resolveCreateOptions({ key: "at1" })).toThrow(/release repo/i);
|
|
64
73
|
});
|
|
65
74
|
|
|
75
|
+
/** Tests that renderCreateTree includes justfile and create-identity. */
|
|
66
76
|
test("renderCreateTree includes justfile and create-identity", () => {
|
|
67
77
|
const tree = renderCreateTree({
|
|
68
78
|
key: "testapp",
|
|
@@ -88,6 +98,7 @@ describe("argsbarg create", () => {
|
|
|
88
98
|
expect(formula).toContain("create-identity.ts");
|
|
89
99
|
});
|
|
90
100
|
|
|
101
|
+
/** Tests that --check detects drift. */
|
|
91
102
|
test("--check detects drift", () => {
|
|
92
103
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
|
|
93
104
|
try {
|
|
@@ -111,6 +122,7 @@ describe("argsbarg create", () => {
|
|
|
111
122
|
}
|
|
112
123
|
});
|
|
113
124
|
|
|
125
|
+
/** Tests that --check infers options from create-identity.ts. */
|
|
114
126
|
test("--check infers options from create-identity.ts", () => {
|
|
115
127
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-create-"));
|
|
116
128
|
try {
|
|
@@ -134,6 +146,7 @@ describe("argsbarg create", () => {
|
|
|
134
146
|
}
|
|
135
147
|
});
|
|
136
148
|
|
|
149
|
+
/** Tests that --diff captures drift details. */
|
|
137
150
|
test("--diff captures drift details", () => {
|
|
138
151
|
const drifts = diffCreateDetails("/nonexistent", { key: "x", releaseRepo: "org/x" });
|
|
139
152
|
expect(drifts.length).toBeGreaterThan(0);
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for cli-tool/full-example-capabilities module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { readFileSync } from "node:fs";
|
|
3
7
|
import { join } from "node:path";
|
|
@@ -45,7 +49,9 @@ const sinkProgram = {
|
|
|
45
49
|
],
|
|
46
50
|
} satisfies CliProgram;
|
|
47
51
|
|
|
52
|
+
/** Tests for full-example template. */
|
|
48
53
|
describe("full-example template", () => {
|
|
54
|
+
/** Tests that program source enables every builtin flag. */
|
|
49
55
|
test("program source enables every builtin flag", () => {
|
|
50
56
|
expect(programSource).toContain("mcpServer: {");
|
|
51
57
|
expect(programSource).toContain("enabled: true");
|
|
@@ -53,11 +59,13 @@ describe("full-example template", () => {
|
|
|
53
59
|
expect(programSource).toContain("appConfig:");
|
|
54
60
|
});
|
|
55
61
|
|
|
62
|
+
/** Status command defines outputSchema. */
|
|
56
63
|
test("status command defines outputSchema", () => {
|
|
57
64
|
const statusSource = readFileSync(join(exampleRoot, "src/commands/status/command.ts"), "utf8");
|
|
58
65
|
expect(statusSource).toContain("outputSchema:");
|
|
59
66
|
});
|
|
60
67
|
|
|
68
|
+
/** ResolveCapabilities matches full sink shape. */
|
|
61
69
|
test("resolveCapabilities matches full sink shape", () => {
|
|
62
70
|
expect(resolveCapabilities(sinkProgram)).toEqual({
|
|
63
71
|
completion: true,
|
package/src/cli-tool/main.ts
CHANGED
|
File without changes
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/context module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -20,7 +24,9 @@ const program: CliProgram = {
|
|
|
20
24
|
handler: () => {},
|
|
21
25
|
};
|
|
22
26
|
|
|
27
|
+
/** Tests for config/context. */
|
|
23
28
|
describe("config/context", () => {
|
|
29
|
+
/** Tests that AppConfigSnapshot get, require, read, set. */
|
|
24
30
|
test("AppConfigSnapshot get, require, read, set", () => {
|
|
25
31
|
const dir = mkdtempSync(join(tmpdir(), "ctx-test-"));
|
|
26
32
|
const prevHome = process.env.HOME;
|
|
@@ -50,6 +56,7 @@ describe("config/context", () => {
|
|
|
50
56
|
}
|
|
51
57
|
});
|
|
52
58
|
|
|
59
|
+
/** Tests that EmptyAppConfigSnapshot when program.appConfig unset. */
|
|
53
60
|
test("EmptyAppConfigSnapshot when program.appConfig unset", () => {
|
|
54
61
|
const programWithoutConfig: CliProgram = {
|
|
55
62
|
key: "x",
|
|
@@ -65,6 +72,7 @@ describe("config/context", () => {
|
|
|
65
72
|
expect(empty.dir).toBe(dirname(empty.path));
|
|
66
73
|
});
|
|
67
74
|
|
|
75
|
+
/** Tests that AppConfigSnapshot path uses OS default from program key. */
|
|
68
76
|
test("AppConfigSnapshot path uses OS default from program key", () => {
|
|
69
77
|
const ctx = createAppConfigSnapshot(program, {}, {});
|
|
70
78
|
expect(ctx.path).toContain("ctx_test");
|
package/src/config/file.test.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/file module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
7
|
import { tmpdir } from "node:os";
|
|
@@ -40,7 +44,9 @@ function withHome<T>(fn: (home: string) => T): T {
|
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
/** Tests for config/file. */
|
|
43
48
|
describe("config/file", () => {
|
|
49
|
+
/** BuildProgramUserConfig from program.appConfig env entries. */
|
|
44
50
|
test("buildProgramUserConfig from program.appConfig env entries", () => {
|
|
45
51
|
const cfg = buildProgramUserConfig(program);
|
|
46
52
|
expect(cfg?.api_token).toMatchObject({
|
|
@@ -51,6 +57,7 @@ describe("config/file", () => {
|
|
|
51
57
|
expect(cfg?.port).toBeUndefined();
|
|
52
58
|
});
|
|
53
59
|
|
|
60
|
+
/** ResolveAppConfigPath uses config.json. */
|
|
54
61
|
test("resolveAppConfigPath uses config.json", () => {
|
|
55
62
|
withHome((home) => {
|
|
56
63
|
expect(resolveAppConfigPath(program)).toBe(
|
|
@@ -59,6 +66,7 @@ describe("config/file", () => {
|
|
|
59
66
|
});
|
|
60
67
|
});
|
|
61
68
|
|
|
69
|
+
/** ResolveAppConfig prefers host env over file. */
|
|
62
70
|
test("resolveAppConfig prefers host env over file", () => {
|
|
63
71
|
withHome((_home) => {
|
|
64
72
|
const prevToken = process.env.API_TOKEN;
|
|
@@ -76,6 +84,7 @@ describe("config/file", () => {
|
|
|
76
84
|
});
|
|
77
85
|
});
|
|
78
86
|
|
|
87
|
+
/** MissingRequiredConfig and formatMissingConfigMessage. */
|
|
79
88
|
test("missingRequiredConfig and formatMissingConfigMessage", () => {
|
|
80
89
|
const prev = process.env.API_TOKEN;
|
|
81
90
|
delete process.env.API_TOKEN;
|
|
@@ -91,6 +100,7 @@ describe("config/file", () => {
|
|
|
91
100
|
}
|
|
92
101
|
});
|
|
93
102
|
|
|
103
|
+
/** Rejects unknown keys on read. */
|
|
94
104
|
test("rejects unknown keys on read", () => {
|
|
95
105
|
withHome(() => {
|
|
96
106
|
const configPath = resolveAppConfigPath(program);
|
|
@@ -100,6 +110,7 @@ describe("config/file", () => {
|
|
|
100
110
|
});
|
|
101
111
|
});
|
|
102
112
|
|
|
113
|
+
/** WriteAppConfigFile round-trip. */
|
|
103
114
|
test("writeAppConfigFile round-trip", () => {
|
|
104
115
|
withHome(() => {
|
|
105
116
|
writeAppConfigFile(program, { apiToken: "saved" });
|
|
@@ -108,6 +119,7 @@ describe("config/file", () => {
|
|
|
108
119
|
});
|
|
109
120
|
});
|
|
110
121
|
|
|
122
|
+
/** Tests that uninstallAppConfig removes config directory recursively. */
|
|
111
123
|
test("uninstallAppConfig removes config directory recursively", () => {
|
|
112
124
|
withHome(() => {
|
|
113
125
|
writeAppConfigFile(program, { apiToken: "saved" });
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/resolve module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import type { CliProgram } from "../types.ts";
|
|
3
7
|
import { captureMappedHostEnv, exportConfigToEnv, resolveAppConfig } from "./resolve.ts";
|
|
@@ -25,7 +29,9 @@ const program: CliProgram = {
|
|
|
25
29
|
handler: () => {},
|
|
26
30
|
};
|
|
27
31
|
|
|
32
|
+
/** Tests for config/resolve. */
|
|
28
33
|
describe("config/resolve", () => {
|
|
34
|
+
/** Tests that prefers env over file for mapped keys. */
|
|
29
35
|
test("prefers env over file for mapped keys", () => {
|
|
30
36
|
const prev = process.env.API_TOKEN;
|
|
31
37
|
process.env.API_TOKEN = "from-env";
|
|
@@ -38,6 +44,7 @@ describe("config/resolve", () => {
|
|
|
38
44
|
}
|
|
39
45
|
});
|
|
40
46
|
|
|
47
|
+
/** Tests that uses file when env empty. */
|
|
41
48
|
test("uses file when env empty", () => {
|
|
42
49
|
const prev = process.env.API_TOKEN;
|
|
43
50
|
delete process.env.API_TOKEN;
|
|
@@ -49,6 +56,7 @@ describe("config/resolve", () => {
|
|
|
49
56
|
}
|
|
50
57
|
});
|
|
51
58
|
|
|
59
|
+
/** Tests that empty string in env or file counts as missing. */
|
|
52
60
|
test("empty string in env or file counts as missing", () => {
|
|
53
61
|
const prev = process.env.API_TOKEN;
|
|
54
62
|
process.env.API_TOKEN = "";
|
|
@@ -61,6 +69,7 @@ describe("config/resolve", () => {
|
|
|
61
69
|
}
|
|
62
70
|
});
|
|
63
71
|
|
|
72
|
+
/** Applies jsonSchema default when file and env absent. */
|
|
64
73
|
test("applies jsonSchema default when file and env absent", () => {
|
|
65
74
|
const prev = process.env.API_TOKEN;
|
|
66
75
|
delete process.env.API_TOKEN;
|
|
@@ -73,6 +82,7 @@ describe("config/resolve", () => {
|
|
|
73
82
|
}
|
|
74
83
|
});
|
|
75
84
|
|
|
85
|
+
/** All-string mode uses entry.default. */
|
|
76
86
|
test("all-string mode uses entry.default", () => {
|
|
77
87
|
const stringProgram: CliProgram = {
|
|
78
88
|
...program,
|
|
@@ -86,6 +96,7 @@ describe("config/resolve", () => {
|
|
|
86
96
|
expect(resolved.greeting).toBe("world");
|
|
87
97
|
});
|
|
88
98
|
|
|
99
|
+
/** Tests that prefers captured host env over file when process.env was exported from file. */
|
|
89
100
|
test("prefers captured host env over file when process.env was exported from file", () => {
|
|
90
101
|
const hostEnv = { API_TOKEN: "from-host" };
|
|
91
102
|
process.env.API_TOKEN = "from-file-export";
|
|
@@ -97,6 +108,7 @@ describe("config/resolve", () => {
|
|
|
97
108
|
}
|
|
98
109
|
});
|
|
99
110
|
|
|
111
|
+
/** ExportConfigToEnv does not overwrite host env. */
|
|
100
112
|
test("exportConfigToEnv does not overwrite host env", () => {
|
|
101
113
|
const prev = process.env.API_TOKEN;
|
|
102
114
|
process.env.API_TOKEN = "from-host";
|
|
@@ -110,6 +122,7 @@ describe("config/resolve", () => {
|
|
|
110
122
|
}
|
|
111
123
|
});
|
|
112
124
|
|
|
125
|
+
/** Resolve callback supplies value when env and file are absent. */
|
|
113
126
|
test("resolve callback supplies value when env and file are absent", () => {
|
|
114
127
|
const resolveProgram: CliProgram = {
|
|
115
128
|
...program,
|
|
@@ -135,6 +148,7 @@ describe("config/resolve", () => {
|
|
|
135
148
|
}
|
|
136
149
|
});
|
|
137
150
|
|
|
151
|
+
/** Env overrides resolve callback. */
|
|
138
152
|
test("env overrides resolve callback", () => {
|
|
139
153
|
const resolveProgram: CliProgram = {
|
|
140
154
|
...program,
|
|
@@ -161,6 +175,7 @@ describe("config/resolve", () => {
|
|
|
161
175
|
}
|
|
162
176
|
});
|
|
163
177
|
|
|
178
|
+
/** File overrides resolve callback. */
|
|
164
179
|
test("file overrides resolve callback", () => {
|
|
165
180
|
const resolveProgram: CliProgram = {
|
|
166
181
|
...program,
|
|
@@ -186,6 +201,7 @@ describe("config/resolve", () => {
|
|
|
186
201
|
}
|
|
187
202
|
});
|
|
188
203
|
|
|
204
|
+
/** Tests that falls back to env when resolve returns undefined. */
|
|
189
205
|
test("falls back to env when resolve returns undefined", () => {
|
|
190
206
|
const resolveProgram: CliProgram = {
|
|
191
207
|
...program,
|
|
@@ -212,6 +228,7 @@ describe("config/resolve", () => {
|
|
|
212
228
|
}
|
|
213
229
|
});
|
|
214
230
|
|
|
231
|
+
/** Resolve callback is skipped when env is set. */
|
|
215
232
|
test("resolve callback is skipped when env is set", () => {
|
|
216
233
|
let resolveCalled = false;
|
|
217
234
|
const resolveProgram: CliProgram = {
|
|
@@ -243,6 +260,7 @@ describe("config/resolve", () => {
|
|
|
243
260
|
}
|
|
244
261
|
});
|
|
245
262
|
|
|
263
|
+
/** Tests that async resolve is ignored with stderr warning. */
|
|
246
264
|
test("async resolve is ignored with stderr warning", () => {
|
|
247
265
|
const resolveProgram: CliProgram = {
|
|
248
266
|
...program,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Tests for config/validate module behavior.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, test } from "bun:test";
|
|
2
6
|
import { parseConfigSetValue, validateConfigDocument } from "./validate.ts";
|
|
3
7
|
|
|
@@ -17,7 +21,9 @@ const rootSchema = {
|
|
|
17
21
|
},
|
|
18
22
|
};
|
|
19
23
|
|
|
24
|
+
/** Tests for config/validate. */
|
|
20
25
|
describe("config/validate", () => {
|
|
26
|
+
/** Tests that accepts valid document. */
|
|
21
27
|
test("accepts valid document", () => {
|
|
22
28
|
const result = validateConfigDocument(
|
|
23
29
|
{ apiToken: "x", maxRetries: 3, prefs: { ttl: 3600 } },
|
|
@@ -27,12 +33,14 @@ describe("config/validate", () => {
|
|
|
27
33
|
expect(result.errors).toEqual([]);
|
|
28
34
|
});
|
|
29
35
|
|
|
36
|
+
/** Rejects missing required property. */
|
|
30
37
|
test("rejects missing required property", () => {
|
|
31
38
|
const result = validateConfigDocument({ apiToken: "x" }, rootSchema);
|
|
32
39
|
expect(result.valid).toBe(false);
|
|
33
40
|
expect(result.errors.some((e) => e.includes("maxRetries"))).toBe(true);
|
|
34
41
|
});
|
|
35
42
|
|
|
43
|
+
/** Rejects unknown property when additionalProperties is false. */
|
|
36
44
|
test("rejects unknown property when additionalProperties is false", () => {
|
|
37
45
|
const result = validateConfigDocument(
|
|
38
46
|
{ apiToken: "x", maxRetries: 1, extra: true },
|
|
@@ -42,16 +50,19 @@ describe("config/validate", () => {
|
|
|
42
50
|
expect(result.errors.some((e) => e.includes("extra"))).toBe(true);
|
|
43
51
|
});
|
|
44
52
|
|
|
53
|
+
/** Rejects type mismatch. */
|
|
45
54
|
test("rejects type mismatch", () => {
|
|
46
55
|
const result = validateConfigDocument({ apiToken: "x", maxRetries: "nope" }, rootSchema);
|
|
47
56
|
expect(result.valid).toBe(false);
|
|
48
57
|
});
|
|
49
58
|
|
|
59
|
+
/** ParseConfigSetValue coerces number and boolean. */
|
|
50
60
|
test("parseConfigSetValue coerces number and boolean", () => {
|
|
51
61
|
expect(parseConfigSetValue("5", { type: "integer" }, rootSchema, false)).toBe(5);
|
|
52
62
|
expect(parseConfigSetValue("true", { type: "boolean" }, rootSchema, false)).toBe(true);
|
|
53
63
|
});
|
|
54
64
|
|
|
65
|
+
/** ParseConfigSetValue requires --json for objects. */
|
|
55
66
|
test("parseConfigSetValue requires --json for objects", () => {
|
|
56
67
|
expect(() => parseConfigSetValue('{"ttl":1}', { type: "object" }, rootSchema, false)).toThrow(
|
|
57
68
|
/--json/,
|
|
@@ -10,6 +10,7 @@ import { bootstrapAppConfig } from "./config/bootstrap.ts";
|
|
|
10
10
|
import { resolveAppConfigPath } from "./config/file.ts";
|
|
11
11
|
import { mcpRequest, testProgram } from "./test-fixtures.ts";
|
|
12
12
|
|
|
13
|
+
/** Tests that bootstrapAppConfig prefers host env over config file. */
|
|
13
14
|
test("bootstrapAppConfig prefers host env over config file", () => {
|
|
14
15
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-env-"));
|
|
15
16
|
const prevHome = process.env.HOME;
|
|
@@ -43,6 +44,7 @@ test("bootstrapAppConfig prefers host env over config file", () => {
|
|
|
43
44
|
}
|
|
44
45
|
});
|
|
45
46
|
|
|
47
|
+
/** MCP program.appConfig fails when required config missing. */
|
|
46
48
|
test("MCP program.appConfig fails when required config missing", async () => {
|
|
47
49
|
const responses = await mcpRequest(
|
|
48
50
|
[
|
|
@@ -60,6 +62,7 @@ test("MCP program.appConfig fails when required config missing", async () => {
|
|
|
60
62
|
expect(res.result.content[0]?.text).toContain("argsTestSecret");
|
|
61
63
|
});
|
|
62
64
|
|
|
65
|
+
/** MCP program.appConfig succeeds when env present. */
|
|
63
66
|
test("MCP program.appConfig succeeds when env present", async () => {
|
|
64
67
|
const responses = await mcpRequest(
|
|
65
68
|
[
|
|
@@ -77,6 +80,7 @@ test("MCP program.appConfig succeeds when env present", async () => {
|
|
|
77
80
|
expect(res.result.content[0]?.text.trim()).toBe("sekrit");
|
|
78
81
|
});
|
|
79
82
|
|
|
83
|
+
/** MCP config file loads and exports vars for tool handlers. */
|
|
80
84
|
test("MCP config file loads and exports vars for tool handlers", async () => {
|
|
81
85
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-mcp-"));
|
|
82
86
|
const configFile = join(dir, ".local", "lib", "mcp_test", "config");
|
|
@@ -106,6 +110,7 @@ test("MCP config file loads and exports vars for tool handlers", async () => {
|
|
|
106
110
|
rmSync(dir, { recursive: true, force: true });
|
|
107
111
|
});
|
|
108
112
|
|
|
113
|
+
/** Cli.run docs api skips required appConfig exit. */
|
|
109
114
|
test("Cli.run docs api skips required appConfig exit", async () => {
|
|
110
115
|
const dir = mkdtempSync(join(tmpdir(), "argsbarg-docs-skip-"));
|
|
111
116
|
const configFile = join(dir, ".local", "lib", "docs_skip_test", "config");
|