@grafema/cli 0.2.4-beta → 0.2.5-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/README.md +73 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +136 -52
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/check.d.ts +2 -6
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +32 -46
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/coverage.js +1 -0
- package/dist/commands/coverage.js.map +1 -0
- package/dist/commands/doctor/checks.d.ts.map +1 -1
- package/dist/commands/doctor/checks.js +9 -5
- package/dist/commands/doctor/checks.js.map +1 -0
- package/dist/commands/doctor/output.js +1 -0
- package/dist/commands/doctor/output.js.map +1 -0
- package/dist/commands/doctor/types.js +1 -0
- package/dist/commands/doctor/types.js.map +1 -0
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/explain.js +1 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/explore.d.ts.map +1 -1
- package/dist/commands/explore.js +9 -4
- package/dist/commands/explore.js.map +1 -0
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +7 -0
- package/dist/commands/get.js.map +1 -0
- package/dist/commands/impact.js +1 -0
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -1
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +7 -0
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/overview.d.ts.map +1 -1
- package/dist/commands/overview.js +1 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +68 -1
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/schema.js +1 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/server.d.ts +2 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +128 -15
- package/dist/commands/server.js.map +1 -0
- package/dist/commands/stats.js +1 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/trace.js +1 -0
- package/dist/commands/trace.js.map +1 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/utils/codePreview.js +1 -0
- package/dist/utils/codePreview.js.map +1 -0
- package/dist/utils/errorFormatter.js +1 -0
- package/dist/utils/errorFormatter.js.map +1 -0
- package/dist/utils/formatNode.js +1 -0
- package/dist/utils/formatNode.js.map +1 -0
- package/dist/utils/progressRenderer.d.ts +119 -0
- package/dist/utils/progressRenderer.d.ts.map +1 -0
- package/dist/utils/progressRenderer.js +245 -0
- package/dist/utils/progressRenderer.js.map +1 -0
- package/dist/utils/spinner.d.ts +39 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +84 -0
- package/dist/utils/spinner.js.map +1 -0
- package/package.json +5 -4
- package/src/commands/analyze.ts +150 -55
- package/src/commands/check.ts +36 -68
- package/src/commands/doctor/checks.ts +8 -5
- package/src/commands/explore.tsx +8 -4
- package/src/commands/get.ts +8 -0
- package/src/commands/impact.ts +1 -1
- package/src/commands/init.ts +6 -2
- package/src/commands/ls.ts +8 -0
- package/src/commands/overview.ts +0 -4
- package/src/commands/query.ts +77 -1
- package/src/commands/server.ts +142 -16
- package/src/utils/progressRenderer.ts +288 -0
- package/src/utils/spinner.ts +94 -0
package/src/commands/explore.tsx
CHANGED
|
@@ -160,9 +160,10 @@ function Explorer({ backend, startNode, projectPath }: ExplorerProps) {
|
|
|
160
160
|
}));
|
|
161
161
|
}
|
|
162
162
|
} catch (err) {
|
|
163
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
164
|
setState(s => ({
|
|
164
165
|
...s,
|
|
165
|
-
error:
|
|
166
|
+
error: message,
|
|
166
167
|
loading: false,
|
|
167
168
|
}));
|
|
168
169
|
}
|
|
@@ -387,9 +388,10 @@ function Explorer({ backend, startNode, projectPath }: ExplorerProps) {
|
|
|
387
388
|
error: results.length === 0 ? `No results for "${query}"` : null,
|
|
388
389
|
}));
|
|
389
390
|
} catch (err) {
|
|
391
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
390
392
|
setState(s => ({
|
|
391
393
|
...s,
|
|
392
|
-
error:
|
|
394
|
+
error: message,
|
|
393
395
|
loading: false,
|
|
394
396
|
}));
|
|
395
397
|
}
|
|
@@ -408,9 +410,10 @@ function Explorer({ backend, startNode, projectPath }: ExplorerProps) {
|
|
|
408
410
|
loading: false,
|
|
409
411
|
}));
|
|
410
412
|
} catch (err) {
|
|
413
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
411
414
|
setState(s => ({
|
|
412
415
|
...s,
|
|
413
|
-
error:
|
|
416
|
+
error: message,
|
|
414
417
|
loading: false,
|
|
415
418
|
}));
|
|
416
419
|
}
|
|
@@ -1010,7 +1013,8 @@ async function runBatchExplore(
|
|
|
1010
1013
|
outputResults(callees, 'callees', useJson, projectPath, target);
|
|
1011
1014
|
}
|
|
1012
1015
|
} catch (err) {
|
|
1013
|
-
|
|
1016
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1017
|
+
exitWithError(`Explore failed: ${message}`);
|
|
1014
1018
|
}
|
|
1015
1019
|
}
|
|
1016
1020
|
|
package/src/commands/get.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { existsSync } from 'fs';
|
|
|
12
12
|
import { RFDBServerBackend } from '@grafema/core';
|
|
13
13
|
import { formatNodeDisplay } from '../utils/formatNode.js';
|
|
14
14
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
15
|
+
import { Spinner } from '../utils/spinner.js';
|
|
15
16
|
|
|
16
17
|
interface GetOptions {
|
|
17
18
|
project: string;
|
|
@@ -66,11 +67,15 @@ Examples:
|
|
|
66
67
|
const backend = new RFDBServerBackend({ dbPath });
|
|
67
68
|
await backend.connect();
|
|
68
69
|
|
|
70
|
+
const spinner = new Spinner('Querying graph...');
|
|
71
|
+
spinner.start();
|
|
72
|
+
|
|
69
73
|
try {
|
|
70
74
|
// Retrieve node by semantic ID
|
|
71
75
|
const node = await backend.getNode(semanticId);
|
|
72
76
|
|
|
73
77
|
if (!node) {
|
|
78
|
+
spinner.stop();
|
|
74
79
|
exitWithError('Node not found', [
|
|
75
80
|
`ID: ${semanticId}`,
|
|
76
81
|
'Try: grafema query "<name>" to search for nodes',
|
|
@@ -81,6 +86,8 @@ Examples:
|
|
|
81
86
|
const incomingEdges = await backend.getIncomingEdges(semanticId, null);
|
|
82
87
|
const outgoingEdges = await backend.getOutgoingEdges(semanticId, null);
|
|
83
88
|
|
|
89
|
+
spinner.stop();
|
|
90
|
+
|
|
84
91
|
if (options.json) {
|
|
85
92
|
await outputJSON(backend, node, incomingEdges, outgoingEdges);
|
|
86
93
|
} else {
|
|
@@ -88,6 +95,7 @@ Examples:
|
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
} finally {
|
|
98
|
+
spinner.stop();
|
|
91
99
|
await backend.close();
|
|
92
100
|
}
|
|
93
101
|
});
|
package/src/commands/impact.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { Command } from 'commander';
|
|
|
10
10
|
import { resolve, join, dirname } from 'path';
|
|
11
11
|
import { relative } from 'path';
|
|
12
12
|
import { existsSync } from 'fs';
|
|
13
|
-
import { RFDBServerBackend, findContainingFunction as findContainingFunctionCore
|
|
13
|
+
import { RFDBServerBackend, findContainingFunction as findContainingFunctionCore } from '@grafema/core';
|
|
14
14
|
import { formatNodeDisplay, formatNodeInline } from '../utils/formatNode.js';
|
|
15
15
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
16
16
|
|
package/src/commands/init.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
import { createInterface } from 'readline';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
-
import { exitWithError } from '../utils/errorFormatter.js';
|
|
12
11
|
import { stringify as stringifyYAML } from 'yaml';
|
|
13
12
|
import { DEFAULT_CONFIG } from '@grafema/core';
|
|
14
13
|
|
|
@@ -75,7 +74,9 @@ function askYesNo(question: string): Promise<boolean> {
|
|
|
75
74
|
function runAnalyze(projectPath: string): Promise<number> {
|
|
76
75
|
return new Promise((resolve) => {
|
|
77
76
|
const cliPath = join(__dirname, '..', 'cli.js');
|
|
78
|
-
|
|
77
|
+
// Use process.execPath (absolute path to current Node binary) instead of
|
|
78
|
+
// 'node' to avoid PATH lookup failures when nvm isn't loaded in the shell.
|
|
79
|
+
const child = spawn(process.execPath, [cliPath, 'analyze', projectPath], {
|
|
79
80
|
stdio: 'inherit', // Pass through all I/O for user to see progress
|
|
80
81
|
});
|
|
81
82
|
child.on('close', (code) => resolve(code ?? 1));
|
|
@@ -92,6 +93,9 @@ function printNextSteps(): void {
|
|
|
92
93
|
console.log(' 1. Review config: code .grafema/config.yaml');
|
|
93
94
|
console.log(' 2. Build graph: grafema analyze');
|
|
94
95
|
console.log(' 3. Explore: grafema overview');
|
|
96
|
+
console.log('');
|
|
97
|
+
console.log('For AI-assisted setup, use the Grafema MCP server');
|
|
98
|
+
console.log('with the "onboard_project" prompt.');
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
/**
|
package/src/commands/ls.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { resolve, join, relative } from 'path';
|
|
|
15
15
|
import { existsSync } from 'fs';
|
|
16
16
|
import { RFDBServerBackend } from '@grafema/core';
|
|
17
17
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
18
|
+
import { Spinner } from '../utils/spinner.js';
|
|
18
19
|
|
|
19
20
|
interface LsOptions {
|
|
20
21
|
project: string;
|
|
@@ -66,6 +67,9 @@ Discover available types:
|
|
|
66
67
|
const backend = new RFDBServerBackend({ dbPath });
|
|
67
68
|
await backend.connect();
|
|
68
69
|
|
|
70
|
+
const spinner = new Spinner('Querying graph...');
|
|
71
|
+
spinner.start();
|
|
72
|
+
|
|
69
73
|
try {
|
|
70
74
|
const limit = parseInt(options.limit, 10);
|
|
71
75
|
const nodeType = options.type;
|
|
@@ -73,6 +77,7 @@ Discover available types:
|
|
|
73
77
|
// Check if type exists in graph
|
|
74
78
|
const typeCounts = await backend.countNodesByType();
|
|
75
79
|
if (!typeCounts[nodeType]) {
|
|
80
|
+
spinner.stop();
|
|
76
81
|
const availableTypes = Object.keys(typeCounts).sort();
|
|
77
82
|
exitWithError(`No nodes of type "${nodeType}" found`, [
|
|
78
83
|
'Available types:',
|
|
@@ -103,6 +108,8 @@ Discover available types:
|
|
|
103
108
|
const totalCount = typeCounts[nodeType];
|
|
104
109
|
const showing = nodes.length;
|
|
105
110
|
|
|
111
|
+
spinner.stop();
|
|
112
|
+
|
|
106
113
|
if (options.json) {
|
|
107
114
|
console.log(JSON.stringify({
|
|
108
115
|
type: nodeType,
|
|
@@ -125,6 +132,7 @@ Discover available types:
|
|
|
125
132
|
}
|
|
126
133
|
}
|
|
127
134
|
} finally {
|
|
135
|
+
spinner.stop();
|
|
128
136
|
await backend.close();
|
|
129
137
|
}
|
|
130
138
|
});
|
package/src/commands/overview.ts
CHANGED
|
@@ -8,10 +8,6 @@ import { existsSync } from 'fs';
|
|
|
8
8
|
import { RFDBServerBackend } from '@grafema/core';
|
|
9
9
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
10
10
|
|
|
11
|
-
interface NodeStats {
|
|
12
|
-
type: string;
|
|
13
|
-
count: number;
|
|
14
|
-
}
|
|
15
11
|
|
|
16
12
|
export const overviewCommand = new Command('overview')
|
|
17
13
|
.description('Show project overview and statistics')
|
package/src/commands/query.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { existsSync } from 'fs';
|
|
|
15
15
|
import { RFDBServerBackend, parseSemanticId, findCallsInFunction as findCallsInFunctionCore, findContainingFunction as findContainingFunctionCore } from '@grafema/core';
|
|
16
16
|
import { formatNodeDisplay, formatNodeInline, formatLocation } from '../utils/formatNode.js';
|
|
17
17
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
18
|
+
import { Spinner } from '../utils/spinner.js';
|
|
18
19
|
|
|
19
20
|
interface QueryOptions {
|
|
20
21
|
project: string;
|
|
@@ -130,9 +131,13 @@ Examples:
|
|
|
130
131
|
const backend = new RFDBServerBackend({ dbPath });
|
|
131
132
|
await backend.connect();
|
|
132
133
|
|
|
134
|
+
const spinner = new Spinner('Querying graph...');
|
|
135
|
+
spinner.start();
|
|
136
|
+
|
|
133
137
|
try {
|
|
134
138
|
// Raw Datalog mode
|
|
135
139
|
if (options.raw) {
|
|
140
|
+
spinner.stop();
|
|
136
141
|
await executeRawQuery(backend, pattern, options);
|
|
137
142
|
return;
|
|
138
143
|
}
|
|
@@ -159,6 +164,8 @@ Examples:
|
|
|
159
164
|
// Find matching nodes
|
|
160
165
|
const nodes = await findNodes(backend, query, limit);
|
|
161
166
|
|
|
167
|
+
spinner.stop();
|
|
168
|
+
|
|
162
169
|
// Check if query has scope constraints for suggestion
|
|
163
170
|
const hasScope = query.file !== null || query.scopes.length > 0;
|
|
164
171
|
|
|
@@ -218,6 +225,7 @@ Examples:
|
|
|
218
225
|
}
|
|
219
226
|
|
|
220
227
|
} finally {
|
|
228
|
+
spinner.stop();
|
|
221
229
|
await backend.close();
|
|
222
230
|
}
|
|
223
231
|
});
|
|
@@ -254,6 +262,11 @@ function parsePattern(pattern: string): { type: string | null; name: string } {
|
|
|
254
262
|
emit: 'socketio:emit',
|
|
255
263
|
on: 'socketio:on',
|
|
256
264
|
listener: 'socketio:on',
|
|
265
|
+
// Grafema internal
|
|
266
|
+
plugin: 'grafema:plugin',
|
|
267
|
+
// Property access aliases (REG-395)
|
|
268
|
+
property: 'PROPERTY_ACCESS',
|
|
269
|
+
prop: 'PROPERTY_ACCESS',
|
|
257
270
|
};
|
|
258
271
|
|
|
259
272
|
if (typeMap[typeWord]) {
|
|
@@ -551,7 +564,8 @@ async function findNodes(
|
|
|
551
564
|
'http:request',
|
|
552
565
|
'socketio:event',
|
|
553
566
|
'socketio:emit',
|
|
554
|
-
'socketio:on'
|
|
567
|
+
'socketio:on',
|
|
568
|
+
'PROPERTY_ACCESS'
|
|
555
569
|
];
|
|
556
570
|
|
|
557
571
|
for (const nodeType of searchTypes) {
|
|
@@ -606,6 +620,21 @@ async function findNodes(
|
|
|
606
620
|
nodeInfo.handlerName = node.handlerName as string | undefined;
|
|
607
621
|
}
|
|
608
622
|
|
|
623
|
+
// Include plugin-specific fields
|
|
624
|
+
if (nodeType === 'grafema:plugin') {
|
|
625
|
+
nodeInfo.phase = node.phase as string | undefined;
|
|
626
|
+
nodeInfo.priority = node.priority as number | undefined;
|
|
627
|
+
nodeInfo.builtin = node.builtin as boolean | undefined;
|
|
628
|
+
nodeInfo.createsNodes = node.createsNodes as string[] | undefined;
|
|
629
|
+
nodeInfo.createsEdges = node.createsEdges as string[] | undefined;
|
|
630
|
+
nodeInfo.dependencies = node.dependencies as string[] | undefined;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Include objectName for PROPERTY_ACCESS nodes (REG-395)
|
|
634
|
+
if (nodeType === 'PROPERTY_ACCESS') {
|
|
635
|
+
nodeInfo.objectName = node.objectName as string | undefined;
|
|
636
|
+
}
|
|
637
|
+
|
|
609
638
|
results.push(nodeInfo);
|
|
610
639
|
if (results.length >= limit) break;
|
|
611
640
|
}
|
|
@@ -735,6 +764,12 @@ async function displayNode(node: NodeInfo, projectPath: string, backend: RFDBSer
|
|
|
735
764
|
return;
|
|
736
765
|
}
|
|
737
766
|
|
|
767
|
+
// Special formatting for Grafema plugin nodes
|
|
768
|
+
if (node.type === 'grafema:plugin') {
|
|
769
|
+
console.log(formatPluginDisplay(node, projectPath));
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
738
773
|
console.log(formatNodeDisplay(node, { projectPath }));
|
|
739
774
|
|
|
740
775
|
// Add scope context if present
|
|
@@ -890,6 +925,47 @@ function formatSocketIONodeDisplay(node: NodeInfo, projectPath: string): string
|
|
|
890
925
|
return lines.join('\n');
|
|
891
926
|
}
|
|
892
927
|
|
|
928
|
+
/**
|
|
929
|
+
* Format Grafema plugin node for display.
|
|
930
|
+
*
|
|
931
|
+
* Output:
|
|
932
|
+
* [grafema:plugin] HTTPConnectionEnricher
|
|
933
|
+
* Phase: ENRICHMENT (priority: 50)
|
|
934
|
+
* Creates: edges: INTERACTS_WITH, HTTP_RECEIVES
|
|
935
|
+
* Dependencies: ExpressRouteAnalyzer, FetchAnalyzer, ExpressResponseAnalyzer
|
|
936
|
+
* Source: packages/core/src/plugins/enrichment/HTTPConnectionEnricher.ts
|
|
937
|
+
*/
|
|
938
|
+
function formatPluginDisplay(node: NodeInfo, projectPath: string): string {
|
|
939
|
+
const lines: string[] = [];
|
|
940
|
+
|
|
941
|
+
lines.push(`[${node.type}] ${node.name}`);
|
|
942
|
+
|
|
943
|
+
const phase = (node.phase as string) || 'unknown';
|
|
944
|
+
const priority = (node.priority as number) ?? 0;
|
|
945
|
+
lines.push(` Phase: ${phase} (priority: ${priority})`);
|
|
946
|
+
|
|
947
|
+
const createsNodes = (node.createsNodes as string[]) || [];
|
|
948
|
+
const createsEdges = (node.createsEdges as string[]) || [];
|
|
949
|
+
const createsParts: string[] = [];
|
|
950
|
+
if (createsNodes.length > 0) createsParts.push(`nodes: ${createsNodes.join(', ')}`);
|
|
951
|
+
if (createsEdges.length > 0) createsParts.push(`edges: ${createsEdges.join(', ')}`);
|
|
952
|
+
if (createsParts.length > 0) {
|
|
953
|
+
lines.push(` Creates: ${createsParts.join('; ')}`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const deps = (node.dependencies as string[]) || [];
|
|
957
|
+
if (deps.length > 0) {
|
|
958
|
+
lines.push(` Dependencies: ${deps.join(', ')}`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (node.file) {
|
|
962
|
+
const relPath = relative(projectPath, node.file);
|
|
963
|
+
lines.push(` Source: ${relPath}`);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return lines.join('\n');
|
|
967
|
+
}
|
|
968
|
+
|
|
893
969
|
/**
|
|
894
970
|
* Execute raw Datalog query (backwards compat)
|
|
895
971
|
*/
|
package/src/commands/server.ts
CHANGED
|
@@ -1,25 +1,94 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server command - Manage RFDB server lifecycle
|
|
2
|
+
* Server command - Manage RFDB (Rega Flow Database) server lifecycle
|
|
3
3
|
*
|
|
4
4
|
* Provides explicit control over the RFDB server process:
|
|
5
5
|
* grafema server start - Start detached server
|
|
6
6
|
* grafema server stop - Stop server gracefully
|
|
7
7
|
* grafema server status - Check if server is running
|
|
8
|
+
* grafema server graphql - Start GraphQL API server
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import { Command } from 'commander';
|
|
11
|
-
import { resolve, join } from 'path';
|
|
12
|
+
import { resolve, join, dirname } from 'path';
|
|
12
13
|
import { existsSync, unlinkSync, writeFileSync, readFileSync } from 'fs';
|
|
13
14
|
import { spawn } from 'child_process';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
14
16
|
import { setTimeout as sleep } from 'timers/promises';
|
|
15
|
-
import { RFDBClient, loadConfig,
|
|
17
|
+
import { RFDBClient, loadConfig, RFDBServerBackend } from '@grafema/core';
|
|
16
18
|
import { exitWithError } from '../utils/errorFormatter.js';
|
|
17
19
|
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
|
|
18
23
|
// Extend config type for server settings
|
|
19
24
|
interface ServerConfig {
|
|
20
25
|
binaryPath?: string;
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Find RFDB server binary in order of preference:
|
|
30
|
+
* 1. Explicit path (from --binary flag or config)
|
|
31
|
+
* 2. Monorepo development (target/release, target/debug)
|
|
32
|
+
* 3. @grafema/rfdb npm package (prebuilt binaries)
|
|
33
|
+
* 4. ~/.local/bin/rfdb-server (user-installed)
|
|
34
|
+
*/
|
|
35
|
+
function findServerBinary(explicitPath?: string): string | null {
|
|
36
|
+
// 1. Explicit path from --binary flag or config
|
|
37
|
+
if (explicitPath) {
|
|
38
|
+
const resolved = resolve(explicitPath);
|
|
39
|
+
if (existsSync(resolved)) {
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
// Explicit path specified but not found - this is an error
|
|
43
|
+
console.error(`Specified binary not found: ${resolved}`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 2. Check packages/rfdb-server in monorepo (for development)
|
|
48
|
+
const projectRoot = join(__dirname, '../../../..');
|
|
49
|
+
const releaseBinary = join(projectRoot, 'packages/rfdb-server/target/release/rfdb-server');
|
|
50
|
+
if (existsSync(releaseBinary)) {
|
|
51
|
+
return releaseBinary;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const debugBinary = join(projectRoot, 'packages/rfdb-server/target/debug/rfdb-server');
|
|
55
|
+
if (existsSync(debugBinary)) {
|
|
56
|
+
return debugBinary;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Check @grafema/rfdb npm package
|
|
60
|
+
try {
|
|
61
|
+
const rfdbPkg = require.resolve('@grafema/rfdb');
|
|
62
|
+
const rfdbDir = dirname(rfdbPkg);
|
|
63
|
+
const platform = process.platform;
|
|
64
|
+
const arch = process.arch;
|
|
65
|
+
|
|
66
|
+
let platformDir: string;
|
|
67
|
+
if (platform === 'darwin') {
|
|
68
|
+
platformDir = arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
|
|
69
|
+
} else if (platform === 'linux') {
|
|
70
|
+
platformDir = arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
71
|
+
} else {
|
|
72
|
+
platformDir = `${platform}-${arch}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const npmBinary = join(rfdbDir, 'prebuilt', platformDir, 'rfdb-server');
|
|
76
|
+
if (existsSync(npmBinary)) {
|
|
77
|
+
return npmBinary;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// @grafema/rfdb not installed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 4. Check ~/.local/bin (user-installed binary for unsupported platforms)
|
|
84
|
+
const homeBinary = join(process.env.HOME || '', '.local', 'bin', 'rfdb-server');
|
|
85
|
+
if (existsSync(homeBinary)) {
|
|
86
|
+
return homeBinary;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
23
92
|
/**
|
|
24
93
|
* Check if server is running by attempting to ping it
|
|
25
94
|
*/
|
|
@@ -56,7 +125,7 @@ function getProjectPaths(projectPath: string) {
|
|
|
56
125
|
|
|
57
126
|
// Create main server command with subcommands
|
|
58
127
|
export const serverCommand = new Command('server')
|
|
59
|
-
.description('Manage RFDB server lifecycle')
|
|
128
|
+
.description('Manage RFDB (Rega Flow Database) server lifecycle')
|
|
60
129
|
.addHelpText('after', `
|
|
61
130
|
Examples:
|
|
62
131
|
grafema server start Start the RFDB server
|
|
@@ -104,34 +173,38 @@ serverCommand
|
|
|
104
173
|
}
|
|
105
174
|
|
|
106
175
|
// Determine binary path: CLI flag > config > auto-detect
|
|
107
|
-
let
|
|
176
|
+
let binaryPath: string | null = null;
|
|
108
177
|
|
|
109
178
|
if (options.binary) {
|
|
110
179
|
// Explicit --binary flag
|
|
111
|
-
|
|
180
|
+
binaryPath = findServerBinary(options.binary);
|
|
112
181
|
} else {
|
|
113
182
|
// Try to read from config
|
|
114
183
|
try {
|
|
115
184
|
const config = loadConfig(projectPath);
|
|
116
185
|
const serverConfig = (config as unknown as { server?: ServerConfig }).server;
|
|
117
186
|
if (serverConfig?.binaryPath) {
|
|
118
|
-
|
|
187
|
+
binaryPath = findServerBinary(serverConfig.binaryPath);
|
|
119
188
|
}
|
|
120
189
|
} catch {
|
|
121
190
|
// Config not found or invalid - continue with auto-detect
|
|
122
191
|
}
|
|
123
|
-
}
|
|
124
192
|
|
|
125
|
-
|
|
193
|
+
// Auto-detect if not specified
|
|
194
|
+
if (!binaryPath) {
|
|
195
|
+
binaryPath = findServerBinary();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
126
198
|
|
|
127
199
|
if (!binaryPath) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
200
|
+
exitWithError('RFDB server binary not found', [
|
|
201
|
+
'Specify path: grafema server start --binary /path/to/rfdb-server',
|
|
202
|
+
'Or add to config.yaml:',
|
|
203
|
+
' server:',
|
|
204
|
+
' binaryPath: /path/to/rfdb-server',
|
|
205
|
+
'Or install: npm install @grafema/rfdb',
|
|
206
|
+
'Or build: cargo build --release && cp target/release/rfdb-server ~/.local/bin/'
|
|
207
|
+
]);
|
|
135
208
|
}
|
|
136
209
|
|
|
137
210
|
console.log(`Starting RFDB server...`);
|
|
@@ -323,3 +396,56 @@ serverCommand
|
|
|
323
396
|
}
|
|
324
397
|
}
|
|
325
398
|
});
|
|
399
|
+
|
|
400
|
+
// grafema server graphql
|
|
401
|
+
serverCommand
|
|
402
|
+
.command('graphql')
|
|
403
|
+
.description('Start GraphQL API server (requires RFDB server running)')
|
|
404
|
+
.option('-p, --project <path>', 'Project path', '.')
|
|
405
|
+
.option('--port <number>', 'Port to listen on', '4000')
|
|
406
|
+
.option('--host <string>', 'Hostname to bind to', 'localhost')
|
|
407
|
+
.action(async (options: { project: string; port: string; host: string }) => {
|
|
408
|
+
const projectPath = resolve(options.project);
|
|
409
|
+
const { socketPath } = getProjectPaths(projectPath);
|
|
410
|
+
|
|
411
|
+
// Check if RFDB server is running
|
|
412
|
+
const status = await isServerRunning(socketPath);
|
|
413
|
+
if (!status.running) {
|
|
414
|
+
exitWithError('RFDB server not running', [
|
|
415
|
+
'Start the server first: grafema server start',
|
|
416
|
+
'Or run: grafema analyze (starts server automatically)'
|
|
417
|
+
]);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Create backend connection
|
|
421
|
+
const backend = new RFDBServerBackend({ socketPath });
|
|
422
|
+
await backend.connect();
|
|
423
|
+
|
|
424
|
+
// Import and start GraphQL server
|
|
425
|
+
const { startServer } = await import('@grafema/api');
|
|
426
|
+
const port = parseInt(options.port, 10);
|
|
427
|
+
|
|
428
|
+
console.log('Starting Grafema GraphQL API...');
|
|
429
|
+
console.log(` RFDB Socket: ${socketPath}`);
|
|
430
|
+
if (status.version) {
|
|
431
|
+
console.log(` RFDB Version: ${status.version}`);
|
|
432
|
+
}
|
|
433
|
+
console.log('');
|
|
434
|
+
|
|
435
|
+
const server = startServer({
|
|
436
|
+
backend,
|
|
437
|
+
port,
|
|
438
|
+
hostname: options.host,
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// Handle shutdown
|
|
442
|
+
const shutdown = async () => {
|
|
443
|
+
console.log('\nShutting down GraphQL server...');
|
|
444
|
+
server.close();
|
|
445
|
+
await backend.close();
|
|
446
|
+
process.exit(0);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
process.on('SIGINT', shutdown);
|
|
450
|
+
process.on('SIGTERM', shutdown);
|
|
451
|
+
});
|