@pyreon/cli 0.5.0
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/lib/analysis/index.js.html +5406 -0
- package/lib/index.js +468 -0
- package/lib/index.js.map +1 -0
- package/lib/types/index.d.ts +441 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index2.d.ts +71 -0
- package/lib/types/index2.d.ts.map +1 -0
- package/package.json +53 -0
- package/src/context.ts +327 -0
- package/src/doctor.ts +251 -0
- package/src/index.ts +75 -0
- package/src/tests/context.test.ts +340 -0
- package/src/tests/doctor.test.ts +257 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { detectReactPatterns, hasReactPatterns, migrateReactCode } from "@pyreon/compiler";
|
|
5
|
+
|
|
6
|
+
//#region src/context.ts
|
|
7
|
+
/**
|
|
8
|
+
* pyreon context — generates .pyreon/context.json for AI tool consumption
|
|
9
|
+
*
|
|
10
|
+
* Scans the project to extract:
|
|
11
|
+
* - Route definitions (paths, params, loaders, guards)
|
|
12
|
+
* - Component inventory (file, props, signals)
|
|
13
|
+
* - Island declarations (name, hydration strategy)
|
|
14
|
+
* - Framework version
|
|
15
|
+
*/
|
|
16
|
+
async function generateContext(options) {
|
|
17
|
+
const version = readVersion(options.cwd);
|
|
18
|
+
const sourceFiles = collectTsxFiles(options.cwd);
|
|
19
|
+
const routes = extractRoutes(sourceFiles, options.cwd);
|
|
20
|
+
const components = extractComponents(sourceFiles, options.cwd);
|
|
21
|
+
const islands = extractIslands(sourceFiles, options.cwd);
|
|
22
|
+
const context = {
|
|
23
|
+
framework: "pyreon",
|
|
24
|
+
version,
|
|
25
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26
|
+
routes,
|
|
27
|
+
components,
|
|
28
|
+
islands
|
|
29
|
+
};
|
|
30
|
+
const outDir = options.outPath ? path.dirname(options.outPath) : path.join(options.cwd, ".pyreon");
|
|
31
|
+
const outFile = options.outPath ?? path.join(outDir, "context.json");
|
|
32
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
33
|
+
fs.writeFileSync(outFile, JSON.stringify(context, null, 2), "utf-8");
|
|
34
|
+
ensureGitignore(options.cwd);
|
|
35
|
+
const relOut = path.relative(options.cwd, outFile);
|
|
36
|
+
console.log(` ✓ Generated ${relOut} (${components.length} components, ${routes.length} routes, ${islands.length} islands)`);
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
39
|
+
function parseRouteFromBlock(block, routeMatch) {
|
|
40
|
+
const routePath = routeMatch[1] ?? "";
|
|
41
|
+
const params = extractParams(routePath);
|
|
42
|
+
const surroundingStart = Math.max(0, routeMatch.index - 50);
|
|
43
|
+
const surroundingEnd = Math.min(block.length, routeMatch.index + 200);
|
|
44
|
+
const surrounding = block.slice(surroundingStart, surroundingEnd);
|
|
45
|
+
const hasLoader = /loader\s*:/.test(surrounding);
|
|
46
|
+
const hasGuard = /beforeEnter\s*:|beforeLeave\s*:/.test(surrounding);
|
|
47
|
+
return {
|
|
48
|
+
path: routePath,
|
|
49
|
+
name: surrounding.match(/name\s*:\s*["']([^"']+)["']/)?.[1],
|
|
50
|
+
hasLoader,
|
|
51
|
+
hasGuard,
|
|
52
|
+
params
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function extractRoutesFromBlock(block) {
|
|
56
|
+
const routes = [];
|
|
57
|
+
const routeObjRe = /path\s*:\s*["']([^"']+)["']/g;
|
|
58
|
+
let routeMatch;
|
|
59
|
+
while (true) {
|
|
60
|
+
routeMatch = routeObjRe.exec(block);
|
|
61
|
+
if (!routeMatch) break;
|
|
62
|
+
routes.push(parseRouteFromBlock(block, routeMatch));
|
|
63
|
+
}
|
|
64
|
+
return routes;
|
|
65
|
+
}
|
|
66
|
+
function extractRoutes(files, _cwd) {
|
|
67
|
+
const routes = [];
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
let code;
|
|
70
|
+
try {
|
|
71
|
+
code = fs.readFileSync(file, "utf-8");
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const routeArrayRe = /(?:createRouter\s*\(\s*\[|(?:const|let)\s+routes\s*(?::\s*RouteRecord\[\])?\s*=\s*\[)([\s\S]*?)\]/g;
|
|
76
|
+
let match;
|
|
77
|
+
while (true) {
|
|
78
|
+
match = routeArrayRe.exec(code);
|
|
79
|
+
if (!match) break;
|
|
80
|
+
const block = match[1] ?? "";
|
|
81
|
+
for (const route of extractRoutesFromBlock(block)) routes.push(route);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return routes;
|
|
85
|
+
}
|
|
86
|
+
function parseProps(propsStr) {
|
|
87
|
+
return propsStr.split(",").map((p) => p.trim().split(":")[0]?.split("=")[0]?.trim() ?? "").filter((p) => p && p !== "props");
|
|
88
|
+
}
|
|
89
|
+
function collectSignalNames(body) {
|
|
90
|
+
const signalNames = [];
|
|
91
|
+
const signalRe = /(?:const|let)\s+(\w+)\s*=\s*signal\s*[<(]/g;
|
|
92
|
+
let sigMatch;
|
|
93
|
+
while (true) {
|
|
94
|
+
sigMatch = signalRe.exec(body);
|
|
95
|
+
if (!sigMatch) break;
|
|
96
|
+
if (sigMatch[1]) signalNames.push(sigMatch[1]);
|
|
97
|
+
}
|
|
98
|
+
return signalNames;
|
|
99
|
+
}
|
|
100
|
+
function extractComponentsFromCode(code, relFile) {
|
|
101
|
+
const components = [];
|
|
102
|
+
const componentRe = /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?::\s*ComponentFn<[^>]+>\s*)?=?\s*\(?(?:\s*\{?\s*([^)]*?)\s*\}?\s*)?\)?\s*(?:=>|{)/g;
|
|
103
|
+
let match;
|
|
104
|
+
while (true) {
|
|
105
|
+
match = componentRe.exec(code);
|
|
106
|
+
if (!match) break;
|
|
107
|
+
const name = match[1] ?? "Unknown";
|
|
108
|
+
const props = parseProps(match[2] ?? "");
|
|
109
|
+
const bodyStart = match.index + match[0].length;
|
|
110
|
+
const signalNames = collectSignalNames(code.slice(bodyStart, Math.min(code.length, bodyStart + 2e3)));
|
|
111
|
+
components.push({
|
|
112
|
+
name,
|
|
113
|
+
file: relFile,
|
|
114
|
+
hasSignals: signalNames.length > 0,
|
|
115
|
+
signalNames,
|
|
116
|
+
props
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return components;
|
|
120
|
+
}
|
|
121
|
+
function extractComponents(files, _cwd) {
|
|
122
|
+
const components = [];
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
let code;
|
|
125
|
+
try {
|
|
126
|
+
code = fs.readFileSync(file, "utf-8");
|
|
127
|
+
} catch {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const relFile = path.relative(_cwd, file);
|
|
131
|
+
for (const comp of extractComponentsFromCode(code, relFile)) components.push(comp);
|
|
132
|
+
}
|
|
133
|
+
return components;
|
|
134
|
+
}
|
|
135
|
+
function extractIslands(files, cwd) {
|
|
136
|
+
const islands = [];
|
|
137
|
+
for (const file of files) {
|
|
138
|
+
let code;
|
|
139
|
+
try {
|
|
140
|
+
code = fs.readFileSync(file, "utf-8");
|
|
141
|
+
} catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const islandRe = /island\s*\(\s*\(\)\s*=>\s*import\(.+?\)\s*,\s*\{[^}]*name\s*:\s*["']([^"']+)["'][^}]*?(?:hydrate\s*:\s*["']([^"']+)["'])?[^}]*\}/g;
|
|
145
|
+
let match;
|
|
146
|
+
while (true) {
|
|
147
|
+
match = islandRe.exec(code);
|
|
148
|
+
if (!match) break;
|
|
149
|
+
if (match[1]) islands.push({
|
|
150
|
+
name: match[1],
|
|
151
|
+
file: path.relative(cwd, file),
|
|
152
|
+
hydrate: match[2] ?? "load"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return islands;
|
|
157
|
+
}
|
|
158
|
+
function extractParams(routePath) {
|
|
159
|
+
const params = [];
|
|
160
|
+
const paramRe = /:(\w+)\??/g;
|
|
161
|
+
let match;
|
|
162
|
+
while (true) {
|
|
163
|
+
match = paramRe.exec(routePath);
|
|
164
|
+
if (!match) break;
|
|
165
|
+
if (match[1]) params.push(match[1]);
|
|
166
|
+
}
|
|
167
|
+
return params;
|
|
168
|
+
}
|
|
169
|
+
function readVersion(cwd) {
|
|
170
|
+
try {
|
|
171
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"));
|
|
172
|
+
const deps = {
|
|
173
|
+
...pkg.dependencies,
|
|
174
|
+
...pkg.devDependencies
|
|
175
|
+
};
|
|
176
|
+
for (const [name, version] of Object.entries(deps)) if (name.startsWith("@pyreon/") && typeof version === "string") return version.replace(/^[\^~]/, "");
|
|
177
|
+
return pkg.version || "unknown";
|
|
178
|
+
} catch {
|
|
179
|
+
return "unknown";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const tsxExtensions = new Set([
|
|
183
|
+
".tsx",
|
|
184
|
+
".jsx",
|
|
185
|
+
".ts",
|
|
186
|
+
".js"
|
|
187
|
+
]);
|
|
188
|
+
const tsxIgnoreDirs = new Set([
|
|
189
|
+
"node_modules",
|
|
190
|
+
"dist",
|
|
191
|
+
"lib",
|
|
192
|
+
".pyreon",
|
|
193
|
+
".git",
|
|
194
|
+
"build"
|
|
195
|
+
]);
|
|
196
|
+
function shouldSkipEntry(entry) {
|
|
197
|
+
if (!entry.isDirectory()) return false;
|
|
198
|
+
return entry.name.startsWith(".") || tsxIgnoreDirs.has(entry.name);
|
|
199
|
+
}
|
|
200
|
+
function walkTsxFiles(dir, results) {
|
|
201
|
+
let entries;
|
|
202
|
+
try {
|
|
203
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
204
|
+
} catch {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
if (shouldSkipEntry(entry)) continue;
|
|
209
|
+
const fullPath = path.join(dir, entry.name);
|
|
210
|
+
if (entry.isDirectory()) walkTsxFiles(fullPath, results);
|
|
211
|
+
else if (entry.isFile() && tsxExtensions.has(path.extname(entry.name))) results.push(fullPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function collectTsxFiles(cwd) {
|
|
215
|
+
const results = [];
|
|
216
|
+
walkTsxFiles(cwd, results);
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
function ensureGitignore(cwd) {
|
|
220
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
221
|
+
try {
|
|
222
|
+
const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
|
|
223
|
+
if (!content.includes(".pyreon/") && !content.includes(".pyreon\n")) {
|
|
224
|
+
const addition = content.endsWith("\n") ? ".pyreon/\n" : "\n.pyreon/\n";
|
|
225
|
+
fs.appendFileSync(gitignorePath, addition);
|
|
226
|
+
}
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/doctor.ts
|
|
232
|
+
/**
|
|
233
|
+
* pyreon doctor — project-wide health check for AI-friendly development
|
|
234
|
+
*
|
|
235
|
+
* Runs a pipeline of checks:
|
|
236
|
+
* 1. React pattern detection (imports, hooks, JSX attributes)
|
|
237
|
+
* 2. Import source validation (@pyreon/* vs react/vue)
|
|
238
|
+
* 3. Common Pyreon mistakes (signal without call, key vs by, etc.)
|
|
239
|
+
*
|
|
240
|
+
* Output modes:
|
|
241
|
+
* - Human-readable (default): colored terminal output
|
|
242
|
+
* - JSON (--json): structured output for AI agent consumption
|
|
243
|
+
* - CI (--ci): exits with code 1 on any error
|
|
244
|
+
*
|
|
245
|
+
* Fix mode (--fix): auto-applies safe transforms via migrateReactCode
|
|
246
|
+
*/
|
|
247
|
+
async function doctor(options) {
|
|
248
|
+
const startTime = performance.now();
|
|
249
|
+
const result = runChecks(collectSourceFiles(options.cwd), options);
|
|
250
|
+
const elapsed = Math.round(performance.now() - startTime);
|
|
251
|
+
if (options.json) printJson(result);
|
|
252
|
+
else printHuman(result, elapsed);
|
|
253
|
+
return result.summary.totalErrors;
|
|
254
|
+
}
|
|
255
|
+
const sourceExtensions = new Set([
|
|
256
|
+
".tsx",
|
|
257
|
+
".jsx",
|
|
258
|
+
".ts",
|
|
259
|
+
".js"
|
|
260
|
+
]);
|
|
261
|
+
const sourceIgnoreDirs = new Set([
|
|
262
|
+
"node_modules",
|
|
263
|
+
"dist",
|
|
264
|
+
"lib",
|
|
265
|
+
".pyreon",
|
|
266
|
+
".git",
|
|
267
|
+
".next",
|
|
268
|
+
"build"
|
|
269
|
+
]);
|
|
270
|
+
function shouldSkipDirEntry(entry) {
|
|
271
|
+
if (!entry.isDirectory()) return false;
|
|
272
|
+
return entry.name.startsWith(".") || sourceIgnoreDirs.has(entry.name);
|
|
273
|
+
}
|
|
274
|
+
function walkSourceFiles(dir, results) {
|
|
275
|
+
let entries;
|
|
276
|
+
try {
|
|
277
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
278
|
+
} catch {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
for (const entry of entries) {
|
|
282
|
+
if (shouldSkipDirEntry(entry)) continue;
|
|
283
|
+
const fullPath = path.join(dir, entry.name);
|
|
284
|
+
if (entry.isDirectory()) walkSourceFiles(fullPath, results);
|
|
285
|
+
else if (entry.isFile() && sourceExtensions.has(path.extname(entry.name))) results.push(fullPath);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function collectSourceFiles(cwd) {
|
|
289
|
+
const results = [];
|
|
290
|
+
walkSourceFiles(cwd, results);
|
|
291
|
+
return results;
|
|
292
|
+
}
|
|
293
|
+
function checkFileWithFix(file, relPath) {
|
|
294
|
+
let code;
|
|
295
|
+
try {
|
|
296
|
+
code = fs.readFileSync(file, "utf-8");
|
|
297
|
+
} catch {
|
|
298
|
+
return {
|
|
299
|
+
result: null,
|
|
300
|
+
fixCount: 0
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (!hasReactPatterns(code)) return {
|
|
304
|
+
result: null,
|
|
305
|
+
fixCount: 0
|
|
306
|
+
};
|
|
307
|
+
const migrated = migrateReactCode(code, relPath);
|
|
308
|
+
if (migrated.changes.length > 0) fs.writeFileSync(file, migrated.code, "utf-8");
|
|
309
|
+
const remaining = detectReactPatterns(migrated.code, relPath);
|
|
310
|
+
if (remaining.length > 0 || migrated.changes.length > 0) return {
|
|
311
|
+
result: {
|
|
312
|
+
file: relPath,
|
|
313
|
+
diagnostics: remaining,
|
|
314
|
+
fixed: migrated.changes.length > 0
|
|
315
|
+
},
|
|
316
|
+
fixCount: migrated.changes.length
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
result: null,
|
|
320
|
+
fixCount: 0
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function checkFileDetectOnly(file, relPath) {
|
|
324
|
+
let code;
|
|
325
|
+
try {
|
|
326
|
+
code = fs.readFileSync(file, "utf-8");
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
if (!hasReactPatterns(code)) return null;
|
|
331
|
+
const diagnostics = detectReactPatterns(code, relPath);
|
|
332
|
+
if (diagnostics.length > 0) return {
|
|
333
|
+
file: relPath,
|
|
334
|
+
diagnostics,
|
|
335
|
+
fixed: false
|
|
336
|
+
};
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
function runChecks(files, options) {
|
|
340
|
+
const fileResults = [];
|
|
341
|
+
let totalFixed = 0;
|
|
342
|
+
for (const file of files) {
|
|
343
|
+
const relPath = path.relative(options.cwd, file);
|
|
344
|
+
if (options.fix) {
|
|
345
|
+
const { result, fixCount } = checkFileWithFix(file, relPath);
|
|
346
|
+
totalFixed += fixCount;
|
|
347
|
+
if (result) fileResults.push(result);
|
|
348
|
+
} else {
|
|
349
|
+
const result = checkFileDetectOnly(file, relPath);
|
|
350
|
+
if (result) fileResults.push(result);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const totalErrors = fileResults.reduce((sum, f) => sum + f.diagnostics.length, 0);
|
|
354
|
+
const totalFixable = fileResults.reduce((sum, f) => sum + f.diagnostics.filter((d) => d.fixable).length, 0);
|
|
355
|
+
return {
|
|
356
|
+
passed: totalErrors === 0,
|
|
357
|
+
files: fileResults,
|
|
358
|
+
summary: {
|
|
359
|
+
filesScanned: files.length,
|
|
360
|
+
filesWithIssues: fileResults.length,
|
|
361
|
+
totalErrors,
|
|
362
|
+
totalFixable,
|
|
363
|
+
totalFixed
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function printJson(result) {
|
|
368
|
+
console.log(JSON.stringify(result, null, 2));
|
|
369
|
+
}
|
|
370
|
+
function printFileResult(fileResult) {
|
|
371
|
+
if (fileResult.diagnostics.length === 0) return;
|
|
372
|
+
console.log(` ${fileResult.file}${fileResult.fixed ? " (partially fixed)" : ""}`);
|
|
373
|
+
for (const diag of fileResult.diagnostics) {
|
|
374
|
+
const fixTag = diag.fixable ? " [fixable]" : "";
|
|
375
|
+
console.log(` ${diag.line}:${diag.column} — ${diag.message}${fixTag}`);
|
|
376
|
+
console.log(` Current: ${diag.current}`);
|
|
377
|
+
console.log(` Suggested: ${diag.suggested}`);
|
|
378
|
+
console.log("");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function printSummary(summary) {
|
|
382
|
+
console.log(` ${summary.totalErrors} issue${summary.totalErrors === 1 ? "" : "s"} in ${summary.filesWithIssues} file${summary.filesWithIssues === 1 ? "" : "s"}`);
|
|
383
|
+
if (summary.totalFixable > 0) console.log(` ${summary.totalFixable} auto-fixable — run 'pyreon doctor --fix' to apply`);
|
|
384
|
+
console.log("");
|
|
385
|
+
}
|
|
386
|
+
function printHuman(result, elapsed) {
|
|
387
|
+
const { summary } = result;
|
|
388
|
+
console.log("");
|
|
389
|
+
console.log(` Pyreon Doctor — scanned ${summary.filesScanned} files in ${elapsed}ms`);
|
|
390
|
+
console.log("");
|
|
391
|
+
if (result.passed && summary.totalFixed === 0) {
|
|
392
|
+
console.log(" ✓ No issues found. Your code is Pyreon-native!");
|
|
393
|
+
console.log("");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (summary.totalFixed > 0) {
|
|
397
|
+
console.log(` ✓ Auto-fixed ${summary.totalFixed} issue${summary.totalFixed === 1 ? "" : "s"}`);
|
|
398
|
+
console.log("");
|
|
399
|
+
}
|
|
400
|
+
for (const fileResult of result.files) printFileResult(fileResult);
|
|
401
|
+
printSummary(summary);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/index.ts
|
|
406
|
+
/**
|
|
407
|
+
* @pyreon/cli — Developer tools for Pyreon
|
|
408
|
+
*
|
|
409
|
+
* Commands:
|
|
410
|
+
* pyreon doctor [--fix] [--json] — Scan project for React patterns, bad imports, etc.
|
|
411
|
+
* pyreon context — Generate .pyreon/context.json for AI tools
|
|
412
|
+
*/
|
|
413
|
+
const args = process.argv.slice(2);
|
|
414
|
+
const command = args[0];
|
|
415
|
+
function printUsage() {
|
|
416
|
+
console.log(`
|
|
417
|
+
pyreon <command> [options]
|
|
418
|
+
|
|
419
|
+
Commands:
|
|
420
|
+
doctor [--fix] [--json] [--ci] Scan for React patterns, bad imports, and common mistakes
|
|
421
|
+
context [--out <path>] Generate .pyreon/context.json for AI tools
|
|
422
|
+
|
|
423
|
+
Options:
|
|
424
|
+
--help Show this help message
|
|
425
|
+
--version Show version
|
|
426
|
+
`);
|
|
427
|
+
}
|
|
428
|
+
async function main() {
|
|
429
|
+
if (!command || command === "--help" || command === "-h") {
|
|
430
|
+
printUsage();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (command === "--version" || command === "-v") {
|
|
434
|
+
console.log("0.4.0");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (command === "doctor") {
|
|
438
|
+
const options = {
|
|
439
|
+
fix: args.includes("--fix"),
|
|
440
|
+
json: args.includes("--json"),
|
|
441
|
+
ci: args.includes("--ci"),
|
|
442
|
+
cwd: process.cwd()
|
|
443
|
+
};
|
|
444
|
+
const exitCode = await doctor(options);
|
|
445
|
+
if (options.ci && exitCode > 0) process.exit(1);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (command === "context") {
|
|
449
|
+
const outIdx = args.indexOf("--out");
|
|
450
|
+
const outPath = outIdx >= 0 ? args[outIdx + 1] : void 0;
|
|
451
|
+
await generateContext({
|
|
452
|
+
cwd: process.cwd(),
|
|
453
|
+
outPath
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
console.error(`Unknown command: ${command}`);
|
|
458
|
+
printUsage();
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
main().catch((err) => {
|
|
462
|
+
console.error(err);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
//#endregion
|
|
467
|
+
export { doctor, generateContext };
|
|
468
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/doctor.ts","../src/index.ts"],"sourcesContent":["/**\n * pyreon context — generates .pyreon/context.json for AI tool consumption\n *\n * Scans the project to extract:\n * - Route definitions (paths, params, loaders, guards)\n * - Component inventory (file, props, signals)\n * - Island declarations (name, hydration strategy)\n * - Framework version\n */\n\nimport * as fs from \"node:fs\"\nimport * as path from \"node:path\"\n\nexport interface ContextOptions {\n cwd: string\n outPath?: string | undefined\n}\n\nexport interface RouteInfo {\n path: string\n name?: string | undefined\n component?: string | undefined\n hasLoader: boolean\n hasGuard: boolean\n params: string[]\n children?: RouteInfo[] | undefined\n}\n\nexport interface ComponentInfo {\n name: string\n file: string\n hasSignals: boolean\n signalNames: string[]\n props: string[]\n}\n\nexport interface IslandInfo {\n name: string\n file: string\n hydrate: string\n}\n\nexport interface ProjectContext {\n framework: \"pyreon\"\n version: string\n generatedAt: string\n routes: RouteInfo[]\n components: ComponentInfo[]\n islands: IslandInfo[]\n}\n\nexport async function generateContext(options: ContextOptions): Promise<ProjectContext> {\n const version = readVersion(options.cwd)\n const sourceFiles = collectTsxFiles(options.cwd)\n\n const routes = extractRoutes(sourceFiles, options.cwd)\n const components = extractComponents(sourceFiles, options.cwd)\n const islands = extractIslands(sourceFiles, options.cwd)\n\n const context: ProjectContext = {\n framework: \"pyreon\",\n version,\n generatedAt: new Date().toISOString(),\n routes,\n components,\n islands,\n }\n\n // Write to .pyreon/context.json\n const outDir = options.outPath ? path.dirname(options.outPath) : path.join(options.cwd, \".pyreon\")\n const outFile = options.outPath ?? path.join(outDir, \"context.json\")\n\n if (!fs.existsSync(outDir)) {\n fs.mkdirSync(outDir, { recursive: true })\n }\n fs.writeFileSync(outFile, JSON.stringify(context, null, 2), \"utf-8\")\n\n // Ensure .pyreon/ is in .gitignore\n ensureGitignore(options.cwd)\n\n const relOut = path.relative(options.cwd, outFile)\n console.log(\n ` ✓ Generated ${relOut} (${components.length} components, ${routes.length} routes, ${islands.length} islands)`,\n )\n\n return context\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Extractors\n// ═══════════════════════════════════════════════════════════════════════════════\n\nfunction parseRouteFromBlock(block: string, routeMatch: RegExpExecArray): RouteInfo {\n const routePath = routeMatch[1] ?? \"\"\n const params = extractParams(routePath)\n\n const surroundingStart = Math.max(0, routeMatch.index - 50)\n const surroundingEnd = Math.min(block.length, routeMatch.index + 200)\n const surrounding = block.slice(surroundingStart, surroundingEnd)\n\n const hasLoader = /loader\\s*:/.test(surrounding)\n const hasGuard = /beforeEnter\\s*:|beforeLeave\\s*:/.test(surrounding)\n const nameMatch = surrounding.match(/name\\s*:\\s*[\"']([^\"']+)[\"']/)\n\n return {\n path: routePath,\n name: nameMatch?.[1],\n hasLoader,\n hasGuard,\n params,\n }\n}\n\nfunction extractRoutesFromBlock(block: string): RouteInfo[] {\n const routes: RouteInfo[] = []\n const routeObjRe = /path\\s*:\\s*[\"']([^\"']+)[\"']/g\n let routeMatch: RegExpExecArray | null\n while (true) {\n routeMatch = routeObjRe.exec(block)\n if (!routeMatch) break\n routes.push(parseRouteFromBlock(block, routeMatch))\n }\n return routes\n}\n\nfunction extractRoutes(files: string[], _cwd: string): RouteInfo[] {\n const routes: RouteInfo[] = []\n\n for (const file of files) {\n let code: string\n try {\n code = fs.readFileSync(file, \"utf-8\")\n } catch {\n continue\n }\n\n const routeArrayRe =\n /(?:createRouter\\s*\\(\\s*\\[|(?:const|let)\\s+routes\\s*(?::\\s*RouteRecord\\[\\])?\\s*=\\s*\\[)([\\s\\S]*?)\\]/g\n let match: RegExpExecArray | null\n while (true) {\n match = routeArrayRe.exec(code)\n if (!match) break\n const block = match[1] ?? \"\"\n for (const route of extractRoutesFromBlock(block)) {\n routes.push(route)\n }\n }\n }\n\n return routes\n}\n\nfunction parseProps(propsStr: string): string[] {\n return propsStr\n .split(\",\")\n .map((p) => p.trim().split(\":\")[0]?.split(\"=\")[0]?.trim() ?? \"\")\n .filter((p) => p && p !== \"props\")\n}\n\nfunction collectSignalNames(body: string): string[] {\n const signalNames: string[] = []\n const signalRe = /(?:const|let)\\s+(\\w+)\\s*=\\s*signal\\s*[<(]/g\n let sigMatch: RegExpExecArray | null\n while (true) {\n sigMatch = signalRe.exec(body)\n if (!sigMatch) break\n if (sigMatch[1]) signalNames.push(sigMatch[1])\n }\n return signalNames\n}\n\nfunction extractComponentsFromCode(code: string, relFile: string): ComponentInfo[] {\n const components: ComponentInfo[] = []\n const componentRe =\n /(?:export\\s+)?(?:const|function)\\s+([A-Z]\\w*)\\s*(?::\\s*ComponentFn<[^>]+>\\s*)?=?\\s*\\(?(?:\\s*\\{?\\s*([^)]*?)\\s*\\}?\\s*)?\\)?\\s*(?:=>|{)/g\n let match: RegExpExecArray | null\n\n while (true) {\n match = componentRe.exec(code)\n if (!match) break\n const name = match[1] ?? \"Unknown\"\n const props = parseProps(match[2] ?? \"\")\n\n const bodyStart = match.index + match[0].length\n const body = code.slice(bodyStart, Math.min(code.length, bodyStart + 2000))\n const signalNames = collectSignalNames(body)\n\n components.push({\n name,\n file: relFile,\n hasSignals: signalNames.length > 0,\n signalNames,\n props,\n })\n }\n\n return components\n}\n\nfunction extractComponents(files: string[], _cwd: string): ComponentInfo[] {\n const components: ComponentInfo[] = []\n\n for (const file of files) {\n let code: string\n try {\n code = fs.readFileSync(file, \"utf-8\")\n } catch {\n continue\n }\n\n const relFile = path.relative(_cwd, file)\n for (const comp of extractComponentsFromCode(code, relFile)) {\n components.push(comp)\n }\n }\n\n return components\n}\n\nfunction extractIslands(files: string[], cwd: string): IslandInfo[] {\n const islands: IslandInfo[] = []\n\n for (const file of files) {\n let code: string\n try {\n code = fs.readFileSync(file, \"utf-8\")\n } catch {\n continue\n }\n\n const islandRe =\n /island\\s*\\(\\s*\\(\\)\\s*=>\\s*import\\(.+?\\)\\s*,\\s*\\{[^}]*name\\s*:\\s*[\"']([^\"']+)[\"'][^}]*?(?:hydrate\\s*:\\s*[\"']([^\"']+)[\"'])?[^}]*\\}/g\n let match: RegExpExecArray | null\n while (true) {\n match = islandRe.exec(code)\n if (!match) break\n if (match[1]) {\n islands.push({\n name: match[1],\n file: path.relative(cwd, file),\n hydrate: match[2] ?? \"load\",\n })\n }\n }\n }\n\n return islands\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Helpers\n// ═══════════════════════════════════════════════════════════════════════════════\n\nfunction extractParams(routePath: string): string[] {\n const params: string[] = []\n const paramRe = /:(\\w+)\\??/g\n let match: RegExpExecArray | null\n while (true) {\n match = paramRe.exec(routePath)\n if (!match) break\n if (match[1]) params.push(match[1])\n }\n return params\n}\n\nfunction readVersion(cwd: string): string {\n try {\n const pkg = JSON.parse(fs.readFileSync(path.join(cwd, \"package.json\"), \"utf-8\"))\n const deps: Record<string, unknown> = { ...pkg.dependencies, ...pkg.devDependencies }\n for (const [name, version] of Object.entries(deps)) {\n if (name.startsWith(\"@pyreon/\") && typeof version === \"string\") {\n return version.replace(/^[\\^~]/, \"\")\n }\n }\n return (pkg.version as string) || \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n\nconst tsxExtensions = new Set([\".tsx\", \".jsx\", \".ts\", \".js\"])\nconst tsxIgnoreDirs = new Set([\"node_modules\", \"dist\", \"lib\", \".pyreon\", \".git\", \"build\"])\n\nfunction shouldSkipEntry(entry: fs.Dirent): boolean {\n if (!entry.isDirectory()) return false\n return entry.name.startsWith(\".\") || tsxIgnoreDirs.has(entry.name)\n}\n\nfunction walkTsxFiles(dir: string, results: string[]): void {\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n\n for (const entry of entries) {\n if (shouldSkipEntry(entry)) continue\n\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n walkTsxFiles(fullPath, results)\n } else if (entry.isFile() && tsxExtensions.has(path.extname(entry.name))) {\n results.push(fullPath)\n }\n }\n}\n\nfunction collectTsxFiles(cwd: string): string[] {\n const results: string[] = []\n walkTsxFiles(cwd, results)\n return results\n}\n\nfunction ensureGitignore(cwd: string): void {\n const gitignorePath = path.join(cwd, \".gitignore\")\n try {\n const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, \"utf-8\") : \"\"\n\n if (!content.includes(\".pyreon/\") && !content.includes(\".pyreon\\n\")) {\n const addition = content.endsWith(\"\\n\") ? \".pyreon/\\n\" : \"\\n.pyreon/\\n\"\n fs.appendFileSync(gitignorePath, addition)\n }\n } catch {\n // Ignore errors with .gitignore\n }\n}\n","/**\n * pyreon doctor — project-wide health check for AI-friendly development\n *\n * Runs a pipeline of checks:\n * 1. React pattern detection (imports, hooks, JSX attributes)\n * 2. Import source validation (@pyreon/* vs react/vue)\n * 3. Common Pyreon mistakes (signal without call, key vs by, etc.)\n *\n * Output modes:\n * - Human-readable (default): colored terminal output\n * - JSON (--json): structured output for AI agent consumption\n * - CI (--ci): exits with code 1 on any error\n *\n * Fix mode (--fix): auto-applies safe transforms via migrateReactCode\n */\n\nimport * as fs from \"node:fs\"\nimport * as path from \"node:path\"\nimport {\n detectReactPatterns,\n hasReactPatterns,\n migrateReactCode,\n type ReactDiagnostic,\n} from \"@pyreon/compiler\"\n\nexport interface DoctorOptions {\n fix: boolean\n json: boolean\n ci: boolean\n cwd: string\n}\n\ninterface FileResult {\n file: string\n diagnostics: ReactDiagnostic[]\n fixed: boolean\n}\n\ninterface DoctorResult {\n passed: boolean\n files: FileResult[]\n summary: {\n filesScanned: number\n filesWithIssues: number\n totalErrors: number\n totalFixable: number\n totalFixed: number\n }\n}\n\nexport async function doctor(options: DoctorOptions): Promise<number> {\n const startTime = performance.now()\n const files = collectSourceFiles(options.cwd)\n const result = runChecks(files, options)\n const elapsed = Math.round(performance.now() - startTime)\n\n if (options.json) {\n printJson(result)\n } else {\n printHuman(result, elapsed)\n }\n\n return result.summary.totalErrors\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// File collection\n// ═══════════════════════════════════════════════════════════════════════════════\n\nconst sourceExtensions = new Set([\".tsx\", \".jsx\", \".ts\", \".js\"])\nconst sourceIgnoreDirs = new Set([\n \"node_modules\",\n \"dist\",\n \"lib\",\n \".pyreon\",\n \".git\",\n \".next\",\n \"build\",\n])\n\nfunction shouldSkipDirEntry(entry: fs.Dirent): boolean {\n if (!entry.isDirectory()) return false\n return entry.name.startsWith(\".\") || sourceIgnoreDirs.has(entry.name)\n}\n\nfunction walkSourceFiles(dir: string, results: string[]): void {\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n\n for (const entry of entries) {\n if (shouldSkipDirEntry(entry)) continue\n\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n walkSourceFiles(fullPath, results)\n } else if (entry.isFile() && sourceExtensions.has(path.extname(entry.name))) {\n results.push(fullPath)\n }\n }\n}\n\nfunction collectSourceFiles(cwd: string): string[] {\n const results: string[] = []\n walkSourceFiles(cwd, results)\n return results\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Check pipeline\n// ═══════════════════════════════════════════════════════════════════════════════\n\nfunction checkFileWithFix(\n file: string,\n relPath: string,\n): { result: FileResult | null; fixCount: number } {\n let code: string\n try {\n code = fs.readFileSync(file, \"utf-8\")\n } catch {\n return { result: null, fixCount: 0 }\n }\n\n if (!hasReactPatterns(code)) return { result: null, fixCount: 0 }\n\n const migrated = migrateReactCode(code, relPath)\n if (migrated.changes.length > 0) {\n fs.writeFileSync(file, migrated.code, \"utf-8\")\n }\n const remaining = detectReactPatterns(migrated.code, relPath)\n if (remaining.length > 0 || migrated.changes.length > 0) {\n return {\n result: { file: relPath, diagnostics: remaining, fixed: migrated.changes.length > 0 },\n fixCount: migrated.changes.length,\n }\n }\n return { result: null, fixCount: 0 }\n}\n\nfunction checkFileDetectOnly(file: string, relPath: string): FileResult | null {\n let code: string\n try {\n code = fs.readFileSync(file, \"utf-8\")\n } catch {\n return null\n }\n\n if (!hasReactPatterns(code)) return null\n\n const diagnostics = detectReactPatterns(code, relPath)\n if (diagnostics.length > 0) {\n return { file: relPath, diagnostics, fixed: false }\n }\n return null\n}\n\nfunction runChecks(files: string[], options: DoctorOptions): DoctorResult {\n const fileResults: FileResult[] = []\n let totalFixed = 0\n\n for (const file of files) {\n const relPath = path.relative(options.cwd, file)\n\n if (options.fix) {\n const { result, fixCount } = checkFileWithFix(file, relPath)\n totalFixed += fixCount\n if (result) fileResults.push(result)\n } else {\n const result = checkFileDetectOnly(file, relPath)\n if (result) fileResults.push(result)\n }\n }\n\n const totalErrors = fileResults.reduce((sum, f) => sum + f.diagnostics.length, 0)\n const totalFixable = fileResults.reduce(\n (sum, f) => sum + f.diagnostics.filter((d) => d.fixable).length,\n 0,\n )\n\n return {\n passed: totalErrors === 0,\n files: fileResults,\n summary: {\n filesScanned: files.length,\n filesWithIssues: fileResults.length,\n totalErrors,\n totalFixable,\n totalFixed,\n },\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Output formatters\n// ═══════════════════════════════════════════════════════════════════════════════\n\nfunction printJson(result: DoctorResult): void {\n console.log(JSON.stringify(result, null, 2))\n}\n\nfunction printFileResult(fileResult: FileResult): void {\n if (fileResult.diagnostics.length === 0) return\n\n console.log(` ${fileResult.file}${fileResult.fixed ? \" (partially fixed)\" : \"\"}`)\n\n for (const diag of fileResult.diagnostics) {\n const fixTag = diag.fixable ? \" [fixable]\" : \"\"\n console.log(` ${diag.line}:${diag.column} — ${diag.message}${fixTag}`)\n console.log(` Current: ${diag.current}`)\n console.log(` Suggested: ${diag.suggested}`)\n console.log(\"\")\n }\n}\n\nfunction printSummary(summary: DoctorResult[\"summary\"]): void {\n console.log(\n ` ${summary.totalErrors} issue${summary.totalErrors === 1 ? \"\" : \"s\"} in ${summary.filesWithIssues} file${summary.filesWithIssues === 1 ? \"\" : \"s\"}`,\n )\n if (summary.totalFixable > 0) {\n console.log(` ${summary.totalFixable} auto-fixable — run 'pyreon doctor --fix' to apply`)\n }\n console.log(\"\")\n}\n\nfunction printHuman(result: DoctorResult, elapsed: number): void {\n const { summary } = result\n\n console.log(\"\")\n console.log(` Pyreon Doctor — scanned ${summary.filesScanned} files in ${elapsed}ms`)\n console.log(\"\")\n\n if (result.passed && summary.totalFixed === 0) {\n console.log(\" ✓ No issues found. Your code is Pyreon-native!\")\n console.log(\"\")\n return\n }\n\n if (summary.totalFixed > 0) {\n console.log(` ✓ Auto-fixed ${summary.totalFixed} issue${summary.totalFixed === 1 ? \"\" : \"s\"}`)\n console.log(\"\")\n }\n\n for (const fileResult of result.files) {\n printFileResult(fileResult)\n }\n\n printSummary(summary)\n}\n","#!/usr/bin/env node\n\n/**\n * @pyreon/cli — Developer tools for Pyreon\n *\n * Commands:\n * pyreon doctor [--fix] [--json] — Scan project for React patterns, bad imports, etc.\n * pyreon context — Generate .pyreon/context.json for AI tools\n */\n\nimport { generateContext } from \"./context\"\nimport { type DoctorOptions, doctor } from \"./doctor\"\n\nconst args = process.argv.slice(2)\nconst command = args[0]\n\nfunction printUsage(): void {\n console.log(`\n pyreon <command> [options]\n\n Commands:\n doctor [--fix] [--json] [--ci] Scan for React patterns, bad imports, and common mistakes\n context [--out <path>] Generate .pyreon/context.json for AI tools\n\n Options:\n --help Show this help message\n --version Show version\n`)\n}\n\nasync function main(): Promise<void> {\n if (!command || command === \"--help\" || command === \"-h\") {\n printUsage()\n return\n }\n\n if (command === \"--version\" || command === \"-v\") {\n console.log(\"0.4.0\")\n return\n }\n\n if (command === \"doctor\") {\n const options: DoctorOptions = {\n fix: args.includes(\"--fix\"),\n json: args.includes(\"--json\"),\n ci: args.includes(\"--ci\"),\n cwd: process.cwd(),\n }\n const exitCode = await doctor(options)\n if (options.ci && exitCode > 0) {\n process.exit(1)\n }\n return\n }\n\n if (command === \"context\") {\n const outIdx = args.indexOf(\"--out\")\n const outPath = outIdx >= 0 ? args[outIdx + 1] : undefined\n await generateContext({ cwd: process.cwd(), outPath })\n return\n }\n\n console.error(`Unknown command: ${command}`)\n printUsage()\n process.exit(1)\n}\n\nmain().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n\nexport type { ContextOptions, ProjectContext } from \"./context\"\nexport type { DoctorOptions } from \"./doctor\"\nexport { doctor, generateContext }\n"],"mappings":"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"}
|