argsbarg 6.2.2 → 6.3.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 +22 -1
- package/README.md +2 -2
- package/docs/README.md +2 -2
- package/docs/ai-skills.md +7 -7
- package/docs/bundled-docs.md +2 -2
- package/docs/cli-program.md +4 -4
- package/docs/configure.md +36 -14
- package/docs/developing.md +7 -3
- package/docs/distribution-homebrew.md +2 -2
- package/docs/mcp.md +3 -3
- package/examples/full-example/.cursor/hooks/run-tests-on-stop.ts +56 -0
- package/examples/full-example/.cursor/hooks.json +12 -0
- package/examples/full-example/Formula/full-example.rb +1 -1
- package/examples/full-example/docs/cli-schema.json +6 -6
- package/examples/full-example/docs/cli.md +6 -6
- package/examples/full-example/docs/mcp.md +2 -2
- package/examples/full-example/docs/skill.md +1 -1
- package/examples/full-example/justfile +10 -14
- package/examples/full-example/scripts/formula-shared.ts +1 -1
- package/examples/full-example-json/.cursor/hooks/run-tests-on-stop.ts +56 -0
- package/examples/full-example-json/.cursor/hooks.json +12 -0
- package/examples/full-example-json/Formula/full-example-json.rb +1 -1
- package/examples/full-example-json/docs/cli-schema.json +27 -27
- package/examples/full-example-json/docs/cli.md +27 -27
- package/examples/full-example-json/docs/mcp.md +2 -2
- package/examples/full-example-json/docs/skill.md +1 -1
- package/examples/full-example-json/justfile +10 -14
- package/examples/full-example-json/scripts/formula-shared.ts +1 -1
- package/index.d.ts +25 -4
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +7 -7
- package/src/builtins/config.test.ts +16 -16
- package/src/builtins/configure-copy.ts +2 -2
- package/src/builtins/configure.ts +4 -4
- package/src/config/context.test.ts +12 -12
- package/src/config/file.test.ts +4 -4
- package/src/configure/artifacts/binary-placement.test.ts +4 -4
- package/src/configure/artifacts/mcp-codex.test.ts +5 -5
- package/src/configure/artifacts/mcp-openclaw.test.ts +5 -5
- package/src/configure/artifacts/mcp-opencode.test.ts +5 -5
- package/src/configure/artifacts/status.test.ts +5 -5
- package/src/configure/configure.test.ts +90 -17
- package/src/configure/index.ts +45 -13
- package/src/core/parse.test.ts +7 -7
- package/src/core/types.ts +21 -4
- package/src/docs/docs.test.ts +2 -2
- package/src/docs/mcp-guide.ts +2 -2
- package/src/index.ts +2 -0
- package/src/paths/host.test.ts +58 -0
- package/src/paths/host.ts +12 -3
- package/src/skill/generate.ts +1 -1
- package/src/test/integration/config.test.ts +6 -6
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ import { cliBuiltinDocsGroup } from "../docs/builtin.ts";
|
|
|
9
9
|
import { resolveCapabilities } from "../runtime/capabilities.ts";
|
|
10
10
|
import { completionBashScript, completionFishScript, completionZshScript } from ".";
|
|
11
11
|
import { cliBuiltinConfigureCommand, configureBuiltinOptions } from "./configure.ts";
|
|
12
|
-
import { configureCommandDescription,
|
|
12
|
+
import { configureCommandDescription, configureRefreshOptionDescription } from "./configure-copy.ts";
|
|
13
13
|
import { exportPresentationBuiltins } from "./export.ts";
|
|
14
14
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
15
15
|
import { cliParseRoot, cliPresentationRoot } from "./presentation.ts";
|
|
@@ -43,7 +43,7 @@ describe("builtins help copy", () => {
|
|
|
43
43
|
expect(configure.description).toContain("MCP config");
|
|
44
44
|
expect(configure.notes).toContain("brew upgrade");
|
|
45
45
|
const names = configureBuiltinOptions(fixture).map((o) => o.name);
|
|
46
|
-
expect(names).toContain("
|
|
46
|
+
expect(names).toContain("refresh");
|
|
47
47
|
expect(names).toContain("remove-all");
|
|
48
48
|
expect(names).toContain("status");
|
|
49
49
|
const yesOpt = configureBuiltinOptions(fixture).find((o) => o.name === "yes");
|
|
@@ -54,7 +54,7 @@ describe("builtins help copy", () => {
|
|
|
54
54
|
const caps = resolveCapabilities(noMcp);
|
|
55
55
|
expect(configureCommandDescription(noMcp, caps)).toBe("Set up agent skills for this app (binary via Homebrew).");
|
|
56
56
|
expect(configureCommandDescription(noMcp, caps)).not.toContain("MCP");
|
|
57
|
-
expect(
|
|
57
|
+
expect(configureRefreshOptionDescription(noMcp, caps)).not.toContain("MCP");
|
|
58
58
|
const configure = cliBuiltinConfigureCommand(noMcp);
|
|
59
59
|
expect(configure.description).not.toContain("MCP");
|
|
60
60
|
});
|
|
@@ -63,7 +63,7 @@ describe("builtins help copy", () => {
|
|
|
63
63
|
test("configure notes mention brew upgrade and interactive configure", () => {
|
|
64
64
|
const configure = cliBuiltinConfigureCommand(fixture);
|
|
65
65
|
expect(configure.notes).toContain("brew upgrade");
|
|
66
|
-
expect(configure.notes).toContain("configure --
|
|
66
|
+
expect(configure.notes).toContain("configure --refresh --yes");
|
|
67
67
|
expect(configure.notes).toContain(`${fixture.key} configure`);
|
|
68
68
|
|
|
69
69
|
const withConfig: CliProgram = {
|
|
@@ -77,11 +77,11 @@ describe("builtins help copy", () => {
|
|
|
77
77
|
|
|
78
78
|
test("configure -y parses as --yes", () => {
|
|
79
79
|
const root = cliParseRoot(fixture);
|
|
80
|
-
const pr = postParseValidate(root, parse(root, ["configure", "-y", "--
|
|
80
|
+
const pr = postParseValidate(root, parse(root, ["configure", "-y", "--refresh"]));
|
|
81
81
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
82
82
|
if (pr.kind === ParseKind.Ok) {
|
|
83
83
|
expect(pr.opts.yes).toBe("1");
|
|
84
|
-
expect(pr.opts.
|
|
84
|
+
expect(pr.opts.refresh).toBe("1");
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
|
|
@@ -134,7 +134,7 @@ describe("completion emitters", () => {
|
|
|
134
134
|
const schema = cliPresentationRoot(fixture);
|
|
135
135
|
const bash = completionBashScript(schema);
|
|
136
136
|
expect(bash).toContain("hello");
|
|
137
|
-
expect(bash).toContain("--
|
|
137
|
+
expect(bash).toContain("--refresh");
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
test("zsh script registers compdef", () => {
|
|
@@ -30,8 +30,8 @@ describe("builtins/config", () => {
|
|
|
30
30
|
/** Tests that config get redacts sensitive values. */
|
|
31
31
|
test("config get redacts sensitive values", async () => {
|
|
32
32
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
33
|
-
const
|
|
34
|
-
process.env.
|
|
33
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
34
|
+
process.env.TEST_USER_HOME = dir;
|
|
35
35
|
const prev = process.env.API_TOKEN;
|
|
36
36
|
delete process.env.API_TOKEN;
|
|
37
37
|
try {
|
|
@@ -43,8 +43,8 @@ describe("builtins/config", () => {
|
|
|
43
43
|
expect(result.exitCode).toBe(0);
|
|
44
44
|
expect(result.stdout.trim()).toBe("REDACTED");
|
|
45
45
|
} finally {
|
|
46
|
-
if (
|
|
47
|
-
else process.env.
|
|
46
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
47
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
48
48
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
49
49
|
rmSync(dir, { recursive: true, force: true });
|
|
50
50
|
}
|
|
@@ -53,8 +53,8 @@ describe("builtins/config", () => {
|
|
|
53
53
|
/** Tests that config get --json redacts sensitive as { set: true }. */
|
|
54
54
|
test("config get --json redacts sensitive as { set: true }", async () => {
|
|
55
55
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
56
|
-
const
|
|
57
|
-
process.env.
|
|
56
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
57
|
+
process.env.TEST_USER_HOME = dir;
|
|
58
58
|
const prev = process.env.API_TOKEN;
|
|
59
59
|
delete process.env.API_TOKEN;
|
|
60
60
|
try {
|
|
@@ -66,8 +66,8 @@ describe("builtins/config", () => {
|
|
|
66
66
|
expect(result.exitCode).toBe(0);
|
|
67
67
|
expect(JSON.parse(result.stdout)).toEqual({ set: true });
|
|
68
68
|
} finally {
|
|
69
|
-
if (
|
|
70
|
-
else process.env.
|
|
69
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
70
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
71
71
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
72
72
|
rmSync(dir, { recursive: true, force: true });
|
|
73
73
|
}
|
|
@@ -76,8 +76,8 @@ describe("builtins/config", () => {
|
|
|
76
76
|
/** Tests that config set writes and resolves without required exit. */
|
|
77
77
|
test("config set writes and resolves without required exit", async () => {
|
|
78
78
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
79
|
-
const
|
|
80
|
-
process.env.
|
|
79
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
80
|
+
process.env.TEST_USER_HOME = dir;
|
|
81
81
|
const prev = process.env.API_TOKEN;
|
|
82
82
|
delete process.env.API_TOKEN;
|
|
83
83
|
try {
|
|
@@ -88,8 +88,8 @@ describe("builtins/config", () => {
|
|
|
88
88
|
const get = await new Cli(program).invoke(["configure", "get", "port"]);
|
|
89
89
|
expect(get.stdout.trim()).toBe("9090");
|
|
90
90
|
} finally {
|
|
91
|
-
if (
|
|
92
|
-
else process.env.
|
|
91
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
92
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
93
93
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
94
94
|
rmSync(dir, { recursive: true, force: true });
|
|
95
95
|
}
|
|
@@ -97,8 +97,8 @@ describe("builtins/config", () => {
|
|
|
97
97
|
|
|
98
98
|
test("config set --from-env stores binding without literal", async () => {
|
|
99
99
|
const dir = mkdtempSync(join(tmpdir(), "cfg-from-env-"));
|
|
100
|
-
const
|
|
101
|
-
process.env.
|
|
100
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
101
|
+
process.env.TEST_USER_HOME = dir;
|
|
102
102
|
const prev = process.env.API_TOKEN;
|
|
103
103
|
process.env.API_TOKEN = "from-env";
|
|
104
104
|
try {
|
|
@@ -114,8 +114,8 @@ describe("builtins/config", () => {
|
|
|
114
114
|
expect(onDisk.apiToken).toBeUndefined();
|
|
115
115
|
expect(readBindings(onDisk).apiToken).toBe("env");
|
|
116
116
|
} finally {
|
|
117
|
-
if (
|
|
118
|
-
else process.env.
|
|
117
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
118
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
119
119
|
if (prev === undefined) delete process.env.API_TOKEN;
|
|
120
120
|
else process.env.API_TOKEN = prev;
|
|
121
121
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -37,7 +37,7 @@ export function configureCommandDescription(program: CliProgram, caps: CliCapabi
|
|
|
37
37
|
return `Set up ${prose(program, caps)} for this app (binary via Homebrew).`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export function
|
|
40
|
+
export function configureRefreshOptionDescription(program: CliProgram, caps: CliCapabilities): string {
|
|
41
41
|
return `Refresh installed ${short(program, caps)}. Used by Homebrew post_install.`;
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -54,7 +54,7 @@ export function configureCommandNotes(program: CliProgram, _caps: CliCapabilitie
|
|
|
54
54
|
"Set up agent artifacts after the binary is installed via Homebrew (see README for tap install).",
|
|
55
55
|
"",
|
|
56
56
|
"Homebrew post_install runs:",
|
|
57
|
-
` ${app} configure --
|
|
57
|
+
` ${app} configure --refresh --yes`,
|
|
58
58
|
"",
|
|
59
59
|
"Interactive setup (per target):",
|
|
60
60
|
` ${app} configure`,
|
|
@@ -12,7 +12,7 @@ import { configureConfigSubcommands } from "./config.ts";
|
|
|
12
12
|
import {
|
|
13
13
|
configureCommandDescription,
|
|
14
14
|
configureCommandNotes,
|
|
15
|
-
|
|
15
|
+
configureRefreshOptionDescription,
|
|
16
16
|
} from "./configure-copy.ts";
|
|
17
17
|
|
|
18
18
|
/** Hidden fallback leaf for bare `configure` (interactive / flag modes). */
|
|
@@ -23,8 +23,8 @@ export function configureBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
23
23
|
const caps = resolveCapabilities(root);
|
|
24
24
|
const opts: CliOption[] = [
|
|
25
25
|
{
|
|
26
|
-
name: "
|
|
27
|
-
description:
|
|
26
|
+
name: "refresh",
|
|
27
|
+
description: configureRefreshOptionDescription(root, caps),
|
|
28
28
|
kind: CliOptionKind.Presence,
|
|
29
29
|
},
|
|
30
30
|
{
|
|
@@ -50,7 +50,7 @@ export function configureBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
name: "yes",
|
|
53
|
-
description: "Skip confirmation (required for --
|
|
53
|
+
description: "Skip confirmation (required for --refresh, --remove-all, --remove-config).",
|
|
54
54
|
kind: CliOptionKind.Presence,
|
|
55
55
|
shortName: "y",
|
|
56
56
|
},
|
|
@@ -29,8 +29,8 @@ describe("config/context", () => {
|
|
|
29
29
|
/** Tests that AppConfigSnapshot get, require, read, set. */
|
|
30
30
|
test("AppConfigSnapshot get, require, read, set", () => {
|
|
31
31
|
const dir = mkdtempSync(join(tmpdir(), "ctx-test-"));
|
|
32
|
-
const
|
|
33
|
-
process.env.
|
|
32
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
33
|
+
process.env.TEST_USER_HOME = dir;
|
|
34
34
|
const prevToken = process.env.API_TOKEN;
|
|
35
35
|
delete process.env.API_TOKEN;
|
|
36
36
|
try {
|
|
@@ -48,8 +48,8 @@ describe("config/context", () => {
|
|
|
48
48
|
expect(ctx.get("note")).toBe("updated");
|
|
49
49
|
expect(ctx.read().note).toBe("updated");
|
|
50
50
|
} finally {
|
|
51
|
-
if (
|
|
52
|
-
else process.env.
|
|
51
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
52
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
53
53
|
if (prevToken === undefined) delete process.env.API_TOKEN;
|
|
54
54
|
else process.env.API_TOKEN = prevToken;
|
|
55
55
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -82,8 +82,8 @@ describe("config/context", () => {
|
|
|
82
82
|
|
|
83
83
|
test("AppConfigSnapshot unsafe read/write", () => {
|
|
84
84
|
const dir = mkdtempSync(join(tmpdir(), "ctx-unsafe-"));
|
|
85
|
-
const
|
|
86
|
-
process.env.
|
|
85
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
86
|
+
process.env.TEST_USER_HOME = dir;
|
|
87
87
|
const prevToken = process.env.API_TOKEN;
|
|
88
88
|
delete process.env.API_TOKEN;
|
|
89
89
|
try {
|
|
@@ -95,8 +95,8 @@ describe("config/context", () => {
|
|
|
95
95
|
ctx.setUnsafe("note", "raw");
|
|
96
96
|
expect(ctx.getUnsafe("note")).toBe("raw");
|
|
97
97
|
} finally {
|
|
98
|
-
if (
|
|
99
|
-
else process.env.
|
|
98
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
99
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
100
100
|
if (prevToken === undefined) delete process.env.API_TOKEN;
|
|
101
101
|
else process.env.API_TOKEN = prevToken;
|
|
102
102
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -105,8 +105,8 @@ describe("config/context", () => {
|
|
|
105
105
|
|
|
106
106
|
test("EmptyAppConfigSnapshot unsafe read/write", () => {
|
|
107
107
|
const dir = mkdtempSync(join(tmpdir(), "ctx-empty-unsafe-"));
|
|
108
|
-
const
|
|
109
|
-
process.env.
|
|
108
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
109
|
+
process.env.TEST_USER_HOME = dir;
|
|
110
110
|
try {
|
|
111
111
|
const programWithoutConfig: CliProgram = {
|
|
112
112
|
key: "rawapp",
|
|
@@ -119,8 +119,8 @@ describe("config/context", () => {
|
|
|
119
119
|
expect(empty.getUnsafe("custom")).toBe(42);
|
|
120
120
|
expect(empty.readUnsafe().custom).toBe(42);
|
|
121
121
|
} finally {
|
|
122
|
-
if (
|
|
123
|
-
else process.env.
|
|
122
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
123
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
124
124
|
rmSync(dir, { recursive: true, force: true });
|
|
125
125
|
}
|
|
126
126
|
});
|
package/src/config/file.test.ts
CHANGED
|
@@ -37,13 +37,13 @@ const program: CliProgram = {
|
|
|
37
37
|
|
|
38
38
|
function withHome<T>(fn: (home: string) => T): T {
|
|
39
39
|
const home = mkdtempSync(join(tmpdir(), "cfg-test-"));
|
|
40
|
-
const
|
|
41
|
-
process.env.
|
|
40
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
41
|
+
process.env.TEST_USER_HOME = home;
|
|
42
42
|
try {
|
|
43
43
|
return fn(home);
|
|
44
44
|
} finally {
|
|
45
|
-
if (
|
|
46
|
-
else process.env.
|
|
45
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
46
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
47
47
|
rmSync(home, { recursive: true, force: true });
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -84,14 +84,14 @@ describe("isAppInstalled", () => {
|
|
|
84
84
|
/** Tests that false when not on PATH and no local copy. */
|
|
85
85
|
test("false when not on PATH and no local copy", () => {
|
|
86
86
|
const home = mkdtempSync(join(tmpdir(), "argsbarg-placement-home-"));
|
|
87
|
-
const
|
|
88
|
-
process.env.
|
|
87
|
+
const prevTestHome = process.env.TEST_USER_HOME;
|
|
88
|
+
process.env.TEST_USER_HOME = home;
|
|
89
89
|
process.env.PATH = tmp;
|
|
90
90
|
try {
|
|
91
91
|
expect(isAppInstalled(program)).toBe(false);
|
|
92
92
|
} finally {
|
|
93
|
-
if (
|
|
94
|
-
else process.env.
|
|
93
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
94
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
95
95
|
rmSync(home, { recursive: true, force: true });
|
|
96
96
|
}
|
|
97
97
|
});
|
|
@@ -9,17 +9,17 @@ import { join } from "node:path";
|
|
|
9
9
|
import { codexMcpHasServer, readCodexMcpEntry, resolveCodexConfigPath } from "./mcp-codex.ts";
|
|
10
10
|
|
|
11
11
|
let home: string;
|
|
12
|
-
let
|
|
12
|
+
let prevTestHome: string | undefined;
|
|
13
13
|
|
|
14
14
|
beforeEach(() => {
|
|
15
15
|
home = mkdtempSync(join(tmpdir(), "argsbarg-codex-"));
|
|
16
|
-
|
|
17
|
-
process.env.
|
|
16
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
17
|
+
process.env.TEST_USER_HOME = home;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
afterEach(() => {
|
|
21
|
-
if (
|
|
22
|
-
else process.env.
|
|
21
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
22
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
23
23
|
rmSync(home, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -9,17 +9,17 @@ import { join } from "node:path";
|
|
|
9
9
|
import { openclawMcpHasServer, resolveOpenclawConfigPath } from "./mcp-openclaw.ts";
|
|
10
10
|
|
|
11
11
|
let home: string;
|
|
12
|
-
let
|
|
12
|
+
let prevTestHome: string | undefined;
|
|
13
13
|
|
|
14
14
|
beforeEach(() => {
|
|
15
15
|
home = mkdtempSync(join(tmpdir(), "argsbarg-openclaw-"));
|
|
16
|
-
|
|
17
|
-
process.env.
|
|
16
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
17
|
+
process.env.TEST_USER_HOME = home;
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
afterEach(() => {
|
|
21
|
-
if (
|
|
22
|
-
else process.env.
|
|
21
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
22
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
23
23
|
rmSync(home, { recursive: true, force: true });
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -26,20 +26,20 @@ const fixture: CliProgram = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
let home: string;
|
|
29
|
-
let
|
|
29
|
+
let prevTestHome: string | undefined;
|
|
30
30
|
let prevXdg: string | undefined;
|
|
31
31
|
|
|
32
32
|
beforeEach(() => {
|
|
33
33
|
home = mkdtempSync(join(tmpdir(), "argsbarg-opencode-"));
|
|
34
|
-
|
|
34
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
35
35
|
prevXdg = process.env.XDG_CONFIG_HOME;
|
|
36
|
-
process.env.
|
|
36
|
+
process.env.TEST_USER_HOME = home;
|
|
37
37
|
delete process.env.XDG_CONFIG_HOME;
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
afterEach(() => {
|
|
41
|
-
if (
|
|
42
|
-
else process.env.
|
|
41
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
42
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
43
43
|
if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
44
44
|
else process.env.XDG_CONFIG_HOME = prevXdg;
|
|
45
45
|
rmSync(home, { recursive: true, force: true });
|
|
@@ -12,17 +12,17 @@ import { printInstallStatus } from "./status.ts";
|
|
|
12
12
|
import { resolveInstallTargetPreview } from "./target-scope.ts";
|
|
13
13
|
|
|
14
14
|
let home: string;
|
|
15
|
-
let
|
|
15
|
+
let prevTestHome: string | undefined;
|
|
16
16
|
|
|
17
17
|
beforeEach(() => {
|
|
18
18
|
home = mkdtempSync(join(tmpdir(), "argsbarg-status-"));
|
|
19
|
-
|
|
20
|
-
process.env.
|
|
19
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
20
|
+
process.env.TEST_USER_HOME = home;
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
afterEach(() => {
|
|
24
|
-
if (
|
|
25
|
-
else process.env.
|
|
24
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
25
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
26
26
|
rmSync(home, { recursive: true, force: true });
|
|
27
27
|
});
|
|
28
28
|
|
|
@@ -33,20 +33,20 @@ const fixture: CliProgram = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
let home: string;
|
|
36
|
-
let
|
|
36
|
+
let prevTestHome: string | undefined;
|
|
37
37
|
let prevXdg: string | undefined;
|
|
38
38
|
|
|
39
39
|
beforeEach(() => {
|
|
40
40
|
home = mkdtempSync(join(tmpdir(), "argsbarg-configure-"));
|
|
41
|
-
|
|
41
|
+
prevTestHome = process.env.TEST_USER_HOME;
|
|
42
42
|
prevXdg = process.env.XDG_CONFIG_HOME;
|
|
43
|
-
process.env.
|
|
43
|
+
process.env.TEST_USER_HOME = home;
|
|
44
44
|
delete process.env.XDG_CONFIG_HOME;
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
afterEach(() => {
|
|
48
|
-
if (
|
|
49
|
-
else process.env.
|
|
48
|
+
if (prevTestHome === undefined) delete process.env.TEST_USER_HOME;
|
|
49
|
+
else process.env.TEST_USER_HOME = prevTestHome;
|
|
50
50
|
if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
51
51
|
else process.env.XDG_CONFIG_HOME = prevXdg;
|
|
52
52
|
rmSync(home, { recursive: true, force: true });
|
|
@@ -55,12 +55,12 @@ afterEach(() => {
|
|
|
55
55
|
/** Tests for configure opts. */
|
|
56
56
|
describe("configure opts", () => {
|
|
57
57
|
test("validate rejects multiple modes", () => {
|
|
58
|
-
const opts = parseConfigureOpts({
|
|
58
|
+
const opts = parseConfigureOpts({ refresh: "1", status: "1" });
|
|
59
59
|
expect(validateConfigureOpts(opts)).toContain("only one");
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
test("
|
|
63
|
-
const opts = parseConfigureOpts({
|
|
62
|
+
test("refresh requires --yes", () => {
|
|
63
|
+
const opts = parseConfigureOpts({ refresh: "1" });
|
|
64
64
|
expect(validateConfigureOpts(opts)).toContain("--yes");
|
|
65
65
|
});
|
|
66
66
|
|
|
@@ -92,12 +92,12 @@ describe("configure mutation summary", () => {
|
|
|
92
92
|
expect(msg).toBe("Installed 1 artifact.");
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
test("
|
|
95
|
+
test("refresh mode uses refreshed verb and artifact count", () => {
|
|
96
96
|
const msg = formatConfigureMutationSummary(
|
|
97
97
|
{ paths: ["a", "b", "c"], installed: 3, removed: 0, configured: 0 },
|
|
98
|
-
{
|
|
98
|
+
{ refresh: true },
|
|
99
99
|
);
|
|
100
|
-
expect(msg).toBe("
|
|
100
|
+
expect(msg).toBe("Refreshed 3 artifacts.");
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test("remove-all uses removed verb", () => {
|
|
@@ -147,8 +147,8 @@ describe("detect installed", () => {
|
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
/** Tests for
|
|
151
|
-
describe("
|
|
150
|
+
/** Tests for refresh plan. */
|
|
151
|
+
describe("refresh plan", () => {
|
|
152
152
|
test("buildUpdatePlan greenfield includes skill when enabled", () => {
|
|
153
153
|
const paths = resolveInstallPaths(fixture);
|
|
154
154
|
const plan = buildUpdatePlan(fixture, paths, parseInstallOpts({ reinstall: "1", yes: "1" }));
|
|
@@ -229,19 +229,92 @@ describe("app config wizard", () => {
|
|
|
229
229
|
});
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
-
describe("configure --
|
|
232
|
+
describe("configure --refresh bootstrap", () => {
|
|
233
233
|
test("creates config.json for apps without appConfig", async () => {
|
|
234
234
|
const program: CliProgram = {
|
|
235
|
-
key: "
|
|
235
|
+
key: "refreshboot",
|
|
236
236
|
version: "0.0.0",
|
|
237
|
-
description: "
|
|
237
|
+
description: "Refresh bootstrap test.",
|
|
238
238
|
skill: { enabled: true },
|
|
239
239
|
handler: () => {},
|
|
240
240
|
configure: { enabled: true },
|
|
241
241
|
};
|
|
242
242
|
expect(appConfigFileExists(program)).toBe(false);
|
|
243
|
-
const result = await new Cli(program).invoke(["configure", "--
|
|
243
|
+
const result = await new Cli(program).invoke(["configure", "--refresh", "--yes"]);
|
|
244
244
|
expect(result.exitCode).toBe(0);
|
|
245
245
|
expect(appConfigFileExists(program)).toBe(true);
|
|
246
246
|
});
|
|
247
247
|
});
|
|
248
|
+
|
|
249
|
+
describe("configure lifecycle hooks", () => {
|
|
250
|
+
test("afterRefresh runs after --refresh plan", async () => {
|
|
251
|
+
const calls: string[] = [];
|
|
252
|
+
const program: CliProgram = {
|
|
253
|
+
...fixture,
|
|
254
|
+
key: "hookrefresh",
|
|
255
|
+
configure: {
|
|
256
|
+
afterRefresh: async (ctx) => {
|
|
257
|
+
calls.push(`after:${ctx.paths.mcpName}:dry=${ctx.dry}`);
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
const result = await new Cli(program).invoke(["configure", "--refresh", "--yes"]);
|
|
262
|
+
expect(result.exitCode).toBe(0);
|
|
263
|
+
expect(calls).toEqual([`after:hookrefresh:dry=false`]);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("beforeRemoveAll runs before --remove-all plan", async () => {
|
|
267
|
+
const paths = resolveInstallPaths({ ...fixture, key: "hookremove" });
|
|
268
|
+
mkdirSync(paths.agentsSkillDir, { recursive: true });
|
|
269
|
+
writeFileSync(join(paths.agentsSkillDir, "SKILL.md"), "# test\n", "utf8");
|
|
270
|
+
|
|
271
|
+
const calls: string[] = [];
|
|
272
|
+
const program: CliProgram = {
|
|
273
|
+
...fixture,
|
|
274
|
+
key: "hookremove",
|
|
275
|
+
configure: {
|
|
276
|
+
beforeRemoveAll: async (ctx) => {
|
|
277
|
+
calls.push(`before:${ctx.paths.skillDirName}`);
|
|
278
|
+
expect(existsSync(ctx.paths.agentsSkillDir)).toBe(true);
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
const result = await new Cli(program).invoke(["configure", "--remove-all", "--yes"]);
|
|
283
|
+
expect(result.exitCode).toBe(0);
|
|
284
|
+
expect(calls).toEqual(["before:hookremove"]);
|
|
285
|
+
expect(existsSync(paths.agentsSkillDir)).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("hooks receive dry from --dry", async () => {
|
|
289
|
+
const calls: string[] = [];
|
|
290
|
+
const program: CliProgram = {
|
|
291
|
+
...fixture,
|
|
292
|
+
key: "hookdry",
|
|
293
|
+
configure: {
|
|
294
|
+
afterRefresh: (ctx) => {
|
|
295
|
+
calls.push(`dry=${ctx.dry}`);
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
const result = await new Cli(program).invoke(["configure", "--refresh", "--yes", "--dry"]);
|
|
300
|
+
expect(result.exitCode).toBe(0);
|
|
301
|
+
expect(calls).toEqual(["dry=true"]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("beforeRemoveAll is not called for --remove-config", async () => {
|
|
305
|
+
let called = false;
|
|
306
|
+
const program: CliProgram = {
|
|
307
|
+
...fixture,
|
|
308
|
+
key: "hookcfg",
|
|
309
|
+
appConfig: { entries: { token: { description: "Token." } } },
|
|
310
|
+
configure: {
|
|
311
|
+
beforeRemoveAll: () => {
|
|
312
|
+
called = true;
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
const result = await new Cli(program).invoke(["configure", "--remove-config", "--yes"]);
|
|
317
|
+
expect(result.exitCode).toBe(0);
|
|
318
|
+
expect(called).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
});
|