@grafema/cli 0.3.24 → 0.3.28

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 (48) hide show
  1. package/README.md +59 -45
  2. package/dist/cli.js +10 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analyzeAction.d.ts.map +1 -1
  5. package/dist/commands/analyzeAction.js +134 -3
  6. package/dist/commands/analyzeAction.js.map +1 -1
  7. package/dist/commands/doctor/checks.d.ts.map +1 -1
  8. package/dist/commands/doctor/checks.js +7 -3
  9. package/dist/commands/doctor/checks.js.map +1 -1
  10. package/dist/commands/export.d.ts +15 -0
  11. package/dist/commands/export.d.ts.map +1 -0
  12. package/dist/commands/export.js +88 -0
  13. package/dist/commands/export.js.map +1 -0
  14. package/dist/commands/exportAction.d.ts +35 -0
  15. package/dist/commands/exportAction.d.ts.map +1 -0
  16. package/dist/commands/exportAction.js +58 -0
  17. package/dist/commands/exportAction.js.map +1 -0
  18. package/dist/commands/features.d.ts +13 -0
  19. package/dist/commands/features.d.ts.map +1 -0
  20. package/dist/commands/features.js +69 -0
  21. package/dist/commands/features.js.map +1 -0
  22. package/dist/commands/featuresAction.d.ts +82 -0
  23. package/dist/commands/featuresAction.d.ts.map +1 -0
  24. package/dist/commands/featuresAction.js +139 -0
  25. package/dist/commands/featuresAction.js.map +1 -0
  26. package/dist/commands/start.d.ts +12 -0
  27. package/dist/commands/start.d.ts.map +1 -0
  28. package/dist/commands/start.js +294 -0
  29. package/dist/commands/start.js.map +1 -0
  30. package/dist/commands/trace.d.ts.map +1 -1
  31. package/dist/commands/trace.js +50 -30
  32. package/dist/commands/trace.js.map +1 -1
  33. package/dist/commands/upgrade.d.ts +3 -0
  34. package/dist/commands/upgrade.d.ts.map +1 -0
  35. package/dist/commands/upgrade.js +279 -0
  36. package/dist/commands/upgrade.js.map +1 -0
  37. package/package.json +8 -8
  38. package/src/cli.ts +11 -0
  39. package/src/commands/analyzeAction.ts +135 -2
  40. package/src/commands/doctor/checks.ts +4 -3
  41. package/src/commands/explore.tsx +29 -2
  42. package/src/commands/export.ts +102 -0
  43. package/src/commands/exportAction.ts +107 -0
  44. package/src/commands/features.ts +88 -0
  45. package/src/commands/featuresAction.ts +218 -0
  46. package/src/commands/start.ts +303 -0
  47. package/src/commands/trace.ts +49 -29
  48. package/src/commands/upgrade.ts +310 -0
@@ -0,0 +1,310 @@
1
+ import { Command } from 'commander';
2
+ import {
3
+ existsSync, readdirSync, lstatSync, unlinkSync, rmSync,
4
+ statSync, mkdirSync,
5
+ } from 'fs';
6
+ import { join, resolve } from 'path';
7
+ import {
8
+ getGrafemaBinDir,
9
+ DOWNLOADABLE_BINARIES,
10
+ isBinaryCurrentVersion,
11
+ downloadBinary,
12
+ GRAFEMA_VERSION,
13
+ } from '@grafema/util';
14
+
15
+ const COLORS = {
16
+ green: '\x1b[32m',
17
+ red: '\x1b[31m',
18
+ yellow: '\x1b[33m',
19
+ cyan: '\x1b[36m',
20
+ dim: '\x1b[2m',
21
+ bold: '\x1b[1m',
22
+ reset: '\x1b[0m',
23
+ };
24
+
25
+ const LANG_BINARIES: Record<string, string[]> = {
26
+ js: ['grafema-analyzer', 'grafema-resolve'],
27
+ python: ['grafema-python-analyzer', 'python-resolve'],
28
+ haskell: ['haskell-analyzer', 'haskell-resolve'],
29
+ rust: ['grafema-rust-resolve'],
30
+ java: ['grafema-java-analyzer', 'java-resolve', 'java-parser'],
31
+ kotlin: ['grafema-kotlin-analyzer', 'kotlin-resolve', 'kotlin-parser'],
32
+ go: ['grafema-go-analyzer', 'go-resolve', 'go-parser'],
33
+ cpp: ['grafema-cpp-analyzer', 'cpp-resolve'],
34
+ swift: ['grafema-swift-analyzer', 'swift-resolve', 'swift-parser'],
35
+ objc: ['grafema-objc-analyzer', 'objc-parser'],
36
+ beam: ['beam-analyzer', 'beam-resolve'],
37
+ };
38
+
39
+ const INFRASTRUCTURE = ['grafema-orchestrator', 'rfdb-server'];
40
+
41
+ const DOWNLOADABLE_SET = new Set(DOWNLOADABLE_BINARIES);
42
+
43
+ interface CleanAction {
44
+ path: string;
45
+ name: string;
46
+ reason: string;
47
+ size: number;
48
+ }
49
+
50
+ function formatBytes(bytes: number): string {
51
+ if (bytes === 0) return '0 B';
52
+ const units = ['B', 'KB', 'MB', 'GB'];
53
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
54
+ const val = bytes / Math.pow(1024, i);
55
+ return `${val.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
56
+ }
57
+
58
+ function auditBinDir(binDir: string): { toClean: CleanAction[]; knownBinaries: string[] } {
59
+ const toClean: CleanAction[] = [];
60
+ const knownBinaries: string[] = [];
61
+
62
+ if (!existsSync(binDir)) return { toClean, knownBinaries };
63
+
64
+ for (const entry of readdirSync(binDir)) {
65
+ const fullPath = join(binDir, entry);
66
+ let stat;
67
+ try {
68
+ stat = lstatSync(fullPath);
69
+ } catch {
70
+ continue;
71
+ }
72
+
73
+ if (stat.isSymbolicLink() && !existsSync(fullPath)) {
74
+ toClean.push({ path: fullPath, name: entry, reason: 'broken symlink', size: 0 });
75
+ continue;
76
+ }
77
+
78
+ if (entry.startsWith('.build-hash.') || entry.endsWith('.downloading')) {
79
+ toClean.push({ path: fullPath, name: entry, reason: 'dev artifact', size: stat.size });
80
+ continue;
81
+ }
82
+
83
+ if (entry.endsWith('.version')) {
84
+ const binaryName = entry.slice(0, -'.version'.length);
85
+ if (!existsSync(join(binDir, binaryName))) {
86
+ toClean.push({ path: fullPath, name: entry, reason: 'orphaned version file', size: stat.size });
87
+ }
88
+ continue;
89
+ }
90
+
91
+ if (entry === '.gitkeep') continue;
92
+
93
+ if (DOWNLOADABLE_SET.has(entry)) {
94
+ knownBinaries.push(entry);
95
+ } else {
96
+ toClean.push({ path: fullPath, name: entry, reason: 'unknown binary', size: stat.size });
97
+ }
98
+ }
99
+
100
+ return { toClean, knownBinaries };
101
+ }
102
+
103
+ function getTargetBinaries(
104
+ options: { all?: boolean; lang?: string },
105
+ existingBinaries: string[],
106
+ ): string[] {
107
+ if (options.all) {
108
+ return [...DOWNLOADABLE_BINARIES];
109
+ }
110
+
111
+ if (options.lang) {
112
+ const langs = options.lang.split(',').map(l => l.trim().toLowerCase());
113
+ const invalid = langs.filter(l => !LANG_BINARIES[l]);
114
+ if (invalid.length > 0) {
115
+ console.error(`${COLORS.red}Unknown languages: ${invalid.join(', ')}${COLORS.reset}`);
116
+ console.error(`Available: ${Object.keys(LANG_BINARIES).join(', ')}`);
117
+ process.exit(1);
118
+ }
119
+ const bins = new Set<string>(INFRASTRUCTURE);
120
+ for (const lang of langs) {
121
+ for (const b of LANG_BINARIES[lang]) bins.add(b);
122
+ }
123
+ return [...bins];
124
+ }
125
+
126
+ const set = new Set<string>(existingBinaries);
127
+ for (const b of INFRASTRUCTURE) set.add(b);
128
+ return [...set];
129
+ }
130
+
131
+ function auditProjectDir(projectPath: string): CleanAction[] {
132
+ const grafemaDir = join(projectPath, '.grafema');
133
+ if (!existsSync(grafemaDir)) return [];
134
+
135
+ const toClean: CleanAction[] = [];
136
+
137
+ const diagLog = join(grafemaDir, 'diagnostics.log');
138
+ if (existsSync(diagLog)) {
139
+ toClean.push({ path: diagLog, name: 'diagnostics.log', reason: 'old diagnostics', size: statSync(diagLog).size });
140
+ }
141
+
142
+ const oldDb = join(grafemaDir, 'grafema.rfdb');
143
+ const newDb = join(grafemaDir, 'graph.rfdb');
144
+ if (existsSync(oldDb) && existsSync(newDb)) {
145
+ let size = 0;
146
+ try {
147
+ for (const f of readdirSync(oldDb)) {
148
+ try { size += statSync(join(oldDb, f)).size; } catch { /* skip */ }
149
+ }
150
+ } catch { /* skip */ }
151
+ toClean.push({ path: oldDb, name: 'grafema.rfdb/', reason: 'stale database (renamed to graph.rfdb)', size });
152
+ }
153
+
154
+ const logsDir = join(grafemaDir, 'logs');
155
+ if (existsSync(logsDir)) {
156
+ const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
157
+ try {
158
+ for (const f of readdirSync(logsDir)) {
159
+ const fp = join(logsDir, f);
160
+ try {
161
+ const s = statSync(fp);
162
+ if (s.isFile() && s.mtimeMs < thirtyDaysAgo) {
163
+ toClean.push({ path: fp, name: `logs/${f}`, reason: 'log older than 30 days', size: s.size });
164
+ }
165
+ } catch { /* skip */ }
166
+ }
167
+ } catch { /* skip */ }
168
+ }
169
+
170
+ return toClean;
171
+ }
172
+
173
+ export const upgradeCommand = new Command('upgrade')
174
+ .description('Clean stale artifacts and upgrade binaries to current version')
175
+ .option('-a, --all', 'Download all available binaries (not just existing)')
176
+ .option('-l, --lang <langs>', 'Comma-separated languages to install (js,python,rust,...)')
177
+ .option('-n, --dry-run', 'Preview changes without executing')
178
+ .option('-p, --project [path]', 'Also clean project .grafema/ artifacts')
179
+ .addHelpText('after', `
180
+ Examples:
181
+ grafema upgrade Clean + upgrade existing binaries
182
+ grafema upgrade --all Clean + download ALL available binaries
183
+ grafema upgrade --lang js,python Clean + ensure JS and Python analyzers
184
+ grafema upgrade --dry-run Preview what would be cleaned/upgraded
185
+ grafema upgrade --project Also clean .grafema/ in current directory
186
+
187
+ Available languages: ${Object.keys(LANG_BINARIES).join(', ')}`)
188
+ .action(async (options: { all?: boolean; lang?: string; dryRun?: boolean; project?: string | true }) => {
189
+ const dryRun = !!options.dryRun;
190
+ const prefix = dryRun ? `${COLORS.dim}[dry-run]${COLORS.reset} ` : '';
191
+ const binDir = getGrafemaBinDir();
192
+
193
+ console.log(`${COLORS.bold}Grafema Upgrade${COLORS.reset} ${COLORS.dim}v${GRAFEMA_VERSION}${COLORS.reset}`);
194
+ console.log();
195
+
196
+ if (!existsSync(binDir)) {
197
+ mkdirSync(binDir, { recursive: true });
198
+ }
199
+
200
+ // Phase 1+2: Audit and clean ~/.grafema/bin/
201
+ const { toClean, knownBinaries } = auditBinDir(binDir);
202
+
203
+ if (toClean.length > 0) {
204
+ console.log(`${COLORS.bold}Cleaning ~/.grafema/bin/${COLORS.reset}`);
205
+ for (const item of toClean) {
206
+ console.log(` ${prefix}${COLORS.red}✗${COLORS.reset} ${item.name} ${COLORS.dim}(${item.reason}, ${formatBytes(item.size)})${COLORS.reset}`);
207
+ if (!dryRun) {
208
+ try {
209
+ const s = lstatSync(item.path);
210
+ if (s.isDirectory()) {
211
+ rmSync(item.path, { recursive: true, force: true });
212
+ } else {
213
+ unlinkSync(item.path);
214
+ }
215
+ } catch (err) {
216
+ console.log(` ${COLORS.yellow}warning: ${err instanceof Error ? err.message : err}${COLORS.reset}`);
217
+ }
218
+ }
219
+ }
220
+ console.log();
221
+ }
222
+
223
+ // Phase 3: Upgrade binaries
224
+ const targets = getTargetBinaries(options, knownBinaries);
225
+
226
+ console.log(`${COLORS.bold}Upgrading binaries${COLORS.reset} ${COLORS.dim}(target: binaries-v${GRAFEMA_VERSION})${COLORS.reset}`);
227
+
228
+ let upgradedCount = 0;
229
+ let currentCount = 0;
230
+ let failedCount = 0;
231
+
232
+ for (const name of targets) {
233
+ const binaryPath = join(binDir, name);
234
+
235
+ if (existsSync(binaryPath) && isBinaryCurrentVersion(binaryPath)) {
236
+ currentCount++;
237
+ console.log(` ${COLORS.green}✓${COLORS.reset} ${name} ${COLORS.dim}-- current${COLORS.reset}`);
238
+ continue;
239
+ }
240
+
241
+ if (dryRun) {
242
+ const status = existsSync(binaryPath) ? 'stale, would upgrade' : 'missing, would download';
243
+ console.log(` ${prefix}${COLORS.cyan}↓${COLORS.reset} ${name} ${COLORS.dim}(${status})${COLORS.reset}`);
244
+ upgradedCount++;
245
+ continue;
246
+ }
247
+
248
+ try {
249
+ process.stdout.write(` ${COLORS.cyan}↓${COLORS.reset} ${name} -- downloading...`);
250
+ const downloaded = await downloadBinary(name);
251
+ const size = statSync(downloaded).size;
252
+ process.stdout.write(`\r ${COLORS.green}✓${COLORS.reset} ${name} ${COLORS.dim}(${formatBytes(size)})${COLORS.reset} \n`);
253
+ upgradedCount++;
254
+ } catch (err) {
255
+ const msg = err instanceof Error ? err.message : String(err);
256
+ process.stdout.write(`\r ${COLORS.red}✗${COLORS.reset} ${name} -- ${msg} \n`);
257
+ failedCount++;
258
+ }
259
+ }
260
+ console.log();
261
+
262
+ // Phase 4: Project cleanup (optional)
263
+ let projectCleanedCount = 0;
264
+ let projectCleanedBytes = 0;
265
+ if (options.project !== undefined) {
266
+ const projectPath = typeof options.project === 'string' ? resolve(options.project) : resolve('.');
267
+ const projectActions = auditProjectDir(projectPath);
268
+ if (projectActions.length > 0) {
269
+ console.log(`${COLORS.bold}Cleaning project artifacts${COLORS.reset} ${COLORS.dim}(${projectPath}/.grafema/)${COLORS.reset}`);
270
+ for (const item of projectActions) {
271
+ console.log(` ${prefix}${COLORS.red}✗${COLORS.reset} ${item.name} ${COLORS.dim}(${item.reason}, ${formatBytes(item.size)})${COLORS.reset}`);
272
+ if (!dryRun) {
273
+ try {
274
+ const s = lstatSync(item.path);
275
+ if (s.isDirectory()) {
276
+ rmSync(item.path, { recursive: true, force: true });
277
+ } else {
278
+ unlinkSync(item.path);
279
+ }
280
+ projectCleanedCount++;
281
+ projectCleanedBytes += item.size;
282
+ } catch (err) {
283
+ console.log(` ${COLORS.yellow}warning: ${err instanceof Error ? err.message : err}${COLORS.reset}`);
284
+ }
285
+ } else {
286
+ projectCleanedCount++;
287
+ projectCleanedBytes += item.size;
288
+ }
289
+ }
290
+ console.log();
291
+ }
292
+ }
293
+
294
+ // Summary
295
+ const parts: string[] = [];
296
+ if (toClean.length > 0) {
297
+ const totalCleaned = toClean.reduce((s, a) => s + a.size, 0);
298
+ parts.push(`cleaned ${toClean.length} files (${formatBytes(totalCleaned)})`);
299
+ }
300
+ if (upgradedCount > 0) parts.push(`upgraded ${upgradedCount} binaries`);
301
+ if (currentCount > 0) parts.push(`${currentCount} already current`);
302
+ if (failedCount > 0) parts.push(`${COLORS.red}${failedCount} failed${COLORS.reset}`);
303
+ if (projectCleanedCount > 0) parts.push(`project: ${projectCleanedCount} files (${formatBytes(projectCleanedBytes)})`);
304
+
305
+ if (parts.length === 0) {
306
+ console.log(`${COLORS.green}✓${COLORS.reset} Everything is up to date.`);
307
+ } else {
308
+ console.log(`${COLORS.bold}Summary:${COLORS.reset} ${parts.join(', ')}`);
309
+ }
310
+ });