@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.
- package/dist/cli.js +10 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +69 -11
- package/dist/commands/check.d.ts +6 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +177 -1
- package/dist/commands/coverage.d.ts.map +1 -1
- package/dist/commands/coverage.js +7 -0
- package/dist/commands/doctor/checks.d.ts +55 -0
- package/dist/commands/doctor/checks.d.ts.map +1 -0
- package/dist/commands/doctor/checks.js +534 -0
- package/dist/commands/doctor/output.d.ts +20 -0
- package/dist/commands/doctor/output.d.ts.map +1 -0
- package/dist/commands/doctor/output.js +94 -0
- package/dist/commands/doctor/types.d.ts +42 -0
- package/dist/commands/doctor/types.d.ts.map +1 -0
- package/dist/commands/doctor/types.js +4 -0
- package/dist/commands/doctor.d.ts +17 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +80 -0
- package/dist/commands/explain.d.ts +16 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +145 -0
- package/dist/commands/explore.d.ts +7 -1
- package/dist/commands/explore.d.ts.map +1 -1
- package/dist/commands/explore.js +204 -85
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +16 -4
- package/dist/commands/impact.d.ts.map +1 -1
- package/dist/commands/impact.js +48 -50
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +93 -15
- package/dist/commands/ls.d.ts +14 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +132 -0
- package/dist/commands/overview.d.ts.map +1 -1
- package/dist/commands/overview.js +15 -2
- package/dist/commands/query.d.ts +98 -0
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +549 -136
- package/dist/commands/schema.d.ts +13 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +279 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +13 -6
- package/dist/commands/stats.d.ts.map +1 -1
- package/dist/commands/stats.js +7 -0
- package/dist/commands/trace.d.ts +73 -0
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +500 -5
- package/dist/commands/types.d.ts +12 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +79 -0
- package/dist/utils/formatNode.d.ts +13 -0
- package/dist/utils/formatNode.d.ts.map +1 -1
- package/dist/utils/formatNode.js +35 -2
- package/package.json +3 -3
- package/src/cli.ts +10 -0
- package/src/commands/analyze.ts +84 -9
- package/src/commands/check.ts +201 -0
- package/src/commands/coverage.ts +7 -0
- package/src/commands/doctor/checks.ts +612 -0
- package/src/commands/doctor/output.ts +115 -0
- package/src/commands/doctor/types.ts +45 -0
- package/src/commands/doctor.ts +106 -0
- package/src/commands/explain.ts +173 -0
- package/src/commands/explore.tsx +247 -97
- package/src/commands/get.ts +20 -6
- package/src/commands/impact.ts +55 -61
- package/src/commands/init.ts +101 -14
- package/src/commands/ls.ts +166 -0
- package/src/commands/overview.ts +15 -2
- package/src/commands/query.ts +643 -149
- package/src/commands/schema.ts +345 -0
- package/src/commands/server.ts +13 -6
- package/src/commands/stats.ts +7 -0
- package/src/commands/trace.ts +647 -6
- package/src/commands/types.ts +94 -0
- package/src/utils/formatNode.ts +42 -2
package/src/commands/init.ts
CHANGED
|
@@ -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
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
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
|
-
#
|
|
44
|
+
#
|
|
45
|
+
# exclude: # Skip files matching these patterns (takes precedence over include)
|
|
40
46
|
# - "**/*.test.ts"
|
|
41
|
-
# - "
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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
|
+
}
|
package/src/commands/overview.ts
CHANGED
|
@@ -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 (
|
|
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('');
|