infernoflow 0.37.1 → 0.37.3
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/CHANGELOG.md +64 -0
- package/dist/bin/infernoflow.mjs +29 -277
- package/dist/lib/adopters/angular.mjs +1 -128
- package/dist/lib/adopters/css.mjs +1 -111
- package/dist/lib/adopters/react.mjs +1 -104
- package/dist/lib/ai/ideDetection.mjs +1 -31
- package/dist/lib/ai/localProvider.mjs +1 -88
- package/dist/lib/ai/providerRouter.mjs +2 -295
- package/dist/lib/commands/adopt.mjs +20 -869
- package/dist/lib/commands/adoptWizard.mjs +9 -320
- package/dist/lib/commands/agent.mjs +5 -191
- package/dist/lib/commands/ai.mjs +2 -407
- package/dist/lib/commands/ask.mjs +4 -299
- package/dist/lib/commands/audit.mjs +13 -300
- package/dist/lib/commands/changelog.mjs +26 -594
- package/dist/lib/commands/check.mjs +3 -184
- package/dist/lib/commands/ci.mjs +3 -208
- package/dist/lib/commands/claudeMd.mjs +30 -135
- package/dist/lib/commands/cloud.mjs +10 -773
- package/dist/lib/commands/context.mjs +34 -346
- package/dist/lib/commands/coverage.mjs +2 -282
- package/dist/lib/commands/dashboard.mjs +123 -635
- package/dist/lib/commands/demo.mjs +8 -465
- package/dist/lib/commands/diff.mjs +5 -274
- package/dist/lib/commands/docGate.mjs +2 -81
- package/dist/lib/commands/doctor.mjs +3 -321
- package/dist/lib/commands/explain.mjs +8 -438
- package/dist/lib/commands/export.mjs +10 -239
- package/dist/lib/commands/feedback.mjs +12 -216
- package/dist/lib/commands/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +11 -378
- package/dist/lib/commands/health.mjs +2 -309
- package/dist/lib/commands/impact.mjs +2 -325
- package/dist/lib/commands/implement.mjs +7 -103
- package/dist/lib/commands/init.mjs +45 -631
- package/dist/lib/commands/installCursorHooks.mjs +1 -36
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
- package/dist/lib/commands/link.mjs +2 -342
- package/dist/lib/commands/log.mjs +18 -248
- package/dist/lib/commands/monorepo.mjs +4 -428
- package/dist/lib/commands/notify.mjs +4 -258
- package/dist/lib/commands/onboard.mjs +4 -296
- package/dist/lib/commands/prComment.mjs +2 -361
- package/dist/lib/commands/prImpact.mjs +2 -157
- package/dist/lib/commands/publish.mjs +15 -316
- package/dist/lib/commands/recap.mjs +6 -380
- package/dist/lib/commands/report.mjs +28 -272
- package/dist/lib/commands/review.mjs +9 -223
- package/dist/lib/commands/run.mjs +8 -336
- package/dist/lib/commands/scaffold.mjs +54 -419
- package/dist/lib/commands/scan.mjs +11 -1118
- package/dist/lib/commands/scout.mjs +2 -291
- package/dist/lib/commands/setup.mjs +5 -310
- package/dist/lib/commands/share.mjs +13 -196
- package/dist/lib/commands/snapshot.mjs +3 -383
- package/dist/lib/commands/stability.mjs +2 -293
- package/dist/lib/commands/stats.mjs +5 -402
- package/dist/lib/commands/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- package/dist/lib/commands/switch.mjs +13 -520
- package/dist/lib/commands/syncAuto.mjs +1 -96
- package/dist/lib/commands/synthesize.mjs +10 -228
- package/dist/lib/commands/teamSync.mjs +2 -388
- package/dist/lib/commands/test.mjs +6 -363
- package/dist/lib/commands/theme.mjs +18 -195
- package/dist/lib/commands/uninstall.mjs +13 -406
- package/dist/lib/commands/upgrade.mjs +20 -153
- package/dist/lib/commands/version.mjs +2 -282
- package/dist/lib/commands/vibe.mjs +7 -357
- package/dist/lib/commands/watch.mjs +4 -203
- package/dist/lib/commands/why.mjs +4 -358
- package/dist/lib/cursorHooksInstall.mjs +1 -60
- package/dist/lib/draftToolingInstall.mjs +7 -68
- package/dist/lib/git/detect-drift.mjs +4 -208
- package/dist/lib/learning/adapt.mjs +6 -101
- package/dist/lib/learning/observe.mjs +1 -119
- package/dist/lib/learning/patternDetector.mjs +1 -298
- package/dist/lib/learning/profile.mjs +2 -279
- package/dist/lib/learning/skillSynthesizer.mjs +24 -145
- package/dist/lib/telemetry.mjs +19 -269
- package/dist/lib/templates/index.mjs +1 -131
- package/dist/lib/theme/scanner.mjs +4 -343
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -95
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/package.json +2 -4
- package/scripts/postinstall.js +2 -2
|
@@ -1,428 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* (nx, turborepo, pnpm workspaces, yarn workspaces, Lerna) and manages
|
|
6
|
-
* per-package contracts.
|
|
7
|
-
*
|
|
8
|
-
* Sub-commands:
|
|
9
|
-
* monorepo init Detect packages, scaffold inferno/ per package
|
|
10
|
-
* monorepo list List all detected packages + contract status
|
|
11
|
-
* monorepo status Show health across all packages at once
|
|
12
|
-
* monorepo diff [--package] Diff capabilities for one or all packages
|
|
13
|
-
* monorepo sync Sync all package contracts to root summary
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* infernoflow monorepo init
|
|
17
|
-
* infernoflow monorepo list
|
|
18
|
-
* infernoflow monorepo status
|
|
19
|
-
* infernoflow monorepo diff --package auth
|
|
20
|
-
* infernoflow monorepo sync
|
|
21
|
-
* infernoflow monorepo status --json
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import * as fs from "node:fs";
|
|
25
|
-
import * as path from "node:path";
|
|
26
|
-
import { fileURLToPath } from "node:url";
|
|
27
|
-
import { spawnSync } from "node:child_process";
|
|
28
|
-
import { header, ok, warn, info, done, bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
|
|
29
|
-
|
|
30
|
-
// ── Package detection ─────────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
function detectWorkspaceType(cwd) {
|
|
33
|
-
const has = (f) => fs.existsSync(path.join(cwd, f));
|
|
34
|
-
|
|
35
|
-
if (has("nx.json")) return "nx";
|
|
36
|
-
if (has("turbo.json")) return "turborepo";
|
|
37
|
-
if (has("lerna.json")) return "lerna";
|
|
38
|
-
if (has("pnpm-workspace.yaml")) return "pnpm";
|
|
39
|
-
|
|
40
|
-
const pkg = (() => {
|
|
41
|
-
try { return JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8")); }
|
|
42
|
-
catch { return {}; }
|
|
43
|
-
})();
|
|
44
|
-
|
|
45
|
-
if (pkg.workspaces) return Array.isArray(pkg.workspaces) ? "yarn" : "npm-workspaces";
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function readWorkspaceGlobs(cwd, wsType) {
|
|
50
|
-
try {
|
|
51
|
-
if (wsType === "pnpm") {
|
|
52
|
-
const raw = fs.readFileSync(path.join(cwd, "pnpm-workspace.yaml"), "utf8");
|
|
53
|
-
const matches = [...raw.matchAll(/^\s*-\s*['"]?([^'"]+)['"]?/gm)];
|
|
54
|
-
return matches.map(m => m[1].trim());
|
|
55
|
-
}
|
|
56
|
-
if (wsType === "lerna") {
|
|
57
|
-
const cfg = JSON.parse(fs.readFileSync(path.join(cwd, "lerna.json"), "utf8"));
|
|
58
|
-
return cfg.packages || ["packages/*"];
|
|
59
|
-
}
|
|
60
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
61
|
-
const ws = pkg.workspaces;
|
|
62
|
-
if (Array.isArray(ws)) return ws;
|
|
63
|
-
if (ws?.packages) return ws.packages;
|
|
64
|
-
} catch {}
|
|
65
|
-
return ["packages/*", "apps/*", "libs/*"];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function globToPackages(cwd, globs) {
|
|
69
|
-
const packages = [];
|
|
70
|
-
for (const pattern of globs) {
|
|
71
|
-
// Simple glob: handle "packages/*" and "apps/*" patterns
|
|
72
|
-
const parts = pattern.split("/");
|
|
73
|
-
const parent = parts.slice(0, -1).join("/");
|
|
74
|
-
const leaf = parts[parts.length - 1];
|
|
75
|
-
const dir = path.join(cwd, parent);
|
|
76
|
-
|
|
77
|
-
if (!fs.existsSync(dir)) continue;
|
|
78
|
-
if (leaf === "*") {
|
|
79
|
-
try {
|
|
80
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
81
|
-
if (entry.isDirectory()) {
|
|
82
|
-
const pkgPath = path.join(dir, entry.name, "package.json");
|
|
83
|
-
if (fs.existsSync(pkgPath)) {
|
|
84
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
85
|
-
packages.push({
|
|
86
|
-
name: pkg.name || entry.name,
|
|
87
|
-
dir: path.join(dir, entry.name),
|
|
88
|
-
version: pkg.version || "0.0.0",
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch {}
|
|
94
|
-
} else {
|
|
95
|
-
const fullDir = path.join(cwd, pattern);
|
|
96
|
-
const pkgPath = path.join(fullDir, "package.json");
|
|
97
|
-
if (fs.existsSync(pkgPath)) {
|
|
98
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
99
|
-
packages.push({ name: pkg.name || leaf, dir: fullDir, version: pkg.version || "0.0.0" });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return packages;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function detectPackages(cwd) {
|
|
107
|
-
const wsType = detectWorkspaceType(cwd);
|
|
108
|
-
if (!wsType) return { type: null, packages: [] };
|
|
109
|
-
const globs = readWorkspaceGlobs(cwd, wsType);
|
|
110
|
-
const packages = globToPackages(cwd, globs);
|
|
111
|
-
return { type: wsType, packages };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ── Contract helpers ──────────────────────────────────────────────────────────
|
|
115
|
-
|
|
116
|
-
function readPackageContract(pkgDir) {
|
|
117
|
-
for (const f of ["contract.json", "capabilities.json"]) {
|
|
118
|
-
const p = path.join(pkgDir, "inferno", f);
|
|
119
|
-
if (fs.existsSync(p)) { try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch {} }
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function hasInferno(pkgDir) {
|
|
125
|
-
return fs.existsSync(path.join(pkgDir, "inferno"));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function contractStatus(pkgDir) {
|
|
129
|
-
if (!hasInferno(pkgDir)) return "not-init";
|
|
130
|
-
const contract = readPackageContract(pkgDir);
|
|
131
|
-
if (!contract) return "no-contract";
|
|
132
|
-
return "ok";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ── CLI runner ────────────────────────────────────────────────────────────────
|
|
136
|
-
|
|
137
|
-
function runInferno(args, cwd) {
|
|
138
|
-
const binPath = path.join(path.dirname(path.dirname(fileURLToPath(import.meta.url))), "..", "bin", "infernoflow.mjs");
|
|
139
|
-
const result = spawnSync(process.execPath, [binPath, ...args], {
|
|
140
|
-
cwd,
|
|
141
|
-
encoding: "utf8",
|
|
142
|
-
timeout: 60_000,
|
|
143
|
-
env: { ...process.env, NO_COLOR: "1" },
|
|
144
|
-
});
|
|
145
|
-
return { stdout: result.stdout || "", stderr: result.stderr || "", status: result.status };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ── Sub-commands ──────────────────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
async function subcmdInit(args, cwd) {
|
|
151
|
-
const jsonMode = args.includes("--json");
|
|
152
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
153
|
-
const autoYes = args.includes("--yes") || args.includes("-y");
|
|
154
|
-
|
|
155
|
-
const { type, packages } = detectPackages(cwd);
|
|
156
|
-
|
|
157
|
-
if (!type) {
|
|
158
|
-
const msg = "No monorepo configuration detected. Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna.";
|
|
159
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: msg })); } else { warn(msg); }
|
|
160
|
-
process.exit(1);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (!jsonMode) {
|
|
164
|
-
header(`Monorepo init (${type})`);
|
|
165
|
-
console.log(` Detected ${bold(String(packages.length))} packages:\n`);
|
|
166
|
-
packages.forEach(p => {
|
|
167
|
-
const status = contractStatus(p.dir);
|
|
168
|
-
const icon = status === "ok" ? green("✔") : yellow("·");
|
|
169
|
-
console.log(` ${icon} ${bold(p.name)} ${gray(path.relative(cwd, p.dir))}`);
|
|
170
|
-
});
|
|
171
|
-
console.log();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (packages.length === 0) {
|
|
175
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: "No packages found" })); }
|
|
176
|
-
else { warn("No packages found matching workspace globs."); }
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const results = [];
|
|
181
|
-
for (const pkg of packages) {
|
|
182
|
-
const status = contractStatus(pkg.dir);
|
|
183
|
-
if (status === "ok" && !force) {
|
|
184
|
-
if (!jsonMode) info(`${pkg.name}: already initialised (use --force to reinit)`);
|
|
185
|
-
results.push({ name: pkg.name, status: "skipped" });
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (!jsonMode) process.stdout.write(` Initialising ${cyan(pkg.name)}… `);
|
|
190
|
-
|
|
191
|
-
const initArgs = ["init", "--adopt", "--yes"];
|
|
192
|
-
if (force) initArgs.push("--force");
|
|
193
|
-
const r = runInferno(initArgs, pkg.dir);
|
|
194
|
-
|
|
195
|
-
if (r.status === 0) {
|
|
196
|
-
if (!jsonMode) console.log(green("done"));
|
|
197
|
-
results.push({ name: pkg.name, status: "ok" });
|
|
198
|
-
} else {
|
|
199
|
-
if (!jsonMode) console.log(red("failed"));
|
|
200
|
-
results.push({ name: pkg.name, status: "error", error: r.stderr.trim().slice(0, 120) });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Write root summary
|
|
205
|
-
const summary = {
|
|
206
|
-
monorepoType: type,
|
|
207
|
-
packages: results,
|
|
208
|
-
updatedAt: new Date().toISOString(),
|
|
209
|
-
};
|
|
210
|
-
fs.writeFileSync(path.join(cwd, "inferno-monorepo.json"), JSON.stringify(summary, null, 2) + "\n");
|
|
211
|
-
|
|
212
|
-
if (jsonMode) {
|
|
213
|
-
console.log(JSON.stringify({ ok: true, type, packages: results }));
|
|
214
|
-
} else {
|
|
215
|
-
console.log();
|
|
216
|
-
const succeeded = results.filter(r => r.status === "ok").length;
|
|
217
|
-
done(`Initialised ${bold(String(succeeded))} of ${results.length} packages`);
|
|
218
|
-
console.log(` ${gray("Root summary:")} ${cyan("inferno-monorepo.json")}`);
|
|
219
|
-
console.log();
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function subcmdList(args, cwd) {
|
|
224
|
-
const jsonMode = args.includes("--json");
|
|
225
|
-
const { type, packages } = detectPackages(cwd);
|
|
226
|
-
|
|
227
|
-
if (!type && packages.length === 0) {
|
|
228
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: "No monorepo detected" })); }
|
|
229
|
-
else { warn("No monorepo detected. Run: infernoflow monorepo init"); }
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const rows = packages.map(p => ({
|
|
234
|
-
name: p.name,
|
|
235
|
-
dir: path.relative(cwd, p.dir),
|
|
236
|
-
version: p.version,
|
|
237
|
-
status: contractStatus(p.dir),
|
|
238
|
-
caps: (() => {
|
|
239
|
-
const c = readPackageContract(p.dir);
|
|
240
|
-
return c ? (c.capabilities || []).length : 0;
|
|
241
|
-
})(),
|
|
242
|
-
}));
|
|
243
|
-
|
|
244
|
-
if (jsonMode) {
|
|
245
|
-
console.log(JSON.stringify({ ok: true, type, packages: rows }));
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.log();
|
|
250
|
-
console.log(` ${bold("Monorepo packages")} ${gray("(" + type + ")")}`);
|
|
251
|
-
console.log();
|
|
252
|
-
const w = Math.max(...rows.map(r => r.name.length), 8) + 2;
|
|
253
|
-
rows.forEach(r => {
|
|
254
|
-
const icon = r.status === "ok" ? green("✔") : r.status === "not-init" ? yellow("○") : red("✗");
|
|
255
|
-
const caps = r.status === "ok" ? gray(`${r.caps} caps`) : gray(r.status);
|
|
256
|
-
console.log(` ${icon} ${r.name.padEnd(w)}${r.version.padEnd(12)}${caps}`);
|
|
257
|
-
});
|
|
258
|
-
console.log();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function subcmdStatus(args, cwd) {
|
|
262
|
-
const jsonMode = args.includes("--json");
|
|
263
|
-
const { type, packages } = detectPackages(cwd);
|
|
264
|
-
|
|
265
|
-
if (!packages.length) {
|
|
266
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: "No packages found" })); }
|
|
267
|
-
else { warn("No packages found."); }
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (!jsonMode) header(`Monorepo status (${packages.length} packages)`);
|
|
272
|
-
|
|
273
|
-
const results = [];
|
|
274
|
-
for (const pkg of packages) {
|
|
275
|
-
if (!hasInferno(pkg.dir)) {
|
|
276
|
-
results.push({ name: pkg.name, status: "not-init", caps: 0 });
|
|
277
|
-
if (!jsonMode) console.log(` ${yellow("○")} ${bold(pkg.name)} ${gray("not initialised")}`);
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
const r = runInferno(["status", "--json"], pkg.dir);
|
|
281
|
-
try {
|
|
282
|
-
const data = JSON.parse(r.stdout.trim());
|
|
283
|
-
const caps = (data.capabilityDetails || []).length;
|
|
284
|
-
const ok_ = data.ok !== false;
|
|
285
|
-
results.push({ name: pkg.name, status: ok_ ? "ok" : "error", caps, version: data.policyVersion });
|
|
286
|
-
if (!jsonMode) {
|
|
287
|
-
const icon = ok_ ? green("✔") : red("✗");
|
|
288
|
-
console.log(` ${icon} ${bold(pkg.name.padEnd(28))}${gray("v" + (data.policyVersion || "?"))} ${caps} caps`);
|
|
289
|
-
}
|
|
290
|
-
} catch {
|
|
291
|
-
results.push({ name: pkg.name, status: "error", caps: 0, error: "status failed" });
|
|
292
|
-
if (!jsonMode) console.log(` ${red("✗")} ${bold(pkg.name)} ${gray("status check failed")}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (jsonMode) {
|
|
297
|
-
const allOk = results.every(r => r.status === "ok");
|
|
298
|
-
console.log(JSON.stringify({ ok: allOk, type, packages: results }));
|
|
299
|
-
} else {
|
|
300
|
-
console.log();
|
|
301
|
-
const ok_ = results.filter(r => r.status === "ok").length;
|
|
302
|
-
console.log(` ${ok_ === results.length ? green("✔") : yellow("⚠")} ${ok_}/${results.length} packages healthy`);
|
|
303
|
-
console.log();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async function subcmdDiff(args, cwd) {
|
|
308
|
-
const jsonMode = args.includes("--json");
|
|
309
|
-
const pkgFilter = args.includes("--package") ? args[args.indexOf("--package") + 1] : null;
|
|
310
|
-
const { packages } = detectPackages(cwd);
|
|
311
|
-
|
|
312
|
-
const targets = pkgFilter
|
|
313
|
-
? packages.filter(p => p.name === pkgFilter || p.name.endsWith("/" + pkgFilter))
|
|
314
|
-
: packages.filter(p => hasInferno(p.dir));
|
|
315
|
-
|
|
316
|
-
if (!targets.length) {
|
|
317
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: pkgFilter ? `Package not found: ${pkgFilter}` : "No initialised packages found" })); }
|
|
318
|
-
else { warn(pkgFilter ? `Package not found: ${pkgFilter}` : "No initialised packages found."); }
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (!jsonMode && !pkgFilter) header(`Monorepo diff (${targets.length} packages)`);
|
|
323
|
-
|
|
324
|
-
const allResults = [];
|
|
325
|
-
for (const pkg of targets) {
|
|
326
|
-
const r = runInferno(["diff", "--json"], pkg.dir);
|
|
327
|
-
try {
|
|
328
|
-
const data = JSON.parse(r.stdout.trim());
|
|
329
|
-
const added = (data.added || []).length;
|
|
330
|
-
const removed = (data.removed || []).length;
|
|
331
|
-
const changed = (data.changed || []).length;
|
|
332
|
-
allResults.push({ name: pkg.name, added, removed, changed, data });
|
|
333
|
-
if (!jsonMode) {
|
|
334
|
-
if (added || removed || changed) {
|
|
335
|
-
console.log(` ${bold(pkg.name)}`);
|
|
336
|
-
if (added) console.log(` ${green("+")} ${added} added`);
|
|
337
|
-
if (removed) console.log(` ${red("-")} ${removed} removed`);
|
|
338
|
-
if (changed) console.log(` ${yellow("~")} ${changed} changed`);
|
|
339
|
-
} else {
|
|
340
|
-
console.log(` ${green("✔")} ${bold(pkg.name)} ${gray("no changes")}`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
} catch {
|
|
344
|
-
allResults.push({ name: pkg.name, error: "diff failed" });
|
|
345
|
-
if (!jsonMode) console.log(` ${red("✗")} ${bold(pkg.name)} ${gray("diff failed")}`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (jsonMode) {
|
|
350
|
-
console.log(JSON.stringify({ ok: true, packages: allResults }));
|
|
351
|
-
} else {
|
|
352
|
-
console.log();
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
async function subcmdSync(args, cwd) {
|
|
357
|
-
const jsonMode = args.includes("--json");
|
|
358
|
-
const { type, packages } = detectPackages(cwd);
|
|
359
|
-
|
|
360
|
-
if (!jsonMode) header("Syncing monorepo contracts");
|
|
361
|
-
|
|
362
|
-
// Build a root aggregate contract
|
|
363
|
-
const aggregate = {
|
|
364
|
-
monorepoType: type,
|
|
365
|
-
updatedAt: new Date().toISOString(),
|
|
366
|
-
packages: [],
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
for (const pkg of packages) {
|
|
370
|
-
const contract = readPackageContract(pkg.dir);
|
|
371
|
-
if (!contract) continue;
|
|
372
|
-
aggregate.packages.push({
|
|
373
|
-
name: pkg.name,
|
|
374
|
-
version: contract.policyVersion || pkg.version,
|
|
375
|
-
capabilities: (contract.capabilities || []).map(c => typeof c === "string" ? c : c.id),
|
|
376
|
-
capsCount: (contract.capabilities || []).length,
|
|
377
|
-
});
|
|
378
|
-
if (!jsonMode) console.log(` ${green("✔")} ${bold(pkg.name)} ${gray((contract.capabilities || []).length + " caps")}`);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const outPath = path.join(cwd, "inferno-monorepo.json");
|
|
382
|
-
fs.writeFileSync(outPath, JSON.stringify(aggregate, null, 2) + "\n");
|
|
383
|
-
|
|
384
|
-
const totalCaps = aggregate.packages.reduce((sum, p) => sum + p.capsCount, 0);
|
|
385
|
-
|
|
386
|
-
if (jsonMode) {
|
|
387
|
-
console.log(JSON.stringify({ ok: true, packages: aggregate.packages.length, totalCaps }));
|
|
388
|
-
} else {
|
|
389
|
-
console.log();
|
|
390
|
-
done(`Synced ${bold(String(aggregate.packages.length))} packages (${totalCaps} total capabilities)`);
|
|
391
|
-
console.log(` ${cyan(outPath)}`);
|
|
392
|
-
console.log();
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ── Entry ─────────────────────────────────────────────────────────────────────
|
|
397
|
-
|
|
398
|
-
export async function monorepoCommand(rawArgs) {
|
|
399
|
-
const args = rawArgs.slice(1);
|
|
400
|
-
const subcmd = args[0];
|
|
401
|
-
const cwd = process.cwd();
|
|
402
|
-
const rest = args.slice(1);
|
|
403
|
-
|
|
404
|
-
switch (subcmd) {
|
|
405
|
-
case "init": return subcmdInit(rest, cwd);
|
|
406
|
-
case "list": return subcmdList(rest, cwd);
|
|
407
|
-
case "status": return subcmdStatus(rest, cwd);
|
|
408
|
-
case "diff": return subcmdDiff(rest, cwd);
|
|
409
|
-
case "sync": return subcmdSync(rest, cwd);
|
|
410
|
-
default: {
|
|
411
|
-
const jsonMode = args.includes("--json");
|
|
412
|
-
const msg = `Unknown monorepo sub-command: ${subcmd || "(none)"}`;
|
|
413
|
-
if (jsonMode) { console.log(JSON.stringify({ ok: false, error: msg })); return; }
|
|
414
|
-
console.log();
|
|
415
|
-
console.log(` ${bold("infernoflow monorepo")} — per-package capability tracking`);
|
|
416
|
-
console.log();
|
|
417
|
-
console.log(` ${cyan("infernoflow monorepo init")} Scaffold inferno/ in each package`);
|
|
418
|
-
console.log(` ${cyan("infernoflow monorepo list")} List packages and contract status`);
|
|
419
|
-
console.log(` ${cyan("infernoflow monorepo status")} Health check across all packages`);
|
|
420
|
-
console.log(` ${cyan("infernoflow monorepo diff")} Capability diff for all packages`);
|
|
421
|
-
console.log(` ${cyan("infernoflow monorepo diff --package auth")} Diff a specific package`);
|
|
422
|
-
console.log(` ${cyan("infernoflow monorepo sync")} Aggregate all contracts to inferno-monorepo.json`);
|
|
423
|
-
console.log();
|
|
424
|
-
console.log(` ${gray("Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna")}`);
|
|
425
|
-
console.log();
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
1
|
+
import*as g from"node:fs";import*as p from"node:path";import{fileURLToPath as F}from"node:url";import{spawnSync as M}from"node:child_process";import{header as b,warn as S,info as P,done as x,bold as u,cyan as k,gray as d,green as y,yellow as j,red as $}from"../ui/output.mjs";function A(e){const a=s=>g.existsSync(p.join(e,s));if(a("nx.json"))return"nx";if(a("turbo.json"))return"turborepo";if(a("lerna.json"))return"lerna";if(a("pnpm-workspace.yaml"))return"pnpm";const n=(()=>{try{return JSON.parse(g.readFileSync(p.join(e,"package.json"),"utf8"))}catch{return{}}})();return n.workspaces?Array.isArray(n.workspaces)?"yarn":"npm-workspaces":null}function C(e,a){try{if(a==="pnpm")return[...g.readFileSync(p.join(e,"pnpm-workspace.yaml"),"utf8").matchAll(/^\s*-\s*['"]?([^'"]+)['"]?/gm)].map(c=>c[1].trim());if(a==="lerna")return JSON.parse(g.readFileSync(p.join(e,"lerna.json"),"utf8")).packages||["packages/*"];const s=JSON.parse(g.readFileSync(p.join(e,"package.json"),"utf8")).workspaces;if(Array.isArray(s))return s;if(s?.packages)return s.packages}catch{}return["packages/*","apps/*","libs/*"]}function D(e,a){const n=[];for(const s of a){const l=s.split("/"),r=l.slice(0,-1).join("/"),c=l[l.length-1],o=p.join(e,r);if(g.existsSync(o))if(c==="*")try{for(const i of g.readdirSync(o,{withFileTypes:!0}))if(i.isDirectory()){const t=p.join(o,i.name,"package.json");if(g.existsSync(t)){const f=JSON.parse(g.readFileSync(t,"utf8"));n.push({name:f.name||i.name,dir:p.join(o,i.name),version:f.version||"0.0.0"})}}}catch{}else{const i=p.join(e,s),t=p.join(i,"package.json");if(g.existsSync(t)){const f=JSON.parse(g.readFileSync(t,"utf8"));n.push({name:f.name||c,dir:i,version:f.version||"0.0.0"})}}}return n}function N(e){const a=A(e);if(!a)return{type:null,packages:[]};const n=C(e,a),s=D(e,n);return{type:a,packages:s}}function w(e){for(const a of["contract.json","capabilities.json"]){const n=p.join(e,"inferno",a);if(g.existsSync(n))try{return JSON.parse(g.readFileSync(n,"utf8"))}catch{}}return null}function O(e){return g.existsSync(p.join(e,"inferno"))}function J(e){return O(e)?w(e)?"ok":"no-contract":"not-init"}function v(e,a){const n=p.join(p.dirname(p.dirname(F(import.meta.url))),"..","bin","infernoflow.mjs"),s=M(process.execPath,[n,...e],{cwd:a,encoding:"utf8",timeout:6e4,env:{...process.env,NO_COLOR:"1"}});return{stdout:s.stdout||"",stderr:s.stderr||"",status:s.status}}async function I(e,a){const n=e.includes("--json"),s=e.includes("--force")||e.includes("-f"),l=e.includes("--yes")||e.includes("-y"),{type:r,packages:c}=N(a);if(!r){const t="No monorepo configuration detected. Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna.";n?console.log(JSON.stringify({ok:!1,error:t})):S(t),process.exit(1)}n||(b(`Monorepo init (${r})`),console.log(` Detected ${u(String(c.length))} packages:
|
|
2
|
+
`),c.forEach(t=>{const m=J(t.dir)==="ok"?y("\u2714"):j("\xB7");console.log(` ${m} ${u(t.name)} ${d(p.relative(a,t.dir))}`)}),console.log()),c.length===0&&(n?console.log(JSON.stringify({ok:!1,error:"No packages found"})):S("No packages found matching workspace globs."),process.exit(1));const o=[];for(const t of c){if(J(t.dir)==="ok"&&!s){n||P(`${t.name}: already initialised (use --force to reinit)`),o.push({name:t.name,status:"skipped"});continue}n||process.stdout.write(` Initialising ${k(t.name)}\u2026 `);const m=["init","--adopt","--yes"];s&&m.push("--force");const h=v(m,t.dir);h.status===0?(n||console.log(y("done")),o.push({name:t.name,status:"ok"})):(n||console.log($("failed")),o.push({name:t.name,status:"error",error:h.stderr.trim().slice(0,120)}))}const i={monorepoType:r,packages:o,updatedAt:new Date().toISOString()};if(g.writeFileSync(p.join(a,"inferno-monorepo.json"),JSON.stringify(i,null,2)+`
|
|
3
|
+
`),n)console.log(JSON.stringify({ok:!0,type:r,packages:o}));else{console.log();const t=o.filter(f=>f.status==="ok").length;x(`Initialised ${u(String(t))} of ${o.length} packages`),console.log(` ${d("Root summary:")} ${k("inferno-monorepo.json")}`),console.log()}}async function T(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);if(!s&&l.length===0){n?console.log(JSON.stringify({ok:!1,error:"No monorepo detected"})):S("No monorepo detected. Run: infernoflow monorepo init");return}const r=l.map(o=>({name:o.name,dir:p.relative(a,o.dir),version:o.version,status:J(o.dir),caps:(()=>{const i=w(o.dir);return i?(i.capabilities||[]).length:0})()}));if(n){console.log(JSON.stringify({ok:!0,type:s,packages:r}));return}console.log(),console.log(` ${u("Monorepo packages")} ${d("("+s+")")}`),console.log();const c=Math.max(...r.map(o=>o.name.length),8)+2;r.forEach(o=>{const i=o.status==="ok"?y("\u2714"):o.status==="not-init"?j("\u25CB"):$("\u2717"),t=o.status==="ok"?d(`${o.caps} caps`):d(o.status);console.log(` ${i} ${o.name.padEnd(c)}${o.version.padEnd(12)}${t}`)}),console.log()}async function E(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);if(!l.length){n?console.log(JSON.stringify({ok:!1,error:"No packages found"})):S("No packages found.");return}n||b(`Monorepo status (${l.length} packages)`);const r=[];for(const c of l){if(!O(c.dir)){r.push({name:c.name,status:"not-init",caps:0}),n||console.log(` ${j("\u25CB")} ${u(c.name)} ${d("not initialised")}`);continue}const o=v(["status","--json"],c.dir);try{const i=JSON.parse(o.stdout.trim()),t=(i.capabilityDetails||[]).length,f=i.ok!==!1;if(r.push({name:c.name,status:f?"ok":"error",caps:t,version:i.policyVersion}),!n){const m=f?y("\u2714"):$("\u2717");console.log(` ${m} ${u(c.name.padEnd(28))}${d("v"+(i.policyVersion||"?"))} ${t} caps`)}}catch{r.push({name:c.name,status:"error",caps:0,error:"status failed"}),n||console.log(` ${$("\u2717")} ${u(c.name)} ${d("status check failed")}`)}}if(n){const c=r.every(o=>o.status==="ok");console.log(JSON.stringify({ok:c,type:s,packages:r}))}else{console.log();const c=r.filter(o=>o.status==="ok").length;console.log(` ${c===r.length?y("\u2714"):j("\u26A0")} ${c}/${r.length} packages healthy`),console.log()}}async function R(e,a){const n=e.includes("--json"),s=e.includes("--package")?e[e.indexOf("--package")+1]:null,{packages:l}=N(a),r=s?l.filter(o=>o.name===s||o.name.endsWith("/"+s)):l.filter(o=>O(o.dir));if(!r.length){n?console.log(JSON.stringify({ok:!1,error:s?`Package not found: ${s}`:"No initialised packages found"})):S(s?`Package not found: ${s}`:"No initialised packages found.");return}!n&&!s&&b(`Monorepo diff (${r.length} packages)`);const c=[];for(const o of r){const i=v(["diff","--json"],o.dir);try{const t=JSON.parse(i.stdout.trim()),f=(t.added||[]).length,m=(t.removed||[]).length,h=(t.changed||[]).length;c.push({name:o.name,added:f,removed:m,changed:h,data:t}),n||(f||m||h?(console.log(` ${u(o.name)}`),f&&console.log(` ${y("+")} ${f} added`),m&&console.log(` ${$("-")} ${m} removed`),h&&console.log(` ${j("~")} ${h} changed`)):console.log(` ${y("\u2714")} ${u(o.name)} ${d("no changes")}`))}catch{c.push({name:o.name,error:"diff failed"}),n||console.log(` ${$("\u2717")} ${u(o.name)} ${d("diff failed")}`)}}n?console.log(JSON.stringify({ok:!0,packages:c})):console.log()}async function L(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);n||b("Syncing monorepo contracts");const r={monorepoType:s,updatedAt:new Date().toISOString(),packages:[]};for(const i of l){const t=w(i.dir);t&&(r.packages.push({name:i.name,version:t.policyVersion||i.version,capabilities:(t.capabilities||[]).map(f=>typeof f=="string"?f:f.id),capsCount:(t.capabilities||[]).length}),n||console.log(` ${y("\u2714")} ${u(i.name)} ${d((t.capabilities||[]).length+" caps")}`))}const c=p.join(a,"inferno-monorepo.json");g.writeFileSync(c,JSON.stringify(r,null,2)+`
|
|
4
|
+
`);const o=r.packages.reduce((i,t)=>i+t.capsCount,0);n?console.log(JSON.stringify({ok:!0,packages:r.packages.length,totalCaps:o})):(console.log(),x(`Synced ${u(String(r.packages.length))} packages (${o} total capabilities)`),console.log(` ${k(c)}`),console.log())}async function G(e){const a=e.slice(1),n=a[0],s=process.cwd(),l=a.slice(1);switch(n){case"init":return I(l,s);case"list":return T(l,s);case"status":return E(l,s);case"diff":return R(l,s);case"sync":return L(l,s);default:{const r=a.includes("--json"),c=`Unknown monorepo sub-command: ${n||"(none)"}`;if(r){console.log(JSON.stringify({ok:!1,error:c}));return}console.log(),console.log(` ${u("infernoflow monorepo")} \u2014 per-package capability tracking`),console.log(),console.log(` ${k("infernoflow monorepo init")} Scaffold inferno/ in each package`),console.log(` ${k("infernoflow monorepo list")} List packages and contract status`),console.log(` ${k("infernoflow monorepo status")} Health check across all packages`),console.log(` ${k("infernoflow monorepo diff")} Capability diff for all packages`),console.log(` ${k("infernoflow monorepo diff --package auth")} Diff a specific package`),console.log(` ${k("infernoflow monorepo sync")} Aggregate all contracts to inferno-monorepo.json`),console.log(),console.log(` ${d("Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna")}`),console.log()}}}export{G as monorepoCommand};
|