@enactprotocol/cli 2.1.4 → 2.1.6

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,231 @@
1
+ /**
2
+ * Tests for the learn command
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import type { ToolVersionInfo } from "@enactprotocol/api";
7
+ import { Command } from "commander";
8
+ import { configureLearnCommand } from "../../src/commands/learn";
9
+
10
+ describe("learn command", () => {
11
+ describe("command configuration", () => {
12
+ test("configures learn command on program", () => {
13
+ const program = new Command();
14
+ configureLearnCommand(program);
15
+
16
+ const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
17
+ expect(learnCmd).toBeDefined();
18
+ });
19
+
20
+ test("has correct description", () => {
21
+ const program = new Command();
22
+ configureLearnCommand(program);
23
+
24
+ const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
25
+ expect(learnCmd?.description()).toBe("Display documentation (enact.md) for a tool");
26
+ });
27
+
28
+ test("accepts tool argument", () => {
29
+ const program = new Command();
30
+ configureLearnCommand(program);
31
+
32
+ const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
33
+ const args = learnCmd?.registeredArguments ?? [];
34
+ expect(args.length).toBeGreaterThan(0);
35
+ expect(args[0]?.name()).toBe("tool");
36
+ });
37
+
38
+ test("has --ver option for specifying version", () => {
39
+ const program = new Command();
40
+ configureLearnCommand(program);
41
+
42
+ const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
43
+ const opts = learnCmd?.options ?? [];
44
+ const verOpt = opts.find((o) => o.long === "--ver");
45
+ expect(verOpt).toBeDefined();
46
+ });
47
+
48
+ test("has --json option", () => {
49
+ const program = new Command();
50
+ configureLearnCommand(program);
51
+
52
+ const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
53
+ const opts = learnCmd?.options ?? [];
54
+ const jsonOpt = opts.find((o) => o.long === "--json");
55
+ expect(jsonOpt).toBeDefined();
56
+ });
57
+
58
+ test("does not have --verbose option (always shows full docs)", () => {
59
+ const program = new Command();
60
+ configureLearnCommand(program);
61
+
62
+ const learnCmd = program.commands.find((cmd) => cmd.name() === "learn");
63
+ const opts = learnCmd?.options ?? [];
64
+ const verboseOpt = opts.find((o) => o.long === "--verbose");
65
+ expect(verboseOpt).toBeUndefined();
66
+ });
67
+ });
68
+
69
+ describe("tool name parsing", () => {
70
+ test("parses simple tool name", () => {
71
+ const toolName = "my-tool";
72
+ expect(toolName).not.toContain("@");
73
+ expect(toolName).not.toContain("/");
74
+ });
75
+
76
+ test("parses namespaced tool name", () => {
77
+ const toolName = "enact/context7/docs";
78
+ const parts = toolName.split("/");
79
+ expect(parts[0]).toBe("enact");
80
+ expect(parts[1]).toBe("context7");
81
+ expect(parts[2]).toBe("docs");
82
+ });
83
+
84
+ test("parses scoped tool name", () => {
85
+ const toolName = "@org/my-tool";
86
+ expect(toolName.startsWith("@")).toBe(true);
87
+ const parts = toolName.slice(1).split("/");
88
+ expect(parts[0]).toBe("org");
89
+ expect(parts[1]).toBe("my-tool");
90
+ });
91
+ });
92
+
93
+ describe("documentation content", () => {
94
+ test("ToolVersionInfo type includes rawManifest field for enact.md content", () => {
95
+ const mockVersion: ToolVersionInfo = {
96
+ name: "test/tool",
97
+ version: "1.0.0",
98
+ description: "Test tool",
99
+ license: "MIT",
100
+ yanked: false,
101
+ manifest: { enact: "2.0.0" },
102
+ rawManifest: "---\nenact: 2.0.0\n---\n# Test Tool\n\nThis is a test tool.",
103
+ bundle: {
104
+ hash: "sha256:abc123",
105
+ size: 1024,
106
+ downloadUrl: "https://example.com/bundle.tar.gz",
107
+ },
108
+ attestations: [],
109
+ publishedBy: { username: "testuser" },
110
+ publishedAt: new Date(),
111
+ downloads: 100,
112
+ };
113
+
114
+ expect(mockVersion.rawManifest).toBeDefined();
115
+ expect(mockVersion.rawManifest).toContain("# Test Tool");
116
+ });
117
+
118
+ test("ToolVersionInfo allows undefined rawManifest", () => {
119
+ const mockVersion: ToolVersionInfo = {
120
+ name: "test/tool",
121
+ version: "1.0.0",
122
+ description: "Test tool",
123
+ license: "MIT",
124
+ yanked: false,
125
+ manifest: { enact: "2.0.0" },
126
+ // rawManifest is optional - not provided
127
+ bundle: {
128
+ hash: "sha256:abc123",
129
+ size: 1024,
130
+ downloadUrl: "https://example.com/bundle.tar.gz",
131
+ },
132
+ attestations: [],
133
+ publishedBy: { username: "testuser" },
134
+ publishedAt: new Date(),
135
+ downloads: 100,
136
+ };
137
+
138
+ expect(mockVersion.rawManifest).toBeUndefined();
139
+ });
140
+
141
+ test("enact.md content should contain frontmatter and markdown", () => {
142
+ const enactMdContent = `---
143
+ enact: 2.0.0
144
+ name: test/tool
145
+ version: 1.0.0
146
+ ---
147
+
148
+ # Test Tool
149
+
150
+ Documentation here.`;
151
+
152
+ // Verify frontmatter is present
153
+ expect(enactMdContent).toContain("---");
154
+ expect(enactMdContent).toContain("enact: 2.0.0");
155
+
156
+ // Verify markdown content
157
+ expect(enactMdContent).toContain("# Test Tool");
158
+ expect(enactMdContent).toContain("Documentation here.");
159
+ });
160
+
161
+ test("documentation includes parameter descriptions", () => {
162
+ const enactMdContent = `---
163
+ enact: 2.0.0
164
+ name: enact/context7/docs
165
+ version: 1.0.0
166
+ inputSchema:
167
+ type: object
168
+ properties:
169
+ library_name:
170
+ type: string
171
+ description: The name of the library to fetch documentation for
172
+ ---
173
+
174
+ # Context7 Documentation Fetcher
175
+
176
+ Fetches up-to-date documentation for any library.
177
+
178
+ ## Parameters
179
+
180
+ - **library_name** (required): The name of the library to fetch documentation for
181
+ `;
182
+
183
+ expect(enactMdContent).toContain("library_name");
184
+ expect(enactMdContent).toContain("Parameters");
185
+ expect(enactMdContent).toContain("required");
186
+ });
187
+
188
+ test("documentation includes usage examples", () => {
189
+ const enactMdContent = `---
190
+ enact: 2.0.0
191
+ name: test/tool
192
+ ---
193
+
194
+ # Test Tool
195
+
196
+ ## Usage
197
+
198
+ \`\`\`bash
199
+ enact run test/tool --input '{"query": "hello"}'
200
+ \`\`\`
201
+ `;
202
+
203
+ expect(enactMdContent).toContain("## Usage");
204
+ expect(enactMdContent).toContain("enact run");
205
+ });
206
+ });
207
+
208
+ describe("JSON output format", () => {
209
+ test("JSON output includes name, version, and documentation", () => {
210
+ const jsonOutput = {
211
+ name: "enact/context7/docs",
212
+ version: "1.0.1",
213
+ documentation: "---\nenact: 2.0.0\n---\n# Context7 Docs\n\nFetches documentation.",
214
+ };
215
+
216
+ expect(jsonOutput.name).toBe("enact/context7/docs");
217
+ expect(jsonOutput.version).toBe("1.0.1");
218
+ expect(jsonOutput.documentation).toContain("# Context7 Docs");
219
+ });
220
+
221
+ test("JSON output handles missing documentation", () => {
222
+ const jsonOutput = {
223
+ name: "test/tool",
224
+ version: "1.0.0",
225
+ documentation: null,
226
+ };
227
+
228
+ expect(jsonOutput.documentation).toBeNull();
229
+ });
230
+ });
231
+ });
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
- import { existsSync, mkdirSync, rmSync } from "node:fs";
6
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { Command } from "commander";
9
9
  import { configureRunCommand } from "../../src/commands/run";
@@ -71,6 +71,16 @@ describe("run command", () => {
71
71
  expect(inputOpt).toBeDefined();
72
72
  });
73
73
 
74
+ test("has --input-file option for JSON file input", () => {
75
+ const program = new Command();
76
+ configureRunCommand(program);
77
+
78
+ const runCmd = program.commands.find((cmd) => cmd.name() === "run");
79
+ const opts = runCmd?.options ?? [];
80
+ const inputFileOpt = opts.find((o) => o.long === "--input-file");
81
+ expect(inputFileOpt).toBeDefined();
82
+ });
83
+
74
84
  test("has --timeout option", () => {
75
85
  const program = new Command();
76
86
  configureRunCommand(program);
@@ -228,4 +238,63 @@ describe("run command", () => {
228
238
  expect(parseTimeout("30")).toBe(30000);
229
239
  });
230
240
  });
241
+
242
+ describe("input file handling", () => {
243
+ test("JSON input file can be parsed", () => {
244
+ // Create a test JSON file
245
+ const inputFilePath = join(FIXTURES_DIR, "test-inputs.json");
246
+ const inputData = { name: "Alice", count: 5, nested: { key: "value" } };
247
+ writeFileSync(inputFilePath, JSON.stringify(inputData));
248
+
249
+ // Verify the file can be read and parsed
250
+ const content = require("node:fs").readFileSync(inputFilePath, "utf-8");
251
+ const parsed = JSON.parse(content);
252
+
253
+ expect(parsed.name).toBe("Alice");
254
+ expect(parsed.count).toBe(5);
255
+ expect(parsed.nested.key).toBe("value");
256
+ });
257
+
258
+ test("JSON input file with optional params can omit them", () => {
259
+ // This is the recommended pattern for optional params
260
+ const inputFilePath = join(FIXTURES_DIR, "optional-inputs.json");
261
+ // Only required param provided, optional params omitted
262
+ const inputData = { name: "Alice" };
263
+ writeFileSync(inputFilePath, JSON.stringify(inputData));
264
+
265
+ const content = require("node:fs").readFileSync(inputFilePath, "utf-8");
266
+ const parsed = JSON.parse(content);
267
+
268
+ expect(parsed.name).toBe("Alice");
269
+ expect(parsed.greeting).toBeUndefined();
270
+ });
271
+
272
+ test("JSON input file with explicit empty values", () => {
273
+ // User can explicitly set empty values for optional params
274
+ const inputFilePath = join(FIXTURES_DIR, "explicit-empty.json");
275
+ const inputData = { name: "Alice", prefix: "", suffix: null };
276
+ writeFileSync(inputFilePath, JSON.stringify(inputData));
277
+
278
+ const content = require("node:fs").readFileSync(inputFilePath, "utf-8");
279
+ const parsed = JSON.parse(content);
280
+
281
+ expect(parsed.name).toBe("Alice");
282
+ expect(parsed.prefix).toBe("");
283
+ expect(parsed.suffix).toBeNull();
284
+ });
285
+
286
+ test("input priority: --input overrides --args overrides --input-file", () => {
287
+ // Simulate the merge logic from parseInputArgs
288
+ const fromFile = { a: "file", b: "file", c: "file" };
289
+ const fromArgs = { b: "args", c: "args" };
290
+ const fromInput = { c: "input" };
291
+
292
+ // Merge in order: file -> args -> input
293
+ const merged = { ...fromFile, ...fromArgs, ...fromInput };
294
+
295
+ expect(merged.a).toBe("file"); // Only from file
296
+ expect(merged.b).toBe("args"); // Overridden by args
297
+ expect(merged.c).toBe("input"); // Overridden by input
298
+ });
299
+ });
231
300
  });