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