@fairfox/polly 0.1.1 ā 0.1.3
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/cli/polly.ts +9 -3
- package/package.json +2 -2
- package/vendor/analysis/src/extract/adr.ts +212 -0
- package/vendor/analysis/src/extract/architecture.ts +160 -0
- package/vendor/analysis/src/extract/contexts.ts +298 -0
- package/vendor/analysis/src/extract/flows.ts +309 -0
- package/vendor/analysis/src/extract/handlers.ts +321 -0
- package/vendor/analysis/src/extract/index.ts +9 -0
- package/vendor/analysis/src/extract/integrations.ts +329 -0
- package/vendor/analysis/src/extract/manifest.ts +298 -0
- package/vendor/analysis/src/extract/types.ts +389 -0
- package/vendor/analysis/src/index.ts +7 -0
- package/vendor/analysis/src/types/adr.ts +53 -0
- package/vendor/analysis/src/types/architecture.ts +245 -0
- package/vendor/analysis/src/types/core.ts +210 -0
- package/vendor/analysis/src/types/index.ts +18 -0
- package/vendor/verify/src/adapters/base.ts +164 -0
- package/vendor/verify/src/adapters/detection.ts +281 -0
- package/vendor/verify/src/adapters/event-bus/index.ts +480 -0
- package/vendor/verify/src/adapters/web-extension/index.ts +508 -0
- package/vendor/verify/src/adapters/websocket/index.ts +486 -0
- package/vendor/verify/src/cli.ts +430 -0
- package/vendor/verify/src/codegen/config.ts +354 -0
- package/vendor/verify/src/codegen/tla.ts +719 -0
- package/vendor/verify/src/config/parser.ts +303 -0
- package/vendor/verify/src/config/types.ts +113 -0
- package/vendor/verify/src/core/model.ts +267 -0
- package/vendor/verify/src/core/primitives.ts +106 -0
- package/vendor/verify/src/extract/handlers.ts +2 -0
- package/vendor/verify/src/extract/types.ts +2 -0
- package/vendor/verify/src/index.ts +150 -0
- package/vendor/verify/src/primitives/index.ts +102 -0
- package/vendor/verify/src/runner/docker.ts +283 -0
- package/vendor/verify/src/types.ts +51 -0
- package/vendor/visualize/src/cli.ts +365 -0
- package/vendor/visualize/src/codegen/structurizr.ts +770 -0
- package/vendor/visualize/src/index.ts +13 -0
- package/vendor/visualize/src/runner/export.ts +235 -0
- package/vendor/visualize/src/viewer/server.ts +485 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// CLI for verification system
|
|
3
|
+
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { analyzeCodebase } from "./extract/types";
|
|
7
|
+
import { generateConfig } from "./codegen/config";
|
|
8
|
+
import { validateConfig } from "./config/parser";
|
|
9
|
+
|
|
10
|
+
const COLORS = {
|
|
11
|
+
reset: "\x1b[0m",
|
|
12
|
+
red: "\x1b[31m",
|
|
13
|
+
green: "\x1b[32m",
|
|
14
|
+
yellow: "\x1b[33m",
|
|
15
|
+
blue: "\x1b[34m",
|
|
16
|
+
gray: "\x1b[90m",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function color(text: string, colorCode: string): string {
|
|
20
|
+
return `${colorCode}${text}${COLORS.reset}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const command = args[0];
|
|
26
|
+
|
|
27
|
+
switch (command) {
|
|
28
|
+
case "--setup":
|
|
29
|
+
case "setup":
|
|
30
|
+
await setupCommand();
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case "--validate":
|
|
34
|
+
case "validate":
|
|
35
|
+
await validateCommand();
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
case "--help":
|
|
39
|
+
case "help":
|
|
40
|
+
showHelp();
|
|
41
|
+
break;
|
|
42
|
+
|
|
43
|
+
default:
|
|
44
|
+
await verifyCommand();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function setupCommand() {
|
|
49
|
+
console.log(color("\nš Analyzing codebase...\n", COLORS.blue));
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Find tsconfig
|
|
53
|
+
const tsConfigPath = findTsConfig();
|
|
54
|
+
if (!tsConfigPath) {
|
|
55
|
+
console.error(color("ā Could not find tsconfig.json", COLORS.red));
|
|
56
|
+
console.error(" Run this command from your project root");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(color(` Using: ${tsConfigPath}`, COLORS.gray));
|
|
61
|
+
|
|
62
|
+
// Analyze codebase
|
|
63
|
+
const analysis = await analyzeCodebase({
|
|
64
|
+
tsConfigPath,
|
|
65
|
+
stateFilePath: findStateFile(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!analysis.stateType) {
|
|
69
|
+
console.log(color("\nā ļø Could not find state type definition", COLORS.yellow));
|
|
70
|
+
console.log(" Expected to find a type named 'AppState' or 'State'");
|
|
71
|
+
console.log(" in a file matching **/state*.ts");
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(" You can still generate a config template:");
|
|
74
|
+
console.log(" It will be empty and you'll need to fill it in manually.");
|
|
75
|
+
console.log();
|
|
76
|
+
} else {
|
|
77
|
+
console.log(
|
|
78
|
+
color(`ā Found state type with ${analysis.fields.length} field(s)`, COLORS.green)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(color(`ā Found ${analysis.messageTypes.length} message type(s)`, COLORS.green));
|
|
83
|
+
|
|
84
|
+
// Show analysis summary
|
|
85
|
+
if (analysis.fields.length > 0) {
|
|
86
|
+
console.log(color("\nš Configuration Summary:\n", COLORS.blue));
|
|
87
|
+
|
|
88
|
+
const table = [
|
|
89
|
+
["Field", "Type", "Status"],
|
|
90
|
+
["ā".repeat(30), "ā".repeat(20), "ā".repeat(20)],
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const field of analysis.fields) {
|
|
94
|
+
const status =
|
|
95
|
+
field.confidence === "high"
|
|
96
|
+
? color("ā Auto-configured", COLORS.green)
|
|
97
|
+
: field.confidence === "medium"
|
|
98
|
+
? color("ā Review needed", COLORS.yellow)
|
|
99
|
+
: color("ā Manual config", COLORS.red);
|
|
100
|
+
|
|
101
|
+
table.push([field.path, field.type.kind, status]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const row of table) {
|
|
105
|
+
console.log(` ${row[0].padEnd(32)} ${row[1].padEnd(22)} ${row[2]}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Generate config
|
|
110
|
+
const configContent = generateConfig(analysis);
|
|
111
|
+
const configPath = path.join(process.cwd(), "specs", "verification.config.ts");
|
|
112
|
+
|
|
113
|
+
// Ensure directory exists
|
|
114
|
+
const configDir = path.dirname(configPath);
|
|
115
|
+
if (!fs.existsSync(configDir)) {
|
|
116
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Write config
|
|
120
|
+
fs.writeFileSync(configPath, configContent, "utf-8");
|
|
121
|
+
|
|
122
|
+
console.log(color("\nā
Configuration generated!\n", COLORS.green));
|
|
123
|
+
console.log(` File: ${color(configPath, COLORS.blue)}`);
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(color("š Next steps:", COLORS.blue));
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(" 1. Review the generated configuration file");
|
|
128
|
+
console.log(" 2. Fill in values marked with /* CONFIGURE */");
|
|
129
|
+
console.log(" 3. Run 'bun verify' to check your configuration");
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(color("š” Tip:", COLORS.gray));
|
|
132
|
+
console.log(color(" Look for comments explaining what each field needs.", COLORS.gray));
|
|
133
|
+
console.log();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(color("\nā Setup failed:", COLORS.red));
|
|
136
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function validateCommand() {
|
|
142
|
+
const configPath = path.join(process.cwd(), "specs", "verification.config.ts");
|
|
143
|
+
|
|
144
|
+
console.log(color("\nš Validating configuration...\n", COLORS.blue));
|
|
145
|
+
|
|
146
|
+
const result = validateConfig(configPath);
|
|
147
|
+
|
|
148
|
+
if (result.valid) {
|
|
149
|
+
console.log(color("ā
Configuration is complete and valid!\n", COLORS.green));
|
|
150
|
+
console.log(" You can now run 'bun verify' to start verification.");
|
|
151
|
+
console.log();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Show errors
|
|
156
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
157
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
158
|
+
|
|
159
|
+
if (errors.length > 0) {
|
|
160
|
+
console.log(color(`ā Found ${errors.length} error(s):\n`, COLORS.red));
|
|
161
|
+
|
|
162
|
+
for (const error of errors) {
|
|
163
|
+
console.log(color(` ⢠${error.message}`, COLORS.red));
|
|
164
|
+
if (error.field) {
|
|
165
|
+
console.log(color(` Field: ${error.field}`, COLORS.gray));
|
|
166
|
+
}
|
|
167
|
+
if (error.location) {
|
|
168
|
+
console.log(color(` Location: line ${error.location.line}`, COLORS.gray));
|
|
169
|
+
}
|
|
170
|
+
console.log(color(` ā ${error.suggestion}`, COLORS.yellow));
|
|
171
|
+
console.log();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (warnings.length > 0) {
|
|
176
|
+
console.log(color(`ā ļø Found ${warnings.length} warning(s):\n`, COLORS.yellow));
|
|
177
|
+
|
|
178
|
+
for (const warning of warnings) {
|
|
179
|
+
console.log(color(` ⢠${warning.message}`, COLORS.yellow));
|
|
180
|
+
if (warning.field) {
|
|
181
|
+
console.log(color(` Field: ${warning.field}`, COLORS.gray));
|
|
182
|
+
}
|
|
183
|
+
console.log(color(` ā ${warning.suggestion}`, COLORS.gray));
|
|
184
|
+
console.log();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(color("Configuration incomplete. Please fix the errors above.\n", COLORS.red));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function verifyCommand() {
|
|
193
|
+
const configPath = path.join(process.cwd(), "specs", "verification.config.ts");
|
|
194
|
+
|
|
195
|
+
console.log(color("\nš Running verification...\n", COLORS.blue));
|
|
196
|
+
|
|
197
|
+
// First validate config
|
|
198
|
+
const validation = validateConfig(configPath);
|
|
199
|
+
|
|
200
|
+
if (!validation.valid) {
|
|
201
|
+
const errors = validation.issues.filter((i) => i.severity === "error");
|
|
202
|
+
console.log(color(`ā Configuration incomplete (${errors.length} error(s))\n`, COLORS.red));
|
|
203
|
+
|
|
204
|
+
for (const error of errors.slice(0, 3)) {
|
|
205
|
+
console.log(color(` ⢠${error.message}`, COLORS.red));
|
|
206
|
+
if (error.field) {
|
|
207
|
+
console.log(color(` Field: ${error.field}`, COLORS.gray));
|
|
208
|
+
}
|
|
209
|
+
console.log();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (errors.length > 3) {
|
|
213
|
+
console.log(color(` ... and ${errors.length - 3} more error(s)`, COLORS.gray));
|
|
214
|
+
console.log();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(" Run 'bun verify --validate' to see all issues");
|
|
218
|
+
console.log(" Run 'bun verify --setup' to regenerate configuration");
|
|
219
|
+
console.log();
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
console.log(color("ā Configuration valid", COLORS.green));
|
|
224
|
+
console.log();
|
|
225
|
+
|
|
226
|
+
// Run full verification
|
|
227
|
+
try {
|
|
228
|
+
await runFullVerification(configPath);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error(color("\nā Verification failed:", COLORS.red));
|
|
231
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function runFullVerification(configPath: string) {
|
|
237
|
+
const { generateTLA } = await import("./codegen/tla");
|
|
238
|
+
const { DockerRunner } = await import("./runner/docker");
|
|
239
|
+
|
|
240
|
+
// Load config
|
|
241
|
+
delete require.cache[require.resolve(path.resolve(configPath))];
|
|
242
|
+
const configModule = require(path.resolve(configPath));
|
|
243
|
+
const config = configModule.default || configModule;
|
|
244
|
+
|
|
245
|
+
// Analyze codebase
|
|
246
|
+
console.log(color("š Analyzing codebase...", COLORS.blue));
|
|
247
|
+
const tsConfigPath = findTsConfig();
|
|
248
|
+
if (!tsConfigPath) {
|
|
249
|
+
throw new Error("Could not find tsconfig.json");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const analysis = await analyzeCodebase({
|
|
253
|
+
tsConfigPath,
|
|
254
|
+
stateFilePath: findStateFile(),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
console.log(color("ā Analysis complete", COLORS.green));
|
|
258
|
+
console.log();
|
|
259
|
+
|
|
260
|
+
// Generate TLA+ specs
|
|
261
|
+
console.log(color("š Generating TLA+ specification...", COLORS.blue));
|
|
262
|
+
const { spec, cfg } = generateTLA(config, analysis);
|
|
263
|
+
|
|
264
|
+
// Write specs to temp directory
|
|
265
|
+
const specDir = path.join(process.cwd(), "specs", "tla", "generated");
|
|
266
|
+
if (!fs.existsSync(specDir)) {
|
|
267
|
+
fs.mkdirSync(specDir, { recursive: true });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const specPath = path.join(specDir, "UserApp.tla");
|
|
271
|
+
const cfgPath = path.join(specDir, "UserApp.cfg");
|
|
272
|
+
|
|
273
|
+
fs.writeFileSync(specPath, spec);
|
|
274
|
+
fs.writeFileSync(cfgPath, cfg);
|
|
275
|
+
|
|
276
|
+
// Copy base MessageRouter spec to generated directory so TLC can find it
|
|
277
|
+
const baseSpecPath = path.join(process.cwd(), "specs", "tla", "MessageRouter.tla");
|
|
278
|
+
if (fs.existsSync(baseSpecPath)) {
|
|
279
|
+
const destSpecPath = path.join(specDir, "MessageRouter.tla");
|
|
280
|
+
fs.copyFileSync(baseSpecPath, destSpecPath);
|
|
281
|
+
} else {
|
|
282
|
+
console.log(
|
|
283
|
+
color("ā ļø Warning: MessageRouter.tla not found, verification may fail", COLORS.yellow)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log(color("ā Specification generated", COLORS.green));
|
|
288
|
+
console.log(color(` ${specPath}`, COLORS.gray));
|
|
289
|
+
console.log();
|
|
290
|
+
|
|
291
|
+
// Check Docker
|
|
292
|
+
console.log(color("š³ Checking Docker...", COLORS.blue));
|
|
293
|
+
const docker = new DockerRunner();
|
|
294
|
+
|
|
295
|
+
if (!(await docker.isDockerAvailable())) {
|
|
296
|
+
throw new Error("Docker is not available. Please install Docker and try again.");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!(await docker.hasImage())) {
|
|
300
|
+
console.log(color(" Pulling TLA+ image (this may take a moment)...", COLORS.gray));
|
|
301
|
+
await docker.pullImage((line) => {
|
|
302
|
+
console.log(color(` ${line}`, COLORS.gray));
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log(color("ā Docker ready", COLORS.green));
|
|
307
|
+
console.log();
|
|
308
|
+
|
|
309
|
+
// Run TLC
|
|
310
|
+
console.log(color("āļø Running TLC model checker...", COLORS.blue));
|
|
311
|
+
console.log(color(" This may take a minute...", COLORS.gray));
|
|
312
|
+
console.log();
|
|
313
|
+
|
|
314
|
+
const result = await docker.runTLC(specPath, {
|
|
315
|
+
workers: 2,
|
|
316
|
+
timeout: 120000, // 2 minutes
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Display results
|
|
320
|
+
if (result.success) {
|
|
321
|
+
console.log(color("ā
Verification passed!\n", COLORS.green));
|
|
322
|
+
console.log(color("Statistics:", COLORS.blue));
|
|
323
|
+
console.log(color(` States explored: ${result.stats?.statesGenerated || 0}`, COLORS.gray));
|
|
324
|
+
console.log(color(` Distinct states: ${result.stats?.distinctStates || 0}`, COLORS.gray));
|
|
325
|
+
console.log();
|
|
326
|
+
} else {
|
|
327
|
+
console.log(color("ā Verification failed!\n", COLORS.red));
|
|
328
|
+
|
|
329
|
+
if (result.violation) {
|
|
330
|
+
console.log(color(`Invariant violated: ${result.violation.name}\n`, COLORS.red));
|
|
331
|
+
console.log(color("Trace to violation:", COLORS.yellow));
|
|
332
|
+
for (const line of result.violation.trace.slice(0, 20)) {
|
|
333
|
+
console.log(color(` ${line}`, COLORS.gray));
|
|
334
|
+
}
|
|
335
|
+
if (result.violation.trace.length > 20) {
|
|
336
|
+
console.log(color(` ... (${result.violation.trace.length - 20} more lines)`, COLORS.gray));
|
|
337
|
+
}
|
|
338
|
+
} else if (result.error) {
|
|
339
|
+
console.log(color(`Error: ${result.error}`, COLORS.red));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.log();
|
|
343
|
+
console.log(color("Full output saved to:", COLORS.gray));
|
|
344
|
+
console.log(color(` ${path.join(specDir, "tlc-output.log")}`, COLORS.gray));
|
|
345
|
+
fs.writeFileSync(path.join(specDir, "tlc-output.log"), result.output);
|
|
346
|
+
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function showHelp() {
|
|
352
|
+
console.log(`
|
|
353
|
+
${color("bun verify", COLORS.blue)} - Formal verification for web extensions
|
|
354
|
+
|
|
355
|
+
${color("Commands:", COLORS.blue)}
|
|
356
|
+
|
|
357
|
+
${color("bun verify", COLORS.green)}
|
|
358
|
+
Run verification (validates config, generates specs, runs TLC)
|
|
359
|
+
|
|
360
|
+
${color("bun verify --setup", COLORS.green)}
|
|
361
|
+
Analyze codebase and generate configuration file
|
|
362
|
+
|
|
363
|
+
${color("bun verify --validate", COLORS.green)}
|
|
364
|
+
Validate existing configuration without running verification
|
|
365
|
+
|
|
366
|
+
${color("bun verify --help", COLORS.green)}
|
|
367
|
+
Show this help message
|
|
368
|
+
|
|
369
|
+
${color("Getting Started:", COLORS.blue)}
|
|
370
|
+
|
|
371
|
+
1. Run ${color("bun verify --setup", COLORS.green)} to generate configuration
|
|
372
|
+
2. Review ${color("specs/verification.config.ts", COLORS.blue)} and fill in marked fields
|
|
373
|
+
3. Run ${color("bun verify --validate", COLORS.green)} to check your configuration
|
|
374
|
+
4. Run ${color("bun verify", COLORS.green)} to start verification
|
|
375
|
+
|
|
376
|
+
${color("Configuration Help:", COLORS.blue)}
|
|
377
|
+
|
|
378
|
+
The generated config file uses special markers:
|
|
379
|
+
|
|
380
|
+
${color("/* CONFIGURE */", COLORS.yellow)} - Replace with your value
|
|
381
|
+
${color("/* REVIEW */", COLORS.yellow)} - Check auto-generated value
|
|
382
|
+
${color("null", COLORS.yellow)} - Must be replaced with concrete value
|
|
383
|
+
|
|
384
|
+
${color("Learn More:", COLORS.blue)}
|
|
385
|
+
|
|
386
|
+
Documentation: https://github.com/fairfox/web-ext
|
|
387
|
+
TLA+ Resources: https://learntla.com
|
|
388
|
+
`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function findTsConfig(): string | null {
|
|
392
|
+
const locations = [
|
|
393
|
+
path.join(process.cwd(), "tsconfig.json"),
|
|
394
|
+
path.join(process.cwd(), "packages", "web-ext", "tsconfig.json"),
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
for (const loc of locations) {
|
|
398
|
+
if (fs.existsSync(loc)) {
|
|
399
|
+
return loc;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function findStateFile(): string | undefined {
|
|
407
|
+
const locations = [
|
|
408
|
+
path.join(process.cwd(), "types", "state.ts"),
|
|
409
|
+
path.join(process.cwd(), "src", "types", "state.ts"),
|
|
410
|
+
path.join(process.cwd(), "packages", "web-ext", "src", "shared", "state", "app-state.ts"),
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
for (const loc of locations) {
|
|
414
|
+
if (fs.existsSync(loc)) {
|
|
415
|
+
return loc;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
main().catch((error) => {
|
|
423
|
+
console.error(color("\nā Fatal error:", COLORS.red));
|
|
424
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
425
|
+
if (error instanceof Error && error.stack) {
|
|
426
|
+
console.error(color("\nStack trace:", COLORS.gray));
|
|
427
|
+
console.error(color(error.stack, COLORS.gray));
|
|
428
|
+
}
|
|
429
|
+
process.exit(1);
|
|
430
|
+
});
|