@meshxdata/fops 0.0.1 → 0.0.3

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.
@@ -1,70 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import os from "node:os";
3
- import { PKG, CLI_BRAND, printFoundationBanner } from "./config.js";
4
-
5
- describe("config", () => {
6
- describe("PKG", () => {
7
- it("exports name and version", () => {
8
- expect(PKG.name).toBe("@meshxdata/fops");
9
- expect(typeof PKG.version).toBe("string");
10
- expect(PKG.version).toMatch(/^\d+\.\d+\.\d+/);
11
- });
12
-
13
- it("only exposes name and version (no extra fields)", () => {
14
- expect(Object.keys(PKG).sort()).toEqual(["name", "version"]);
15
- });
16
- });
17
-
18
- describe("CLI_BRAND", () => {
19
- it("has the expected shape", () => {
20
- expect(CLI_BRAND.title).toBe("Foundation Operator CLI");
21
- expect(CLI_BRAND.version).toMatch(/^v\d+/);
22
- expect(CLI_BRAND.byline).toContain("meshx");
23
- });
24
-
25
- it("version matches PKG.version", () => {
26
- expect(CLI_BRAND.version).toBe(`v${PKG.version}`);
27
- });
28
- });
29
-
30
- describe("printFoundationBanner", () => {
31
- it("prints CLI info to stdout", () => {
32
- const spy = vi.spyOn(console, "log").mockImplementation(() => {});
33
- printFoundationBanner("/tmp/test-project");
34
- expect(spy).toHaveBeenCalled();
35
- const output = spy.mock.calls.map((c) => c[0]).join("\n");
36
- expect(output).toContain("Foundation Operator CLI");
37
- });
38
-
39
- it("shortens home dir to ~", () => {
40
- const spy = vi.spyOn(console, "log").mockImplementation(() => {});
41
- const home = os.homedir();
42
- printFoundationBanner(home + "/projects/test");
43
- const output = spy.mock.calls.map((c) => c[0]).join("\n");
44
- expect(output).toContain("~/projects/test");
45
- });
46
-
47
- it("does not replace ~ when path is not under homedir", () => {
48
- const spy = vi.spyOn(console, "log").mockImplementation(() => {});
49
- printFoundationBanner("/opt/data/project");
50
- const output = spy.mock.calls.map((c) => c[0]).join("\n");
51
- expect(output).toContain("/opt/data/project");
52
- expect(output).not.toContain("~");
53
- });
54
-
55
- it("prints version and byline", () => {
56
- const spy = vi.spyOn(console, "log").mockImplementation(() => {});
57
- printFoundationBanner("/tmp");
58
- const output = spy.mock.calls.map((c) => c[0]).join("\n");
59
- expect(output).toContain(CLI_BRAND.version);
60
- expect(output).toContain("meshx");
61
- });
62
-
63
- it("prints a trailing blank line", () => {
64
- const spy = vi.spyOn(console, "log").mockImplementation(() => {});
65
- printFoundationBanner("/tmp");
66
- // Last call should be empty string (blank line)
67
- expect(spy.mock.calls[spy.mock.calls.length - 1][0]).toBe("");
68
- });
69
- });
70
- });
@@ -1,134 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- const doctorSource = fs.readFileSync(path.join(__dirname, "doctor.js"), "utf8");
8
-
9
- // readNetrcToken is not exported, so we inline a copy for testing
10
- function readNetrcToken(content, machine) {
11
- const lines = content.replace(/\r\n/g, "\n").split("\n");
12
- let inMachine = false;
13
- for (const line of lines) {
14
- const tokens = line.trim().split(/\s+/);
15
- for (let i = 0; i < tokens.length; i++) {
16
- if (tokens[i] === "machine" && tokens[i + 1] === machine) {
17
- inMachine = true;
18
- } else if (tokens[i] === "machine" && tokens[i + 1] !== machine) {
19
- if (inMachine) return null;
20
- }
21
- if (inMachine && tokens[i] === "password" && tokens[i + 1]) {
22
- return tokens[i + 1];
23
- }
24
- }
25
- }
26
- return null;
27
- }
28
-
29
- describe("doctor", () => {
30
- describe("readNetrcToken", () => {
31
- it("returns token for multi-line netrc", () => {
32
- const content = `machine github.com\nlogin user\npassword ghp_abc123\n`;
33
- expect(readNetrcToken(content, "github.com")).toBe("ghp_abc123");
34
- });
35
-
36
- it("returns token for single-line netrc", () => {
37
- const content = `machine github.com login user password ghp_abc123`;
38
- expect(readNetrcToken(content, "github.com")).toBe("ghp_abc123");
39
- });
40
-
41
- it("returns null for wrong machine", () => {
42
- const content = `machine gitlab.com\nlogin user\npassword glpat_xyz\n`;
43
- expect(readNetrcToken(content, "github.com")).toBe(null);
44
- });
45
-
46
- it("returns null for missing password", () => {
47
- const content = `machine github.com\nlogin user\n`;
48
- expect(readNetrcToken(content, "github.com")).toBe(null);
49
- });
50
-
51
- it("handles multiple machines", () => {
52
- const content = [
53
- "machine gitlab.com login a password glpat_xyz",
54
- "machine github.com login b password ghp_123",
55
- ].join("\n");
56
- expect(readNetrcToken(content, "github.com")).toBe("ghp_123");
57
- expect(readNetrcToken(content, "gitlab.com")).toBe("glpat_xyz");
58
- });
59
-
60
- it("stops at next machine block", () => {
61
- const content = [
62
- "machine github.com",
63
- "login user",
64
- "machine other.com",
65
- "password secret",
66
- ].join("\n");
67
- expect(readNetrcToken(content, "github.com")).toBe(null);
68
- });
69
-
70
- it("handles windows line endings", () => {
71
- const content = "machine github.com\r\nlogin user\r\npassword ghp_win\r\n";
72
- expect(readNetrcToken(content, "github.com")).toBe("ghp_win");
73
- });
74
-
75
- it("handles empty content", () => {
76
- expect(readNetrcToken("", "github.com")).toBe(null);
77
- });
78
-
79
- it("handles whitespace-only content", () => {
80
- expect(readNetrcToken(" \n \n ", "github.com")).toBe(null);
81
- });
82
-
83
- it("handles machine with no following tokens", () => {
84
- const content = "machine\ngithub.com\npassword ghp_123";
85
- // "machine" without immediate next token matching
86
- expect(readNetrcToken(content, "github.com")).toBe(null);
87
- });
88
-
89
- it("handles mixed multi-line and single-line entries", () => {
90
- const content = [
91
- "machine gitlab.com login gl password glpat_abc",
92
- "",
93
- "machine github.com",
94
- "login gh",
95
- "password ghp_xyz",
96
- ].join("\n");
97
- expect(readNetrcToken(content, "github.com")).toBe("ghp_xyz");
98
- expect(readNetrcToken(content, "gitlab.com")).toBe("glpat_abc");
99
- });
100
-
101
- it("returns first password for a machine", () => {
102
- const content = "machine github.com login user password first_token";
103
- expect(readNetrcToken(content, "github.com")).toBe("first_token");
104
- });
105
- });
106
-
107
- describe("KEY_PORTS", () => {
108
- it("defines all expected ports", () => {
109
- expect(doctorSource).toContain("5432");
110
- expect(doctorSource).toContain("Postgres");
111
- expect(doctorSource).toContain("9092");
112
- expect(doctorSource).toContain("Kafka");
113
- expect(doctorSource).toContain("9001");
114
- expect(doctorSource).toContain("Backend");
115
- expect(doctorSource).toContain("3002");
116
- expect(doctorSource).toContain("Frontend");
117
- expect(doctorSource).toContain("8081");
118
- expect(doctorSource).toContain("Trino");
119
- expect(doctorSource).toContain("9083");
120
- expect(doctorSource).toContain("Hive Metastore");
121
- expect(doctorSource).toContain("8181");
122
- expect(doctorSource).toContain("OPA");
123
- expect(doctorSource).toContain("18201");
124
- expect(doctorSource).toContain("Vault");
125
- });
126
- });
127
-
128
- describe("runDoctor export", () => {
129
- it("is exported as a function", async () => {
130
- const { runDoctor } = await import("./doctor.js");
131
- expect(typeof runDoctor).toBe("function");
132
- });
133
- });
134
- });
@@ -1,95 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { createPluginApi } from "./api.js";
3
- import { createRegistry } from "./registry.js";
4
-
5
- describe("plugins/api", () => {
6
- describe("createPluginApi", () => {
7
- it("returns api with id and methods", () => {
8
- const registry = createRegistry();
9
- const api = createPluginApi("test-plugin", registry);
10
- expect(api.id).toBe("test-plugin");
11
- expect(typeof api.registerCommand).toBe("function");
12
- expect(typeof api.registerDoctorCheck).toBe("function");
13
- expect(typeof api.registerHook).toBe("function");
14
- });
15
-
16
- it("has config property (empty by default)", () => {
17
- const registry = createRegistry();
18
- const api = createPluginApi("test-plugin", registry);
19
- expect(api.config).toEqual({});
20
- });
21
-
22
- it("registerCommand adds to registry.commands", () => {
23
- const registry = createRegistry();
24
- const api = createPluginApi("test-plugin", registry);
25
- const spec = { name: "my-cmd", description: "A command" };
26
- api.registerCommand(spec);
27
- expect(registry.commands).toHaveLength(1);
28
- expect(registry.commands[0]).toEqual({ pluginId: "test-plugin", spec });
29
- });
30
-
31
- it("registerCommand can add multiple commands", () => {
32
- const registry = createRegistry();
33
- const api = createPluginApi("test-plugin", registry);
34
- api.registerCommand({ name: "cmd1" });
35
- api.registerCommand({ name: "cmd2" });
36
- api.registerCommand({ name: "cmd3" });
37
- expect(registry.commands).toHaveLength(3);
38
- });
39
-
40
- it("registerDoctorCheck adds to registry.doctorChecks", () => {
41
- const registry = createRegistry();
42
- const api = createPluginApi("test-plugin", registry);
43
- const fn = vi.fn();
44
- api.registerDoctorCheck({ name: "check-foo", fn });
45
- expect(registry.doctorChecks).toHaveLength(1);
46
- expect(registry.doctorChecks[0].name).toBe("check-foo");
47
- expect(registry.doctorChecks[0].fn).toBe(fn);
48
- expect(registry.doctorChecks[0].pluginId).toBe("test-plugin");
49
- });
50
-
51
- it("registerHook adds to registry.hooks with default priority", () => {
52
- const registry = createRegistry();
53
- const api = createPluginApi("test-plugin", registry);
54
- const handler = vi.fn();
55
- api.registerHook("pre:up", handler);
56
- expect(registry.hooks).toHaveLength(1);
57
- expect(registry.hooks[0]).toEqual({
58
- pluginId: "test-plugin",
59
- event: "pre:up",
60
- handler,
61
- priority: 0,
62
- });
63
- });
64
-
65
- it("registerHook respects custom priority", () => {
66
- const registry = createRegistry();
67
- const api = createPluginApi("test-plugin", registry);
68
- const handler = vi.fn();
69
- api.registerHook("post:up", handler, 10);
70
- expect(registry.hooks[0].priority).toBe(10);
71
- });
72
-
73
- it("multiple plugins can register on same registry", () => {
74
- const registry = createRegistry();
75
- const api1 = createPluginApi("plugin-a", registry);
76
- const api2 = createPluginApi("plugin-b", registry);
77
- api1.registerCommand({ name: "cmd-a" });
78
- api2.registerCommand({ name: "cmd-b" });
79
- api1.registerHook("pre:up", vi.fn());
80
- api2.registerHook("pre:up", vi.fn());
81
- expect(registry.commands).toHaveLength(2);
82
- expect(registry.hooks).toHaveLength(2);
83
- expect(registry.commands[0].pluginId).toBe("plugin-a");
84
- expect(registry.commands[1].pluginId).toBe("plugin-b");
85
- });
86
-
87
- it("registerDoctorCheck preserves fn reference", () => {
88
- const registry = createRegistry();
89
- const api = createPluginApi("test", registry);
90
- const checkFn = async (ok, warn, fail) => { ok("test passed"); };
91
- api.registerDoctorCheck({ name: "my-check", fn: checkFn });
92
- expect(registry.doctorChecks[0].fn).toBe(checkFn);
93
- });
94
- });
95
- });
@@ -1,92 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
-
6
- describe("plugins/discovery", () => {
7
- let tmpHome;
8
-
9
- beforeEach(() => {
10
- tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "fops-home-"));
11
- vi.spyOn(os, "homedir").mockReturnValue(tmpHome);
12
- });
13
-
14
- afterEach(() => {
15
- vi.restoreAllMocks();
16
- fs.rmSync(tmpHome, { recursive: true, force: true });
17
- });
18
-
19
- it("returns empty when no plugin dirs exist", async () => {
20
- vi.resetModules();
21
- const { discoverPlugins } = await import("./discovery.js");
22
- const result = discoverPlugins();
23
- const globalPlugins = result.filter((p) => p.source === "global");
24
- expect(globalPlugins).toHaveLength(0);
25
- });
26
-
27
- it("discovers global plugins with fops.plugin.json", async () => {
28
- const pluginDir = path.join(tmpHome, ".fops", "plugins", "my-plugin");
29
- fs.mkdirSync(pluginDir, { recursive: true });
30
- fs.writeFileSync(
31
- path.join(pluginDir, "fops.plugin.json"),
32
- JSON.stringify({ id: "my-plugin", name: "My Plugin", version: "1.0.0" })
33
- );
34
-
35
- vi.resetModules();
36
- const { discoverPlugins } = await import("./discovery.js");
37
- const result = discoverPlugins();
38
- const globalPlugins = result.filter((p) => p.source === "global");
39
- expect(globalPlugins).toHaveLength(1);
40
- expect(globalPlugins[0].id).toBe("my-plugin");
41
- expect(globalPlugins[0].path).toBe(pluginDir);
42
- expect(globalPlugins[0].source).toBe("global");
43
- });
44
-
45
- it("skips global dirs without fops.plugin.json", async () => {
46
- const pluginDir = path.join(tmpHome, ".fops", "plugins", "no-manifest");
47
- fs.mkdirSync(pluginDir, { recursive: true });
48
- fs.writeFileSync(path.join(pluginDir, "index.js"), "");
49
-
50
- vi.resetModules();
51
- const { discoverPlugins } = await import("./discovery.js");
52
- const result = discoverPlugins();
53
- const globalPlugins = result.filter((p) => p.source === "global");
54
- expect(globalPlugins).toHaveLength(0);
55
- });
56
-
57
- it("discovers multiple global plugins", async () => {
58
- for (const name of ["plugin-a", "plugin-b", "plugin-c"]) {
59
- const dir = path.join(tmpHome, ".fops", "plugins", name);
60
- fs.mkdirSync(dir, { recursive: true });
61
- fs.writeFileSync(
62
- path.join(dir, "fops.plugin.json"),
63
- JSON.stringify({ id: name, name, version: "1.0.0" })
64
- );
65
- }
66
-
67
- vi.resetModules();
68
- const { discoverPlugins } = await import("./discovery.js");
69
- const result = discoverPlugins();
70
- const globalPlugins = result.filter((p) => p.source === "global");
71
- expect(globalPlugins).toHaveLength(3);
72
- });
73
-
74
- it("skips files (non-directories) in plugins dir", async () => {
75
- const pluginsDir = path.join(tmpHome, ".fops", "plugins");
76
- fs.mkdirSync(pluginsDir, { recursive: true });
77
- fs.writeFileSync(path.join(pluginsDir, "not-a-dir.txt"), "");
78
-
79
- vi.resetModules();
80
- const { discoverPlugins } = await import("./discovery.js");
81
- const result = discoverPlugins();
82
- const globalPlugins = result.filter((p) => p.source === "global");
83
- expect(globalPlugins).toHaveLength(0);
84
- });
85
-
86
- it("returns array even when ~/.fops/plugins does not exist", async () => {
87
- vi.resetModules();
88
- const { discoverPlugins } = await import("./discovery.js");
89
- const result = discoverPlugins();
90
- expect(Array.isArray(result)).toBe(true);
91
- });
92
- });
@@ -1,118 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { runHook } from "./hooks.js";
3
-
4
- describe("plugins/hooks", () => {
5
- describe("runHook", () => {
6
- it("runs matching handlers", async () => {
7
- const handler = vi.fn();
8
- const registry = {
9
- hooks: [{ event: "pre:up", handler, priority: 0 }],
10
- };
11
- await runHook(registry, "pre:up", { root: "/project" });
12
- expect(handler).toHaveBeenCalledWith({ root: "/project" });
13
- });
14
-
15
- it("skips non-matching events", async () => {
16
- const handler = vi.fn();
17
- const registry = {
18
- hooks: [{ event: "pre:up", handler, priority: 0 }],
19
- };
20
- await runHook(registry, "post:up");
21
- expect(handler).not.toHaveBeenCalled();
22
- });
23
-
24
- it("sorts by priority (higher first)", async () => {
25
- const order = [];
26
- const low = vi.fn(() => order.push("low"));
27
- const high = vi.fn(() => order.push("high"));
28
- const registry = {
29
- hooks: [
30
- { event: "pre:up", handler: low, priority: 1 },
31
- { event: "pre:up", handler: high, priority: 10 },
32
- ],
33
- };
34
- await runHook(registry, "pre:up");
35
- expect(order).toEqual(["high", "low"]);
36
- });
37
-
38
- it("runs handlers sequentially (not parallel)", async () => {
39
- const order = [];
40
- const slow = vi.fn(async () => {
41
- await new Promise((r) => setTimeout(r, 10));
42
- order.push("slow");
43
- });
44
- const fast = vi.fn(() => order.push("fast"));
45
- const registry = {
46
- hooks: [
47
- { event: "test", handler: slow, priority: 10 },
48
- { event: "test", handler: fast, priority: 0 },
49
- ],
50
- };
51
- await runHook(registry, "test");
52
- expect(order).toEqual(["slow", "fast"]);
53
- });
54
-
55
- it("handles empty hooks array", async () => {
56
- const registry = { hooks: [] };
57
- await expect(runHook(registry, "pre:up")).resolves.toBeUndefined();
58
- });
59
-
60
- it("passes context to all handlers", async () => {
61
- const h1 = vi.fn();
62
- const h2 = vi.fn();
63
- const ctx = { root: "/proj", extra: "data" };
64
- const registry = {
65
- hooks: [
66
- { event: "test", handler: h1, priority: 0 },
67
- { event: "test", handler: h2, priority: 0 },
68
- ],
69
- };
70
- await runHook(registry, "test", ctx);
71
- expect(h1).toHaveBeenCalledWith(ctx);
72
- expect(h2).toHaveBeenCalledWith(ctx);
73
- });
74
-
75
- it("uses empty object as default context", async () => {
76
- const handler = vi.fn();
77
- const registry = {
78
- hooks: [{ event: "test", handler, priority: 0 }],
79
- };
80
- await runHook(registry, "test");
81
- expect(handler).toHaveBeenCalledWith({});
82
- });
83
-
84
- it("handles three priority levels correctly", async () => {
85
- const order = [];
86
- const registry = {
87
- hooks: [
88
- { event: "e", handler: () => order.push("med"), priority: 5 },
89
- { event: "e", handler: () => order.push("low"), priority: 1 },
90
- { event: "e", handler: () => order.push("high"), priority: 10 },
91
- ],
92
- };
93
- await runHook(registry, "e");
94
- expect(order).toEqual(["high", "med", "low"]);
95
- });
96
-
97
- it("only runs handlers for the exact event name", async () => {
98
- const h1 = vi.fn();
99
- const h2 = vi.fn();
100
- const registry = {
101
- hooks: [
102
- { event: "pre:up", handler: h1, priority: 0 },
103
- { event: "pre:down", handler: h2, priority: 0 },
104
- ],
105
- };
106
- await runHook(registry, "pre:up");
107
- expect(h1).toHaveBeenCalled();
108
- expect(h2).not.toHaveBeenCalled();
109
- });
110
-
111
- it("handler errors propagate", async () => {
112
- const registry = {
113
- hooks: [{ event: "test", handler: () => { throw new Error("boom"); }, priority: 0 }],
114
- };
115
- await expect(runHook(registry, "test")).rejects.toThrow("boom");
116
- });
117
- });
118
- });
@@ -1,106 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { validateManifest } from "./manifest.js";
6
-
7
- describe("plugins/manifest", () => {
8
- let tmpDir;
9
-
10
- beforeEach(() => {
11
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "fops-manifest-"));
12
- });
13
-
14
- afterEach(() => {
15
- fs.rmSync(tmpDir, { recursive: true, force: true });
16
- });
17
-
18
- it("returns null when no fops.plugin.json exists", () => {
19
- expect(validateManifest(tmpDir)).toBe(null);
20
- });
21
-
22
- it("returns null for invalid JSON", () => {
23
- fs.writeFileSync(path.join(tmpDir, "fops.plugin.json"), "not json{{{");
24
- expect(validateManifest(tmpDir)).toBe(null);
25
- });
26
-
27
- it("returns null when id is missing", () => {
28
- fs.writeFileSync(
29
- path.join(tmpDir, "fops.plugin.json"),
30
- JSON.stringify({ name: "Test", version: "1.0.0" })
31
- );
32
- expect(validateManifest(tmpDir)).toBe(null);
33
- });
34
-
35
- it("returns null when name is missing", () => {
36
- fs.writeFileSync(
37
- path.join(tmpDir, "fops.plugin.json"),
38
- JSON.stringify({ id: "test", version: "1.0.0" })
39
- );
40
- expect(validateManifest(tmpDir)).toBe(null);
41
- });
42
-
43
- it("returns null when version is missing", () => {
44
- fs.writeFileSync(
45
- path.join(tmpDir, "fops.plugin.json"),
46
- JSON.stringify({ id: "test", name: "Test" })
47
- );
48
- expect(validateManifest(tmpDir)).toBe(null);
49
- });
50
-
51
- it("returns null when fields are not strings", () => {
52
- fs.writeFileSync(
53
- path.join(tmpDir, "fops.plugin.json"),
54
- JSON.stringify({ id: "test", name: "Test", version: 1 })
55
- );
56
- expect(validateManifest(tmpDir)).toBe(null);
57
- });
58
-
59
- it("returns null when id is empty string", () => {
60
- fs.writeFileSync(
61
- path.join(tmpDir, "fops.plugin.json"),
62
- JSON.stringify({ id: "", name: "Test", version: "1.0.0" })
63
- );
64
- expect(validateManifest(tmpDir)).toBe(null);
65
- });
66
-
67
- it("returns null when fields are numbers", () => {
68
- fs.writeFileSync(
69
- path.join(tmpDir, "fops.plugin.json"),
70
- JSON.stringify({ id: 1, name: 2, version: 3 })
71
- );
72
- expect(validateManifest(tmpDir)).toBe(null);
73
- });
74
-
75
- it("returns manifest when all required fields present", () => {
76
- const manifest = { id: "test-plugin", name: "Test Plugin", version: "1.0.0" };
77
- fs.writeFileSync(path.join(tmpDir, "fops.plugin.json"), JSON.stringify(manifest));
78
- const result = validateManifest(tmpDir);
79
- expect(result).toEqual(manifest);
80
- });
81
-
82
- it("preserves extra fields in manifest", () => {
83
- const manifest = {
84
- id: "test-plugin",
85
- name: "Test Plugin",
86
- version: "1.0.0",
87
- description: "A test",
88
- author: "test",
89
- skills: ["skills/foo"],
90
- };
91
- fs.writeFileSync(path.join(tmpDir, "fops.plugin.json"), JSON.stringify(manifest));
92
- const result = validateManifest(tmpDir);
93
- expect(result.description).toBe("A test");
94
- expect(result.skills).toEqual(["skills/foo"]);
95
- });
96
-
97
- it("returns null for empty JSON object", () => {
98
- fs.writeFileSync(path.join(tmpDir, "fops.plugin.json"), "{}");
99
- expect(validateManifest(tmpDir)).toBe(null);
100
- });
101
-
102
- it("returns null for empty file", () => {
103
- fs.writeFileSync(path.join(tmpDir, "fops.plugin.json"), "");
104
- expect(validateManifest(tmpDir)).toBe(null);
105
- });
106
- });
@@ -1,43 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createRegistry } from "./registry.js";
3
-
4
- describe("plugins/registry", () => {
5
- describe("createRegistry", () => {
6
- it("returns object with expected arrays", () => {
7
- const reg = createRegistry();
8
- expect(reg).toEqual({
9
- plugins: [],
10
- commands: [],
11
- doctorChecks: [],
12
- hooks: [],
13
- skills: [],
14
- });
15
- });
16
-
17
- it("creates independent instances", () => {
18
- const a = createRegistry();
19
- const b = createRegistry();
20
- a.plugins.push({ id: "test" });
21
- expect(b.plugins).toHaveLength(0);
22
- });
23
-
24
- it("all arrays are mutable", () => {
25
- const reg = createRegistry();
26
- reg.plugins.push({ id: "p1" });
27
- reg.commands.push({ pluginId: "p1", spec: {} });
28
- reg.doctorChecks.push({ pluginId: "p1", name: "check" });
29
- reg.hooks.push({ pluginId: "p1", event: "test" });
30
- reg.skills.push({ pluginId: "p1", name: "skill" });
31
- expect(reg.plugins).toHaveLength(1);
32
- expect(reg.commands).toHaveLength(1);
33
- expect(reg.doctorChecks).toHaveLength(1);
34
- expect(reg.hooks).toHaveLength(1);
35
- expect(reg.skills).toHaveLength(1);
36
- });
37
-
38
- it("has exactly 5 keys", () => {
39
- const reg = createRegistry();
40
- expect(Object.keys(reg)).toHaveLength(5);
41
- });
42
- });
43
- });