@grafema/cli 0.1.1-alpha → 0.2.1-beta

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 (79) hide show
  1. package/dist/cli.js +10 -0
  2. package/dist/commands/analyze.d.ts.map +1 -1
  3. package/dist/commands/analyze.js +69 -11
  4. package/dist/commands/check.d.ts +6 -0
  5. package/dist/commands/check.d.ts.map +1 -1
  6. package/dist/commands/check.js +177 -1
  7. package/dist/commands/coverage.d.ts.map +1 -1
  8. package/dist/commands/coverage.js +7 -0
  9. package/dist/commands/doctor/checks.d.ts +55 -0
  10. package/dist/commands/doctor/checks.d.ts.map +1 -0
  11. package/dist/commands/doctor/checks.js +534 -0
  12. package/dist/commands/doctor/output.d.ts +20 -0
  13. package/dist/commands/doctor/output.d.ts.map +1 -0
  14. package/dist/commands/doctor/output.js +94 -0
  15. package/dist/commands/doctor/types.d.ts +42 -0
  16. package/dist/commands/doctor/types.d.ts.map +1 -0
  17. package/dist/commands/doctor/types.js +4 -0
  18. package/dist/commands/doctor.d.ts +17 -0
  19. package/dist/commands/doctor.d.ts.map +1 -0
  20. package/dist/commands/doctor.js +80 -0
  21. package/dist/commands/explain.d.ts +16 -0
  22. package/dist/commands/explain.d.ts.map +1 -0
  23. package/dist/commands/explain.js +145 -0
  24. package/dist/commands/explore.d.ts +7 -1
  25. package/dist/commands/explore.d.ts.map +1 -1
  26. package/dist/commands/explore.js +204 -85
  27. package/dist/commands/get.d.ts.map +1 -1
  28. package/dist/commands/get.js +16 -4
  29. package/dist/commands/impact.d.ts.map +1 -1
  30. package/dist/commands/impact.js +48 -50
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +93 -15
  33. package/dist/commands/ls.d.ts +14 -0
  34. package/dist/commands/ls.d.ts.map +1 -0
  35. package/dist/commands/ls.js +132 -0
  36. package/dist/commands/overview.d.ts.map +1 -1
  37. package/dist/commands/overview.js +15 -2
  38. package/dist/commands/query.d.ts +98 -0
  39. package/dist/commands/query.d.ts.map +1 -1
  40. package/dist/commands/query.js +549 -136
  41. package/dist/commands/schema.d.ts +13 -0
  42. package/dist/commands/schema.d.ts.map +1 -0
  43. package/dist/commands/schema.js +279 -0
  44. package/dist/commands/server.d.ts.map +1 -1
  45. package/dist/commands/server.js +13 -6
  46. package/dist/commands/stats.d.ts.map +1 -1
  47. package/dist/commands/stats.js +7 -0
  48. package/dist/commands/trace.d.ts +73 -0
  49. package/dist/commands/trace.d.ts.map +1 -1
  50. package/dist/commands/trace.js +500 -5
  51. package/dist/commands/types.d.ts +12 -0
  52. package/dist/commands/types.d.ts.map +1 -0
  53. package/dist/commands/types.js +79 -0
  54. package/dist/utils/formatNode.d.ts +13 -0
  55. package/dist/utils/formatNode.d.ts.map +1 -1
  56. package/dist/utils/formatNode.js +35 -2
  57. package/package.json +3 -3
  58. package/src/cli.ts +10 -0
  59. package/src/commands/analyze.ts +84 -9
  60. package/src/commands/check.ts +201 -0
  61. package/src/commands/coverage.ts +7 -0
  62. package/src/commands/doctor/checks.ts +612 -0
  63. package/src/commands/doctor/output.ts +115 -0
  64. package/src/commands/doctor/types.ts +45 -0
  65. package/src/commands/doctor.ts +106 -0
  66. package/src/commands/explain.ts +173 -0
  67. package/src/commands/explore.tsx +247 -97
  68. package/src/commands/get.ts +20 -6
  69. package/src/commands/impact.ts +55 -61
  70. package/src/commands/init.ts +101 -14
  71. package/src/commands/ls.ts +166 -0
  72. package/src/commands/overview.ts +15 -2
  73. package/src/commands/query.ts +643 -149
  74. package/src/commands/schema.ts +345 -0
  75. package/src/commands/server.ts +13 -6
  76. package/src/commands/stats.ts +7 -0
  77. package/src/commands/trace.ts +647 -6
  78. package/src/commands/types.ts +94 -0
  79. package/src/utils/formatNode.ts +42 -2
@@ -5,10 +5,15 @@
5
5
  import { Command } from 'commander';
6
6
  import { resolve, join } from 'path';
7
7
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
8
+ import { spawn } from 'child_process';
9
+ import { createInterface } from 'readline';
10
+ import { fileURLToPath } from 'url';
8
11
  import { exitWithError } from '../utils/errorFormatter.js';
9
12
  import { stringify as stringifyYAML } from 'yaml';
10
13
  import { DEFAULT_CONFIG } from '@grafema/core';
11
14
 
15
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
16
+
12
17
  /**
13
18
  * Generate config.yaml content with commented future features.
14
19
  * Only includes implemented features (plugins).
@@ -30,26 +35,90 @@ function generateConfigYAML(): string {
30
35
  # Documentation: https://github.com/grafema/grafema#configuration
31
36
 
32
37
  ${yaml}
33
- # Future: File discovery patterns (not yet implemented)
34
- # Grafema currently uses entrypoint-based discovery (follows imports from package.json main field)
35
- # Glob-based include/exclude patterns will be added in a future release
38
+ # File filtering patterns (optional)
39
+ # By default, Grafema follows imports from package.json entry points.
40
+ # Use these patterns to control which files are analyzed:
36
41
  #
37
- # include:
42
+ # include: # Only analyze files matching these patterns
38
43
  # - "src/**/*.{ts,js,tsx,jsx}"
39
- # exclude:
44
+ #
45
+ # exclude: # Skip files matching these patterns (takes precedence over include)
40
46
  # - "**/*.test.ts"
41
- # - "node_modules/**"
47
+ # - "**/__tests__/**"
48
+ # - "**/node_modules/**"
42
49
  `;
43
50
  }
44
51
 
52
+ /**
53
+ * Ask user a yes/no question. Returns true for yes (default), false for no.
54
+ */
55
+ function askYesNo(question: string): Promise<boolean> {
56
+ const rl = createInterface({
57
+ input: process.stdin,
58
+ output: process.stdout,
59
+ });
60
+
61
+ return new Promise((resolve) => {
62
+ rl.question(question, (answer) => {
63
+ rl.close();
64
+ // Default yes (empty answer or 'y' or 'yes')
65
+ const normalized = answer.toLowerCase().trim();
66
+ resolve(normalized !== 'n' && normalized !== 'no');
67
+ });
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Run grafema analyze in the given project path.
73
+ * Returns the exit code of the analyze process.
74
+ */
75
+ function runAnalyze(projectPath: string): Promise<number> {
76
+ return new Promise((resolve) => {
77
+ const cliPath = join(__dirname, '..', 'cli.js');
78
+ const child = spawn('node', [cliPath, 'analyze', projectPath], {
79
+ stdio: 'inherit', // Pass through all I/O for user to see progress
80
+ });
81
+ child.on('close', (code) => resolve(code ?? 1));
82
+ child.on('error', () => resolve(1));
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Print next steps after init.
88
+ */
89
+ function printNextSteps(): void {
90
+ console.log('');
91
+ console.log('Next steps:');
92
+ console.log(' 1. Review config: code .grafema/config.yaml');
93
+ console.log(' 2. Build graph: grafema analyze');
94
+ console.log(' 3. Explore: grafema overview');
95
+ }
96
+
97
+ /**
98
+ * Check if running in interactive mode.
99
+ * Interactive if stdin is TTY and --yes flag not provided.
100
+ */
101
+ function isInteractive(options: InitOptions): boolean {
102
+ return options.yes !== true && process.stdin.isTTY === true;
103
+ }
104
+
45
105
  interface InitOptions {
46
106
  force?: boolean;
107
+ yes?: boolean;
47
108
  }
48
109
 
49
110
  export const initCommand = new Command('init')
50
111
  .description('Initialize Grafema in current project')
51
112
  .argument('[path]', 'Project path', '.')
52
113
  .option('-f, --force', 'Overwrite existing config')
114
+ .option('-y, --yes', 'Skip prompts (non-interactive mode)')
115
+ .addHelpText('after', `
116
+ Examples:
117
+ grafema init Initialize in current directory
118
+ grafema init ./my-project Initialize in specific directory
119
+ grafema init --force Overwrite existing configuration
120
+ grafema init --yes Skip prompts, auto-run analyze
121
+ `)
53
122
  .action(async (path: string, options: InitOptions) => {
54
123
  const projectPath = resolve(path);
55
124
  const grafemaDir = join(projectPath, '.grafema');
@@ -59,10 +128,15 @@ export const initCommand = new Command('init')
59
128
 
60
129
  // Check package.json
61
130
  if (!existsSync(packageJsonPath)) {
62
- exitWithError('No package.json found', [
63
- 'Initialize a project: npm init',
64
- 'Or check you are in the right directory'
65
- ]);
131
+ console.error(' Grafema currently supports JavaScript/TypeScript projects only.');
132
+ console.error(` No package.json found in ${projectPath}`);
133
+ console.error('');
134
+ console.error(' Supported: Node.js, React, Express, Next.js, Vue, Angular, etc.');
135
+ console.error(' Coming soon: Python, Go, Rust');
136
+ console.error('');
137
+ console.error(' If this IS a JS/TS project, create package.json first:');
138
+ console.error(' npm init -y');
139
+ process.exit(1);
66
140
  }
67
141
  console.log('✓ Found package.json');
68
142
 
@@ -79,8 +153,7 @@ export const initCommand = new Command('init')
79
153
  console.log('');
80
154
  console.log('✓ Grafema already initialized');
81
155
  console.log(' → Use --force to overwrite config');
82
- console.log('');
83
- console.log('Next: Run "grafema analyze" to build the code graph');
156
+ printNextSteps();
84
157
  return;
85
158
  }
86
159
 
@@ -107,6 +180,20 @@ export const initCommand = new Command('init')
107
180
  }
108
181
  }
109
182
 
110
- console.log('');
111
- console.log('Next: Run "grafema analyze" to build the code graph');
183
+ printNextSteps();
184
+
185
+ // Prompt to run analyze in interactive mode
186
+ if (isInteractive(options)) {
187
+ console.log('');
188
+ const runNow = await askYesNo('Run analysis now? [Y/n] ');
189
+ if (runNow) {
190
+ console.log('');
191
+ console.log('Starting analysis...');
192
+ console.log('');
193
+ const exitCode = await runAnalyze(projectPath);
194
+ if (exitCode !== 0) {
195
+ process.exit(exitCode);
196
+ }
197
+ }
198
+ }
112
199
  });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * List command - List nodes by type
3
+ *
4
+ * Unix-style listing of nodes in the graph. Similar to `ls` for files,
5
+ * but for code graph nodes.
6
+ *
7
+ * Use cases:
8
+ * - "Show me all HTTP routes in this project"
9
+ * - "List all functions" (with limit for large codebases)
10
+ * - "What Socket.IO events are defined?"
11
+ */
12
+
13
+ import { Command } from 'commander';
14
+ import { resolve, join, relative } from 'path';
15
+ import { existsSync } from 'fs';
16
+ import { RFDBServerBackend } from '@grafema/core';
17
+ import { exitWithError } from '../utils/errorFormatter.js';
18
+
19
+ interface LsOptions {
20
+ project: string;
21
+ type: string;
22
+ json?: boolean;
23
+ limit: string;
24
+ }
25
+
26
+ interface NodeInfo {
27
+ id: string;
28
+ type: string;
29
+ name: string;
30
+ file: string;
31
+ line?: number;
32
+ method?: string;
33
+ path?: string;
34
+ url?: string;
35
+ event?: string;
36
+ [key: string]: unknown;
37
+ }
38
+
39
+ export const lsCommand = new Command('ls')
40
+ .description('List nodes by type')
41
+ .requiredOption('-t, --type <nodeType>', 'Node type to list (required)')
42
+ .option('-p, --project <path>', 'Project path', '.')
43
+ .option('-j, --json', 'Output as JSON')
44
+ .option('-l, --limit <n>', 'Limit results (default: 50)', '50')
45
+ .addHelpText('after', `
46
+ Examples:
47
+ grafema ls --type FUNCTION List functions (up to 50)
48
+ grafema ls --type http:route List all HTTP routes
49
+ grafema ls --type http:request List all HTTP requests (fetch/axios)
50
+ grafema ls -t socketio:event List Socket.IO events
51
+ grafema ls --type CLASS -l 100 List up to 100 classes
52
+ grafema ls --type jsx:component --json Output as JSON
53
+
54
+ Discover available types:
55
+ grafema types List all types with counts
56
+ `)
57
+ .action(async (options: LsOptions) => {
58
+ const projectPath = resolve(options.project);
59
+ const grafemaDir = join(projectPath, '.grafema');
60
+ const dbPath = join(grafemaDir, 'graph.rfdb');
61
+
62
+ if (!existsSync(dbPath)) {
63
+ exitWithError('No graph database found', ['Run: grafema analyze']);
64
+ }
65
+
66
+ const backend = new RFDBServerBackend({ dbPath });
67
+ await backend.connect();
68
+
69
+ try {
70
+ const limit = parseInt(options.limit, 10);
71
+ const nodeType = options.type;
72
+
73
+ // Check if type exists in graph
74
+ const typeCounts = await backend.countNodesByType();
75
+ if (!typeCounts[nodeType]) {
76
+ const availableTypes = Object.keys(typeCounts).sort();
77
+ exitWithError(`No nodes of type "${nodeType}" found`, [
78
+ 'Available types:',
79
+ ...availableTypes.slice(0, 10).map(t => ` ${t}`),
80
+ availableTypes.length > 10 ? ` ... and ${availableTypes.length - 10} more` : '',
81
+ '',
82
+ 'Run: grafema types to see all types with counts',
83
+ ].filter(Boolean));
84
+ }
85
+
86
+ // Collect nodes
87
+ const nodes: NodeInfo[] = [];
88
+ for await (const node of backend.queryNodes({ nodeType: nodeType as any })) {
89
+ nodes.push({
90
+ id: node.id,
91
+ type: node.type || nodeType,
92
+ name: node.name || '',
93
+ file: node.file || '',
94
+ line: node.line,
95
+ method: node.method as string | undefined,
96
+ path: node.path as string | undefined,
97
+ url: node.url as string | undefined,
98
+ event: node.event as string | undefined,
99
+ });
100
+ if (nodes.length >= limit) break;
101
+ }
102
+
103
+ const totalCount = typeCounts[nodeType];
104
+ const showing = nodes.length;
105
+
106
+ if (options.json) {
107
+ console.log(JSON.stringify({
108
+ type: nodeType,
109
+ nodes,
110
+ showing,
111
+ total: totalCount,
112
+ }, null, 2));
113
+ } else {
114
+ console.log(`[${nodeType}] (${showing}${showing < totalCount ? ` of ${totalCount}` : ''}):`);
115
+ console.log('');
116
+
117
+ for (const node of nodes) {
118
+ const display = formatNodeForList(node, nodeType, projectPath);
119
+ console.log(` ${display}`);
120
+ }
121
+
122
+ if (showing < totalCount) {
123
+ console.log('');
124
+ console.log(` ... ${totalCount - showing} more. Use --limit ${totalCount} to see all.`);
125
+ }
126
+ }
127
+ } finally {
128
+ await backend.close();
129
+ }
130
+ });
131
+
132
+ /**
133
+ * Format a node for list display based on its type.
134
+ * Different types show different fields.
135
+ */
136
+ function formatNodeForList(node: NodeInfo, nodeType: string, projectPath: string): string {
137
+ const relFile = node.file ? relative(projectPath, node.file) : '';
138
+ const loc = node.line ? `${relFile}:${node.line}` : relFile;
139
+
140
+ // HTTP routes: METHOD PATH (location)
141
+ if (nodeType === 'http:route' && node.method && node.path) {
142
+ return `${node.method.padEnd(6)} ${node.path} (${loc})`;
143
+ }
144
+
145
+ // HTTP requests: METHOD URL (location)
146
+ if (nodeType === 'http:request') {
147
+ const method = (node.method || 'GET').padEnd(6);
148
+ const url = node.url || 'dynamic';
149
+ return `${method} ${url} (${loc})`;
150
+ }
151
+
152
+ // Socket.IO events: event_name
153
+ if (nodeType === 'socketio:event') {
154
+ return node.name || node.id;
155
+ }
156
+
157
+ // Socket.IO emit/on: event (location)
158
+ if (nodeType === 'socketio:emit' || nodeType === 'socketio:on') {
159
+ const event = node.event || node.name || 'unknown';
160
+ return `${event} (${loc})`;
161
+ }
162
+
163
+ // Default: name (location)
164
+ const name = node.name || node.id;
165
+ return loc ? `${name} (${loc})` : name;
166
+ }
@@ -17,6 +17,12 @@ export const overviewCommand = new Command('overview')
17
17
  .description('Show project overview and statistics')
18
18
  .option('-p, --project <path>', 'Project path', '.')
19
19
  .option('-j, --json', 'Output as JSON')
20
+ .addHelpText('after', `
21
+ Examples:
22
+ grafema overview Show project dashboard
23
+ grafema overview --json Output statistics as JSON
24
+ grafema overview -p ./app Overview for specific project
25
+ `)
20
26
  .action(async (options: { project: string; json?: boolean }) => {
21
27
  const projectPath = resolve(options.project);
22
28
  const grafemaDir = join(projectPath, '.grafema');
@@ -61,20 +67,27 @@ export const overviewCommand = new Command('overview')
61
67
  console.log('External Interactions:');
62
68
  const httpRoutes = stats.nodesByType['http:route'] || 0;
63
69
  const dbQueries = stats.nodesByType['db:query'] || 0;
70
+ const socketEvents = stats.nodesByType['socketio:event'] || 0;
64
71
  const socketEmit = stats.nodesByType['socketio:emit'] || 0;
65
72
  const socketOn = stats.nodesByType['socketio:on'] || 0;
66
73
  const events = stats.nodesByType['event:listener'] || 0;
67
74
 
68
75
  if (httpRoutes > 0) console.log(`├─ HTTP routes: ${httpRoutes}`);
69
76
  if (dbQueries > 0) console.log(`├─ Database queries: ${dbQueries}`);
70
- if (socketEmit + socketOn > 0) console.log(`├─ Socket.IO: ${socketEmit} emit, ${socketOn} listeners`);
77
+ if (socketEvents > 0) {
78
+ // New format showing event count prominently
79
+ console.log(`├─ Socket.IO: ${socketEvents} events (${socketEmit} emit, ${socketOn} listeners)`);
80
+ } else if (socketEmit + socketOn > 0) {
81
+ // Fallback for graphs analyzed before REG-209
82
+ console.log(`├─ Socket.IO: ${socketEmit} emit, ${socketOn} listeners`);
83
+ }
71
84
  if (events > 0) console.log(`├─ Event listeners: ${events}`);
72
85
 
73
86
  // Check for external module refs
74
87
  const externalModules = stats.nodesByType['EXTERNAL_MODULE'] || 0;
75
88
  if (externalModules > 0) console.log(`└─ External modules: ${externalModules}`);
76
89
 
77
- if (httpRoutes + dbQueries + socketEmit + socketOn + events + externalModules === 0) {
90
+ if (httpRoutes + dbQueries + socketEvents + socketEmit + socketOn + events + externalModules === 0) {
78
91
  console.log('└─ (none detected)');
79
92
  }
80
93
  console.log('');