@enactprotocol/cli 1.2.13 → 2.0.0

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.
Files changed (73) hide show
  1. package/README.md +88 -0
  2. package/package.json +34 -38
  3. package/src/commands/auth/index.ts +940 -0
  4. package/src/commands/cache/index.ts +361 -0
  5. package/src/commands/config/README.md +239 -0
  6. package/src/commands/config/index.ts +164 -0
  7. package/src/commands/env/README.md +197 -0
  8. package/src/commands/env/index.ts +392 -0
  9. package/src/commands/exec/README.md +110 -0
  10. package/src/commands/exec/index.ts +195 -0
  11. package/src/commands/get/index.ts +198 -0
  12. package/src/commands/index.ts +30 -0
  13. package/src/commands/inspect/index.ts +264 -0
  14. package/src/commands/install/README.md +146 -0
  15. package/src/commands/install/index.ts +682 -0
  16. package/src/commands/list/README.md +115 -0
  17. package/src/commands/list/index.ts +138 -0
  18. package/src/commands/publish/index.ts +350 -0
  19. package/src/commands/report/index.ts +366 -0
  20. package/src/commands/run/README.md +124 -0
  21. package/src/commands/run/index.ts +686 -0
  22. package/src/commands/search/index.ts +368 -0
  23. package/src/commands/setup/index.ts +274 -0
  24. package/src/commands/sign/index.ts +652 -0
  25. package/src/commands/trust/README.md +214 -0
  26. package/src/commands/trust/index.ts +453 -0
  27. package/src/commands/unyank/index.ts +107 -0
  28. package/src/commands/yank/index.ts +143 -0
  29. package/src/index.ts +96 -0
  30. package/src/types.ts +81 -0
  31. package/src/utils/errors.ts +409 -0
  32. package/src/utils/exit-codes.ts +159 -0
  33. package/src/utils/ignore.ts +147 -0
  34. package/src/utils/index.ts +107 -0
  35. package/src/utils/output.ts +242 -0
  36. package/src/utils/spinner.ts +214 -0
  37. package/tests/commands/auth.test.ts +217 -0
  38. package/tests/commands/cache.test.ts +286 -0
  39. package/tests/commands/config.test.ts +277 -0
  40. package/tests/commands/env.test.ts +293 -0
  41. package/tests/commands/exec.test.ts +112 -0
  42. package/tests/commands/get.test.ts +179 -0
  43. package/tests/commands/inspect.test.ts +201 -0
  44. package/tests/commands/install-integration.test.ts +343 -0
  45. package/tests/commands/install.test.ts +288 -0
  46. package/tests/commands/list.test.ts +160 -0
  47. package/tests/commands/publish.test.ts +186 -0
  48. package/tests/commands/report.test.ts +194 -0
  49. package/tests/commands/run.test.ts +231 -0
  50. package/tests/commands/search.test.ts +131 -0
  51. package/tests/commands/sign.test.ts +164 -0
  52. package/tests/commands/trust.test.ts +236 -0
  53. package/tests/commands/unyank.test.ts +114 -0
  54. package/tests/commands/yank.test.ts +154 -0
  55. package/tests/e2e.test.ts +554 -0
  56. package/tests/fixtures/calculator/enact.yaml +34 -0
  57. package/tests/fixtures/echo-tool/enact.md +31 -0
  58. package/tests/fixtures/env-tool/enact.yaml +19 -0
  59. package/tests/fixtures/greeter/enact.yaml +18 -0
  60. package/tests/fixtures/invalid-tool/enact.yaml +4 -0
  61. package/tests/index.test.ts +8 -0
  62. package/tests/types.test.ts +84 -0
  63. package/tests/utils/errors.test.ts +303 -0
  64. package/tests/utils/exit-codes.test.ts +189 -0
  65. package/tests/utils/ignore.test.ts +461 -0
  66. package/tests/utils/output.test.ts +126 -0
  67. package/tsconfig.json +17 -0
  68. package/tsconfig.tsbuildinfo +1 -0
  69. package/dist/index.js +0 -231612
  70. package/dist/index.js.bak +0 -231611
  71. package/dist/web/static/app.js +0 -663
  72. package/dist/web/static/index.html +0 -117
  73. package/dist/web/static/style.css +0 -291
@@ -0,0 +1,461 @@
1
+ /**
2
+ * Tests for file ignore utilities
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import {
10
+ ALWAYS_IGNORE,
11
+ loadGitignore,
12
+ matchesPattern,
13
+ parseGitignoreContent,
14
+ shouldIgnore,
15
+ } from "../../src/utils/ignore";
16
+
17
+ describe("file ignore utilities", () => {
18
+ describe("ALWAYS_IGNORE list", () => {
19
+ test("includes .env files", () => {
20
+ expect(ALWAYS_IGNORE).toContain(".env");
21
+ expect(ALWAYS_IGNORE).toContain(".env.local");
22
+ expect(ALWAYS_IGNORE).toContain(".env.development");
23
+ expect(ALWAYS_IGNORE).toContain(".env.production");
24
+ });
25
+
26
+ test("includes secret key files", () => {
27
+ expect(ALWAYS_IGNORE).toContain("*.pem");
28
+ expect(ALWAYS_IGNORE).toContain("*.key");
29
+ expect(ALWAYS_IGNORE).toContain("*.p12");
30
+ expect(ALWAYS_IGNORE).toContain("*.pfx");
31
+ });
32
+
33
+ test("includes version control directories", () => {
34
+ expect(ALWAYS_IGNORE).toContain(".git");
35
+ expect(ALWAYS_IGNORE).toContain(".gitignore");
36
+ expect(ALWAYS_IGNORE).toContain(".gitattributes");
37
+ });
38
+
39
+ test("includes IDE directories", () => {
40
+ expect(ALWAYS_IGNORE).toContain(".vscode");
41
+ expect(ALWAYS_IGNORE).toContain(".idea");
42
+ });
43
+
44
+ test("includes OS files", () => {
45
+ expect(ALWAYS_IGNORE).toContain(".DS_Store");
46
+ expect(ALWAYS_IGNORE).toContain("Thumbs.db");
47
+ });
48
+
49
+ test("includes dependency directories", () => {
50
+ expect(ALWAYS_IGNORE).toContain("node_modules");
51
+ expect(ALWAYS_IGNORE).toContain("__pycache__");
52
+ expect(ALWAYS_IGNORE).toContain(".venv");
53
+ expect(ALWAYS_IGNORE).toContain("venv");
54
+ });
55
+
56
+ test("includes build artifact directories", () => {
57
+ expect(ALWAYS_IGNORE).toContain("dist");
58
+ expect(ALWAYS_IGNORE).toContain("build");
59
+ expect(ALWAYS_IGNORE).toContain("out");
60
+ expect(ALWAYS_IGNORE).toContain("target");
61
+ });
62
+ });
63
+
64
+ describe("parseGitignoreContent", () => {
65
+ test("parses simple patterns", () => {
66
+ const content = `
67
+ node_modules
68
+ *.log
69
+ dist/
70
+ `;
71
+ const patterns = parseGitignoreContent(content);
72
+ expect(patterns).toEqual(["node_modules", "*.log", "dist/"]);
73
+ });
74
+
75
+ test("ignores comments", () => {
76
+ const content = `
77
+ # This is a comment
78
+ node_modules
79
+ # Another comment
80
+ *.log
81
+ `;
82
+ const patterns = parseGitignoreContent(content);
83
+ expect(patterns).toEqual(["node_modules", "*.log"]);
84
+ });
85
+
86
+ test("ignores empty lines", () => {
87
+ const content = `
88
+ node_modules
89
+
90
+ *.log
91
+
92
+ dist
93
+ `;
94
+ const patterns = parseGitignoreContent(content);
95
+ expect(patterns).toEqual(["node_modules", "*.log", "dist"]);
96
+ });
97
+
98
+ test("trims whitespace", () => {
99
+ const content = ` node_modules
100
+ *.log
101
+ dist `;
102
+ const patterns = parseGitignoreContent(content);
103
+ expect(patterns).toEqual(["node_modules", "*.log", "dist"]);
104
+ });
105
+
106
+ test("handles empty content", () => {
107
+ const patterns = parseGitignoreContent("");
108
+ expect(patterns).toEqual([]);
109
+ });
110
+
111
+ test("preserves negation patterns", () => {
112
+ const content = `
113
+ *.log
114
+ !important.log
115
+ `;
116
+ const patterns = parseGitignoreContent(content);
117
+ expect(patterns).toEqual(["*.log", "!important.log"]);
118
+ });
119
+ });
120
+
121
+ describe("matchesPattern", () => {
122
+ describe("exact matches", () => {
123
+ test("matches exact filename", () => {
124
+ expect(matchesPattern("file.txt", "file.txt")).toBe(true);
125
+ expect(matchesPattern("other.txt", "file.txt")).toBe(false);
126
+ });
127
+
128
+ test("matches exact directory name", () => {
129
+ expect(matchesPattern("node_modules", "node_modules")).toBe(true);
130
+ expect(matchesPattern("src/node_modules", "node_modules")).toBe(true);
131
+ });
132
+ });
133
+
134
+ describe("wildcard patterns (*)", () => {
135
+ test("matches *.extension patterns", () => {
136
+ expect(matchesPattern("file.log", "*.log")).toBe(true);
137
+ expect(matchesPattern("error.log", "*.log")).toBe(true);
138
+ expect(matchesPattern("file.txt", "*.log")).toBe(false);
139
+ });
140
+
141
+ test("matches prefix* patterns", () => {
142
+ expect(matchesPattern("test-file.js", "test-*")).toBe(true);
143
+ expect(matchesPattern("test-another.ts", "test-*")).toBe(true);
144
+ expect(matchesPattern("other-file.js", "test-*")).toBe(false);
145
+ });
146
+
147
+ test("matches patterns in subdirectories", () => {
148
+ expect(matchesPattern("src/file.log", "*.log")).toBe(true);
149
+ expect(matchesPattern("deep/nested/file.log", "*.log")).toBe(true);
150
+ });
151
+ });
152
+
153
+ describe("globstar patterns (**)", () => {
154
+ test("matches **/*.extension patterns", () => {
155
+ expect(matchesPattern("src/file.js", "**/*.js")).toBe(true);
156
+ expect(matchesPattern("deep/nested/file.js", "**/*.js")).toBe(true);
157
+ // Note: **/*.js requires at least one directory level
158
+ // For root-level matching, use *.js pattern
159
+ expect(matchesPattern("file.js", "*.js")).toBe(true);
160
+ });
161
+
162
+ test("matches prefix/** patterns", () => {
163
+ expect(matchesPattern("logs/error.log", "logs/**")).toBe(true);
164
+ expect(matchesPattern("logs/2024/01/error.log", "logs/**")).toBe(true);
165
+ });
166
+ });
167
+
168
+ describe("rooted patterns (/)", () => {
169
+ test("matches only at root with leading /", () => {
170
+ expect(matchesPattern("dist", "/dist")).toBe(true);
171
+ expect(matchesPattern("src/dist", "/dist")).toBe(false);
172
+ });
173
+
174
+ test("matches nested paths at root", () => {
175
+ expect(matchesPattern("build/output", "/build/output")).toBe(true);
176
+ expect(matchesPattern("src/build/output", "/build/output")).toBe(false);
177
+ });
178
+ });
179
+
180
+ describe("directory patterns (/)", () => {
181
+ test("matches directories with trailing /", () => {
182
+ expect(matchesPattern("logs", "logs/")).toBe(true);
183
+ expect(matchesPattern("src/logs", "logs/")).toBe(true);
184
+ });
185
+ });
186
+
187
+ describe("negation patterns", () => {
188
+ test("negation patterns return false (not supported)", () => {
189
+ expect(matchesPattern("important.log", "!important.log")).toBe(false);
190
+ expect(matchesPattern("file.txt", "!*.txt")).toBe(false);
191
+ });
192
+ });
193
+
194
+ describe("special characters", () => {
195
+ test("escapes dots in patterns", () => {
196
+ expect(matchesPattern(".env", ".env")).toBe(true);
197
+ expect(matchesPattern("aenv", ".env")).toBe(false);
198
+ });
199
+
200
+ test("handles question mark wildcard", () => {
201
+ expect(matchesPattern("file1.txt", "file?.txt")).toBe(true);
202
+ expect(matchesPattern("file2.txt", "file?.txt")).toBe(true);
203
+ expect(matchesPattern("file12.txt", "file?.txt")).toBe(false);
204
+ });
205
+ });
206
+ });
207
+
208
+ describe("shouldIgnore", () => {
209
+ describe("hidden files", () => {
210
+ test("ignores all hidden files", () => {
211
+ expect(shouldIgnore(".hidden", ".hidden")).toBe(true);
212
+ expect(shouldIgnore(".env", ".env")).toBe(true);
213
+ expect(shouldIgnore(".gitignore", ".gitignore")).toBe(true);
214
+ });
215
+
216
+ test("ignores hidden directories", () => {
217
+ expect(shouldIgnore(".git", ".git")).toBe(true);
218
+ expect(shouldIgnore(".vscode", ".vscode")).toBe(true);
219
+ });
220
+ });
221
+
222
+ describe("ALWAYS_IGNORE patterns", () => {
223
+ test("ignores .env files", () => {
224
+ expect(shouldIgnore("src/.env", ".env")).toBe(true);
225
+ expect(shouldIgnore(".env.local", ".env.local")).toBe(true);
226
+ });
227
+
228
+ test("ignores key files", () => {
229
+ expect(shouldIgnore("private.key", "private.key")).toBe(true);
230
+ expect(shouldIgnore("cert.pem", "cert.pem")).toBe(true);
231
+ expect(shouldIgnore("keys/server.pfx", "server.pfx")).toBe(true);
232
+ });
233
+
234
+ test("ignores node_modules", () => {
235
+ expect(shouldIgnore("node_modules", "node_modules")).toBe(true);
236
+ expect(shouldIgnore("packages/cli/node_modules", "node_modules")).toBe(true);
237
+ });
238
+
239
+ test("ignores log files", () => {
240
+ expect(shouldIgnore("error.log", "error.log")).toBe(true);
241
+ expect(shouldIgnore("logs/debug.log", "debug.log")).toBe(true);
242
+ });
243
+
244
+ test("ignores build directories", () => {
245
+ expect(shouldIgnore("dist", "dist")).toBe(true);
246
+ expect(shouldIgnore("build", "build")).toBe(true);
247
+ expect(shouldIgnore("out", "out")).toBe(true);
248
+ });
249
+ });
250
+
251
+ describe("custom gitignore patterns", () => {
252
+ test("ignores files matching custom patterns", () => {
253
+ const patterns = ["*.tmp", "cache/"];
254
+ expect(shouldIgnore("temp.tmp", "temp.tmp", patterns)).toBe(true);
255
+ expect(shouldIgnore("data.tmp", "data.tmp", patterns)).toBe(true);
256
+ expect(shouldIgnore("cache", "cache", patterns)).toBe(true);
257
+ });
258
+
259
+ test("allows files not matching any pattern", () => {
260
+ const patterns = ["*.tmp"];
261
+ expect(shouldIgnore("data.txt", "data.txt", patterns)).toBe(false);
262
+ expect(shouldIgnore("src/index.ts", "index.ts", patterns)).toBe(false);
263
+ });
264
+
265
+ test("combines default and custom patterns", () => {
266
+ const patterns = ["custom.ignore"];
267
+ // Should ignore from ALWAYS_IGNORE
268
+ expect(shouldIgnore("error.log", "error.log", patterns)).toBe(true);
269
+ // Should ignore from custom patterns
270
+ expect(shouldIgnore("custom.ignore", "custom.ignore", patterns)).toBe(true);
271
+ // Should allow normal files
272
+ expect(shouldIgnore("index.ts", "index.ts", patterns)).toBe(false);
273
+ });
274
+ });
275
+
276
+ describe("allowed files", () => {
277
+ test("allows normal source files", () => {
278
+ expect(shouldIgnore("index.ts", "index.ts")).toBe(false);
279
+ expect(shouldIgnore("src/main.py", "main.py")).toBe(false);
280
+ expect(shouldIgnore("lib/utils.js", "utils.js")).toBe(false);
281
+ });
282
+
283
+ test("allows enact manifest files", () => {
284
+ expect(shouldIgnore("enact.md", "enact.md")).toBe(false);
285
+ expect(shouldIgnore("enact.yaml", "enact.yaml")).toBe(false);
286
+ expect(shouldIgnore("enact.yml", "enact.yml")).toBe(false);
287
+ });
288
+
289
+ test("allows README files", () => {
290
+ expect(shouldIgnore("README.md", "README.md")).toBe(false);
291
+ expect(shouldIgnore("readme.md", "readme.md")).toBe(false);
292
+ });
293
+
294
+ test("allows data files", () => {
295
+ expect(shouldIgnore("data.json", "data.json")).toBe(false);
296
+ expect(shouldIgnore("config.yaml", "config.yaml")).toBe(false);
297
+ });
298
+ });
299
+ });
300
+
301
+ describe("loadGitignore", () => {
302
+ let testDir: string;
303
+
304
+ beforeEach(() => {
305
+ testDir = join(tmpdir(), `enact-test-${Date.now()}`);
306
+ mkdirSync(testDir, { recursive: true });
307
+ });
308
+
309
+ afterEach(() => {
310
+ try {
311
+ rmSync(testDir, { recursive: true, force: true });
312
+ } catch {
313
+ // Ignore cleanup errors
314
+ }
315
+ });
316
+
317
+ test("returns empty array when no .gitignore exists", () => {
318
+ const patterns = loadGitignore(testDir);
319
+ expect(patterns).toEqual([]);
320
+ });
321
+
322
+ test("parses .gitignore file", () => {
323
+ writeFileSync(
324
+ join(testDir, ".gitignore"),
325
+ `# Dependencies
326
+ node_modules
327
+ *.log
328
+ dist/
329
+ `
330
+ );
331
+ const patterns = loadGitignore(testDir);
332
+ expect(patterns).toEqual(["node_modules", "*.log", "dist/"]);
333
+ });
334
+
335
+ test("handles complex .gitignore", () => {
336
+ writeFileSync(
337
+ join(testDir, ".gitignore"),
338
+ `# Build output
339
+ dist/
340
+ build/
341
+ *.min.js
342
+ *.min.css
343
+
344
+ # Test coverage
345
+ coverage/
346
+ .nyc_output/
347
+
348
+ # Temp files
349
+ *.tmp
350
+ *.bak
351
+ *~
352
+
353
+ # Secrets
354
+ .env*
355
+ !.env.example
356
+ `
357
+ );
358
+ const patterns = loadGitignore(testDir);
359
+ expect(patterns).toContain("dist/");
360
+ expect(patterns).toContain("*.min.js");
361
+ expect(patterns).toContain("coverage/");
362
+ expect(patterns).toContain("*.tmp");
363
+ expect(patterns).toContain(".env*");
364
+ expect(patterns).toContain("!.env.example");
365
+ expect(patterns).not.toContain("# Build output");
366
+ });
367
+ });
368
+
369
+ describe("integration scenarios", () => {
370
+ test("typical project structure filtering", () => {
371
+ const projectFiles = [
372
+ "enact.md",
373
+ "README.md",
374
+ "src/index.ts",
375
+ "src/utils.ts",
376
+ ".env",
377
+ ".env.local",
378
+ ".git/config",
379
+ ".gitignore",
380
+ "node_modules/lodash/index.js",
381
+ "dist/bundle.js",
382
+ "private.key",
383
+ ".DS_Store",
384
+ ];
385
+
386
+ const allowedFiles = projectFiles.filter((file) => {
387
+ const fileName = file.split("/").pop() || file;
388
+ return !shouldIgnore(file, fileName);
389
+ });
390
+
391
+ expect(allowedFiles).toContain("enact.md");
392
+ expect(allowedFiles).toContain("README.md");
393
+ expect(allowedFiles).toContain("src/index.ts");
394
+ expect(allowedFiles).toContain("src/utils.ts");
395
+
396
+ expect(allowedFiles).not.toContain(".env");
397
+ expect(allowedFiles).not.toContain(".env.local");
398
+ expect(allowedFiles).not.toContain(".git/config");
399
+ expect(allowedFiles).not.toContain("node_modules/lodash/index.js");
400
+ expect(allowedFiles).not.toContain("dist/bundle.js");
401
+ expect(allowedFiles).not.toContain("private.key");
402
+ expect(allowedFiles).not.toContain(".DS_Store");
403
+ });
404
+
405
+ test("respects custom gitignore patterns", () => {
406
+ const projectFiles = [
407
+ "enact.md",
408
+ "src/index.ts",
409
+ "test.log",
410
+ "data.tmp",
411
+ "cache/data.json",
412
+ "output.txt",
413
+ ];
414
+
415
+ const customPatterns = ["test.log", "*.tmp", "cache/"];
416
+
417
+ const allowedFiles = projectFiles.filter((file) => {
418
+ const fileName = file.split("/").pop() || file;
419
+ return !shouldIgnore(file, fileName, customPatterns);
420
+ });
421
+
422
+ expect(allowedFiles).toContain("enact.md");
423
+ expect(allowedFiles).toContain("src/index.ts");
424
+ expect(allowedFiles).toContain("output.txt");
425
+
426
+ expect(allowedFiles).not.toContain("test.log");
427
+ expect(allowedFiles).not.toContain("data.tmp");
428
+ expect(allowedFiles).not.toContain("cache/data.json");
429
+ });
430
+
431
+ test("security: never includes sensitive files", () => {
432
+ const sensitiveFiles = [
433
+ // These are caught by hidden file check (starting with .)
434
+ ".env",
435
+ ".env.local",
436
+ ".env.development",
437
+ ".env.production",
438
+ ".env.staging.local",
439
+ // These are caught by extension patterns
440
+ "secrets.key",
441
+ "private.pem",
442
+ "cert.p12",
443
+ "keystore.pfx",
444
+ ];
445
+
446
+ for (const file of sensitiveFiles) {
447
+ const fileName = file.split("/").pop() || file;
448
+ const isIgnored = shouldIgnore(file, fileName);
449
+ expect(isIgnored).toBe(true);
450
+ }
451
+ });
452
+
453
+ test("note: SSH keys without extensions need explicit gitignore", () => {
454
+ // SSH keys like id_rsa don't have a recognizable extension
455
+ // They need to be explicitly added to .gitignore
456
+ // This test documents expected behavior
457
+ expect(shouldIgnore("id_rsa", "id_rsa", ["id_rsa"])).toBe(true);
458
+ expect(shouldIgnore("id_rsa.pub", "id_rsa.pub", ["id_rsa.pub"])).toBe(true);
459
+ });
460
+ });
461
+ });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Tests for CLI output utilities
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { colors, formatError, symbols } from "../../src/utils/output";
7
+
8
+ describe("Output Utilities", () => {
9
+ describe("colors", () => {
10
+ test("exports all expected color functions", () => {
11
+ expect(colors.success).toBeDefined();
12
+ expect(colors.error).toBeDefined();
13
+ expect(colors.warning).toBeDefined();
14
+ expect(colors.info).toBeDefined();
15
+ expect(colors.dim).toBeDefined();
16
+ expect(colors.bold).toBeDefined();
17
+ expect(colors.command).toBeDefined();
18
+ expect(colors.path).toBeDefined();
19
+ expect(colors.value).toBeDefined();
20
+ expect(colors.key).toBeDefined();
21
+ expect(colors.version).toBeDefined();
22
+ });
23
+
24
+ test("color functions return strings", () => {
25
+ expect(typeof colors.success("test")).toBe("string");
26
+ expect(typeof colors.error("test")).toBe("string");
27
+ expect(typeof colors.warning("test")).toBe("string");
28
+ expect(typeof colors.info("test")).toBe("string");
29
+ expect(typeof colors.dim("test")).toBe("string");
30
+ expect(typeof colors.bold("test")).toBe("string");
31
+ });
32
+
33
+ test("color functions preserve input text", () => {
34
+ const input = "Hello World";
35
+ // The text should be contained in the output (may have color codes)
36
+ expect(colors.success(input)).toContain("Hello");
37
+ expect(colors.error(input)).toContain("World");
38
+ });
39
+ });
40
+
41
+ describe("symbols", () => {
42
+ test("exports all expected symbols", () => {
43
+ expect(symbols.success).toBeDefined();
44
+ expect(symbols.error).toBeDefined();
45
+ expect(symbols.warning).toBeDefined();
46
+ expect(symbols.info).toBeDefined();
47
+ expect(symbols.arrow).toBeDefined();
48
+ expect(symbols.bullet).toBeDefined();
49
+ expect(symbols.check).toBeDefined();
50
+ expect(symbols.cross).toBeDefined();
51
+ });
52
+
53
+ test("symbols are non-empty strings", () => {
54
+ expect(symbols.success.length).toBeGreaterThan(0);
55
+ expect(symbols.error.length).toBeGreaterThan(0);
56
+ expect(symbols.warning.length).toBeGreaterThan(0);
57
+ expect(symbols.info.length).toBeGreaterThan(0);
58
+ });
59
+ });
60
+
61
+ describe("formatError", () => {
62
+ test("formats Error objects", () => {
63
+ const error = new Error("Something went wrong");
64
+ expect(formatError(error)).toBe("Something went wrong");
65
+ });
66
+
67
+ test("formats string errors", () => {
68
+ expect(formatError("String error")).toBe("String error");
69
+ });
70
+
71
+ test("formats number errors", () => {
72
+ expect(formatError(42)).toBe("42");
73
+ });
74
+
75
+ test("formats null", () => {
76
+ expect(formatError(null)).toBe("null");
77
+ });
78
+
79
+ test("formats undefined", () => {
80
+ expect(formatError(undefined)).toBe("undefined");
81
+ });
82
+
83
+ test("formats object errors", () => {
84
+ const result = formatError({ code: "ERR_123" });
85
+ expect(result).toContain("object");
86
+ });
87
+ });
88
+ });
89
+
90
+ describe("TableColumn type", () => {
91
+ test("accepts valid column configuration", () => {
92
+ const column: {
93
+ key: string;
94
+ header: string;
95
+ width?: number;
96
+ align?: "left" | "right" | "center";
97
+ } = {
98
+ key: "name",
99
+ header: "Name",
100
+ width: 20,
101
+ align: "left" as const,
102
+ };
103
+
104
+ expect(column.key).toBe("name");
105
+ expect(column.header).toBe("Name");
106
+ expect(column.width).toBe(20);
107
+ expect(column.align).toBe("left");
108
+ });
109
+
110
+ test("width and align are optional", () => {
111
+ const column: {
112
+ key: string;
113
+ header: string;
114
+ width?: number;
115
+ align?: "left" | "right" | "center";
116
+ } = {
117
+ key: "id",
118
+ header: "ID",
119
+ };
120
+
121
+ expect(column.key).toBe("id");
122
+ expect(column.header).toBe("ID");
123
+ expect(column.width).toBeUndefined();
124
+ expect(column.align).toBeUndefined();
125
+ });
126
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "noEmit": false
8
+ },
9
+ "include": ["src/**/*"],
10
+ "exclude": ["node_modules", "dist", "tests"],
11
+ "references": [
12
+ { "path": "../shared" },
13
+ { "path": "../secrets" },
14
+ { "path": "../execution" },
15
+ { "path": "../api" }
16
+ ]
17
+ }