@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,217 @@
1
+ /**
2
+ * Tests for the auth command
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { Command } from "commander";
7
+ import { configureAuthCommand } from "../../src/commands/auth";
8
+
9
+ describe("auth command", () => {
10
+ describe("command configuration", () => {
11
+ test("configures auth command on program", () => {
12
+ const program = new Command();
13
+ configureAuthCommand(program);
14
+
15
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
16
+ expect(authCmd).toBeDefined();
17
+ });
18
+
19
+ test("has correct description", () => {
20
+ const program = new Command();
21
+ configureAuthCommand(program);
22
+
23
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
24
+ expect(authCmd?.description()).toBe("Manage registry authentication");
25
+ });
26
+ });
27
+
28
+ describe("auth login subcommand", () => {
29
+ test("has login subcommand", () => {
30
+ const program = new Command();
31
+ configureAuthCommand(program);
32
+
33
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
34
+ const loginCmd = authCmd?.commands.find((cmd) => cmd.name() === "login");
35
+ expect(loginCmd).toBeDefined();
36
+ });
37
+
38
+ test("login has correct description", () => {
39
+ const program = new Command();
40
+ configureAuthCommand(program);
41
+
42
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
43
+ const loginCmd = authCmd?.commands.find((cmd) => cmd.name() === "login");
44
+ expect(loginCmd?.description()).toBe("Authenticate with the Enact registry");
45
+ });
46
+
47
+ test("login has --json option", () => {
48
+ const program = new Command();
49
+ configureAuthCommand(program);
50
+
51
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
52
+ const loginCmd = authCmd?.commands.find((cmd) => cmd.name() === "login");
53
+ const opts = loginCmd?.options ?? [];
54
+ const jsonOpt = opts.find((o) => o.long === "--json");
55
+ expect(jsonOpt).toBeDefined();
56
+ });
57
+ });
58
+
59
+ describe("auth logout subcommand", () => {
60
+ test("has logout subcommand", () => {
61
+ const program = new Command();
62
+ configureAuthCommand(program);
63
+
64
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
65
+ const logoutCmd = authCmd?.commands.find((cmd) => cmd.name() === "logout");
66
+ expect(logoutCmd).toBeDefined();
67
+ });
68
+
69
+ test("logout has correct description", () => {
70
+ const program = new Command();
71
+ configureAuthCommand(program);
72
+
73
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
74
+ const logoutCmd = authCmd?.commands.find((cmd) => cmd.name() === "logout");
75
+ expect(logoutCmd?.description()).toBe("Sign out from the Enact registry");
76
+ });
77
+ });
78
+
79
+ describe("auth status subcommand", () => {
80
+ test("has status subcommand", () => {
81
+ const program = new Command();
82
+ configureAuthCommand(program);
83
+
84
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
85
+ const statusCmd = authCmd?.commands.find((cmd) => cmd.name() === "status");
86
+ expect(statusCmd).toBeDefined();
87
+ });
88
+
89
+ test("status has correct description", () => {
90
+ const program = new Command();
91
+ configureAuthCommand(program);
92
+
93
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
94
+ const statusCmd = authCmd?.commands.find((cmd) => cmd.name() === "status");
95
+ expect(statusCmd?.description()).toBe("Show current authentication status");
96
+ });
97
+
98
+ test("status has --json option", () => {
99
+ const program = new Command();
100
+ configureAuthCommand(program);
101
+
102
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
103
+ const statusCmd = authCmd?.commands.find((cmd) => cmd.name() === "status");
104
+ const opts = statusCmd?.options ?? [];
105
+ const jsonOpt = opts.find((o) => o.long === "--json");
106
+ expect(jsonOpt).toBeDefined();
107
+ });
108
+ });
109
+
110
+ describe("auth whoami subcommand", () => {
111
+ test("has whoami subcommand", () => {
112
+ const program = new Command();
113
+ configureAuthCommand(program);
114
+
115
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
116
+ const whoamiCmd = authCmd?.commands.find((cmd) => cmd.name() === "whoami");
117
+ expect(whoamiCmd).toBeDefined();
118
+ });
119
+
120
+ test("whoami has correct description", () => {
121
+ const program = new Command();
122
+ configureAuthCommand(program);
123
+
124
+ const authCmd = program.commands.find((cmd) => cmd.name() === "auth");
125
+ const whoamiCmd = authCmd?.commands.find((cmd) => cmd.name() === "whoami");
126
+ expect(whoamiCmd?.description()).toBe("Print the current username");
127
+ });
128
+ });
129
+
130
+ describe("token validation", () => {
131
+ test("validates token format", () => {
132
+ const isValidToken = (token: string): boolean => {
133
+ // Tokens should be non-empty and not contain whitespace
134
+ return token.length > 0 && !/\s/.test(token);
135
+ };
136
+
137
+ expect(isValidToken("abc123")).toBe(true);
138
+ expect(isValidToken("token_with_underscore")).toBe(true);
139
+ expect(isValidToken("")).toBe(false);
140
+ expect(isValidToken("token with space")).toBe(false);
141
+ });
142
+
143
+ test("token storage key pattern", () => {
144
+ const tokenKey = "enact_auth_token";
145
+ expect(tokenKey).toContain("enact");
146
+ expect(tokenKey).toContain("auth");
147
+ expect(tokenKey).toContain("token");
148
+ });
149
+ });
150
+
151
+ describe("auth status response", () => {
152
+ test("authenticated status structure", () => {
153
+ interface AuthStatus {
154
+ authenticated: boolean;
155
+ user?: {
156
+ username: string;
157
+ email?: string;
158
+ };
159
+ expiresAt?: string;
160
+ }
161
+
162
+ const authStatus: AuthStatus = {
163
+ authenticated: true,
164
+ user: {
165
+ username: "testuser",
166
+ email: "test@example.com",
167
+ },
168
+ expiresAt: "2024-12-31T23:59:59Z",
169
+ };
170
+
171
+ expect(authStatus.authenticated).toBe(true);
172
+ expect(authStatus.user?.username).toBe("testuser");
173
+ });
174
+
175
+ test("unauthenticated status structure", () => {
176
+ interface AuthStatus {
177
+ authenticated: boolean;
178
+ user?: {
179
+ username: string;
180
+ email?: string;
181
+ };
182
+ }
183
+
184
+ const authStatus: AuthStatus = {
185
+ authenticated: false,
186
+ };
187
+
188
+ expect(authStatus.authenticated).toBe(false);
189
+ expect(authStatus.user).toBeUndefined();
190
+ });
191
+ });
192
+
193
+ describe("OIDC providers", () => {
194
+ test("supported providers list", () => {
195
+ const supportedProviders = ["github", "google", "microsoft", "gitlab"];
196
+
197
+ expect(supportedProviders).toContain("github");
198
+ expect(supportedProviders).toContain("google");
199
+ expect(supportedProviders).toContain("microsoft");
200
+ expect(supportedProviders).toContain("gitlab");
201
+ });
202
+
203
+ test("provider URLs pattern", () => {
204
+ const providers: Record<string, string> = {
205
+ github: "https://github.com/login/oauth",
206
+ google: "https://accounts.google.com/o/oauth2",
207
+ microsoft: "https://login.microsoftonline.com",
208
+ gitlab: "https://gitlab.com/oauth",
209
+ };
210
+
211
+ expect(providers.github).toContain("github.com");
212
+ expect(providers.google).toContain("google.com");
213
+ expect(providers.microsoft).toContain("microsoft");
214
+ expect(providers.gitlab).toContain("gitlab.com");
215
+ });
216
+ });
217
+ });
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Tests for the cache command
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { Command } from "commander";
7
+ import { configureCacheCommand } from "../../src/commands/cache";
8
+
9
+ describe("cache command", () => {
10
+ describe("command configuration", () => {
11
+ test("configures cache command on program", () => {
12
+ const program = new Command();
13
+ configureCacheCommand(program);
14
+
15
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
16
+ expect(cacheCmd).toBeDefined();
17
+ });
18
+
19
+ test("has correct description", () => {
20
+ const program = new Command();
21
+ configureCacheCommand(program);
22
+
23
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
24
+ expect(cacheCmd?.description()).toBe("Manage the local tool cache");
25
+ });
26
+ });
27
+
28
+ describe("cache list subcommand", () => {
29
+ test("has list subcommand", () => {
30
+ const program = new Command();
31
+ configureCacheCommand(program);
32
+
33
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
34
+ const listCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "list");
35
+ expect(listCmd).toBeDefined();
36
+ });
37
+
38
+ test("list has ls alias", () => {
39
+ const program = new Command();
40
+ configureCacheCommand(program);
41
+
42
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
43
+ const listCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "list");
44
+ expect(listCmd?.aliases()).toContain("ls");
45
+ });
46
+
47
+ test("list has correct description", () => {
48
+ const program = new Command();
49
+ configureCacheCommand(program);
50
+
51
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
52
+ const listCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "list");
53
+ expect(listCmd?.description()).toBe("List cached tools");
54
+ });
55
+
56
+ test("list has --json option", () => {
57
+ const program = new Command();
58
+ configureCacheCommand(program);
59
+
60
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
61
+ const listCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "list");
62
+ const opts = listCmd?.options ?? [];
63
+ const jsonOpt = opts.find((o) => o.long === "--json");
64
+ expect(jsonOpt).toBeDefined();
65
+ });
66
+ });
67
+
68
+ describe("cache clean subcommand", () => {
69
+ test("has clean subcommand", () => {
70
+ const program = new Command();
71
+ configureCacheCommand(program);
72
+
73
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
74
+ const cleanCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clean");
75
+ expect(cleanCmd).toBeDefined();
76
+ });
77
+
78
+ test("clean has correct description", () => {
79
+ const program = new Command();
80
+ configureCacheCommand(program);
81
+
82
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
83
+ const cleanCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clean");
84
+ expect(cleanCmd?.description()).toBe("Remove old or unused cached tools");
85
+ });
86
+
87
+ test("clean has --json option", () => {
88
+ const program = new Command();
89
+ configureCacheCommand(program);
90
+
91
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
92
+ const cleanCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clean");
93
+ const opts = cleanCmd?.options ?? [];
94
+ const jsonOpt = opts.find((o) => o.long === "--json");
95
+ expect(jsonOpt).toBeDefined();
96
+ });
97
+
98
+ test("clean has --force option", () => {
99
+ const program = new Command();
100
+ configureCacheCommand(program);
101
+
102
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
103
+ const cleanCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clean");
104
+ const opts = cleanCmd?.options ?? [];
105
+ const forceOpt = opts.find((o) => o.long === "--force");
106
+ expect(forceOpt).toBeDefined();
107
+ });
108
+ });
109
+
110
+ describe("cache clear subcommand", () => {
111
+ test("has clear subcommand", () => {
112
+ const program = new Command();
113
+ configureCacheCommand(program);
114
+
115
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
116
+ const clearCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clear");
117
+ expect(clearCmd).toBeDefined();
118
+ });
119
+
120
+ test("clear has correct description", () => {
121
+ const program = new Command();
122
+ configureCacheCommand(program);
123
+
124
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
125
+ const clearCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clear");
126
+ expect(clearCmd?.description()).toBe("Clear the entire cache");
127
+ });
128
+
129
+ test("clear has --force option", () => {
130
+ const program = new Command();
131
+ configureCacheCommand(program);
132
+
133
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
134
+ const clearCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clear");
135
+ const opts = clearCmd?.options ?? [];
136
+ const forceOpt = opts.find((o) => o.long === "--force");
137
+ expect(forceOpt).toBeDefined();
138
+ });
139
+
140
+ test("clear has -f short option for force", () => {
141
+ const program = new Command();
142
+ configureCacheCommand(program);
143
+
144
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
145
+ const clearCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "clear");
146
+ const opts = clearCmd?.options ?? [];
147
+ const forceOpt = opts.find((o) => o.short === "-f");
148
+ expect(forceOpt).toBeDefined();
149
+ });
150
+ });
151
+
152
+ describe("cache info subcommand", () => {
153
+ test("has info subcommand", () => {
154
+ const program = new Command();
155
+ configureCacheCommand(program);
156
+
157
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
158
+ const infoCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "info");
159
+ expect(infoCmd).toBeDefined();
160
+ });
161
+
162
+ test("info has correct description", () => {
163
+ const program = new Command();
164
+ configureCacheCommand(program);
165
+
166
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
167
+ const infoCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "info");
168
+ expect(infoCmd?.description()).toBe("Show cache information");
169
+ });
170
+
171
+ test("info has --json option", () => {
172
+ const program = new Command();
173
+ configureCacheCommand(program);
174
+
175
+ const cacheCmd = program.commands.find((cmd) => cmd.name() === "cache");
176
+ const infoCmd = cacheCmd?.commands.find((cmd) => cmd.name() === "info");
177
+ const opts = infoCmd?.options ?? [];
178
+ const jsonOpt = opts.find((o) => o.long === "--json");
179
+ expect(jsonOpt).toBeDefined();
180
+ });
181
+ });
182
+
183
+ describe("duration parsing", () => {
184
+ test("parses days duration", () => {
185
+ const parseDuration = (duration: string): number | null => {
186
+ const match = duration.match(/^(\d+)(d|w|m)$/);
187
+ if (!match) return null;
188
+ const value = Number.parseInt(match[1] ?? "0", 10);
189
+ const unit = match[2];
190
+ switch (unit) {
191
+ case "d":
192
+ return value * 24 * 60 * 60 * 1000;
193
+ case "w":
194
+ return value * 7 * 24 * 60 * 60 * 1000;
195
+ case "m":
196
+ return value * 30 * 24 * 60 * 60 * 1000;
197
+ default:
198
+ return null;
199
+ }
200
+ };
201
+
202
+ expect(parseDuration("7d")).toBe(7 * 24 * 60 * 60 * 1000);
203
+ expect(parseDuration("2w")).toBe(14 * 24 * 60 * 60 * 1000);
204
+ expect(parseDuration("1m")).toBe(30 * 24 * 60 * 60 * 1000);
205
+ expect(parseDuration("invalid")).toBeNull();
206
+ });
207
+
208
+ test("default older-than is 30 days", () => {
209
+ const defaultOlderThan = "30d";
210
+ expect(defaultOlderThan).toBe("30d");
211
+ });
212
+ });
213
+
214
+ describe("cache entry structure", () => {
215
+ test("cached tool entry structure", () => {
216
+ interface CacheEntry {
217
+ name: string;
218
+ version: string;
219
+ cachedAt: string;
220
+ size: number;
221
+ accessedAt?: string;
222
+ hash?: string;
223
+ }
224
+
225
+ const entry: CacheEntry = {
226
+ name: "my-tool",
227
+ version: "1.0.0",
228
+ cachedAt: "2024-01-15T10:00:00Z",
229
+ size: 1024,
230
+ accessedAt: "2024-01-16T12:00:00Z",
231
+ hash: "sha256:abc123",
232
+ };
233
+
234
+ expect(entry.name).toBe("my-tool");
235
+ expect(entry.version).toBe("1.0.0");
236
+ expect(entry.size).toBeGreaterThan(0);
237
+ });
238
+ });
239
+
240
+ describe("size formatting", () => {
241
+ test("formats bytes correctly", () => {
242
+ const formatSize = (bytes: number): string => {
243
+ if (bytes >= 1_073_741_824) {
244
+ return `${(bytes / 1_073_741_824).toFixed(1)} GB`;
245
+ }
246
+ if (bytes >= 1_048_576) {
247
+ return `${(bytes / 1_048_576).toFixed(1)} MB`;
248
+ }
249
+ if (bytes >= 1024) {
250
+ return `${(bytes / 1024).toFixed(1)} KB`;
251
+ }
252
+ return `${bytes} B`;
253
+ };
254
+
255
+ expect(formatSize(500)).toBe("500 B");
256
+ expect(formatSize(1024)).toBe("1.0 KB");
257
+ expect(formatSize(1536)).toBe("1.5 KB");
258
+ expect(formatSize(1_048_576)).toBe("1.0 MB");
259
+ expect(formatSize(1_610_612_736)).toBe("1.5 GB");
260
+ });
261
+ });
262
+
263
+ describe("cache statistics", () => {
264
+ test("cache info structure", () => {
265
+ interface CacheInfo {
266
+ path: string;
267
+ totalSize: number;
268
+ toolCount: number;
269
+ oldestEntry?: string;
270
+ newestEntry?: string;
271
+ }
272
+
273
+ const info: CacheInfo = {
274
+ path: "/Users/test/.enact/cache",
275
+ totalSize: 10_485_760, // 10 MB
276
+ toolCount: 25,
277
+ oldestEntry: "2024-01-01T00:00:00Z",
278
+ newestEntry: "2024-01-20T12:00:00Z",
279
+ };
280
+
281
+ expect(info.path).toContain(".enact");
282
+ expect(info.totalSize).toBeGreaterThan(0);
283
+ expect(info.toolCount).toBeGreaterThanOrEqual(0);
284
+ });
285
+ });
286
+ });