@rcrsr/rill-cli 0.6.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.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/check/config.d.ts +20 -0
  3. package/dist/check/config.d.ts.map +1 -0
  4. package/dist/check/config.js +151 -0
  5. package/dist/check/config.js.map +1 -0
  6. package/dist/check/fixer.d.ts +39 -0
  7. package/dist/check/fixer.d.ts.map +1 -0
  8. package/dist/check/fixer.js +119 -0
  9. package/dist/check/fixer.js.map +1 -0
  10. package/dist/check/index.d.ts +10 -0
  11. package/dist/check/index.d.ts.map +1 -0
  12. package/dist/check/index.js +21 -0
  13. package/dist/check/index.js.map +1 -0
  14. package/dist/check/rules/anti-patterns.d.ts +65 -0
  15. package/dist/check/rules/anti-patterns.d.ts.map +1 -0
  16. package/dist/check/rules/anti-patterns.js +481 -0
  17. package/dist/check/rules/anti-patterns.js.map +1 -0
  18. package/dist/check/rules/closures.d.ts +66 -0
  19. package/dist/check/rules/closures.d.ts.map +1 -0
  20. package/dist/check/rules/closures.js +370 -0
  21. package/dist/check/rules/closures.js.map +1 -0
  22. package/dist/check/rules/collections.d.ts +90 -0
  23. package/dist/check/rules/collections.d.ts.map +1 -0
  24. package/dist/check/rules/collections.js +373 -0
  25. package/dist/check/rules/collections.js.map +1 -0
  26. package/dist/check/rules/conditionals.d.ts +41 -0
  27. package/dist/check/rules/conditionals.d.ts.map +1 -0
  28. package/dist/check/rules/conditionals.js +134 -0
  29. package/dist/check/rules/conditionals.js.map +1 -0
  30. package/dist/check/rules/flow.d.ts +46 -0
  31. package/dist/check/rules/flow.d.ts.map +1 -0
  32. package/dist/check/rules/flow.js +206 -0
  33. package/dist/check/rules/flow.js.map +1 -0
  34. package/dist/check/rules/formatting.d.ts +143 -0
  35. package/dist/check/rules/formatting.d.ts.map +1 -0
  36. package/dist/check/rules/formatting.js +656 -0
  37. package/dist/check/rules/formatting.js.map +1 -0
  38. package/dist/check/rules/helpers.d.ts +26 -0
  39. package/dist/check/rules/helpers.d.ts.map +1 -0
  40. package/dist/check/rules/helpers.js +66 -0
  41. package/dist/check/rules/helpers.js.map +1 -0
  42. package/dist/check/rules/index.d.ts +21 -0
  43. package/dist/check/rules/index.d.ts.map +1 -0
  44. package/dist/check/rules/index.js +78 -0
  45. package/dist/check/rules/index.js.map +1 -0
  46. package/dist/check/rules/loops.d.ts +77 -0
  47. package/dist/check/rules/loops.d.ts.map +1 -0
  48. package/dist/check/rules/loops.js +310 -0
  49. package/dist/check/rules/loops.js.map +1 -0
  50. package/dist/check/rules/naming.d.ts +21 -0
  51. package/dist/check/rules/naming.d.ts.map +1 -0
  52. package/dist/check/rules/naming.js +174 -0
  53. package/dist/check/rules/naming.js.map +1 -0
  54. package/dist/check/rules/strings.d.ts +28 -0
  55. package/dist/check/rules/strings.d.ts.map +1 -0
  56. package/dist/check/rules/strings.js +79 -0
  57. package/dist/check/rules/strings.js.map +1 -0
  58. package/dist/check/rules/types.d.ts +41 -0
  59. package/dist/check/rules/types.d.ts.map +1 -0
  60. package/dist/check/rules/types.js +167 -0
  61. package/dist/check/rules/types.js.map +1 -0
  62. package/dist/check/types.d.ts +112 -0
  63. package/dist/check/types.d.ts.map +1 -0
  64. package/dist/check/types.js +6 -0
  65. package/dist/check/types.js.map +1 -0
  66. package/dist/check/validator.d.ts +18 -0
  67. package/dist/check/validator.d.ts.map +1 -0
  68. package/dist/check/validator.js +110 -0
  69. package/dist/check/validator.js.map +1 -0
  70. package/dist/check/visitor.d.ts +33 -0
  71. package/dist/check/visitor.d.ts.map +1 -0
  72. package/dist/check/visitor.js +259 -0
  73. package/dist/check/visitor.js.map +1 -0
  74. package/dist/cli-check.d.ts +43 -0
  75. package/dist/cli-check.d.ts.map +1 -0
  76. package/dist/cli-check.js +366 -0
  77. package/dist/cli-check.js.map +1 -0
  78. package/dist/cli-error-enrichment.d.ts +73 -0
  79. package/dist/cli-error-enrichment.d.ts.map +1 -0
  80. package/dist/cli-error-enrichment.js +205 -0
  81. package/dist/cli-error-enrichment.js.map +1 -0
  82. package/dist/cli-error-formatter.d.ts +45 -0
  83. package/dist/cli-error-formatter.d.ts.map +1 -0
  84. package/dist/cli-error-formatter.js +218 -0
  85. package/dist/cli-error-formatter.js.map +1 -0
  86. package/dist/cli-eval.d.ts +15 -0
  87. package/dist/cli-eval.d.ts.map +1 -0
  88. package/dist/cli-eval.js +116 -0
  89. package/dist/cli-eval.js.map +1 -0
  90. package/dist/cli-exec.d.ts +58 -0
  91. package/dist/cli-exec.d.ts.map +1 -0
  92. package/dist/cli-exec.js +326 -0
  93. package/dist/cli-exec.js.map +1 -0
  94. package/dist/cli-explain.d.ts +24 -0
  95. package/dist/cli-explain.d.ts.map +1 -0
  96. package/dist/cli-explain.js +68 -0
  97. package/dist/cli-explain.js.map +1 -0
  98. package/dist/cli-lsp-diagnostic.d.ts +35 -0
  99. package/dist/cli-lsp-diagnostic.d.ts.map +1 -0
  100. package/dist/cli-lsp-diagnostic.js +98 -0
  101. package/dist/cli-lsp-diagnostic.js.map +1 -0
  102. package/dist/cli-module-loader.d.ts +19 -0
  103. package/dist/cli-module-loader.d.ts.map +1 -0
  104. package/dist/cli-module-loader.js +83 -0
  105. package/dist/cli-module-loader.js.map +1 -0
  106. package/dist/cli-shared.d.ts +62 -0
  107. package/dist/cli-shared.d.ts.map +1 -0
  108. package/dist/cli-shared.js +158 -0
  109. package/dist/cli-shared.js.map +1 -0
  110. package/dist/cli.d.ts +13 -0
  111. package/dist/cli.d.ts.map +1 -0
  112. package/dist/cli.js +62 -0
  113. package/dist/cli.js.map +1 -0
  114. package/dist/test-internal-import.d.ts +2 -0
  115. package/dist/test-internal-import.d.ts.map +1 -0
  116. package/dist/test-internal-import.js +7 -0
  117. package/dist/test-internal-import.js.map +1 -0
  118. package/package.json +24 -0
  119. package/src/check/config.ts +202 -0
  120. package/src/check/fixer.ts +174 -0
  121. package/src/check/index.ts +39 -0
  122. package/src/check/rules/anti-patterns.ts +585 -0
  123. package/src/check/rules/closures.ts +445 -0
  124. package/src/check/rules/collections.ts +437 -0
  125. package/src/check/rules/conditionals.ts +155 -0
  126. package/src/check/rules/flow.ts +262 -0
  127. package/src/check/rules/formatting.ts +811 -0
  128. package/src/check/rules/helpers.ts +89 -0
  129. package/src/check/rules/index.ts +140 -0
  130. package/src/check/rules/loops.ts +372 -0
  131. package/src/check/rules/naming.ts +242 -0
  132. package/src/check/rules/strings.ts +104 -0
  133. package/src/check/rules/types.ts +214 -0
  134. package/src/check/types.ts +163 -0
  135. package/src/check/validator.ts +136 -0
  136. package/src/check/visitor.ts +338 -0
  137. package/src/cli-check.ts +456 -0
  138. package/src/cli-error-enrichment.ts +274 -0
  139. package/src/cli-error-formatter.ts +313 -0
  140. package/src/cli-eval.ts +145 -0
  141. package/src/cli-exec.ts +408 -0
  142. package/src/cli-explain.ts +76 -0
  143. package/src/cli-lsp-diagnostic.ts +132 -0
  144. package/src/cli-module-loader.ts +101 -0
  145. package/src/cli-shared.ts +187 -0
  146. package/tests/check/cli-check.test.ts +189 -0
  147. package/tests/check/config.test.ts +350 -0
  148. package/tests/check/fixer.test.ts +373 -0
  149. package/tests/check/format-diagnostics.test.ts +327 -0
  150. package/tests/check/rules/anti-patterns.test.ts +467 -0
  151. package/tests/check/rules/closures.test.ts +192 -0
  152. package/tests/check/rules/collections.test.ts +380 -0
  153. package/tests/check/rules/conditionals.test.ts +185 -0
  154. package/tests/check/rules/flow.test.ts +250 -0
  155. package/tests/check/rules/formatting.test.ts +755 -0
  156. package/tests/check/rules/loops.test.ts +334 -0
  157. package/tests/check/rules/naming.test.ts +336 -0
  158. package/tests/check/rules/strings.test.ts +129 -0
  159. package/tests/check/rules/types.test.ts +257 -0
  160. package/tests/check/validator.test.ts +444 -0
  161. package/tests/check/visitor.test.ts +171 -0
  162. package/tests/cli/check.test.ts +801 -0
  163. package/tests/cli/error-enrichment.test.ts +510 -0
  164. package/tests/cli/error-formatter.test.ts +631 -0
  165. package/tests/cli/eval.test.ts +85 -0
  166. package/tests/cli/exec.test.ts +537 -0
  167. package/tests/cli-explain.test.ts +249 -0
  168. package/tests/cli-lsp-diagnostic.test.ts +202 -0
  169. package/tests/cli-shared.test.ts +439 -0
  170. package/tsconfig.json +9 -0
  171. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,456 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI Check Entry Point
4
+ *
5
+ * Implements argument parsing for rill-check.
6
+ * Validates Rill source files against linting rules.
7
+ */
8
+
9
+ import type { Diagnostic } from './check/index.js';
10
+ import {
11
+ VALIDATION_RULES,
12
+ loadConfig,
13
+ createDefaultConfig,
14
+ validateScript,
15
+ applyFixes,
16
+ } from './check/index.js';
17
+ import { parseWithRecovery } from '@rcrsr/rill';
18
+ import { VERSION, detectHelpVersionFlag } from './cli-shared.js';
19
+
20
+ /**
21
+ * Parsed command-line arguments for rill-check
22
+ */
23
+ export type ParsedCheckArgs =
24
+ | {
25
+ mode: 'check';
26
+ file: string;
27
+ fix: boolean;
28
+ verbose: boolean;
29
+ format: 'text' | 'json';
30
+ }
31
+ | { mode: 'help' | 'version' };
32
+
33
+ /**
34
+ * Parse command-line arguments for rill-check
35
+ *
36
+ * @param argv - Raw command-line arguments (typically process.argv.slice(2))
37
+ * @returns Parsed command object
38
+ */
39
+ export function parseCheckArgs(argv: string[]): ParsedCheckArgs {
40
+ // Check for --help or --version flags in any position
41
+ const helpVersionFlag = detectHelpVersionFlag(argv);
42
+ if (helpVersionFlag !== null) {
43
+ return helpVersionFlag;
44
+ }
45
+
46
+ // Extract flags
47
+ const fix = argv.includes('--fix');
48
+ const verbose = argv.includes('--verbose');
49
+
50
+ // Extract format flag
51
+ let format: 'text' | 'json' = 'text';
52
+ const formatIndex = argv.indexOf('--format');
53
+ if (formatIndex !== -1) {
54
+ const formatValue = argv[formatIndex + 1];
55
+ if (formatValue === 'text' || formatValue === 'json') {
56
+ format = formatValue;
57
+ } else if (!formatValue || formatValue.startsWith('-')) {
58
+ throw new Error('--format requires argument: text or json');
59
+ } else {
60
+ throw new Error(`Invalid format: ${formatValue}. Expected text or json`);
61
+ }
62
+ }
63
+
64
+ // Check for unknown flags
65
+ const knownFlags = new Set([
66
+ '--help',
67
+ '-h',
68
+ '--version',
69
+ '-v',
70
+ '--fix',
71
+ '--verbose',
72
+ '--format',
73
+ ]);
74
+
75
+ for (let i = 0; i < argv.length; i++) {
76
+ const arg = argv[i];
77
+ if (!arg) continue;
78
+
79
+ // Skip non-flag arguments
80
+ if (!arg.startsWith('-')) {
81
+ continue;
82
+ }
83
+
84
+ // Skip format value argument
85
+ if (i > 0 && argv[i - 1] === '--format') {
86
+ continue;
87
+ }
88
+
89
+ // Check if unknown flag
90
+ if (!knownFlags.has(arg)) {
91
+ throw new Error(`Unknown option: ${arg}`);
92
+ }
93
+ }
94
+
95
+ // Extract file path (first non-flag argument)
96
+ let file: string | undefined;
97
+ for (let i = 0; i < argv.length; i++) {
98
+ const arg = argv[i];
99
+ if (!arg) continue;
100
+
101
+ // Skip flags
102
+ if (arg.startsWith('-')) {
103
+ // Skip --format and its value
104
+ if (arg === '--format') {
105
+ i++; // Skip next argument (the format value)
106
+ }
107
+ continue;
108
+ }
109
+
110
+ // First non-flag argument is the file
111
+ file = arg;
112
+ break;
113
+ }
114
+
115
+ if (!file) {
116
+ throw new Error('Missing file argument');
117
+ }
118
+
119
+ return { mode: 'check', file, fix, verbose, format };
120
+ }
121
+
122
+ // ============================================================
123
+ // DIAGNOSTIC FORMATTING
124
+ // ============================================================
125
+
126
+ /**
127
+ * Format diagnostics for output
128
+ *
129
+ * Adapts pattern from cli-shared.ts formatError function.
130
+ * Text format: file:line:col: severity: message (code)
131
+ * JSON format: complete schema with errors array and summary
132
+ * Verbose mode: adds category field to diagnostics
133
+ *
134
+ * @param file - File path being checked
135
+ * @param diagnostics - Array of diagnostics to format
136
+ * @param format - Output format ('text' or 'json')
137
+ * @param verbose - Whether to include category and doc references
138
+ * @returns Formatted output string
139
+ */
140
+ export function formatDiagnostics(
141
+ file: string,
142
+ diagnostics: Diagnostic[],
143
+ format: 'text' | 'json',
144
+ verbose: boolean
145
+ ): string {
146
+ if (format === 'json') {
147
+ return formatDiagnosticsJSON(file, diagnostics, verbose);
148
+ }
149
+ return formatDiagnosticsText(file, diagnostics);
150
+ }
151
+
152
+ /**
153
+ * Format diagnostics as text
154
+ * Pattern: file:line:col: severity: message (code)
155
+ */
156
+ function formatDiagnosticsText(
157
+ file: string,
158
+ diagnostics: Diagnostic[]
159
+ ): string {
160
+ return diagnostics
161
+ .map((d) => {
162
+ const { line, column } = d.location;
163
+ return `${file}:${line}:${column}: ${d.severity}: ${d.message} (${d.code})`;
164
+ })
165
+ .join('\n');
166
+ }
167
+
168
+ /**
169
+ * Format diagnostics as JSON
170
+ * Includes file, errors array, and summary
171
+ */
172
+ function formatDiagnosticsJSON(
173
+ file: string,
174
+ diagnostics: Diagnostic[],
175
+ verbose: boolean
176
+ ): string {
177
+ // Build category lookup map from validation rules
178
+ const categoryMap = new Map<string, string>();
179
+ for (const rule of VALIDATION_RULES) {
180
+ categoryMap.set(rule.code, rule.category);
181
+ }
182
+
183
+ // Format each diagnostic
184
+ const errors = diagnostics.map((d) => {
185
+ const error: Record<string, unknown> = {
186
+ location: {
187
+ line: d.location.line,
188
+ column: d.location.column,
189
+ offset: d.location.offset,
190
+ },
191
+ severity: d.severity,
192
+ code: d.code,
193
+ message: d.message,
194
+ context: d.context,
195
+ };
196
+
197
+ // Add category if verbose mode
198
+ if (verbose) {
199
+ const category = categoryMap.get(d.code);
200
+ if (category) {
201
+ error['category'] = category;
202
+ }
203
+ }
204
+
205
+ // Add fix if present
206
+ if (d.fix) {
207
+ error['fix'] = {
208
+ description: d.fix.description,
209
+ applicable: d.fix.applicable,
210
+ range: {
211
+ start: {
212
+ line: d.fix.range.start.line,
213
+ column: d.fix.range.start.column,
214
+ offset: d.fix.range.start.offset,
215
+ },
216
+ end: {
217
+ line: d.fix.range.end.line,
218
+ column: d.fix.range.end.column,
219
+ offset: d.fix.range.end.offset,
220
+ },
221
+ },
222
+ replacement: d.fix.replacement,
223
+ };
224
+ }
225
+
226
+ return error;
227
+ });
228
+
229
+ // Count diagnostics by severity
230
+ const summary = {
231
+ total: diagnostics.length,
232
+ errors: diagnostics.filter((d) => d.severity === 'error').length,
233
+ warnings: diagnostics.filter((d) => d.severity === 'warning').length,
234
+ info: diagnostics.filter((d) => d.severity === 'info').length,
235
+ };
236
+
237
+ const output = {
238
+ file,
239
+ errors,
240
+ summary,
241
+ };
242
+
243
+ return JSON.stringify(output, null, 2);
244
+ }
245
+
246
+ // ============================================================
247
+ // MAIN ENTRY POINT
248
+ // ============================================================
249
+
250
+ /**
251
+ * Main entry point for rill-check CLI.
252
+ * Orchestrates argument parsing, file reading, validation, fixing, and output.
253
+ */
254
+ async function main(): Promise<void> {
255
+ try {
256
+ // Parse command-line arguments
257
+ const args = parseCheckArgs(process.argv.slice(2));
258
+
259
+ // Handle help mode
260
+ if (args.mode === 'help') {
261
+ console.log(`rill-check - Validate Rill scripts
262
+
263
+ Usage: rill-check [options] <file>
264
+
265
+ Options:
266
+ --fix Apply automatic fixes
267
+ --format <fmt> Output format: text (default) or json
268
+ --verbose Include category and documentation references
269
+ -h, --help Show this help message
270
+ -v, --version Show version number`);
271
+ process.exit(0);
272
+ }
273
+
274
+ // Handle version mode
275
+ if (args.mode === 'version') {
276
+ console.log(VERSION);
277
+ process.exit(0);
278
+ }
279
+
280
+ // At this point, args.mode must be 'check'
281
+ // TypeScript needs explicit assertion after early returns
282
+ if (args.mode !== 'check') {
283
+ throw new Error('Unexpected mode');
284
+ }
285
+
286
+ // Load configuration from cwd (null if not present)
287
+ const config = loadConfig(process.cwd()) ?? createDefaultConfig();
288
+
289
+ // Read source file
290
+ let source: string;
291
+ try {
292
+ const fs = await import('node:fs');
293
+
294
+ // Check if file exists
295
+ if (!fs.existsSync(args.file)) {
296
+ console.error(`Error [RILL-C001]: File not found: ${args.file}`);
297
+ process.exit(2);
298
+ }
299
+
300
+ // Check if path is a directory
301
+ const stats = fs.statSync(args.file);
302
+ if (stats.isDirectory()) {
303
+ console.error(`Error [RILL-C002]: Path is a directory: ${args.file}`);
304
+ process.exit(2);
305
+ }
306
+
307
+ // Read file contents
308
+ source = fs.readFileSync(args.file, 'utf-8');
309
+ } catch (err) {
310
+ // Handle read errors (permissions, etc.)
311
+ if (
312
+ err instanceof Error &&
313
+ 'code' in err &&
314
+ typeof (err as { code?: string }).code === 'string'
315
+ ) {
316
+ const code = (err as { code: string }).code;
317
+ if (code === 'ENOENT') {
318
+ console.error(`Error [RILL-C001]: File not found: ${args.file}`);
319
+ } else if (code === 'EISDIR') {
320
+ console.error(`Error [RILL-C002]: Path is a directory: ${args.file}`);
321
+ } else {
322
+ console.error(`Error [RILL-C002]: Cannot read file: ${args.file}`);
323
+ }
324
+ } else {
325
+ console.error(`Error [RILL-C002]: Cannot read file: ${args.file}`);
326
+ }
327
+ process.exit(2);
328
+ }
329
+
330
+ // Parse AST with recovery to collect all errors
331
+ const parseResult = parseWithRecovery(source);
332
+
333
+ // Convert parse errors to diagnostics
334
+ // Only report the first parse error; subsequent errors are usually cascade noise
335
+ const parseDiagnostics: Diagnostic[] = parseResult.errors
336
+ .slice(0, 1)
337
+ .map((err) => {
338
+ const location = err.location ?? { line: 1, column: 1, offset: 0 };
339
+ const lineContent = source.split('\n')[location.line - 1]?.trim() ?? '';
340
+ return {
341
+ code: 'parse-error',
342
+ severity: 'error' as const,
343
+ message: err.message.replace(/ at \d+:\d+$/, ''),
344
+ location,
345
+ context: lineContent,
346
+ fix: null,
347
+ };
348
+ });
349
+
350
+ // If there are parse errors, report them and exit
351
+ if (parseDiagnostics.length > 0) {
352
+ const output = formatDiagnostics(
353
+ args.file,
354
+ parseDiagnostics,
355
+ args.format,
356
+ args.verbose
357
+ );
358
+ console.log(output);
359
+
360
+ // If --fix was requested, report that fixes cannot be applied
361
+ if (args.fix) {
362
+ console.error('Cannot apply fixes: file has parse errors');
363
+ }
364
+
365
+ process.exit(3);
366
+ }
367
+
368
+ const ast = parseResult.ast;
369
+
370
+ // Run validation
371
+ const diagnostics = validateScript(ast, source, config);
372
+
373
+ // Apply fixes if requested
374
+ if (args.fix && diagnostics.length > 0) {
375
+ const result = applyFixes(source, diagnostics, {
376
+ source,
377
+ ast,
378
+ config,
379
+ diagnostics: [],
380
+ variables: new Map(),
381
+ assertedHostCalls: new Set(),
382
+ variableScopes: new Map(),
383
+ scopeStack: [],
384
+ });
385
+
386
+ // Write fixed source back to file
387
+ if (result.applied > 0) {
388
+ const fs = await import('node:fs');
389
+ fs.writeFileSync(args.file, result.modified, 'utf-8');
390
+ }
391
+
392
+ // Report fix results to stderr
393
+ if (result.applied > 0 || result.skipped > 0) {
394
+ if (result.applied > 0) {
395
+ console.error(
396
+ `Applied ${result.applied} fix${result.applied === 1 ? '' : 'es'}`
397
+ );
398
+ }
399
+ if (result.skipped > 0) {
400
+ console.error(
401
+ `Skipped ${result.skipped} fix${result.skipped === 1 ? '' : 'es'}`
402
+ );
403
+ }
404
+ }
405
+ }
406
+
407
+ // Format and output diagnostics
408
+ if (diagnostics.length === 0) {
409
+ // No errors - success
410
+ if (args.format === 'json') {
411
+ console.log(
412
+ JSON.stringify(
413
+ {
414
+ file: args.file,
415
+ errors: [],
416
+ summary: { total: 0, errors: 0, warnings: 0, info: 0 },
417
+ },
418
+ null,
419
+ 2
420
+ )
421
+ );
422
+ } else {
423
+ console.log('No issues found');
424
+ }
425
+ process.exit(0);
426
+ } else {
427
+ // Output diagnostics
428
+ const output = formatDiagnostics(
429
+ args.file,
430
+ diagnostics,
431
+ args.format,
432
+ args.verbose
433
+ );
434
+ console.log(output);
435
+ process.exit(1);
436
+ }
437
+ } catch (err) {
438
+ // Handle unexpected errors
439
+ if (err instanceof Error) {
440
+ console.error(`Error: ${err.message}`);
441
+ } else {
442
+ console.error(`Error: ${String(err)}`);
443
+ }
444
+ process.exit(1);
445
+ }
446
+ }
447
+
448
+ // Only run main if this is the entry point (not imported)
449
+ const shouldRunMain =
450
+ process.env['NODE_ENV'] !== 'test' &&
451
+ !process.env['VITEST'] &&
452
+ !process.env['VITEST_WORKER_ID'];
453
+
454
+ if (shouldRunMain) {
455
+ main();
456
+ }