viberails 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1223 -963
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1224 -964
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
VERSION: () => VERSION
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
-
var
|
|
37
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/boundaries.ts
|
|
@@ -64,14 +64,66 @@ function findProjectRoot(startDir) {
|
|
|
64
64
|
var clack5 = __toESM(require("@clack/prompts"), 1);
|
|
65
65
|
|
|
66
66
|
// src/utils/prompt-integrations.ts
|
|
67
|
+
var import_node_child_process = require("child_process");
|
|
67
68
|
var clack = __toESM(require("@clack/prompts"), 1);
|
|
68
|
-
async function
|
|
69
|
-
const
|
|
69
|
+
async function promptHookManagerInstall(projectRoot, packageManager) {
|
|
70
|
+
const choice = await clack.select({
|
|
71
|
+
message: "No git hook manager detected. Install Lefthook for shareable pre-commit hooks?",
|
|
72
|
+
options: [
|
|
73
|
+
{
|
|
74
|
+
value: "install",
|
|
75
|
+
label: "Yes, install Lefthook",
|
|
76
|
+
hint: "recommended \u2014 hooks are committed to the repo and shared with your team"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
value: "skip",
|
|
80
|
+
label: "No, skip",
|
|
81
|
+
hint: "pre-commit hooks will be local-only (.git/hooks) and not shared"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
assertNotCancelled(choice);
|
|
86
|
+
if (choice !== "install") return void 0;
|
|
87
|
+
const pm = packageManager || "npm";
|
|
88
|
+
const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? "pnpm add -D lefthook" : "npm install -D lefthook";
|
|
89
|
+
const s = clack.spinner();
|
|
90
|
+
s.start("Installing Lefthook...");
|
|
91
|
+
const result = (0, import_node_child_process.spawnSync)(installCmd, {
|
|
92
|
+
cwd: projectRoot,
|
|
93
|
+
shell: true,
|
|
94
|
+
encoding: "utf-8",
|
|
95
|
+
stdio: "pipe"
|
|
96
|
+
});
|
|
97
|
+
if (result.status === 0) {
|
|
98
|
+
const fs20 = await import("fs");
|
|
99
|
+
const path20 = await import("path");
|
|
100
|
+
const lefthookPath = path20.join(projectRoot, "lefthook.yml");
|
|
101
|
+
if (!fs20.existsSync(lefthookPath)) {
|
|
102
|
+
fs20.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
|
|
103
|
+
}
|
|
104
|
+
s.stop("Installed Lefthook");
|
|
105
|
+
return "Lefthook";
|
|
106
|
+
}
|
|
107
|
+
s.stop("Failed to install Lefthook");
|
|
108
|
+
clack.log.warn(`Install manually: ${installCmd}`);
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
async function promptIntegrations(projectRoot, hookManager, tools) {
|
|
112
|
+
let resolvedHookManager = hookManager;
|
|
113
|
+
if (!resolvedHookManager) {
|
|
114
|
+
resolvedHookManager = await promptHookManagerInstall(
|
|
115
|
+
projectRoot,
|
|
116
|
+
tools?.packageManager ?? "npm"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const isBareHook = !resolvedHookManager;
|
|
120
|
+
const hookLabel = resolvedHookManager ? `Pre-commit hook (${resolvedHookManager})` : "Pre-commit hook (git hook \u2014 local only)";
|
|
121
|
+
const hookHint = isBareHook ? "local only \u2014 will NOT be committed or shared with collaborators" : "runs viberails checks when you commit";
|
|
70
122
|
const options = [
|
|
71
123
|
{
|
|
72
124
|
value: "preCommit",
|
|
73
125
|
label: hookLabel,
|
|
74
|
-
hint:
|
|
126
|
+
hint: hookHint
|
|
75
127
|
}
|
|
76
128
|
];
|
|
77
129
|
if (tools?.isTypeScript) {
|
|
@@ -106,7 +158,7 @@ async function promptIntegrations(hookManager, tools) {
|
|
|
106
158
|
hint: "blocks PRs that fail viberails check"
|
|
107
159
|
}
|
|
108
160
|
);
|
|
109
|
-
const initialValues = options.map((o) => o.value);
|
|
161
|
+
const initialValues = isBareHook ? options.filter((o) => o.value !== "preCommit").map((o) => o.value) : options.map((o) => o.value);
|
|
110
162
|
const result = await clack.multiselect({
|
|
111
163
|
message: "Set up integrations?",
|
|
112
164
|
options,
|
|
@@ -309,11 +361,10 @@ function buildMenuOptions(state, packageCount) {
|
|
|
309
361
|
hint: state.fileNamingValue
|
|
310
362
|
});
|
|
311
363
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
});
|
|
364
|
+
const isMonorepo = packageCount > 0;
|
|
365
|
+
const coverageLabel = isMonorepo ? "Default coverage target" : "Test coverage target";
|
|
366
|
+
const coverageHint = state.testCoverage === 0 ? "0 (disabled)" : isMonorepo ? `${state.testCoverage}% (per-package default)` : `${state.testCoverage}%`;
|
|
367
|
+
options.push({ value: "testCoverage", label: coverageLabel, hint: coverageHint });
|
|
317
368
|
options.push({
|
|
318
369
|
value: "enforceMissingTests",
|
|
319
370
|
label: "Enforce missing tests",
|
|
@@ -323,16 +374,16 @@ function buildMenuOptions(state, packageCount) {
|
|
|
323
374
|
options.push(
|
|
324
375
|
{
|
|
325
376
|
value: "coverageSummaryPath",
|
|
326
|
-
label: "Coverage summary path",
|
|
377
|
+
label: isMonorepo ? "Default coverage summary path" : "Coverage summary path",
|
|
327
378
|
hint: state.coverageSummaryPath
|
|
328
379
|
},
|
|
329
380
|
{
|
|
330
381
|
value: "coverageCommand",
|
|
331
|
-
label: "Coverage command",
|
|
382
|
+
label: isMonorepo ? "Default coverage command" : "Coverage command",
|
|
332
383
|
hint: state.coverageCommand ?? "auto-detect from package.json test runner"
|
|
333
384
|
}
|
|
334
385
|
);
|
|
335
|
-
if (
|
|
386
|
+
if (isMonorepo) {
|
|
336
387
|
options.push({
|
|
337
388
|
value: "packageOverrides",
|
|
338
389
|
label: "Per-package coverage overrides",
|
|
@@ -678,7 +729,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
|
|
|
678
729
|
// src/commands/check.ts
|
|
679
730
|
var fs7 = __toESM(require("fs"), 1);
|
|
680
731
|
var path7 = __toESM(require("path"), 1);
|
|
681
|
-
var
|
|
732
|
+
var import_config5 = require("@viberails/config");
|
|
682
733
|
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
683
734
|
|
|
684
735
|
// src/commands/check-config.ts
|
|
@@ -723,9 +774,10 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
723
774
|
}
|
|
724
775
|
|
|
725
776
|
// src/commands/check-coverage.ts
|
|
726
|
-
var
|
|
777
|
+
var import_node_child_process2 = require("child_process");
|
|
727
778
|
var fs4 = __toESM(require("fs"), 1);
|
|
728
779
|
var path4 = __toESM(require("path"), 1);
|
|
780
|
+
var import_config3 = require("@viberails/config");
|
|
729
781
|
var DEFAULT_SUMMARY_PATH = "coverage/coverage-summary.json";
|
|
730
782
|
function packageRoot(projectRoot, pkg) {
|
|
731
783
|
return pkg.path === "." ? projectRoot : path4.join(projectRoot, pkg.path);
|
|
@@ -765,7 +817,7 @@ function readCoveragePercentage(summaryPath) {
|
|
|
765
817
|
}
|
|
766
818
|
}
|
|
767
819
|
function runCoverageCommand(pkgRoot, command) {
|
|
768
|
-
const result = (0,
|
|
820
|
+
const result = (0, import_node_child_process2.spawnSync)(command, {
|
|
769
821
|
cwd: pkgRoot,
|
|
770
822
|
shell: true,
|
|
771
823
|
encoding: "utf-8",
|
|
@@ -806,13 +858,14 @@ function checkCoverage(projectRoot, config, filesToCheck, options) {
|
|
|
806
858
|
const violations = [];
|
|
807
859
|
for (const target of targets) {
|
|
808
860
|
if (target.rules.testCoverage <= 0) continue;
|
|
861
|
+
if (!target.pkg.stack?.testRunner) continue;
|
|
809
862
|
const pkgRoot = packageRoot(projectRoot, target.pkg);
|
|
810
863
|
const summaryPath = target.coverage.summaryPath ?? DEFAULT_SUMMARY_PATH;
|
|
811
864
|
const summaryAbs = path4.join(pkgRoot, summaryPath);
|
|
812
865
|
const summaryRel = violationFilePath(projectRoot, pkgRoot, summaryPath);
|
|
813
866
|
let pct = readCoveragePercentage(summaryAbs);
|
|
814
867
|
if (pct === void 0 && !options.staged) {
|
|
815
|
-
const command = target.coverage.command;
|
|
868
|
+
const command = target.coverage.command ?? (0, import_config3.inferCoverageCommand)(target.pkg.stack.testRunner);
|
|
816
869
|
if (!command) {
|
|
817
870
|
const pkgLabel = target.pkg.path === "." ? "root package" : target.pkg.path;
|
|
818
871
|
pushViolation(
|
|
@@ -823,6 +876,7 @@ function checkCoverage(projectRoot, config, filesToCheck, options) {
|
|
|
823
876
|
);
|
|
824
877
|
continue;
|
|
825
878
|
}
|
|
879
|
+
options.onProgress?.(target.pkg.path === "." ? "root" : target.pkg.path);
|
|
826
880
|
const run = runCoverageCommand(pkgRoot, command);
|
|
827
881
|
if (!run.ok) {
|
|
828
882
|
pushViolation(
|
|
@@ -857,10 +911,10 @@ function checkCoverage(projectRoot, config, filesToCheck, options) {
|
|
|
857
911
|
}
|
|
858
912
|
|
|
859
913
|
// src/commands/check-files.ts
|
|
860
|
-
var
|
|
914
|
+
var import_node_child_process3 = require("child_process");
|
|
861
915
|
var fs5 = __toESM(require("fs"), 1);
|
|
862
916
|
var path5 = __toESM(require("path"), 1);
|
|
863
|
-
var
|
|
917
|
+
var import_config4 = require("@viberails/config");
|
|
864
918
|
var import_picomatch = __toESM(require("picomatch"), 1);
|
|
865
919
|
var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
866
920
|
"node_modules",
|
|
@@ -930,7 +984,7 @@ function checkNaming(relPath, conventions) {
|
|
|
930
984
|
}
|
|
931
985
|
function getStagedFiles(projectRoot) {
|
|
932
986
|
try {
|
|
933
|
-
const output = (0,
|
|
987
|
+
const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=ACM", {
|
|
934
988
|
cwd: projectRoot,
|
|
935
989
|
encoding: "utf-8",
|
|
936
990
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -942,12 +996,12 @@ function getStagedFiles(projectRoot) {
|
|
|
942
996
|
}
|
|
943
997
|
function getDiffFiles(projectRoot, base) {
|
|
944
998
|
try {
|
|
945
|
-
const allOutput = (0,
|
|
999
|
+
const allOutput = (0, import_node_child_process3.execSync)(`git diff --name-only --diff-filter=ACMR ${base}...HEAD`, {
|
|
946
1000
|
cwd: projectRoot,
|
|
947
1001
|
encoding: "utf-8",
|
|
948
1002
|
stdio: ["ignore", "pipe", "ignore"]
|
|
949
1003
|
});
|
|
950
|
-
const addedOutput = (0,
|
|
1004
|
+
const addedOutput = (0, import_node_child_process3.execSync)(`git diff --name-only --diff-filter=A ${base}...HEAD`, {
|
|
951
1005
|
cwd: projectRoot,
|
|
952
1006
|
encoding: "utf-8",
|
|
953
1007
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -961,7 +1015,7 @@ function getDiffFiles(projectRoot, base) {
|
|
|
961
1015
|
}
|
|
962
1016
|
}
|
|
963
1017
|
function getAllSourceFiles(projectRoot, config) {
|
|
964
|
-
const effectiveIgnore = [...
|
|
1018
|
+
const effectiveIgnore = [...import_config4.BUILTIN_IGNORE, ...config.ignore ?? []];
|
|
965
1019
|
const files = [];
|
|
966
1020
|
const walk = (dir) => {
|
|
967
1021
|
let entries;
|
|
@@ -1141,7 +1195,7 @@ async function checkCommand(options, cwd) {
|
|
|
1141
1195
|
);
|
|
1142
1196
|
return 1;
|
|
1143
1197
|
}
|
|
1144
|
-
const config = await (0,
|
|
1198
|
+
const config = await (0, import_config5.loadConfig)(configPath);
|
|
1145
1199
|
let filesToCheck;
|
|
1146
1200
|
let diffAddedFiles = null;
|
|
1147
1201
|
if (options.staged) {
|
|
@@ -1165,6 +1219,9 @@ async function checkCommand(options, cwd) {
|
|
|
1165
1219
|
}
|
|
1166
1220
|
const violations = [];
|
|
1167
1221
|
const severity = options.enforce ? "error" : "warn";
|
|
1222
|
+
const log7 = options.format !== "json" && !options.hook ? (msg) => process.stderr.write(import_chalk2.default.dim(msg)) : () => {
|
|
1223
|
+
};
|
|
1224
|
+
log7(" Checking files...");
|
|
1168
1225
|
for (const file of filesToCheck) {
|
|
1169
1226
|
const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
|
|
1170
1227
|
const relPath = path7.relative(projectRoot, absPath);
|
|
@@ -1197,18 +1254,22 @@ async function checkCommand(options, cwd) {
|
|
|
1197
1254
|
}
|
|
1198
1255
|
}
|
|
1199
1256
|
}
|
|
1257
|
+
log7(" done\n");
|
|
1200
1258
|
if (!options.staged && !options.files) {
|
|
1259
|
+
log7(" Checking missing tests...");
|
|
1201
1260
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
}
|
|
1261
|
+
violations.push(
|
|
1262
|
+
...diffAddedFiles ? testViolations.filter((v) => diffAddedFiles.has(v.file)) : testViolations
|
|
1263
|
+
);
|
|
1264
|
+
log7(" done\n");
|
|
1207
1265
|
}
|
|
1208
1266
|
if (!options.files && !options.staged && !options.diffBase) {
|
|
1267
|
+
log7(" Running test coverage...\n");
|
|
1209
1268
|
const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
|
|
1210
1269
|
staged: options.staged,
|
|
1211
|
-
enforce: options.enforce
|
|
1270
|
+
enforce: options.enforce,
|
|
1271
|
+
onProgress: (pkg) => log7(` Coverage: ${pkg}...
|
|
1272
|
+
`)
|
|
1212
1273
|
});
|
|
1213
1274
|
violations.push(...coverageViolations);
|
|
1214
1275
|
}
|
|
@@ -1232,10 +1293,8 @@ async function checkCommand(options, cwd) {
|
|
|
1232
1293
|
severity
|
|
1233
1294
|
});
|
|
1234
1295
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
console.log(import_chalk2.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
|
|
1238
|
-
}
|
|
1296
|
+
log7(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
|
|
1297
|
+
`);
|
|
1239
1298
|
}
|
|
1240
1299
|
if (options.format === "json") {
|
|
1241
1300
|
console.log(
|
|
@@ -1307,431 +1366,367 @@ async function hookCheckCommand(cwd) {
|
|
|
1307
1366
|
}
|
|
1308
1367
|
}
|
|
1309
1368
|
|
|
1310
|
-
// src/commands/
|
|
1311
|
-
var
|
|
1312
|
-
var
|
|
1313
|
-
var
|
|
1369
|
+
// src/commands/config.ts
|
|
1370
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
1371
|
+
var path9 = __toESM(require("path"), 1);
|
|
1372
|
+
var clack6 = __toESM(require("@clack/prompts"), 1);
|
|
1373
|
+
var import_config6 = require("@viberails/config");
|
|
1374
|
+
var import_scanner = require("@viberails/scanner");
|
|
1375
|
+
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1376
|
+
|
|
1377
|
+
// src/display-text.ts
|
|
1378
|
+
var import_types4 = require("@viberails/types");
|
|
1379
|
+
|
|
1380
|
+
// src/display.ts
|
|
1381
|
+
var import_types3 = require("@viberails/types");
|
|
1314
1382
|
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
1315
1383
|
|
|
1316
|
-
// src/
|
|
1317
|
-
var
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1384
|
+
// src/display-helpers.ts
|
|
1385
|
+
var import_types = require("@viberails/types");
|
|
1386
|
+
function groupByRole(directories) {
|
|
1387
|
+
const map = /* @__PURE__ */ new Map();
|
|
1388
|
+
for (const dir of directories) {
|
|
1389
|
+
if (dir.role === "unknown") continue;
|
|
1390
|
+
const existing = map.get(dir.role);
|
|
1391
|
+
if (existing) {
|
|
1392
|
+
existing.dirs.push(dir);
|
|
1393
|
+
} else {
|
|
1394
|
+
map.set(dir.role, { dirs: [dir] });
|
|
1324
1395
|
}
|
|
1325
1396
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1397
|
+
const groups = [];
|
|
1398
|
+
for (const [role, { dirs }] of map) {
|
|
1399
|
+
const label = import_types.ROLE_DESCRIPTIONS[role] ?? role;
|
|
1400
|
+
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
1401
|
+
groups.push({
|
|
1402
|
+
role,
|
|
1403
|
+
label,
|
|
1404
|
+
dirCount: dirs.length,
|
|
1405
|
+
totalFiles,
|
|
1406
|
+
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
1407
|
+
});
|
|
1331
1408
|
}
|
|
1409
|
+
return groups;
|
|
1332
1410
|
}
|
|
1333
|
-
function
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
encoding: "utf-8",
|
|
1338
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1339
|
-
});
|
|
1340
|
-
return output.trim().length > 0;
|
|
1341
|
-
} catch {
|
|
1342
|
-
return false;
|
|
1411
|
+
function formatSummary(stats, packageCount) {
|
|
1412
|
+
const parts = [];
|
|
1413
|
+
if (packageCount && packageCount > 1) {
|
|
1414
|
+
parts.push(`${packageCount} packages`);
|
|
1343
1415
|
}
|
|
1416
|
+
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
1417
|
+
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
1418
|
+
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
1419
|
+
return parts.join(" \xB7 ");
|
|
1344
1420
|
}
|
|
1345
|
-
function
|
|
1346
|
-
|
|
1347
|
-
return void 0;
|
|
1421
|
+
function formatExtensions(filesByExtension, maxEntries = 4) {
|
|
1422
|
+
return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
|
|
1348
1423
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1424
|
+
function formatRoleGroup(group) {
|
|
1425
|
+
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1426
|
+
if (group.singlePath) {
|
|
1427
|
+
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1428
|
+
}
|
|
1429
|
+
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
1430
|
+
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1354
1431
|
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1432
|
+
|
|
1433
|
+
// src/display-monorepo.ts
|
|
1434
|
+
var import_types2 = require("@viberails/types");
|
|
1435
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
1436
|
+
function formatPackageSummary(pkg) {
|
|
1437
|
+
const parts = [];
|
|
1438
|
+
if (pkg.stack.framework) {
|
|
1439
|
+
parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
|
|
1440
|
+
}
|
|
1441
|
+
if (pkg.stack.styling) {
|
|
1442
|
+
parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
|
|
1443
|
+
}
|
|
1444
|
+
const files = `${pkg.statistics.totalFiles} files`;
|
|
1445
|
+
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1446
|
+
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1362
1447
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
const newName = newFilename.slice(0, newFilename.indexOf("."));
|
|
1371
|
-
renameMap.set(oldStripped, { newBare: newName });
|
|
1448
|
+
function displayMonorepoResults(scanResult) {
|
|
1449
|
+
const { stack, packages } = scanResult;
|
|
1450
|
+
console.log(`
|
|
1451
|
+
${import_chalk3.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1452
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1453
|
+
if (stack.packageManager) {
|
|
1454
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1372
1455
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
const updates = [];
|
|
1379
|
-
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
1380
|
-
for (const sourceFile of project.getSourceFiles()) {
|
|
1381
|
-
const filePath = sourceFile.getFilePath();
|
|
1382
|
-
const segments = filePath.split(path8.sep);
|
|
1383
|
-
if (segments.includes("node_modules") || segments.includes("dist")) continue;
|
|
1384
|
-
const fileDir = path8.dirname(filePath);
|
|
1385
|
-
for (const decl of sourceFile.getImportDeclarations()) {
|
|
1386
|
-
const specifier = decl.getModuleSpecifierValue();
|
|
1387
|
-
if (!specifier.startsWith(".")) continue;
|
|
1388
|
-
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
1389
|
-
if (!match) continue;
|
|
1390
|
-
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
1391
|
-
updates.push({
|
|
1392
|
-
file: filePath,
|
|
1393
|
-
oldSpecifier: specifier,
|
|
1394
|
-
newSpecifier: newSpec,
|
|
1395
|
-
line: decl.getStartLineNumber()
|
|
1396
|
-
});
|
|
1397
|
-
decl.setModuleSpecifier(newSpec);
|
|
1398
|
-
}
|
|
1399
|
-
for (const decl of sourceFile.getExportDeclarations()) {
|
|
1400
|
-
const specifier = decl.getModuleSpecifierValue();
|
|
1401
|
-
if (!specifier || !specifier.startsWith(".")) continue;
|
|
1402
|
-
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
1403
|
-
if (!match) continue;
|
|
1404
|
-
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
1405
|
-
updates.push({
|
|
1406
|
-
file: filePath,
|
|
1407
|
-
oldSpecifier: specifier,
|
|
1408
|
-
newSpecifier: newSpec,
|
|
1409
|
-
line: decl.getStartLineNumber()
|
|
1410
|
-
});
|
|
1411
|
-
decl.setModuleSpecifier(newSpec);
|
|
1456
|
+
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1457
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
|
|
1458
|
+
} else {
|
|
1459
|
+
if (stack.linter) {
|
|
1460
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1412
1461
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
const args = call.getArguments();
|
|
1416
|
-
if (args.length === 0) continue;
|
|
1417
|
-
const arg = args[0];
|
|
1418
|
-
if (arg.getKind() !== SyntaxKind.StringLiteral) continue;
|
|
1419
|
-
const specifier = arg.getText().slice(1, -1);
|
|
1420
|
-
if (!specifier.startsWith(".")) continue;
|
|
1421
|
-
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
1422
|
-
if (!match) continue;
|
|
1423
|
-
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
1424
|
-
updates.push({
|
|
1425
|
-
file: filePath,
|
|
1426
|
-
oldSpecifier: specifier,
|
|
1427
|
-
newSpecifier: newSpec,
|
|
1428
|
-
line: call.getStartLineNumber()
|
|
1429
|
-
});
|
|
1430
|
-
const quote = arg.getText()[0];
|
|
1431
|
-
arg.replaceWithText(`${quote}${newSpec}${quote}`);
|
|
1462
|
+
if (stack.formatter) {
|
|
1463
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1432
1464
|
}
|
|
1433
1465
|
}
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1466
|
+
if (stack.testRunner) {
|
|
1467
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1436
1468
|
}
|
|
1437
|
-
|
|
1469
|
+
console.log("");
|
|
1470
|
+
for (const pkg of packages) {
|
|
1471
|
+
console.log(formatPackageSummary(pkg));
|
|
1472
|
+
}
|
|
1473
|
+
const packagesWithDirs = packages.filter(
|
|
1474
|
+
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1475
|
+
);
|
|
1476
|
+
if (packagesWithDirs.length > 0) {
|
|
1477
|
+
console.log(`
|
|
1478
|
+
${import_chalk3.default.bold("Structure:")}`);
|
|
1479
|
+
for (const pkg of packagesWithDirs) {
|
|
1480
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
1481
|
+
if (groups.length === 0) continue;
|
|
1482
|
+
console.log(` ${pkg.relativePath}:`);
|
|
1483
|
+
for (const group of groups) {
|
|
1484
|
+
console.log(` ${import_chalk3.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
displayConventions(scanResult);
|
|
1489
|
+
displaySummarySection(scanResult);
|
|
1490
|
+
console.log("");
|
|
1438
1491
|
}
|
|
1439
|
-
function
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
const candidate = resolved + ext;
|
|
1444
|
-
const stripped = stripExtension(candidate);
|
|
1445
|
-
const match = renameMap.get(stripped);
|
|
1446
|
-
if (match) return match;
|
|
1492
|
+
function formatPackageSummaryPlain(pkg) {
|
|
1493
|
+
const parts = [];
|
|
1494
|
+
if (pkg.stack.framework) {
|
|
1495
|
+
parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
|
|
1447
1496
|
}
|
|
1448
|
-
|
|
1497
|
+
if (pkg.stack.styling) {
|
|
1498
|
+
parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
|
|
1499
|
+
}
|
|
1500
|
+
const files = `${pkg.statistics.totalFiles} files`;
|
|
1501
|
+
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1502
|
+
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1449
1503
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
if (
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1504
|
+
function formatMonorepoResultsText(scanResult) {
|
|
1505
|
+
const lines = [];
|
|
1506
|
+
const { stack, packages } = scanResult;
|
|
1507
|
+
lines.push(`Detected: (monorepo, ${packages.length} packages)`);
|
|
1508
|
+
const sharedParts = [formatItem(stack.language)];
|
|
1509
|
+
if (stack.packageManager) sharedParts.push(formatItem(stack.packageManager));
|
|
1510
|
+
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1511
|
+
sharedParts.push(`${formatItem(stack.linter)} (lint + format)`);
|
|
1512
|
+
} else {
|
|
1513
|
+
if (stack.linter) sharedParts.push(formatItem(stack.linter));
|
|
1514
|
+
if (stack.formatter) sharedParts.push(formatItem(stack.formatter));
|
|
1515
|
+
}
|
|
1516
|
+
if (stack.testRunner) sharedParts.push(formatItem(stack.testRunner));
|
|
1517
|
+
lines.push(` \u2713 ${sharedParts.join(" \xB7 ")}`);
|
|
1518
|
+
lines.push("");
|
|
1519
|
+
for (const pkg of packages) {
|
|
1520
|
+
lines.push(formatPackageSummaryPlain(pkg));
|
|
1521
|
+
}
|
|
1522
|
+
const packagesWithDirs = packages.filter(
|
|
1523
|
+
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1524
|
+
);
|
|
1525
|
+
if (packagesWithDirs.length > 0) {
|
|
1526
|
+
lines.push("");
|
|
1527
|
+
lines.push("Structure:");
|
|
1528
|
+
for (const pkg of packagesWithDirs) {
|
|
1529
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
1530
|
+
if (groups.length === 0) continue;
|
|
1531
|
+
lines.push(` ${pkg.relativePath}:`);
|
|
1532
|
+
for (const group of groups) {
|
|
1533
|
+
lines.push(` \u2713 ${formatRoleGroup(group)}`);
|
|
1472
1534
|
}
|
|
1473
|
-
current += ch;
|
|
1474
1535
|
}
|
|
1475
|
-
if (current) words.push(current.toLowerCase());
|
|
1476
1536
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
return words.join("-");
|
|
1485
|
-
case "camelCase":
|
|
1486
|
-
return words[0] + words.slice(1).map(capitalize).join("");
|
|
1487
|
-
case "PascalCase":
|
|
1488
|
-
return words.map(capitalize).join("");
|
|
1489
|
-
case "snake_case":
|
|
1490
|
-
return words.join("_");
|
|
1491
|
-
default:
|
|
1492
|
-
return bare;
|
|
1537
|
+
lines.push(...formatConventionsText(scanResult));
|
|
1538
|
+
const pkgCount = packages.length > 1 ? packages.length : void 0;
|
|
1539
|
+
lines.push("");
|
|
1540
|
+
lines.push(formatSummary(scanResult.statistics, pkgCount));
|
|
1541
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1542
|
+
if (ext) {
|
|
1543
|
+
lines.push(ext);
|
|
1493
1544
|
}
|
|
1494
|
-
|
|
1495
|
-
function capitalize(word) {
|
|
1496
|
-
if (word.length === 0) return word;
|
|
1497
|
-
return word[0].toUpperCase() + word.slice(1);
|
|
1545
|
+
return lines.join("\n");
|
|
1498
1546
|
}
|
|
1499
1547
|
|
|
1500
|
-
// src/
|
|
1501
|
-
function
|
|
1502
|
-
const
|
|
1503
|
-
|
|
1504
|
-
const dotIndex = filename.indexOf(".");
|
|
1505
|
-
if (dotIndex === -1) return null;
|
|
1506
|
-
const bare = filename.slice(0, dotIndex);
|
|
1507
|
-
const suffix = filename.slice(dotIndex);
|
|
1508
|
-
const newBare = convertName(bare, targetConvention);
|
|
1509
|
-
if (newBare === bare) return null;
|
|
1510
|
-
const newFilename = newBare + suffix;
|
|
1511
|
-
const newRelPath = path9.join(dir, newFilename);
|
|
1512
|
-
const oldAbsPath = path9.join(projectRoot, relPath);
|
|
1513
|
-
const newAbsPath = path9.join(projectRoot, newRelPath);
|
|
1514
|
-
if (fs9.existsSync(newAbsPath)) return null;
|
|
1515
|
-
return { oldPath: relPath, newPath: newRelPath, oldAbsPath, newAbsPath };
|
|
1516
|
-
}
|
|
1517
|
-
function executeRename(rename) {
|
|
1518
|
-
if (fs9.existsSync(rename.newAbsPath)) return false;
|
|
1519
|
-
fs9.renameSync(rename.oldAbsPath, rename.newAbsPath);
|
|
1520
|
-
return true;
|
|
1548
|
+
// src/display.ts
|
|
1549
|
+
function formatItem(item, nameMap) {
|
|
1550
|
+
const name = nameMap?.[item.name] ?? item.name;
|
|
1551
|
+
return item.version ? `${name} ${item.version}` : name;
|
|
1521
1552
|
}
|
|
1522
|
-
function
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
if (seen.has(r.newAbsPath)) continue;
|
|
1527
|
-
seen.add(r.newAbsPath);
|
|
1528
|
-
result.push(r);
|
|
1553
|
+
function confidenceLabel(convention) {
|
|
1554
|
+
const pct = Math.round(convention.consistency);
|
|
1555
|
+
if (convention.confidence === "high") {
|
|
1556
|
+
return `${pct}% \u2014 high confidence, will enforce`;
|
|
1529
1557
|
}
|
|
1530
|
-
return
|
|
1558
|
+
return `${pct}% \u2014 medium confidence, suggested only`;
|
|
1531
1559
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1560
|
+
function displayConventions(scanResult) {
|
|
1561
|
+
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1562
|
+
if (conventionEntries.length === 0) return;
|
|
1563
|
+
console.log(`
|
|
1564
|
+
${import_chalk4.default.bold("Conventions:")}`);
|
|
1565
|
+
for (const [key, convention] of conventionEntries) {
|
|
1566
|
+
if (convention.confidence === "low") continue;
|
|
1567
|
+
const label = import_types3.CONVENTION_LABELS[key] ?? key;
|
|
1568
|
+
if (scanResult.packages.length > 1) {
|
|
1569
|
+
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1570
|
+
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1571
|
+
if (allSame || pkgValues.length <= 1) {
|
|
1572
|
+
const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
|
|
1573
|
+
const detail = import_chalk4.default.dim(`(${confidenceLabel(convention)})`);
|
|
1574
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1575
|
+
} else {
|
|
1576
|
+
console.log(` ${import_chalk4.default.yellow("~")} ${label}: varies by package`);
|
|
1577
|
+
for (const pv of pkgValues) {
|
|
1578
|
+
const pct = Math.round(pv.convention.consistency);
|
|
1579
|
+
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
} else {
|
|
1583
|
+
const ind = convention.confidence === "high" ? import_chalk4.default.green("\u2713") : import_chalk4.default.yellow("~");
|
|
1584
|
+
const detail = import_chalk4.default.dim(`(${confidenceLabel(convention)})`);
|
|
1585
|
+
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1554
1588
|
}
|
|
1555
|
-
function
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
});
|
|
1563
|
-
|
|
1564
|
-
fs10.mkdirSync(path10.dirname(stub.absPath), { recursive: true });
|
|
1565
|
-
fs10.writeFileSync(stub.absPath, content);
|
|
1589
|
+
function displaySummarySection(scanResult) {
|
|
1590
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1591
|
+
console.log(`
|
|
1592
|
+
${import_chalk4.default.bold("Summary:")}`);
|
|
1593
|
+
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1594
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1595
|
+
if (ext) {
|
|
1596
|
+
console.log(` ${ext}`);
|
|
1597
|
+
}
|
|
1566
1598
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const startDir = cwd ?? process.cwd();
|
|
1572
|
-
const projectRoot = findProjectRoot(startDir);
|
|
1573
|
-
if (!projectRoot) {
|
|
1574
|
-
console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
1575
|
-
return 1;
|
|
1599
|
+
function displayScanResults(scanResult) {
|
|
1600
|
+
if (scanResult.packages.length > 1) {
|
|
1601
|
+
displayMonorepoResults(scanResult);
|
|
1602
|
+
return;
|
|
1576
1603
|
}
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
);
|
|
1582
|
-
return 1;
|
|
1604
|
+
const { stack } = scanResult;
|
|
1605
|
+
console.log(`
|
|
1606
|
+
${import_chalk4.default.bold("Detected:")}`);
|
|
1607
|
+
if (stack.framework) {
|
|
1608
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1583
1609
|
}
|
|
1584
|
-
|
|
1585
|
-
if (
|
|
1586
|
-
|
|
1587
|
-
if (isDirty) {
|
|
1588
|
-
console.log(
|
|
1589
|
-
import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
1590
|
-
);
|
|
1591
|
-
}
|
|
1610
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1611
|
+
if (stack.styling) {
|
|
1612
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1592
1613
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
const allFiles = getAllSourceFiles(projectRoot, config);
|
|
1596
|
-
const renames = [];
|
|
1597
|
-
if (shouldFixNaming) {
|
|
1598
|
-
for (const file of allFiles) {
|
|
1599
|
-
const resolved = resolveConfigForFile(file, config);
|
|
1600
|
-
if (!resolved.rules.enforceNaming || !resolved.conventions.fileNaming) continue;
|
|
1601
|
-
const violation = checkNaming(file, resolved.conventions);
|
|
1602
|
-
if (!violation) continue;
|
|
1603
|
-
const convention = getConventionValue(resolved.conventions.fileNaming);
|
|
1604
|
-
if (!convention) continue;
|
|
1605
|
-
const rename = computeRename(file, convention, projectRoot);
|
|
1606
|
-
if (rename) renames.push(rename);
|
|
1607
|
-
}
|
|
1614
|
+
if (stack.backend) {
|
|
1615
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1608
1616
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
if (shouldFixTests) {
|
|
1612
|
-
const testViolations = checkMissingTests(projectRoot, config, "warn");
|
|
1613
|
-
for (const v of testViolations) {
|
|
1614
|
-
const stub = generateTestStub(v.file, config, projectRoot);
|
|
1615
|
-
if (stub) testStubs.push(stub);
|
|
1616
|
-
}
|
|
1617
|
+
if (stack.orm) {
|
|
1618
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1617
1619
|
}
|
|
1618
|
-
if (
|
|
1619
|
-
console.log(
|
|
1620
|
-
|
|
1620
|
+
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1621
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
|
|
1622
|
+
} else {
|
|
1623
|
+
if (stack.linter) {
|
|
1624
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1625
|
+
}
|
|
1626
|
+
if (stack.formatter) {
|
|
1627
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1628
|
+
}
|
|
1621
1629
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
|
|
1625
|
-
return 0;
|
|
1630
|
+
if (stack.testRunner) {
|
|
1631
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1626
1632
|
}
|
|
1627
|
-
if (
|
|
1628
|
-
|
|
1629
|
-
if (!confirmed) {
|
|
1630
|
-
console.log("Aborted.");
|
|
1631
|
-
return 0;
|
|
1632
|
-
}
|
|
1633
|
+
if (stack.packageManager) {
|
|
1634
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1633
1635
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
renameCount++;
|
|
1636
|
+
if (stack.libraries.length > 0) {
|
|
1637
|
+
for (const lib of stack.libraries) {
|
|
1638
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1638
1639
|
}
|
|
1639
1640
|
}
|
|
1640
|
-
|
|
1641
|
-
if (
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}
|
|
1646
|
-
let stubCount = 0;
|
|
1647
|
-
for (const stub of testStubs) {
|
|
1648
|
-
if (!fs11.existsSync(stub.absPath)) {
|
|
1649
|
-
writeTestStub(stub, config);
|
|
1650
|
-
stubCount++;
|
|
1641
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1642
|
+
if (groups.length > 0) {
|
|
1643
|
+
console.log(`
|
|
1644
|
+
${import_chalk4.default.bold("Structure:")}`);
|
|
1645
|
+
for (const group of groups) {
|
|
1646
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1651
1647
|
}
|
|
1652
1648
|
}
|
|
1649
|
+
displayConventions(scanResult);
|
|
1650
|
+
displaySummarySection(scanResult);
|
|
1653
1651
|
console.log("");
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1652
|
+
}
|
|
1653
|
+
function displayRulesPreview(config) {
|
|
1654
|
+
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1655
|
+
console.log(
|
|
1656
|
+
`${import_chalk4.default.bold("Rules:")} ${import_chalk4.default.dim("(warns on violation; use --enforce in CI to block)")}`
|
|
1657
|
+
);
|
|
1658
|
+
console.log(` ${import_chalk4.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1659
|
+
if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
|
|
1660
|
+
console.log(
|
|
1661
|
+
` ${import_chalk4.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
|
|
1662
|
+
);
|
|
1663
|
+
} else if (config.rules.testCoverage > 0) {
|
|
1664
|
+
console.log(` ${import_chalk4.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
|
|
1665
|
+
} else {
|
|
1666
|
+
console.log(` ${import_chalk4.default.dim("\u2022")} Test coverage target: disabled`);
|
|
1664
1667
|
}
|
|
1665
|
-
|
|
1668
|
+
if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
|
|
1669
|
+
console.log(` ${import_chalk4.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
|
|
1670
|
+
} else {
|
|
1671
|
+
console.log(` ${import_chalk4.default.dim("\u2022")} Enforce file naming: no`);
|
|
1672
|
+
}
|
|
1673
|
+
console.log(
|
|
1674
|
+
` ${import_chalk4.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1675
|
+
);
|
|
1676
|
+
console.log("");
|
|
1666
1677
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1678
|
+
function displayInitSummary(config, exemptedPackages) {
|
|
1679
|
+
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1680
|
+
const isMonorepo = config.packages.length > 1;
|
|
1681
|
+
const ok = import_chalk4.default.green("\u2713");
|
|
1682
|
+
const off = import_chalk4.default.dim("\u25CB");
|
|
1683
|
+
console.log("");
|
|
1684
|
+
console.log(` ${import_chalk4.default.bold("Rules to apply:")}`);
|
|
1685
|
+
console.log(` ${ok} Max file size: ${import_chalk4.default.cyan(`${config.rules.maxFileLines} lines`)}`);
|
|
1686
|
+
if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
|
|
1687
|
+
console.log(` ${ok} File naming: ${import_chalk4.default.cyan(root.conventions.fileNaming)}`);
|
|
1688
|
+
} else {
|
|
1689
|
+
console.log(` ${off} File naming: ${import_chalk4.default.dim("not enforced")}`);
|
|
1690
|
+
}
|
|
1691
|
+
if (config.rules.enforceMissingTests && root?.structure?.testPattern) {
|
|
1692
|
+
console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan(`enforced (${root.structure.testPattern})`)}`);
|
|
1693
|
+
} else if (config.rules.enforceMissingTests) {
|
|
1694
|
+
console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan("enforced")}`);
|
|
1695
|
+
} else {
|
|
1696
|
+
console.log(` ${off} Missing tests: ${import_chalk4.default.dim("not enforced")}`);
|
|
1697
|
+
}
|
|
1698
|
+
if (config.rules.testCoverage > 0) {
|
|
1699
|
+
if (isMonorepo) {
|
|
1700
|
+
const withCoverage = config.packages.filter(
|
|
1701
|
+
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
1702
|
+
);
|
|
1703
|
+
console.log(
|
|
1704
|
+
` ${ok} Coverage: ${import_chalk4.default.cyan(`${config.rules.testCoverage}%`)} default ${import_chalk4.default.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
|
|
1705
|
+
);
|
|
1689
1706
|
} else {
|
|
1690
|
-
|
|
1707
|
+
console.log(` ${ok} Coverage: ${import_chalk4.default.cyan(`${config.rules.testCoverage}%`)}`);
|
|
1691
1708
|
}
|
|
1709
|
+
} else {
|
|
1710
|
+
console.log(` ${off} Coverage: ${import_chalk4.default.dim("disabled")}`);
|
|
1692
1711
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
groups.push({
|
|
1698
|
-
role,
|
|
1699
|
-
label,
|
|
1700
|
-
dirCount: dirs.length,
|
|
1701
|
-
totalFiles,
|
|
1702
|
-
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
1703
|
-
});
|
|
1704
|
-
}
|
|
1705
|
-
return groups;
|
|
1706
|
-
}
|
|
1707
|
-
function formatSummary(stats, packageCount) {
|
|
1708
|
-
const parts = [];
|
|
1709
|
-
if (packageCount && packageCount > 1) {
|
|
1710
|
-
parts.push(`${packageCount} packages`);
|
|
1712
|
+
if (exemptedPackages.length > 0) {
|
|
1713
|
+
console.log(
|
|
1714
|
+
` ${import_chalk4.default.dim(" exempted:")} ${import_chalk4.default.dim(exemptedPackages.join(", "))} ${import_chalk4.default.dim("(types-only)")}`
|
|
1715
|
+
);
|
|
1711
1716
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
}
|
|
1720
|
-
function formatRoleGroup(group) {
|
|
1721
|
-
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1722
|
-
if (group.singlePath) {
|
|
1723
|
-
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1717
|
+
if (isMonorepo) {
|
|
1718
|
+
console.log(
|
|
1719
|
+
`
|
|
1720
|
+
${import_chalk4.default.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
|
|
1721
|
+
);
|
|
1722
|
+
} else {
|
|
1723
|
+
console.log(`
|
|
1724
|
+
${import_chalk4.default.dim("warns on violation \xB7 use --enforce in CI to block")}`);
|
|
1724
1725
|
}
|
|
1725
|
-
|
|
1726
|
-
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1726
|
+
console.log("");
|
|
1727
1727
|
}
|
|
1728
1728
|
|
|
1729
|
-
// src/display-monorepo.ts
|
|
1730
|
-
var import_types3 = require("@viberails/types");
|
|
1731
|
-
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1732
|
-
|
|
1733
1729
|
// src/display-text.ts
|
|
1734
|
-
var import_types2 = require("@viberails/types");
|
|
1735
1730
|
function plainConfidenceLabel(convention) {
|
|
1736
1731
|
const pct = Math.round(convention.consistency);
|
|
1737
1732
|
if (convention.confidence === "high") {
|
|
@@ -1747,7 +1742,7 @@ function formatConventionsText(scanResult) {
|
|
|
1747
1742
|
lines.push("Conventions:");
|
|
1748
1743
|
for (const [key, convention] of conventionEntries) {
|
|
1749
1744
|
if (convention.confidence === "low") continue;
|
|
1750
|
-
const label =
|
|
1745
|
+
const label = import_types4.CONVENTION_LABELS[key] ?? key;
|
|
1751
1746
|
if (scanResult.packages.length > 1) {
|
|
1752
1747
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1753
1748
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
@@ -1770,10 +1765,13 @@ function formatConventionsText(scanResult) {
|
|
|
1770
1765
|
}
|
|
1771
1766
|
function formatRulesText(config) {
|
|
1772
1767
|
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1768
|
+
const isMonorepo = config.packages.length > 1;
|
|
1773
1769
|
const lines = [];
|
|
1774
1770
|
lines.push(`Max file size: ${config.rules.maxFileLines} lines`);
|
|
1775
1771
|
if (config.rules.testCoverage > 0) {
|
|
1776
|
-
|
|
1772
|
+
const label = isMonorepo ? "Default coverage target" : "Test coverage target";
|
|
1773
|
+
const suffix = isMonorepo ? " (per-package)" : "";
|
|
1774
|
+
lines.push(`${label}: ${config.rules.testCoverage}%${suffix}`);
|
|
1777
1775
|
} else {
|
|
1778
1776
|
lines.push("Test coverage target: disabled");
|
|
1779
1777
|
}
|
|
@@ -1798,17 +1796,17 @@ function formatScanResultsText(scanResult) {
|
|
|
1798
1796
|
const { stack } = scanResult;
|
|
1799
1797
|
lines.push("Detected:");
|
|
1800
1798
|
if (stack.framework) {
|
|
1801
|
-
lines.push(` \u2713 ${formatItem(stack.framework,
|
|
1799
|
+
lines.push(` \u2713 ${formatItem(stack.framework, import_types4.FRAMEWORK_NAMES)}`);
|
|
1802
1800
|
}
|
|
1803
1801
|
lines.push(` \u2713 ${formatItem(stack.language)}`);
|
|
1804
1802
|
if (stack.styling) {
|
|
1805
|
-
lines.push(` \u2713 ${formatItem(stack.styling,
|
|
1803
|
+
lines.push(` \u2713 ${formatItem(stack.styling, import_types4.STYLING_NAMES)}`);
|
|
1806
1804
|
}
|
|
1807
1805
|
if (stack.backend) {
|
|
1808
|
-
lines.push(` \u2713 ${formatItem(stack.backend,
|
|
1806
|
+
lines.push(` \u2713 ${formatItem(stack.backend, import_types4.FRAMEWORK_NAMES)}`);
|
|
1809
1807
|
}
|
|
1810
1808
|
if (stack.orm) {
|
|
1811
|
-
lines.push(` \u2713 ${formatItem(stack.orm,
|
|
1809
|
+
lines.push(` \u2713 ${formatItem(stack.orm, import_types4.ORM_NAMES)}`);
|
|
1812
1810
|
}
|
|
1813
1811
|
const secondaryParts = [];
|
|
1814
1812
|
if (stack.packageManager) secondaryParts.push(formatItem(stack.packageManager));
|
|
@@ -1820,7 +1818,7 @@ function formatScanResultsText(scanResult) {
|
|
|
1820
1818
|
}
|
|
1821
1819
|
if (stack.libraries.length > 0) {
|
|
1822
1820
|
for (const lib of stack.libraries) {
|
|
1823
|
-
lines.push(` \u2713 ${formatItem(lib,
|
|
1821
|
+
lines.push(` \u2713 ${formatItem(lib, import_types4.LIBRARY_NAMES)}`);
|
|
1824
1822
|
}
|
|
1825
1823
|
}
|
|
1826
1824
|
const groups = groupByRole(scanResult.structure.directories);
|
|
@@ -1842,256 +1840,652 @@ function formatScanResultsText(scanResult) {
|
|
|
1842
1840
|
return lines.join("\n");
|
|
1843
1841
|
}
|
|
1844
1842
|
|
|
1845
|
-
// src/
|
|
1846
|
-
function
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1843
|
+
// src/utils/apply-rule-overrides.ts
|
|
1844
|
+
function applyRuleOverrides(config, overrides) {
|
|
1845
|
+
if (overrides.packageOverrides) config.packages = overrides.packageOverrides;
|
|
1846
|
+
config.rules.maxFileLines = overrides.maxFileLines;
|
|
1847
|
+
config.rules.testCoverage = overrides.testCoverage;
|
|
1848
|
+
config.rules.enforceMissingTests = overrides.enforceMissingTests;
|
|
1849
|
+
config.rules.enforceNaming = overrides.enforceNaming;
|
|
1850
|
+
for (const pkg of config.packages) {
|
|
1851
|
+
pkg.coverage = pkg.coverage ?? {};
|
|
1852
|
+
if (pkg.coverage.summaryPath === void 0) {
|
|
1853
|
+
pkg.coverage.summaryPath = overrides.coverageSummaryPath;
|
|
1854
|
+
}
|
|
1855
|
+
if (pkg.coverage.command === void 0 && overrides.coverageCommand) {
|
|
1856
|
+
pkg.coverage.command = overrides.coverageCommand;
|
|
1857
|
+
}
|
|
1850
1858
|
}
|
|
1851
|
-
if (
|
|
1852
|
-
|
|
1859
|
+
if (overrides.fileNamingValue) {
|
|
1860
|
+
const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1861
|
+
const oldNaming = rootPkg.conventions?.fileNaming;
|
|
1862
|
+
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
1863
|
+
rootPkg.conventions.fileNaming = overrides.fileNamingValue;
|
|
1864
|
+
if (oldNaming && oldNaming !== overrides.fileNamingValue) {
|
|
1865
|
+
for (const pkg of config.packages) {
|
|
1866
|
+
if (pkg.conventions?.fileNaming === oldNaming) {
|
|
1867
|
+
pkg.conventions.fileNaming = overrides.fileNamingValue;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1853
1871
|
}
|
|
1854
|
-
const files = `${pkg.statistics.totalFiles} files`;
|
|
1855
|
-
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1856
|
-
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1857
1872
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
if (
|
|
1864
|
-
|
|
1873
|
+
|
|
1874
|
+
// src/utils/diff-configs.ts
|
|
1875
|
+
var import_types5 = require("@viberails/types");
|
|
1876
|
+
function parseStackString(s) {
|
|
1877
|
+
const atIdx = s.indexOf("@");
|
|
1878
|
+
if (atIdx > 0) {
|
|
1879
|
+
return { name: s.slice(0, atIdx), version: s.slice(atIdx + 1) };
|
|
1865
1880
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1881
|
+
return { name: s };
|
|
1882
|
+
}
|
|
1883
|
+
function displayStackName(s) {
|
|
1884
|
+
const { name, version } = parseStackString(s);
|
|
1885
|
+
const allMaps = {
|
|
1886
|
+
...import_types5.FRAMEWORK_NAMES,
|
|
1887
|
+
...import_types5.STYLING_NAMES,
|
|
1888
|
+
...import_types5.ORM_NAMES
|
|
1889
|
+
};
|
|
1890
|
+
const display = allMaps[name] ?? name;
|
|
1891
|
+
return version ? `${display} ${version}` : display;
|
|
1892
|
+
}
|
|
1893
|
+
function isNewlyDetected(config, pkgPath, key) {
|
|
1894
|
+
return config._meta?.packages?.[pkgPath]?.conventions?.[key]?.detected === true;
|
|
1895
|
+
}
|
|
1896
|
+
var STACK_FIELDS = [
|
|
1897
|
+
"framework",
|
|
1898
|
+
"styling",
|
|
1899
|
+
"backend",
|
|
1900
|
+
"orm",
|
|
1901
|
+
"linter",
|
|
1902
|
+
"formatter",
|
|
1903
|
+
"testRunner"
|
|
1904
|
+
];
|
|
1905
|
+
var CONVENTION_KEYS = [
|
|
1906
|
+
"fileNaming",
|
|
1907
|
+
"componentNaming",
|
|
1908
|
+
"hookNaming",
|
|
1909
|
+
"importAlias"
|
|
1910
|
+
];
|
|
1911
|
+
var STRUCTURE_FIELDS = [
|
|
1912
|
+
{ key: "srcDir", label: "source directory" },
|
|
1913
|
+
{ key: "pages", label: "pages directory" },
|
|
1914
|
+
{ key: "components", label: "components directory" },
|
|
1915
|
+
{ key: "hooks", label: "hooks directory" },
|
|
1916
|
+
{ key: "utils", label: "utilities directory" },
|
|
1917
|
+
{ key: "types", label: "types directory" },
|
|
1918
|
+
{ key: "tests", label: "tests directory" },
|
|
1919
|
+
{ key: "testPattern", label: "test pattern" }
|
|
1920
|
+
];
|
|
1921
|
+
function diffPackage(existing, merged, mergedConfig) {
|
|
1922
|
+
const changes = [];
|
|
1923
|
+
const pkgPrefix = existing.path === "." ? "" : `${existing.path}: `;
|
|
1924
|
+
for (const field of STACK_FIELDS) {
|
|
1925
|
+
const oldVal = existing.stack?.[field];
|
|
1926
|
+
const newVal = merged.stack?.[field];
|
|
1927
|
+
if (!oldVal && newVal) {
|
|
1928
|
+
changes.push({
|
|
1929
|
+
type: "added",
|
|
1930
|
+
description: `${pkgPrefix}Stack: added ${displayStackName(newVal)}`
|
|
1931
|
+
});
|
|
1932
|
+
} else if (oldVal && newVal && oldVal !== newVal) {
|
|
1933
|
+
changes.push({
|
|
1934
|
+
type: "changed",
|
|
1935
|
+
description: `${pkgPrefix}Stack: ${displayStackName(oldVal)} \u2192 ${displayStackName(newVal)}`
|
|
1936
|
+
});
|
|
1871
1937
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1938
|
+
}
|
|
1939
|
+
for (const key of CONVENTION_KEYS) {
|
|
1940
|
+
const oldVal = existing.conventions?.[key];
|
|
1941
|
+
const newVal = merged.conventions?.[key];
|
|
1942
|
+
const label = import_types5.CONVENTION_LABELS[key] ?? key;
|
|
1943
|
+
if (!oldVal && newVal) {
|
|
1944
|
+
changes.push({
|
|
1945
|
+
type: "added",
|
|
1946
|
+
description: `${pkgPrefix}New convention: ${label} (${newVal})`
|
|
1947
|
+
});
|
|
1948
|
+
} else if (oldVal && newVal && oldVal !== newVal) {
|
|
1949
|
+
const suffix = isNewlyDetected(mergedConfig, merged.path, key) ? " (newly detected)" : "";
|
|
1950
|
+
changes.push({
|
|
1951
|
+
type: "changed",
|
|
1952
|
+
description: `${pkgPrefix}Convention updated: ${label} (${newVal})${suffix}`
|
|
1953
|
+
});
|
|
1874
1954
|
}
|
|
1875
1955
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1956
|
+
for (const { key, label } of STRUCTURE_FIELDS) {
|
|
1957
|
+
const oldVal = existing.structure?.[key];
|
|
1958
|
+
const newVal = merged.structure?.[key];
|
|
1959
|
+
if (!oldVal && newVal) {
|
|
1960
|
+
changes.push({
|
|
1961
|
+
type: "added",
|
|
1962
|
+
description: `${pkgPrefix}Structure: detected ${label} (${newVal})`
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1878
1965
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1966
|
+
return changes;
|
|
1967
|
+
}
|
|
1968
|
+
function diffConfigs(existing, merged) {
|
|
1969
|
+
const changes = [];
|
|
1970
|
+
const existingByPath = new Map(existing.packages.map((p) => [p.path, p]));
|
|
1971
|
+
const mergedByPath = new Map(merged.packages.map((p) => [p.path, p]));
|
|
1972
|
+
for (const existingPkg of existing.packages) {
|
|
1973
|
+
const mergedPkg = mergedByPath.get(existingPkg.path);
|
|
1974
|
+
if (mergedPkg) {
|
|
1975
|
+
changes.push(...diffPackage(existingPkg, mergedPkg, merged));
|
|
1976
|
+
}
|
|
1882
1977
|
}
|
|
1883
|
-
const
|
|
1884
|
-
(
|
|
1885
|
-
|
|
1886
|
-
if (packagesWithDirs.length > 0) {
|
|
1887
|
-
console.log(`
|
|
1888
|
-
${import_chalk5.default.bold("Structure:")}`);
|
|
1889
|
-
for (const pkg of packagesWithDirs) {
|
|
1890
|
-
const groups = groupByRole(pkg.structure.directories);
|
|
1891
|
-
if (groups.length === 0) continue;
|
|
1892
|
-
console.log(` ${pkg.relativePath}:`);
|
|
1893
|
-
for (const group of groups) {
|
|
1894
|
-
console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1895
|
-
}
|
|
1978
|
+
for (const mergedPkg of merged.packages) {
|
|
1979
|
+
if (!existingByPath.has(mergedPkg.path)) {
|
|
1980
|
+
changes.push({ type: "added", description: `New package: ${mergedPkg.path}` });
|
|
1896
1981
|
}
|
|
1897
1982
|
}
|
|
1898
|
-
|
|
1899
|
-
displaySummarySection(scanResult);
|
|
1900
|
-
console.log("");
|
|
1983
|
+
return changes;
|
|
1901
1984
|
}
|
|
1902
|
-
function
|
|
1985
|
+
function formatStatsDelta(oldStats, newStats) {
|
|
1986
|
+
const fileDelta = newStats.totalFiles - oldStats.totalFiles;
|
|
1987
|
+
const lineDelta = newStats.totalLines - oldStats.totalLines;
|
|
1988
|
+
if (fileDelta === 0 && lineDelta === 0) return void 0;
|
|
1903
1989
|
const parts = [];
|
|
1904
|
-
if (
|
|
1905
|
-
|
|
1990
|
+
if (fileDelta !== 0) {
|
|
1991
|
+
const sign = fileDelta > 0 ? "+" : "";
|
|
1992
|
+
parts.push(`${sign}${fileDelta.toLocaleString()} files`);
|
|
1906
1993
|
}
|
|
1907
|
-
if (
|
|
1908
|
-
|
|
1994
|
+
if (lineDelta !== 0) {
|
|
1995
|
+
const sign = lineDelta > 0 ? "+" : "";
|
|
1996
|
+
parts.push(`${sign}${lineDelta.toLocaleString()} lines`);
|
|
1909
1997
|
}
|
|
1910
|
-
|
|
1911
|
-
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1912
|
-
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1998
|
+
return `${parts.join(", ")} since last sync`;
|
|
1913
1999
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2000
|
+
|
|
2001
|
+
// src/utils/write-generated-files.ts
|
|
2002
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
2003
|
+
var path8 = __toESM(require("path"), 1);
|
|
2004
|
+
var import_context = require("@viberails/context");
|
|
2005
|
+
var CONTEXT_DIR = ".viberails";
|
|
2006
|
+
var CONTEXT_FILE = "context.md";
|
|
2007
|
+
var SCAN_RESULT_FILE = "scan-result.json";
|
|
2008
|
+
function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
2009
|
+
const contextDir = path8.join(projectRoot, CONTEXT_DIR);
|
|
2010
|
+
try {
|
|
2011
|
+
if (!fs9.existsSync(contextDir)) {
|
|
2012
|
+
fs9.mkdirSync(contextDir, { recursive: true });
|
|
2013
|
+
}
|
|
2014
|
+
const context = (0, import_context.generateContext)(config);
|
|
2015
|
+
fs9.writeFileSync(path8.join(contextDir, CONTEXT_FILE), context);
|
|
2016
|
+
fs9.writeFileSync(
|
|
2017
|
+
path8.join(contextDir, SCAN_RESULT_FILE),
|
|
2018
|
+
`${JSON.stringify(scanResult, null, 2)}
|
|
2019
|
+
`
|
|
2020
|
+
);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2023
|
+
throw new Error(`Failed to write generated files to ${contextDir}: ${message}`);
|
|
1925
2024
|
}
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/commands/config.ts
|
|
2028
|
+
var CONFIG_FILE3 = "viberails.config.json";
|
|
2029
|
+
async function configCommand(options, cwd) {
|
|
2030
|
+
const projectRoot = findProjectRoot(cwd ?? process.cwd());
|
|
2031
|
+
if (!projectRoot) {
|
|
2032
|
+
throw new Error("No package.json found. Make sure you are inside a JS/TS project.");
|
|
1931
2033
|
}
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2034
|
+
const configPath = path9.join(projectRoot, CONFIG_FILE3);
|
|
2035
|
+
if (!fs10.existsSync(configPath)) {
|
|
2036
|
+
console.log(`${import_chalk5.default.yellow("!")} No config found. Run ${import_chalk5.default.cyan("viberails init")} first.`);
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
clack6.intro("viberails config");
|
|
2040
|
+
const config = await (0, import_config6.loadConfig)(configPath);
|
|
2041
|
+
let scanResult = options.rescan ? await rescanAndMerge(projectRoot, config) : void 0;
|
|
2042
|
+
clack6.note(formatRulesText(config).join("\n"), "Current rules");
|
|
2043
|
+
const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
2044
|
+
const overrides = await promptRuleMenu({
|
|
2045
|
+
maxFileLines: config.rules.maxFileLines,
|
|
2046
|
+
testCoverage: config.rules.testCoverage,
|
|
2047
|
+
enforceMissingTests: config.rules.enforceMissingTests,
|
|
2048
|
+
enforceNaming: config.rules.enforceNaming,
|
|
2049
|
+
fileNamingValue: rootPkg.conventions?.fileNaming,
|
|
2050
|
+
coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
|
|
2051
|
+
coverageCommand: config.defaults?.coverage?.command,
|
|
2052
|
+
packageOverrides: config.packages
|
|
2053
|
+
});
|
|
2054
|
+
applyRuleOverrides(config, overrides);
|
|
2055
|
+
if (options.rescan && config.packages.length > 1) {
|
|
2056
|
+
const shouldInfer = await confirm3("Re-infer boundary rules from import patterns?");
|
|
2057
|
+
if (shouldInfer) {
|
|
2058
|
+
const bs = clack6.spinner();
|
|
2059
|
+
bs.start("Building import graph...");
|
|
2060
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
2061
|
+
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
2062
|
+
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
2063
|
+
const inferred = inferBoundaries(graph);
|
|
2064
|
+
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
2065
|
+
if (denyCount > 0) {
|
|
2066
|
+
config.boundaries = inferred;
|
|
2067
|
+
config.rules.enforceBoundaries = true;
|
|
2068
|
+
bs.stop(`Inferred ${denyCount} boundary rules`);
|
|
2069
|
+
} else {
|
|
2070
|
+
bs.stop("No boundary rules inferred");
|
|
1944
2071
|
}
|
|
1945
2072
|
}
|
|
1946
2073
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
2074
|
+
const shouldWrite = await confirm3("Save updated configuration?");
|
|
2075
|
+
if (!shouldWrite) {
|
|
2076
|
+
clack6.outro("No changes written.");
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
const compacted = (0, import_config6.compactConfig)(config);
|
|
2080
|
+
fs10.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
2081
|
+
`);
|
|
2082
|
+
if (!scanResult) {
|
|
2083
|
+
const s = clack6.spinner();
|
|
2084
|
+
s.start("Scanning for context generation...");
|
|
2085
|
+
scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
2086
|
+
s.stop("Scan complete");
|
|
2087
|
+
}
|
|
2088
|
+
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
2089
|
+
clack6.log.success(
|
|
2090
|
+
`Updated:
|
|
2091
|
+
${CONFIG_FILE3}
|
|
2092
|
+
.viberails/context.md
|
|
2093
|
+
.viberails/scan-result.json`
|
|
2094
|
+
);
|
|
2095
|
+
clack6.outro("Done! Run viberails check to verify.");
|
|
2096
|
+
}
|
|
2097
|
+
async function rescanAndMerge(projectRoot, config) {
|
|
2098
|
+
const s = clack6.spinner();
|
|
2099
|
+
s.start("Re-scanning project...");
|
|
2100
|
+
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
2101
|
+
const merged = (0, import_config6.mergeConfig)(config, scanResult);
|
|
2102
|
+
s.stop("Scan complete");
|
|
2103
|
+
const changes = diffConfigs(config, merged);
|
|
2104
|
+
if (changes.length > 0) {
|
|
2105
|
+
const changeLines = changes.map((c) => {
|
|
2106
|
+
const icon = c.type === "removed" ? "-" : "+";
|
|
2107
|
+
return `${icon} ${c.description}`;
|
|
2108
|
+
}).join("\n");
|
|
2109
|
+
clack6.note(changeLines, "Changes detected");
|
|
2110
|
+
} else {
|
|
2111
|
+
clack6.log.info("No new changes detected from scan.");
|
|
2112
|
+
}
|
|
2113
|
+
Object.assign(config, merged);
|
|
2114
|
+
return scanResult;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// src/commands/fix.ts
|
|
2118
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
2119
|
+
var path13 = __toESM(require("path"), 1);
|
|
2120
|
+
var import_config7 = require("@viberails/config");
|
|
2121
|
+
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
2122
|
+
|
|
2123
|
+
// src/commands/fix-helpers.ts
|
|
2124
|
+
var import_node_child_process4 = require("child_process");
|
|
2125
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
2126
|
+
function printPlan(renames, stubs) {
|
|
2127
|
+
if (renames.length > 0) {
|
|
2128
|
+
console.log(import_chalk6.default.bold("\nFile renames:"));
|
|
2129
|
+
for (const r of renames) {
|
|
2130
|
+
console.log(` ${import_chalk6.default.red(r.oldPath)} \u2192 ${import_chalk6.default.green(r.newPath)}`);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
if (stubs.length > 0) {
|
|
2134
|
+
console.log(import_chalk6.default.bold("\nTest stubs to create:"));
|
|
2135
|
+
for (const s of stubs) {
|
|
2136
|
+
console.log(` ${import_chalk6.default.green("+")} ${s.path}`);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
function checkGitDirty(projectRoot) {
|
|
2141
|
+
try {
|
|
2142
|
+
const output = (0, import_node_child_process4.execSync)("git status --porcelain", {
|
|
2143
|
+
cwd: projectRoot,
|
|
2144
|
+
encoding: "utf-8",
|
|
2145
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2146
|
+
});
|
|
2147
|
+
return output.trim().length > 0;
|
|
2148
|
+
} catch {
|
|
2149
|
+
return false;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
function getConventionValue(convention) {
|
|
2153
|
+
if (typeof convention === "string") return convention;
|
|
2154
|
+
return void 0;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// src/commands/fix-imports.ts
|
|
2158
|
+
var path10 = __toESM(require("path"), 1);
|
|
2159
|
+
function stripExtension(filePath) {
|
|
2160
|
+
return filePath.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2161
|
+
}
|
|
2162
|
+
function computeNewSpecifier(oldSpecifier, newBare) {
|
|
2163
|
+
const hasJsExt = oldSpecifier.endsWith(".js");
|
|
2164
|
+
const base = hasJsExt ? oldSpecifier.slice(0, -3) : oldSpecifier;
|
|
2165
|
+
const dir = base.lastIndexOf("/");
|
|
2166
|
+
const prefix = dir >= 0 ? base.slice(0, dir + 1) : "";
|
|
2167
|
+
const newSpec = prefix + newBare;
|
|
2168
|
+
return hasJsExt ? `${newSpec}.js` : newSpec;
|
|
2169
|
+
}
|
|
2170
|
+
async function updateImportsAfterRenames(renames, projectRoot) {
|
|
2171
|
+
if (renames.length === 0) return [];
|
|
2172
|
+
const { Project, SyntaxKind } = await import("ts-morph");
|
|
2173
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
2174
|
+
for (const r of renames) {
|
|
2175
|
+
const oldStripped = stripExtension(r.oldAbsPath);
|
|
2176
|
+
const newFilename = path10.basename(r.newPath);
|
|
2177
|
+
const newName = newFilename.slice(0, newFilename.indexOf("."));
|
|
2178
|
+
renameMap.set(oldStripped, { newBare: newName });
|
|
2179
|
+
}
|
|
2180
|
+
const project = new Project({
|
|
2181
|
+
tsConfigFilePath: void 0,
|
|
2182
|
+
skipAddingFilesFromTsConfig: true
|
|
2183
|
+
});
|
|
2184
|
+
project.addSourceFilesAtPaths(path10.join(projectRoot, "**/*.{ts,tsx,js,jsx,mjs,cjs}"));
|
|
2185
|
+
const updates = [];
|
|
2186
|
+
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
2187
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
2188
|
+
const filePath = sourceFile.getFilePath();
|
|
2189
|
+
const segments = filePath.split(path10.sep);
|
|
2190
|
+
if (segments.includes("node_modules") || segments.includes("dist")) continue;
|
|
2191
|
+
const fileDir = path10.dirname(filePath);
|
|
2192
|
+
for (const decl of sourceFile.getImportDeclarations()) {
|
|
2193
|
+
const specifier = decl.getModuleSpecifierValue();
|
|
2194
|
+
if (!specifier.startsWith(".")) continue;
|
|
2195
|
+
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
2196
|
+
if (!match) continue;
|
|
2197
|
+
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
2198
|
+
updates.push({
|
|
2199
|
+
file: filePath,
|
|
2200
|
+
oldSpecifier: specifier,
|
|
2201
|
+
newSpecifier: newSpec,
|
|
2202
|
+
line: decl.getStartLineNumber()
|
|
2203
|
+
});
|
|
2204
|
+
decl.setModuleSpecifier(newSpec);
|
|
2205
|
+
}
|
|
2206
|
+
for (const decl of sourceFile.getExportDeclarations()) {
|
|
2207
|
+
const specifier = decl.getModuleSpecifierValue();
|
|
2208
|
+
if (!specifier || !specifier.startsWith(".")) continue;
|
|
2209
|
+
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
2210
|
+
if (!match) continue;
|
|
2211
|
+
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
2212
|
+
updates.push({
|
|
2213
|
+
file: filePath,
|
|
2214
|
+
oldSpecifier: specifier,
|
|
2215
|
+
newSpecifier: newSpec,
|
|
2216
|
+
line: decl.getStartLineNumber()
|
|
2217
|
+
});
|
|
2218
|
+
decl.setModuleSpecifier(newSpec);
|
|
2219
|
+
}
|
|
2220
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
2221
|
+
if (call.getExpression().getKind() !== SyntaxKind.ImportKeyword) continue;
|
|
2222
|
+
const args = call.getArguments();
|
|
2223
|
+
if (args.length === 0) continue;
|
|
2224
|
+
const arg = args[0];
|
|
2225
|
+
if (arg.getKind() !== SyntaxKind.StringLiteral) continue;
|
|
2226
|
+
const specifier = arg.getText().slice(1, -1);
|
|
2227
|
+
if (!specifier.startsWith(".")) continue;
|
|
2228
|
+
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
2229
|
+
if (!match) continue;
|
|
2230
|
+
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
2231
|
+
updates.push({
|
|
2232
|
+
file: filePath,
|
|
2233
|
+
oldSpecifier: specifier,
|
|
2234
|
+
newSpecifier: newSpec,
|
|
2235
|
+
line: call.getStartLineNumber()
|
|
2236
|
+
});
|
|
2237
|
+
const quote = arg.getText()[0];
|
|
2238
|
+
arg.replaceWithText(`${quote}${newSpec}${quote}`);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
if (updates.length > 0) {
|
|
2242
|
+
await project.save();
|
|
1954
2243
|
}
|
|
1955
|
-
return
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
// src/display.ts
|
|
1959
|
-
function formatItem(item, nameMap) {
|
|
1960
|
-
const name = nameMap?.[item.name] ?? item.name;
|
|
1961
|
-
return item.version ? `${name} ${item.version}` : name;
|
|
2244
|
+
return updates;
|
|
1962
2245
|
}
|
|
1963
|
-
function
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
2246
|
+
function resolveToRenamedFile(specifier, fromDir, renameMap, extensions) {
|
|
2247
|
+
const cleanSpec = specifier.endsWith(".js") ? specifier.slice(0, -3) : specifier;
|
|
2248
|
+
const resolved = path10.resolve(fromDir, cleanSpec);
|
|
2249
|
+
for (const ext of extensions) {
|
|
2250
|
+
const candidate = resolved + ext;
|
|
2251
|
+
const stripped = stripExtension(candidate);
|
|
2252
|
+
const match = renameMap.get(stripped);
|
|
2253
|
+
if (match) return match;
|
|
1967
2254
|
}
|
|
1968
|
-
return
|
|
2255
|
+
return void 0;
|
|
1969
2256
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2257
|
+
|
|
2258
|
+
// src/commands/fix-naming.ts
|
|
2259
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
2260
|
+
var path11 = __toESM(require("path"), 1);
|
|
2261
|
+
|
|
2262
|
+
// src/commands/convert-name.ts
|
|
2263
|
+
function splitIntoWords(name) {
|
|
2264
|
+
const parts = name.split(/[-_]/);
|
|
2265
|
+
const words = [];
|
|
2266
|
+
for (const part of parts) {
|
|
2267
|
+
if (part === "") continue;
|
|
2268
|
+
let current = "";
|
|
2269
|
+
for (let i = 0; i < part.length; i++) {
|
|
2270
|
+
const ch = part[i];
|
|
2271
|
+
const isUpper = ch >= "A" && ch <= "Z";
|
|
2272
|
+
if (isUpper && current.length > 0) {
|
|
2273
|
+
const prevIsUpper = current[current.length - 1] >= "A" && current[current.length - 1] <= "Z";
|
|
2274
|
+
const nextIsLower = i + 1 < part.length && part[i + 1] >= "a" && part[i + 1] <= "z";
|
|
2275
|
+
if (!prevIsUpper || nextIsLower) {
|
|
2276
|
+
words.push(current.toLowerCase());
|
|
2277
|
+
current = "";
|
|
1990
2278
|
}
|
|
1991
2279
|
}
|
|
1992
|
-
|
|
1993
|
-
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1994
|
-
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1995
|
-
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
2280
|
+
current += ch;
|
|
1996
2281
|
}
|
|
2282
|
+
if (current) words.push(current.toLowerCase());
|
|
1997
2283
|
}
|
|
2284
|
+
return words;
|
|
1998
2285
|
}
|
|
1999
|
-
function
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2286
|
+
function convertName(bare, target) {
|
|
2287
|
+
const words = splitIntoWords(bare);
|
|
2288
|
+
if (words.length === 0) return bare;
|
|
2289
|
+
switch (target) {
|
|
2290
|
+
case "kebab-case":
|
|
2291
|
+
return words.join("-");
|
|
2292
|
+
case "camelCase":
|
|
2293
|
+
return words[0] + words.slice(1).map(capitalize).join("");
|
|
2294
|
+
case "PascalCase":
|
|
2295
|
+
return words.map(capitalize).join("");
|
|
2296
|
+
case "snake_case":
|
|
2297
|
+
return words.join("_");
|
|
2298
|
+
default:
|
|
2299
|
+
return bare;
|
|
2007
2300
|
}
|
|
2008
2301
|
}
|
|
2009
|
-
function
|
|
2010
|
-
if (
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2302
|
+
function capitalize(word) {
|
|
2303
|
+
if (word.length === 0) return word;
|
|
2304
|
+
return word[0].toUpperCase() + word.slice(1);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// src/commands/fix-naming.ts
|
|
2308
|
+
function computeRename(relPath, targetConvention, projectRoot) {
|
|
2309
|
+
const filename = path11.basename(relPath);
|
|
2310
|
+
const dir = path11.dirname(relPath);
|
|
2311
|
+
const dotIndex = filename.indexOf(".");
|
|
2312
|
+
if (dotIndex === -1) return null;
|
|
2313
|
+
const bare = filename.slice(0, dotIndex);
|
|
2314
|
+
const suffix = filename.slice(dotIndex);
|
|
2315
|
+
const newBare = convertName(bare, targetConvention);
|
|
2316
|
+
if (newBare === bare) return null;
|
|
2317
|
+
const newFilename = newBare + suffix;
|
|
2318
|
+
const newRelPath = path11.join(dir, newFilename);
|
|
2319
|
+
const oldAbsPath = path11.join(projectRoot, relPath);
|
|
2320
|
+
const newAbsPath = path11.join(projectRoot, newRelPath);
|
|
2321
|
+
if (fs11.existsSync(newAbsPath)) return null;
|
|
2322
|
+
return { oldPath: relPath, newPath: newRelPath, oldAbsPath, newAbsPath };
|
|
2323
|
+
}
|
|
2324
|
+
function executeRename(rename) {
|
|
2325
|
+
if (fs11.existsSync(rename.newAbsPath)) return false;
|
|
2326
|
+
fs11.renameSync(rename.oldAbsPath, rename.newAbsPath);
|
|
2327
|
+
return true;
|
|
2328
|
+
}
|
|
2329
|
+
function deduplicateRenames(renames) {
|
|
2330
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2331
|
+
const result = [];
|
|
2332
|
+
for (const r of renames) {
|
|
2333
|
+
if (seen.has(r.newAbsPath)) continue;
|
|
2334
|
+
seen.add(r.newAbsPath);
|
|
2335
|
+
result.push(r);
|
|
2019
2336
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2337
|
+
return result;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/commands/fix-tests.ts
|
|
2341
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
2342
|
+
var path12 = __toESM(require("path"), 1);
|
|
2343
|
+
function generateTestStub(sourceRelPath, config, projectRoot) {
|
|
2344
|
+
const pkg = resolvePackageForFile(sourceRelPath, config);
|
|
2345
|
+
const testPattern = pkg?.structure?.testPattern;
|
|
2346
|
+
if (!testPattern) return null;
|
|
2347
|
+
const basename8 = path12.basename(sourceRelPath);
|
|
2348
|
+
const ext = path12.extname(basename8);
|
|
2349
|
+
if (!ext) return null;
|
|
2350
|
+
const stem = basename8.slice(0, -ext.length);
|
|
2351
|
+
const testSuffix = testPattern.replace("*", "");
|
|
2352
|
+
const testFilename = `${stem}${testSuffix}`;
|
|
2353
|
+
const dir = path12.dirname(path12.join(projectRoot, sourceRelPath));
|
|
2354
|
+
const testAbsPath = path12.join(dir, testFilename);
|
|
2355
|
+
if (fs12.existsSync(testAbsPath)) return null;
|
|
2356
|
+
return {
|
|
2357
|
+
path: path12.relative(projectRoot, testAbsPath),
|
|
2358
|
+
absPath: testAbsPath,
|
|
2359
|
+
moduleName: stem
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function writeTestStub(stub, config) {
|
|
2363
|
+
const pkg = resolvePackageForFile(stub.path, config);
|
|
2364
|
+
const testRunner = pkg?.stack?.testRunner ?? "";
|
|
2365
|
+
const runner = testRunner.startsWith("jest") ? "jest" : "vitest";
|
|
2366
|
+
const importLine = runner === "jest" ? "" : "import { describe, it, expect } from 'vitest';\n\n";
|
|
2367
|
+
const content = `${importLine}describe('${stub.moduleName}', () => {
|
|
2368
|
+
it.todo('add tests');
|
|
2369
|
+
});
|
|
2370
|
+
`;
|
|
2371
|
+
fs12.mkdirSync(path12.dirname(stub.absPath), { recursive: true });
|
|
2372
|
+
fs12.writeFileSync(stub.absPath, content);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// src/commands/fix.ts
|
|
2376
|
+
var CONFIG_FILE4 = "viberails.config.json";
|
|
2377
|
+
async function fixCommand(options, cwd) {
|
|
2378
|
+
const startDir = cwd ?? process.cwd();
|
|
2379
|
+
const projectRoot = findProjectRoot(startDir);
|
|
2380
|
+
if (!projectRoot) {
|
|
2381
|
+
console.error(`${import_chalk7.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
2382
|
+
return 1;
|
|
2023
2383
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2384
|
+
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
2385
|
+
if (!fs13.existsSync(configPath)) {
|
|
2386
|
+
console.error(
|
|
2387
|
+
`${import_chalk7.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
2388
|
+
);
|
|
2389
|
+
return 1;
|
|
2026
2390
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2391
|
+
const config = await (0, import_config7.loadConfig)(configPath);
|
|
2392
|
+
if (!options.dryRun) {
|
|
2393
|
+
const isDirty = checkGitDirty(projectRoot);
|
|
2394
|
+
if (isDirty) {
|
|
2395
|
+
console.log(
|
|
2396
|
+
import_chalk7.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2029
2399
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2400
|
+
const shouldFixNaming = !options.rule || options.rule.includes("file-naming");
|
|
2401
|
+
const shouldFixTests = !options.rule || options.rule.includes("missing-test");
|
|
2402
|
+
const allFiles = getAllSourceFiles(projectRoot, config);
|
|
2403
|
+
const renames = [];
|
|
2404
|
+
if (shouldFixNaming) {
|
|
2405
|
+
for (const file of allFiles) {
|
|
2406
|
+
const resolved = resolveConfigForFile(file, config);
|
|
2407
|
+
if (!resolved.rules.enforceNaming || !resolved.conventions.fileNaming) continue;
|
|
2408
|
+
const violation = checkNaming(file, resolved.conventions);
|
|
2409
|
+
if (!violation) continue;
|
|
2410
|
+
const convention = getConventionValue(resolved.conventions.fileNaming);
|
|
2411
|
+
if (!convention) continue;
|
|
2412
|
+
const rename = computeRename(file, convention, projectRoot);
|
|
2413
|
+
if (rename) renames.push(rename);
|
|
2035
2414
|
}
|
|
2036
|
-
|
|
2037
|
-
|
|
2415
|
+
}
|
|
2416
|
+
const dedupedRenames = deduplicateRenames(renames);
|
|
2417
|
+
const testStubs = [];
|
|
2418
|
+
if (shouldFixTests) {
|
|
2419
|
+
const testViolations = checkMissingTests(projectRoot, config, "warn");
|
|
2420
|
+
for (const v of testViolations) {
|
|
2421
|
+
const stub = generateTestStub(v.file, config, projectRoot);
|
|
2422
|
+
if (stub) testStubs.push(stub);
|
|
2038
2423
|
}
|
|
2039
2424
|
}
|
|
2040
|
-
if (
|
|
2041
|
-
console.log(
|
|
2425
|
+
if (dedupedRenames.length === 0 && testStubs.length === 0) {
|
|
2426
|
+
console.log(`${import_chalk7.default.green("\u2713")} No fixable violations found.`);
|
|
2427
|
+
return 0;
|
|
2042
2428
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2429
|
+
printPlan(dedupedRenames, testStubs);
|
|
2430
|
+
if (options.dryRun) {
|
|
2431
|
+
console.log(import_chalk7.default.dim("\nDry run \u2014 no changes applied."));
|
|
2432
|
+
return 0;
|
|
2045
2433
|
}
|
|
2046
|
-
if (
|
|
2047
|
-
|
|
2048
|
-
|
|
2434
|
+
if (!options.yes) {
|
|
2435
|
+
const confirmed = await confirmDangerous("Apply these fixes?");
|
|
2436
|
+
if (!confirmed) {
|
|
2437
|
+
console.log("Aborted.");
|
|
2438
|
+
return 0;
|
|
2049
2439
|
}
|
|
2050
2440
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
for (const group of groups) {
|
|
2056
|
-
console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
2441
|
+
let renameCount = 0;
|
|
2442
|
+
for (const rename of dedupedRenames) {
|
|
2443
|
+
if (executeRename(rename)) {
|
|
2444
|
+
renameCount++;
|
|
2057
2445
|
}
|
|
2058
2446
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2447
|
+
let importUpdateCount = 0;
|
|
2448
|
+
if (renameCount > 0) {
|
|
2449
|
+
const appliedRenames = dedupedRenames.filter((r) => fs13.existsSync(r.newAbsPath));
|
|
2450
|
+
const updates = await updateImportsAfterRenames(appliedRenames, projectRoot);
|
|
2451
|
+
importUpdateCount = updates.length;
|
|
2452
|
+
}
|
|
2453
|
+
let stubCount = 0;
|
|
2454
|
+
for (const stub of testStubs) {
|
|
2455
|
+
if (!fs13.existsSync(stub.absPath)) {
|
|
2456
|
+
writeTestStub(stub, config);
|
|
2457
|
+
stubCount++;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
console.log("");
|
|
2461
|
+
if (renameCount > 0) {
|
|
2462
|
+
console.log(`${import_chalk7.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
2463
|
+
}
|
|
2464
|
+
if (importUpdateCount > 0) {
|
|
2070
2465
|
console.log(
|
|
2071
|
-
|
|
2466
|
+
`${import_chalk7.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
2072
2467
|
);
|
|
2073
|
-
} else if (config.rules.testCoverage > 0) {
|
|
2074
|
-
console.log(` ${import_chalk6.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
|
|
2075
|
-
} else {
|
|
2076
|
-
console.log(` ${import_chalk6.default.dim("\u2022")} Test coverage target: disabled`);
|
|
2077
2468
|
}
|
|
2078
|
-
if (
|
|
2079
|
-
console.log(
|
|
2080
|
-
} else {
|
|
2081
|
-
console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: no`);
|
|
2469
|
+
if (stubCount > 0) {
|
|
2470
|
+
console.log(`${import_chalk7.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
2082
2471
|
}
|
|
2083
|
-
|
|
2084
|
-
` ${import_chalk6.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
2085
|
-
);
|
|
2086
|
-
console.log("");
|
|
2472
|
+
return 0;
|
|
2087
2473
|
}
|
|
2088
2474
|
|
|
2475
|
+
// src/commands/init.ts
|
|
2476
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
2477
|
+
var path18 = __toESM(require("path"), 1);
|
|
2478
|
+
var clack8 = __toESM(require("@clack/prompts"), 1);
|
|
2479
|
+
var import_config8 = require("@viberails/config");
|
|
2480
|
+
var import_scanner2 = require("@viberails/scanner");
|
|
2481
|
+
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
2482
|
+
|
|
2089
2483
|
// src/utils/check-prerequisites.ts
|
|
2090
|
-
var
|
|
2091
|
-
var
|
|
2092
|
-
var
|
|
2093
|
-
var
|
|
2094
|
-
var
|
|
2484
|
+
var import_node_child_process5 = require("child_process");
|
|
2485
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
2486
|
+
var path14 = __toESM(require("path"), 1);
|
|
2487
|
+
var clack7 = __toESM(require("@clack/prompts"), 1);
|
|
2488
|
+
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
2095
2489
|
function checkCoveragePrereqs(projectRoot, scanResult) {
|
|
2096
2490
|
const testRunner = scanResult.stack.testRunner;
|
|
2097
2491
|
if (!testRunner) return [];
|
|
@@ -2116,9 +2510,9 @@ function checkCoveragePrereqs(projectRoot, scanResult) {
|
|
|
2116
2510
|
function displayMissingPrereqs(prereqs) {
|
|
2117
2511
|
const missing = prereqs.filter((p) => !p.installed);
|
|
2118
2512
|
for (const m of missing) {
|
|
2119
|
-
console.log(` ${
|
|
2513
|
+
console.log(` ${import_chalk8.default.yellow("!")} ${m.label} not installed \u2014 ${m.reason}`);
|
|
2120
2514
|
if (m.installCommand) {
|
|
2121
|
-
console.log(` Install: ${
|
|
2515
|
+
console.log(` Install: ${import_chalk8.default.cyan(m.installCommand)}`);
|
|
2122
2516
|
}
|
|
2123
2517
|
}
|
|
2124
2518
|
}
|
|
@@ -2128,11 +2522,11 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
|
|
|
2128
2522
|
const prereqLines = prereqs.map(
|
|
2129
2523
|
(p) => `${p.installed ? "\u2713" : "\u2717"} ${p.label}${p.installed ? "" : ` \u2014 ${p.reason}`}`
|
|
2130
2524
|
).join("\n");
|
|
2131
|
-
|
|
2525
|
+
clack7.note(prereqLines, "Coverage prerequisites");
|
|
2132
2526
|
let disableCoverage = false;
|
|
2133
2527
|
for (const m of missing) {
|
|
2134
2528
|
if (!m.installCommand) continue;
|
|
2135
|
-
const choice = await
|
|
2529
|
+
const choice = await clack7.select({
|
|
2136
2530
|
message: `${m.label} is not installed. It is required for coverage percentage checks.`,
|
|
2137
2531
|
options: [
|
|
2138
2532
|
{
|
|
@@ -2154,9 +2548,9 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
|
|
|
2154
2548
|
});
|
|
2155
2549
|
assertNotCancelled(choice);
|
|
2156
2550
|
if (choice === "install") {
|
|
2157
|
-
const is =
|
|
2551
|
+
const is = clack7.spinner();
|
|
2158
2552
|
is.start(`Installing ${m.label}...`);
|
|
2159
|
-
const result = (0,
|
|
2553
|
+
const result = (0, import_node_child_process5.spawnSync)(m.installCommand, {
|
|
2160
2554
|
cwd: projectRoot,
|
|
2161
2555
|
shell: true,
|
|
2162
2556
|
encoding: "utf-8",
|
|
@@ -2166,16 +2560,16 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
|
|
|
2166
2560
|
is.stop(`Installed ${m.label}`);
|
|
2167
2561
|
} else {
|
|
2168
2562
|
is.stop(`Failed to install ${m.label}`);
|
|
2169
|
-
|
|
2563
|
+
clack7.log.warn(
|
|
2170
2564
|
`Install manually: ${m.installCommand}
|
|
2171
2565
|
Coverage percentage checks will not work until the dependency is installed.`
|
|
2172
2566
|
);
|
|
2173
2567
|
}
|
|
2174
2568
|
} else if (choice === "disable") {
|
|
2175
2569
|
disableCoverage = true;
|
|
2176
|
-
|
|
2570
|
+
clack7.log.info("Coverage percentage checks disabled. Missing-test checks remain active.");
|
|
2177
2571
|
} else {
|
|
2178
|
-
|
|
2572
|
+
clack7.log.info(
|
|
2179
2573
|
`Coverage percentage checks will fail until ${m.label} is installed.
|
|
2180
2574
|
Install later: ${m.installCommand}`
|
|
2181
2575
|
);
|
|
@@ -2185,8 +2579,8 @@ Install later: ${m.installCommand}`
|
|
|
2185
2579
|
}
|
|
2186
2580
|
function hasDependency(projectRoot, name) {
|
|
2187
2581
|
try {
|
|
2188
|
-
const pkgPath =
|
|
2189
|
-
const pkg = JSON.parse(
|
|
2582
|
+
const pkgPath = path14.join(projectRoot, "package.json");
|
|
2583
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
2190
2584
|
return !!(pkg.devDependencies?.[name] || pkg.dependencies?.[name]);
|
|
2191
2585
|
} catch {
|
|
2192
2586
|
return false;
|
|
@@ -2208,84 +2602,58 @@ function filterHighConfidence(conventions, meta) {
|
|
|
2208
2602
|
}
|
|
2209
2603
|
|
|
2210
2604
|
// src/utils/update-gitignore.ts
|
|
2211
|
-
var
|
|
2212
|
-
var
|
|
2605
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
2606
|
+
var path15 = __toESM(require("path"), 1);
|
|
2213
2607
|
function updateGitignore(projectRoot) {
|
|
2214
|
-
const gitignorePath =
|
|
2608
|
+
const gitignorePath = path15.join(projectRoot, ".gitignore");
|
|
2215
2609
|
let content = "";
|
|
2216
|
-
if (
|
|
2217
|
-
content =
|
|
2610
|
+
if (fs15.existsSync(gitignorePath)) {
|
|
2611
|
+
content = fs15.readFileSync(gitignorePath, "utf-8");
|
|
2218
2612
|
}
|
|
2219
2613
|
if (!content.includes(".viberails/scan-result.json")) {
|
|
2220
2614
|
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
2221
2615
|
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
2222
2616
|
`;
|
|
2223
|
-
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
// src/utils/write-generated-files.ts
|
|
2228
|
-
var fs14 = __toESM(require("fs"), 1);
|
|
2229
|
-
var path14 = __toESM(require("path"), 1);
|
|
2230
|
-
var import_context = require("@viberails/context");
|
|
2231
|
-
var CONTEXT_DIR = ".viberails";
|
|
2232
|
-
var CONTEXT_FILE = "context.md";
|
|
2233
|
-
var SCAN_RESULT_FILE = "scan-result.json";
|
|
2234
|
-
function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
2235
|
-
const contextDir = path14.join(projectRoot, CONTEXT_DIR);
|
|
2236
|
-
try {
|
|
2237
|
-
if (!fs14.existsSync(contextDir)) {
|
|
2238
|
-
fs14.mkdirSync(contextDir, { recursive: true });
|
|
2239
|
-
}
|
|
2240
|
-
const context = (0, import_context.generateContext)(config);
|
|
2241
|
-
fs14.writeFileSync(path14.join(contextDir, CONTEXT_FILE), context);
|
|
2242
|
-
fs14.writeFileSync(
|
|
2243
|
-
path14.join(contextDir, SCAN_RESULT_FILE),
|
|
2244
|
-
`${JSON.stringify(scanResult, null, 2)}
|
|
2245
|
-
`
|
|
2246
|
-
);
|
|
2247
|
-
} catch (err) {
|
|
2248
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2249
|
-
throw new Error(`Failed to write generated files to ${contextDir}: ${message}`);
|
|
2617
|
+
fs15.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
2250
2618
|
}
|
|
2251
2619
|
}
|
|
2252
2620
|
|
|
2253
2621
|
// src/commands/init-hooks.ts
|
|
2254
|
-
var
|
|
2255
|
-
var
|
|
2256
|
-
var
|
|
2622
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
2623
|
+
var path16 = __toESM(require("path"), 1);
|
|
2624
|
+
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
2257
2625
|
var import_yaml = require("yaml");
|
|
2258
2626
|
function setupPreCommitHook(projectRoot) {
|
|
2259
|
-
const lefthookPath =
|
|
2260
|
-
if (
|
|
2627
|
+
const lefthookPath = path16.join(projectRoot, "lefthook.yml");
|
|
2628
|
+
if (fs16.existsSync(lefthookPath)) {
|
|
2261
2629
|
addLefthookPreCommit(lefthookPath);
|
|
2262
|
-
console.log(` ${
|
|
2630
|
+
console.log(` ${import_chalk9.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
2263
2631
|
return "lefthook.yml";
|
|
2264
2632
|
}
|
|
2265
|
-
const huskyDir =
|
|
2266
|
-
if (
|
|
2633
|
+
const huskyDir = path16.join(projectRoot, ".husky");
|
|
2634
|
+
if (fs16.existsSync(huskyDir)) {
|
|
2267
2635
|
writeHuskyPreCommit(huskyDir);
|
|
2268
|
-
console.log(` ${
|
|
2636
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
2269
2637
|
return ".husky/pre-commit";
|
|
2270
2638
|
}
|
|
2271
|
-
const gitDir =
|
|
2272
|
-
if (
|
|
2273
|
-
const hooksDir =
|
|
2274
|
-
if (!
|
|
2275
|
-
|
|
2639
|
+
const gitDir = path16.join(projectRoot, ".git");
|
|
2640
|
+
if (fs16.existsSync(gitDir)) {
|
|
2641
|
+
const hooksDir = path16.join(gitDir, "hooks");
|
|
2642
|
+
if (!fs16.existsSync(hooksDir)) {
|
|
2643
|
+
fs16.mkdirSync(hooksDir, { recursive: true });
|
|
2276
2644
|
}
|
|
2277
2645
|
writeGitHookPreCommit(hooksDir);
|
|
2278
|
-
console.log(` ${
|
|
2646
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
2279
2647
|
return ".git/hooks/pre-commit";
|
|
2280
2648
|
}
|
|
2281
2649
|
return void 0;
|
|
2282
2650
|
}
|
|
2283
2651
|
function writeGitHookPreCommit(hooksDir) {
|
|
2284
|
-
const hookPath =
|
|
2285
|
-
if (
|
|
2286
|
-
const existing =
|
|
2652
|
+
const hookPath = path16.join(hooksDir, "pre-commit");
|
|
2653
|
+
if (fs16.existsSync(hookPath)) {
|
|
2654
|
+
const existing = fs16.readFileSync(hookPath, "utf-8");
|
|
2287
2655
|
if (existing.includes("viberails")) return;
|
|
2288
|
-
|
|
2656
|
+
fs16.writeFileSync(
|
|
2289
2657
|
hookPath,
|
|
2290
2658
|
`${existing.trimEnd()}
|
|
2291
2659
|
|
|
@@ -2302,10 +2670,10 @@ npx viberails check --staged
|
|
|
2302
2670
|
"npx viberails check --staged",
|
|
2303
2671
|
""
|
|
2304
2672
|
].join("\n");
|
|
2305
|
-
|
|
2673
|
+
fs16.writeFileSync(hookPath, script, { mode: 493 });
|
|
2306
2674
|
}
|
|
2307
2675
|
function addLefthookPreCommit(lefthookPath) {
|
|
2308
|
-
const content =
|
|
2676
|
+
const content = fs16.readFileSync(lefthookPath, "utf-8");
|
|
2309
2677
|
if (content.includes("viberails")) return;
|
|
2310
2678
|
const doc = (0, import_yaml.parse)(content) ?? {};
|
|
2311
2679
|
if (!doc["pre-commit"]) {
|
|
@@ -2317,29 +2685,29 @@ function addLefthookPreCommit(lefthookPath) {
|
|
|
2317
2685
|
doc["pre-commit"].commands.viberails = {
|
|
2318
2686
|
run: "npx viberails check --staged"
|
|
2319
2687
|
};
|
|
2320
|
-
|
|
2688
|
+
fs16.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
|
|
2321
2689
|
}
|
|
2322
2690
|
function detectHookManager(projectRoot) {
|
|
2323
|
-
if (
|
|
2324
|
-
if (
|
|
2325
|
-
if (
|
|
2691
|
+
if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
2692
|
+
if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
|
|
2693
|
+
if (fs16.existsSync(path16.join(projectRoot, ".git"))) return "git hook";
|
|
2326
2694
|
return void 0;
|
|
2327
2695
|
}
|
|
2328
2696
|
function setupClaudeCodeHook(projectRoot) {
|
|
2329
|
-
const claudeDir =
|
|
2330
|
-
if (!
|
|
2331
|
-
|
|
2697
|
+
const claudeDir = path16.join(projectRoot, ".claude");
|
|
2698
|
+
if (!fs16.existsSync(claudeDir)) {
|
|
2699
|
+
fs16.mkdirSync(claudeDir, { recursive: true });
|
|
2332
2700
|
}
|
|
2333
|
-
const settingsPath =
|
|
2701
|
+
const settingsPath = path16.join(claudeDir, "settings.json");
|
|
2334
2702
|
let settings = {};
|
|
2335
|
-
if (
|
|
2703
|
+
if (fs16.existsSync(settingsPath)) {
|
|
2336
2704
|
try {
|
|
2337
|
-
settings = JSON.parse(
|
|
2705
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
2338
2706
|
} catch {
|
|
2339
2707
|
console.warn(
|
|
2340
|
-
` ${
|
|
2708
|
+
` ${import_chalk9.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
2341
2709
|
);
|
|
2342
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
2710
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk9.default.cyan("viberails init --force")}`);
|
|
2343
2711
|
return;
|
|
2344
2712
|
}
|
|
2345
2713
|
}
|
|
@@ -2360,30 +2728,30 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
2360
2728
|
}
|
|
2361
2729
|
];
|
|
2362
2730
|
settings.hooks = hooks;
|
|
2363
|
-
|
|
2731
|
+
fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
2364
2732
|
`);
|
|
2365
|
-
console.log(` ${
|
|
2733
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
2366
2734
|
}
|
|
2367
2735
|
function setupClaudeMdReference(projectRoot) {
|
|
2368
|
-
const claudeMdPath =
|
|
2736
|
+
const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
|
|
2369
2737
|
let content = "";
|
|
2370
|
-
if (
|
|
2371
|
-
content =
|
|
2738
|
+
if (fs16.existsSync(claudeMdPath)) {
|
|
2739
|
+
content = fs16.readFileSync(claudeMdPath, "utf-8");
|
|
2372
2740
|
}
|
|
2373
2741
|
if (content.includes("@.viberails/context.md")) return;
|
|
2374
2742
|
const ref = "\n@.viberails/context.md\n";
|
|
2375
2743
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
2376
|
-
|
|
2377
|
-
console.log(` ${
|
|
2744
|
+
fs16.writeFileSync(claudeMdPath, prefix + ref);
|
|
2745
|
+
console.log(` ${import_chalk9.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
2378
2746
|
}
|
|
2379
2747
|
function setupGithubAction(projectRoot, packageManager) {
|
|
2380
|
-
const workflowDir =
|
|
2381
|
-
const workflowPath =
|
|
2382
|
-
if (
|
|
2383
|
-
const existing =
|
|
2748
|
+
const workflowDir = path16.join(projectRoot, ".github", "workflows");
|
|
2749
|
+
const workflowPath = path16.join(workflowDir, "viberails.yml");
|
|
2750
|
+
if (fs16.existsSync(workflowPath)) {
|
|
2751
|
+
const existing = fs16.readFileSync(workflowPath, "utf-8");
|
|
2384
2752
|
if (existing.includes("viberails")) return void 0;
|
|
2385
2753
|
}
|
|
2386
|
-
|
|
2754
|
+
fs16.mkdirSync(workflowDir, { recursive: true });
|
|
2387
2755
|
const pm = packageManager || "npm";
|
|
2388
2756
|
const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
|
|
2389
2757
|
const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
|
|
@@ -2417,71 +2785,71 @@ function setupGithubAction(projectRoot, packageManager) {
|
|
|
2417
2785
|
""
|
|
2418
2786
|
);
|
|
2419
2787
|
const content = lines.filter((l) => l !== void 0).join("\n");
|
|
2420
|
-
|
|
2788
|
+
fs16.writeFileSync(workflowPath, content);
|
|
2421
2789
|
return ".github/workflows/viberails.yml";
|
|
2422
2790
|
}
|
|
2423
2791
|
function writeHuskyPreCommit(huskyDir) {
|
|
2424
|
-
const hookPath =
|
|
2425
|
-
if (
|
|
2426
|
-
const existing =
|
|
2792
|
+
const hookPath = path16.join(huskyDir, "pre-commit");
|
|
2793
|
+
if (fs16.existsSync(hookPath)) {
|
|
2794
|
+
const existing = fs16.readFileSync(hookPath, "utf-8");
|
|
2427
2795
|
if (!existing.includes("viberails")) {
|
|
2428
|
-
|
|
2796
|
+
fs16.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
2429
2797
|
npx viberails check --staged
|
|
2430
2798
|
`);
|
|
2431
2799
|
}
|
|
2432
2800
|
return;
|
|
2433
2801
|
}
|
|
2434
|
-
|
|
2802
|
+
fs16.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
|
|
2435
2803
|
}
|
|
2436
2804
|
|
|
2437
2805
|
// src/commands/init-hooks-extra.ts
|
|
2438
|
-
var
|
|
2439
|
-
var
|
|
2440
|
-
var
|
|
2806
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
2807
|
+
var path17 = __toESM(require("path"), 1);
|
|
2808
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
2441
2809
|
var import_yaml2 = require("yaml");
|
|
2442
2810
|
function addPreCommitStep(projectRoot, name, command, marker) {
|
|
2443
|
-
const lefthookPath =
|
|
2444
|
-
if (
|
|
2445
|
-
const content =
|
|
2811
|
+
const lefthookPath = path17.join(projectRoot, "lefthook.yml");
|
|
2812
|
+
if (fs17.existsSync(lefthookPath)) {
|
|
2813
|
+
const content = fs17.readFileSync(lefthookPath, "utf-8");
|
|
2446
2814
|
if (content.includes(marker)) return void 0;
|
|
2447
2815
|
const doc = (0, import_yaml2.parse)(content) ?? {};
|
|
2448
2816
|
if (!doc["pre-commit"]) doc["pre-commit"] = { commands: {} };
|
|
2449
2817
|
if (!doc["pre-commit"].commands) doc["pre-commit"].commands = {};
|
|
2450
2818
|
doc["pre-commit"].commands[name] = { run: command };
|
|
2451
|
-
|
|
2819
|
+
fs17.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
|
|
2452
2820
|
return "lefthook.yml";
|
|
2453
2821
|
}
|
|
2454
|
-
const huskyDir =
|
|
2455
|
-
if (
|
|
2456
|
-
const hookPath =
|
|
2457
|
-
if (
|
|
2458
|
-
const existing =
|
|
2822
|
+
const huskyDir = path17.join(projectRoot, ".husky");
|
|
2823
|
+
if (fs17.existsSync(huskyDir)) {
|
|
2824
|
+
const hookPath = path17.join(huskyDir, "pre-commit");
|
|
2825
|
+
if (fs17.existsSync(hookPath)) {
|
|
2826
|
+
const existing = fs17.readFileSync(hookPath, "utf-8");
|
|
2459
2827
|
if (existing.includes(marker)) return void 0;
|
|
2460
|
-
|
|
2828
|
+
fs17.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
2461
2829
|
${command}
|
|
2462
2830
|
`);
|
|
2463
2831
|
} else {
|
|
2464
|
-
|
|
2832
|
+
fs17.writeFileSync(hookPath, `#!/bin/sh
|
|
2465
2833
|
${command}
|
|
2466
2834
|
`, { mode: 493 });
|
|
2467
2835
|
}
|
|
2468
2836
|
return ".husky/pre-commit";
|
|
2469
2837
|
}
|
|
2470
|
-
const gitDir =
|
|
2471
|
-
if (
|
|
2472
|
-
const hooksDir =
|
|
2473
|
-
if (!
|
|
2474
|
-
const hookPath =
|
|
2475
|
-
if (
|
|
2476
|
-
const existing =
|
|
2838
|
+
const gitDir = path17.join(projectRoot, ".git");
|
|
2839
|
+
if (fs17.existsSync(gitDir)) {
|
|
2840
|
+
const hooksDir = path17.join(gitDir, "hooks");
|
|
2841
|
+
if (!fs17.existsSync(hooksDir)) fs17.mkdirSync(hooksDir, { recursive: true });
|
|
2842
|
+
const hookPath = path17.join(hooksDir, "pre-commit");
|
|
2843
|
+
if (fs17.existsSync(hookPath)) {
|
|
2844
|
+
const existing = fs17.readFileSync(hookPath, "utf-8");
|
|
2477
2845
|
if (existing.includes(marker)) return void 0;
|
|
2478
|
-
|
|
2846
|
+
fs17.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
2479
2847
|
|
|
2480
2848
|
# ${name}
|
|
2481
2849
|
${command}
|
|
2482
2850
|
`);
|
|
2483
2851
|
} else {
|
|
2484
|
-
|
|
2852
|
+
fs17.writeFileSync(hookPath, `#!/bin/sh
|
|
2485
2853
|
# Generated by viberails
|
|
2486
2854
|
|
|
2487
2855
|
# ${name}
|
|
@@ -2497,7 +2865,7 @@ ${command}
|
|
|
2497
2865
|
function setupTypecheckHook(projectRoot) {
|
|
2498
2866
|
const target = addPreCommitStep(projectRoot, "typecheck", "npx tsc --noEmit", "tsc");
|
|
2499
2867
|
if (target) {
|
|
2500
|
-
console.log(` ${
|
|
2868
|
+
console.log(` ${import_chalk10.default.green("\u2713")} ${target} \u2014 added typecheck (tsc --noEmit)`);
|
|
2501
2869
|
}
|
|
2502
2870
|
return target;
|
|
2503
2871
|
}
|
|
@@ -2506,7 +2874,7 @@ function setupLintHook(projectRoot, linter) {
|
|
|
2506
2874
|
const linterName = linter === "biome" ? "Biome" : "ESLint";
|
|
2507
2875
|
const target = addPreCommitStep(projectRoot, "lint", command, linter);
|
|
2508
2876
|
if (target) {
|
|
2509
|
-
console.log(` ${
|
|
2877
|
+
console.log(` ${import_chalk10.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
2510
2878
|
}
|
|
2511
2879
|
return target;
|
|
2512
2880
|
}
|
|
@@ -2540,7 +2908,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
2540
2908
|
}
|
|
2541
2909
|
|
|
2542
2910
|
// src/commands/init.ts
|
|
2543
|
-
var
|
|
2911
|
+
var CONFIG_FILE5 = "viberails.config.json";
|
|
2544
2912
|
function getExemptedPackages(config) {
|
|
2545
2913
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
2546
2914
|
}
|
|
@@ -2551,11 +2919,11 @@ async function initCommand(options, cwd) {
|
|
|
2551
2919
|
"No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
|
|
2552
2920
|
);
|
|
2553
2921
|
}
|
|
2554
|
-
const configPath =
|
|
2555
|
-
if (
|
|
2922
|
+
const configPath = path18.join(projectRoot, CONFIG_FILE5);
|
|
2923
|
+
if (fs18.existsSync(configPath) && !options.force) {
|
|
2556
2924
|
console.log(
|
|
2557
|
-
`${
|
|
2558
|
-
Run ${
|
|
2925
|
+
`${import_chalk11.default.yellow("!")} viberails is already initialized.
|
|
2926
|
+
Run ${import_chalk11.default.cyan("viberails config")} to edit rules, ${import_chalk11.default.cyan("viberails sync")} to update, or ${import_chalk11.default.cyan("viberails init --force")} to start fresh.`
|
|
2559
2927
|
);
|
|
2560
2928
|
return;
|
|
2561
2929
|
}
|
|
@@ -2563,9 +2931,9 @@ async function initCommand(options, cwd) {
|
|
|
2563
2931
|
await initInteractive(projectRoot, configPath, options);
|
|
2564
2932
|
}
|
|
2565
2933
|
async function initNonInteractive(projectRoot, configPath) {
|
|
2566
|
-
console.log(
|
|
2567
|
-
const scanResult = await (0,
|
|
2568
|
-
const config = (0,
|
|
2934
|
+
console.log(import_chalk11.default.dim("Scanning project..."));
|
|
2935
|
+
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
2936
|
+
const config = (0, import_config8.generateConfig)(scanResult);
|
|
2569
2937
|
for (const pkg of config.packages) {
|
|
2570
2938
|
const pkgMeta = config._meta?.packages?.[pkg.path]?.conventions;
|
|
2571
2939
|
pkg.conventions = filterHighConfidence(pkg.conventions ?? {}, pkgMeta);
|
|
@@ -2576,11 +2944,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
2576
2944
|
const exempted = getExemptedPackages(config);
|
|
2577
2945
|
if (exempted.length > 0) {
|
|
2578
2946
|
console.log(
|
|
2579
|
-
` ${
|
|
2947
|
+
` ${import_chalk11.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk11.default.dim("(types-only)")}`
|
|
2580
2948
|
);
|
|
2581
2949
|
}
|
|
2582
2950
|
if (config.packages.length > 1) {
|
|
2583
|
-
console.log(
|
|
2951
|
+
console.log(import_chalk11.default.dim("Building import graph..."));
|
|
2584
2952
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
2585
2953
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
2586
2954
|
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
@@ -2592,51 +2960,51 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
2592
2960
|
console.log(` Inferred ${denyCount} boundary rules`);
|
|
2593
2961
|
}
|
|
2594
2962
|
}
|
|
2595
|
-
const compacted = (0,
|
|
2596
|
-
|
|
2963
|
+
const compacted = (0, import_config8.compactConfig)(config);
|
|
2964
|
+
fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
2597
2965
|
`);
|
|
2598
2966
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
2599
2967
|
updateGitignore(projectRoot);
|
|
2600
2968
|
setupClaudeCodeHook(projectRoot);
|
|
2601
2969
|
setupClaudeMdReference(projectRoot);
|
|
2602
|
-
const preCommitTarget = setupPreCommitHook(projectRoot);
|
|
2603
2970
|
const rootPkg = config.packages[0];
|
|
2604
2971
|
const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
|
|
2605
2972
|
const actionTarget = setupGithubAction(projectRoot, rootPkgPm);
|
|
2606
|
-
const
|
|
2973
|
+
const hookManager = detectHookManager(projectRoot);
|
|
2974
|
+
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
2975
|
+
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
2607
2976
|
const linter = rootPkg?.stack?.linter?.split("@")[0];
|
|
2608
|
-
const
|
|
2609
|
-
const ok = import_chalk10.default.green("\u2713");
|
|
2977
|
+
const ok = import_chalk11.default.green("\u2713");
|
|
2610
2978
|
const created = [
|
|
2611
|
-
`${ok} ${
|
|
2979
|
+
`${ok} ${path18.basename(configPath)}`,
|
|
2612
2980
|
`${ok} .viberails/context.md`,
|
|
2613
2981
|
`${ok} .viberails/scan-result.json`,
|
|
2614
2982
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
2615
2983
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
2616
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
2617
|
-
typecheckTarget ? `${ok} ${typecheckTarget} \u2014 added typecheck` : "",
|
|
2618
|
-
lintTarget ? `${ok} ${lintTarget} \u2014 added lint check` : "",
|
|
2984
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk11.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
2619
2985
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
2620
2986
|
].filter(Boolean);
|
|
2987
|
+
if (hasHookManager && rootPkg?.stack?.language === "typescript") setupTypecheckHook(projectRoot);
|
|
2988
|
+
if (hasHookManager && linter) setupLintHook(projectRoot, linter);
|
|
2621
2989
|
console.log(`
|
|
2622
2990
|
Created:
|
|
2623
2991
|
${created.map((f) => ` ${f}`).join("\n")}`);
|
|
2624
2992
|
}
|
|
2625
2993
|
async function initInteractive(projectRoot, configPath, options) {
|
|
2626
|
-
|
|
2627
|
-
if (
|
|
2994
|
+
clack8.intro("viberails");
|
|
2995
|
+
if (fs18.existsSync(configPath) && options.force) {
|
|
2628
2996
|
const replace = await confirmDangerous(
|
|
2629
|
-
`${
|
|
2997
|
+
`${path18.basename(configPath)} already exists and will be replaced. Continue?`
|
|
2630
2998
|
);
|
|
2631
2999
|
if (!replace) {
|
|
2632
|
-
|
|
3000
|
+
clack8.outro("Aborted. No files were written.");
|
|
2633
3001
|
return;
|
|
2634
3002
|
}
|
|
2635
3003
|
}
|
|
2636
|
-
const s =
|
|
3004
|
+
const s = clack8.spinner();
|
|
2637
3005
|
s.start("Scanning project...");
|
|
2638
|
-
const scanResult = await (0,
|
|
2639
|
-
const config = (0,
|
|
3006
|
+
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3007
|
+
const config = (0, import_config8.generateConfig)(scanResult);
|
|
2640
3008
|
s.stop("Scan complete");
|
|
2641
3009
|
const prereqResult = await promptMissingPrereqs(
|
|
2642
3010
|
projectRoot,
|
|
@@ -2646,16 +3014,13 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
2646
3014
|
config.rules.testCoverage = 0;
|
|
2647
3015
|
}
|
|
2648
3016
|
if (scanResult.statistics.totalFiles === 0) {
|
|
2649
|
-
|
|
3017
|
+
clack8.log.warn(
|
|
2650
3018
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
2651
3019
|
);
|
|
2652
3020
|
}
|
|
2653
|
-
|
|
2654
|
-
const rulesLines = formatRulesText(config);
|
|
3021
|
+
clack8.note(formatScanResultsText(scanResult), "Scan results");
|
|
2655
3022
|
const exemptedPkgs = getExemptedPackages(config);
|
|
2656
|
-
|
|
2657
|
-
rulesLines.push(`Auto-exempted from coverage: ${exemptedPkgs.join(", ")} (types-only)`);
|
|
2658
|
-
clack7.note(rulesLines.join("\n"), "Rules");
|
|
3023
|
+
displayInitSummary(config, exemptedPkgs);
|
|
2659
3024
|
const decision = await promptInitDecision();
|
|
2660
3025
|
if (decision === "customize") {
|
|
2661
3026
|
const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
@@ -2669,41 +3034,16 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
2669
3034
|
coverageCommand: config.defaults?.coverage?.command,
|
|
2670
3035
|
packageOverrides: config.packages
|
|
2671
3036
|
});
|
|
2672
|
-
|
|
2673
|
-
config.rules.maxFileLines = overrides.maxFileLines;
|
|
2674
|
-
config.rules.testCoverage = overrides.testCoverage;
|
|
2675
|
-
config.rules.enforceMissingTests = overrides.enforceMissingTests;
|
|
2676
|
-
config.rules.enforceNaming = overrides.enforceNaming;
|
|
2677
|
-
for (const pkg of config.packages) {
|
|
2678
|
-
pkg.coverage = pkg.coverage ?? {};
|
|
2679
|
-
if (pkg.coverage.summaryPath === void 0) {
|
|
2680
|
-
pkg.coverage.summaryPath = overrides.coverageSummaryPath;
|
|
2681
|
-
}
|
|
2682
|
-
if (pkg.coverage.command === void 0 && overrides.coverageCommand) {
|
|
2683
|
-
pkg.coverage.command = overrides.coverageCommand;
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
if (overrides.fileNamingValue) {
|
|
2687
|
-
const oldNaming = rootPkg.conventions?.fileNaming;
|
|
2688
|
-
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
2689
|
-
rootPkg.conventions.fileNaming = overrides.fileNamingValue;
|
|
2690
|
-
if (oldNaming && oldNaming !== overrides.fileNamingValue) {
|
|
2691
|
-
for (const pkg of config.packages) {
|
|
2692
|
-
if (pkg.conventions?.fileNaming === oldNaming) {
|
|
2693
|
-
pkg.conventions.fileNaming = overrides.fileNamingValue;
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
3037
|
+
applyRuleOverrides(config, overrides);
|
|
2698
3038
|
}
|
|
2699
3039
|
if (config.packages.length > 1) {
|
|
2700
|
-
|
|
3040
|
+
clack8.note(
|
|
2701
3041
|
"Boundary rules prevent packages from importing where they\nshouldn't. viberails scans your existing imports and creates\nrules based on what's already working.",
|
|
2702
3042
|
"Boundaries"
|
|
2703
3043
|
);
|
|
2704
3044
|
const shouldInfer = await confirm3("Infer boundary rules from import patterns?");
|
|
2705
3045
|
if (shouldInfer) {
|
|
2706
|
-
const bs =
|
|
3046
|
+
const bs = clack8.spinner();
|
|
2707
3047
|
bs.start("Building import graph...");
|
|
2708
3048
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
2709
3049
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -2715,7 +3055,7 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
2715
3055
|
config.rules.enforceBoundaries = true;
|
|
2716
3056
|
bs.stop(`Inferred ${denyCount} boundary rules`);
|
|
2717
3057
|
const boundaryLines = Object.entries(inferred.deny).map(([pkg, denied]) => `${pkg} must NOT import from: ${denied.join(", ")}`).join("\n");
|
|
2718
|
-
|
|
3058
|
+
clack8.note(boundaryLines, "Boundary rules");
|
|
2719
3059
|
} else {
|
|
2720
3060
|
bs.stop("No boundary rules inferred");
|
|
2721
3061
|
}
|
|
@@ -2723,22 +3063,23 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
2723
3063
|
}
|
|
2724
3064
|
const hookManager = detectHookManager(projectRoot);
|
|
2725
3065
|
const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
|
|
2726
|
-
const integrations = await promptIntegrations(hookManager, {
|
|
3066
|
+
const integrations = await promptIntegrations(projectRoot, hookManager, {
|
|
2727
3067
|
isTypeScript: rootPkgStack?.language === "typescript",
|
|
2728
|
-
linter: rootPkgStack?.linter?.split("@")[0]
|
|
3068
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3069
|
+
packageManager: rootPkgStack?.packageManager
|
|
2729
3070
|
});
|
|
2730
3071
|
const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
|
|
2731
3072
|
if (!shouldWrite) {
|
|
2732
|
-
|
|
3073
|
+
clack8.outro("Aborted. No files were written.");
|
|
2733
3074
|
return;
|
|
2734
3075
|
}
|
|
2735
|
-
const compacted = (0,
|
|
2736
|
-
|
|
3076
|
+
const compacted = (0, import_config8.compactConfig)(config);
|
|
3077
|
+
fs18.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
2737
3078
|
`);
|
|
2738
3079
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
2739
3080
|
updateGitignore(projectRoot);
|
|
2740
3081
|
const createdFiles = [
|
|
2741
|
-
|
|
3082
|
+
path18.basename(configPath),
|
|
2742
3083
|
".viberails/context.md",
|
|
2743
3084
|
".viberails/scan-result.json",
|
|
2744
3085
|
...setupSelectedIntegrations(projectRoot, integrations, {
|
|
@@ -2746,155 +3087,27 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
2746
3087
|
packageManager: rootPkgStack?.packageManager
|
|
2747
3088
|
})
|
|
2748
3089
|
];
|
|
2749
|
-
|
|
3090
|
+
clack8.log.success(`Created:
|
|
2750
3091
|
${createdFiles.map((f) => ` ${f}`).join("\n")}`);
|
|
2751
|
-
|
|
3092
|
+
clack8.outro(
|
|
2752
3093
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
2753
|
-
${
|
|
3094
|
+
${import_chalk11.default.dim("Tip: use")} ${import_chalk11.default.cyan("viberails check --enforce")} ${import_chalk11.default.dim("in CI to block PRs on violations.")}`
|
|
2754
3095
|
);
|
|
2755
3096
|
}
|
|
2756
3097
|
|
|
2757
3098
|
// src/commands/sync.ts
|
|
2758
|
-
var
|
|
2759
|
-
var
|
|
2760
|
-
var
|
|
2761
|
-
var
|
|
2762
|
-
var
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
var import_types5 = require("@viberails/types");
|
|
2766
|
-
function parseStackString(s) {
|
|
2767
|
-
const atIdx = s.indexOf("@");
|
|
2768
|
-
if (atIdx > 0) {
|
|
2769
|
-
return { name: s.slice(0, atIdx), version: s.slice(atIdx + 1) };
|
|
2770
|
-
}
|
|
2771
|
-
return { name: s };
|
|
2772
|
-
}
|
|
2773
|
-
function displayStackName(s) {
|
|
2774
|
-
const { name, version } = parseStackString(s);
|
|
2775
|
-
const allMaps = {
|
|
2776
|
-
...import_types5.FRAMEWORK_NAMES,
|
|
2777
|
-
...import_types5.STYLING_NAMES,
|
|
2778
|
-
...import_types5.ORM_NAMES
|
|
2779
|
-
};
|
|
2780
|
-
const display = allMaps[name] ?? name;
|
|
2781
|
-
return version ? `${display} ${version}` : display;
|
|
2782
|
-
}
|
|
2783
|
-
function isNewlyDetected(config, pkgPath, key) {
|
|
2784
|
-
return config._meta?.packages?.[pkgPath]?.conventions?.[key]?.detected === true;
|
|
2785
|
-
}
|
|
2786
|
-
var STACK_FIELDS = [
|
|
2787
|
-
"framework",
|
|
2788
|
-
"styling",
|
|
2789
|
-
"backend",
|
|
2790
|
-
"orm",
|
|
2791
|
-
"linter",
|
|
2792
|
-
"formatter",
|
|
2793
|
-
"testRunner"
|
|
2794
|
-
];
|
|
2795
|
-
var CONVENTION_KEYS = [
|
|
2796
|
-
"fileNaming",
|
|
2797
|
-
"componentNaming",
|
|
2798
|
-
"hookNaming",
|
|
2799
|
-
"importAlias"
|
|
2800
|
-
];
|
|
2801
|
-
var STRUCTURE_FIELDS = [
|
|
2802
|
-
{ key: "srcDir", label: "source directory" },
|
|
2803
|
-
{ key: "pages", label: "pages directory" },
|
|
2804
|
-
{ key: "components", label: "components directory" },
|
|
2805
|
-
{ key: "hooks", label: "hooks directory" },
|
|
2806
|
-
{ key: "utils", label: "utilities directory" },
|
|
2807
|
-
{ key: "types", label: "types directory" },
|
|
2808
|
-
{ key: "tests", label: "tests directory" },
|
|
2809
|
-
{ key: "testPattern", label: "test pattern" }
|
|
2810
|
-
];
|
|
2811
|
-
function diffPackage(existing, merged, mergedConfig) {
|
|
2812
|
-
const changes = [];
|
|
2813
|
-
const pkgPrefix = existing.path === "." ? "" : `${existing.path}: `;
|
|
2814
|
-
for (const field of STACK_FIELDS) {
|
|
2815
|
-
const oldVal = existing.stack?.[field];
|
|
2816
|
-
const newVal = merged.stack?.[field];
|
|
2817
|
-
if (!oldVal && newVal) {
|
|
2818
|
-
changes.push({
|
|
2819
|
-
type: "added",
|
|
2820
|
-
description: `${pkgPrefix}Stack: added ${displayStackName(newVal)}`
|
|
2821
|
-
});
|
|
2822
|
-
} else if (oldVal && newVal && oldVal !== newVal) {
|
|
2823
|
-
changes.push({
|
|
2824
|
-
type: "changed",
|
|
2825
|
-
description: `${pkgPrefix}Stack: ${displayStackName(oldVal)} \u2192 ${displayStackName(newVal)}`
|
|
2826
|
-
});
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
for (const key of CONVENTION_KEYS) {
|
|
2830
|
-
const oldVal = existing.conventions?.[key];
|
|
2831
|
-
const newVal = merged.conventions?.[key];
|
|
2832
|
-
const label = import_types5.CONVENTION_LABELS[key] ?? key;
|
|
2833
|
-
if (!oldVal && newVal) {
|
|
2834
|
-
changes.push({
|
|
2835
|
-
type: "added",
|
|
2836
|
-
description: `${pkgPrefix}New convention: ${label} (${newVal})`
|
|
2837
|
-
});
|
|
2838
|
-
} else if (oldVal && newVal && oldVal !== newVal) {
|
|
2839
|
-
const suffix = isNewlyDetected(mergedConfig, merged.path, key) ? " (newly detected)" : "";
|
|
2840
|
-
changes.push({
|
|
2841
|
-
type: "changed",
|
|
2842
|
-
description: `${pkgPrefix}Convention updated: ${label} (${newVal})${suffix}`
|
|
2843
|
-
});
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
for (const { key, label } of STRUCTURE_FIELDS) {
|
|
2847
|
-
const oldVal = existing.structure?.[key];
|
|
2848
|
-
const newVal = merged.structure?.[key];
|
|
2849
|
-
if (!oldVal && newVal) {
|
|
2850
|
-
changes.push({
|
|
2851
|
-
type: "added",
|
|
2852
|
-
description: `${pkgPrefix}Structure: detected ${label} (${newVal})`
|
|
2853
|
-
});
|
|
2854
|
-
}
|
|
2855
|
-
}
|
|
2856
|
-
return changes;
|
|
2857
|
-
}
|
|
2858
|
-
function diffConfigs(existing, merged) {
|
|
2859
|
-
const changes = [];
|
|
2860
|
-
const existingByPath = new Map(existing.packages.map((p) => [p.path, p]));
|
|
2861
|
-
const mergedByPath = new Map(merged.packages.map((p) => [p.path, p]));
|
|
2862
|
-
for (const existingPkg of existing.packages) {
|
|
2863
|
-
const mergedPkg = mergedByPath.get(existingPkg.path);
|
|
2864
|
-
if (mergedPkg) {
|
|
2865
|
-
changes.push(...diffPackage(existingPkg, mergedPkg, merged));
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
for (const mergedPkg of merged.packages) {
|
|
2869
|
-
if (!existingByPath.has(mergedPkg.path)) {
|
|
2870
|
-
changes.push({ type: "added", description: `New package: ${mergedPkg.path}` });
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
return changes;
|
|
2874
|
-
}
|
|
2875
|
-
function formatStatsDelta(oldStats, newStats) {
|
|
2876
|
-
const fileDelta = newStats.totalFiles - oldStats.totalFiles;
|
|
2877
|
-
const lineDelta = newStats.totalLines - oldStats.totalLines;
|
|
2878
|
-
if (fileDelta === 0 && lineDelta === 0) return void 0;
|
|
2879
|
-
const parts = [];
|
|
2880
|
-
if (fileDelta !== 0) {
|
|
2881
|
-
const sign = fileDelta > 0 ? "+" : "";
|
|
2882
|
-
parts.push(`${sign}${fileDelta.toLocaleString()} files`);
|
|
2883
|
-
}
|
|
2884
|
-
if (lineDelta !== 0) {
|
|
2885
|
-
const sign = lineDelta > 0 ? "+" : "";
|
|
2886
|
-
parts.push(`${sign}${lineDelta.toLocaleString()} lines`);
|
|
2887
|
-
}
|
|
2888
|
-
return `${parts.join(", ")} since last sync`;
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
|
-
// src/commands/sync.ts
|
|
2892
|
-
var CONFIG_FILE5 = "viberails.config.json";
|
|
3099
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
3100
|
+
var path19 = __toESM(require("path"), 1);
|
|
3101
|
+
var clack9 = __toESM(require("@clack/prompts"), 1);
|
|
3102
|
+
var import_config9 = require("@viberails/config");
|
|
3103
|
+
var import_scanner3 = require("@viberails/scanner");
|
|
3104
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3105
|
+
var CONFIG_FILE6 = "viberails.config.json";
|
|
2893
3106
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
2894
3107
|
function loadPreviousStats(projectRoot) {
|
|
2895
|
-
const scanResultPath =
|
|
3108
|
+
const scanResultPath = path19.join(projectRoot, SCAN_RESULT_FILE2);
|
|
2896
3109
|
try {
|
|
2897
|
-
const raw =
|
|
3110
|
+
const raw = fs19.readFileSync(scanResultPath, "utf-8");
|
|
2898
3111
|
const parsed = JSON.parse(raw);
|
|
2899
3112
|
if (parsed?.statistics?.totalFiles !== void 0) {
|
|
2900
3113
|
return parsed.statistics;
|
|
@@ -2903,7 +3116,7 @@ function loadPreviousStats(projectRoot) {
|
|
|
2903
3116
|
}
|
|
2904
3117
|
return void 0;
|
|
2905
3118
|
}
|
|
2906
|
-
async function syncCommand(cwd) {
|
|
3119
|
+
async function syncCommand(options, cwd) {
|
|
2907
3120
|
const startDir = cwd ?? process.cwd();
|
|
2908
3121
|
const projectRoot = findProjectRoot(startDir);
|
|
2909
3122
|
if (!projectRoot) {
|
|
@@ -2911,15 +3124,15 @@ async function syncCommand(cwd) {
|
|
|
2911
3124
|
"No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
|
|
2912
3125
|
);
|
|
2913
3126
|
}
|
|
2914
|
-
const configPath =
|
|
2915
|
-
const existing = await (0,
|
|
3127
|
+
const configPath = path19.join(projectRoot, CONFIG_FILE6);
|
|
3128
|
+
const existing = await (0, import_config9.loadConfig)(configPath);
|
|
2916
3129
|
const previousStats = loadPreviousStats(projectRoot);
|
|
2917
|
-
console.log(
|
|
2918
|
-
const scanResult = await (0,
|
|
2919
|
-
const merged = (0,
|
|
2920
|
-
const compacted = (0,
|
|
3130
|
+
console.log(import_chalk12.default.dim("Scanning project..."));
|
|
3131
|
+
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3132
|
+
const merged = (0, import_config9.mergeConfig)(existing, scanResult);
|
|
3133
|
+
const compacted = (0, import_config9.compactConfig)(merged);
|
|
2921
3134
|
const compactedJson = JSON.stringify(compacted, null, 2);
|
|
2922
|
-
const rawDisk =
|
|
3135
|
+
const rawDisk = fs19.readFileSync(configPath, "utf-8").trim();
|
|
2923
3136
|
const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
|
|
2924
3137
|
const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
|
|
2925
3138
|
const configChanged = diskWithoutSync !== mergedWithoutSync;
|
|
@@ -2927,31 +3140,69 @@ async function syncCommand(cwd) {
|
|
|
2927
3140
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
2928
3141
|
if (changes.length > 0 || statsDelta) {
|
|
2929
3142
|
console.log(`
|
|
2930
|
-
${
|
|
3143
|
+
${import_chalk12.default.bold("Changes:")}`);
|
|
2931
3144
|
for (const change of changes) {
|
|
2932
|
-
const icon = change.type === "removed" ?
|
|
3145
|
+
const icon = change.type === "removed" ? import_chalk12.default.red("-") : import_chalk12.default.green("+");
|
|
2933
3146
|
console.log(` ${icon} ${change.description}`);
|
|
2934
3147
|
}
|
|
2935
3148
|
if (statsDelta) {
|
|
2936
|
-
console.log(` ${
|
|
3149
|
+
console.log(` ${import_chalk12.default.dim(statsDelta)}`);
|
|
2937
3150
|
}
|
|
2938
3151
|
}
|
|
2939
|
-
|
|
3152
|
+
if (options?.interactive) {
|
|
3153
|
+
clack9.intro("viberails sync (interactive)");
|
|
3154
|
+
clack9.note(formatRulesText(merged).join("\n"), "Rules after sync");
|
|
3155
|
+
const decision = await clack9.select({
|
|
3156
|
+
message: "How would you like to proceed?",
|
|
3157
|
+
options: [
|
|
3158
|
+
{ value: "accept", label: "Accept changes" },
|
|
3159
|
+
{ value: "customize", label: "Customize rules" },
|
|
3160
|
+
{ value: "cancel", label: "Cancel (no changes written)" }
|
|
3161
|
+
]
|
|
3162
|
+
});
|
|
3163
|
+
assertNotCancelled(decision);
|
|
3164
|
+
if (decision === "cancel") {
|
|
3165
|
+
clack9.outro("Sync cancelled. No files were written.");
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
if (decision === "customize") {
|
|
3169
|
+
const rootPkg = merged.packages.find((p) => p.path === ".") ?? merged.packages[0];
|
|
3170
|
+
const overrides = await promptRuleMenu({
|
|
3171
|
+
maxFileLines: merged.rules.maxFileLines,
|
|
3172
|
+
testCoverage: merged.rules.testCoverage,
|
|
3173
|
+
enforceMissingTests: merged.rules.enforceMissingTests,
|
|
3174
|
+
enforceNaming: merged.rules.enforceNaming,
|
|
3175
|
+
fileNamingValue: rootPkg.conventions?.fileNaming,
|
|
3176
|
+
coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
|
|
3177
|
+
coverageCommand: merged.defaults?.coverage?.command,
|
|
3178
|
+
packageOverrides: merged.packages
|
|
3179
|
+
});
|
|
3180
|
+
applyRuleOverrides(merged, overrides);
|
|
3181
|
+
const recompacted = (0, import_config9.compactConfig)(merged);
|
|
3182
|
+
fs19.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
3183
|
+
`);
|
|
3184
|
+
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
3185
|
+
clack9.log.success("Updated config with your customizations.");
|
|
3186
|
+
clack9.outro("Done! Run viberails check to verify.");
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
fs19.writeFileSync(configPath, `${compactedJson}
|
|
2940
3191
|
`);
|
|
2941
3192
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
2942
3193
|
console.log(`
|
|
2943
|
-
${
|
|
3194
|
+
${import_chalk12.default.bold("Synced:")}`);
|
|
2944
3195
|
if (configChanged) {
|
|
2945
|
-
console.log(` ${
|
|
3196
|
+
console.log(` ${import_chalk12.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
2946
3197
|
} else {
|
|
2947
|
-
console.log(` ${
|
|
3198
|
+
console.log(` ${import_chalk12.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
2948
3199
|
}
|
|
2949
|
-
console.log(` ${
|
|
2950
|
-
console.log(` ${
|
|
3200
|
+
console.log(` ${import_chalk12.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
3201
|
+
console.log(` ${import_chalk12.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
2951
3202
|
}
|
|
2952
3203
|
|
|
2953
3204
|
// src/index.ts
|
|
2954
|
-
var VERSION = "0.5.
|
|
3205
|
+
var VERSION = "0.5.2";
|
|
2955
3206
|
var program = new import_commander.Command();
|
|
2956
3207
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
2957
3208
|
program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {
|
|
@@ -2959,16 +3210,25 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
2959
3210
|
await initCommand(options);
|
|
2960
3211
|
} catch (err) {
|
|
2961
3212
|
const message = err instanceof Error ? err.message : String(err);
|
|
2962
|
-
console.error(`${
|
|
3213
|
+
console.error(`${import_chalk13.default.red("Error:")} ${message}`);
|
|
3214
|
+
process.exit(1);
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3217
|
+
program.command("sync").description("Re-scan and update generated files").option("-i, --interactive", "Review changes before writing").action(async (options) => {
|
|
3218
|
+
try {
|
|
3219
|
+
await syncCommand(options);
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3222
|
+
console.error(`${import_chalk13.default.red("Error:")} ${message}`);
|
|
2963
3223
|
process.exit(1);
|
|
2964
3224
|
}
|
|
2965
3225
|
});
|
|
2966
|
-
program.command("
|
|
3226
|
+
program.command("config").description("Interactively edit existing config rules").option("--rescan", "Re-scan project first (picks up new packages, stack changes)").action(async (options) => {
|
|
2967
3227
|
try {
|
|
2968
|
-
await
|
|
3228
|
+
await configCommand(options);
|
|
2969
3229
|
} catch (err) {
|
|
2970
3230
|
const message = err instanceof Error ? err.message : String(err);
|
|
2971
|
-
console.error(`${
|
|
3231
|
+
console.error(`${import_chalk13.default.red("Error:")} ${message}`);
|
|
2972
3232
|
process.exit(1);
|
|
2973
3233
|
}
|
|
2974
3234
|
});
|
|
@@ -2989,7 +3249,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
2989
3249
|
process.exit(exitCode);
|
|
2990
3250
|
} catch (err) {
|
|
2991
3251
|
const message = err instanceof Error ? err.message : String(err);
|
|
2992
|
-
console.error(`${
|
|
3252
|
+
console.error(`${import_chalk13.default.red("Error:")} ${message}`);
|
|
2993
3253
|
process.exit(1);
|
|
2994
3254
|
}
|
|
2995
3255
|
}
|
|
@@ -3000,7 +3260,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
3000
3260
|
process.exit(exitCode);
|
|
3001
3261
|
} catch (err) {
|
|
3002
3262
|
const message = err instanceof Error ? err.message : String(err);
|
|
3003
|
-
console.error(`${
|
|
3263
|
+
console.error(`${import_chalk13.default.red("Error:")} ${message}`);
|
|
3004
3264
|
process.exit(1);
|
|
3005
3265
|
}
|
|
3006
3266
|
});
|
|
@@ -3009,7 +3269,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
3009
3269
|
await boundariesCommand(options);
|
|
3010
3270
|
} catch (err) {
|
|
3011
3271
|
const message = err instanceof Error ? err.message : String(err);
|
|
3012
|
-
console.error(`${
|
|
3272
|
+
console.error(`${import_chalk13.default.red("Error:")} ${message}`);
|
|
3013
3273
|
process.exit(1);
|
|
3014
3274
|
}
|
|
3015
3275
|
});
|