-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchannels.test.js
More file actions
67 lines (59 loc) · 2.9 KB
/
Copy pathchannels.test.js
File metadata and controls
67 lines (59 loc) · 2.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const readJson = (p) => JSON.parse(readFileSync(join(root, p), "utf8"));
test("plugin channel points at global/ (no duplication)", () => {
const plugin = readJson(".claude-plugin/plugin.json");
assert.equal(plugin.skills, "./global/tools");
// The manifest schema requires each `agents` entry to be a path to a .md file
// (pattern ^\.\/.*\.md$), not a directory — so crew agents are listed explicitly.
assert.ok(Array.isArray(plugin.agents), "agents is an array of agent files");
for (const a of plugin.agents) {
assert.match(a, /^\.\/global\/crew\/.+\.md$/, `agent path ${a} is a ./global/crew/*.md file`);
assert.ok(existsSync(join(root, a)), `${a} exists`);
}
assert.ok(existsSync(join(root, "global/tools")), "global/tools exists");
assert.ok(existsSync(join(root, "global/crew")), "global/crew exists");
});
test("plugin name matches the distributable id (brand pkg)", () => {
assert.equal(readJson(".claude-plugin/plugin.json").name, readJson("brand.json").pkg);
});
test("marketplace lists the plugin at the repo root", () => {
const mkt = readJson(".claude-plugin/marketplace.json");
assert.ok(Array.isArray(mkt.plugins) && mkt.plugins.length >= 1);
assert.equal(mkt.plugins[0].source, ".");
});
test("npm channel ships global/ and the forge bin", () => {
const pkg = readJson("package.json");
assert.ok(pkg.files.includes("global"), "files includes global");
assert.equal(pkg.bin.forge, "src/cli.js");
});
test("installer channel references global/", () => {
const sh = readFileSync(join(root, "install.sh"), "utf8");
assert.match(sh, /global\/tools/);
assert.match(sh, /"\$REPO\/global"/);
});
test("plugin hooks wire guards from the plugin root", () => {
const hooks = JSON.stringify(readJson("hooks/hooks.json"));
assert.match(hooks, /CLAUDE_PLUGIN_ROOT/);
assert.match(hooks, /global\/guards\/protect-paths\.sh/);
});
// Claude Code loads the standard hooks/hooks.json automatically. Declaring it in
// `manifest.hooks` too registers the same file twice, and the loader rejects the
// WHOLE plugin ("Duplicate hooks file detected") — skills, agents, guards and the
// MCP server all vanish. manifest.hooks is only for ADDITIONAL hook files.
test("plugin manifest does not re-declare the standard hooks file", () => {
const plugin = readJson(".claude-plugin/plugin.json");
const declared = [plugin.hooks ?? []].flat();
for (const h of declared) {
assert.notMatch(
h,
/^\.\/hooks\/hooks\.json$/,
"hooks/hooks.json loads automatically; declaring it fails plugin load",
);
}
assert.ok(existsSync(join(root, "hooks/hooks.json")), "the auto-loaded hooks file still exists");
});