@enactprotocol/cli 1.2.8 → 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 -231410
  70. package/dist/index.js.bak +0 -231409
  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,236 @@
1
+ /**
2
+ * Tests for the trust command
3
+ */
4
+
5
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { Command } from "commander";
9
+ import { configureTrustCommand } from "../../src/commands/trust";
10
+
11
+ // Test fixtures directory
12
+ const FIXTURES_DIR = join(import.meta.dir, "..", "fixtures", "trust-cmd");
13
+
14
+ describe("trust command", () => {
15
+ beforeAll(() => {
16
+ mkdirSync(FIXTURES_DIR, { recursive: true });
17
+ });
18
+
19
+ beforeEach(() => {
20
+ if (existsSync(FIXTURES_DIR)) {
21
+ rmSync(FIXTURES_DIR, { recursive: true, force: true });
22
+ mkdirSync(FIXTURES_DIR, { recursive: true });
23
+ }
24
+ });
25
+
26
+ afterAll(() => {
27
+ if (existsSync(FIXTURES_DIR)) {
28
+ rmSync(FIXTURES_DIR, { recursive: true, force: true });
29
+ }
30
+ });
31
+
32
+ describe("command configuration", () => {
33
+ test("configures trust command on program", () => {
34
+ const program = new Command();
35
+ configureTrustCommand(program);
36
+
37
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
38
+ expect(trustCmd).toBeDefined();
39
+ });
40
+
41
+ test("has correct description", () => {
42
+ const program = new Command();
43
+ configureTrustCommand(program);
44
+
45
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
46
+ expect(trustCmd?.description()).toBe("Manage trusted publishers and auditors");
47
+ });
48
+
49
+ test("has optional identity argument", () => {
50
+ const program = new Command();
51
+ configureTrustCommand(program);
52
+
53
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
54
+ const args = trustCmd?.registeredArguments ?? [];
55
+ expect(args.length).toBe(1);
56
+ expect(args[0]?.name()).toBe("identity");
57
+ expect(args[0]?.required).toBe(false);
58
+ });
59
+
60
+ test("has --remove option", () => {
61
+ const program = new Command();
62
+ configureTrustCommand(program);
63
+
64
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
65
+ const opts = trustCmd?.options ?? [];
66
+ const removeOpt = opts.find((o) => o.long === "--remove");
67
+ expect(removeOpt).toBeDefined();
68
+ });
69
+
70
+ test("has -r short option for remove", () => {
71
+ const program = new Command();
72
+ configureTrustCommand(program);
73
+
74
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
75
+ const opts = trustCmd?.options ?? [];
76
+ const removeOpt = opts.find((o) => o.short === "-r");
77
+ expect(removeOpt).toBeDefined();
78
+ });
79
+
80
+ test("has list subcommand", () => {
81
+ const program = new Command();
82
+ configureTrustCommand(program);
83
+
84
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
85
+ const listCmd = trustCmd?.commands.find((cmd) => cmd.name() === "list");
86
+ expect(listCmd).toBeDefined();
87
+ });
88
+
89
+ test("has check subcommand", () => {
90
+ const program = new Command();
91
+ configureTrustCommand(program);
92
+
93
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
94
+ const checkCmd = trustCmd?.commands.find((cmd) => cmd.name() === "check");
95
+ expect(checkCmd).toBeDefined();
96
+ });
97
+ });
98
+
99
+ describe("trust check subcommand", () => {
100
+ test("has tool argument", () => {
101
+ const program = new Command();
102
+ configureTrustCommand(program);
103
+
104
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
105
+ const checkCmd = trustCmd?.commands.find((cmd) => cmd.name() === "check");
106
+ const args = checkCmd?.registeredArguments ?? [];
107
+ expect(args.length).toBe(1);
108
+ expect(args[0]?.name()).toBe("tool");
109
+ });
110
+
111
+ test("has --json option", () => {
112
+ const program = new Command();
113
+ configureTrustCommand(program);
114
+
115
+ const trustCmd = program.commands.find((cmd) => cmd.name() === "trust");
116
+ const checkCmd = trustCmd?.commands.find((cmd) => cmd.name() === "check");
117
+ const opts = checkCmd?.options ?? [];
118
+ const jsonOpt = opts.find((o) => o.long === "--json");
119
+ expect(jsonOpt).toBeDefined();
120
+ });
121
+ });
122
+
123
+ describe("identity parsing", () => {
124
+ // Helper function that mirrors the command's identity parsing logic
125
+ const parseIdentity = (identity: string): { type: "publisher" | "auditor"; value: string } => {
126
+ if (identity.includes(":")) {
127
+ return { type: "auditor", value: identity };
128
+ }
129
+ return { type: "publisher", value: identity };
130
+ };
131
+
132
+ test("parses publisher identity (no colon)", () => {
133
+ const result = parseIdentity("alice");
134
+ expect(result.type).toBe("publisher");
135
+ expect(result.value).toBe("alice");
136
+ });
137
+
138
+ test("parses publisher with dashes", () => {
139
+ const result = parseIdentity("acme-corp");
140
+ expect(result.type).toBe("publisher");
141
+ expect(result.value).toBe("acme-corp");
142
+ });
143
+
144
+ test("parses github auditor identity", () => {
145
+ const result = parseIdentity("github:EnactProtocol");
146
+ expect(result.type).toBe("auditor");
147
+ expect(result.value).toBe("github:EnactProtocol");
148
+ });
149
+
150
+ test("parses google auditor identity", () => {
151
+ const result = parseIdentity("google:security@company.com");
152
+ expect(result.type).toBe("auditor");
153
+ expect(result.value).toBe("google:security@company.com");
154
+ });
155
+
156
+ test("parses microsoft auditor identity", () => {
157
+ const result = parseIdentity("microsoft:user@company.com");
158
+ expect(result.type).toBe("auditor");
159
+ expect(result.value).toBe("microsoft:user@company.com");
160
+ });
161
+
162
+ test("parses wildcard auditor identity", () => {
163
+ const result = parseIdentity("github:my-org/*");
164
+ expect(result.type).toBe("auditor");
165
+ expect(result.value).toBe("github:my-org/*");
166
+ });
167
+
168
+ test("parses email wildcard auditor identity", () => {
169
+ const result = parseIdentity("google:*@company.com");
170
+ expect(result.type).toBe("auditor");
171
+ expect(result.value).toBe("google:*@company.com");
172
+ });
173
+ });
174
+
175
+ describe("trust config structure", () => {
176
+ test("default trust config shape", () => {
177
+ const defaultTrust = {
178
+ publishers: [] as string[],
179
+ auditors: [] as string[],
180
+ policy: "warn" as const,
181
+ };
182
+
183
+ expect(Array.isArray(defaultTrust.publishers)).toBe(true);
184
+ expect(Array.isArray(defaultTrust.auditors)).toBe(true);
185
+ expect(defaultTrust.policy).toBe("warn");
186
+ });
187
+
188
+ test("trust policies", () => {
189
+ const validPolicies = ["require_audit", "prompt", "allow", "warn"];
190
+ expect(validPolicies).toContain("require_audit");
191
+ expect(validPolicies).toContain("prompt");
192
+ expect(validPolicies).toContain("allow");
193
+ expect(validPolicies).toContain("warn");
194
+ });
195
+
196
+ test("publishers array manipulation", () => {
197
+ const publishers: string[] = [];
198
+
199
+ // Add
200
+ publishers.push("alice");
201
+ expect(publishers).toContain("alice");
202
+
203
+ // Prevent duplicates
204
+ if (!publishers.includes("alice")) {
205
+ publishers.push("alice");
206
+ }
207
+ expect(publishers.filter((p) => p === "alice").length).toBe(1);
208
+
209
+ // Remove
210
+ const index = publishers.indexOf("alice");
211
+ if (index > -1) {
212
+ publishers.splice(index, 1);
213
+ }
214
+ expect(publishers).not.toContain("alice");
215
+ });
216
+
217
+ test("auditors array manipulation", () => {
218
+ const auditors: string[] = [];
219
+
220
+ // Add
221
+ auditors.push("github:EnactProtocol");
222
+ expect(auditors).toContain("github:EnactProtocol");
223
+
224
+ // Check existence
225
+ const exists = auditors.includes("github:EnactProtocol");
226
+ expect(exists).toBe(true);
227
+
228
+ // Remove
229
+ const index = auditors.indexOf("github:EnactProtocol");
230
+ if (index > -1) {
231
+ auditors.splice(index, 1);
232
+ }
233
+ expect(auditors).not.toContain("github:EnactProtocol");
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Tests for the unyank command
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { Command } from "commander";
7
+ import { configureUnyankCommand } from "../../src/commands/unyank";
8
+
9
+ describe("unyank command", () => {
10
+ describe("command configuration", () => {
11
+ test("configures unyank command on program", () => {
12
+ const program = new Command();
13
+ configureUnyankCommand(program);
14
+
15
+ const unyankCmd = program.commands.find((cmd) => cmd.name() === "unyank");
16
+ expect(unyankCmd).toBeDefined();
17
+ });
18
+
19
+ test("has correct description", () => {
20
+ const program = new Command();
21
+ configureUnyankCommand(program);
22
+
23
+ const unyankCmd = program.commands.find((cmd) => cmd.name() === "unyank");
24
+ expect(unyankCmd?.description()).toBe("Restore a previously yanked tool version");
25
+ });
26
+
27
+ test("accepts tool@version argument", () => {
28
+ const program = new Command();
29
+ configureUnyankCommand(program);
30
+
31
+ const unyankCmd = program.commands.find((cmd) => cmd.name() === "unyank");
32
+ const args = unyankCmd?.registeredArguments ?? [];
33
+ expect(args.length).toBeGreaterThanOrEqual(1);
34
+ });
35
+
36
+ test("has --verbose option", () => {
37
+ const program = new Command();
38
+ configureUnyankCommand(program);
39
+
40
+ const unyankCmd = program.commands.find((cmd) => cmd.name() === "unyank");
41
+ const opts = unyankCmd?.options ?? [];
42
+ const verboseOpt = opts.find((o) => o.long === "--verbose");
43
+ expect(verboseOpt).toBeDefined();
44
+ });
45
+
46
+ test("has --json option", () => {
47
+ const program = new Command();
48
+ configureUnyankCommand(program);
49
+
50
+ const unyankCmd = program.commands.find((cmd) => cmd.name() === "unyank");
51
+ const opts = unyankCmd?.options ?? [];
52
+ const jsonOpt = opts.find((o) => o.long === "--json");
53
+ expect(jsonOpt).toBeDefined();
54
+ });
55
+ });
56
+
57
+ describe("tool spec parsing", () => {
58
+ // Helper function that mirrors the command's parseToolSpec
59
+ const parseToolSpec = (spec: string): { name: string; version: string } => {
60
+ const atIndex = spec.lastIndexOf("@");
61
+ if (atIndex === -1 || atIndex === 0) {
62
+ throw new Error(`Invalid tool specification: ${spec}\nExpected format: tool-name@version`);
63
+ }
64
+ return {
65
+ name: spec.slice(0, atIndex),
66
+ version: spec.slice(atIndex + 1),
67
+ };
68
+ };
69
+
70
+ test("parses tool@version format", () => {
71
+ const result = parseToolSpec("alice/utils/greeter@1.0.0");
72
+ expect(result.name).toBe("alice/utils/greeter");
73
+ expect(result.version).toBe("1.0.0");
74
+ });
75
+
76
+ test("parses tool with semver prerelease", () => {
77
+ const result = parseToolSpec("alice/utils/greeter@2.0.0-beta.1");
78
+ expect(result.name).toBe("alice/utils/greeter");
79
+ expect(result.version).toBe("2.0.0-beta.1");
80
+ });
81
+
82
+ test("throws error for missing version", () => {
83
+ expect(() => parseToolSpec("alice/utils/greeter")).toThrow();
84
+ });
85
+
86
+ test("parses scoped package with version", () => {
87
+ const result = parseToolSpec("@scope/package@1.2.3");
88
+ expect(result.name).toBe("@scope/package");
89
+ expect(result.version).toBe("1.2.3");
90
+ });
91
+ });
92
+
93
+ describe("yank/unyank workflow", () => {
94
+ test("unyank requires same tool@version format as yank", () => {
95
+ // Both yank and unyank use the same parseToolSpec format
96
+ const spec = "alice/tools/helper@1.0.0";
97
+ const atIndex = spec.lastIndexOf("@");
98
+
99
+ expect(atIndex).toBeGreaterThan(0);
100
+ expect(spec.slice(0, atIndex)).toBe("alice/tools/helper");
101
+ expect(spec.slice(atIndex + 1)).toBe("1.0.0");
102
+ });
103
+
104
+ test("unyank is the inverse of yank", () => {
105
+ // Conceptual test - unyank should restore a yanked version
106
+ const yankedState = { yanked: true, version: "1.0.0" };
107
+ const unyankedState = { yanked: false, version: "1.0.0" };
108
+
109
+ expect(yankedState.yanked).toBe(true);
110
+ expect(unyankedState.yanked).toBe(false);
111
+ expect(yankedState.version).toBe(unyankedState.version);
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Tests for the yank command
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { Command } from "commander";
7
+ import { configureYankCommand } from "../../src/commands/yank";
8
+
9
+ describe("yank command", () => {
10
+ describe("command configuration", () => {
11
+ test("configures yank command on program", () => {
12
+ const program = new Command();
13
+ configureYankCommand(program);
14
+
15
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
16
+ expect(yankCmd).toBeDefined();
17
+ });
18
+
19
+ test("has correct description", () => {
20
+ const program = new Command();
21
+ configureYankCommand(program);
22
+
23
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
24
+ expect(yankCmd?.description()).toBe("Yank a published tool version from the registry");
25
+ });
26
+
27
+ test("accepts tool@version argument", () => {
28
+ const program = new Command();
29
+ configureYankCommand(program);
30
+
31
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
32
+ const args = yankCmd?.registeredArguments ?? [];
33
+ expect(args.length).toBeGreaterThanOrEqual(1);
34
+ });
35
+
36
+ test("has --reason option", () => {
37
+ const program = new Command();
38
+ configureYankCommand(program);
39
+
40
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
41
+ const opts = yankCmd?.options ?? [];
42
+ const reasonOpt = opts.find((o) => o.long === "--reason");
43
+ expect(reasonOpt).toBeDefined();
44
+ });
45
+
46
+ test("has -r short option for reason", () => {
47
+ const program = new Command();
48
+ configureYankCommand(program);
49
+
50
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
51
+ const opts = yankCmd?.options ?? [];
52
+ const reasonOpt = opts.find((o) => o.short === "-r");
53
+ expect(reasonOpt).toBeDefined();
54
+ });
55
+
56
+ test("has --replacement option", () => {
57
+ const program = new Command();
58
+ configureYankCommand(program);
59
+
60
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
61
+ const opts = yankCmd?.options ?? [];
62
+ const replacementOpt = opts.find((o) => o.long === "--replacement");
63
+ expect(replacementOpt).toBeDefined();
64
+ });
65
+
66
+ test("has --verbose option", () => {
67
+ const program = new Command();
68
+ configureYankCommand(program);
69
+
70
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
71
+ const opts = yankCmd?.options ?? [];
72
+ const verboseOpt = opts.find((o) => o.long === "--verbose");
73
+ expect(verboseOpt).toBeDefined();
74
+ });
75
+
76
+ test("has --json option", () => {
77
+ const program = new Command();
78
+ configureYankCommand(program);
79
+
80
+ const yankCmd = program.commands.find((cmd) => cmd.name() === "yank");
81
+ const opts = yankCmd?.options ?? [];
82
+ const jsonOpt = opts.find((o) => o.long === "--json");
83
+ expect(jsonOpt).toBeDefined();
84
+ });
85
+ });
86
+
87
+ describe("tool spec parsing", () => {
88
+ // Helper function that mirrors the command's parseToolSpec
89
+ const parseToolSpec = (spec: string): { name: string; version: string } => {
90
+ const atIndex = spec.lastIndexOf("@");
91
+ if (atIndex === -1 || atIndex === 0) {
92
+ throw new Error(`Invalid tool specification: ${spec}\nExpected format: tool-name@version`);
93
+ }
94
+ return {
95
+ name: spec.slice(0, atIndex),
96
+ version: spec.slice(atIndex + 1),
97
+ };
98
+ };
99
+
100
+ test("parses tool@version format", () => {
101
+ const result = parseToolSpec("alice/utils/greeter@1.0.0");
102
+ expect(result.name).toBe("alice/utils/greeter");
103
+ expect(result.version).toBe("1.0.0");
104
+ });
105
+
106
+ test("parses tool with semver prerelease", () => {
107
+ const result = parseToolSpec("alice/utils/greeter@2.0.0-beta.1");
108
+ expect(result.name).toBe("alice/utils/greeter");
109
+ expect(result.version).toBe("2.0.0-beta.1");
110
+ });
111
+
112
+ test("throws error for missing version", () => {
113
+ expect(() => parseToolSpec("alice/utils/greeter")).toThrow();
114
+ });
115
+
116
+ test("throws error for @ at start (scoped without version)", () => {
117
+ expect(() => parseToolSpec("@scope/package")).toThrow();
118
+ });
119
+
120
+ test("parses scoped package with version", () => {
121
+ // Scoped packages have @ at the start, so we need lastIndexOf
122
+ const result = parseToolSpec("@scope/package@1.2.3");
123
+ expect(result.name).toBe("@scope/package");
124
+ expect(result.version).toBe("1.2.3");
125
+ });
126
+ });
127
+
128
+ describe("yank reasons", () => {
129
+ test("common yank reasons are strings", () => {
130
+ const reasons = [
131
+ "Security vulnerability discovered",
132
+ "Critical bug in this version",
133
+ "Accidental publish",
134
+ "Deprecated in favor of newer version",
135
+ ];
136
+
137
+ for (const reason of reasons) {
138
+ expect(typeof reason).toBe("string");
139
+ expect(reason.length).toBeGreaterThan(0);
140
+ }
141
+ });
142
+ });
143
+
144
+ describe("replacement version format", () => {
145
+ test("replacement should be a valid version string", () => {
146
+ const validVersions = ["1.0.1", "2.0.0", "1.2.3-patch.1", "3.0.0+build.123"];
147
+
148
+ for (const version of validVersions) {
149
+ // Basic semver check
150
+ expect(version).toMatch(/^\d+\.\d+\.\d+/);
151
+ }
152
+ });
153
+ });
154
+ });