viberails 0.5.5 → 0.6.1
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 +567 -300
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +567 -300
- 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_chalk14 = __toESM(require("chalk"), 1);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/boundaries.ts
|
|
@@ -95,11 +95,11 @@ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace
|
|
|
95
95
|
stdio: "pipe"
|
|
96
96
|
});
|
|
97
97
|
if (result.status === 0) {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const lefthookPath =
|
|
101
|
-
if (!
|
|
102
|
-
|
|
98
|
+
const fs21 = await import("fs");
|
|
99
|
+
const path21 = await import("path");
|
|
100
|
+
const lefthookPath = path21.join(projectRoot, "lefthook.yml");
|
|
101
|
+
if (!fs21.existsSync(lefthookPath)) {
|
|
102
|
+
fs21.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
|
|
103
103
|
}
|
|
104
104
|
s.stop("Installed Lefthook");
|
|
105
105
|
return "Lefthook";
|
|
@@ -731,7 +731,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
|
|
|
731
731
|
var fs7 = __toESM(require("fs"), 1);
|
|
732
732
|
var path7 = __toESM(require("path"), 1);
|
|
733
733
|
var import_config5 = require("@viberails/config");
|
|
734
|
-
var
|
|
734
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
735
735
|
|
|
736
736
|
// src/commands/check-config.ts
|
|
737
737
|
var import_config2 = require("@viberails/config");
|
|
@@ -822,7 +822,8 @@ function runCoverageCommand(pkgRoot, command) {
|
|
|
822
822
|
cwd: pkgRoot,
|
|
823
823
|
shell: true,
|
|
824
824
|
encoding: "utf-8",
|
|
825
|
-
stdio: "pipe"
|
|
825
|
+
stdio: "pipe",
|
|
826
|
+
timeout: 3e5
|
|
826
827
|
});
|
|
827
828
|
if (result.status === 0) return { ok: true };
|
|
828
829
|
const stderr = result.stderr?.trim() ?? "";
|
|
@@ -985,7 +986,7 @@ function checkNaming(relPath, conventions) {
|
|
|
985
986
|
}
|
|
986
987
|
function getStagedFiles(projectRoot) {
|
|
987
988
|
try {
|
|
988
|
-
const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=
|
|
989
|
+
const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=ACMR", {
|
|
989
990
|
cwd: projectRoot,
|
|
990
991
|
encoding: "utf-8",
|
|
991
992
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1012,7 +1013,62 @@ function getDiffFiles(projectRoot, base) {
|
|
|
1012
1013
|
added: addedOutput.trim().split("\n").filter(Boolean)
|
|
1013
1014
|
};
|
|
1014
1015
|
} catch {
|
|
1015
|
-
|
|
1016
|
+
const msg = `git diff failed for base '${base}' \u2014 no files will be checked`;
|
|
1017
|
+
process.stderr.write(`Warning: ${msg}
|
|
1018
|
+
`);
|
|
1019
|
+
return { all: [], added: [], error: msg };
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function testFileToSourceFile(testFile) {
|
|
1023
|
+
const match = testFile.match(/^(.+)\.(test|spec)(\.[^.]+)$/);
|
|
1024
|
+
if (!match) return null;
|
|
1025
|
+
return `${match[1]}${match[3]}`;
|
|
1026
|
+
}
|
|
1027
|
+
function deletedTestFileToSourceFile(deletedTestFile, config) {
|
|
1028
|
+
const normalized = deletedTestFile.replaceAll("\\", "/");
|
|
1029
|
+
const sortedPackages = [...config.packages].sort((a, b) => b.path.length - a.path.length);
|
|
1030
|
+
for (const pkg of sortedPackages) {
|
|
1031
|
+
const relInPkg = pkg.path === "." ? normalized : normalized.startsWith(`${pkg.path}/`) ? normalized.slice(pkg.path.length + 1) : null;
|
|
1032
|
+
if (relInPkg === null) continue;
|
|
1033
|
+
const srcDir = pkg.structure?.srcDir;
|
|
1034
|
+
if (!srcDir) continue;
|
|
1035
|
+
const testsDir = pkg.structure?.tests;
|
|
1036
|
+
if (testsDir && relInPkg.startsWith(`${testsDir}/`)) {
|
|
1037
|
+
const relWithinTests = relInPkg.slice(testsDir.length + 1);
|
|
1038
|
+
const relWithinSrc = testFileToSourceFile(relWithinTests);
|
|
1039
|
+
if (relWithinSrc) {
|
|
1040
|
+
return pkg.path === "." ? path5.posix.join(srcDir, relWithinSrc) : path5.posix.join(pkg.path, srcDir, relWithinSrc);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const colocated = testFileToSourceFile(relInPkg);
|
|
1044
|
+
if (colocated) {
|
|
1045
|
+
return pkg.path === "." ? colocated : path5.posix.join(pkg.path, colocated);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
function getStagedDeletedTestSourceFiles(projectRoot, config) {
|
|
1051
|
+
try {
|
|
1052
|
+
const output = (0, import_node_child_process3.execSync)("git diff --cached --name-only --diff-filter=D", {
|
|
1053
|
+
cwd: projectRoot,
|
|
1054
|
+
encoding: "utf-8",
|
|
1055
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1056
|
+
});
|
|
1057
|
+
return output.trim().split("\n").filter(Boolean).map((file) => deletedTestFileToSourceFile(file, config)).filter((f) => f !== null);
|
|
1058
|
+
} catch {
|
|
1059
|
+
return [];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function getDiffDeletedTestSourceFiles(projectRoot, base, config) {
|
|
1063
|
+
try {
|
|
1064
|
+
const output = (0, import_node_child_process3.execSync)(`git diff --name-only --diff-filter=D ${base}...HEAD`, {
|
|
1065
|
+
cwd: projectRoot,
|
|
1066
|
+
encoding: "utf-8",
|
|
1067
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1068
|
+
});
|
|
1069
|
+
return output.trim().split("\n").filter(Boolean).map((file) => deletedTestFileToSourceFile(file, config)).filter((f) => f !== null);
|
|
1070
|
+
} catch {
|
|
1071
|
+
return [];
|
|
1016
1072
|
}
|
|
1017
1073
|
}
|
|
1018
1074
|
function getAllSourceFiles(projectRoot, config) {
|
|
@@ -1055,7 +1111,7 @@ function collectSourceFiles(dir, projectRoot) {
|
|
|
1055
1111
|
}
|
|
1056
1112
|
for (const entry of entries) {
|
|
1057
1113
|
if (entry.isDirectory()) {
|
|
1058
|
-
if (entry.name
|
|
1114
|
+
if (ALWAYS_SKIP_DIRS.has(entry.name)) continue;
|
|
1059
1115
|
walk(path5.join(d, entry.name));
|
|
1060
1116
|
} else if (entry.isFile()) {
|
|
1061
1117
|
files.push(path5.relative(projectRoot, path5.join(d, entry.name)));
|
|
@@ -1066,6 +1122,55 @@ function collectSourceFiles(dir, projectRoot) {
|
|
|
1066
1122
|
return files;
|
|
1067
1123
|
}
|
|
1068
1124
|
|
|
1125
|
+
// src/commands/check-print.ts
|
|
1126
|
+
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
1127
|
+
function printGroupedViolations(violations, limit) {
|
|
1128
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1129
|
+
for (const v of violations) {
|
|
1130
|
+
const existing = groups.get(v.rule) ?? [];
|
|
1131
|
+
existing.push(v);
|
|
1132
|
+
groups.set(v.rule, existing);
|
|
1133
|
+
}
|
|
1134
|
+
const ruleOrder = [
|
|
1135
|
+
"file-size",
|
|
1136
|
+
"file-naming",
|
|
1137
|
+
"missing-test",
|
|
1138
|
+
"test-coverage",
|
|
1139
|
+
"boundary-violation"
|
|
1140
|
+
];
|
|
1141
|
+
const sortedKeys = [...groups.keys()].sort(
|
|
1142
|
+
(a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
|
|
1143
|
+
);
|
|
1144
|
+
let totalShown = 0;
|
|
1145
|
+
const totalLimit = limit ?? Number.POSITIVE_INFINITY;
|
|
1146
|
+
for (const rule of sortedKeys) {
|
|
1147
|
+
const group = groups.get(rule);
|
|
1148
|
+
if (!group) continue;
|
|
1149
|
+
const remaining = totalLimit - totalShown;
|
|
1150
|
+
if (remaining <= 0) break;
|
|
1151
|
+
const toShow = group.slice(0, remaining);
|
|
1152
|
+
const hidden = group.length - toShow.length;
|
|
1153
|
+
for (const v of toShow) {
|
|
1154
|
+
const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
|
|
1155
|
+
console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
1156
|
+
}
|
|
1157
|
+
totalShown += toShow.length;
|
|
1158
|
+
if (hidden > 0) {
|
|
1159
|
+
console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function printSummary(violations) {
|
|
1164
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1165
|
+
for (const v of violations) {
|
|
1166
|
+
counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
|
|
1167
|
+
}
|
|
1168
|
+
const word = violations.length === 1 ? "violation" : "violations";
|
|
1169
|
+
const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
|
|
1170
|
+
console.log(`
|
|
1171
|
+
${violations.length} ${word} found (${parts.join(", ")}).`);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1069
1174
|
// src/commands/check-tests.ts
|
|
1070
1175
|
var fs6 = __toESM(require("fs"), 1);
|
|
1071
1176
|
var path6 = __toESM(require("path"), 1);
|
|
@@ -1095,18 +1200,19 @@ function checkMissingTests(projectRoot, config, severity) {
|
|
|
1095
1200
|
const testSuffix = testPattern.replace("*", "");
|
|
1096
1201
|
const sourceFiles = collectSourceFiles(srcPath, projectRoot);
|
|
1097
1202
|
for (const relFile of sourceFiles) {
|
|
1098
|
-
const
|
|
1099
|
-
if (
|
|
1203
|
+
const basename9 = path6.basename(relFile);
|
|
1204
|
+
if (basename9.includes(".test.") || basename9.includes(".spec.") || basename9.startsWith("index.") || basename9.endsWith(".d.ts")) {
|
|
1100
1205
|
continue;
|
|
1101
1206
|
}
|
|
1102
|
-
const ext = path6.extname(
|
|
1207
|
+
const ext = path6.extname(basename9);
|
|
1103
1208
|
if (!SOURCE_EXTS2.has(ext)) continue;
|
|
1104
|
-
const stem =
|
|
1209
|
+
const stem = basename9.slice(0, -ext.length);
|
|
1105
1210
|
const expectedTestFile = `${stem}${testSuffix}`;
|
|
1106
1211
|
const dir = path6.dirname(path6.join(projectRoot, relFile));
|
|
1107
1212
|
const colocatedTest = path6.join(dir, expectedTestFile);
|
|
1108
1213
|
const testsDir = pkg.structure?.tests;
|
|
1109
|
-
const
|
|
1214
|
+
const relToSrc = path6.relative(srcPath, path6.join(projectRoot, path6.dirname(relFile)));
|
|
1215
|
+
const dedicatedTest = testsDir ? path6.join(packageRoot2, testsDir, relToSrc, expectedTestFile) : null;
|
|
1110
1216
|
const hasTest = fs6.existsSync(colocatedTest) || dedicatedTest !== null && fs6.existsSync(dedicatedTest);
|
|
1111
1217
|
if (!hasTest) {
|
|
1112
1218
|
violations.push({
|
|
@@ -1136,91 +1242,52 @@ function isTestFile(relPath) {
|
|
|
1136
1242
|
const filename = path7.basename(relPath);
|
|
1137
1243
|
return filename.includes(".test.") || filename.includes(".spec.") || filename.startsWith("test.") || filename.startsWith("spec.") || relPath.includes("__tests__/") || relPath.includes("__test__/");
|
|
1138
1244
|
}
|
|
1139
|
-
function printGroupedViolations(violations, limit) {
|
|
1140
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1141
|
-
for (const v of violations) {
|
|
1142
|
-
const existing = groups.get(v.rule) ?? [];
|
|
1143
|
-
existing.push(v);
|
|
1144
|
-
groups.set(v.rule, existing);
|
|
1145
|
-
}
|
|
1146
|
-
const ruleOrder = [
|
|
1147
|
-
"file-size",
|
|
1148
|
-
"file-naming",
|
|
1149
|
-
"missing-test",
|
|
1150
|
-
"test-coverage",
|
|
1151
|
-
"boundary-violation"
|
|
1152
|
-
];
|
|
1153
|
-
const sortedKeys = [...groups.keys()].sort(
|
|
1154
|
-
(a, b) => (ruleOrder.indexOf(a) === -1 ? 99 : ruleOrder.indexOf(a)) - (ruleOrder.indexOf(b) === -1 ? 99 : ruleOrder.indexOf(b))
|
|
1155
|
-
);
|
|
1156
|
-
let totalShown = 0;
|
|
1157
|
-
const totalLimit = limit ?? Number.POSITIVE_INFINITY;
|
|
1158
|
-
for (const rule of sortedKeys) {
|
|
1159
|
-
const group = groups.get(rule);
|
|
1160
|
-
if (!group) continue;
|
|
1161
|
-
const remaining = totalLimit - totalShown;
|
|
1162
|
-
if (remaining <= 0) break;
|
|
1163
|
-
const toShow = group.slice(0, remaining);
|
|
1164
|
-
const hidden = group.length - toShow.length;
|
|
1165
|
-
for (const v of toShow) {
|
|
1166
|
-
const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
|
|
1167
|
-
console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
1168
|
-
}
|
|
1169
|
-
totalShown += toShow.length;
|
|
1170
|
-
if (hidden > 0) {
|
|
1171
|
-
console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
function printSummary(violations) {
|
|
1176
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1177
|
-
for (const v of violations) {
|
|
1178
|
-
counts.set(v.rule, (counts.get(v.rule) ?? 0) + 1);
|
|
1179
|
-
}
|
|
1180
|
-
const word = violations.length === 1 ? "violation" : "violations";
|
|
1181
|
-
const parts = [...counts.entries()].map(([rule, count]) => `${count} ${rule}`);
|
|
1182
|
-
console.log(`
|
|
1183
|
-
${violations.length} ${word} found (${parts.join(", ")}).`);
|
|
1184
|
-
}
|
|
1185
1245
|
async function checkCommand(options, cwd) {
|
|
1186
1246
|
const startDir = cwd ?? process.cwd();
|
|
1187
1247
|
const projectRoot = findProjectRoot(startDir);
|
|
1188
1248
|
if (!projectRoot) {
|
|
1189
|
-
console.error(`${
|
|
1249
|
+
console.error(`${import_chalk3.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
1190
1250
|
return 1;
|
|
1191
1251
|
}
|
|
1192
1252
|
const configPath = path7.join(projectRoot, CONFIG_FILE2);
|
|
1193
1253
|
if (!fs7.existsSync(configPath)) {
|
|
1194
1254
|
console.error(
|
|
1195
|
-
`${
|
|
1255
|
+
`${import_chalk3.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
1196
1256
|
);
|
|
1197
1257
|
return 1;
|
|
1198
1258
|
}
|
|
1199
1259
|
const config = await (0, import_config5.loadConfig)(configPath);
|
|
1200
1260
|
let filesToCheck;
|
|
1201
1261
|
let diffAddedFiles = null;
|
|
1262
|
+
let deletedTestSourceFiles = [];
|
|
1202
1263
|
if (options.staged) {
|
|
1203
|
-
filesToCheck = getStagedFiles(projectRoot);
|
|
1264
|
+
filesToCheck = getStagedFiles(projectRoot).filter((f) => SOURCE_EXTS.has(path7.extname(f)));
|
|
1265
|
+
deletedTestSourceFiles = getStagedDeletedTestSourceFiles(projectRoot, config);
|
|
1204
1266
|
} else if (options.diffBase) {
|
|
1205
1267
|
const diff = getDiffFiles(projectRoot, options.diffBase);
|
|
1268
|
+
if (diff.error && options.enforce) {
|
|
1269
|
+
console.error(`${import_chalk3.default.red("Error:")} ${diff.error}`);
|
|
1270
|
+
return 1;
|
|
1271
|
+
}
|
|
1206
1272
|
filesToCheck = diff.all.filter((f) => SOURCE_EXTS.has(path7.extname(f)));
|
|
1207
1273
|
diffAddedFiles = new Set(diff.added);
|
|
1274
|
+
deletedTestSourceFiles = getDiffDeletedTestSourceFiles(projectRoot, options.diffBase, config);
|
|
1208
1275
|
} else if (options.files && options.files.length > 0) {
|
|
1209
1276
|
filesToCheck = options.files;
|
|
1210
1277
|
} else {
|
|
1211
1278
|
filesToCheck = getAllSourceFiles(projectRoot, config);
|
|
1212
1279
|
}
|
|
1213
|
-
if (filesToCheck.length === 0) {
|
|
1280
|
+
if (filesToCheck.length === 0 && deletedTestSourceFiles.length === 0) {
|
|
1214
1281
|
if (options.format === "json") {
|
|
1215
1282
|
console.log(JSON.stringify({ violations: [], checkedFiles: 0 }));
|
|
1216
1283
|
} else {
|
|
1217
|
-
console.log(`${
|
|
1284
|
+
console.log(`${import_chalk3.default.green("\u2713")} No files to check.`);
|
|
1218
1285
|
}
|
|
1219
1286
|
return 0;
|
|
1220
1287
|
}
|
|
1221
1288
|
const violations = [];
|
|
1222
1289
|
const severity = options.enforce ? "error" : "warn";
|
|
1223
|
-
const log7 = options.format !== "json" && !options.hook ? (msg) => process.stderr.write(
|
|
1290
|
+
const log7 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk3.default.dim(msg)) : () => {
|
|
1224
1291
|
};
|
|
1225
1292
|
log7(" Checking files...");
|
|
1226
1293
|
for (const file of filesToCheck) {
|
|
@@ -1256,12 +1323,20 @@ async function checkCommand(options, cwd) {
|
|
|
1256
1323
|
}
|
|
1257
1324
|
}
|
|
1258
1325
|
log7(" done\n");
|
|
1259
|
-
if (!options.
|
|
1326
|
+
if (!options.files) {
|
|
1260
1327
|
log7(" Checking missing tests...");
|
|
1261
1328
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1329
|
+
if (options.staged) {
|
|
1330
|
+
const stagedSet = new Set(filesToCheck);
|
|
1331
|
+
for (const f of deletedTestSourceFiles) stagedSet.add(f);
|
|
1332
|
+
violations.push(...testViolations.filter((v) => stagedSet.has(v.file)));
|
|
1333
|
+
} else if (diffAddedFiles) {
|
|
1334
|
+
const checkSet = new Set(diffAddedFiles);
|
|
1335
|
+
for (const f of deletedTestSourceFiles) checkSet.add(f);
|
|
1336
|
+
violations.push(...testViolations.filter((v) => checkSet.has(v.file)));
|
|
1337
|
+
} else {
|
|
1338
|
+
violations.push(...testViolations);
|
|
1339
|
+
}
|
|
1265
1340
|
log7(" done\n");
|
|
1266
1341
|
}
|
|
1267
1342
|
if (!options.files && !options.staged && !options.diffBase) {
|
|
@@ -1307,7 +1382,7 @@ async function checkCommand(options, cwd) {
|
|
|
1307
1382
|
return options.enforce && violations.length > 0 ? 1 : 0;
|
|
1308
1383
|
}
|
|
1309
1384
|
if (violations.length === 0) {
|
|
1310
|
-
console.log(`${
|
|
1385
|
+
console.log(`${import_chalk3.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
1311
1386
|
return 0;
|
|
1312
1387
|
}
|
|
1313
1388
|
if (!options.quiet) {
|
|
@@ -1315,7 +1390,7 @@ async function checkCommand(options, cwd) {
|
|
|
1315
1390
|
}
|
|
1316
1391
|
printSummary(violations);
|
|
1317
1392
|
if (options.enforce) {
|
|
1318
|
-
console.log(
|
|
1393
|
+
console.log(import_chalk3.default.red("Fix violations before committing."));
|
|
1319
1394
|
return 1;
|
|
1320
1395
|
}
|
|
1321
1396
|
return 0;
|
|
@@ -1373,14 +1448,14 @@ var path9 = __toESM(require("path"), 1);
|
|
|
1373
1448
|
var clack6 = __toESM(require("@clack/prompts"), 1);
|
|
1374
1449
|
var import_config6 = require("@viberails/config");
|
|
1375
1450
|
var import_scanner = require("@viberails/scanner");
|
|
1376
|
-
var
|
|
1451
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1377
1452
|
|
|
1378
1453
|
// src/display-text.ts
|
|
1379
1454
|
var import_types4 = require("@viberails/types");
|
|
1380
1455
|
|
|
1381
1456
|
// src/display.ts
|
|
1382
1457
|
var import_types3 = require("@viberails/types");
|
|
1383
|
-
var
|
|
1458
|
+
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1384
1459
|
|
|
1385
1460
|
// src/display-helpers.ts
|
|
1386
1461
|
var import_types = require("@viberails/types");
|
|
@@ -1433,7 +1508,7 @@ function formatRoleGroup(group) {
|
|
|
1433
1508
|
|
|
1434
1509
|
// src/display-monorepo.ts
|
|
1435
1510
|
var import_types2 = require("@viberails/types");
|
|
1436
|
-
var
|
|
1511
|
+
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
1437
1512
|
function formatPackageSummary(pkg) {
|
|
1438
1513
|
const parts = [];
|
|
1439
1514
|
if (pkg.stack.framework) {
|
|
@@ -1450,23 +1525,23 @@ function formatPackageSummary(pkg) {
|
|
|
1450
1525
|
function displayMonorepoResults(scanResult) {
|
|
1451
1526
|
const { stack, packages } = scanResult;
|
|
1452
1527
|
console.log(`
|
|
1453
|
-
${
|
|
1454
|
-
console.log(` ${
|
|
1528
|
+
${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1529
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1455
1530
|
if (stack.packageManager) {
|
|
1456
|
-
console.log(` ${
|
|
1531
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1457
1532
|
}
|
|
1458
1533
|
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1459
|
-
console.log(` ${
|
|
1534
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
|
|
1460
1535
|
} else {
|
|
1461
1536
|
if (stack.linter) {
|
|
1462
|
-
console.log(` ${
|
|
1537
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1463
1538
|
}
|
|
1464
1539
|
if (stack.formatter) {
|
|
1465
|
-
console.log(` ${
|
|
1540
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1466
1541
|
}
|
|
1467
1542
|
}
|
|
1468
1543
|
if (stack.testRunner) {
|
|
1469
|
-
console.log(` ${
|
|
1544
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1470
1545
|
}
|
|
1471
1546
|
console.log("");
|
|
1472
1547
|
for (const pkg of packages) {
|
|
@@ -1477,13 +1552,13 @@ ${import_chalk3.default.bold(`Detected: (monorepo, ${packages.length} packages)`
|
|
|
1477
1552
|
);
|
|
1478
1553
|
if (packagesWithDirs.length > 0) {
|
|
1479
1554
|
console.log(`
|
|
1480
|
-
${
|
|
1555
|
+
${import_chalk4.default.bold("Structure:")}`);
|
|
1481
1556
|
for (const pkg of packagesWithDirs) {
|
|
1482
1557
|
const groups = groupByRole(pkg.structure.directories);
|
|
1483
1558
|
if (groups.length === 0) continue;
|
|
1484
1559
|
console.log(` ${pkg.relativePath}:`);
|
|
1485
1560
|
for (const group of groups) {
|
|
1486
|
-
console.log(` ${
|
|
1561
|
+
console.log(` ${import_chalk4.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1487
1562
|
}
|
|
1488
1563
|
}
|
|
1489
1564
|
}
|
|
@@ -1564,7 +1639,7 @@ function displayConventions(scanResult) {
|
|
|
1564
1639
|
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1565
1640
|
if (conventionEntries.length === 0) return;
|
|
1566
1641
|
console.log(`
|
|
1567
|
-
${
|
|
1642
|
+
${import_chalk5.default.bold("Conventions:")}`);
|
|
1568
1643
|
for (const [key, convention] of conventionEntries) {
|
|
1569
1644
|
if (convention.confidence === "low") continue;
|
|
1570
1645
|
const label = import_types3.CONVENTION_LABELS[key] ?? key;
|
|
@@ -1572,19 +1647,19 @@ ${import_chalk4.default.bold("Conventions:")}`);
|
|
|
1572
1647
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1573
1648
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1574
1649
|
if (allSame || pkgValues.length <= 1) {
|
|
1575
|
-
const ind = convention.confidence === "high" ?
|
|
1576
|
-
const detail =
|
|
1650
|
+
const ind = convention.confidence === "high" ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
|
|
1651
|
+
const detail = import_chalk5.default.dim(`(${confidenceLabel(convention)})`);
|
|
1577
1652
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1578
1653
|
} else {
|
|
1579
|
-
console.log(` ${
|
|
1654
|
+
console.log(` ${import_chalk5.default.yellow("~")} ${label}: varies by package`);
|
|
1580
1655
|
for (const pv of pkgValues) {
|
|
1581
1656
|
const pct = Math.round(pv.convention.consistency);
|
|
1582
1657
|
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1583
1658
|
}
|
|
1584
1659
|
}
|
|
1585
1660
|
} else {
|
|
1586
|
-
const ind = convention.confidence === "high" ?
|
|
1587
|
-
const detail =
|
|
1661
|
+
const ind = convention.confidence === "high" ? import_chalk5.default.green("\u2713") : import_chalk5.default.yellow("~");
|
|
1662
|
+
const detail = import_chalk5.default.dim(`(${confidenceLabel(convention)})`);
|
|
1588
1663
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1589
1664
|
}
|
|
1590
1665
|
}
|
|
@@ -1592,7 +1667,7 @@ ${import_chalk4.default.bold("Conventions:")}`);
|
|
|
1592
1667
|
function displaySummarySection(scanResult) {
|
|
1593
1668
|
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1594
1669
|
console.log(`
|
|
1595
|
-
${
|
|
1670
|
+
${import_chalk5.default.bold("Summary:")}`);
|
|
1596
1671
|
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1597
1672
|
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1598
1673
|
if (ext) {
|
|
@@ -1606,47 +1681,47 @@ function displayScanResults(scanResult) {
|
|
|
1606
1681
|
}
|
|
1607
1682
|
const { stack } = scanResult;
|
|
1608
1683
|
console.log(`
|
|
1609
|
-
${
|
|
1684
|
+
${import_chalk5.default.bold("Detected:")}`);
|
|
1610
1685
|
if (stack.framework) {
|
|
1611
|
-
console.log(` ${
|
|
1686
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1612
1687
|
}
|
|
1613
|
-
console.log(` ${
|
|
1688
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1614
1689
|
if (stack.styling) {
|
|
1615
|
-
console.log(` ${
|
|
1690
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1616
1691
|
}
|
|
1617
1692
|
if (stack.backend) {
|
|
1618
|
-
console.log(` ${
|
|
1693
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1619
1694
|
}
|
|
1620
1695
|
if (stack.orm) {
|
|
1621
|
-
console.log(` ${
|
|
1696
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1622
1697
|
}
|
|
1623
1698
|
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1624
|
-
console.log(` ${
|
|
1699
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
|
|
1625
1700
|
} else {
|
|
1626
1701
|
if (stack.linter) {
|
|
1627
|
-
console.log(` ${
|
|
1702
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1628
1703
|
}
|
|
1629
1704
|
if (stack.formatter) {
|
|
1630
|
-
console.log(` ${
|
|
1705
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1631
1706
|
}
|
|
1632
1707
|
}
|
|
1633
1708
|
if (stack.testRunner) {
|
|
1634
|
-
console.log(` ${
|
|
1709
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1635
1710
|
}
|
|
1636
1711
|
if (stack.packageManager) {
|
|
1637
|
-
console.log(` ${
|
|
1712
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1638
1713
|
}
|
|
1639
1714
|
if (stack.libraries.length > 0) {
|
|
1640
1715
|
for (const lib of stack.libraries) {
|
|
1641
|
-
console.log(` ${
|
|
1716
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1642
1717
|
}
|
|
1643
1718
|
}
|
|
1644
1719
|
const groups = groupByRole(scanResult.structure.directories);
|
|
1645
1720
|
if (groups.length > 0) {
|
|
1646
1721
|
console.log(`
|
|
1647
|
-
${
|
|
1722
|
+
${import_chalk5.default.bold("Structure:")}`);
|
|
1648
1723
|
for (const group of groups) {
|
|
1649
|
-
console.log(` ${
|
|
1724
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1650
1725
|
}
|
|
1651
1726
|
}
|
|
1652
1727
|
displayConventions(scanResult);
|
|
@@ -1656,49 +1731,49 @@ ${import_chalk4.default.bold("Structure:")}`);
|
|
|
1656
1731
|
function displayRulesPreview(config) {
|
|
1657
1732
|
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1658
1733
|
console.log(
|
|
1659
|
-
`${
|
|
1734
|
+
`${import_chalk5.default.bold("Rules:")} ${import_chalk5.default.dim("(warns on violation; use --enforce in CI to block)")}`
|
|
1660
1735
|
);
|
|
1661
|
-
console.log(` ${
|
|
1736
|
+
console.log(` ${import_chalk5.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1662
1737
|
if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
|
|
1663
1738
|
console.log(
|
|
1664
|
-
` ${
|
|
1739
|
+
` ${import_chalk5.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
|
|
1665
1740
|
);
|
|
1666
1741
|
} else if (config.rules.testCoverage > 0) {
|
|
1667
|
-
console.log(` ${
|
|
1742
|
+
console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
|
|
1668
1743
|
} else {
|
|
1669
|
-
console.log(` ${
|
|
1744
|
+
console.log(` ${import_chalk5.default.dim("\u2022")} Test coverage target: disabled`);
|
|
1670
1745
|
}
|
|
1671
1746
|
if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
|
|
1672
|
-
console.log(` ${
|
|
1747
|
+
console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
|
|
1673
1748
|
} else {
|
|
1674
|
-
console.log(` ${
|
|
1749
|
+
console.log(` ${import_chalk5.default.dim("\u2022")} Enforce file naming: no`);
|
|
1675
1750
|
}
|
|
1676
1751
|
console.log(
|
|
1677
|
-
` ${
|
|
1752
|
+
` ${import_chalk5.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1678
1753
|
);
|
|
1679
1754
|
console.log("");
|
|
1680
1755
|
}
|
|
1681
1756
|
function displayInitSummary(config, exemptedPackages) {
|
|
1682
1757
|
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1683
1758
|
const isMonorepo = config.packages.length > 1;
|
|
1684
|
-
const ok =
|
|
1685
|
-
const off =
|
|
1759
|
+
const ok = import_chalk5.default.green("\u2713");
|
|
1760
|
+
const off = import_chalk5.default.dim("\u25CB");
|
|
1686
1761
|
console.log("");
|
|
1687
|
-
console.log(` ${
|
|
1688
|
-
console.log(` ${ok} Max file size: ${
|
|
1762
|
+
console.log(` ${import_chalk5.default.bold("Rules to apply:")}`);
|
|
1763
|
+
console.log(` ${ok} Max file size: ${import_chalk5.default.cyan(`${config.rules.maxFileLines} lines`)}`);
|
|
1689
1764
|
const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
|
|
1690
1765
|
if (config.rules.enforceNaming && fileNaming) {
|
|
1691
|
-
console.log(` ${ok} File naming: ${
|
|
1766
|
+
console.log(` ${ok} File naming: ${import_chalk5.default.cyan(fileNaming)}`);
|
|
1692
1767
|
} else {
|
|
1693
|
-
console.log(` ${off} File naming: ${
|
|
1768
|
+
console.log(` ${off} File naming: ${import_chalk5.default.dim("not enforced")}`);
|
|
1694
1769
|
}
|
|
1695
1770
|
const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
|
|
1696
1771
|
if (config.rules.enforceMissingTests && testPattern) {
|
|
1697
|
-
console.log(` ${ok} Missing tests: ${
|
|
1772
|
+
console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan(`enforced (${testPattern})`)}`);
|
|
1698
1773
|
} else if (config.rules.enforceMissingTests) {
|
|
1699
|
-
console.log(` ${ok} Missing tests: ${
|
|
1774
|
+
console.log(` ${ok} Missing tests: ${import_chalk5.default.cyan("enforced")}`);
|
|
1700
1775
|
} else {
|
|
1701
|
-
console.log(` ${off} Missing tests: ${
|
|
1776
|
+
console.log(` ${off} Missing tests: ${import_chalk5.default.dim("not enforced")}`);
|
|
1702
1777
|
}
|
|
1703
1778
|
if (config.rules.testCoverage > 0) {
|
|
1704
1779
|
if (isMonorepo) {
|
|
@@ -1706,27 +1781,27 @@ function displayInitSummary(config, exemptedPackages) {
|
|
|
1706
1781
|
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
1707
1782
|
);
|
|
1708
1783
|
console.log(
|
|
1709
|
-
` ${ok} Coverage: ${
|
|
1784
|
+
` ${ok} Coverage: ${import_chalk5.default.cyan(`${config.rules.testCoverage}%`)} default ${import_chalk5.default.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
|
|
1710
1785
|
);
|
|
1711
1786
|
} else {
|
|
1712
|
-
console.log(` ${ok} Coverage: ${
|
|
1787
|
+
console.log(` ${ok} Coverage: ${import_chalk5.default.cyan(`${config.rules.testCoverage}%`)}`);
|
|
1713
1788
|
}
|
|
1714
1789
|
} else {
|
|
1715
|
-
console.log(` ${off} Coverage: ${
|
|
1790
|
+
console.log(` ${off} Coverage: ${import_chalk5.default.dim("disabled")}`);
|
|
1716
1791
|
}
|
|
1717
1792
|
if (exemptedPackages.length > 0) {
|
|
1718
1793
|
console.log(
|
|
1719
|
-
` ${
|
|
1794
|
+
` ${import_chalk5.default.dim(" exempted:")} ${import_chalk5.default.dim(exemptedPackages.join(", "))} ${import_chalk5.default.dim("(types-only)")}`
|
|
1720
1795
|
);
|
|
1721
1796
|
}
|
|
1722
1797
|
if (isMonorepo) {
|
|
1723
1798
|
console.log(
|
|
1724
1799
|
`
|
|
1725
|
-
${
|
|
1800
|
+
${import_chalk5.default.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
|
|
1726
1801
|
);
|
|
1727
1802
|
} else {
|
|
1728
1803
|
console.log(`
|
|
1729
|
-
${
|
|
1804
|
+
${import_chalk5.default.dim("warns on violation \xB7 use --enforce in CI to block")}`);
|
|
1730
1805
|
}
|
|
1731
1806
|
console.log("");
|
|
1732
1807
|
}
|
|
@@ -2038,7 +2113,7 @@ async function configCommand(options, cwd) {
|
|
|
2038
2113
|
}
|
|
2039
2114
|
const configPath = path9.join(projectRoot, CONFIG_FILE3);
|
|
2040
2115
|
if (!fs10.existsSync(configPath)) {
|
|
2041
|
-
console.log(`${
|
|
2116
|
+
console.log(`${import_chalk6.default.yellow("!")} No config found. Run ${import_chalk6.default.cyan("viberails init")} first.`);
|
|
2042
2117
|
return;
|
|
2043
2118
|
}
|
|
2044
2119
|
clack6.intro("viberails config");
|
|
@@ -2123,22 +2198,22 @@ async function rescanAndMerge(projectRoot, config) {
|
|
|
2123
2198
|
var fs13 = __toESM(require("fs"), 1);
|
|
2124
2199
|
var path13 = __toESM(require("path"), 1);
|
|
2125
2200
|
var import_config7 = require("@viberails/config");
|
|
2126
|
-
var
|
|
2201
|
+
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
2127
2202
|
|
|
2128
2203
|
// src/commands/fix-helpers.ts
|
|
2129
2204
|
var import_node_child_process4 = require("child_process");
|
|
2130
|
-
var
|
|
2205
|
+
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
2131
2206
|
function printPlan(renames, stubs) {
|
|
2132
2207
|
if (renames.length > 0) {
|
|
2133
|
-
console.log(
|
|
2208
|
+
console.log(import_chalk7.default.bold("\nFile renames:"));
|
|
2134
2209
|
for (const r of renames) {
|
|
2135
|
-
console.log(` ${
|
|
2210
|
+
console.log(` ${import_chalk7.default.red(r.oldPath)} \u2192 ${import_chalk7.default.green(r.newPath)}`);
|
|
2136
2211
|
}
|
|
2137
2212
|
}
|
|
2138
2213
|
if (stubs.length > 0) {
|
|
2139
|
-
console.log(
|
|
2214
|
+
console.log(import_chalk7.default.bold("\nTest stubs to create:"));
|
|
2140
2215
|
for (const s of stubs) {
|
|
2141
|
-
console.log(` ${
|
|
2216
|
+
console.log(` ${import_chalk7.default.green("+")} ${s.path}`);
|
|
2142
2217
|
}
|
|
2143
2218
|
}
|
|
2144
2219
|
}
|
|
@@ -2172,8 +2247,62 @@ function computeNewSpecifier(oldSpecifier, newBare) {
|
|
|
2172
2247
|
const newSpec = prefix + newBare;
|
|
2173
2248
|
return hasJsExt ? `${newSpec}.js` : newSpec;
|
|
2174
2249
|
}
|
|
2175
|
-
async function
|
|
2250
|
+
async function scanForAliasImports(renames, projectRoot) {
|
|
2176
2251
|
if (renames.length === 0) return [];
|
|
2252
|
+
const { readFile, readdir } = await import("fs/promises");
|
|
2253
|
+
const oldBareNames = /* @__PURE__ */ new Set();
|
|
2254
|
+
for (const r of renames) {
|
|
2255
|
+
const oldFilename = path10.basename(r.oldPath);
|
|
2256
|
+
oldBareNames.add(oldFilename.slice(0, oldFilename.indexOf(".")));
|
|
2257
|
+
}
|
|
2258
|
+
const importPattern = /(?:import|export)\s+.*?from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
2259
|
+
const skipDirs = /* @__PURE__ */ new Set([
|
|
2260
|
+
"node_modules",
|
|
2261
|
+
"dist",
|
|
2262
|
+
"build",
|
|
2263
|
+
".next",
|
|
2264
|
+
".expo",
|
|
2265
|
+
".turbo",
|
|
2266
|
+
"coverage"
|
|
2267
|
+
]);
|
|
2268
|
+
const sourceExts = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
2269
|
+
const allEntries = await readdir(projectRoot, { recursive: true, withFileTypes: true });
|
|
2270
|
+
const files = allEntries.filter((e) => {
|
|
2271
|
+
if (!e.isFile()) return false;
|
|
2272
|
+
const ext = path10.extname(e.name);
|
|
2273
|
+
if (!sourceExts.has(ext)) return false;
|
|
2274
|
+
const rel = path10.join(e.parentPath, e.name);
|
|
2275
|
+
const segments = path10.relative(projectRoot, rel).split(path10.sep);
|
|
2276
|
+
return !segments.some((s) => skipDirs.has(s));
|
|
2277
|
+
}).map((e) => path10.join(e.parentPath, e.name));
|
|
2278
|
+
const aliases = [];
|
|
2279
|
+
for (const file of files) {
|
|
2280
|
+
let content;
|
|
2281
|
+
try {
|
|
2282
|
+
content = await readFile(file, "utf-8");
|
|
2283
|
+
} catch {
|
|
2284
|
+
continue;
|
|
2285
|
+
}
|
|
2286
|
+
const lines = content.split("\n");
|
|
2287
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2288
|
+
const line = lines[i];
|
|
2289
|
+
importPattern.lastIndex = 0;
|
|
2290
|
+
for (let match = importPattern.exec(line); match !== null; match = importPattern.exec(line)) {
|
|
2291
|
+
const specifier = match[1] ?? match[2];
|
|
2292
|
+
if (!specifier || specifier.startsWith(".")) continue;
|
|
2293
|
+
if (!specifier.includes("/")) continue;
|
|
2294
|
+
const lastSegment = specifier.split("/").pop() ?? "";
|
|
2295
|
+
const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2296
|
+
if (oldBareNames.has(bare)) {
|
|
2297
|
+
aliases.push({ file, specifier, line: i + 1 });
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
return aliases;
|
|
2303
|
+
}
|
|
2304
|
+
async function updateImportsAfterRenames(renames, projectRoot) {
|
|
2305
|
+
if (renames.length === 0) return { updates: [], skippedAliases: [] };
|
|
2177
2306
|
const { Project, SyntaxKind } = await import("ts-morph");
|
|
2178
2307
|
const renameMap = /* @__PURE__ */ new Map();
|
|
2179
2308
|
for (const r of renames) {
|
|
@@ -2188,15 +2317,44 @@ async function updateImportsAfterRenames(renames, projectRoot) {
|
|
|
2188
2317
|
});
|
|
2189
2318
|
project.addSourceFilesAtPaths(path10.join(projectRoot, "**/*.{ts,tsx,js,jsx,mjs,cjs}"));
|
|
2190
2319
|
const updates = [];
|
|
2320
|
+
const skippedAliases = [];
|
|
2191
2321
|
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
2322
|
+
const oldBareNames = /* @__PURE__ */ new Set();
|
|
2323
|
+
for (const r of renames) {
|
|
2324
|
+
const oldFilename = path10.basename(r.oldPath);
|
|
2325
|
+
oldBareNames.add(oldFilename.slice(0, oldFilename.indexOf(".")));
|
|
2326
|
+
}
|
|
2192
2327
|
for (const sourceFile of project.getSourceFiles()) {
|
|
2193
2328
|
const filePath = sourceFile.getFilePath();
|
|
2194
2329
|
const segments = filePath.split(path10.sep);
|
|
2195
|
-
|
|
2330
|
+
const skipDirs = /* @__PURE__ */ new Set([
|
|
2331
|
+
"node_modules",
|
|
2332
|
+
"dist",
|
|
2333
|
+
"build",
|
|
2334
|
+
".next",
|
|
2335
|
+
".expo",
|
|
2336
|
+
".svelte-kit",
|
|
2337
|
+
".turbo",
|
|
2338
|
+
"coverage"
|
|
2339
|
+
]);
|
|
2340
|
+
if (segments.some((s) => skipDirs.has(s))) continue;
|
|
2196
2341
|
const fileDir = path10.dirname(filePath);
|
|
2197
2342
|
for (const decl of sourceFile.getImportDeclarations()) {
|
|
2198
2343
|
const specifier = decl.getModuleSpecifierValue();
|
|
2199
|
-
if (!specifier.startsWith("."))
|
|
2344
|
+
if (!specifier.startsWith(".")) {
|
|
2345
|
+
if (specifier.includes("/")) {
|
|
2346
|
+
const lastSegment = specifier.split("/").pop() ?? "";
|
|
2347
|
+
const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2348
|
+
if (oldBareNames.has(bare)) {
|
|
2349
|
+
skippedAliases.push({
|
|
2350
|
+
file: filePath,
|
|
2351
|
+
specifier,
|
|
2352
|
+
line: decl.getStartLineNumber()
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2200
2358
|
const match = resolveToRenamedFile(specifier, fileDir, renameMap, extensions);
|
|
2201
2359
|
if (!match) continue;
|
|
2202
2360
|
const newSpec = computeNewSpecifier(specifier, match.newBare);
|
|
@@ -2246,7 +2404,7 @@ async function updateImportsAfterRenames(renames, projectRoot) {
|
|
|
2246
2404
|
if (updates.length > 0) {
|
|
2247
2405
|
await project.save();
|
|
2248
2406
|
}
|
|
2249
|
-
return updates;
|
|
2407
|
+
return { updates, skippedAliases };
|
|
2250
2408
|
}
|
|
2251
2409
|
function resolveToRenamedFile(specifier, fromDir, renameMap, extensions) {
|
|
2252
2410
|
const cleanSpec = specifier.endsWith(".js") ? specifier.slice(0, -3) : specifier;
|
|
@@ -2349,10 +2507,10 @@ function generateTestStub(sourceRelPath, config, projectRoot) {
|
|
|
2349
2507
|
const pkg = resolvePackageForFile(sourceRelPath, config);
|
|
2350
2508
|
const testPattern = pkg?.structure?.testPattern;
|
|
2351
2509
|
if (!testPattern) return null;
|
|
2352
|
-
const
|
|
2353
|
-
const ext = path12.extname(
|
|
2510
|
+
const basename9 = path12.basename(sourceRelPath);
|
|
2511
|
+
const ext = path12.extname(basename9);
|
|
2354
2512
|
if (!ext) return null;
|
|
2355
|
-
const stem =
|
|
2513
|
+
const stem = basename9.slice(0, -ext.length);
|
|
2356
2514
|
const testSuffix = testPattern.replace("*", "");
|
|
2357
2515
|
const testFilename = `${stem}${testSuffix}`;
|
|
2358
2516
|
const dir = path12.dirname(path12.join(projectRoot, sourceRelPath));
|
|
@@ -2383,13 +2541,13 @@ async function fixCommand(options, cwd) {
|
|
|
2383
2541
|
const startDir = cwd ?? process.cwd();
|
|
2384
2542
|
const projectRoot = findProjectRoot(startDir);
|
|
2385
2543
|
if (!projectRoot) {
|
|
2386
|
-
console.error(`${
|
|
2544
|
+
console.error(`${import_chalk8.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
2387
2545
|
return 1;
|
|
2388
2546
|
}
|
|
2389
2547
|
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
2390
2548
|
if (!fs13.existsSync(configPath)) {
|
|
2391
2549
|
console.error(
|
|
2392
|
-
`${
|
|
2550
|
+
`${import_chalk8.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
2393
2551
|
);
|
|
2394
2552
|
return 1;
|
|
2395
2553
|
}
|
|
@@ -2398,7 +2556,7 @@ async function fixCommand(options, cwd) {
|
|
|
2398
2556
|
const isDirty = checkGitDirty(projectRoot);
|
|
2399
2557
|
if (isDirty) {
|
|
2400
2558
|
console.log(
|
|
2401
|
-
|
|
2559
|
+
import_chalk8.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
2402
2560
|
);
|
|
2403
2561
|
}
|
|
2404
2562
|
}
|
|
@@ -2427,13 +2585,59 @@ async function fixCommand(options, cwd) {
|
|
|
2427
2585
|
if (stub) testStubs.push(stub);
|
|
2428
2586
|
}
|
|
2429
2587
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2588
|
+
const aliasImports = await scanForAliasImports(dedupedRenames, projectRoot);
|
|
2589
|
+
const blockedOldBareNames = /* @__PURE__ */ new Set();
|
|
2590
|
+
for (const alias of aliasImports) {
|
|
2591
|
+
const lastSegment = alias.specifier.split("/").pop() ?? "";
|
|
2592
|
+
const bare = lastSegment.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
2593
|
+
blockedOldBareNames.add(bare);
|
|
2594
|
+
}
|
|
2595
|
+
const safeRenames = dedupedRenames.filter((r) => {
|
|
2596
|
+
const oldFilename = path13.basename(r.oldPath);
|
|
2597
|
+
const bare = oldFilename.slice(0, oldFilename.indexOf("."));
|
|
2598
|
+
return !blockedOldBareNames.has(bare);
|
|
2599
|
+
});
|
|
2600
|
+
const skippedRenames = dedupedRenames.filter((r) => {
|
|
2601
|
+
const oldFilename = path13.basename(r.oldPath);
|
|
2602
|
+
const bare = oldFilename.slice(0, oldFilename.indexOf("."));
|
|
2603
|
+
return blockedOldBareNames.has(bare);
|
|
2604
|
+
});
|
|
2605
|
+
if (safeRenames.length === 0 && testStubs.length === 0 && skippedRenames.length === 0) {
|
|
2606
|
+
console.log(`${import_chalk8.default.green("\u2713")} No fixable violations found.`);
|
|
2607
|
+
return 0;
|
|
2608
|
+
}
|
|
2609
|
+
printPlan(safeRenames, testStubs);
|
|
2610
|
+
if (skippedRenames.length > 0) {
|
|
2611
|
+
console.log("");
|
|
2612
|
+
console.log(
|
|
2613
|
+
import_chalk8.default.yellow(
|
|
2614
|
+
`Skipping ${skippedRenames.length} rename${skippedRenames.length > 1 ? "s" : ""} \u2014 aliased imports would break:`
|
|
2615
|
+
)
|
|
2616
|
+
);
|
|
2617
|
+
for (const r of skippedRenames.slice(0, 5)) {
|
|
2618
|
+
console.log(import_chalk8.default.dim(` ${r.oldPath} \u2192 ${r.newPath}`));
|
|
2619
|
+
}
|
|
2620
|
+
if (skippedRenames.length > 5) {
|
|
2621
|
+
console.log(import_chalk8.default.dim(` ... and ${skippedRenames.length - 5} more`));
|
|
2622
|
+
}
|
|
2623
|
+
console.log("");
|
|
2624
|
+
console.log(import_chalk8.default.yellow("Affected aliased imports:"));
|
|
2625
|
+
for (const alias of aliasImports.slice(0, 5)) {
|
|
2626
|
+
const relFile = path13.relative(projectRoot, alias.file);
|
|
2627
|
+
console.log(import_chalk8.default.dim(` ${relFile}:${alias.line} \u2014 ${alias.specifier}`));
|
|
2628
|
+
}
|
|
2629
|
+
if (aliasImports.length > 5) {
|
|
2630
|
+
console.log(import_chalk8.default.dim(` ... and ${aliasImports.length - 5} more`));
|
|
2631
|
+
}
|
|
2632
|
+
console.log(import_chalk8.default.dim(" Update these imports to relative paths first, then re-run fix."));
|
|
2633
|
+
}
|
|
2634
|
+
if (safeRenames.length === 0 && testStubs.length === 0) {
|
|
2635
|
+
console.log(`
|
|
2636
|
+
${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
|
|
2432
2637
|
return 0;
|
|
2433
2638
|
}
|
|
2434
|
-
printPlan(dedupedRenames, testStubs);
|
|
2435
2639
|
if (options.dryRun) {
|
|
2436
|
-
console.log(
|
|
2640
|
+
console.log(import_chalk8.default.dim("\nDry run \u2014 no changes applied."));
|
|
2437
2641
|
return 0;
|
|
2438
2642
|
}
|
|
2439
2643
|
if (!options.yes) {
|
|
@@ -2444,15 +2648,15 @@ async function fixCommand(options, cwd) {
|
|
|
2444
2648
|
}
|
|
2445
2649
|
}
|
|
2446
2650
|
let renameCount = 0;
|
|
2447
|
-
for (const rename of
|
|
2651
|
+
for (const rename of safeRenames) {
|
|
2448
2652
|
if (executeRename(rename)) {
|
|
2449
2653
|
renameCount++;
|
|
2450
2654
|
}
|
|
2451
2655
|
}
|
|
2452
2656
|
let importUpdateCount = 0;
|
|
2453
2657
|
if (renameCount > 0) {
|
|
2454
|
-
const appliedRenames =
|
|
2455
|
-
const updates = await updateImportsAfterRenames(appliedRenames, projectRoot);
|
|
2658
|
+
const appliedRenames = safeRenames.filter((r) => fs13.existsSync(r.newAbsPath));
|
|
2659
|
+
const { updates } = await updateImportsAfterRenames(appliedRenames, projectRoot);
|
|
2456
2660
|
importUpdateCount = updates.length;
|
|
2457
2661
|
}
|
|
2458
2662
|
let stubCount = 0;
|
|
@@ -2464,33 +2668,33 @@ async function fixCommand(options, cwd) {
|
|
|
2464
2668
|
}
|
|
2465
2669
|
console.log("");
|
|
2466
2670
|
if (renameCount > 0) {
|
|
2467
|
-
console.log(`${
|
|
2671
|
+
console.log(`${import_chalk8.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
2468
2672
|
}
|
|
2469
2673
|
if (importUpdateCount > 0) {
|
|
2470
2674
|
console.log(
|
|
2471
|
-
`${
|
|
2675
|
+
`${import_chalk8.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
2472
2676
|
);
|
|
2473
2677
|
}
|
|
2474
2678
|
if (stubCount > 0) {
|
|
2475
|
-
console.log(`${
|
|
2679
|
+
console.log(`${import_chalk8.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
2476
2680
|
}
|
|
2477
2681
|
return 0;
|
|
2478
2682
|
}
|
|
2479
2683
|
|
|
2480
2684
|
// src/commands/init.ts
|
|
2481
|
-
var
|
|
2482
|
-
var
|
|
2685
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
2686
|
+
var path19 = __toESM(require("path"), 1);
|
|
2483
2687
|
var clack8 = __toESM(require("@clack/prompts"), 1);
|
|
2484
2688
|
var import_config8 = require("@viberails/config");
|
|
2485
2689
|
var import_scanner2 = require("@viberails/scanner");
|
|
2486
|
-
var
|
|
2690
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
2487
2691
|
|
|
2488
2692
|
// src/utils/check-prerequisites.ts
|
|
2489
2693
|
var import_node_child_process5 = require("child_process");
|
|
2490
2694
|
var fs14 = __toESM(require("fs"), 1);
|
|
2491
2695
|
var path14 = __toESM(require("path"), 1);
|
|
2492
2696
|
var clack7 = __toESM(require("@clack/prompts"), 1);
|
|
2493
|
-
var
|
|
2697
|
+
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
2494
2698
|
function checkCoveragePrereqs(projectRoot, scanResult) {
|
|
2495
2699
|
const pm = scanResult.stack.packageManager.name;
|
|
2496
2700
|
const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
|
|
@@ -2521,9 +2725,9 @@ function displayMissingPrereqs(prereqs) {
|
|
|
2521
2725
|
const missing = prereqs.filter((p) => !p.installed);
|
|
2522
2726
|
for (const m of missing) {
|
|
2523
2727
|
const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
|
|
2524
|
-
console.log(` ${
|
|
2728
|
+
console.log(` ${import_chalk9.default.yellow("!")} ${m.label} not installed${suffix}`);
|
|
2525
2729
|
if (m.installCommand) {
|
|
2526
|
-
console.log(` Install: ${
|
|
2730
|
+
console.log(` Install: ${import_chalk9.default.cyan(m.installCommand)}`);
|
|
2527
2731
|
}
|
|
2528
2732
|
}
|
|
2529
2733
|
}
|
|
@@ -2634,46 +2838,85 @@ function updateGitignore(projectRoot) {
|
|
|
2634
2838
|
}
|
|
2635
2839
|
|
|
2636
2840
|
// src/commands/init-hooks.ts
|
|
2841
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
2842
|
+
var path17 = __toESM(require("path"), 1);
|
|
2843
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
2844
|
+
var import_yaml = require("yaml");
|
|
2845
|
+
|
|
2846
|
+
// src/commands/resolve-typecheck.ts
|
|
2637
2847
|
var fs16 = __toESM(require("fs"), 1);
|
|
2638
2848
|
var path16 = __toESM(require("path"), 1);
|
|
2639
|
-
|
|
2640
|
-
|
|
2849
|
+
function hasTurboTask(projectRoot, taskName) {
|
|
2850
|
+
const turboPath = path16.join(projectRoot, "turbo.json");
|
|
2851
|
+
if (!fs16.existsSync(turboPath)) return false;
|
|
2852
|
+
try {
|
|
2853
|
+
const turbo = JSON.parse(fs16.readFileSync(turboPath, "utf-8"));
|
|
2854
|
+
const tasks = turbo.tasks ?? turbo.pipeline ?? {};
|
|
2855
|
+
return taskName in tasks;
|
|
2856
|
+
} catch {
|
|
2857
|
+
return false;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
2861
|
+
if (hasTurboTask(projectRoot, "typecheck")) {
|
|
2862
|
+
return { command: "npx turbo typecheck", label: "turbo typecheck" };
|
|
2863
|
+
}
|
|
2864
|
+
const pkgJsonPath = path16.join(projectRoot, "package.json");
|
|
2865
|
+
if (fs16.existsSync(pkgJsonPath)) {
|
|
2866
|
+
try {
|
|
2867
|
+
const pkg = JSON.parse(fs16.readFileSync(pkgJsonPath, "utf-8"));
|
|
2868
|
+
if (pkg.scripts?.typecheck) {
|
|
2869
|
+
const pm = packageManager ?? "npm";
|
|
2870
|
+
return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
|
|
2871
|
+
}
|
|
2872
|
+
} catch {
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
if (fs16.existsSync(path16.join(projectRoot, "tsconfig.json"))) {
|
|
2876
|
+
return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
|
|
2877
|
+
}
|
|
2878
|
+
return {
|
|
2879
|
+
reason: "no root tsconfig.json, no typecheck script, and no turbo typecheck task found"
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
// src/commands/init-hooks.ts
|
|
2641
2884
|
function setupPreCommitHook(projectRoot) {
|
|
2642
|
-
const lefthookPath =
|
|
2643
|
-
if (
|
|
2885
|
+
const lefthookPath = path17.join(projectRoot, "lefthook.yml");
|
|
2886
|
+
if (fs17.existsSync(lefthookPath)) {
|
|
2644
2887
|
addLefthookPreCommit(lefthookPath);
|
|
2645
|
-
console.log(` ${
|
|
2888
|
+
console.log(` ${import_chalk10.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
2646
2889
|
return "lefthook.yml";
|
|
2647
2890
|
}
|
|
2648
|
-
const huskyDir =
|
|
2649
|
-
if (
|
|
2891
|
+
const huskyDir = path17.join(projectRoot, ".husky");
|
|
2892
|
+
if (fs17.existsSync(huskyDir)) {
|
|
2650
2893
|
writeHuskyPreCommit(huskyDir);
|
|
2651
|
-
console.log(` ${
|
|
2894
|
+
console.log(` ${import_chalk10.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
2652
2895
|
return ".husky/pre-commit";
|
|
2653
2896
|
}
|
|
2654
|
-
const gitDir =
|
|
2655
|
-
if (
|
|
2656
|
-
const hooksDir =
|
|
2657
|
-
if (!
|
|
2658
|
-
|
|
2897
|
+
const gitDir = path17.join(projectRoot, ".git");
|
|
2898
|
+
if (fs17.existsSync(gitDir)) {
|
|
2899
|
+
const hooksDir = path17.join(gitDir, "hooks");
|
|
2900
|
+
if (!fs17.existsSync(hooksDir)) {
|
|
2901
|
+
fs17.mkdirSync(hooksDir, { recursive: true });
|
|
2659
2902
|
}
|
|
2660
2903
|
writeGitHookPreCommit(hooksDir);
|
|
2661
|
-
console.log(` ${
|
|
2904
|
+
console.log(` ${import_chalk10.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
2662
2905
|
return ".git/hooks/pre-commit";
|
|
2663
2906
|
}
|
|
2664
2907
|
return void 0;
|
|
2665
2908
|
}
|
|
2666
2909
|
function writeGitHookPreCommit(hooksDir) {
|
|
2667
|
-
const hookPath =
|
|
2668
|
-
if (
|
|
2669
|
-
const existing =
|
|
2910
|
+
const hookPath = path17.join(hooksDir, "pre-commit");
|
|
2911
|
+
if (fs17.existsSync(hookPath)) {
|
|
2912
|
+
const existing = fs17.readFileSync(hookPath, "utf-8");
|
|
2670
2913
|
if (existing.includes("viberails")) return;
|
|
2671
|
-
|
|
2914
|
+
fs17.writeFileSync(
|
|
2672
2915
|
hookPath,
|
|
2673
2916
|
`${existing.trimEnd()}
|
|
2674
2917
|
|
|
2675
2918
|
# viberails check
|
|
2676
|
-
npx viberails check --staged
|
|
2919
|
+
if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi
|
|
2677
2920
|
`
|
|
2678
2921
|
);
|
|
2679
2922
|
return;
|
|
@@ -2682,13 +2925,13 @@ npx viberails check --staged
|
|
|
2682
2925
|
"#!/bin/sh",
|
|
2683
2926
|
"# Generated by viberails \u2014 https://viberails.sh",
|
|
2684
2927
|
"",
|
|
2685
|
-
"npx viberails check --staged",
|
|
2928
|
+
"if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
|
|
2686
2929
|
""
|
|
2687
2930
|
].join("\n");
|
|
2688
|
-
|
|
2931
|
+
fs17.writeFileSync(hookPath, script, { mode: 493 });
|
|
2689
2932
|
}
|
|
2690
2933
|
function addLefthookPreCommit(lefthookPath) {
|
|
2691
|
-
const content =
|
|
2934
|
+
const content = fs17.readFileSync(lefthookPath, "utf-8");
|
|
2692
2935
|
if (content.includes("viberails")) return;
|
|
2693
2936
|
const doc = (0, import_yaml.parse)(content) ?? {};
|
|
2694
2937
|
if (!doc["pre-commit"]) {
|
|
@@ -2698,30 +2941,30 @@ function addLefthookPreCommit(lefthookPath) {
|
|
|
2698
2941
|
doc["pre-commit"].commands = {};
|
|
2699
2942
|
}
|
|
2700
2943
|
doc["pre-commit"].commands.viberails = {
|
|
2701
|
-
run: "npx viberails check --staged"
|
|
2944
|
+
run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
|
|
2702
2945
|
};
|
|
2703
|
-
|
|
2946
|
+
fs17.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
|
|
2704
2947
|
}
|
|
2705
2948
|
function detectHookManager(projectRoot) {
|
|
2706
|
-
if (
|
|
2707
|
-
if (
|
|
2949
|
+
if (fs17.existsSync(path17.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
2950
|
+
if (fs17.existsSync(path17.join(projectRoot, ".husky"))) return "Husky";
|
|
2708
2951
|
return void 0;
|
|
2709
2952
|
}
|
|
2710
2953
|
function setupClaudeCodeHook(projectRoot) {
|
|
2711
|
-
const claudeDir =
|
|
2712
|
-
if (!
|
|
2713
|
-
|
|
2954
|
+
const claudeDir = path17.join(projectRoot, ".claude");
|
|
2955
|
+
if (!fs17.existsSync(claudeDir)) {
|
|
2956
|
+
fs17.mkdirSync(claudeDir, { recursive: true });
|
|
2714
2957
|
}
|
|
2715
|
-
const settingsPath =
|
|
2958
|
+
const settingsPath = path17.join(claudeDir, "settings.json");
|
|
2716
2959
|
let settings = {};
|
|
2717
|
-
if (
|
|
2960
|
+
if (fs17.existsSync(settingsPath)) {
|
|
2718
2961
|
try {
|
|
2719
|
-
settings = JSON.parse(
|
|
2962
|
+
settings = JSON.parse(fs17.readFileSync(settingsPath, "utf-8"));
|
|
2720
2963
|
} catch {
|
|
2721
2964
|
console.warn(
|
|
2722
|
-
` ${
|
|
2965
|
+
` ${import_chalk10.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
2723
2966
|
);
|
|
2724
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
2967
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk10.default.cyan("viberails init --force")}`);
|
|
2725
2968
|
return;
|
|
2726
2969
|
}
|
|
2727
2970
|
}
|
|
@@ -2742,30 +2985,30 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
2742
2985
|
}
|
|
2743
2986
|
];
|
|
2744
2987
|
settings.hooks = hooks;
|
|
2745
|
-
|
|
2988
|
+
fs17.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
2746
2989
|
`);
|
|
2747
|
-
console.log(` ${
|
|
2990
|
+
console.log(` ${import_chalk10.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
2748
2991
|
}
|
|
2749
2992
|
function setupClaudeMdReference(projectRoot) {
|
|
2750
|
-
const claudeMdPath =
|
|
2993
|
+
const claudeMdPath = path17.join(projectRoot, "CLAUDE.md");
|
|
2751
2994
|
let content = "";
|
|
2752
|
-
if (
|
|
2753
|
-
content =
|
|
2995
|
+
if (fs17.existsSync(claudeMdPath)) {
|
|
2996
|
+
content = fs17.readFileSync(claudeMdPath, "utf-8");
|
|
2754
2997
|
}
|
|
2755
2998
|
if (content.includes("@.viberails/context.md")) return;
|
|
2756
2999
|
const ref = "\n@.viberails/context.md\n";
|
|
2757
3000
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
2758
|
-
|
|
2759
|
-
console.log(` ${
|
|
3001
|
+
fs17.writeFileSync(claudeMdPath, prefix + ref);
|
|
3002
|
+
console.log(` ${import_chalk10.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
2760
3003
|
}
|
|
2761
3004
|
function setupGithubAction(projectRoot, packageManager, options) {
|
|
2762
|
-
const workflowDir =
|
|
2763
|
-
const workflowPath =
|
|
2764
|
-
if (
|
|
2765
|
-
const existing =
|
|
3005
|
+
const workflowDir = path17.join(projectRoot, ".github", "workflows");
|
|
3006
|
+
const workflowPath = path17.join(workflowDir, "viberails.yml");
|
|
3007
|
+
if (fs17.existsSync(workflowPath)) {
|
|
3008
|
+
const existing = fs17.readFileSync(workflowPath, "utf-8");
|
|
2766
3009
|
if (existing.includes("viberails")) return void 0;
|
|
2767
3010
|
}
|
|
2768
|
-
|
|
3011
|
+
fs17.mkdirSync(workflowDir, { recursive: true });
|
|
2769
3012
|
const pm = packageManager || "npm";
|
|
2770
3013
|
const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
|
|
2771
3014
|
const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
|
|
@@ -2774,7 +3017,7 @@ function setupGithubAction(projectRoot, packageManager, options) {
|
|
|
2774
3017
|
"",
|
|
2775
3018
|
"on:",
|
|
2776
3019
|
" pull_request:",
|
|
2777
|
-
" branches: [main]",
|
|
3020
|
+
" branches: [main, master]",
|
|
2778
3021
|
"",
|
|
2779
3022
|
"jobs:",
|
|
2780
3023
|
" check:",
|
|
@@ -2792,87 +3035,94 @@ function setupGithubAction(projectRoot, packageManager, options) {
|
|
|
2792
3035
|
" - uses: actions/setup-node@v4",
|
|
2793
3036
|
" with:",
|
|
2794
3037
|
" node-version: 22",
|
|
2795
|
-
|
|
3038
|
+
` cache: ${pm}`,
|
|
2796
3039
|
"",
|
|
2797
3040
|
` - run: ${installCmd}`
|
|
2798
3041
|
);
|
|
2799
3042
|
if (options?.typecheck) {
|
|
2800
|
-
|
|
3043
|
+
const resolved = resolveTypecheckCommand(projectRoot, pm);
|
|
3044
|
+
if (resolved.command) {
|
|
3045
|
+
const ciCmd = resolved.command.startsWith("npx ") ? `${runPrefix} ${resolved.command.slice(4)}` : resolved.command;
|
|
3046
|
+
lines.push(` - run: ${ciCmd}`);
|
|
3047
|
+
}
|
|
2801
3048
|
}
|
|
2802
3049
|
if (options?.linter) {
|
|
2803
3050
|
const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
|
|
2804
3051
|
lines.push(` - run: ${runPrefix} ${lintCmd}`);
|
|
2805
3052
|
}
|
|
2806
3053
|
lines.push(
|
|
2807
|
-
` - run:
|
|
3054
|
+
` - run: npx viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
|
|
2808
3055
|
""
|
|
2809
3056
|
);
|
|
2810
3057
|
const content = lines.filter((l) => l !== void 0).join("\n");
|
|
2811
|
-
|
|
3058
|
+
fs17.writeFileSync(workflowPath, content);
|
|
2812
3059
|
return ".github/workflows/viberails.yml";
|
|
2813
3060
|
}
|
|
2814
3061
|
function writeHuskyPreCommit(huskyDir) {
|
|
2815
|
-
const hookPath =
|
|
2816
|
-
if
|
|
2817
|
-
|
|
3062
|
+
const hookPath = path17.join(huskyDir, "pre-commit");
|
|
3063
|
+
const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
|
|
3064
|
+
if (fs17.existsSync(hookPath)) {
|
|
3065
|
+
const existing = fs17.readFileSync(hookPath, "utf-8");
|
|
2818
3066
|
if (!existing.includes("viberails")) {
|
|
2819
|
-
|
|
2820
|
-
|
|
3067
|
+
fs17.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
3068
|
+
${cmd}
|
|
2821
3069
|
`);
|
|
2822
3070
|
}
|
|
2823
3071
|
return;
|
|
2824
3072
|
}
|
|
2825
|
-
|
|
3073
|
+
fs17.writeFileSync(hookPath, `#!/bin/sh
|
|
3074
|
+
${cmd}
|
|
3075
|
+
`, { mode: 493 });
|
|
2826
3076
|
}
|
|
2827
3077
|
|
|
2828
3078
|
// src/commands/init-hooks-extra.ts
|
|
2829
|
-
var
|
|
2830
|
-
var
|
|
2831
|
-
var
|
|
3079
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
3080
|
+
var path18 = __toESM(require("path"), 1);
|
|
3081
|
+
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
2832
3082
|
var import_yaml2 = require("yaml");
|
|
2833
|
-
function addPreCommitStep(projectRoot, name, command, marker) {
|
|
2834
|
-
const lefthookPath =
|
|
2835
|
-
if (
|
|
2836
|
-
const content =
|
|
3083
|
+
function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
|
|
3084
|
+
const lefthookPath = path18.join(projectRoot, "lefthook.yml");
|
|
3085
|
+
if (fs18.existsSync(lefthookPath)) {
|
|
3086
|
+
const content = fs18.readFileSync(lefthookPath, "utf-8");
|
|
2837
3087
|
if (content.includes(marker)) return void 0;
|
|
2838
3088
|
const doc = (0, import_yaml2.parse)(content) ?? {};
|
|
2839
3089
|
if (!doc["pre-commit"]) doc["pre-commit"] = { commands: {} };
|
|
2840
3090
|
if (!doc["pre-commit"].commands) doc["pre-commit"].commands = {};
|
|
2841
|
-
doc["pre-commit"].commands[name] = { run: command };
|
|
2842
|
-
|
|
3091
|
+
doc["pre-commit"].commands[name] = { run: command, ...lefthookExtra };
|
|
3092
|
+
fs18.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
|
|
2843
3093
|
return "lefthook.yml";
|
|
2844
3094
|
}
|
|
2845
|
-
const huskyDir =
|
|
2846
|
-
if (
|
|
2847
|
-
const hookPath =
|
|
2848
|
-
if (
|
|
2849
|
-
const existing =
|
|
3095
|
+
const huskyDir = path18.join(projectRoot, ".husky");
|
|
3096
|
+
if (fs18.existsSync(huskyDir)) {
|
|
3097
|
+
const hookPath = path18.join(huskyDir, "pre-commit");
|
|
3098
|
+
if (fs18.existsSync(hookPath)) {
|
|
3099
|
+
const existing = fs18.readFileSync(hookPath, "utf-8");
|
|
2850
3100
|
if (existing.includes(marker)) return void 0;
|
|
2851
|
-
|
|
3101
|
+
fs18.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
2852
3102
|
${command}
|
|
2853
3103
|
`);
|
|
2854
3104
|
} else {
|
|
2855
|
-
|
|
3105
|
+
fs18.writeFileSync(hookPath, `#!/bin/sh
|
|
2856
3106
|
${command}
|
|
2857
3107
|
`, { mode: 493 });
|
|
2858
3108
|
}
|
|
2859
3109
|
return ".husky/pre-commit";
|
|
2860
3110
|
}
|
|
2861
|
-
const gitDir =
|
|
2862
|
-
if (
|
|
2863
|
-
const hooksDir =
|
|
2864
|
-
if (!
|
|
2865
|
-
const hookPath =
|
|
2866
|
-
if (
|
|
2867
|
-
const existing =
|
|
3111
|
+
const gitDir = path18.join(projectRoot, ".git");
|
|
3112
|
+
if (fs18.existsSync(gitDir)) {
|
|
3113
|
+
const hooksDir = path18.join(gitDir, "hooks");
|
|
3114
|
+
if (!fs18.existsSync(hooksDir)) fs18.mkdirSync(hooksDir, { recursive: true });
|
|
3115
|
+
const hookPath = path18.join(hooksDir, "pre-commit");
|
|
3116
|
+
if (fs18.existsSync(hookPath)) {
|
|
3117
|
+
const existing = fs18.readFileSync(hookPath, "utf-8");
|
|
2868
3118
|
if (existing.includes(marker)) return void 0;
|
|
2869
|
-
|
|
3119
|
+
fs18.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
2870
3120
|
|
|
2871
3121
|
# ${name}
|
|
2872
3122
|
${command}
|
|
2873
3123
|
`);
|
|
2874
3124
|
} else {
|
|
2875
|
-
|
|
3125
|
+
fs18.writeFileSync(hookPath, `#!/bin/sh
|
|
2876
3126
|
# Generated by viberails
|
|
2877
3127
|
|
|
2878
3128
|
# ${name}
|
|
@@ -2885,19 +3135,36 @@ ${command}
|
|
|
2885
3135
|
}
|
|
2886
3136
|
return void 0;
|
|
2887
3137
|
}
|
|
2888
|
-
function setupTypecheckHook(projectRoot) {
|
|
2889
|
-
const
|
|
3138
|
+
function setupTypecheckHook(projectRoot, packageManager) {
|
|
3139
|
+
const resolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3140
|
+
if (!resolved.command) {
|
|
3141
|
+
console.log(` ${import_chalk11.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
|
|
3142
|
+
return void 0;
|
|
3143
|
+
}
|
|
3144
|
+
const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
|
|
2890
3145
|
if (target) {
|
|
2891
|
-
console.log(` ${
|
|
3146
|
+
console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
|
|
2892
3147
|
}
|
|
2893
3148
|
return target;
|
|
2894
3149
|
}
|
|
2895
3150
|
function setupLintHook(projectRoot, linter) {
|
|
2896
|
-
const
|
|
3151
|
+
const isLefthook = fs18.existsSync(path18.join(projectRoot, "lefthook.yml"));
|
|
2897
3152
|
const linterName = linter === "biome" ? "Biome" : "ESLint";
|
|
2898
|
-
|
|
3153
|
+
let command;
|
|
3154
|
+
let lefthookExtra;
|
|
3155
|
+
if (isLefthook) {
|
|
3156
|
+
command = linter === "biome" ? "npx biome check {staged_files}" : "npx eslint {staged_files}";
|
|
3157
|
+
lefthookExtra = {
|
|
3158
|
+
glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}"
|
|
3159
|
+
};
|
|
3160
|
+
} else {
|
|
3161
|
+
const exts = linter === "biome" ? "'*.js' '*.ts' '*.jsx' '*.tsx' '*.json' '*.css'" : "'*.js' '*.ts' '*.jsx' '*.tsx'";
|
|
3162
|
+
const lintCmd = linter === "biome" ? "biome check" : "eslint";
|
|
3163
|
+
command = `git diff --cached --name-only --diff-filter=ACMR -- ${exts} | xargs npx ${lintCmd}`;
|
|
3164
|
+
}
|
|
3165
|
+
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
2899
3166
|
if (target) {
|
|
2900
|
-
console.log(` ${
|
|
3167
|
+
console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
2901
3168
|
}
|
|
2902
3169
|
return target;
|
|
2903
3170
|
}
|
|
@@ -2908,7 +3175,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
2908
3175
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
2909
3176
|
}
|
|
2910
3177
|
if (integrations.typecheckHook) {
|
|
2911
|
-
const t = setupTypecheckHook(projectRoot);
|
|
3178
|
+
const t = setupTypecheckHook(projectRoot, opts.packageManager);
|
|
2912
3179
|
if (t) created.push(`${t} \u2014 added typecheck`);
|
|
2913
3180
|
}
|
|
2914
3181
|
if (integrations.lintHook && opts.linter) {
|
|
@@ -2945,11 +3212,11 @@ async function initCommand(options, cwd) {
|
|
|
2945
3212
|
"No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
|
|
2946
3213
|
);
|
|
2947
3214
|
}
|
|
2948
|
-
const configPath =
|
|
2949
|
-
if (
|
|
3215
|
+
const configPath = path19.join(projectRoot, CONFIG_FILE5);
|
|
3216
|
+
if (fs19.existsSync(configPath) && !options.force) {
|
|
2950
3217
|
console.log(
|
|
2951
|
-
`${
|
|
2952
|
-
Run ${
|
|
3218
|
+
`${import_chalk12.default.yellow("!")} viberails is already initialized.
|
|
3219
|
+
Run ${import_chalk12.default.cyan("viberails config")} to edit rules, ${import_chalk12.default.cyan("viberails sync")} to update, or ${import_chalk12.default.cyan("viberails init --force")} to start fresh.`
|
|
2953
3220
|
);
|
|
2954
3221
|
return;
|
|
2955
3222
|
}
|
|
@@ -2957,7 +3224,7 @@ async function initCommand(options, cwd) {
|
|
|
2957
3224
|
await initInteractive(projectRoot, configPath, options);
|
|
2958
3225
|
}
|
|
2959
3226
|
async function initNonInteractive(projectRoot, configPath) {
|
|
2960
|
-
console.log(
|
|
3227
|
+
console.log(import_chalk12.default.dim("Scanning project..."));
|
|
2961
3228
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
2962
3229
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
2963
3230
|
for (const pkg of config.packages) {
|
|
@@ -2970,11 +3237,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
2970
3237
|
const exempted = getExemptedPackages(config);
|
|
2971
3238
|
if (exempted.length > 0) {
|
|
2972
3239
|
console.log(
|
|
2973
|
-
` ${
|
|
3240
|
+
` ${import_chalk12.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk12.default.dim("(types-only)")}`
|
|
2974
3241
|
);
|
|
2975
3242
|
}
|
|
2976
3243
|
if (config.packages.length > 1) {
|
|
2977
|
-
console.log(
|
|
3244
|
+
console.log(import_chalk12.default.dim("Building import graph..."));
|
|
2978
3245
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
2979
3246
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
2980
3247
|
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
@@ -2987,16 +3254,16 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
2987
3254
|
}
|
|
2988
3255
|
}
|
|
2989
3256
|
const compacted = (0, import_config8.compactConfig)(config);
|
|
2990
|
-
|
|
3257
|
+
fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
2991
3258
|
`);
|
|
2992
3259
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
2993
3260
|
updateGitignore(projectRoot);
|
|
2994
3261
|
setupClaudeCodeHook(projectRoot);
|
|
2995
3262
|
setupClaudeMdReference(projectRoot);
|
|
2996
3263
|
const rootPkg = config.packages[0];
|
|
2997
|
-
const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
|
|
3264
|
+
const rootPkgPm = rootPkg?.stack?.packageManager?.split("@")[0] ?? "npm";
|
|
2998
3265
|
const linter = rootPkg?.stack?.linter?.split("@")[0];
|
|
2999
|
-
const isTypeScript = rootPkg?.stack?.language === "typescript";
|
|
3266
|
+
const isTypeScript = rootPkg?.stack?.language?.split("@")[0] === "typescript";
|
|
3000
3267
|
const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
|
|
3001
3268
|
linter,
|
|
3002
3269
|
typecheck: isTypeScript
|
|
@@ -3004,17 +3271,17 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3004
3271
|
const hookManager = detectHookManager(projectRoot);
|
|
3005
3272
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3006
3273
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3007
|
-
const ok =
|
|
3274
|
+
const ok = import_chalk12.default.green("\u2713");
|
|
3008
3275
|
const created = [
|
|
3009
|
-
`${ok} ${
|
|
3276
|
+
`${ok} ${path19.basename(configPath)}`,
|
|
3010
3277
|
`${ok} .viberails/context.md`,
|
|
3011
3278
|
`${ok} .viberails/scan-result.json`,
|
|
3012
3279
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3013
3280
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3014
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
3281
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk12.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3015
3282
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3016
3283
|
].filter(Boolean);
|
|
3017
|
-
if (hasHookManager &&
|
|
3284
|
+
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
3018
3285
|
if (hasHookManager && linter) setupLintHook(projectRoot, linter);
|
|
3019
3286
|
console.log(`
|
|
3020
3287
|
Created:
|
|
@@ -3022,9 +3289,9 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
|
|
|
3022
3289
|
}
|
|
3023
3290
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3024
3291
|
clack8.intro("viberails");
|
|
3025
|
-
if (
|
|
3292
|
+
if (fs19.existsSync(configPath) && options.force) {
|
|
3026
3293
|
const replace = await confirmDangerous(
|
|
3027
|
-
`${
|
|
3294
|
+
`${path19.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3028
3295
|
);
|
|
3029
3296
|
if (!replace) {
|
|
3030
3297
|
clack8.outro("Aborted. No files were written.");
|
|
@@ -3095,9 +3362,9 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3095
3362
|
}
|
|
3096
3363
|
const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
|
|
3097
3364
|
const integrations = await promptIntegrations(projectRoot, hookManager, {
|
|
3098
|
-
isTypeScript: rootPkgStack?.language === "typescript",
|
|
3365
|
+
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
3099
3366
|
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3100
|
-
packageManager: rootPkgStack?.packageManager,
|
|
3367
|
+
packageManager: rootPkgStack?.packageManager?.split("@")[0],
|
|
3101
3368
|
isWorkspace: config.packages.length > 1
|
|
3102
3369
|
});
|
|
3103
3370
|
const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
|
|
@@ -3106,37 +3373,37 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3106
3373
|
return;
|
|
3107
3374
|
}
|
|
3108
3375
|
const compacted = (0, import_config8.compactConfig)(config);
|
|
3109
|
-
|
|
3376
|
+
fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
3110
3377
|
`);
|
|
3111
3378
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
3112
3379
|
updateGitignore(projectRoot);
|
|
3113
|
-
const ok =
|
|
3114
|
-
clack8.log.step(`${ok} ${
|
|
3380
|
+
const ok = import_chalk12.default.green("\u2713");
|
|
3381
|
+
clack8.log.step(`${ok} ${path19.basename(configPath)}`);
|
|
3115
3382
|
clack8.log.step(`${ok} .viberails/context.md`);
|
|
3116
3383
|
clack8.log.step(`${ok} .viberails/scan-result.json`);
|
|
3117
3384
|
setupSelectedIntegrations(projectRoot, integrations, {
|
|
3118
3385
|
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3119
|
-
packageManager: rootPkgStack?.packageManager
|
|
3386
|
+
packageManager: rootPkgStack?.packageManager?.split("@")[0]
|
|
3120
3387
|
});
|
|
3121
3388
|
clack8.outro(
|
|
3122
3389
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
3123
|
-
${
|
|
3390
|
+
${import_chalk12.default.dim("Tip: use")} ${import_chalk12.default.cyan("viberails check --enforce")} ${import_chalk12.default.dim("in CI to block PRs on violations.")}`
|
|
3124
3391
|
);
|
|
3125
3392
|
}
|
|
3126
3393
|
|
|
3127
3394
|
// src/commands/sync.ts
|
|
3128
|
-
var
|
|
3129
|
-
var
|
|
3395
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
3396
|
+
var path20 = __toESM(require("path"), 1);
|
|
3130
3397
|
var clack9 = __toESM(require("@clack/prompts"), 1);
|
|
3131
3398
|
var import_config9 = require("@viberails/config");
|
|
3132
3399
|
var import_scanner3 = require("@viberails/scanner");
|
|
3133
|
-
var
|
|
3400
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
3134
3401
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
3135
3402
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
3136
3403
|
function loadPreviousStats(projectRoot) {
|
|
3137
|
-
const scanResultPath =
|
|
3404
|
+
const scanResultPath = path20.join(projectRoot, SCAN_RESULT_FILE2);
|
|
3138
3405
|
try {
|
|
3139
|
-
const raw =
|
|
3406
|
+
const raw = fs20.readFileSync(scanResultPath, "utf-8");
|
|
3140
3407
|
const parsed = JSON.parse(raw);
|
|
3141
3408
|
if (parsed?.statistics?.totalFiles !== void 0) {
|
|
3142
3409
|
return parsed.statistics;
|
|
@@ -3153,15 +3420,15 @@ async function syncCommand(options, cwd) {
|
|
|
3153
3420
|
"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"
|
|
3154
3421
|
);
|
|
3155
3422
|
}
|
|
3156
|
-
const configPath =
|
|
3423
|
+
const configPath = path20.join(projectRoot, CONFIG_FILE6);
|
|
3157
3424
|
const existing = await (0, import_config9.loadConfig)(configPath);
|
|
3158
3425
|
const previousStats = loadPreviousStats(projectRoot);
|
|
3159
|
-
console.log(
|
|
3426
|
+
console.log(import_chalk13.default.dim("Scanning project..."));
|
|
3160
3427
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3161
3428
|
const merged = (0, import_config9.mergeConfig)(existing, scanResult);
|
|
3162
3429
|
const compacted = (0, import_config9.compactConfig)(merged);
|
|
3163
3430
|
const compactedJson = JSON.stringify(compacted, null, 2);
|
|
3164
|
-
const rawDisk =
|
|
3431
|
+
const rawDisk = fs20.readFileSync(configPath, "utf-8").trim();
|
|
3165
3432
|
const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
|
|
3166
3433
|
const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
|
|
3167
3434
|
const configChanged = diskWithoutSync !== mergedWithoutSync;
|
|
@@ -3169,13 +3436,13 @@ async function syncCommand(options, cwd) {
|
|
|
3169
3436
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
3170
3437
|
if (changes.length > 0 || statsDelta) {
|
|
3171
3438
|
console.log(`
|
|
3172
|
-
${
|
|
3439
|
+
${import_chalk13.default.bold("Changes:")}`);
|
|
3173
3440
|
for (const change of changes) {
|
|
3174
|
-
const icon = change.type === "removed" ?
|
|
3441
|
+
const icon = change.type === "removed" ? import_chalk13.default.red("-") : import_chalk13.default.green("+");
|
|
3175
3442
|
console.log(` ${icon} ${change.description}`);
|
|
3176
3443
|
}
|
|
3177
3444
|
if (statsDelta) {
|
|
3178
|
-
console.log(` ${
|
|
3445
|
+
console.log(` ${import_chalk13.default.dim(statsDelta)}`);
|
|
3179
3446
|
}
|
|
3180
3447
|
}
|
|
3181
3448
|
if (options?.interactive) {
|
|
@@ -3208,7 +3475,7 @@ ${import_chalk12.default.bold("Changes:")}`);
|
|
|
3208
3475
|
});
|
|
3209
3476
|
applyRuleOverrides(merged, overrides);
|
|
3210
3477
|
const recompacted = (0, import_config9.compactConfig)(merged);
|
|
3211
|
-
|
|
3478
|
+
fs20.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
3212
3479
|
`);
|
|
3213
3480
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
3214
3481
|
clack9.log.success("Updated config with your customizations.");
|
|
@@ -3216,22 +3483,22 @@ ${import_chalk12.default.bold("Changes:")}`);
|
|
|
3216
3483
|
return;
|
|
3217
3484
|
}
|
|
3218
3485
|
}
|
|
3219
|
-
|
|
3486
|
+
fs20.writeFileSync(configPath, `${compactedJson}
|
|
3220
3487
|
`);
|
|
3221
3488
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
3222
3489
|
console.log(`
|
|
3223
|
-
${
|
|
3490
|
+
${import_chalk13.default.bold("Synced:")}`);
|
|
3224
3491
|
if (configChanged) {
|
|
3225
|
-
console.log(` ${
|
|
3492
|
+
console.log(` ${import_chalk13.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
3226
3493
|
} else {
|
|
3227
|
-
console.log(` ${
|
|
3494
|
+
console.log(` ${import_chalk13.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
3228
3495
|
}
|
|
3229
|
-
console.log(` ${
|
|
3230
|
-
console.log(` ${
|
|
3496
|
+
console.log(` ${import_chalk13.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
3497
|
+
console.log(` ${import_chalk13.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
3231
3498
|
}
|
|
3232
3499
|
|
|
3233
3500
|
// src/index.ts
|
|
3234
|
-
var VERSION = "0.
|
|
3501
|
+
var VERSION = "0.6.1";
|
|
3235
3502
|
var program = new import_commander.Command();
|
|
3236
3503
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
3237
3504
|
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) => {
|
|
@@ -3239,7 +3506,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
3239
3506
|
await initCommand(options);
|
|
3240
3507
|
} catch (err) {
|
|
3241
3508
|
const message = err instanceof Error ? err.message : String(err);
|
|
3242
|
-
console.error(`${
|
|
3509
|
+
console.error(`${import_chalk14.default.red("Error:")} ${message}`);
|
|
3243
3510
|
process.exit(1);
|
|
3244
3511
|
}
|
|
3245
3512
|
});
|
|
@@ -3248,7 +3515,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
3248
3515
|
await syncCommand(options);
|
|
3249
3516
|
} catch (err) {
|
|
3250
3517
|
const message = err instanceof Error ? err.message : String(err);
|
|
3251
|
-
console.error(`${
|
|
3518
|
+
console.error(`${import_chalk14.default.red("Error:")} ${message}`);
|
|
3252
3519
|
process.exit(1);
|
|
3253
3520
|
}
|
|
3254
3521
|
});
|
|
@@ -3257,7 +3524,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
3257
3524
|
await configCommand(options);
|
|
3258
3525
|
} catch (err) {
|
|
3259
3526
|
const message = err instanceof Error ? err.message : String(err);
|
|
3260
|
-
console.error(`${
|
|
3527
|
+
console.error(`${import_chalk14.default.red("Error:")} ${message}`);
|
|
3261
3528
|
process.exit(1);
|
|
3262
3529
|
}
|
|
3263
3530
|
});
|
|
@@ -3278,7 +3545,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
3278
3545
|
process.exit(exitCode);
|
|
3279
3546
|
} catch (err) {
|
|
3280
3547
|
const message = err instanceof Error ? err.message : String(err);
|
|
3281
|
-
console.error(`${
|
|
3548
|
+
console.error(`${import_chalk14.default.red("Error:")} ${message}`);
|
|
3282
3549
|
process.exit(1);
|
|
3283
3550
|
}
|
|
3284
3551
|
}
|
|
@@ -3289,7 +3556,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
3289
3556
|
process.exit(exitCode);
|
|
3290
3557
|
} catch (err) {
|
|
3291
3558
|
const message = err instanceof Error ? err.message : String(err);
|
|
3292
|
-
console.error(`${
|
|
3559
|
+
console.error(`${import_chalk14.default.red("Error:")} ${message}`);
|
|
3293
3560
|
process.exit(1);
|
|
3294
3561
|
}
|
|
3295
3562
|
});
|
|
@@ -3298,7 +3565,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
3298
3565
|
await boundariesCommand(options);
|
|
3299
3566
|
} catch (err) {
|
|
3300
3567
|
const message = err instanceof Error ? err.message : String(err);
|
|
3301
|
-
console.error(`${
|
|
3568
|
+
console.error(`${import_chalk14.default.red("Error:")} ${message}`);
|
|
3302
3569
|
process.exit(1);
|
|
3303
3570
|
}
|
|
3304
3571
|
});
|