@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.
- package/README.md +88 -0
- package/package.json +34 -38
- package/src/commands/auth/index.ts +940 -0
- package/src/commands/cache/index.ts +361 -0
- package/src/commands/config/README.md +239 -0
- package/src/commands/config/index.ts +164 -0
- package/src/commands/env/README.md +197 -0
- package/src/commands/env/index.ts +392 -0
- package/src/commands/exec/README.md +110 -0
- package/src/commands/exec/index.ts +195 -0
- package/src/commands/get/index.ts +198 -0
- package/src/commands/index.ts +30 -0
- package/src/commands/inspect/index.ts +264 -0
- package/src/commands/install/README.md +146 -0
- package/src/commands/install/index.ts +682 -0
- package/src/commands/list/README.md +115 -0
- package/src/commands/list/index.ts +138 -0
- package/src/commands/publish/index.ts +350 -0
- package/src/commands/report/index.ts +366 -0
- package/src/commands/run/README.md +124 -0
- package/src/commands/run/index.ts +686 -0
- package/src/commands/search/index.ts +368 -0
- package/src/commands/setup/index.ts +274 -0
- package/src/commands/sign/index.ts +652 -0
- package/src/commands/trust/README.md +214 -0
- package/src/commands/trust/index.ts +453 -0
- package/src/commands/unyank/index.ts +107 -0
- package/src/commands/yank/index.ts +143 -0
- package/src/index.ts +96 -0
- package/src/types.ts +81 -0
- package/src/utils/errors.ts +409 -0
- package/src/utils/exit-codes.ts +159 -0
- package/src/utils/ignore.ts +147 -0
- package/src/utils/index.ts +107 -0
- package/src/utils/output.ts +242 -0
- package/src/utils/spinner.ts +214 -0
- package/tests/commands/auth.test.ts +217 -0
- package/tests/commands/cache.test.ts +286 -0
- package/tests/commands/config.test.ts +277 -0
- package/tests/commands/env.test.ts +293 -0
- package/tests/commands/exec.test.ts +112 -0
- package/tests/commands/get.test.ts +179 -0
- package/tests/commands/inspect.test.ts +201 -0
- package/tests/commands/install-integration.test.ts +343 -0
- package/tests/commands/install.test.ts +288 -0
- package/tests/commands/list.test.ts +160 -0
- package/tests/commands/publish.test.ts +186 -0
- package/tests/commands/report.test.ts +194 -0
- package/tests/commands/run.test.ts +231 -0
- package/tests/commands/search.test.ts +131 -0
- package/tests/commands/sign.test.ts +164 -0
- package/tests/commands/trust.test.ts +236 -0
- package/tests/commands/unyank.test.ts +114 -0
- package/tests/commands/yank.test.ts +154 -0
- package/tests/e2e.test.ts +554 -0
- package/tests/fixtures/calculator/enact.yaml +34 -0
- package/tests/fixtures/echo-tool/enact.md +31 -0
- package/tests/fixtures/env-tool/enact.yaml +19 -0
- package/tests/fixtures/greeter/enact.yaml +18 -0
- package/tests/fixtures/invalid-tool/enact.yaml +4 -0
- package/tests/index.test.ts +8 -0
- package/tests/types.test.ts +84 -0
- package/tests/utils/errors.test.ts +303 -0
- package/tests/utils/exit-codes.test.ts +189 -0
- package/tests/utils/ignore.test.ts +461 -0
- package/tests/utils/output.test.ts +126 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/index.js +0 -231612
- package/dist/index.js.bak +0 -231611
- package/dist/web/static/app.js +0 -663
- package/dist/web/static/index.html +0 -117
- 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
|
+
});
|