@grafema/cli 0.2.5-beta → 0.2.7

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 (105) hide show
  1. package/README.md +12 -0
  2. package/dist/cli.js +6 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analyze.d.ts +3 -10
  5. package/dist/commands/analyze.d.ts.map +1 -1
  6. package/dist/commands/analyze.js +5 -347
  7. package/dist/commands/analyze.js.map +1 -1
  8. package/dist/commands/analyzeAction.d.ts +28 -0
  9. package/dist/commands/analyzeAction.d.ts.map +1 -0
  10. package/dist/commands/analyzeAction.js +243 -0
  11. package/dist/commands/analyzeAction.js.map +1 -0
  12. package/dist/commands/check.js +2 -2
  13. package/dist/commands/check.js.map +1 -1
  14. package/dist/commands/context.d.ts +16 -0
  15. package/dist/commands/context.d.ts.map +1 -0
  16. package/dist/commands/context.js +238 -0
  17. package/dist/commands/context.js.map +1 -0
  18. package/dist/commands/doctor/checks.js +1 -1
  19. package/dist/commands/doctor/checks.js.map +1 -1
  20. package/dist/commands/explain.d.ts.map +1 -1
  21. package/dist/commands/explain.js +4 -3
  22. package/dist/commands/explain.js.map +1 -1
  23. package/dist/commands/file.d.ts +15 -0
  24. package/dist/commands/file.d.ts.map +1 -0
  25. package/dist/commands/file.js +144 -0
  26. package/dist/commands/file.js.map +1 -0
  27. package/dist/commands/impact.d.ts.map +1 -1
  28. package/dist/commands/impact.js +2 -3
  29. package/dist/commands/impact.js.map +1 -1
  30. package/dist/commands/init.d.ts.map +1 -1
  31. package/dist/commands/init.js +13 -1
  32. package/dist/commands/init.js.map +1 -1
  33. package/dist/commands/ls.d.ts.map +1 -1
  34. package/dist/commands/ls.js +3 -2
  35. package/dist/commands/ls.js.map +1 -1
  36. package/dist/commands/query.d.ts +8 -0
  37. package/dist/commands/query.d.ts.map +1 -1
  38. package/dist/commands/query.js +158 -51
  39. package/dist/commands/query.js.map +1 -1
  40. package/dist/commands/schema.d.ts.map +1 -1
  41. package/dist/commands/schema.js +3 -2
  42. package/dist/commands/schema.js.map +1 -1
  43. package/dist/commands/server.d.ts.map +1 -1
  44. package/dist/commands/server.js +8 -59
  45. package/dist/commands/server.js.map +1 -1
  46. package/dist/commands/setup-skill.d.ts +17 -0
  47. package/dist/commands/setup-skill.d.ts.map +1 -0
  48. package/dist/commands/setup-skill.js +131 -0
  49. package/dist/commands/setup-skill.js.map +1 -0
  50. package/dist/commands/trace.d.ts.map +1 -1
  51. package/dist/commands/trace.js +20 -10
  52. package/dist/commands/trace.js.map +1 -1
  53. package/dist/plugins/builtinPlugins.d.ts +10 -0
  54. package/dist/plugins/builtinPlugins.d.ts.map +1 -0
  55. package/dist/plugins/builtinPlugins.js +68 -0
  56. package/dist/plugins/builtinPlugins.js.map +1 -0
  57. package/dist/plugins/pluginLoader.d.ts +16 -0
  58. package/dist/plugins/pluginLoader.d.ts.map +1 -0
  59. package/dist/plugins/pluginLoader.js +101 -0
  60. package/dist/plugins/pluginLoader.js.map +1 -0
  61. package/dist/plugins/pluginResolver.js +38 -0
  62. package/dist/utils/codePreview.d.ts +1 -0
  63. package/dist/utils/codePreview.d.ts.map +1 -1
  64. package/dist/utils/codePreview.js +5 -3
  65. package/dist/utils/codePreview.js.map +1 -1
  66. package/dist/utils/formatNode.d.ts +1 -1
  67. package/dist/utils/formatNode.d.ts.map +1 -1
  68. package/dist/utils/formatNode.js +2 -2
  69. package/dist/utils/formatNode.js.map +1 -1
  70. package/dist/utils/pathUtils.d.ts +2 -0
  71. package/dist/utils/pathUtils.d.ts.map +1 -0
  72. package/dist/utils/pathUtils.js +9 -0
  73. package/dist/utils/pathUtils.js.map +1 -0
  74. package/dist/utils/progressRenderer.d.ts +4 -0
  75. package/dist/utils/progressRenderer.d.ts.map +1 -1
  76. package/dist/utils/progressRenderer.js +23 -4
  77. package/dist/utils/progressRenderer.js.map +1 -1
  78. package/package.json +7 -9
  79. package/skills/grafema-codebase-analysis/SKILL.md +295 -0
  80. package/skills/grafema-codebase-analysis/references/node-edge-types.md +123 -0
  81. package/skills/grafema-codebase-analysis/references/query-patterns.md +205 -0
  82. package/src/cli.ts +8 -2
  83. package/src/commands/analyze.ts +5 -435
  84. package/src/commands/analyzeAction.ts +284 -0
  85. package/src/commands/check.ts +2 -2
  86. package/src/commands/context.ts +309 -0
  87. package/src/commands/doctor/checks.ts +1 -1
  88. package/src/commands/explain.ts +4 -3
  89. package/src/commands/explore.tsx +7 -5
  90. package/src/commands/file.ts +179 -0
  91. package/src/commands/impact.ts +2 -3
  92. package/src/commands/init.ts +13 -1
  93. package/src/commands/ls.ts +3 -2
  94. package/src/commands/query.ts +167 -52
  95. package/src/commands/schema.ts +3 -2
  96. package/src/commands/server.ts +8 -64
  97. package/src/commands/setup-skill.ts +162 -0
  98. package/src/commands/trace.ts +18 -9
  99. package/src/plugins/builtinPlugins.ts +108 -0
  100. package/src/plugins/pluginLoader.ts +123 -0
  101. package/src/plugins/pluginResolver.js +38 -0
  102. package/src/utils/codePreview.ts +7 -3
  103. package/src/utils/formatNode.ts +3 -3
  104. package/src/utils/pathUtils.ts +9 -0
  105. package/src/utils/progressRenderer.ts +25 -4
@@ -1,214 +1,12 @@
1
1
  /**
2
- * Analyze command - Run project analysis via Orchestrator
2
+ * Analyze command Run project analysis via Orchestrator.
3
+ *
4
+ * Command definition only. Execution logic is in analyzeAction.ts.
3
5
  */
4
6
 
5
7
  import { Command } from 'commander';
6
- import { resolve, join } from 'path';
7
- import { existsSync, mkdirSync, readdirSync } from 'fs';
8
- import { pathToFileURL } from 'url';
9
- import type {
10
- Plugin} from '@grafema/core';
11
- import {
12
- Orchestrator,
13
- RFDBServerBackend,
14
- DiagnosticReporter,
15
- DiagnosticWriter,
16
- createLogger,
17
- loadConfig,
18
- StrictModeFailure,
19
- type GrafemaConfig,
20
- // Discovery
21
- SimpleProjectDiscovery,
22
- MonorepoServiceDiscovery,
23
- WorkspaceDiscovery,
24
- // Indexing
25
- JSModuleIndexer,
26
- RustModuleIndexer,
27
- // Analysis
28
- JSASTAnalyzer,
29
- ExpressRouteAnalyzer,
30
- ExpressResponseAnalyzer,
31
- SocketIOAnalyzer,
32
- DatabaseAnalyzer,
33
- FetchAnalyzer,
34
- ServiceLayerAnalyzer,
35
- ReactAnalyzer,
36
- RustAnalyzer,
37
- // Enrichment
38
- MethodCallResolver,
39
- ArgumentParameterLinker,
40
- AliasTracker,
41
- ValueDomainAnalyzer,
42
- MountPointResolver,
43
- ExpressHandlerLinker,
44
- PrefixEvaluator,
45
- InstanceOfResolver,
46
- ImportExportLinker,
47
- FunctionCallResolver,
48
- HTTPConnectionEnricher,
49
- RustFFIEnricher,
50
- RejectionPropagationEnricher,
51
- // Validation
52
- CallResolverValidator,
53
- EvalBanValidator,
54
- SQLInjectionValidator,
55
- ShadowingDetector,
56
- GraphConnectivityValidator,
57
- DataFlowValidator,
58
- TypeScriptDeadCodeValidator,
59
- BrokenImportValidator,
60
- } from '@grafema/core';
61
- import type { LogLevel, GraphBackend } from '@grafema/types';
62
- import { ProgressRenderer } from '../utils/progressRenderer.js';
63
-
64
- export interface NodeEdgeCountBackend {
65
- nodeCount: () => Promise<number>;
66
- edgeCount: () => Promise<number>;
67
- }
68
-
69
- export async function fetchNodeEdgeCounts(backend: NodeEdgeCountBackend): Promise<{ nodeCount: number; edgeCount: number }> {
70
- const [nodeCount, edgeCount] = await Promise.all([backend.nodeCount(), backend.edgeCount()]);
71
- return { nodeCount, edgeCount };
72
- }
73
-
74
- export function exitWithCode(code: number, exitFn: (code: number) => void = process.exit): void {
75
- exitFn(code);
76
- }
77
-
78
- const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
79
- // Discovery
80
- SimpleProjectDiscovery: () => new SimpleProjectDiscovery() as Plugin,
81
- MonorepoServiceDiscovery: () => new MonorepoServiceDiscovery() as Plugin,
82
- WorkspaceDiscovery: () => new WorkspaceDiscovery() as Plugin,
83
- // Indexing
84
- JSModuleIndexer: () => new JSModuleIndexer() as Plugin,
85
- RustModuleIndexer: () => new RustModuleIndexer() as Plugin,
86
- // Analysis
87
- JSASTAnalyzer: () => new JSASTAnalyzer() as Plugin,
88
- ExpressRouteAnalyzer: () => new ExpressRouteAnalyzer() as Plugin,
89
- ExpressResponseAnalyzer: () => new ExpressResponseAnalyzer() as Plugin,
90
- SocketIOAnalyzer: () => new SocketIOAnalyzer() as Plugin,
91
- DatabaseAnalyzer: () => new DatabaseAnalyzer() as Plugin,
92
- FetchAnalyzer: () => new FetchAnalyzer() as Plugin,
93
- ServiceLayerAnalyzer: () => new ServiceLayerAnalyzer() as Plugin,
94
- ReactAnalyzer: () => new ReactAnalyzer() as Plugin,
95
- RustAnalyzer: () => new RustAnalyzer() as Plugin,
96
- // Enrichment
97
- MethodCallResolver: () => new MethodCallResolver() as Plugin,
98
- ArgumentParameterLinker: () => new ArgumentParameterLinker() as Plugin,
99
- AliasTracker: () => new AliasTracker() as Plugin,
100
- ValueDomainAnalyzer: () => new ValueDomainAnalyzer() as Plugin,
101
- MountPointResolver: () => new MountPointResolver() as Plugin,
102
- ExpressHandlerLinker: () => new ExpressHandlerLinker() as Plugin,
103
- PrefixEvaluator: () => new PrefixEvaluator() as Plugin,
104
- InstanceOfResolver: () => new InstanceOfResolver() as Plugin,
105
- ImportExportLinker: () => new ImportExportLinker() as Plugin,
106
- FunctionCallResolver: () => new FunctionCallResolver() as Plugin,
107
- HTTPConnectionEnricher: () => new HTTPConnectionEnricher() as Plugin,
108
- RustFFIEnricher: () => new RustFFIEnricher() as Plugin,
109
- RejectionPropagationEnricher: () => new RejectionPropagationEnricher() as Plugin,
110
- // Validation
111
- CallResolverValidator: () => new CallResolverValidator() as Plugin,
112
- EvalBanValidator: () => new EvalBanValidator() as Plugin,
113
- SQLInjectionValidator: () => new SQLInjectionValidator() as Plugin,
114
- ShadowingDetector: () => new ShadowingDetector() as Plugin,
115
- GraphConnectivityValidator: () => new GraphConnectivityValidator() as Plugin,
116
- DataFlowValidator: () => new DataFlowValidator() as Plugin,
117
- TypeScriptDeadCodeValidator: () => new TypeScriptDeadCodeValidator() as Plugin,
118
- BrokenImportValidator: () => new BrokenImportValidator() as Plugin,
119
- };
120
-
121
- /**
122
- * Load custom plugins from .grafema/plugins/ directory
123
- */
124
- async function loadCustomPlugins(
125
- projectPath: string,
126
- log: (msg: string) => void
127
- ): Promise<Record<string, () => Plugin>> {
128
- const pluginsDir = join(projectPath, '.grafema', 'plugins');
129
- if (!existsSync(pluginsDir)) {
130
- return {};
131
- }
132
-
133
- const customPlugins: Record<string, () => Plugin> = {};
134
-
135
- try {
136
- const files = readdirSync(pluginsDir).filter(
137
- (f) => f.endsWith('.js') || f.endsWith('.mjs') || f.endsWith('.cjs')
138
- );
139
-
140
- for (const file of files) {
141
- try {
142
- const pluginPath = join(pluginsDir, file);
143
- const pluginUrl = pathToFileURL(pluginPath).href;
144
- const module = await import(pluginUrl);
145
-
146
- const PluginClass = module.default || module[file.replace(/\.[cm]?js$/, '')];
147
- if (PluginClass && typeof PluginClass === 'function') {
148
- const pluginName = PluginClass.name || file.replace(/\.[cm]?js$/, '');
149
- customPlugins[pluginName] = () => {
150
- const instance = new PluginClass() as Plugin;
151
- instance.config.sourceFile = pluginPath;
152
- return instance;
153
- };
154
- log(`Loaded custom plugin: ${pluginName}`);
155
- }
156
- } catch (err) {
157
- const message = err instanceof Error ? err.message : String(err);
158
- console.warn(`Failed to load plugin ${file}: ${message}`);
159
- }
160
- }
161
- } catch (err) {
162
- const message = err instanceof Error ? err.message : String(err);
163
- console.warn(`Error loading custom plugins: ${message}`);
164
- }
165
-
166
- return customPlugins;
167
- }
8
+ import { analyzeAction } from './analyzeAction.js';
168
9
 
169
- function createPlugins(
170
- config: GrafemaConfig['plugins'],
171
- customPlugins: Record<string, () => Plugin> = {},
172
- verbose: boolean = false
173
- ): Plugin[] {
174
- const plugins: Plugin[] = [];
175
- const phases: (keyof GrafemaConfig['plugins'])[] = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
176
-
177
- for (const phase of phases) {
178
- const names = config[phase] || [];
179
- for (const name of names) {
180
- // Check built-in first, then custom
181
- const factory = BUILTIN_PLUGINS[name] || customPlugins[name];
182
- if (factory) {
183
- plugins.push(factory());
184
- } else if (verbose) {
185
- // Only show plugin warning in verbose mode
186
- console.warn(`Plugin not found: ${name} (skipping). Check .grafema/config.yaml or add to .grafema/plugins/`);
187
- }
188
- }
189
- }
190
-
191
- return plugins;
192
- }
193
-
194
- /**
195
- * Determine log level from CLI options.
196
- * Priority: --log-level > --quiet > --verbose > default ('silent')
197
- *
198
- * By default, logs are silent to allow clean progress UI.
199
- * Use --verbose to see detailed logs (disables interactive progress).
200
- */
201
- function getLogLevel(options: { quiet?: boolean; verbose?: boolean; logLevel?: string }): LogLevel {
202
- if (options.logLevel) {
203
- const validLevels: LogLevel[] = ['silent', 'errors', 'warnings', 'info', 'debug'];
204
- if (validLevels.includes(options.logLevel as LogLevel)) {
205
- return options.logLevel as LogLevel;
206
- }
207
- }
208
- if (options.quiet) return 'silent';
209
- if (options.verbose) return 'info'; // --verbose shows logs instead of progress UI
210
- return 'silent'; // Default: silent logs, clean progress UI
211
- }
212
10
 
213
11
  export const analyzeCommand = new Command('analyze')
214
12
  .description('Run project analysis')
@@ -237,232 +35,4 @@ Examples:
237
35
 
238
36
  Note: Start the server first with: grafema server start
239
37
  `)
240
- .action(async (path: string, options: { service?: string; entrypoint?: string; clear?: boolean; quiet?: boolean; verbose?: boolean; debug?: boolean; logLevel?: string; logFile?: string; strict?: boolean; autoStart?: boolean }) => {
241
- const projectPath = resolve(path);
242
- const grafemaDir = join(projectPath, '.grafema');
243
- const dbPath = join(grafemaDir, 'graph.rfdb');
244
-
245
- if (!existsSync(grafemaDir)) {
246
- mkdirSync(grafemaDir, { recursive: true });
247
- }
248
-
249
- // Two log levels for CLI output:
250
- // - info: important results (shows unless --quiet)
251
- // - debug: verbose details (shows only with --verbose)
252
- const info = options.quiet ? () => {} : console.log;
253
- const debug = options.verbose ? console.log : () => {};
254
-
255
- // Create logger based on CLI flags
256
- const logLevel = getLogLevel(options);
257
- const logFile = options.logFile ? resolve(options.logFile) : undefined;
258
- const logger = createLogger(logLevel, logFile ? { logFile } : undefined);
259
-
260
- if (logFile) {
261
- debug(`Log file: ${logFile}`);
262
- }
263
- debug(`Analyzing project: ${projectPath}`);
264
-
265
- // Connect to RFDB server
266
- // Default: require explicit `grafema server start`
267
- // Use --auto-start for CI or backwards compatibility
268
- // In normal mode (not verbose), suppress backend logs for clean progress UI
269
- const backend = new RFDBServerBackend({
270
- dbPath,
271
- autoStart: options.autoStart ?? false,
272
- silent: !options.verbose // Silent in normal mode (show progress), verbose shows logs
273
- });
274
-
275
- try {
276
- await backend.connect();
277
- } catch (err) {
278
- if (!options.autoStart && err instanceof Error && err.message.includes('not running')) {
279
- console.error('');
280
- console.error('RFDB server is not running.');
281
- console.error('');
282
- console.error('Start the server first:');
283
- console.error(' grafema server start');
284
- console.error('');
285
- console.error('Or use --auto-start flag:');
286
- console.error(' grafema analyze --auto-start');
287
- console.error('');
288
- process.exit(1);
289
- }
290
- throw err;
291
- }
292
-
293
- if (options.clear) {
294
- debug('Clearing existing database...');
295
- await backend.clear();
296
- }
297
-
298
- const config = loadConfig(projectPath, logger);
299
-
300
- // Extract services from config (REG-174)
301
- if (config.services.length > 0) {
302
- debug(`Loaded ${config.services.length} service(s) from config`);
303
- for (const svc of config.services) {
304
- const entry = svc.entryPoint ? ` (entry: ${svc.entryPoint})` : '';
305
- debug(` - ${svc.name}: ${svc.path}${entry}`);
306
- }
307
- }
308
-
309
- // Load custom plugins from .grafema/plugins/
310
- const customPlugins = await loadCustomPlugins(projectPath, debug);
311
- const plugins = createPlugins(config.plugins, customPlugins, options.verbose);
312
-
313
- debug(`Loaded ${plugins.length} plugins`);
314
-
315
- // Resolve strict mode: CLI flag overrides config
316
- const strictMode = options.strict ?? config.strict ?? false;
317
- if (strictMode) {
318
- debug('Strict mode enabled - analysis will fail on unresolved references');
319
- }
320
-
321
- const startTime = Date.now();
322
-
323
- // Create progress renderer for CLI output
324
- // In quiet mode, use a no-op renderer (skip rendering)
325
- // In verbose mode, use non-interactive (newlines per update)
326
- // In normal mode, use interactive (spinner with line overwrite)
327
- const renderer = options.quiet
328
- ? null
329
- : new ProgressRenderer({
330
- isInteractive: !options.verbose && process.stdout.isTTY,
331
- });
332
-
333
- // Poll graph stats periodically to show node/edge counts in progress
334
- let statsInterval: NodeJS.Timeout | null = null;
335
- if (renderer && !options.quiet) {
336
- statsInterval = setInterval(async () => {
337
- try {
338
- const stats = await fetchNodeEdgeCounts(backend);
339
- renderer.setStats(stats.nodeCount, stats.edgeCount);
340
- } catch {
341
- // Ignore stats errors during analysis
342
- }
343
- }, 500); // Poll every 500ms
344
- statsInterval.unref?.();
345
- }
346
-
347
- const orchestrator = new Orchestrator({
348
- graph: backend as unknown as GraphBackend,
349
- plugins,
350
- serviceFilter: options.service || null,
351
- entrypoint: options.entrypoint,
352
- forceAnalysis: options.clear || false,
353
- logger,
354
- services: config.services.length > 0 ? config.services : undefined, // Pass config services (REG-174)
355
- strictMode, // REG-330: Pass strict mode flag
356
- onProgress: (progress) => {
357
- renderer?.update(progress);
358
- },
359
- });
360
-
361
- let exitCode = 0;
362
-
363
- try {
364
- await orchestrator.run(projectPath);
365
- await backend.flush();
366
-
367
- const elapsedSeconds = (Date.now() - startTime) / 1000;
368
- const stats = await fetchNodeEdgeCounts(backend);
369
-
370
- // Clear progress line in interactive mode, then show results
371
- if (renderer && process.stdout.isTTY) {
372
- process.stdout.write('\r\x1b[K'); // Clear line
373
- }
374
- info('');
375
- info(renderer ? renderer.finish(elapsedSeconds) : `Analysis complete in ${elapsedSeconds.toFixed(2)}s`);
376
- info(` Nodes: ${stats.nodeCount}`);
377
- info(` Edges: ${stats.edgeCount}`);
378
-
379
- // Get diagnostics and report summary
380
- const diagnostics = orchestrator.getDiagnostics();
381
- const reporter = new DiagnosticReporter(diagnostics);
382
-
383
- // Print summary if there are any issues
384
- if (diagnostics.count() > 0) {
385
- info('');
386
- info(reporter.categorizedSummary());
387
-
388
- // In verbose mode, print full report
389
- if (options.verbose) {
390
- debug('');
391
- debug(reporter.report({ format: 'text', includeSummary: false }));
392
- }
393
- }
394
-
395
- // Always write diagnostics.log (required for `grafema check` command)
396
- const writer = new DiagnosticWriter();
397
- await writer.write(diagnostics, grafemaDir);
398
- if (options.debug) {
399
- debug(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
400
- }
401
-
402
- // Determine exit code based on severity
403
- if (diagnostics.hasFatal()) {
404
- exitCode = 1;
405
- } else if (diagnostics.hasErrors()) {
406
- exitCode = 2; // Completed with errors
407
- } else {
408
- exitCode = 0; // Success (maybe warnings)
409
- }
410
- } catch (e) {
411
- const diagnostics = orchestrator.getDiagnostics();
412
- const reporter = new DiagnosticReporter(diagnostics);
413
-
414
- // Clear progress line in interactive mode
415
- if (renderer && process.stdout.isTTY) {
416
- process.stdout.write('\r\x1b[K');
417
- }
418
-
419
- // Check if this is a strict mode failure (REG-332: structured output)
420
- if (e instanceof StrictModeFailure) {
421
- // Format ONLY from diagnostics, not from error.message
422
- console.error('');
423
- console.error(`✗ Strict mode: ${e.count} unresolved reference(s) found during ENRICHMENT.`);
424
- console.error('');
425
- console.error(reporter.formatStrict(e.diagnostics, {
426
- verbose: options.verbose,
427
- suppressedCount: e.suppressedCount, // REG-332
428
- }));
429
- console.error('');
430
- console.error('Run without --strict for graceful degradation, or fix the underlying issues.');
431
- } else {
432
- // Generic error handling (non-strict)
433
- const error = e instanceof Error ? e : new Error(String(e));
434
- console.error('');
435
- console.error(`✗ Analysis failed: ${error.message}`);
436
- console.error('');
437
- console.error('→ Run with --debug for detailed diagnostics');
438
-
439
- if (diagnostics.count() > 0) {
440
- console.error('');
441
- console.error(reporter.report({ format: 'text', includeSummary: true }));
442
- }
443
- }
444
-
445
- // Write diagnostics.log in debug mode even on failure
446
- if (options.debug) {
447
- const writer = new DiagnosticWriter();
448
- await writer.write(diagnostics, grafemaDir);
449
- console.error(`Diagnostics written to ${writer.getLogPath(grafemaDir)}`);
450
- }
451
-
452
- exitCode = 1;
453
- } finally {
454
- // Stop stats polling
455
- if (statsInterval) {
456
- clearInterval(statsInterval);
457
- statsInterval = null;
458
- }
459
-
460
- if (backend.connected) {
461
- await backend.close();
462
- }
463
-
464
- // Exit with appropriate code
465
- // 0 = success, 1 = fatal, 2 = errors
466
- exitWithCode(exitCode);
467
- }
468
- });
38
+ .action(analyzeAction);