viberails 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +329 -294
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +329 -294
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import chalk11 from "chalk";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/boundaries.ts
|
|
@@ -66,7 +66,7 @@ function resolveWorkspacePackages(projectRoot, workspace) {
|
|
|
66
66
|
];
|
|
67
67
|
packages.push({ name, path: absPath, relativePath, internalDeps: allDeps });
|
|
68
68
|
}
|
|
69
|
-
const packageNames = new Set(packages.map((
|
|
69
|
+
const packageNames = new Set(packages.map((p3) => p3.name));
|
|
70
70
|
for (const pkg of packages) {
|
|
71
71
|
pkg.internalDeps = pkg.internalDeps.filter((dep) => packageNames.has(dep));
|
|
72
72
|
}
|
|
@@ -96,23 +96,37 @@ async function boundariesCommand(options, cwd) {
|
|
|
96
96
|
}
|
|
97
97
|
displayRules(config);
|
|
98
98
|
}
|
|
99
|
+
function countBoundaries(boundaries) {
|
|
100
|
+
if (!boundaries) return 0;
|
|
101
|
+
if (Array.isArray(boundaries)) return boundaries.length;
|
|
102
|
+
return Object.values(boundaries).reduce((sum, denied) => sum + denied.length, 0);
|
|
103
|
+
}
|
|
99
104
|
function displayRules(config) {
|
|
100
|
-
|
|
105
|
+
const total = countBoundaries(config.boundaries);
|
|
106
|
+
if (total === 0) {
|
|
101
107
|
console.log(chalk.yellow("No boundary rules configured."));
|
|
102
108
|
console.log(`Run ${chalk.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
103
109
|
return;
|
|
104
110
|
}
|
|
105
|
-
const allowRules = config.boundaries.filter((r) => r.allow);
|
|
106
|
-
const denyRules = config.boundaries.filter((r) => !r.allow);
|
|
107
111
|
console.log(`
|
|
108
|
-
${chalk.bold(`Boundary rules (${
|
|
112
|
+
${chalk.bold(`Boundary rules (${total} rules):`)}
|
|
109
113
|
`);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
if (Array.isArray(config.boundaries)) {
|
|
115
|
+
const allowRules = config.boundaries.filter((r) => r.allow);
|
|
116
|
+
const denyRules = config.boundaries.filter((r) => !r.allow);
|
|
117
|
+
for (const r of allowRules) {
|
|
118
|
+
console.log(` ${chalk.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
119
|
+
}
|
|
120
|
+
for (const r of denyRules) {
|
|
121
|
+
const reason = r.reason ? chalk.dim(` (${r.reason})`) : "";
|
|
122
|
+
console.log(` ${chalk.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
123
|
+
}
|
|
124
|
+
} else if (config.boundaries) {
|
|
125
|
+
for (const [from, denied] of Object.entries(config.boundaries)) {
|
|
126
|
+
for (const to of denied) {
|
|
127
|
+
console.log(` ${chalk.red("\u2717")} ${from} \u2192 ${to}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
116
130
|
}
|
|
117
131
|
console.log(
|
|
118
132
|
`
|
|
@@ -129,31 +143,29 @@ async function inferAndDisplay(projectRoot, config, configPath) {
|
|
|
129
143
|
});
|
|
130
144
|
console.log(chalk.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
131
145
|
const inferred = inferBoundaries(graph);
|
|
132
|
-
|
|
146
|
+
const entries = Object.entries(inferred);
|
|
147
|
+
if (entries.length === 0) {
|
|
133
148
|
console.log(chalk.yellow("No boundary rules could be inferred."));
|
|
134
149
|
return;
|
|
135
150
|
}
|
|
136
|
-
const
|
|
137
|
-
const deny = inferred.filter((r) => !r.allow);
|
|
151
|
+
const totalRules = entries.reduce((sum, [, denied]) => sum + denied.length, 0);
|
|
138
152
|
console.log(`
|
|
139
153
|
${chalk.bold("Inferred boundary rules:")}
|
|
140
154
|
`);
|
|
141
|
-
for (const
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const reason = r.reason ? chalk.dim(` (${r.reason})`) : "";
|
|
146
|
-
console.log(` ${chalk.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
155
|
+
for (const [from, denied] of entries) {
|
|
156
|
+
for (const to of denied) {
|
|
157
|
+
console.log(` ${chalk.red("\u2717")} ${from} \u2192 ${to}`);
|
|
158
|
+
}
|
|
147
159
|
}
|
|
148
160
|
console.log(`
|
|
149
|
-
${
|
|
161
|
+
${totalRules} deny rules`);
|
|
150
162
|
const shouldSave = await confirm("\nSave to viberails.config.json?");
|
|
151
163
|
if (shouldSave) {
|
|
152
164
|
config.boundaries = inferred;
|
|
153
165
|
config.rules.enforceBoundaries = true;
|
|
154
166
|
fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
155
167
|
`);
|
|
156
|
-
console.log(`${chalk.green("\u2713")} Saved ${
|
|
168
|
+
console.log(`${chalk.green("\u2713")} Saved ${totalRules} rules`);
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
async function showGraph(projectRoot, config) {
|
|
@@ -527,7 +539,8 @@ async function checkCommand(options, cwd) {
|
|
|
527
539
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
528
540
|
violations.push(...testViolations);
|
|
529
541
|
}
|
|
530
|
-
|
|
542
|
+
const hasBoundaries = config.boundaries ? Array.isArray(config.boundaries) ? config.boundaries.length > 0 : Object.keys(config.boundaries).length > 0 : false;
|
|
543
|
+
if (config.rules.enforceBoundaries && hasBoundaries && !options.noBoundaries) {
|
|
531
544
|
const startTime = Date.now();
|
|
532
545
|
const { buildImportGraph, checkBoundaries } = await import("@viberails/graph");
|
|
533
546
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
@@ -932,223 +945,10 @@ async function fixCommand(options, cwd) {
|
|
|
932
945
|
// src/commands/init.ts
|
|
933
946
|
import * as fs12 from "fs";
|
|
934
947
|
import * as path13 from "path";
|
|
948
|
+
import * as p2 from "@clack/prompts";
|
|
935
949
|
import { generateConfig } from "@viberails/config";
|
|
936
950
|
import { scan } from "@viberails/scanner";
|
|
937
|
-
import
|
|
938
|
-
|
|
939
|
-
// src/display.ts
|
|
940
|
-
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
|
|
941
|
-
import chalk6 from "chalk";
|
|
942
|
-
|
|
943
|
-
// src/display-helpers.ts
|
|
944
|
-
import { ROLE_DESCRIPTIONS } from "@viberails/types";
|
|
945
|
-
function groupByRole(directories) {
|
|
946
|
-
const map = /* @__PURE__ */ new Map();
|
|
947
|
-
for (const dir of directories) {
|
|
948
|
-
if (dir.role === "unknown") continue;
|
|
949
|
-
const existing = map.get(dir.role);
|
|
950
|
-
if (existing) {
|
|
951
|
-
existing.dirs.push(dir);
|
|
952
|
-
} else {
|
|
953
|
-
map.set(dir.role, { dirs: [dir] });
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
const groups = [];
|
|
957
|
-
for (const [role, { dirs }] of map) {
|
|
958
|
-
const label = ROLE_DESCRIPTIONS[role] ?? role;
|
|
959
|
-
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
960
|
-
groups.push({
|
|
961
|
-
role,
|
|
962
|
-
label,
|
|
963
|
-
dirCount: dirs.length,
|
|
964
|
-
totalFiles,
|
|
965
|
-
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
return groups;
|
|
969
|
-
}
|
|
970
|
-
function formatSummary(stats, packageCount) {
|
|
971
|
-
const parts = [];
|
|
972
|
-
if (packageCount && packageCount > 1) {
|
|
973
|
-
parts.push(`${packageCount} packages`);
|
|
974
|
-
}
|
|
975
|
-
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
976
|
-
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
977
|
-
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
978
|
-
return parts.join(" \xB7 ");
|
|
979
|
-
}
|
|
980
|
-
function formatExtensions(filesByExtension, maxEntries = 4) {
|
|
981
|
-
return Object.entries(filesByExtension).sort(([, a], [, b]) => b - a).slice(0, maxEntries).map(([ext, count]) => `${ext} ${count}`).join(" \xB7 ");
|
|
982
|
-
}
|
|
983
|
-
function formatRoleGroup(group) {
|
|
984
|
-
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
985
|
-
if (group.singlePath) {
|
|
986
|
-
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
987
|
-
}
|
|
988
|
-
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
989
|
-
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// src/display-monorepo.ts
|
|
993
|
-
import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
|
|
994
|
-
import chalk5 from "chalk";
|
|
995
|
-
function formatPackageSummary(pkg) {
|
|
996
|
-
const parts = [];
|
|
997
|
-
if (pkg.stack.framework) {
|
|
998
|
-
parts.push(formatItem(pkg.stack.framework, FRAMEWORK_NAMES));
|
|
999
|
-
}
|
|
1000
|
-
if (pkg.stack.styling) {
|
|
1001
|
-
parts.push(formatItem(pkg.stack.styling, STYLING_NAMES));
|
|
1002
|
-
}
|
|
1003
|
-
const files = `${pkg.statistics.totalFiles} files`;
|
|
1004
|
-
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1005
|
-
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1006
|
-
}
|
|
1007
|
-
function displayMonorepoResults(scanResult) {
|
|
1008
|
-
const { stack, packages } = scanResult;
|
|
1009
|
-
console.log(`
|
|
1010
|
-
${chalk5.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1011
|
-
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1012
|
-
if (stack.packageManager) {
|
|
1013
|
-
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1014
|
-
}
|
|
1015
|
-
if (stack.linter) {
|
|
1016
|
-
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1017
|
-
}
|
|
1018
|
-
if (stack.formatter) {
|
|
1019
|
-
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1020
|
-
}
|
|
1021
|
-
if (stack.testRunner) {
|
|
1022
|
-
console.log(` ${chalk5.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1023
|
-
}
|
|
1024
|
-
console.log("");
|
|
1025
|
-
for (const pkg of packages) {
|
|
1026
|
-
console.log(formatPackageSummary(pkg));
|
|
1027
|
-
}
|
|
1028
|
-
const packagesWithDirs = packages.filter(
|
|
1029
|
-
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1030
|
-
);
|
|
1031
|
-
if (packagesWithDirs.length > 0) {
|
|
1032
|
-
console.log(`
|
|
1033
|
-
${chalk5.bold("Structure:")}`);
|
|
1034
|
-
for (const pkg of packagesWithDirs) {
|
|
1035
|
-
const groups = groupByRole(pkg.structure.directories);
|
|
1036
|
-
if (groups.length === 0) continue;
|
|
1037
|
-
console.log(` ${pkg.relativePath}:`);
|
|
1038
|
-
for (const group of groups) {
|
|
1039
|
-
console.log(` ${chalk5.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
displayConventions(scanResult);
|
|
1044
|
-
displaySummarySection(scanResult);
|
|
1045
|
-
console.log("");
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// src/display.ts
|
|
1049
|
-
var CONVENTION_LABELS = {
|
|
1050
|
-
fileNaming: "File naming",
|
|
1051
|
-
componentNaming: "Component naming",
|
|
1052
|
-
hookNaming: "Hook naming",
|
|
1053
|
-
importAlias: "Import alias"
|
|
1054
|
-
};
|
|
1055
|
-
function formatItem(item, nameMap) {
|
|
1056
|
-
const name = nameMap?.[item.name] ?? item.name;
|
|
1057
|
-
return item.version ? `${name} ${item.version}` : name;
|
|
1058
|
-
}
|
|
1059
|
-
function confidenceLabel(convention) {
|
|
1060
|
-
const pct = Math.round(convention.consistency);
|
|
1061
|
-
if (convention.confidence === "high") {
|
|
1062
|
-
return `${pct}% \u2014 high confidence, will enforce`;
|
|
1063
|
-
}
|
|
1064
|
-
return `${pct}% \u2014 medium confidence, suggested only`;
|
|
1065
|
-
}
|
|
1066
|
-
function displayConventions(scanResult) {
|
|
1067
|
-
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1068
|
-
if (conventionEntries.length === 0) return;
|
|
1069
|
-
console.log(`
|
|
1070
|
-
${chalk6.bold("Conventions:")}`);
|
|
1071
|
-
for (const [key, convention] of conventionEntries) {
|
|
1072
|
-
if (convention.confidence === "low") continue;
|
|
1073
|
-
const label = CONVENTION_LABELS[key] ?? key;
|
|
1074
|
-
if (scanResult.packages.length > 1) {
|
|
1075
|
-
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1076
|
-
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1077
|
-
if (allSame || pkgValues.length <= 1) {
|
|
1078
|
-
const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
|
|
1079
|
-
const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
|
|
1080
|
-
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1081
|
-
} else {
|
|
1082
|
-
console.log(` ${chalk6.yellow("~")} ${label}: varies by package`);
|
|
1083
|
-
for (const pv of pkgValues) {
|
|
1084
|
-
const pct = Math.round(pv.convention.consistency);
|
|
1085
|
-
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
} else {
|
|
1089
|
-
const ind = convention.confidence === "high" ? chalk6.green("\u2713") : chalk6.yellow("~");
|
|
1090
|
-
const detail = chalk6.dim(`(${confidenceLabel(convention)})`);
|
|
1091
|
-
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
function displaySummarySection(scanResult) {
|
|
1096
|
-
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1097
|
-
console.log(`
|
|
1098
|
-
${chalk6.bold("Summary:")}`);
|
|
1099
|
-
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1100
|
-
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1101
|
-
if (ext) {
|
|
1102
|
-
console.log(` ${ext}`);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
function displayScanResults(scanResult) {
|
|
1106
|
-
if (scanResult.packages.length > 1) {
|
|
1107
|
-
displayMonorepoResults(scanResult);
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
const { stack } = scanResult;
|
|
1111
|
-
console.log(`
|
|
1112
|
-
${chalk6.bold("Detected:")}`);
|
|
1113
|
-
if (stack.framework) {
|
|
1114
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.framework, FRAMEWORK_NAMES2)}`);
|
|
1115
|
-
}
|
|
1116
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1117
|
-
if (stack.styling) {
|
|
1118
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.styling, STYLING_NAMES2)}`);
|
|
1119
|
-
}
|
|
1120
|
-
if (stack.backend) {
|
|
1121
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.backend, FRAMEWORK_NAMES2)}`);
|
|
1122
|
-
}
|
|
1123
|
-
if (stack.linter) {
|
|
1124
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1125
|
-
}
|
|
1126
|
-
if (stack.formatter) {
|
|
1127
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1128
|
-
}
|
|
1129
|
-
if (stack.testRunner) {
|
|
1130
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1131
|
-
}
|
|
1132
|
-
if (stack.packageManager) {
|
|
1133
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1134
|
-
}
|
|
1135
|
-
if (stack.libraries.length > 0) {
|
|
1136
|
-
for (const lib of stack.libraries) {
|
|
1137
|
-
console.log(` ${chalk6.green("\u2713")} ${formatItem(lib, LIBRARY_NAMES)}`);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
const groups = groupByRole(scanResult.structure.directories);
|
|
1141
|
-
if (groups.length > 0) {
|
|
1142
|
-
console.log(`
|
|
1143
|
-
${chalk6.bold("Structure:")}`);
|
|
1144
|
-
for (const group of groups) {
|
|
1145
|
-
console.log(` ${chalk6.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
displayConventions(scanResult);
|
|
1149
|
-
displaySummarySection(scanResult);
|
|
1150
|
-
console.log("");
|
|
1151
|
-
}
|
|
951
|
+
import chalk9 from "chalk";
|
|
1152
952
|
|
|
1153
953
|
// src/utils/write-generated-files.ts
|
|
1154
954
|
import * as fs10 from "fs";
|
|
@@ -1179,18 +979,60 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
|
1179
979
|
// src/commands/init-hooks.ts
|
|
1180
980
|
import * as fs11 from "fs";
|
|
1181
981
|
import * as path12 from "path";
|
|
1182
|
-
import
|
|
982
|
+
import chalk5 from "chalk";
|
|
983
|
+
function setupClaudeCodeHook(projectRoot) {
|
|
984
|
+
const claudeDir = path12.join(projectRoot, ".claude");
|
|
985
|
+
if (!fs11.existsSync(claudeDir)) {
|
|
986
|
+
fs11.mkdirSync(claudeDir, { recursive: true });
|
|
987
|
+
}
|
|
988
|
+
const settingsPath = path12.join(claudeDir, "settings.json");
|
|
989
|
+
let settings = {};
|
|
990
|
+
if (fs11.existsSync(settingsPath)) {
|
|
991
|
+
try {
|
|
992
|
+
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const hooks = settings.hooks ?? {};
|
|
997
|
+
const postToolUse = hooks.PostToolUse;
|
|
998
|
+
if (Array.isArray(postToolUse)) {
|
|
999
|
+
const hasViberails = postToolUse.some(
|
|
1000
|
+
(entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.hooks) && entry.hooks.some(
|
|
1001
|
+
(h) => typeof h === "object" && h !== null && typeof h.command === "string" && h.command.includes("viberails")
|
|
1002
|
+
)
|
|
1003
|
+
);
|
|
1004
|
+
if (hasViberails) return;
|
|
1005
|
+
}
|
|
1006
|
+
const viberailsHook = {
|
|
1007
|
+
matcher: "Edit|Write",
|
|
1008
|
+
hooks: [
|
|
1009
|
+
{
|
|
1010
|
+
type: "command",
|
|
1011
|
+
command: "jq -r '.tool_input.file_path' | xargs npx viberails check --files"
|
|
1012
|
+
}
|
|
1013
|
+
]
|
|
1014
|
+
};
|
|
1015
|
+
if (!hooks.PostToolUse) {
|
|
1016
|
+
hooks.PostToolUse = [viberailsHook];
|
|
1017
|
+
} else if (Array.isArray(hooks.PostToolUse)) {
|
|
1018
|
+
hooks.PostToolUse.push(viberailsHook);
|
|
1019
|
+
}
|
|
1020
|
+
settings.hooks = hooks;
|
|
1021
|
+
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1022
|
+
`);
|
|
1023
|
+
console.log(` ${chalk5.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1024
|
+
}
|
|
1183
1025
|
function setupPreCommitHook(projectRoot) {
|
|
1184
1026
|
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1185
1027
|
if (fs11.existsSync(lefthookPath)) {
|
|
1186
1028
|
addLefthookPreCommit(lefthookPath);
|
|
1187
|
-
console.log(` ${
|
|
1029
|
+
console.log(` ${chalk5.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1188
1030
|
return;
|
|
1189
1031
|
}
|
|
1190
1032
|
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1191
1033
|
if (fs11.existsSync(huskyDir)) {
|
|
1192
1034
|
writeHuskyPreCommit(huskyDir);
|
|
1193
|
-
console.log(` ${
|
|
1035
|
+
console.log(` ${chalk5.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1194
1036
|
return;
|
|
1195
1037
|
}
|
|
1196
1038
|
const gitDir = path12.join(projectRoot, ".git");
|
|
@@ -1200,7 +1042,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
1200
1042
|
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1201
1043
|
}
|
|
1202
1044
|
writeGitHookPreCommit(hooksDir);
|
|
1203
|
-
console.log(` ${
|
|
1045
|
+
console.log(` ${chalk5.green("\u2713")} .git/hooks/pre-commit`);
|
|
1204
1046
|
}
|
|
1205
1047
|
}
|
|
1206
1048
|
function writeGitHookPreCommit(hooksDir) {
|
|
@@ -1249,6 +1091,187 @@ npx viberails check --staged
|
|
|
1249
1091
|
fs11.writeFileSync(hookPath, "#!/bin/sh\nnpx viberails check --staged\n", { mode: 493 });
|
|
1250
1092
|
}
|
|
1251
1093
|
|
|
1094
|
+
// src/commands/init-wizard.ts
|
|
1095
|
+
import * as p from "@clack/prompts";
|
|
1096
|
+
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES3, LIBRARY_NAMES as LIBRARY_NAMES2, STYLING_NAMES as STYLING_NAMES3 } from "@viberails/types";
|
|
1097
|
+
import chalk8 from "chalk";
|
|
1098
|
+
|
|
1099
|
+
// src/display.ts
|
|
1100
|
+
import { FRAMEWORK_NAMES as FRAMEWORK_NAMES2, LIBRARY_NAMES, STYLING_NAMES as STYLING_NAMES2 } from "@viberails/types";
|
|
1101
|
+
import chalk7 from "chalk";
|
|
1102
|
+
|
|
1103
|
+
// src/display-helpers.ts
|
|
1104
|
+
import { ROLE_DESCRIPTIONS } from "@viberails/types";
|
|
1105
|
+
function groupByRole(directories) {
|
|
1106
|
+
const map = /* @__PURE__ */ new Map();
|
|
1107
|
+
for (const dir of directories) {
|
|
1108
|
+
if (dir.role === "unknown") continue;
|
|
1109
|
+
const existing = map.get(dir.role);
|
|
1110
|
+
if (existing) {
|
|
1111
|
+
existing.dirs.push(dir);
|
|
1112
|
+
} else {
|
|
1113
|
+
map.set(dir.role, { dirs: [dir] });
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
const groups = [];
|
|
1117
|
+
for (const [role, { dirs }] of map) {
|
|
1118
|
+
const label = ROLE_DESCRIPTIONS[role] ?? role;
|
|
1119
|
+
const totalFiles = dirs.reduce((sum, d) => sum + d.fileCount, 0);
|
|
1120
|
+
groups.push({
|
|
1121
|
+
role,
|
|
1122
|
+
label,
|
|
1123
|
+
dirCount: dirs.length,
|
|
1124
|
+
totalFiles,
|
|
1125
|
+
singlePath: dirs.length === 1 ? dirs[0].path : void 0
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return groups;
|
|
1129
|
+
}
|
|
1130
|
+
function formatSummary(stats, packageCount) {
|
|
1131
|
+
const parts = [];
|
|
1132
|
+
if (packageCount && packageCount > 1) {
|
|
1133
|
+
parts.push(`${packageCount} packages`);
|
|
1134
|
+
}
|
|
1135
|
+
parts.push(`${stats.totalFiles.toLocaleString()} source files`);
|
|
1136
|
+
parts.push(`${stats.totalLines.toLocaleString()} lines`);
|
|
1137
|
+
parts.push(`avg ${Math.round(stats.averageFileLines)} lines/file`);
|
|
1138
|
+
return parts.join(" \xB7 ");
|
|
1139
|
+
}
|
|
1140
|
+
function formatRoleGroup(group) {
|
|
1141
|
+
const files = group.totalFiles === 1 ? "1 file" : `${group.totalFiles} files`;
|
|
1142
|
+
if (group.singlePath) {
|
|
1143
|
+
return `${group.label} \u2014 ${group.singlePath} (${files})`;
|
|
1144
|
+
}
|
|
1145
|
+
const dirs = group.dirCount === 1 ? "1 dir" : `${group.dirCount} dirs`;
|
|
1146
|
+
return `${group.label} \u2014 ${dirs} (${files})`;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/display-monorepo.ts
|
|
1150
|
+
import { FRAMEWORK_NAMES, STYLING_NAMES } from "@viberails/types";
|
|
1151
|
+
import chalk6 from "chalk";
|
|
1152
|
+
|
|
1153
|
+
// src/display.ts
|
|
1154
|
+
function formatItem(item, nameMap) {
|
|
1155
|
+
const name = nameMap?.[item.name] ?? item.name;
|
|
1156
|
+
return item.version ? `${name} ${item.version}` : name;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/commands/init-wizard.ts
|
|
1160
|
+
var DEFAULT_WIZARD_RESULT = {
|
|
1161
|
+
enforcement: "warn",
|
|
1162
|
+
checks: {
|
|
1163
|
+
fileSize: true,
|
|
1164
|
+
naming: true,
|
|
1165
|
+
tests: true,
|
|
1166
|
+
boundaries: false
|
|
1167
|
+
},
|
|
1168
|
+
integration: ["pre-commit"]
|
|
1169
|
+
};
|
|
1170
|
+
async function runWizard(scanResult) {
|
|
1171
|
+
const isMonorepo = scanResult.packages.length > 1;
|
|
1172
|
+
displayScanSummary(scanResult);
|
|
1173
|
+
const enforcement = await p.select({
|
|
1174
|
+
message: "How strict should viberails be?",
|
|
1175
|
+
initialValue: "warn",
|
|
1176
|
+
options: [
|
|
1177
|
+
{ value: "warn", label: "Warn", hint: "show issues, never block commits" },
|
|
1178
|
+
{ value: "enforce", label: "Enforce", hint: "block commits with violations" }
|
|
1179
|
+
]
|
|
1180
|
+
});
|
|
1181
|
+
if (p.isCancel(enforcement)) {
|
|
1182
|
+
p.cancel("Setup cancelled.");
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const checkOptions = [
|
|
1186
|
+
{ value: "fileSize", label: "File size limit (300 lines)" },
|
|
1187
|
+
{ value: "naming", label: "File naming conventions" },
|
|
1188
|
+
{ value: "tests", label: "Missing test files" }
|
|
1189
|
+
];
|
|
1190
|
+
if (isMonorepo) {
|
|
1191
|
+
checkOptions.push({ value: "boundaries", label: "Import boundaries" });
|
|
1192
|
+
}
|
|
1193
|
+
const enabledChecks = await p.multiselect({
|
|
1194
|
+
message: "Which checks should viberails run?",
|
|
1195
|
+
options: checkOptions,
|
|
1196
|
+
initialValues: ["fileSize", "naming", "tests"],
|
|
1197
|
+
required: false
|
|
1198
|
+
});
|
|
1199
|
+
if (p.isCancel(enabledChecks)) {
|
|
1200
|
+
p.cancel("Setup cancelled.");
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
const checks = {
|
|
1204
|
+
fileSize: enabledChecks.includes("fileSize"),
|
|
1205
|
+
naming: enabledChecks.includes("naming"),
|
|
1206
|
+
tests: enabledChecks.includes("tests"),
|
|
1207
|
+
boundaries: enabledChecks.includes("boundaries")
|
|
1208
|
+
};
|
|
1209
|
+
const integrationOptions = [
|
|
1210
|
+
{ value: "pre-commit", label: "Git pre-commit hook", hint: "runs on every commit" },
|
|
1211
|
+
{
|
|
1212
|
+
value: "claude-hook",
|
|
1213
|
+
label: "Claude Code hook",
|
|
1214
|
+
hint: "checks files as Claude edits them"
|
|
1215
|
+
},
|
|
1216
|
+
{ value: "context-only", label: "Context files only", hint: "no hooks" }
|
|
1217
|
+
];
|
|
1218
|
+
const integration = await p.multiselect({
|
|
1219
|
+
message: "Where should checks run?",
|
|
1220
|
+
options: integrationOptions,
|
|
1221
|
+
initialValues: ["pre-commit"],
|
|
1222
|
+
required: true
|
|
1223
|
+
});
|
|
1224
|
+
if (p.isCancel(integration)) {
|
|
1225
|
+
p.cancel("Setup cancelled.");
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
const finalIntegration = integration.includes("context-only") ? ["context-only"] : integration;
|
|
1229
|
+
return {
|
|
1230
|
+
enforcement,
|
|
1231
|
+
checks,
|
|
1232
|
+
integration: finalIntegration
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
function displayScanSummary(scanResult) {
|
|
1236
|
+
const { stack } = scanResult;
|
|
1237
|
+
const parts = [];
|
|
1238
|
+
if (stack.framework) parts.push(formatItem(stack.framework, FRAMEWORK_NAMES3));
|
|
1239
|
+
parts.push(formatItem(stack.language));
|
|
1240
|
+
if (stack.styling) parts.push(formatItem(stack.styling, STYLING_NAMES3));
|
|
1241
|
+
if (stack.backend) parts.push(formatItem(stack.backend, FRAMEWORK_NAMES3));
|
|
1242
|
+
p.log.info(`${chalk8.bold("Stack:")} ${parts.join(", ")}`);
|
|
1243
|
+
if (stack.linter || stack.formatter || stack.testRunner || stack.packageManager) {
|
|
1244
|
+
const tools = [];
|
|
1245
|
+
if (stack.linter) tools.push(formatItem(stack.linter));
|
|
1246
|
+
if (stack.formatter && stack.formatter !== stack.linter)
|
|
1247
|
+
tools.push(formatItem(stack.formatter));
|
|
1248
|
+
if (stack.testRunner) tools.push(formatItem(stack.testRunner));
|
|
1249
|
+
if (stack.packageManager) tools.push(formatItem(stack.packageManager));
|
|
1250
|
+
p.log.info(`${chalk8.bold("Tools:")} ${tools.join(", ")}`);
|
|
1251
|
+
}
|
|
1252
|
+
if (stack.libraries.length > 0) {
|
|
1253
|
+
const libs = stack.libraries.map((lib) => formatItem(lib, LIBRARY_NAMES2)).join(", ");
|
|
1254
|
+
p.log.info(`${chalk8.bold("Libraries:")} ${libs}`);
|
|
1255
|
+
}
|
|
1256
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1257
|
+
if (groups.length > 0) {
|
|
1258
|
+
const structParts = groups.map((g) => formatRoleGroup(g));
|
|
1259
|
+
p.log.info(`${chalk8.bold("Structure:")} ${structParts.join(", ")}`);
|
|
1260
|
+
}
|
|
1261
|
+
const conventionEntries = Object.entries(scanResult.conventions).filter(
|
|
1262
|
+
([, c]) => c.confidence !== "low"
|
|
1263
|
+
);
|
|
1264
|
+
if (conventionEntries.length > 0) {
|
|
1265
|
+
const convParts = conventionEntries.map(([, c]) => {
|
|
1266
|
+
const pct = Math.round(c.consistency);
|
|
1267
|
+
return `${c.value} (${pct}%)`;
|
|
1268
|
+
});
|
|
1269
|
+
p.log.info(`${chalk8.bold("Conventions:")} ${convParts.join(", ")}`);
|
|
1270
|
+
}
|
|
1271
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1272
|
+
p.log.info(`${chalk8.bold("Summary:")} ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1252
1275
|
// src/commands/init.ts
|
|
1253
1276
|
var CONFIG_FILE4 = "viberails.config.json";
|
|
1254
1277
|
function filterHighConfidence(conventions) {
|
|
@@ -1263,6 +1286,13 @@ function filterHighConfidence(conventions) {
|
|
|
1263
1286
|
}
|
|
1264
1287
|
return filtered;
|
|
1265
1288
|
}
|
|
1289
|
+
function applyWizardResult(config, wizard) {
|
|
1290
|
+
config.enforcement = wizard.enforcement;
|
|
1291
|
+
if (!wizard.checks.fileSize) config.rules.maxFileLines = 0;
|
|
1292
|
+
config.rules.enforceNaming = wizard.checks.naming;
|
|
1293
|
+
config.rules.requireTests = wizard.checks.tests;
|
|
1294
|
+
config.rules.enforceBoundaries = wizard.checks.boundaries;
|
|
1295
|
+
}
|
|
1266
1296
|
async function initCommand(options, cwd) {
|
|
1267
1297
|
const startDir = cwd ?? process.cwd();
|
|
1268
1298
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1274,64 +1304,69 @@ async function initCommand(options, cwd) {
|
|
|
1274
1304
|
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
1275
1305
|
if (fs12.existsSync(configPath)) {
|
|
1276
1306
|
console.log(
|
|
1277
|
-
|
|
1307
|
+
chalk9.yellow("!") + " viberails is already initialized in this project.\n Run " + chalk9.cyan("viberails sync") + " to update the generated files."
|
|
1278
1308
|
);
|
|
1279
1309
|
return;
|
|
1280
1310
|
}
|
|
1281
|
-
|
|
1311
|
+
p2.intro("viberails");
|
|
1312
|
+
const s = p2.spinner();
|
|
1313
|
+
s.start("Scanning project...");
|
|
1282
1314
|
const scanResult = await scan(projectRoot);
|
|
1283
|
-
|
|
1315
|
+
s.stop("Scan complete");
|
|
1284
1316
|
if (scanResult.statistics.totalFiles === 0) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1317
|
+
p2.log.warn(
|
|
1318
|
+
`No source files detected. viberails will generate context with minimal content.
|
|
1319
|
+
Run ${chalk9.cyan("viberails sync")} after adding source files.`
|
|
1287
1320
|
);
|
|
1288
1321
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1322
|
+
let wizard;
|
|
1323
|
+
if (options.yes) {
|
|
1324
|
+
wizard = { ...DEFAULT_WIZARD_RESULT };
|
|
1325
|
+
} else {
|
|
1326
|
+
const result = await runWizard(scanResult);
|
|
1327
|
+
if (!result) return;
|
|
1328
|
+
wizard = result;
|
|
1295
1329
|
}
|
|
1296
1330
|
const config = generateConfig(scanResult);
|
|
1297
1331
|
if (options.yes) {
|
|
1298
1332
|
config.conventions = filterHighConfidence(config.conventions);
|
|
1299
1333
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
console.log(` ${chalk8.green("\u2713")} Inferred ${inferred.length} boundary rules`);
|
|
1315
|
-
}
|
|
1334
|
+
applyWizardResult(config, wizard);
|
|
1335
|
+
if (wizard.checks.boundaries && config.workspace && config.workspace.packages.length > 0) {
|
|
1336
|
+
s.start("Inferring boundary rules...");
|
|
1337
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1338
|
+
const packages = resolveWorkspacePackages(projectRoot, config.workspace);
|
|
1339
|
+
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
1340
|
+
const inferred = inferBoundaries(graph);
|
|
1341
|
+
const ruleCount = Object.values(inferred).reduce((sum, denied) => sum + denied.length, 0);
|
|
1342
|
+
if (ruleCount > 0) {
|
|
1343
|
+
config.boundaries = inferred;
|
|
1344
|
+
s.stop(`Inferred ${ruleCount} boundary rules`);
|
|
1345
|
+
} else {
|
|
1346
|
+
s.stop("No boundary rules could be inferred");
|
|
1347
|
+
config.rules.enforceBoundaries = false;
|
|
1316
1348
|
}
|
|
1317
1349
|
}
|
|
1318
1350
|
fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1319
1351
|
`);
|
|
1320
1352
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
1321
1353
|
updateGitignore(projectRoot);
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
${
|
|
1330
|
-
console.log(`
|
|
1331
|
-
console.log(
|
|
1332
|
-
|
|
1354
|
+
if (wizard.integration.includes("pre-commit")) {
|
|
1355
|
+
setupPreCommitHook(projectRoot);
|
|
1356
|
+
}
|
|
1357
|
+
if (wizard.integration.includes("claude-hook")) {
|
|
1358
|
+
setupClaudeCodeHook(projectRoot);
|
|
1359
|
+
}
|
|
1360
|
+
p2.log.success(`${chalk9.bold("Created:")}`);
|
|
1361
|
+
console.log(` ${chalk9.green("\u2713")} ${CONFIG_FILE4}`);
|
|
1362
|
+
console.log(` ${chalk9.green("\u2713")} .viberails/context.md`);
|
|
1363
|
+
console.log(` ${chalk9.green("\u2713")} .viberails/scan-result.json`);
|
|
1364
|
+
p2.outro(
|
|
1365
|
+
`${chalk9.bold("Next steps:")}
|
|
1366
|
+
1. Review ${chalk9.cyan("viberails.config.json")} and adjust rules
|
|
1367
|
+
2. Commit ${chalk9.cyan("viberails.config.json")} and ${chalk9.cyan(".viberails/context.md")}
|
|
1368
|
+
3. Run ${chalk9.cyan("viberails check")} to verify your project passes`
|
|
1333
1369
|
);
|
|
1334
|
-
console.log(` 3. Run ${chalk8.cyan("viberails check")} to verify your project passes`);
|
|
1335
1370
|
}
|
|
1336
1371
|
function updateGitignore(projectRoot) {
|
|
1337
1372
|
const gitignorePath = path13.join(projectRoot, ".gitignore");
|
|
@@ -1351,7 +1386,7 @@ import * as fs13 from "fs";
|
|
|
1351
1386
|
import * as path14 from "path";
|
|
1352
1387
|
import { loadConfig as loadConfig4, mergeConfig } from "@viberails/config";
|
|
1353
1388
|
import { scan as scan2 } from "@viberails/scanner";
|
|
1354
|
-
import
|
|
1389
|
+
import chalk10 from "chalk";
|
|
1355
1390
|
var CONFIG_FILE5 = "viberails.config.json";
|
|
1356
1391
|
async function syncCommand(cwd) {
|
|
1357
1392
|
const startDir = cwd ?? process.cwd();
|
|
@@ -1363,21 +1398,21 @@ async function syncCommand(cwd) {
|
|
|
1363
1398
|
}
|
|
1364
1399
|
const configPath = path14.join(projectRoot, CONFIG_FILE5);
|
|
1365
1400
|
const existing = await loadConfig4(configPath);
|
|
1366
|
-
console.log(
|
|
1401
|
+
console.log(chalk10.dim("Scanning project..."));
|
|
1367
1402
|
const scanResult = await scan2(projectRoot);
|
|
1368
1403
|
const merged = mergeConfig(existing, scanResult);
|
|
1369
1404
|
fs13.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}
|
|
1370
1405
|
`);
|
|
1371
1406
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
1372
1407
|
console.log(`
|
|
1373
|
-
${
|
|
1374
|
-
console.log(` ${
|
|
1375
|
-
console.log(` ${
|
|
1376
|
-
console.log(` ${
|
|
1408
|
+
${chalk10.bold("Synced:")}`);
|
|
1409
|
+
console.log(` ${chalk10.green("\u2713")} ${CONFIG_FILE5} \u2014 updated`);
|
|
1410
|
+
console.log(` ${chalk10.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
1411
|
+
console.log(` ${chalk10.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
1377
1412
|
}
|
|
1378
1413
|
|
|
1379
1414
|
// src/index.ts
|
|
1380
|
-
var VERSION = "0.
|
|
1415
|
+
var VERSION = "0.3.0";
|
|
1381
1416
|
var program = new Command();
|
|
1382
1417
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1383
1418
|
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)").action(async (options) => {
|
|
@@ -1385,7 +1420,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
1385
1420
|
await initCommand(options);
|
|
1386
1421
|
} catch (err) {
|
|
1387
1422
|
const message = err instanceof Error ? err.message : String(err);
|
|
1388
|
-
console.error(`${
|
|
1423
|
+
console.error(`${chalk11.red("Error:")} ${message}`);
|
|
1389
1424
|
process.exit(1);
|
|
1390
1425
|
}
|
|
1391
1426
|
});
|
|
@@ -1394,7 +1429,7 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1394
1429
|
await syncCommand();
|
|
1395
1430
|
} catch (err) {
|
|
1396
1431
|
const message = err instanceof Error ? err.message : String(err);
|
|
1397
|
-
console.error(`${
|
|
1432
|
+
console.error(`${chalk11.red("Error:")} ${message}`);
|
|
1398
1433
|
process.exit(1);
|
|
1399
1434
|
}
|
|
1400
1435
|
});
|
|
@@ -1408,7 +1443,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
1408
1443
|
process.exit(exitCode);
|
|
1409
1444
|
} catch (err) {
|
|
1410
1445
|
const message = err instanceof Error ? err.message : String(err);
|
|
1411
|
-
console.error(`${
|
|
1446
|
+
console.error(`${chalk11.red("Error:")} ${message}`);
|
|
1412
1447
|
process.exit(1);
|
|
1413
1448
|
}
|
|
1414
1449
|
}
|
|
@@ -1419,7 +1454,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
1419
1454
|
process.exit(exitCode);
|
|
1420
1455
|
} catch (err) {
|
|
1421
1456
|
const message = err instanceof Error ? err.message : String(err);
|
|
1422
|
-
console.error(`${
|
|
1457
|
+
console.error(`${chalk11.red("Error:")} ${message}`);
|
|
1423
1458
|
process.exit(1);
|
|
1424
1459
|
}
|
|
1425
1460
|
});
|
|
@@ -1428,7 +1463,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
1428
1463
|
await boundariesCommand(options);
|
|
1429
1464
|
} catch (err) {
|
|
1430
1465
|
const message = err instanceof Error ? err.message : String(err);
|
|
1431
|
-
console.error(`${
|
|
1466
|
+
console.error(`${chalk11.red("Error:")} ${message}`);
|
|
1432
1467
|
process.exit(1);
|
|
1433
1468
|
}
|
|
1434
1469
|
});
|