@grafema/cli 0.3.18 → 0.3.20
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 +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +2 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/analyzeAction.d.ts +1 -0
- package/dist/commands/analyzeAction.d.ts.map +1 -1
- package/dist/commands/analyzeAction.js +34 -3
- package/dist/commands/analyzeAction.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -74
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/registry.d.ts +10 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +113 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +1 -0
- package/dist/commands/trace.js.map +1 -1
- package/dist/commands/wtf.d.ts.map +1 -1
- package/dist/commands/wtf.js +3 -1
- package/dist/commands/wtf.js.map +1 -1
- package/dist/utils/quickstart.d.ts +35 -0
- package/dist/utils/quickstart.d.ts.map +1 -0
- package/dist/utils/quickstart.js +175 -0
- package/dist/utils/quickstart.js.map +1 -0
- package/package.json +4 -4
- package/src/cli.ts +2 -0
- package/src/commands/analyze.ts +2 -0
- package/src/commands/analyzeAction.ts +36 -3
- package/src/commands/init.ts +7 -82
- package/src/commands/registry.ts +138 -0
- package/src/commands/trace.ts +1 -0
- package/src/commands/wtf.ts +4 -1
- package/src/utils/quickstart.ts +192 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for project initialization and quickstart.
|
|
3
|
+
*
|
|
4
|
+
* Used by both `grafema init` and `grafema analyze --quickstart`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
9
|
+
import { stringify as stringifyYAML } from 'yaml';
|
|
10
|
+
import { GRAFEMA_VERSION, getSchemaVersion } from '@grafema/util';
|
|
11
|
+
|
|
12
|
+
/** All file extensions supported by Grafema's analyzers. */
|
|
13
|
+
export const SUPPORTED_EXTENSIONS = [
|
|
14
|
+
// JavaScript / TypeScript
|
|
15
|
+
'js', 'jsx', 'mjs', 'cjs', 'ts', 'tsx',
|
|
16
|
+
// Rust
|
|
17
|
+
'rs',
|
|
18
|
+
// Java / Kotlin
|
|
19
|
+
'java', 'kt', 'kts',
|
|
20
|
+
// Python
|
|
21
|
+
'py', 'pyi',
|
|
22
|
+
// Go
|
|
23
|
+
'go',
|
|
24
|
+
// Haskell
|
|
25
|
+
'hs',
|
|
26
|
+
// C / C++
|
|
27
|
+
'c', 'h', 'cpp', 'hpp', 'cc', 'cxx', 'hxx', 'hh', 'inl', 'ipp', 'tpp', 'txx',
|
|
28
|
+
// Swift / Objective-C
|
|
29
|
+
'swift', 'm', 'mm',
|
|
30
|
+
// BEAM (Elixir / Erlang)
|
|
31
|
+
'ex', 'exs', 'erl', 'hrl',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/** Directories to skip during project scanning. */
|
|
35
|
+
const SKIP_DIRS = new Set([
|
|
36
|
+
'node_modules', 'dist', 'build', 'target', 'vendor',
|
|
37
|
+
'.git', '.grafema', 'coverage', '.next', '.nuxt',
|
|
38
|
+
'__pycache__', '.tox', '.venv',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const SUPPORTED_SET = new Set(SUPPORTED_EXTENSIONS);
|
|
42
|
+
const MAX_DEPTH = 10;
|
|
43
|
+
const MAX_FILES = 50_000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Scan a project directory for supported file extensions.
|
|
47
|
+
* Returns a Map of extension → file count for detected extensions.
|
|
48
|
+
*/
|
|
49
|
+
export function scanExtensions(projectPath: string): Map<string, number> {
|
|
50
|
+
const counts = new Map<string, number>();
|
|
51
|
+
let filesScanned = 0;
|
|
52
|
+
|
|
53
|
+
function walk(dir: string, depth: number): void {
|
|
54
|
+
if (depth > MAX_DEPTH || filesScanned >= MAX_FILES) return;
|
|
55
|
+
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
59
|
+
} catch {
|
|
60
|
+
return; // Permission denied or similar — skip
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (filesScanned >= MAX_FILES) return;
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
|
|
68
|
+
walk(join(dir, entry.name), depth + 1);
|
|
69
|
+
}
|
|
70
|
+
} else if (entry.isFile()) {
|
|
71
|
+
filesScanned++;
|
|
72
|
+
const dot = entry.name.lastIndexOf('.');
|
|
73
|
+
if (dot !== -1) {
|
|
74
|
+
const ext = entry.name.slice(dot + 1);
|
|
75
|
+
if (SUPPORTED_SET.has(ext)) {
|
|
76
|
+
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
walk(projectPath, 0);
|
|
84
|
+
return counts;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generate config YAML content.
|
|
89
|
+
*
|
|
90
|
+
* If `detectedExtensions` is provided and non-empty, only includes those extensions.
|
|
91
|
+
* Otherwise falls back to all supported extensions (same as `grafema init`).
|
|
92
|
+
*/
|
|
93
|
+
export function generateSmartConfig(detectedExtensions?: Map<string, number>): string {
|
|
94
|
+
const exts = (detectedExtensions && detectedExtensions.size > 0)
|
|
95
|
+
? [...detectedExtensions.keys()]
|
|
96
|
+
: SUPPORTED_EXTENSIONS;
|
|
97
|
+
|
|
98
|
+
const extensions = `*.{${exts.join(',')}}`;
|
|
99
|
+
const config = {
|
|
100
|
+
version: getSchemaVersion(GRAFEMA_VERSION),
|
|
101
|
+
root: '..',
|
|
102
|
+
include: [`**/${extensions}`],
|
|
103
|
+
exclude: [
|
|
104
|
+
'**/*.test.*',
|
|
105
|
+
'**/__tests__/**',
|
|
106
|
+
'**/node_modules/**',
|
|
107
|
+
'**/dist/**',
|
|
108
|
+
'**/build/**',
|
|
109
|
+
'**/target/**',
|
|
110
|
+
'**/vendor/**',
|
|
111
|
+
'**/.git/**',
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const yaml = stringifyYAML(config, {
|
|
116
|
+
lineWidth: 0,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return `# Grafema Configuration
|
|
120
|
+
# Documentation: https://github.com/grafema/grafema#configuration
|
|
121
|
+
# Supported: JS/TS, Rust, Java, Kotlin, Python, Go, Haskell, C/C++, Swift, Elixir/Erlang
|
|
122
|
+
|
|
123
|
+
${yaml}
|
|
124
|
+
# services: # Explicit service definitions (overrides auto-discovery)
|
|
125
|
+
# - name: "api"
|
|
126
|
+
# path: "."
|
|
127
|
+
# entryPoint: "src/index.ts"
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Write config file to .grafema/config.yaml, creating the directory if needed.
|
|
133
|
+
*/
|
|
134
|
+
export function writeConfig(projectPath: string, configContent: string): void {
|
|
135
|
+
const grafemaDir = join(projectPath, '.grafema');
|
|
136
|
+
if (!existsSync(grafemaDir)) {
|
|
137
|
+
mkdirSync(grafemaDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
writeFileSync(join(grafemaDir, 'config.yaml'), configContent);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Add Grafema entries to .gitignore if it exists and doesn't already have them.
|
|
144
|
+
*/
|
|
145
|
+
export function updateGitignore(projectPath: string): boolean {
|
|
146
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
147
|
+
if (existsSync(gitignorePath)) {
|
|
148
|
+
const gitignore = readFileSync(gitignorePath, 'utf-8');
|
|
149
|
+
if (!gitignore.includes('.grafema/graph.rfdb')) {
|
|
150
|
+
writeFileSync(
|
|
151
|
+
gitignorePath,
|
|
152
|
+
gitignore + '\n# Grafema\n.grafema/graph.rfdb\n.grafema/rfdb.sock\n'
|
|
153
|
+
);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Map of extension to human-readable language name. */
|
|
161
|
+
const EXT_LANGUAGE: Record<string, string> = {
|
|
162
|
+
js: 'JavaScript', jsx: 'JavaScript', mjs: 'JavaScript', cjs: 'JavaScript',
|
|
163
|
+
ts: 'TypeScript', tsx: 'TypeScript',
|
|
164
|
+
rs: 'Rust',
|
|
165
|
+
java: 'Java', kt: 'Kotlin', kts: 'Kotlin',
|
|
166
|
+
py: 'Python', pyi: 'Python',
|
|
167
|
+
go: 'Go',
|
|
168
|
+
hs: 'Haskell',
|
|
169
|
+
c: 'C', h: 'C/C++', cpp: 'C++', hpp: 'C++', cc: 'C++', cxx: 'C++', hxx: 'C++', hh: 'C++',
|
|
170
|
+
inl: 'C++', ipp: 'C++', tpp: 'C++', txx: 'C++',
|
|
171
|
+
swift: 'Swift', m: 'Objective-C', mm: 'Objective-C++',
|
|
172
|
+
ex: 'Elixir', exs: 'Elixir', erl: 'Erlang', hrl: 'Erlang',
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Format detected extensions as a human-readable string.
|
|
177
|
+
* Groups by language and shows total file count per language.
|
|
178
|
+
*
|
|
179
|
+
* Example: "TypeScript (342 files), JavaScript (18 files), Python (5 files)"
|
|
180
|
+
*/
|
|
181
|
+
export function formatDetected(detected: Map<string, number>): string {
|
|
182
|
+
// Group counts by language
|
|
183
|
+
const langCounts = new Map<string, number>();
|
|
184
|
+
for (const [ext, count] of detected) {
|
|
185
|
+
const lang = EXT_LANGUAGE[ext] ?? ext;
|
|
186
|
+
langCounts.set(lang, (langCounts.get(lang) ?? 0) + count);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Sort by count descending
|
|
190
|
+
const sorted = [...langCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
191
|
+
return sorted.map(([lang, count]) => `${lang} (${count} files)`).join(', ');
|
|
192
|
+
}
|