@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,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities for CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent error handling with helpful suggestions
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
EXIT_AUTH_ERROR,
|
|
9
|
+
EXIT_CONFIG,
|
|
10
|
+
EXIT_CONTAINER_ERROR,
|
|
11
|
+
EXIT_EXECUTION_ERROR,
|
|
12
|
+
EXIT_FAILURE,
|
|
13
|
+
EXIT_MANIFEST_ERROR,
|
|
14
|
+
EXIT_NETWORK_ERROR,
|
|
15
|
+
EXIT_NOINPUT,
|
|
16
|
+
EXIT_NOPERM,
|
|
17
|
+
EXIT_REGISTRY_ERROR,
|
|
18
|
+
EXIT_TIMEOUT,
|
|
19
|
+
EXIT_TOOL_NOT_FOUND,
|
|
20
|
+
EXIT_TRUST_ERROR,
|
|
21
|
+
EXIT_USAGE,
|
|
22
|
+
EXIT_VALIDATION_ERROR,
|
|
23
|
+
} from "./exit-codes";
|
|
24
|
+
import { colors, dim, newline, error as printError, suggest } from "./output";
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Error Classes
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Base CLI error with exit code
|
|
32
|
+
*/
|
|
33
|
+
export class CliError extends Error {
|
|
34
|
+
constructor(
|
|
35
|
+
message: string,
|
|
36
|
+
public readonly exitCode: number = EXIT_FAILURE,
|
|
37
|
+
public readonly suggestion?: string
|
|
38
|
+
) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "CliError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tool not found error
|
|
46
|
+
*/
|
|
47
|
+
export class ToolNotFoundError extends CliError {
|
|
48
|
+
constructor(toolName: string) {
|
|
49
|
+
super(
|
|
50
|
+
`Tool not found: ${toolName}`,
|
|
51
|
+
EXIT_TOOL_NOT_FOUND,
|
|
52
|
+
"Check the tool name or provide a path to a local tool.\nFor registry tools, use the format: owner/namespace/tool[@version]"
|
|
53
|
+
);
|
|
54
|
+
this.name = "ToolNotFoundError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Manifest error
|
|
60
|
+
*/
|
|
61
|
+
export class ManifestError extends CliError {
|
|
62
|
+
constructor(message: string, path?: string) {
|
|
63
|
+
const fullMessage = path ? `${message} in ${path}` : message;
|
|
64
|
+
super(
|
|
65
|
+
fullMessage,
|
|
66
|
+
EXIT_MANIFEST_ERROR,
|
|
67
|
+
"Ensure the directory contains a valid enact.yaml or enact.md file."
|
|
68
|
+
);
|
|
69
|
+
this.name = "ManifestError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validation error
|
|
75
|
+
*/
|
|
76
|
+
export class ValidationError extends CliError {
|
|
77
|
+
constructor(message: string, field?: string) {
|
|
78
|
+
const fullMessage = field ? `Invalid ${field}: ${message}` : message;
|
|
79
|
+
super(fullMessage, EXIT_VALIDATION_ERROR);
|
|
80
|
+
this.name = "ValidationError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Authentication error
|
|
86
|
+
*/
|
|
87
|
+
export class AuthError extends CliError {
|
|
88
|
+
constructor(message: string) {
|
|
89
|
+
super(message, EXIT_AUTH_ERROR, "Run 'enact auth login' to authenticate.");
|
|
90
|
+
this.name = "AuthError";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Network error
|
|
96
|
+
*/
|
|
97
|
+
export class NetworkError extends CliError {
|
|
98
|
+
constructor(message: string) {
|
|
99
|
+
super(message, EXIT_NETWORK_ERROR, "Check your network connection and try again.");
|
|
100
|
+
this.name = "NetworkError";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Registry error
|
|
106
|
+
*/
|
|
107
|
+
export class RegistryError extends CliError {
|
|
108
|
+
constructor(message: string) {
|
|
109
|
+
super(
|
|
110
|
+
message,
|
|
111
|
+
EXIT_REGISTRY_ERROR,
|
|
112
|
+
"The registry may be temporarily unavailable. Try again later."
|
|
113
|
+
);
|
|
114
|
+
this.name = "RegistryError";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Trust verification error
|
|
120
|
+
*/
|
|
121
|
+
export class TrustError extends CliError {
|
|
122
|
+
constructor(message: string) {
|
|
123
|
+
super(
|
|
124
|
+
message,
|
|
125
|
+
EXIT_TRUST_ERROR,
|
|
126
|
+
"Verify the tool's attestations with 'enact trust check <tool>'"
|
|
127
|
+
);
|
|
128
|
+
this.name = "TrustError";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Timeout error
|
|
134
|
+
*/
|
|
135
|
+
export class TimeoutError extends CliError {
|
|
136
|
+
constructor(operation: string, timeoutMs: number) {
|
|
137
|
+
const timeoutSec = Math.round(timeoutMs / 1000);
|
|
138
|
+
super(
|
|
139
|
+
`${operation} timed out after ${timeoutSec}s`,
|
|
140
|
+
EXIT_TIMEOUT,
|
|
141
|
+
"Try increasing the timeout with --timeout option."
|
|
142
|
+
);
|
|
143
|
+
this.name = "TimeoutError";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Execution error
|
|
149
|
+
*/
|
|
150
|
+
export class ExecutionError extends CliError {
|
|
151
|
+
constructor(
|
|
152
|
+
message: string,
|
|
153
|
+
public readonly stderr?: string
|
|
154
|
+
) {
|
|
155
|
+
super(message, EXIT_EXECUTION_ERROR);
|
|
156
|
+
this.name = "ExecutionError";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Container error
|
|
162
|
+
*/
|
|
163
|
+
export class ContainerError extends CliError {
|
|
164
|
+
constructor(message: string) {
|
|
165
|
+
super(message, EXIT_CONTAINER_ERROR, "Ensure Docker or another container runtime is running.");
|
|
166
|
+
this.name = "ContainerError";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* File not found error
|
|
172
|
+
*/
|
|
173
|
+
export class FileNotFoundError extends CliError {
|
|
174
|
+
constructor(path: string) {
|
|
175
|
+
super(`File not found: ${path}`, EXIT_NOINPUT);
|
|
176
|
+
this.name = "FileNotFoundError";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Permission error
|
|
182
|
+
*/
|
|
183
|
+
export class PermissionError extends CliError {
|
|
184
|
+
constructor(path: string) {
|
|
185
|
+
super(`Permission denied: ${path}`, EXIT_NOPERM);
|
|
186
|
+
this.name = "PermissionError";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Configuration error
|
|
192
|
+
*/
|
|
193
|
+
export class ConfigError extends CliError {
|
|
194
|
+
constructor(message: string) {
|
|
195
|
+
super(message, EXIT_CONFIG, "Check your configuration with 'enact config list'.");
|
|
196
|
+
this.name = "ConfigError";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Usage error (invalid arguments)
|
|
202
|
+
*/
|
|
203
|
+
export class UsageError extends CliError {
|
|
204
|
+
constructor(message: string) {
|
|
205
|
+
super(message, EXIT_USAGE);
|
|
206
|
+
this.name = "UsageError";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Error Handling
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handle an error and exit with appropriate code
|
|
216
|
+
*/
|
|
217
|
+
export function handleError(err: unknown, options?: { verbose: boolean }): never {
|
|
218
|
+
if (err instanceof CliError) {
|
|
219
|
+
printError(err.message);
|
|
220
|
+
if (err.suggestion) {
|
|
221
|
+
suggest(err.suggestion);
|
|
222
|
+
}
|
|
223
|
+
if (options?.verbose && err.stack) {
|
|
224
|
+
newline();
|
|
225
|
+
dim(err.stack);
|
|
226
|
+
}
|
|
227
|
+
process.exit(err.exitCode);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Handle standard errors
|
|
231
|
+
if (err instanceof Error) {
|
|
232
|
+
printError(err.message);
|
|
233
|
+
if (options?.verbose && err.stack) {
|
|
234
|
+
newline();
|
|
235
|
+
dim(err.stack);
|
|
236
|
+
}
|
|
237
|
+
process.exit(EXIT_FAILURE);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Handle unknown errors
|
|
241
|
+
printError(String(err));
|
|
242
|
+
process.exit(EXIT_FAILURE);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Wrap an async function with error handling
|
|
247
|
+
*/
|
|
248
|
+
export function withErrorHandling<T extends unknown[]>(
|
|
249
|
+
fn: (...args: T) => Promise<void>,
|
|
250
|
+
options?: { verbose: boolean }
|
|
251
|
+
): (...args: T) => Promise<void> {
|
|
252
|
+
return async (...args: T) => {
|
|
253
|
+
try {
|
|
254
|
+
await fn(...args);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
handleError(err, options);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Categorize an error and return appropriate CliError
|
|
263
|
+
*/
|
|
264
|
+
export function categorizeError(err: unknown): CliError {
|
|
265
|
+
if (err instanceof CliError) {
|
|
266
|
+
return err;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (err instanceof Error) {
|
|
270
|
+
const message = err.message.toLowerCase();
|
|
271
|
+
|
|
272
|
+
// Network errors
|
|
273
|
+
if (
|
|
274
|
+
message.includes("network") ||
|
|
275
|
+
message.includes("econnrefused") ||
|
|
276
|
+
message.includes("enotfound") ||
|
|
277
|
+
message.includes("etimedout") ||
|
|
278
|
+
message.includes("fetch failed")
|
|
279
|
+
) {
|
|
280
|
+
return new NetworkError(err.message);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Permission errors
|
|
284
|
+
if (
|
|
285
|
+
message.includes("eacces") ||
|
|
286
|
+
message.includes("permission denied") ||
|
|
287
|
+
message.includes("eperm")
|
|
288
|
+
) {
|
|
289
|
+
return new PermissionError(err.message);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// File not found
|
|
293
|
+
if (message.includes("enoent") || message.includes("no such file")) {
|
|
294
|
+
return new FileNotFoundError(err.message);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Timeout
|
|
298
|
+
if (message.includes("timeout") || message.includes("timed out")) {
|
|
299
|
+
return new TimeoutError("Operation", 30000);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Authentication
|
|
303
|
+
if (
|
|
304
|
+
message.includes("unauthorized") ||
|
|
305
|
+
message.includes("401") ||
|
|
306
|
+
message.includes("authentication")
|
|
307
|
+
) {
|
|
308
|
+
return new AuthError(err.message);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Return generic CliError
|
|
312
|
+
return new CliError(err.message);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return new CliError(String(err));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// Error Messages with Actions
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Common error messages with actionable suggestions
|
|
324
|
+
*/
|
|
325
|
+
export const ErrorMessages = {
|
|
326
|
+
toolNotFound: (name: string) => ({
|
|
327
|
+
message: `Tool "${name}" not found`,
|
|
328
|
+
suggestions: [
|
|
329
|
+
"Check the tool name spelling",
|
|
330
|
+
`Search for tools: ${colors.command("enact search <query>")}`,
|
|
331
|
+
`Install from path: ${colors.command("enact install ./path/to/tool")}`,
|
|
332
|
+
],
|
|
333
|
+
}),
|
|
334
|
+
|
|
335
|
+
notAuthenticated: () => ({
|
|
336
|
+
message: "You are not authenticated",
|
|
337
|
+
suggestions: [
|
|
338
|
+
`Log in: ${colors.command("enact auth login")}`,
|
|
339
|
+
`Check status: ${colors.command("enact auth status")}`,
|
|
340
|
+
],
|
|
341
|
+
}),
|
|
342
|
+
|
|
343
|
+
manifestNotFound: (dir: string) => ({
|
|
344
|
+
message: `No manifest found in ${dir}`,
|
|
345
|
+
suggestions: [
|
|
346
|
+
`Create a manifest: ${colors.command("enact init")}`,
|
|
347
|
+
"Ensure the directory contains enact.yaml or enact.md",
|
|
348
|
+
],
|
|
349
|
+
}),
|
|
350
|
+
|
|
351
|
+
invalidManifest: (errors: string[]) => ({
|
|
352
|
+
message: "Invalid manifest",
|
|
353
|
+
suggestions: [
|
|
354
|
+
"Fix the following errors:",
|
|
355
|
+
...errors.map((e) => ` ${colors.error("•")} ${e}`),
|
|
356
|
+
`Validate manifest: ${colors.command("enact validate")}`,
|
|
357
|
+
],
|
|
358
|
+
}),
|
|
359
|
+
|
|
360
|
+
registryUnavailable: () => ({
|
|
361
|
+
message: "Registry is unavailable",
|
|
362
|
+
suggestions: [
|
|
363
|
+
"Check your internet connection",
|
|
364
|
+
`Verify registry URL: ${colors.command("enact config get registry.url")}`,
|
|
365
|
+
"Try again later",
|
|
366
|
+
],
|
|
367
|
+
}),
|
|
368
|
+
|
|
369
|
+
containerRuntimeNotFound: () => ({
|
|
370
|
+
message: "No container runtime found",
|
|
371
|
+
suggestions: [
|
|
372
|
+
"Install Docker: https://docs.docker.com/get-docker/",
|
|
373
|
+
"Or install Podman: https://podman.io/getting-started/installation",
|
|
374
|
+
"Ensure the runtime is running",
|
|
375
|
+
],
|
|
376
|
+
}),
|
|
377
|
+
|
|
378
|
+
trustVerificationFailed: (tool: string) => ({
|
|
379
|
+
message: `Trust verification failed for "${tool}"`,
|
|
380
|
+
suggestions: [
|
|
381
|
+
"The tool has no valid attestations",
|
|
382
|
+
`Add a trusted publisher: ${colors.command("enact trust add <identity>")}`,
|
|
383
|
+
`Check attestations: ${colors.command(`enact trust check ${tool}`)}`,
|
|
384
|
+
],
|
|
385
|
+
}),
|
|
386
|
+
|
|
387
|
+
executionFailed: (tool: string, exitCode: number) => ({
|
|
388
|
+
message: `Tool "${tool}" failed with exit code ${exitCode}`,
|
|
389
|
+
suggestions: [
|
|
390
|
+
"Check tool logs for details",
|
|
391
|
+
"Verify input parameters",
|
|
392
|
+
`Run with verbose output: ${colors.command("enact run --verbose")}`,
|
|
393
|
+
],
|
|
394
|
+
}),
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Print an error with actionable suggestions
|
|
399
|
+
*/
|
|
400
|
+
export function printErrorWithSuggestions(errorInfo: {
|
|
401
|
+
message: string;
|
|
402
|
+
suggestions: string[];
|
|
403
|
+
}): void {
|
|
404
|
+
printError(errorInfo.message);
|
|
405
|
+
newline();
|
|
406
|
+
for (const suggestion of errorInfo.suggestions) {
|
|
407
|
+
console.log(` ${colors.dim("•")} ${suggestion}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit codes for CLI commands
|
|
3
|
+
*
|
|
4
|
+
* Standardized exit codes following Unix conventions:
|
|
5
|
+
* - 0: Success
|
|
6
|
+
* - 1: General error
|
|
7
|
+
* - 2: Misuse of shell command (invalid args)
|
|
8
|
+
* - 64-78: BSD sysexits.h codes
|
|
9
|
+
* - 128+: Signal-based exits
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.freebsd.org/cgi/man.cgi?query=sysexits
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Success - command completed successfully */
|
|
15
|
+
export const EXIT_SUCCESS = 0;
|
|
16
|
+
|
|
17
|
+
/** General error - unspecified failure */
|
|
18
|
+
export const EXIT_FAILURE = 1;
|
|
19
|
+
|
|
20
|
+
/** Usage error - invalid command line arguments */
|
|
21
|
+
export const EXIT_USAGE = 2;
|
|
22
|
+
|
|
23
|
+
/** Data error - input data was incorrect */
|
|
24
|
+
export const EXIT_DATAERR = 65;
|
|
25
|
+
|
|
26
|
+
/** No input - input file did not exist or was not readable */
|
|
27
|
+
export const EXIT_NOINPUT = 66;
|
|
28
|
+
|
|
29
|
+
/** No user - user does not exist */
|
|
30
|
+
export const EXIT_NOUSER = 67;
|
|
31
|
+
|
|
32
|
+
/** No host - host does not exist */
|
|
33
|
+
export const EXIT_NOHOST = 68;
|
|
34
|
+
|
|
35
|
+
/** Service unavailable - a required service is unavailable */
|
|
36
|
+
export const EXIT_UNAVAILABLE = 69;
|
|
37
|
+
|
|
38
|
+
/** Software error - internal error */
|
|
39
|
+
export const EXIT_SOFTWARE = 70;
|
|
40
|
+
|
|
41
|
+
/** OS error - system error */
|
|
42
|
+
export const EXIT_OSERR = 71;
|
|
43
|
+
|
|
44
|
+
/** OS file - system file missing or not creatable */
|
|
45
|
+
export const EXIT_OSFILE = 72;
|
|
46
|
+
|
|
47
|
+
/** Can't create - output file cannot be created */
|
|
48
|
+
export const EXIT_CANTCREAT = 73;
|
|
49
|
+
|
|
50
|
+
/** I/O error - input/output error */
|
|
51
|
+
export const EXIT_IOERR = 74;
|
|
52
|
+
|
|
53
|
+
/** Temp failure - temporary failure, try again */
|
|
54
|
+
export const EXIT_TEMPFAIL = 75;
|
|
55
|
+
|
|
56
|
+
/** Protocol error - remote error in protocol */
|
|
57
|
+
export const EXIT_PROTOCOL = 76;
|
|
58
|
+
|
|
59
|
+
/** No permission - permission denied */
|
|
60
|
+
export const EXIT_NOPERM = 77;
|
|
61
|
+
|
|
62
|
+
/** Configuration error - configuration error */
|
|
63
|
+
export const EXIT_CONFIG = 78;
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Enact-specific exit codes (100-119)
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/** Tool not found */
|
|
70
|
+
export const EXIT_TOOL_NOT_FOUND = 100;
|
|
71
|
+
|
|
72
|
+
/** Manifest error - invalid or missing manifest */
|
|
73
|
+
export const EXIT_MANIFEST_ERROR = 101;
|
|
74
|
+
|
|
75
|
+
/** Execution error - tool execution failed */
|
|
76
|
+
export const EXIT_EXECUTION_ERROR = 102;
|
|
77
|
+
|
|
78
|
+
/** Timeout error - operation timed out */
|
|
79
|
+
export const EXIT_TIMEOUT = 103;
|
|
80
|
+
|
|
81
|
+
/** Trust error - trust verification failed */
|
|
82
|
+
export const EXIT_TRUST_ERROR = 104;
|
|
83
|
+
|
|
84
|
+
/** Registry error - registry communication failed */
|
|
85
|
+
export const EXIT_REGISTRY_ERROR = 105;
|
|
86
|
+
|
|
87
|
+
/** Authentication error - not authenticated or token expired */
|
|
88
|
+
export const EXIT_AUTH_ERROR = 106;
|
|
89
|
+
|
|
90
|
+
/** Validation error - input validation failed */
|
|
91
|
+
export const EXIT_VALIDATION_ERROR = 107;
|
|
92
|
+
|
|
93
|
+
/** Network error - network communication failed */
|
|
94
|
+
export const EXIT_NETWORK_ERROR = 108;
|
|
95
|
+
|
|
96
|
+
/** Container error - container runtime error */
|
|
97
|
+
export const EXIT_CONTAINER_ERROR = 109;
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Exit code descriptions
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
const EXIT_CODE_DESCRIPTIONS: Record<number, string> = {
|
|
104
|
+
[EXIT_SUCCESS]: "Success",
|
|
105
|
+
[EXIT_FAILURE]: "General error",
|
|
106
|
+
[EXIT_USAGE]: "Invalid command line arguments",
|
|
107
|
+
[EXIT_DATAERR]: "Input data was incorrect",
|
|
108
|
+
[EXIT_NOINPUT]: "Input file not found or not readable",
|
|
109
|
+
[EXIT_NOUSER]: "User not found",
|
|
110
|
+
[EXIT_NOHOST]: "Host not found",
|
|
111
|
+
[EXIT_UNAVAILABLE]: "Service unavailable",
|
|
112
|
+
[EXIT_SOFTWARE]: "Internal software error",
|
|
113
|
+
[EXIT_OSERR]: "System error",
|
|
114
|
+
[EXIT_OSFILE]: "System file missing",
|
|
115
|
+
[EXIT_CANTCREAT]: "Cannot create output file",
|
|
116
|
+
[EXIT_IOERR]: "I/O error",
|
|
117
|
+
[EXIT_TEMPFAIL]: "Temporary failure, try again",
|
|
118
|
+
[EXIT_PROTOCOL]: "Protocol error",
|
|
119
|
+
[EXIT_NOPERM]: "Permission denied",
|
|
120
|
+
[EXIT_CONFIG]: "Configuration error",
|
|
121
|
+
[EXIT_TOOL_NOT_FOUND]: "Tool not found",
|
|
122
|
+
[EXIT_MANIFEST_ERROR]: "Manifest error",
|
|
123
|
+
[EXIT_EXECUTION_ERROR]: "Tool execution failed",
|
|
124
|
+
[EXIT_TIMEOUT]: "Operation timed out",
|
|
125
|
+
[EXIT_TRUST_ERROR]: "Trust verification failed",
|
|
126
|
+
[EXIT_REGISTRY_ERROR]: "Registry error",
|
|
127
|
+
[EXIT_AUTH_ERROR]: "Authentication error",
|
|
128
|
+
[EXIT_VALIDATION_ERROR]: "Validation error",
|
|
129
|
+
[EXIT_NETWORK_ERROR]: "Network error",
|
|
130
|
+
[EXIT_CONTAINER_ERROR]: "Container runtime error",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get description for an exit code
|
|
135
|
+
*/
|
|
136
|
+
export function getExitCodeDescription(code: number): string {
|
|
137
|
+
return EXIT_CODE_DESCRIPTIONS[code] ?? `Unknown error (code ${code})`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Exit with a specific code
|
|
142
|
+
*/
|
|
143
|
+
export function exit(code: number): never {
|
|
144
|
+
process.exit(code);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Exit with success
|
|
149
|
+
*/
|
|
150
|
+
export function exitSuccess(): never {
|
|
151
|
+
process.exit(EXIT_SUCCESS);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Exit with failure
|
|
156
|
+
*/
|
|
157
|
+
export function exitFailure(): never {
|
|
158
|
+
process.exit(EXIT_FAILURE);
|
|
159
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File ignore utilities for bundling
|
|
3
|
+
*
|
|
4
|
+
* Provides gitignore-style pattern matching and default ignore lists
|
|
5
|
+
* to prevent sensitive files from being included in tool bundles.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
/** Files/patterns that should never be included in bundles */
|
|
12
|
+
export const ALWAYS_IGNORE = [
|
|
13
|
+
// Environment and secrets
|
|
14
|
+
".env",
|
|
15
|
+
".env.local",
|
|
16
|
+
".env.development",
|
|
17
|
+
".env.production",
|
|
18
|
+
".env.*.local",
|
|
19
|
+
"*.pem",
|
|
20
|
+
"*.key",
|
|
21
|
+
"*.p12",
|
|
22
|
+
"*.pfx",
|
|
23
|
+
|
|
24
|
+
// Version control
|
|
25
|
+
".git",
|
|
26
|
+
".gitignore",
|
|
27
|
+
".gitattributes",
|
|
28
|
+
|
|
29
|
+
// IDE/Editor
|
|
30
|
+
".vscode",
|
|
31
|
+
".idea",
|
|
32
|
+
"*.swp",
|
|
33
|
+
"*.swo",
|
|
34
|
+
"*~",
|
|
35
|
+
|
|
36
|
+
// OS files
|
|
37
|
+
".DS_Store",
|
|
38
|
+
"Thumbs.db",
|
|
39
|
+
"desktop.ini",
|
|
40
|
+
|
|
41
|
+
// Dependencies
|
|
42
|
+
"node_modules",
|
|
43
|
+
"vendor",
|
|
44
|
+
"__pycache__",
|
|
45
|
+
"*.pyc",
|
|
46
|
+
".venv",
|
|
47
|
+
"venv",
|
|
48
|
+
|
|
49
|
+
// Build artifacts
|
|
50
|
+
"dist",
|
|
51
|
+
"build",
|
|
52
|
+
"out",
|
|
53
|
+
"target",
|
|
54
|
+
"*.log",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load and parse a .gitignore file from a directory
|
|
59
|
+
*/
|
|
60
|
+
export function loadGitignore(toolDir: string): string[] {
|
|
61
|
+
const gitignorePath = join(toolDir, ".gitignore");
|
|
62
|
+
if (!existsSync(gitignorePath)) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
67
|
+
return parseGitignoreContent(content);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse gitignore content string into patterns
|
|
72
|
+
*/
|
|
73
|
+
export function parseGitignoreContent(content: string): string[] {
|
|
74
|
+
return content
|
|
75
|
+
.split("\n")
|
|
76
|
+
.map((line) => line.trim())
|
|
77
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a path matches a gitignore-style pattern
|
|
82
|
+
*/
|
|
83
|
+
export function matchesPattern(relativePath: string, pattern: string): boolean {
|
|
84
|
+
// Handle negation patterns (we don't support re-including for now)
|
|
85
|
+
if (pattern.startsWith("!")) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Normalize pattern
|
|
90
|
+
let normalizedPattern = pattern;
|
|
91
|
+
|
|
92
|
+
// Handle directory-specific patterns (ending with /)
|
|
93
|
+
const isDirPattern = pattern.endsWith("/");
|
|
94
|
+
if (isDirPattern) {
|
|
95
|
+
normalizedPattern = pattern.slice(0, -1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle patterns starting with /
|
|
99
|
+
const isRooted = pattern.startsWith("/");
|
|
100
|
+
if (isRooted) {
|
|
101
|
+
normalizedPattern = normalizedPattern.slice(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Simple glob matching
|
|
105
|
+
const regexPattern = normalizedPattern
|
|
106
|
+
.replace(/\./g, "\\.")
|
|
107
|
+
.replace(/\*\*/g, "<<<GLOBSTAR>>>")
|
|
108
|
+
.replace(/\*/g, "[^/]*")
|
|
109
|
+
.replace(/<<<GLOBSTAR>>>/g, ".*")
|
|
110
|
+
.replace(/\?/g, ".");
|
|
111
|
+
|
|
112
|
+
const regex = isRooted
|
|
113
|
+
? new RegExp(`^${regexPattern}($|/)`)
|
|
114
|
+
: new RegExp(`(^|/)${regexPattern}($|/)`);
|
|
115
|
+
|
|
116
|
+
return regex.test(relativePath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if a file should be ignored based on patterns
|
|
121
|
+
*/
|
|
122
|
+
export function shouldIgnore(
|
|
123
|
+
relativePath: string,
|
|
124
|
+
fileName: string,
|
|
125
|
+
ignorePatterns: string[] = []
|
|
126
|
+
): boolean {
|
|
127
|
+
// Always skip hidden files (starting with .)
|
|
128
|
+
if (fileName.startsWith(".")) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check against always-ignore list
|
|
133
|
+
for (const pattern of ALWAYS_IGNORE) {
|
|
134
|
+
if (matchesPattern(relativePath, pattern) || matchesPattern(fileName, pattern)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check against gitignore patterns
|
|
140
|
+
for (const pattern of ignorePatterns) {
|
|
141
|
+
if (matchesPattern(relativePath, pattern)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return false;
|
|
147
|
+
}
|