@enactprotocol/cli 2.0.9 → 2.0.11

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.
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Tests for the init command
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { Command } from "commander";
9
+ import { configureInitCommand } from "../../src/commands/init";
10
+
11
+ describe("init command", () => {
12
+ describe("command configuration", () => {
13
+ test("configures init command on program", () => {
14
+ const program = new Command();
15
+ configureInitCommand(program);
16
+
17
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
18
+ expect(initCmd).toBeDefined();
19
+ });
20
+
21
+ test("has correct description", () => {
22
+ const program = new Command();
23
+ configureInitCommand(program);
24
+
25
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
26
+ expect(initCmd?.description()).toBe("Initialize Enact in the current directory");
27
+ });
28
+
29
+ test("has --name option", () => {
30
+ const program = new Command();
31
+ configureInitCommand(program);
32
+
33
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
34
+ const opts = initCmd?.options ?? [];
35
+ const nameOpt = opts.find((o) => o.long === "--name");
36
+ expect(nameOpt).toBeDefined();
37
+ expect(nameOpt?.short).toBe("-n");
38
+ });
39
+
40
+ test("has --force option", () => {
41
+ const program = new Command();
42
+ configureInitCommand(program);
43
+
44
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
45
+ const opts = initCmd?.options ?? [];
46
+ const forceOpt = opts.find((o) => o.long === "--force");
47
+ expect(forceOpt).toBeDefined();
48
+ expect(forceOpt?.short).toBe("-f");
49
+ });
50
+
51
+ test("has --tool option", () => {
52
+ const program = new Command();
53
+ configureInitCommand(program);
54
+
55
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
56
+ const opts = initCmd?.options ?? [];
57
+ const toolOpt = opts.find((o) => o.long === "--tool");
58
+ expect(toolOpt).toBeDefined();
59
+ });
60
+
61
+ test("has --agent option", () => {
62
+ const program = new Command();
63
+ configureInitCommand(program);
64
+
65
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
66
+ const opts = initCmd?.options ?? [];
67
+ const agentOpt = opts.find((o) => o.long === "--agent");
68
+ expect(agentOpt).toBeDefined();
69
+ });
70
+
71
+ test("has --claude option", () => {
72
+ const program = new Command();
73
+ configureInitCommand(program);
74
+
75
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
76
+ const opts = initCmd?.options ?? [];
77
+ const claudeOpt = opts.find((o) => o.long === "--claude");
78
+ expect(claudeOpt).toBeDefined();
79
+ });
80
+
81
+ test("has --verbose option", () => {
82
+ const program = new Command();
83
+ configureInitCommand(program);
84
+
85
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
86
+ const opts = initCmd?.options ?? [];
87
+ const verboseOpt = opts.find((o) => o.long === "--verbose");
88
+ expect(verboseOpt).toBeDefined();
89
+ });
90
+ });
91
+
92
+ describe("file generation", () => {
93
+ const testDir = join(import.meta.dir, ".test-init-temp");
94
+
95
+ beforeEach(() => {
96
+ // Clean up before each test
97
+ if (existsSync(testDir)) {
98
+ rmSync(testDir, { recursive: true });
99
+ }
100
+ mkdirSync(testDir, { recursive: true });
101
+ });
102
+
103
+ afterEach(() => {
104
+ // Clean up after each test
105
+ if (existsSync(testDir)) {
106
+ rmSync(testDir, { recursive: true });
107
+ }
108
+ });
109
+
110
+ test("default mode creates enact.md", async () => {
111
+ const program = new Command();
112
+ program.exitOverride(); // Prevent process.exit
113
+ configureInitCommand(program);
114
+
115
+ // Change to test directory and run init
116
+ const originalCwd = process.cwd();
117
+ process.chdir(testDir);
118
+
119
+ try {
120
+ await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
121
+ } catch {
122
+ // Command may throw due to exitOverride
123
+ } finally {
124
+ process.chdir(originalCwd);
125
+ }
126
+
127
+ const manifestPath = join(testDir, "enact.md");
128
+ expect(existsSync(manifestPath)).toBe(true);
129
+
130
+ const content = readFileSync(manifestPath, "utf-8");
131
+ expect(content).toContain("name: test/my-tool");
132
+ expect(content).toContain("description:");
133
+ expect(content).toContain("command:");
134
+ });
135
+
136
+ test("default mode creates AGENTS.md for tool development", async () => {
137
+ const program = new Command();
138
+ program.exitOverride();
139
+ configureInitCommand(program);
140
+
141
+ const originalCwd = process.cwd();
142
+ process.chdir(testDir);
143
+
144
+ try {
145
+ await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
146
+ } catch {
147
+ // Command may throw due to exitOverride
148
+ } finally {
149
+ process.chdir(originalCwd);
150
+ }
151
+
152
+ const agentsPath = join(testDir, "AGENTS.md");
153
+ expect(existsSync(agentsPath)).toBe(true);
154
+
155
+ const content = readFileSync(agentsPath, "utf-8");
156
+ expect(content).toContain("enact run");
157
+ expect(content).toContain("enact.md");
158
+ expect(content).toContain("Parameter Substitution");
159
+ });
160
+
161
+ test("--agent mode creates AGENTS.md for tool consumers", async () => {
162
+ const program = new Command();
163
+ program.exitOverride();
164
+ configureInitCommand(program);
165
+
166
+ const originalCwd = process.cwd();
167
+ process.chdir(testDir);
168
+
169
+ try {
170
+ await program.parseAsync(["node", "test", "init", "--agent"]);
171
+ } catch {
172
+ // Command may throw due to exitOverride
173
+ } finally {
174
+ process.chdir(originalCwd);
175
+ }
176
+
177
+ const agentsPath = join(testDir, "AGENTS.md");
178
+ expect(existsSync(agentsPath)).toBe(true);
179
+
180
+ const content = readFileSync(agentsPath, "utf-8");
181
+ expect(content).toContain("enact search");
182
+ expect(content).toContain("enact install");
183
+ expect(content).toContain("Finding & Installing Tools");
184
+
185
+ // Should NOT create enact.md in agent mode
186
+ const manifestPath = join(testDir, "enact.md");
187
+ expect(existsSync(manifestPath)).toBe(false);
188
+ });
189
+
190
+ test("--claude mode creates CLAUDE.md", async () => {
191
+ const program = new Command();
192
+ program.exitOverride();
193
+ configureInitCommand(program);
194
+
195
+ const originalCwd = process.cwd();
196
+ process.chdir(testDir);
197
+
198
+ try {
199
+ await program.parseAsync(["node", "test", "init", "--claude"]);
200
+ } catch {
201
+ // Command may throw due to exitOverride
202
+ } finally {
203
+ process.chdir(originalCwd);
204
+ }
205
+
206
+ const claudePath = join(testDir, "CLAUDE.md");
207
+ expect(existsSync(claudePath)).toBe(true);
208
+
209
+ const content = readFileSync(claudePath, "utf-8");
210
+ expect(content).toContain("enact run");
211
+ expect(content).toContain("enact search");
212
+
213
+ // Should NOT create enact.md or AGENTS.md in claude mode
214
+ expect(existsSync(join(testDir, "enact.md"))).toBe(false);
215
+ expect(existsSync(join(testDir, "AGENTS.md"))).toBe(false);
216
+ });
217
+
218
+ test("enact.md contains valid YAML frontmatter", async () => {
219
+ const program = new Command();
220
+ program.exitOverride();
221
+ configureInitCommand(program);
222
+
223
+ const originalCwd = process.cwd();
224
+ process.chdir(testDir);
225
+
226
+ try {
227
+ await program.parseAsync(["node", "test", "init", "--name", "myorg/utils/greeter"]);
228
+ } catch {
229
+ // Command may throw due to exitOverride
230
+ } finally {
231
+ process.chdir(originalCwd);
232
+ }
233
+
234
+ const content = readFileSync(join(testDir, "enact.md"), "utf-8");
235
+
236
+ // Check frontmatter structure
237
+ expect(content.startsWith("---")).toBe(true);
238
+ expect(content).toContain("name: myorg/utils/greeter");
239
+ expect(content).toContain("version:");
240
+ expect(content).toContain("from:");
241
+ expect(content).toContain("inputSchema:");
242
+ expect(content).toContain("command:");
243
+
244
+ // Check it has closing frontmatter and markdown body
245
+ const parts = content.split("---");
246
+ expect(parts.length).toBeGreaterThanOrEqual(3); // empty, frontmatter, body
247
+ });
248
+
249
+ test("AGENTS.md for tools contains development instructions", async () => {
250
+ const program = new Command();
251
+ program.exitOverride();
252
+ configureInitCommand(program);
253
+
254
+ const originalCwd = process.cwd();
255
+ process.chdir(testDir);
256
+
257
+ try {
258
+ await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
259
+ } catch {
260
+ // Command may throw due to exitOverride
261
+ } finally {
262
+ process.chdir(originalCwd);
263
+ }
264
+
265
+ const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
266
+
267
+ // Should contain tool development-specific content
268
+ expect(content).toContain("enact sign");
269
+ expect(content).toContain("enact publish");
270
+ expect(content).toContain("${param}");
271
+ expect(content).toContain("build:");
272
+ expect(content).toContain("/work");
273
+ });
274
+
275
+ test("AGENTS.md for agent projects contains usage instructions", async () => {
276
+ const program = new Command();
277
+ program.exitOverride();
278
+ configureInitCommand(program);
279
+
280
+ const originalCwd = process.cwd();
281
+ process.chdir(testDir);
282
+
283
+ try {
284
+ await program.parseAsync(["node", "test", "init", "--agent"]);
285
+ } catch {
286
+ // Command may throw due to exitOverride
287
+ } finally {
288
+ process.chdir(originalCwd);
289
+ }
290
+
291
+ const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
292
+
293
+ // Should contain consumer-focused content
294
+ expect(content).toContain("enact search");
295
+ expect(content).toContain("enact install");
296
+ expect(content).toContain("enact list");
297
+ expect(content).toContain(".enact/tools.json");
298
+ });
299
+ });
300
+
301
+ describe("option conflicts", () => {
302
+ test("--tool is the default when no mode specified", () => {
303
+ const program = new Command();
304
+ configureInitCommand(program);
305
+
306
+ const initCmd = program.commands.find((cmd) => cmd.name() === "init");
307
+ const opts = initCmd?.options ?? [];
308
+ const toolOpt = opts.find((o) => o.long === "--tool");
309
+
310
+ // Description should indicate it's the default
311
+ expect(toolOpt?.description).toContain("default");
312
+ });
313
+ });
314
+
315
+ describe("template content quality", () => {
316
+ test("tool template has all required manifest fields", async () => {
317
+ const program = new Command();
318
+ program.exitOverride();
319
+ configureInitCommand(program);
320
+
321
+ const testDir = join(import.meta.dir, ".test-init-quality");
322
+ if (existsSync(testDir)) {
323
+ rmSync(testDir, { recursive: true });
324
+ }
325
+ mkdirSync(testDir, { recursive: true });
326
+
327
+ const originalCwd = process.cwd();
328
+ process.chdir(testDir);
329
+
330
+ try {
331
+ await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
332
+ } catch {
333
+ // Command may throw due to exitOverride
334
+ } finally {
335
+ process.chdir(originalCwd);
336
+ }
337
+
338
+ const content = readFileSync(join(testDir, "enact.md"), "utf-8");
339
+
340
+ // Required fields per spec
341
+ expect(content).toContain("name:");
342
+ expect(content).toContain("description:");
343
+
344
+ // Recommended fields
345
+ expect(content).toContain("version:");
346
+ expect(content).toContain("enact:");
347
+ expect(content).toContain("from:");
348
+ expect(content).toContain("inputSchema:");
349
+ expect(content).toContain("command:");
350
+
351
+ // Clean up
352
+ rmSync(testDir, { recursive: true });
353
+ });
354
+
355
+ test("AGENTS.md does not contain unnecessary verbosity", async () => {
356
+ const program = new Command();
357
+ program.exitOverride();
358
+ configureInitCommand(program);
359
+
360
+ const testDir = join(import.meta.dir, ".test-init-verbosity");
361
+ if (existsSync(testDir)) {
362
+ rmSync(testDir, { recursive: true });
363
+ }
364
+ mkdirSync(testDir, { recursive: true });
365
+
366
+ const originalCwd = process.cwd();
367
+ process.chdir(testDir);
368
+
369
+ try {
370
+ await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
371
+ } catch {
372
+ // Command may throw due to exitOverride
373
+ } finally {
374
+ process.chdir(originalCwd);
375
+ }
376
+
377
+ const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
378
+
379
+ // Should be concise - check line count is reasonable
380
+ const lines = content.split("\n").length;
381
+ expect(lines).toBeLessThan(100); // Should be concise
382
+
383
+ // Clean up
384
+ rmSync(testDir, { recursive: true });
385
+ });
386
+ });
387
+ });