magus-cli 7.9.0 → 7.9.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/package.json +4 -4
- package/src/__tests__/blind/review-followups/bun-install-path.test.ts +57 -0
- package/src/__tests__/blind/review-followups/d1-empty-lock.test.ts +94 -0
- package/src/__tests__/blind/review-followups/d2-disappearing-lock.test.ts +90 -0
- package/src/__tests__/blind/review-followups/d3-release-temp-cleanup.test.ts +83 -0
- package/src/__tests__/blind/review-followups/d4-tool-stdout.test.ts +152 -0
- package/src/__tests__/blind/review-followups/installer-redirect.test.ts +74 -0
- package/src/__tests__/blind/review-followups/ledger-permissions.test.ts +78 -0
- package/src/__tests__/blind/review-followups/ledger-pruning.test.ts +79 -0
- package/src/__tests__/blind/review-followups/path-advice.test.ts +46 -0
- package/src/__tests__/blind/review-followups/stats-requires.test.ts +16 -0
- package/src/__tests__/deps-plan.test.ts +3 -2
- package/src/services/deps-plan.ts +273 -81
- package/src/shared/plugin/hooks/bun-missing.sh +35 -10
- package/src/shared/plugin/hooks/deps-check.ts +25 -9
- package/src/shared/plugin/hooks/lib/requires-probe.ts +101 -5
- package/src/utils/run.ts +21 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
access,
|
|
4
|
+
chmod,
|
|
5
|
+
mkdir,
|
|
6
|
+
readdir,
|
|
7
|
+
utimes,
|
|
8
|
+
writeFile,
|
|
9
|
+
} from "node:fs/promises";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
hookCommand,
|
|
14
|
+
linkExecutable,
|
|
15
|
+
makeHookPlugin,
|
|
16
|
+
makeShellPath,
|
|
17
|
+
makeWorld,
|
|
18
|
+
pinnedEnv,
|
|
19
|
+
removeWorld,
|
|
20
|
+
runChild,
|
|
21
|
+
sessionInput,
|
|
22
|
+
} from "../plugin-deps/helpers";
|
|
23
|
+
|
|
24
|
+
test("each stale ledger is pruned even when another stale ledger cannot be removed", async () => {
|
|
25
|
+
const world = await makeWorld();
|
|
26
|
+
let blocked = "";
|
|
27
|
+
try {
|
|
28
|
+
const plugin = await makeHookPlugin(world, "ledger-pruning", {
|
|
29
|
+
schema: 1,
|
|
30
|
+
bin: [],
|
|
31
|
+
});
|
|
32
|
+
const shellPath = await makeShellPath(world);
|
|
33
|
+
await linkExecutable(world.bin, "bun", process.execPath);
|
|
34
|
+
|
|
35
|
+
for (const name of ["magus-deps-candidate-a", "magus-deps-candidate-b"]) {
|
|
36
|
+
await mkdir(join(world.tmp, name));
|
|
37
|
+
}
|
|
38
|
+
const candidates = (await readdir(world.tmp)).filter((name) =>
|
|
39
|
+
name.startsWith("magus-deps-candidate-"),
|
|
40
|
+
);
|
|
41
|
+
blocked = join(world.tmp, candidates[0] ?? "magus-deps-candidate-a");
|
|
42
|
+
const removable = join(
|
|
43
|
+
world.tmp,
|
|
44
|
+
candidates[1] ?? "magus-deps-candidate-b",
|
|
45
|
+
);
|
|
46
|
+
await mkdir(join(blocked, "contents"));
|
|
47
|
+
await writeFile(join(blocked, "contents", "ledger"), "stale\n");
|
|
48
|
+
const stale = new Date(0);
|
|
49
|
+
await utimes(blocked, stale, stale);
|
|
50
|
+
await utimes(removable, stale, stale);
|
|
51
|
+
await chmod(blocked, 0o000);
|
|
52
|
+
|
|
53
|
+
await runChild(
|
|
54
|
+
join(shellPath, "sh"),
|
|
55
|
+
["-c", `umask 022; ${hookCommand("pruner")}`],
|
|
56
|
+
{
|
|
57
|
+
cwd: world.project,
|
|
58
|
+
env: pinnedEnv(world, [world.bin, shellPath], {
|
|
59
|
+
BUN_INSTALL: join(world.home, ".bun"),
|
|
60
|
+
CLAUDE_PLUGIN_ROOT: plugin,
|
|
61
|
+
SHELL: join(shellPath, "sh"),
|
|
62
|
+
}),
|
|
63
|
+
input: sessionInput("current-ledger"),
|
|
64
|
+
timeoutMs: 9_500,
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
let removableExists = true;
|
|
69
|
+
try {
|
|
70
|
+
await access(removable);
|
|
71
|
+
} catch {
|
|
72
|
+
removableExists = false;
|
|
73
|
+
}
|
|
74
|
+
expect(removableExists).toBe(false);
|
|
75
|
+
} finally {
|
|
76
|
+
if (blocked) await chmod(blocked, 0o700).catch(() => undefined);
|
|
77
|
+
await removeWorld(world);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
configureClaude,
|
|
7
|
+
makePlugin,
|
|
8
|
+
makeWorld,
|
|
9
|
+
removeWorld,
|
|
10
|
+
runDoctor,
|
|
11
|
+
writeExecutable,
|
|
12
|
+
} from "../plugin-deps/helpers";
|
|
13
|
+
|
|
14
|
+
test("doctor does not advise adding a PATH line already present in the shell rc file", async () => {
|
|
15
|
+
const world = await makeWorld();
|
|
16
|
+
try {
|
|
17
|
+
const rcLine = 'export PATH="$HOME/.bun/bin:$PATH"';
|
|
18
|
+
await writeFile(join(world.home, ".zshrc"), `${rcLine}\n`);
|
|
19
|
+
const plugin = await makePlugin(world, "path-advice", {
|
|
20
|
+
schema: 1,
|
|
21
|
+
bin: [
|
|
22
|
+
{
|
|
23
|
+
name: "bun",
|
|
24
|
+
check: ["bun", "--version"],
|
|
25
|
+
install: [{ tool: "bun", via: "bootstrap" }],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "widget",
|
|
29
|
+
check: ["widget", "--version"],
|
|
30
|
+
install: [{ package: "widget", via: "bun" }],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
await configureClaude(world, [{ key: "path-advice@test", root: plugin }]);
|
|
35
|
+
await writeExecutable(join(world.bin, "bun"), "exit 0");
|
|
36
|
+
await writeExecutable(join(world.home, ".bun", "bin", "widget"), "exit 0");
|
|
37
|
+
|
|
38
|
+
const result = await runDoctor(world, ["--fix", "--yes"], {
|
|
39
|
+
extraEnv: { BUN_INSTALL: join(world.home, ".bun") },
|
|
40
|
+
});
|
|
41
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
42
|
+
expect(output).not.toContain(`${rcLine} to ~/.zshrc`);
|
|
43
|
+
} finally {
|
|
44
|
+
await removeWorld(world);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { REPO_ROOT } from "../plugin-deps/helpers";
|
|
6
|
+
|
|
7
|
+
test("the stats plugin declares only tools its runtime uses", async () => {
|
|
8
|
+
const manifest = JSON.parse(
|
|
9
|
+
await readFile(
|
|
10
|
+
join(REPO_ROOT, "plugins", "stats", ".claude-plugin", "plugin.json"),
|
|
11
|
+
"utf8",
|
|
12
|
+
),
|
|
13
|
+
) as { requires: { bin: { name: string }[] } };
|
|
14
|
+
|
|
15
|
+
expect(manifest.requires.bin.map(({ name }) => name).sort()).toEqual(["bun"]);
|
|
16
|
+
});
|
|
@@ -468,7 +468,7 @@ describe("applyDepsPlan — uv-module", () => {
|
|
|
468
468
|
env,
|
|
469
469
|
);
|
|
470
470
|
const { calls, run } = fakeRun([
|
|
471
|
-
[(a) => a[1] === "python" && a[2] === "find", {
|
|
471
|
+
[(a) => a[1] === "python" && a[2] === "find", { stdout: `${PY}\n` }],
|
|
472
472
|
]);
|
|
473
473
|
const outcomes = await applyDepsPlan(plan, options({ run }));
|
|
474
474
|
expect(outcomes.map((o) => [o.name, o.status])).toEqual([
|
|
@@ -518,7 +518,7 @@ describe("applyDepsPlan — uv-module", () => {
|
|
|
518
518
|
for (const t of ["sudo", "uv"]) tool(bin, t);
|
|
519
519
|
const plan = planDeps([LIBS], [missing("chromium-libs")], env);
|
|
520
520
|
const { calls, run } = fakeRun([
|
|
521
|
-
[(a) => a[1] === "python", {
|
|
521
|
+
[(a) => a[1] === "python", { stdout: "/usr/bin/python3.12\n" }],
|
|
522
522
|
[(a) => a[1] === "-c", { exitCode: 1 }],
|
|
523
523
|
]);
|
|
524
524
|
const [outcome] = await applyDepsPlan(plan, options({ run }));
|
|
@@ -835,6 +835,7 @@ describe("applyDepsPlan — installed means the re-probe found it", () => {
|
|
|
835
835
|
signal: null,
|
|
836
836
|
timedOut: false,
|
|
837
837
|
outputTail: argv[1] === "pm" ? `${xdgBin}\n` : "installed claudish",
|
|
838
|
+
...(argv[1] === "pm" ? { stdout: `${xdgBin}\n` } : {}),
|
|
838
839
|
durationMs: 1,
|
|
839
840
|
};
|
|
840
841
|
};
|