infernoflow 0.10.4 → 0.10.6
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 +15 -0
- package/bin/infernoflow.mjs +3 -0
- package/lib/commands/adopt.mjs +273 -3
- package/lib/commands/init.mjs +53 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,18 @@ Non-interactive adoption:
|
|
|
56
56
|
infernoflow init --adopt --yes
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
Override detected stack during adoption:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
infernoflow init --adopt --lang ts --framework angular --project-type frontend
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
C# / ASP.NET example:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
infernoflow init --adopt --lang cs --framework aspnet --project-type backend --report-human-only
|
|
69
|
+
```
|
|
70
|
+
|
|
59
71
|
JSON report for CI/logging:
|
|
60
72
|
|
|
61
73
|
```bash
|
|
@@ -79,6 +91,8 @@ What adoption creates:
|
|
|
79
91
|
- `inferno/capabilities.json` (inferred registry)
|
|
80
92
|
- `inferno/scenarios/adoption_baseline.json` (coverage baseline)
|
|
81
93
|
- `inferno/adoption_profile.json` (detected components, display fields, external libraries, UI layout, styling hints)
|
|
94
|
+
- `inferno/adoption_profile.json` (detected components, display fields, external libraries, UI layout, styling hints, API call map)
|
|
95
|
+
- `inferno/context-state.json` (saved development profile: language/framework/project type)
|
|
82
96
|
- `inferno/CHANGELOG.md` (adoption entry)
|
|
83
97
|
|
|
84
98
|
Safety:
|
|
@@ -162,6 +176,7 @@ infernoflow doc-gate --json
|
|
|
162
176
|
infernoflow init --force # overwrite existing files
|
|
163
177
|
infernoflow init --yes # skip prompts, use defaults
|
|
164
178
|
infernoflow init --adopt # infer baseline from existing project
|
|
179
|
+
infernoflow init --adopt --lang ts --framework react --project-type frontend
|
|
165
180
|
infernoflow init --adopt --report-human-only
|
|
166
181
|
infernoflow suggest "..." # describe what changed
|
|
167
182
|
infernoflow implement "..." --mode both
|
package/bin/infernoflow.mjs
CHANGED
|
@@ -45,6 +45,9 @@ ${formatCommandsHelp()}
|
|
|
45
45
|
|
|
46
46
|
${bold("init options:")}
|
|
47
47
|
--adopt Infer capabilities from an existing codebase
|
|
48
|
+
--lang <name> Override detected language (e.g. ts, js, py)
|
|
49
|
+
--framework <name> Override detected framework (e.g. react, angular, express)
|
|
50
|
+
--project-type <t> Override project type (frontend|backend|fullstack|cli|library)
|
|
48
51
|
--report-json Print inferred adoption report as JSON
|
|
49
52
|
--report-json-only Print JSON report only (no human-readable logs)
|
|
50
53
|
--report-human-only Print only human-readable adoption report (no JSON block)
|
package/lib/commands/adopt.mjs
CHANGED
|
@@ -43,7 +43,7 @@ export function discoverCapabilities(cwd) {
|
|
|
43
43
|
|
|
44
44
|
function collectCodeFiles(cwd) {
|
|
45
45
|
const files = [];
|
|
46
|
-
const roots = ["src", "server", "app", "backend", "frontend", "api"];
|
|
46
|
+
const roots = ["src", "server", "app", "backend", "frontend", "api", "Controllers"];
|
|
47
47
|
for (const r of roots) {
|
|
48
48
|
const root = path.join(cwd, r);
|
|
49
49
|
if (!fs.existsSync(root)) continue;
|
|
@@ -55,12 +55,19 @@ function collectCodeFiles(cwd) {
|
|
|
55
55
|
if (entry.isDirectory()) {
|
|
56
56
|
if (["node_modules", ".git", "dist", "build"].includes(entry.name)) continue;
|
|
57
57
|
stack.push(p);
|
|
58
|
-
} else if (/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm)$/.test(entry.name)) {
|
|
58
|
+
} else if (/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm|cs|csproj)$/.test(entry.name)) {
|
|
59
59
|
files.push(p);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
// Include common root-level .NET entry files without scanning the whole repo.
|
|
65
|
+
for (const entry of fs.readdirSync(cwd, { withFileTypes: true })) {
|
|
66
|
+
if (!entry.isFile()) continue;
|
|
67
|
+
if (/^(Program\.cs|.+\.csproj)$/i.test(entry.name)) {
|
|
68
|
+
files.push(path.join(cwd, entry.name));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
64
71
|
return files;
|
|
65
72
|
}
|
|
66
73
|
|
|
@@ -218,7 +225,248 @@ function detectUiLayout(files) {
|
|
|
218
225
|
};
|
|
219
226
|
}
|
|
220
227
|
|
|
221
|
-
|
|
228
|
+
function detectDevelopmentProfile(cwd, files, externalLibraries, overrides = {}) {
|
|
229
|
+
const extCount = { ts: 0, js: 0, py: 0, java: 0, go: 0, rb: 0, rs: 0, cs: 0, php: 0 };
|
|
230
|
+
for (const filePath of files) {
|
|
231
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
232
|
+
if (ext === ".ts" || ext === ".tsx") extCount.ts += 1;
|
|
233
|
+
if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") extCount.js += 1;
|
|
234
|
+
if (ext === ".py") extCount.py += 1;
|
|
235
|
+
if (ext === ".java") extCount.java += 1;
|
|
236
|
+
if (ext === ".go") extCount.go += 1;
|
|
237
|
+
if (ext === ".rb") extCount.rb += 1;
|
|
238
|
+
if (ext === ".rs") extCount.rs += 1;
|
|
239
|
+
if (ext === ".cs") extCount.cs += 1;
|
|
240
|
+
if (ext === ".php") extCount.php += 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const sortedLang = Object.entries(extCount).sort((a, b) => b[1] - a[1]);
|
|
244
|
+
const autoLanguage = sortedLang[0]?.[1] > 0 ? sortedLang[0][0] : "unknown";
|
|
245
|
+
|
|
246
|
+
let autoFramework = "unknown";
|
|
247
|
+
let hasDotnetWebSdk = false;
|
|
248
|
+
let hasMinimalApi = false;
|
|
249
|
+
let hasBlazor = false;
|
|
250
|
+
for (const filePath of files) {
|
|
251
|
+
const base = path.basename(filePath).toLowerCase();
|
|
252
|
+
if (base.endsWith(".csproj")) {
|
|
253
|
+
const text = safeRead(filePath);
|
|
254
|
+
if (/Microsoft\.NET\.Sdk\.Web/i.test(text)) hasDotnetWebSdk = true;
|
|
255
|
+
if (/Blazor/i.test(text) || /Microsoft\.AspNetCore\.Components/i.test(text)) hasBlazor = true;
|
|
256
|
+
}
|
|
257
|
+
if (base === "program.cs") {
|
|
258
|
+
const text = safeRead(filePath);
|
|
259
|
+
if (/app\.Map(Get|Post|Put|Delete|Patch)\s*\(/i.test(text)) hasMinimalApi = true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const hasDep = (name) => externalLibraries.includes(name);
|
|
263
|
+
if (externalLibraries.some((d) => d.startsWith("@angular/"))) autoFramework = "angular";
|
|
264
|
+
else if (hasDep("react")) autoFramework = "react";
|
|
265
|
+
else if (hasDep("vue")) autoFramework = "vue";
|
|
266
|
+
else if (hasDep("svelte")) autoFramework = "svelte";
|
|
267
|
+
else if (hasDep("next")) autoFramework = "nextjs";
|
|
268
|
+
else if (hasDep("nuxt")) autoFramework = "nuxt";
|
|
269
|
+
else if (hasDep("express")) autoFramework = "express";
|
|
270
|
+
else if (hasDep("@nestjs/core")) autoFramework = "nestjs";
|
|
271
|
+
else if (hasDep("fastify")) autoFramework = "fastify";
|
|
272
|
+
else if (hasDep("flask")) autoFramework = "flask";
|
|
273
|
+
else if (hasDep("django")) autoFramework = "django";
|
|
274
|
+
else if (hasDep("spring-boot")) autoFramework = "spring";
|
|
275
|
+
else if (hasBlazor) autoFramework = "blazor";
|
|
276
|
+
else if (hasMinimalApi) autoFramework = "minimalapi";
|
|
277
|
+
else if (hasDotnetWebSdk || extCount.cs > 0) autoFramework = "aspnet";
|
|
278
|
+
|
|
279
|
+
let autoProjectType = "fullstack";
|
|
280
|
+
const hasClientRoots = ["src", "frontend", "app"].some((d) => fs.existsSync(path.join(cwd, d)));
|
|
281
|
+
const hasServerRoots = ["server", "backend", "api"].some((d) => fs.existsSync(path.join(cwd, d)));
|
|
282
|
+
if (["react", "angular", "vue", "svelte", "nextjs", "nuxt"].includes(autoFramework)) autoProjectType = "frontend";
|
|
283
|
+
if (["express", "nestjs", "fastify", "flask", "django", "spring", "aspnet", "minimalapi"].includes(autoFramework)) autoProjectType = "backend";
|
|
284
|
+
if (hasClientRoots && hasServerRoots) autoProjectType = "fullstack";
|
|
285
|
+
if (!hasClientRoots && !hasServerRoots) autoProjectType = "library";
|
|
286
|
+
if (autoFramework === "blazor") autoProjectType = "frontend";
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
language: overrides.language || autoLanguage,
|
|
290
|
+
framework: overrides.framework || autoFramework,
|
|
291
|
+
projectType: overrides.projectType || autoProjectType,
|
|
292
|
+
detected: {
|
|
293
|
+
language: autoLanguage,
|
|
294
|
+
framework: autoFramework,
|
|
295
|
+
projectType: autoProjectType,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function detectApiCalls(cwd, files) {
|
|
301
|
+
const calls = [];
|
|
302
|
+
const seen = new Set();
|
|
303
|
+
const addCall = (call) => {
|
|
304
|
+
const key = `${call.method}|${call.endpointPattern}|${call.sourceFile}|${call.style}`;
|
|
305
|
+
if (seen.has(key)) return;
|
|
306
|
+
seen.add(key);
|
|
307
|
+
calls.push(call);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
for (const filePath of files) {
|
|
311
|
+
if (!/\.(ts|tsx|js|jsx|mjs|cjs|cs)$/i.test(filePath)) continue;
|
|
312
|
+
const rel = path.relative(cwd, filePath);
|
|
313
|
+
const text = safeRead(filePath);
|
|
314
|
+
const looksLikeService = /service|api|client|controller|program\.cs/i.test(rel) || /HttpClient|fetch\(|app\.Map(Get|Post|Put|Delete|Patch)\(/i.test(text);
|
|
315
|
+
if (!looksLikeService) continue;
|
|
316
|
+
|
|
317
|
+
const normalized = text.replace(/\r\n/g, "\n");
|
|
318
|
+
|
|
319
|
+
const constStrings = {};
|
|
320
|
+
const normalizeExpr = (expr) => {
|
|
321
|
+
let out = String(expr || "").trim();
|
|
322
|
+
if (!out) return "";
|
|
323
|
+
out = out.replace(/;+$/, "").trim();
|
|
324
|
+
out = out.replace(/\(\s*$/, ""); // e.g. "this._nextPage("
|
|
325
|
+
// unwrap surrounding quotes/templates
|
|
326
|
+
while ((out.startsWith("'") && out.endsWith("'")) || (out.startsWith('"') && out.endsWith('"')) || (out.startsWith("`") && out.endsWith("`"))) {
|
|
327
|
+
out = out.slice(1, -1).trim();
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
};
|
|
331
|
+
const isLikelyEndpoint = (value) => {
|
|
332
|
+
const v = String(value || "").trim();
|
|
333
|
+
if (!v) return false;
|
|
334
|
+
if (/^https?:\/\//i.test(v)) return true;
|
|
335
|
+
if (v.startsWith("/")) return true;
|
|
336
|
+
if (/\bapi\b/i.test(v)) return true;
|
|
337
|
+
if (/\$\{[^}]+\}/.test(v)) return true;
|
|
338
|
+
if (/\?[^=\s]+=?/.test(v)) return true;
|
|
339
|
+
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_./${}-]+$/.test(v)) return true;
|
|
340
|
+
return false;
|
|
341
|
+
};
|
|
342
|
+
const storeConst = (name, raw) => {
|
|
343
|
+
if (!name || !raw) return;
|
|
344
|
+
constStrings[name] = normalizeExpr(raw);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const constPattern = /(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;
|
|
348
|
+
for (const m of normalized.matchAll(constPattern)) {
|
|
349
|
+
storeConst(m[1], m[2]);
|
|
350
|
+
}
|
|
351
|
+
const readonlyPattern =
|
|
352
|
+
/(?:public|private|protected)?\s*(?:readonly\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;
|
|
353
|
+
for (const m of normalized.matchAll(readonlyPattern)) {
|
|
354
|
+
storeConst(m[1], m[2]);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const resolveExpr = (expr) => {
|
|
358
|
+
const trimmed = normalizeExpr(expr);
|
|
359
|
+
if (!trimmed) return "";
|
|
360
|
+
if (/^['"`][\s\S]*['"`]$/.test(trimmed)) {
|
|
361
|
+
return trimmed.replace(/^['"`]|['"`]$/g, "");
|
|
362
|
+
}
|
|
363
|
+
if (constStrings[trimmed] && isLikelyEndpoint(constStrings[trimmed])) return constStrings[trimmed];
|
|
364
|
+
const thisRef = trimmed.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
365
|
+
if (thisRef && constStrings[thisRef[1]] && isLikelyEndpoint(constStrings[thisRef[1]])) return constStrings[thisRef[1]];
|
|
366
|
+
const parts = trimmed.split("+").map((s) => s.trim()).filter(Boolean);
|
|
367
|
+
if (parts.length > 1) {
|
|
368
|
+
const rebuilt = parts
|
|
369
|
+
.map((p) => {
|
|
370
|
+
if (/^['"`][\s\S]*['"`]$/.test(p)) return p.replace(/^['"`]|['"`]$/g, "");
|
|
371
|
+
if (constStrings[p] && isLikelyEndpoint(constStrings[p])) return constStrings[p];
|
|
372
|
+
const thisP = p.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
373
|
+
if (thisP && constStrings[thisP[1]] && isLikelyEndpoint(constStrings[thisP[1]])) return constStrings[thisP[1]];
|
|
374
|
+
return `{${p}}`;
|
|
375
|
+
})
|
|
376
|
+
.join("");
|
|
377
|
+
if (rebuilt) return rebuilt;
|
|
378
|
+
}
|
|
379
|
+
const ternary = trimmed.match(/^(.+?)\?(.+?):(.+)$/s);
|
|
380
|
+
if (ternary) {
|
|
381
|
+
const left = resolveExpr(ternary[2]);
|
|
382
|
+
const right = resolveExpr(ternary[3]);
|
|
383
|
+
if (left || right) return `${left || "{optionA}"} | ${right || "{optionB}"}`;
|
|
384
|
+
}
|
|
385
|
+
return trimmed;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const httpClientPattern =
|
|
389
|
+
/\.\s*(get|post|put|patch|delete)\s*(?:<[\s\S]*?>)?\s*\(\s*([\s\S]*?)(?:,|\))/gi;
|
|
390
|
+
for (const m of normalized.matchAll(httpClientPattern)) {
|
|
391
|
+
const method = m[1].toUpperCase();
|
|
392
|
+
const raw = resolveExpr(m[2]);
|
|
393
|
+
if (!raw || !isLikelyEndpoint(raw)) continue;
|
|
394
|
+
addCall({
|
|
395
|
+
method,
|
|
396
|
+
endpointPattern: normalizeExpr(raw),
|
|
397
|
+
style: "httpClient",
|
|
398
|
+
sourceFile: rel,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const fetchPattern = /\bfetch\s*\(\s*([\s\S]*?)(?:,|\))/gi;
|
|
403
|
+
for (const m of normalized.matchAll(fetchPattern)) {
|
|
404
|
+
const firstArg = resolveExpr(m[1]);
|
|
405
|
+
if (!firstArg || !isLikelyEndpoint(firstArg)) continue;
|
|
406
|
+
const fromIdx = m.index || 0;
|
|
407
|
+
const lookahead = normalized.slice(fromIdx, fromIdx + 260);
|
|
408
|
+
const methodMatch = /method\s*:\s*["'](GET|POST|PUT|PATCH|DELETE)["']/i.exec(lookahead);
|
|
409
|
+
const method = (methodMatch?.[1] || "GET").toUpperCase();
|
|
410
|
+
addCall({
|
|
411
|
+
method,
|
|
412
|
+
endpointPattern: normalizeExpr(firstArg),
|
|
413
|
+
style: "fetch",
|
|
414
|
+
sourceFile: rel,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (/\.cs$/i.test(filePath)) {
|
|
419
|
+
const mapPattern = /\bapp\.Map(Get|Post|Put|Delete|Patch)\s*\(\s*"([^"]+)"/gi;
|
|
420
|
+
for (const m of normalized.matchAll(mapPattern)) {
|
|
421
|
+
addCall({
|
|
422
|
+
method: m[1].toUpperCase(),
|
|
423
|
+
endpointPattern: m[2],
|
|
424
|
+
style: "csharp-map",
|
|
425
|
+
sourceFile: rel,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const classRouteMatch = /\[Route\("([^"]+)"\)\][\s\S]*?class\s+\w+/i.exec(normalized);
|
|
430
|
+
const classRoute = classRouteMatch ? classRouteMatch[1] : "";
|
|
431
|
+
const attrPattern = /\[(HttpGet|HttpPost|HttpPut|HttpDelete|HttpPatch)(?:\("([^"]*)"\))?\]/gi;
|
|
432
|
+
for (const m of normalized.matchAll(attrPattern)) {
|
|
433
|
+
const method = m[1].replace("Http", "").toUpperCase();
|
|
434
|
+
const attrRoute = m[2] || "";
|
|
435
|
+
const endpoint = [classRoute, attrRoute].filter(Boolean).join("/").replace(/\/+/g, "/");
|
|
436
|
+
addCall({
|
|
437
|
+
method,
|
|
438
|
+
endpointPattern: endpoint || classRoute || "{controller-route}",
|
|
439
|
+
style: "csharp-controller",
|
|
440
|
+
sourceFile: rel,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const httpClientPattern = /\b(GetAsync|PostAsync|PutAsync|DeleteAsync|SendAsync)\s*\(\s*"([^"]+)"/gi;
|
|
445
|
+
for (const m of normalized.matchAll(httpClientPattern)) {
|
|
446
|
+
const method = m[1].replace("Async", "").replace("Send", "SEND").toUpperCase();
|
|
447
|
+
addCall({
|
|
448
|
+
method,
|
|
449
|
+
endpointPattern: m[2],
|
|
450
|
+
style: "csharp-httpclient",
|
|
451
|
+
sourceFile: rel,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const byMethod = calls.reduce((acc, c) => {
|
|
458
|
+
acc[c.method] = (acc[c.method] || 0) + 1;
|
|
459
|
+
return acc;
|
|
460
|
+
}, {});
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
totalCalls: calls.length,
|
|
464
|
+
byMethod,
|
|
465
|
+
calls: calls.slice(0, 80),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function discoverProjectSignals(cwd, profileOverrides = {}) {
|
|
222
470
|
const files = collectCodeFiles(cwd);
|
|
223
471
|
const inferred = new Map();
|
|
224
472
|
const addHit = (cap, filePath) => {
|
|
@@ -270,6 +518,8 @@ export function discoverProjectSignals(cwd) {
|
|
|
270
518
|
externalLibraries,
|
|
271
519
|
uiLayout: detectUiLayout(files),
|
|
272
520
|
styling: detectStyling(cwd, files, externalLibraries),
|
|
521
|
+
developmentProfile: detectDevelopmentProfile(cwd, files, externalLibraries, profileOverrides),
|
|
522
|
+
apiCalls: detectApiCalls(cwd, files),
|
|
273
523
|
};
|
|
274
524
|
}
|
|
275
525
|
|
|
@@ -366,6 +616,19 @@ export function buildSignalsReport(signals) {
|
|
|
366
616
|
` - frameworks : ${(signals.styling?.cssFrameworks || []).join(", ") || "none detected"}`,
|
|
367
617
|
` - style files: ${signals.styling?.styleFileCount ?? 0}`,
|
|
368
618
|
` - tokens : ${(signals.styling?.designTokens || []).slice(0, 8).join(", ") || "none detected"}`,
|
|
619
|
+
"Development profile",
|
|
620
|
+
"-".repeat(56),
|
|
621
|
+
` - language : ${signals.developmentProfile?.language || "unknown"} (auto: ${signals.developmentProfile?.detected?.language || "unknown"})`,
|
|
622
|
+
` - framework : ${signals.developmentProfile?.framework || "unknown"} (auto: ${signals.developmentProfile?.detected?.framework || "unknown"})`,
|
|
623
|
+
` - project type: ${signals.developmentProfile?.projectType || "unknown"} (auto: ${signals.developmentProfile?.detected?.projectType || "unknown"})`,
|
|
624
|
+
"API calls",
|
|
625
|
+
"-".repeat(56),
|
|
626
|
+
` - total calls : ${signals.apiCalls?.totalCalls ?? 0}`,
|
|
627
|
+
` - by method : ${Object.entries(signals.apiCalls?.byMethod || {}).map(([k, v]) => `${k}:${v}`).join(", ") || "none"}`,
|
|
628
|
+
...(signals.apiCalls?.calls || []).slice(0, 6).map((c) => ` - ${c.method} ${c.endpointPattern} [${c.style}] (${c.sourceFile})`),
|
|
629
|
+
...((signals.apiCalls?.calls || []).length > 6
|
|
630
|
+
? [` - ... +${(signals.apiCalls?.calls || []).length - 6} more`]
|
|
631
|
+
: []),
|
|
369
632
|
"=".repeat(56),
|
|
370
633
|
].join("\n");
|
|
371
634
|
}
|
|
@@ -423,6 +686,13 @@ export function writeAdoptionBaseline(infernoDir, policyId, capabilities, signal
|
|
|
423
686
|
externalLibraries: signals.externalLibraries || [],
|
|
424
687
|
uiLayout: signals.uiLayout || { layoutType: "unknown", usesGrid: false, usesFlex: false, sections: [] },
|
|
425
688
|
styling: signals.styling || { cssFrameworks: [], styleFileCount: 0, styleFilesSample: [], designTokens: [] },
|
|
689
|
+
developmentProfile: signals.developmentProfile || {
|
|
690
|
+
language: "unknown",
|
|
691
|
+
framework: "unknown",
|
|
692
|
+
projectType: "unknown",
|
|
693
|
+
detected: { language: "unknown", framework: "unknown", projectType: "unknown" },
|
|
694
|
+
},
|
|
695
|
+
apiCalls: signals.apiCalls || { totalCalls: 0, byMethod: {}, calls: [] },
|
|
426
696
|
};
|
|
427
697
|
fs.writeFileSync(path.join(infernoDir, "adoption_profile.json"), JSON.stringify(profile, null, 2) + "\n");
|
|
428
698
|
}
|
package/lib/commands/init.mjs
CHANGED
|
@@ -27,6 +27,14 @@ function ask(rl, question, defaultVal = "") {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function getArgValue(args, ...flags) {
|
|
31
|
+
for (const flag of flags) {
|
|
32
|
+
const i = args.indexOf(flag);
|
|
33
|
+
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith("-")) return args[i + 1];
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
function copyFile(src, dst, force, silent = false) {
|
|
31
39
|
if (fs.existsSync(dst) && !force) {
|
|
32
40
|
if (!silent) warn("Skipped (exists): " + path.relative(process.cwd(), dst));
|
|
@@ -144,6 +152,9 @@ export async function initCommand(args) {
|
|
|
144
152
|
const reportJson = args.includes("--report-json");
|
|
145
153
|
const reportJsonOnly = args.includes("--report-json-only");
|
|
146
154
|
const reportHumanOnly = args.includes("--report-human-only");
|
|
155
|
+
const langOverride = getArgValue(args, "--lang");
|
|
156
|
+
const frameworkOverride = getArgValue(args, "--framework");
|
|
157
|
+
const projectTypeOverride = getArgValue(args, "--project-type");
|
|
147
158
|
const silent = reportJsonOnly;
|
|
148
159
|
|
|
149
160
|
if (reportJsonOnly && reportHumanOnly) {
|
|
@@ -173,7 +184,23 @@ export async function initCommand(args) {
|
|
|
173
184
|
let capabilities = defaultCaps.split(",").map(c => c.trim());
|
|
174
185
|
|
|
175
186
|
if (adopt) {
|
|
176
|
-
const
|
|
187
|
+
const profileOverrides = {
|
|
188
|
+
language: langOverride || undefined,
|
|
189
|
+
framework: frameworkOverride || undefined,
|
|
190
|
+
projectType: projectTypeOverride || undefined,
|
|
191
|
+
};
|
|
192
|
+
let signals = discoverProjectSignals(cwd, profileOverrides);
|
|
193
|
+
if (!yes && !reportJsonOnly) {
|
|
194
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
195
|
+
const profile = signals.developmentProfile || {};
|
|
196
|
+
const detected = profile.detected || {};
|
|
197
|
+
console.log(gray(" Review inferred development stack (press Enter to accept detected values)\n"));
|
|
198
|
+
const language = await ask(rl, "Language", profile.language || detected.language || "unknown");
|
|
199
|
+
const framework = await ask(rl, "Framework", profile.framework || detected.framework || "unknown");
|
|
200
|
+
const projectType = await ask(rl, "Project type", profile.projectType || detected.projectType || "unknown");
|
|
201
|
+
rl.close();
|
|
202
|
+
signals = discoverProjectSignals(cwd, { language, framework, projectType });
|
|
203
|
+
}
|
|
177
204
|
const inferred = signals.capabilities;
|
|
178
205
|
const summarized = summarizeCapabilities(inferred);
|
|
179
206
|
if (reportJsonOnly) {
|
|
@@ -188,6 +215,8 @@ export async function initCommand(args) {
|
|
|
188
215
|
externalLibraries: signals.externalLibraries,
|
|
189
216
|
uiLayout: signals.uiLayout,
|
|
190
217
|
styling: signals.styling,
|
|
218
|
+
developmentProfile: signals.developmentProfile,
|
|
219
|
+
apiCalls: signals.apiCalls,
|
|
191
220
|
},
|
|
192
221
|
null,
|
|
193
222
|
2
|
|
@@ -211,6 +240,8 @@ export async function initCommand(args) {
|
|
|
211
240
|
externalLibraries: signals.externalLibraries,
|
|
212
241
|
uiLayout: signals.uiLayout,
|
|
213
242
|
styling: signals.styling,
|
|
243
|
+
developmentProfile: signals.developmentProfile,
|
|
244
|
+
apiCalls: signals.apiCalls,
|
|
214
245
|
},
|
|
215
246
|
null,
|
|
216
247
|
2
|
|
@@ -240,7 +271,11 @@ export async function initCommand(args) {
|
|
|
240
271
|
id,
|
|
241
272
|
title: id.replace(/([A-Z])/g, " $1").trim(),
|
|
242
273
|
}));
|
|
243
|
-
const signals = discoverProjectSignals(cwd
|
|
274
|
+
const signals = discoverProjectSignals(cwd, {
|
|
275
|
+
language: langOverride || undefined,
|
|
276
|
+
framework: frameworkOverride || undefined,
|
|
277
|
+
projectType: projectTypeOverride || undefined,
|
|
278
|
+
});
|
|
244
279
|
writeAdoptionBaseline(infernoDir, policyId, capDetails, signals);
|
|
245
280
|
if (!silent) {
|
|
246
281
|
ok("Created: " + cyan("inferno/contract.json"));
|
|
@@ -271,6 +306,22 @@ export async function initCommand(args) {
|
|
|
271
306
|
|
|
272
307
|
upsertScripts(cwd, silent);
|
|
273
308
|
|
|
309
|
+
if (adopt) {
|
|
310
|
+
const statePath = path.join(infernoDir, "context-state.json");
|
|
311
|
+
let state = {};
|
|
312
|
+
try {
|
|
313
|
+
state = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
314
|
+
} catch {}
|
|
315
|
+
const signals = discoverProjectSignals(cwd, {
|
|
316
|
+
language: langOverride || undefined,
|
|
317
|
+
framework: frameworkOverride || undefined,
|
|
318
|
+
projectType: projectTypeOverride || undefined,
|
|
319
|
+
});
|
|
320
|
+
state.stack = signals.developmentProfile;
|
|
321
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
322
|
+
if (!silent) ok("Created: " + cyan("inferno/context-state.json"));
|
|
323
|
+
}
|
|
324
|
+
|
|
274
325
|
if (!silent) {
|
|
275
326
|
done("infernoflow initialized!");
|
|
276
327
|
|