@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,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the list 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 { configureListCommand } from "../../src/commands/list";
|
|
10
|
+
|
|
11
|
+
// Test fixtures directory
|
|
12
|
+
const FIXTURES_DIR = join(import.meta.dir, "..", "fixtures", "list-cmd");
|
|
13
|
+
|
|
14
|
+
describe("list 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 list command on program", () => {
|
|
34
|
+
const program = new Command();
|
|
35
|
+
configureListCommand(program);
|
|
36
|
+
|
|
37
|
+
const listCmd = program.commands.find((cmd) => cmd.name() === "list");
|
|
38
|
+
expect(listCmd).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("has correct description", () => {
|
|
42
|
+
const program = new Command();
|
|
43
|
+
configureListCommand(program);
|
|
44
|
+
|
|
45
|
+
const listCmd = program.commands.find((cmd) => cmd.name() === "list");
|
|
46
|
+
expect(listCmd?.description()).toBe("List installed tools");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("has 'ls' as alias", () => {
|
|
50
|
+
const program = new Command();
|
|
51
|
+
configureListCommand(program);
|
|
52
|
+
|
|
53
|
+
const listCmd = program.commands.find((cmd) => cmd.name() === "list");
|
|
54
|
+
const aliases = listCmd?.aliases() ?? [];
|
|
55
|
+
expect(aliases).toContain("ls");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("has --global option", () => {
|
|
59
|
+
const program = new Command();
|
|
60
|
+
configureListCommand(program);
|
|
61
|
+
|
|
62
|
+
const listCmd = program.commands.find((cmd) => cmd.name() === "list");
|
|
63
|
+
const opts = listCmd?.options ?? [];
|
|
64
|
+
const globalOpt = opts.find((o) => o.long === "--global");
|
|
65
|
+
expect(globalOpt).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("has --verbose option", () => {
|
|
69
|
+
const program = new Command();
|
|
70
|
+
configureListCommand(program);
|
|
71
|
+
|
|
72
|
+
const listCmd = program.commands.find((cmd) => cmd.name() === "list");
|
|
73
|
+
const opts = listCmd?.options ?? [];
|
|
74
|
+
const verboseOpt = opts.find((o) => o.long === "--verbose");
|
|
75
|
+
expect(verboseOpt).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("has --json option", () => {
|
|
79
|
+
const program = new Command();
|
|
80
|
+
configureListCommand(program);
|
|
81
|
+
|
|
82
|
+
const listCmd = program.commands.find((cmd) => cmd.name() === "list");
|
|
83
|
+
const opts = listCmd?.options ?? [];
|
|
84
|
+
const jsonOpt = opts.find((o) => o.long === "--json");
|
|
85
|
+
expect(jsonOpt).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("scope filtering logic", () => {
|
|
90
|
+
test("default shows project and user tools", () => {
|
|
91
|
+
const options: { project?: boolean; user?: boolean; cache?: boolean; all?: boolean } = {};
|
|
92
|
+
const showProject = options.project || options.all || (!options.user && !options.cache);
|
|
93
|
+
const showUser = options.user || options.all || (!options.project && !options.cache);
|
|
94
|
+
const showCache = options.cache || options.all || false;
|
|
95
|
+
|
|
96
|
+
expect(showProject).toBe(true);
|
|
97
|
+
expect(showUser).toBe(true);
|
|
98
|
+
expect(showCache).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("--project shows only project tools", () => {
|
|
102
|
+
const options = { project: true };
|
|
103
|
+
const showProject = options.project || false;
|
|
104
|
+
const showUser = !options.project && false;
|
|
105
|
+
const showCache = false;
|
|
106
|
+
|
|
107
|
+
expect(showProject).toBe(true);
|
|
108
|
+
expect(showUser).toBe(false);
|
|
109
|
+
expect(showCache).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("--user shows only user tools", () => {
|
|
113
|
+
const options = { user: true };
|
|
114
|
+
expect(options.user).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("--cache shows only cached tools", () => {
|
|
118
|
+
const options = { cache: true };
|
|
119
|
+
expect(options.cache).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("--all shows all locations", () => {
|
|
123
|
+
const options = { all: true };
|
|
124
|
+
const showProject = options.all;
|
|
125
|
+
const showUser = options.all;
|
|
126
|
+
const showCache = options.all;
|
|
127
|
+
|
|
128
|
+
expect(showProject).toBe(true);
|
|
129
|
+
expect(showUser).toBe(true);
|
|
130
|
+
expect(showCache).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("tool info structure", () => {
|
|
135
|
+
test("tool info has required properties", () => {
|
|
136
|
+
const toolInfo = {
|
|
137
|
+
name: "alice/utils/greeter",
|
|
138
|
+
version: "1.0.0",
|
|
139
|
+
location: "/path/to/tool",
|
|
140
|
+
scope: "project",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
expect(toolInfo.name).toBe("alice/utils/greeter");
|
|
144
|
+
expect(toolInfo.version).toBe("1.0.0");
|
|
145
|
+
expect(toolInfo.location).toBe("/path/to/tool");
|
|
146
|
+
expect(toolInfo.scope).toBe("project");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("version defaults to dash for unversioned tools", () => {
|
|
150
|
+
const toolInfo = {
|
|
151
|
+
name: "alice/utils/greeter",
|
|
152
|
+
version: "-",
|
|
153
|
+
location: "/path/to/tool",
|
|
154
|
+
scope: "user",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
expect(toolInfo.version).toBe("-");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the publish command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { configurePublishCommand } from "../../src/commands/publish";
|
|
8
|
+
|
|
9
|
+
describe("publish command", () => {
|
|
10
|
+
describe("command configuration", () => {
|
|
11
|
+
test("configures publish command on program", () => {
|
|
12
|
+
const program = new Command();
|
|
13
|
+
configurePublishCommand(program);
|
|
14
|
+
|
|
15
|
+
const publishCmd = program.commands.find((cmd) => cmd.name() === "publish");
|
|
16
|
+
expect(publishCmd).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("has correct description", () => {
|
|
20
|
+
const program = new Command();
|
|
21
|
+
configurePublishCommand(program);
|
|
22
|
+
|
|
23
|
+
const publishCmd = program.commands.find((cmd) => cmd.name() === "publish");
|
|
24
|
+
expect(publishCmd?.description()).toBe("Publish a tool to the Enact registry");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("accepts optional file argument", () => {
|
|
28
|
+
const program = new Command();
|
|
29
|
+
configurePublishCommand(program);
|
|
30
|
+
|
|
31
|
+
const publishCmd = program.commands.find((cmd) => cmd.name() === "publish");
|
|
32
|
+
const args = publishCmd?.registeredArguments ?? [];
|
|
33
|
+
// Check there's an argument (file)
|
|
34
|
+
expect(args.length).toBeGreaterThanOrEqual(0);
|
|
35
|
+
// Could be optional, so may have no required args
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("has --dry-run option", () => {
|
|
39
|
+
const program = new Command();
|
|
40
|
+
configurePublishCommand(program);
|
|
41
|
+
|
|
42
|
+
const publishCmd = program.commands.find((cmd) => cmd.name() === "publish");
|
|
43
|
+
const opts = publishCmd?.options ?? [];
|
|
44
|
+
const dryRunOpt = opts.find((o) => o.long === "--dry-run");
|
|
45
|
+
expect(dryRunOpt).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("has --json option", () => {
|
|
49
|
+
const program = new Command();
|
|
50
|
+
configurePublishCommand(program);
|
|
51
|
+
|
|
52
|
+
const publishCmd = program.commands.find((cmd) => cmd.name() === "publish");
|
|
53
|
+
const opts = publishCmd?.options ?? [];
|
|
54
|
+
const jsonOpt = opts.find((o) => o.long === "--json");
|
|
55
|
+
expect(jsonOpt).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("manifest file detection", () => {
|
|
60
|
+
test("identifies enact.yaml as manifest", () => {
|
|
61
|
+
const isManifest = (filename: string): boolean => {
|
|
62
|
+
return filename === "enact.yaml" || filename === "enact.yml" || filename === "enact.md";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
expect(isManifest("enact.yaml")).toBe(true);
|
|
66
|
+
expect(isManifest("enact.yml")).toBe(true);
|
|
67
|
+
expect(isManifest("enact.md")).toBe(true);
|
|
68
|
+
expect(isManifest("package.json")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("detects file extensions correctly", () => {
|
|
72
|
+
const getExtension = (filename: string): string => {
|
|
73
|
+
const lastDot = filename.lastIndexOf(".");
|
|
74
|
+
return lastDot === -1 ? "" : filename.slice(lastDot + 1);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
expect(getExtension("enact.yaml")).toBe("yaml");
|
|
78
|
+
expect(getExtension("enact.yml")).toBe("yml");
|
|
79
|
+
expect(getExtension("enact.md")).toBe("md");
|
|
80
|
+
expect(getExtension("noextension")).toBe("");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("version validation", () => {
|
|
85
|
+
test("validates semver format", () => {
|
|
86
|
+
const isValidSemver = (version: string): boolean => {
|
|
87
|
+
const semverRegex = /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/;
|
|
88
|
+
return semverRegex.test(version);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
expect(isValidSemver("1.0.0")).toBe(true);
|
|
92
|
+
expect(isValidSemver("0.1.0")).toBe(true);
|
|
93
|
+
expect(isValidSemver("1.0.0-alpha")).toBe(true);
|
|
94
|
+
expect(isValidSemver("1.0.0-alpha.1")).toBe(true);
|
|
95
|
+
expect(isValidSemver("1.0.0+build")).toBe(true);
|
|
96
|
+
expect(isValidSemver("1.0")).toBe(false);
|
|
97
|
+
expect(isValidSemver("v1.0.0")).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("dry run behavior", () => {
|
|
102
|
+
test("dry run flag changes behavior", () => {
|
|
103
|
+
const shouldPublish = (dryRun: boolean): boolean => {
|
|
104
|
+
return !dryRun;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
expect(shouldPublish(false)).toBe(true);
|
|
108
|
+
expect(shouldPublish(true)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("bundle creation", () => {
|
|
113
|
+
test("bundle metadata structure", () => {
|
|
114
|
+
interface BundleMetadata {
|
|
115
|
+
name: string;
|
|
116
|
+
version: string;
|
|
117
|
+
hash: string;
|
|
118
|
+
size: number;
|
|
119
|
+
createdAt: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const metadata: BundleMetadata = {
|
|
123
|
+
name: "my-tool",
|
|
124
|
+
version: "1.0.0",
|
|
125
|
+
hash: "sha256:abc123",
|
|
126
|
+
size: 1024,
|
|
127
|
+
createdAt: new Date().toISOString(),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
expect(metadata.name).toBe("my-tool");
|
|
131
|
+
expect(metadata.version).toBe("1.0.0");
|
|
132
|
+
expect(metadata.hash).toContain("sha256:");
|
|
133
|
+
expect(metadata.size).toBeGreaterThan(0);
|
|
134
|
+
expect(metadata.createdAt).toContain("T");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("hash format validation", () => {
|
|
138
|
+
const isValidHash = (hash: string): boolean => {
|
|
139
|
+
return hash.startsWith("sha256:") && hash.length === 71; // sha256: + 64 hex chars
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
expect(isValidHash(`sha256:${"a".repeat(64)}`)).toBe(true);
|
|
143
|
+
expect(isValidHash(`sha512:${"a".repeat(128)}`)).toBe(false);
|
|
144
|
+
expect(isValidHash("invalid")).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("publish result handling", () => {
|
|
149
|
+
test("success result structure", () => {
|
|
150
|
+
interface PublishResult {
|
|
151
|
+
success: boolean;
|
|
152
|
+
toolName: string;
|
|
153
|
+
version: string;
|
|
154
|
+
url?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result: PublishResult = {
|
|
158
|
+
success: true,
|
|
159
|
+
toolName: "my-tool",
|
|
160
|
+
version: "1.0.0",
|
|
161
|
+
url: "https://registry.enact.dev/tools/my-tool/1.0.0",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
expect(result.success).toBe(true);
|
|
165
|
+
expect(result.toolName).toBeDefined();
|
|
166
|
+
expect(result.version).toBeDefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("error result structure", () => {
|
|
170
|
+
interface PublishError {
|
|
171
|
+
success: boolean;
|
|
172
|
+
error: string;
|
|
173
|
+
code?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const error: PublishError = {
|
|
177
|
+
success: false,
|
|
178
|
+
error: "Authentication required",
|
|
179
|
+
code: "UNAUTHORIZED",
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
expect(error.success).toBe(false);
|
|
183
|
+
expect(error.error).toBeDefined();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the report command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { configureReportCommand } from "../../src/commands/report";
|
|
8
|
+
|
|
9
|
+
describe("report command", () => {
|
|
10
|
+
describe("command configuration", () => {
|
|
11
|
+
test("configures report command on program", () => {
|
|
12
|
+
const program = new Command();
|
|
13
|
+
configureReportCommand(program);
|
|
14
|
+
|
|
15
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
16
|
+
expect(reportCmd).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("has correct description", () => {
|
|
20
|
+
const program = new Command();
|
|
21
|
+
configureReportCommand(program);
|
|
22
|
+
|
|
23
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
24
|
+
expect(reportCmd?.description()).toBe(
|
|
25
|
+
"Report security vulnerabilities or issues with a tool (creates signed attestation)"
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("accepts tool argument", () => {
|
|
30
|
+
const program = new Command();
|
|
31
|
+
configureReportCommand(program);
|
|
32
|
+
|
|
33
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
34
|
+
const args = reportCmd?.registeredArguments ?? [];
|
|
35
|
+
expect(args.length).toBeGreaterThanOrEqual(1);
|
|
36
|
+
expect(args[0]?.name()).toBe("tool");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("has required --reason option", () => {
|
|
40
|
+
const program = new Command();
|
|
41
|
+
configureReportCommand(program);
|
|
42
|
+
|
|
43
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
44
|
+
const opts = reportCmd?.options ?? [];
|
|
45
|
+
const reasonOpt = opts.find((o) => o.long === "--reason");
|
|
46
|
+
expect(reasonOpt).toBeDefined();
|
|
47
|
+
expect(reasonOpt?.required).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("has --severity option", () => {
|
|
51
|
+
const program = new Command();
|
|
52
|
+
configureReportCommand(program);
|
|
53
|
+
|
|
54
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
55
|
+
const opts = reportCmd?.options ?? [];
|
|
56
|
+
const severityOpt = opts.find((o) => o.long === "--severity");
|
|
57
|
+
expect(severityOpt).toBeDefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("has --category option", () => {
|
|
61
|
+
const program = new Command();
|
|
62
|
+
configureReportCommand(program);
|
|
63
|
+
|
|
64
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
65
|
+
const opts = reportCmd?.options ?? [];
|
|
66
|
+
const categoryOpt = opts.find((o) => o.long === "--category");
|
|
67
|
+
expect(categoryOpt).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("has --dry-run option", () => {
|
|
71
|
+
const program = new Command();
|
|
72
|
+
configureReportCommand(program);
|
|
73
|
+
|
|
74
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
75
|
+
const opts = reportCmd?.options ?? [];
|
|
76
|
+
const dryRunOpt = opts.find((o) => o.long === "--dry-run");
|
|
77
|
+
expect(dryRunOpt).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("has --verbose option", () => {
|
|
81
|
+
const program = new Command();
|
|
82
|
+
configureReportCommand(program);
|
|
83
|
+
|
|
84
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
85
|
+
const opts = reportCmd?.options ?? [];
|
|
86
|
+
const verboseOpt = opts.find((o) => o.long === "--verbose");
|
|
87
|
+
expect(verboseOpt).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("has --json option", () => {
|
|
91
|
+
const program = new Command();
|
|
92
|
+
configureReportCommand(program);
|
|
93
|
+
|
|
94
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
95
|
+
const opts = reportCmd?.options ?? [];
|
|
96
|
+
const jsonOpt = opts.find((o) => o.long === "--json");
|
|
97
|
+
expect(jsonOpt).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("severity levels", () => {
|
|
102
|
+
const severityLevels = ["critical", "high", "medium", "low"];
|
|
103
|
+
|
|
104
|
+
test("supports critical severity", () => {
|
|
105
|
+
expect(severityLevels).toContain("critical");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("supports high severity", () => {
|
|
109
|
+
expect(severityLevels).toContain("high");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("supports medium severity", () => {
|
|
113
|
+
expect(severityLevels).toContain("medium");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("supports low severity", () => {
|
|
117
|
+
expect(severityLevels).toContain("low");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("default severity is medium", () => {
|
|
121
|
+
const program = new Command();
|
|
122
|
+
configureReportCommand(program);
|
|
123
|
+
|
|
124
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
125
|
+
const opts = reportCmd?.options ?? [];
|
|
126
|
+
const severityOpt = opts.find((o) => o.long === "--severity");
|
|
127
|
+
expect(severityOpt?.defaultValue).toBe("medium");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("category types", () => {
|
|
132
|
+
const categories = ["security", "malware", "quality", "license", "other"];
|
|
133
|
+
|
|
134
|
+
test("supports security category", () => {
|
|
135
|
+
expect(categories).toContain("security");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("supports malware category", () => {
|
|
139
|
+
expect(categories).toContain("malware");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("supports quality category", () => {
|
|
143
|
+
expect(categories).toContain("quality");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("supports license category", () => {
|
|
147
|
+
expect(categories).toContain("license");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("supports other category", () => {
|
|
151
|
+
expect(categories).toContain("other");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("default category is other", () => {
|
|
155
|
+
const program = new Command();
|
|
156
|
+
configureReportCommand(program);
|
|
157
|
+
|
|
158
|
+
const reportCmd = program.commands.find((cmd) => cmd.name() === "report");
|
|
159
|
+
const opts = reportCmd?.options ?? [];
|
|
160
|
+
const categoryOpt = opts.find((o) => o.long === "--category");
|
|
161
|
+
expect(categoryOpt?.defaultValue).toBe("other");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("tool@version parsing", () => {
|
|
166
|
+
test("parses tool name without version", () => {
|
|
167
|
+
const toolArg = "alice/utils/greeter";
|
|
168
|
+
const atIndex = toolArg.lastIndexOf("@");
|
|
169
|
+
expect(atIndex).toBe(-1); // No @ in this string
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("parses tool name with version", () => {
|
|
173
|
+
const toolArg = "alice/utils/greeter@1.0.0";
|
|
174
|
+
const atIndex = toolArg.lastIndexOf("@");
|
|
175
|
+
expect(atIndex).toBeGreaterThan(0);
|
|
176
|
+
expect(toolArg.slice(0, atIndex)).toBe("alice/utils/greeter");
|
|
177
|
+
expect(toolArg.slice(atIndex + 1)).toBe("1.0.0");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("parses scoped package without version", () => {
|
|
181
|
+
const toolArg = "@scope/package";
|
|
182
|
+
// Should not split on the first @
|
|
183
|
+
expect(toolArg.startsWith("@")).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("parses scoped package with version", () => {
|
|
187
|
+
const toolArg = "@scope/package@2.0.0";
|
|
188
|
+
const match = toolArg.match(/^(@[^/]+\/[^@]+)(?:@(.+))?$/);
|
|
189
|
+
expect(match).toBeDefined();
|
|
190
|
+
expect(match?.[1]).toBe("@scope/package");
|
|
191
|
+
expect(match?.[2]).toBe("2.0.0");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|