viberails 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +471 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +471 -195
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -34,14 +34,14 @@ __export(index_exports, {
|
|
|
34
34
|
VERSION: () => VERSION
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
-
var
|
|
37
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/boundaries.ts
|
|
41
41
|
var fs3 = __toESM(require("fs"), 1);
|
|
42
42
|
var path3 = __toESM(require("path"), 1);
|
|
43
43
|
var import_config = require("@viberails/config");
|
|
44
|
-
var
|
|
44
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
45
45
|
|
|
46
46
|
// src/utils/find-project-root.ts
|
|
47
47
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -61,43 +61,104 @@ function findProjectRoot(startDir) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// src/utils/prompt.ts
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
var clack = __toESM(require("@clack/prompts"), 1);
|
|
65
|
+
function assertNotCancelled(value) {
|
|
66
|
+
if (clack.isCancel(value)) {
|
|
67
|
+
clack.cancel("Setup cancelled.");
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function confirm2(message) {
|
|
72
|
+
const result = await clack.confirm({ message, initialValue: true });
|
|
73
|
+
assertNotCancelled(result);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
async function confirmDangerous(message) {
|
|
77
|
+
const result = await clack.confirm({ message, initialValue: false });
|
|
78
|
+
assertNotCancelled(result);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
async function promptInitDecision() {
|
|
82
|
+
const result = await clack.select({
|
|
83
|
+
message: "Accept these settings?",
|
|
84
|
+
options: [
|
|
85
|
+
{ value: "accept", label: "Yes, looks good", hint: "recommended" },
|
|
86
|
+
{ value: "customize", label: "Let me customize" }
|
|
87
|
+
]
|
|
77
88
|
});
|
|
89
|
+
assertNotCancelled(result);
|
|
90
|
+
return result;
|
|
78
91
|
}
|
|
79
|
-
async function
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
async function promptRuleCustomization(defaults) {
|
|
93
|
+
const maxFileLinesResult = await clack.text({
|
|
94
|
+
message: "Maximum lines per source file?",
|
|
95
|
+
placeholder: String(defaults.maxFileLines),
|
|
96
|
+
initialValue: String(defaults.maxFileLines),
|
|
97
|
+
validate: (v) => {
|
|
98
|
+
const n = Number.parseInt(v, 10);
|
|
99
|
+
if (Number.isNaN(n) || n < 1) return "Enter a positive number";
|
|
100
|
+
}
|
|
86
101
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (trimmed === "") resolve4(defaultYes);
|
|
92
|
-
else resolve4(trimmed === "y" || trimmed === "yes");
|
|
93
|
-
});
|
|
102
|
+
assertNotCancelled(maxFileLinesResult);
|
|
103
|
+
const requireTestsResult = await clack.confirm({
|
|
104
|
+
message: "Require matching test files for source files?",
|
|
105
|
+
initialValue: defaults.requireTests
|
|
94
106
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
assertNotCancelled(requireTestsResult);
|
|
108
|
+
const namingLabel = defaults.fileNamingValue ? `Enforce file naming? (detected: ${defaults.fileNamingValue})` : "Enforce file naming?";
|
|
109
|
+
const enforceNamingResult = await clack.confirm({
|
|
110
|
+
message: namingLabel,
|
|
111
|
+
initialValue: defaults.enforceNaming
|
|
112
|
+
});
|
|
113
|
+
assertNotCancelled(enforceNamingResult);
|
|
114
|
+
const enforcementResult = await clack.select({
|
|
115
|
+
message: "Enforcement mode",
|
|
116
|
+
options: [
|
|
117
|
+
{
|
|
118
|
+
value: "warn",
|
|
119
|
+
label: "warn",
|
|
120
|
+
hint: "show violations but don't block commits (recommended)"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
value: "enforce",
|
|
124
|
+
label: "enforce",
|
|
125
|
+
hint: "block commits with violations"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
initialValue: defaults.enforcement
|
|
129
|
+
});
|
|
130
|
+
assertNotCancelled(enforcementResult);
|
|
131
|
+
return {
|
|
132
|
+
maxFileLines: Number.parseInt(maxFileLinesResult, 10),
|
|
133
|
+
requireTests: requireTestsResult,
|
|
134
|
+
enforceNaming: enforceNamingResult,
|
|
135
|
+
enforcement: enforcementResult
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function promptIntegrations(hookManager) {
|
|
139
|
+
const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook (git hook)";
|
|
140
|
+
const result = await clack.multiselect({
|
|
141
|
+
message: "Set up integrations?",
|
|
142
|
+
options: [
|
|
143
|
+
{
|
|
144
|
+
value: "preCommit",
|
|
145
|
+
label: hookLabel,
|
|
146
|
+
hint: "runs checks when you commit"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
value: "claude",
|
|
150
|
+
label: "Claude Code hook",
|
|
151
|
+
hint: "checks files when Claude edits them"
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
initialValues: ["preCommit", "claude"],
|
|
155
|
+
required: false
|
|
156
|
+
});
|
|
157
|
+
assertNotCancelled(result);
|
|
158
|
+
return {
|
|
159
|
+
preCommitHook: result.includes("preCommit"),
|
|
160
|
+
claudeCodeHook: result.includes("claude")
|
|
161
|
+
};
|
|
101
162
|
}
|
|
102
163
|
|
|
103
164
|
// src/utils/resolve-workspace-packages.ts
|
|
@@ -155,66 +216,67 @@ async function boundariesCommand(options, cwd) {
|
|
|
155
216
|
}
|
|
156
217
|
function displayRules(config) {
|
|
157
218
|
if (!config.boundaries || config.boundaries.length === 0) {
|
|
158
|
-
console.log(
|
|
159
|
-
console.log(`Run ${
|
|
219
|
+
console.log(import_chalk.default.yellow("No boundary rules configured."));
|
|
220
|
+
console.log(`Run ${import_chalk.default.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
160
221
|
return;
|
|
161
222
|
}
|
|
162
223
|
const allowRules = config.boundaries.filter((r) => r.allow);
|
|
163
224
|
const denyRules = config.boundaries.filter((r) => !r.allow);
|
|
164
225
|
console.log(`
|
|
165
|
-
${
|
|
226
|
+
${import_chalk.default.bold(`Boundary rules (${config.boundaries.length} rules):`)}
|
|
166
227
|
`);
|
|
167
228
|
for (const r of allowRules) {
|
|
168
|
-
console.log(` ${
|
|
229
|
+
console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
169
230
|
}
|
|
170
231
|
for (const r of denyRules) {
|
|
171
|
-
const reason = r.reason ?
|
|
172
|
-
console.log(` ${
|
|
232
|
+
const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
|
|
233
|
+
console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
173
234
|
}
|
|
174
235
|
console.log(
|
|
175
236
|
`
|
|
176
|
-
Enforcement: ${config.rules.enforceBoundaries ?
|
|
237
|
+
Enforcement: ${config.rules.enforceBoundaries ? import_chalk.default.green("on") : import_chalk.default.yellow("off")}`
|
|
177
238
|
);
|
|
178
239
|
}
|
|
179
240
|
async function inferAndDisplay(projectRoot, config, configPath) {
|
|
180
|
-
console.log(
|
|
241
|
+
console.log(import_chalk.default.dim("Analyzing imports..."));
|
|
181
242
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
182
243
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
183
244
|
const graph = await buildImportGraph(projectRoot, {
|
|
184
245
|
packages,
|
|
185
246
|
ignore: config.ignore
|
|
186
247
|
});
|
|
187
|
-
console.log(
|
|
248
|
+
console.log(import_chalk.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
188
249
|
const inferred = inferBoundaries(graph);
|
|
189
250
|
if (inferred.length === 0) {
|
|
190
|
-
console.log(
|
|
251
|
+
console.log(import_chalk.default.yellow("No boundary rules could be inferred."));
|
|
191
252
|
return;
|
|
192
253
|
}
|
|
193
254
|
const allow = inferred.filter((r) => r.allow);
|
|
194
255
|
const deny = inferred.filter((r) => !r.allow);
|
|
195
256
|
console.log(`
|
|
196
|
-
${
|
|
257
|
+
${import_chalk.default.bold("Inferred boundary rules:")}
|
|
197
258
|
`);
|
|
198
259
|
for (const r of allow) {
|
|
199
|
-
console.log(` ${
|
|
260
|
+
console.log(` ${import_chalk.default.green("\u2713")} ${r.from} \u2192 ${r.to}`);
|
|
200
261
|
}
|
|
201
262
|
for (const r of deny) {
|
|
202
|
-
const reason = r.reason ?
|
|
203
|
-
console.log(` ${
|
|
263
|
+
const reason = r.reason ? import_chalk.default.dim(` (${r.reason})`) : "";
|
|
264
|
+
console.log(` ${import_chalk.default.red("\u2717")} ${r.from} \u2192 ${r.to}${reason}`);
|
|
204
265
|
}
|
|
205
266
|
console.log(`
|
|
206
267
|
${allow.length} allowed, ${deny.length} denied`);
|
|
207
|
-
|
|
268
|
+
console.log("");
|
|
269
|
+
const shouldSave = await confirm2("Save to viberails.config.json?");
|
|
208
270
|
if (shouldSave) {
|
|
209
271
|
config.boundaries = inferred;
|
|
210
272
|
config.rules.enforceBoundaries = true;
|
|
211
273
|
fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
212
274
|
`);
|
|
213
|
-
console.log(`${
|
|
275
|
+
console.log(`${import_chalk.default.green("\u2713")} Saved ${inferred.length} rules`);
|
|
214
276
|
}
|
|
215
277
|
}
|
|
216
278
|
async function showGraph(projectRoot, config) {
|
|
217
|
-
console.log(
|
|
279
|
+
console.log(import_chalk.default.dim("Building import graph..."));
|
|
218
280
|
const { buildImportGraph } = await import("@viberails/graph");
|
|
219
281
|
const packages = config.workspace ? resolveWorkspacePackages(projectRoot, config.workspace) : void 0;
|
|
220
282
|
const graph = await buildImportGraph(projectRoot, {
|
|
@@ -222,20 +284,20 @@ async function showGraph(projectRoot, config) {
|
|
|
222
284
|
ignore: config.ignore
|
|
223
285
|
});
|
|
224
286
|
console.log(`
|
|
225
|
-
${
|
|
287
|
+
${import_chalk.default.bold("Import dependency graph:")}
|
|
226
288
|
`);
|
|
227
289
|
console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
|
|
228
290
|
`);
|
|
229
291
|
if (graph.packages.length > 0) {
|
|
230
292
|
for (const pkg of graph.packages) {
|
|
231
293
|
const deps = pkg.internalDeps.length > 0 ? `
|
|
232
|
-
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` :
|
|
294
|
+
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk.default.dim(" (no internal deps)");
|
|
233
295
|
console.log(` ${pkg.name}${deps}`);
|
|
234
296
|
}
|
|
235
297
|
}
|
|
236
298
|
if (graph.cycles.length > 0) {
|
|
237
299
|
console.log(`
|
|
238
|
-
${
|
|
300
|
+
${import_chalk.default.yellow("Cycles detected:")}`);
|
|
239
301
|
for (const cycle of graph.cycles) {
|
|
240
302
|
const paths = cycle.map((f) => path3.relative(projectRoot, f));
|
|
241
303
|
console.log(` ${paths.join(" \u2192 ")}`);
|
|
@@ -247,7 +309,7 @@ ${import_chalk2.default.yellow("Cycles detected:")}`);
|
|
|
247
309
|
var fs6 = __toESM(require("fs"), 1);
|
|
248
310
|
var path6 = __toESM(require("path"), 1);
|
|
249
311
|
var import_config2 = require("@viberails/config");
|
|
250
|
-
var
|
|
312
|
+
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
251
313
|
|
|
252
314
|
// src/commands/check-config.ts
|
|
253
315
|
function resolveConfigForFile(relPath, config) {
|
|
@@ -485,12 +547,12 @@ function printGroupedViolations(violations, limit) {
|
|
|
485
547
|
const toShow = group.slice(0, remaining);
|
|
486
548
|
const hidden = group.length - toShow.length;
|
|
487
549
|
for (const v of toShow) {
|
|
488
|
-
const icon = v.severity === "error" ?
|
|
489
|
-
console.log(`${icon} ${
|
|
550
|
+
const icon = v.severity === "error" ? import_chalk2.default.red("\u2717") : import_chalk2.default.yellow("!");
|
|
551
|
+
console.log(`${icon} ${import_chalk2.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
490
552
|
}
|
|
491
553
|
totalShown += toShow.length;
|
|
492
554
|
if (hidden > 0) {
|
|
493
|
-
console.log(
|
|
555
|
+
console.log(import_chalk2.default.dim(` ... and ${hidden} more ${rule} violations`));
|
|
494
556
|
}
|
|
495
557
|
}
|
|
496
558
|
}
|
|
@@ -508,13 +570,13 @@ async function checkCommand(options, cwd) {
|
|
|
508
570
|
const startDir = cwd ?? process.cwd();
|
|
509
571
|
const projectRoot = findProjectRoot(startDir);
|
|
510
572
|
if (!projectRoot) {
|
|
511
|
-
console.error(`${
|
|
573
|
+
console.error(`${import_chalk2.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
512
574
|
return 1;
|
|
513
575
|
}
|
|
514
576
|
const configPath = path6.join(projectRoot, CONFIG_FILE2);
|
|
515
577
|
if (!fs6.existsSync(configPath)) {
|
|
516
578
|
console.error(
|
|
517
|
-
`${
|
|
579
|
+
`${import_chalk2.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
518
580
|
);
|
|
519
581
|
return 1;
|
|
520
582
|
}
|
|
@@ -528,7 +590,7 @@ async function checkCommand(options, cwd) {
|
|
|
528
590
|
filesToCheck = getAllSourceFiles(projectRoot, config);
|
|
529
591
|
}
|
|
530
592
|
if (filesToCheck.length === 0) {
|
|
531
|
-
console.log(`${
|
|
593
|
+
console.log(`${import_chalk2.default.green("\u2713")} No files to check.`);
|
|
532
594
|
return 0;
|
|
533
595
|
}
|
|
534
596
|
const violations = [];
|
|
@@ -590,7 +652,7 @@ async function checkCommand(options, cwd) {
|
|
|
590
652
|
});
|
|
591
653
|
}
|
|
592
654
|
const elapsed = Date.now() - startTime;
|
|
593
|
-
console.log(
|
|
655
|
+
console.log(import_chalk2.default.dim(` Boundary check: ${graph.nodes.length} files in ${elapsed}ms`));
|
|
594
656
|
}
|
|
595
657
|
if (options.format === "json") {
|
|
596
658
|
console.log(
|
|
@@ -603,7 +665,7 @@ async function checkCommand(options, cwd) {
|
|
|
603
665
|
return config.enforcement === "enforce" && violations.length > 0 ? 1 : 0;
|
|
604
666
|
}
|
|
605
667
|
if (violations.length === 0) {
|
|
606
|
-
console.log(`${
|
|
668
|
+
console.log(`${import_chalk2.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
607
669
|
return 0;
|
|
608
670
|
}
|
|
609
671
|
if (!options.quiet) {
|
|
@@ -611,7 +673,7 @@ async function checkCommand(options, cwd) {
|
|
|
611
673
|
}
|
|
612
674
|
printSummary(violations);
|
|
613
675
|
if (config.enforcement === "enforce") {
|
|
614
|
-
console.log(
|
|
676
|
+
console.log(import_chalk2.default.red("Fix violations before committing."));
|
|
615
677
|
return 1;
|
|
616
678
|
}
|
|
617
679
|
return 0;
|
|
@@ -621,23 +683,22 @@ async function checkCommand(options, cwd) {
|
|
|
621
683
|
var fs9 = __toESM(require("fs"), 1);
|
|
622
684
|
var path10 = __toESM(require("path"), 1);
|
|
623
685
|
var import_config3 = require("@viberails/config");
|
|
624
|
-
var
|
|
686
|
+
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
625
687
|
|
|
626
688
|
// src/commands/fix-helpers.ts
|
|
627
689
|
var import_node_child_process2 = require("child_process");
|
|
628
|
-
var
|
|
629
|
-
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
690
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
630
691
|
function printPlan(renames, stubs) {
|
|
631
692
|
if (renames.length > 0) {
|
|
632
|
-
console.log(
|
|
693
|
+
console.log(import_chalk3.default.bold("\nFile renames:"));
|
|
633
694
|
for (const r of renames) {
|
|
634
|
-
console.log(` ${
|
|
695
|
+
console.log(` ${import_chalk3.default.red(r.oldPath)} \u2192 ${import_chalk3.default.green(r.newPath)}`);
|
|
635
696
|
}
|
|
636
697
|
}
|
|
637
698
|
if (stubs.length > 0) {
|
|
638
|
-
console.log(
|
|
699
|
+
console.log(import_chalk3.default.bold("\nTest stubs to create:"));
|
|
639
700
|
for (const s of stubs) {
|
|
640
|
-
console.log(` ${
|
|
701
|
+
console.log(` ${import_chalk3.default.green("+")} ${s.path}`);
|
|
641
702
|
}
|
|
642
703
|
}
|
|
643
704
|
}
|
|
@@ -659,15 +720,6 @@ function getConventionValue(convention) {
|
|
|
659
720
|
}
|
|
660
721
|
return void 0;
|
|
661
722
|
}
|
|
662
|
-
function promptConfirm(question) {
|
|
663
|
-
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
664
|
-
return new Promise((resolve4) => {
|
|
665
|
-
rl.question(`${question} (y/N) `, (answer) => {
|
|
666
|
-
rl.close();
|
|
667
|
-
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
723
|
|
|
672
724
|
// src/commands/fix-imports.ts
|
|
673
725
|
var path7 = __toESM(require("path"), 1);
|
|
@@ -888,13 +940,13 @@ async function fixCommand(options, cwd) {
|
|
|
888
940
|
const startDir = cwd ?? process.cwd();
|
|
889
941
|
const projectRoot = findProjectRoot(startDir);
|
|
890
942
|
if (!projectRoot) {
|
|
891
|
-
console.error(`${
|
|
943
|
+
console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
892
944
|
return 1;
|
|
893
945
|
}
|
|
894
946
|
const configPath = path10.join(projectRoot, CONFIG_FILE3);
|
|
895
947
|
if (!fs9.existsSync(configPath)) {
|
|
896
948
|
console.error(
|
|
897
|
-
`${
|
|
949
|
+
`${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
898
950
|
);
|
|
899
951
|
return 1;
|
|
900
952
|
}
|
|
@@ -903,7 +955,7 @@ async function fixCommand(options, cwd) {
|
|
|
903
955
|
const isDirty = checkGitDirty(projectRoot);
|
|
904
956
|
if (isDirty) {
|
|
905
957
|
console.log(
|
|
906
|
-
|
|
958
|
+
import_chalk4.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
907
959
|
);
|
|
908
960
|
}
|
|
909
961
|
}
|
|
@@ -933,16 +985,16 @@ async function fixCommand(options, cwd) {
|
|
|
933
985
|
}
|
|
934
986
|
}
|
|
935
987
|
if (dedupedRenames.length === 0 && testStubs.length === 0) {
|
|
936
|
-
console.log(`${
|
|
988
|
+
console.log(`${import_chalk4.default.green("\u2713")} No fixable violations found.`);
|
|
937
989
|
return 0;
|
|
938
990
|
}
|
|
939
991
|
printPlan(dedupedRenames, testStubs);
|
|
940
992
|
if (options.dryRun) {
|
|
941
|
-
console.log(
|
|
993
|
+
console.log(import_chalk4.default.dim("\nDry run \u2014 no changes applied."));
|
|
942
994
|
return 0;
|
|
943
995
|
}
|
|
944
996
|
if (!options.yes) {
|
|
945
|
-
const confirmed = await
|
|
997
|
+
const confirmed = await confirmDangerous("Apply these fixes?");
|
|
946
998
|
if (!confirmed) {
|
|
947
999
|
console.log("Aborted.");
|
|
948
1000
|
return 0;
|
|
@@ -969,15 +1021,15 @@ async function fixCommand(options, cwd) {
|
|
|
969
1021
|
}
|
|
970
1022
|
console.log("");
|
|
971
1023
|
if (renameCount > 0) {
|
|
972
|
-
console.log(`${
|
|
1024
|
+
console.log(`${import_chalk4.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
973
1025
|
}
|
|
974
1026
|
if (importUpdateCount > 0) {
|
|
975
1027
|
console.log(
|
|
976
|
-
`${
|
|
1028
|
+
`${import_chalk4.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
977
1029
|
);
|
|
978
1030
|
}
|
|
979
1031
|
if (stubCount > 0) {
|
|
980
|
-
console.log(`${
|
|
1032
|
+
console.log(`${import_chalk4.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
981
1033
|
}
|
|
982
1034
|
return 0;
|
|
983
1035
|
}
|
|
@@ -985,13 +1037,14 @@ async function fixCommand(options, cwd) {
|
|
|
985
1037
|
// src/commands/init.ts
|
|
986
1038
|
var fs12 = __toESM(require("fs"), 1);
|
|
987
1039
|
var path13 = __toESM(require("path"), 1);
|
|
1040
|
+
var clack2 = __toESM(require("@clack/prompts"), 1);
|
|
988
1041
|
var import_config4 = require("@viberails/config");
|
|
989
1042
|
var import_scanner = require("@viberails/scanner");
|
|
990
|
-
var
|
|
1043
|
+
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
991
1044
|
|
|
992
1045
|
// src/display.ts
|
|
993
1046
|
var import_types3 = require("@viberails/types");
|
|
994
|
-
var
|
|
1047
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
995
1048
|
|
|
996
1049
|
// src/display-helpers.ts
|
|
997
1050
|
var import_types = require("@viberails/types");
|
|
@@ -1044,7 +1097,7 @@ function formatRoleGroup(group) {
|
|
|
1044
1097
|
|
|
1045
1098
|
// src/display-monorepo.ts
|
|
1046
1099
|
var import_types2 = require("@viberails/types");
|
|
1047
|
-
var
|
|
1100
|
+
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1048
1101
|
function formatPackageSummary(pkg) {
|
|
1049
1102
|
const parts = [];
|
|
1050
1103
|
if (pkg.stack.framework) {
|
|
@@ -1060,19 +1113,19 @@ function formatPackageSummary(pkg) {
|
|
|
1060
1113
|
function displayMonorepoResults(scanResult) {
|
|
1061
1114
|
const { stack, packages } = scanResult;
|
|
1062
1115
|
console.log(`
|
|
1063
|
-
${
|
|
1064
|
-
console.log(` ${
|
|
1116
|
+
${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1117
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1065
1118
|
if (stack.packageManager) {
|
|
1066
|
-
console.log(` ${
|
|
1119
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1067
1120
|
}
|
|
1068
1121
|
if (stack.linter) {
|
|
1069
|
-
console.log(` ${
|
|
1122
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1070
1123
|
}
|
|
1071
1124
|
if (stack.formatter) {
|
|
1072
|
-
console.log(` ${
|
|
1125
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1073
1126
|
}
|
|
1074
1127
|
if (stack.testRunner) {
|
|
1075
|
-
console.log(` ${
|
|
1128
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1076
1129
|
}
|
|
1077
1130
|
console.log("");
|
|
1078
1131
|
for (const pkg of packages) {
|
|
@@ -1083,13 +1136,13 @@ ${import_chalk6.default.bold(`Detected: (monorepo, ${packages.length} packages)`
|
|
|
1083
1136
|
);
|
|
1084
1137
|
if (packagesWithDirs.length > 0) {
|
|
1085
1138
|
console.log(`
|
|
1086
|
-
${
|
|
1139
|
+
${import_chalk5.default.bold("Structure:")}`);
|
|
1087
1140
|
for (const pkg of packagesWithDirs) {
|
|
1088
1141
|
const groups = groupByRole(pkg.structure.directories);
|
|
1089
1142
|
if (groups.length === 0) continue;
|
|
1090
1143
|
console.log(` ${pkg.relativePath}:`);
|
|
1091
1144
|
for (const group of groups) {
|
|
1092
|
-
console.log(` ${
|
|
1145
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1093
1146
|
}
|
|
1094
1147
|
}
|
|
1095
1148
|
}
|
|
@@ -1097,6 +1150,58 @@ ${import_chalk6.default.bold("Structure:")}`);
|
|
|
1097
1150
|
displaySummarySection(scanResult);
|
|
1098
1151
|
console.log("");
|
|
1099
1152
|
}
|
|
1153
|
+
function formatPackageSummaryPlain(pkg) {
|
|
1154
|
+
const parts = [];
|
|
1155
|
+
if (pkg.stack.framework) {
|
|
1156
|
+
parts.push(formatItem(pkg.stack.framework, import_types2.FRAMEWORK_NAMES));
|
|
1157
|
+
}
|
|
1158
|
+
if (pkg.stack.styling) {
|
|
1159
|
+
parts.push(formatItem(pkg.stack.styling, import_types2.STYLING_NAMES));
|
|
1160
|
+
}
|
|
1161
|
+
const files = `${pkg.statistics.totalFiles} files`;
|
|
1162
|
+
const detail = parts.length > 0 ? `${parts.join(", ")} (${files})` : `(${files})`;
|
|
1163
|
+
return ` ${pkg.relativePath} \u2014 ${detail}`;
|
|
1164
|
+
}
|
|
1165
|
+
function formatMonorepoResultsText(scanResult, config) {
|
|
1166
|
+
const lines = [];
|
|
1167
|
+
const { stack, packages } = scanResult;
|
|
1168
|
+
lines.push(`Detected: (monorepo, ${packages.length} packages)`);
|
|
1169
|
+
const sharedParts = [formatItem(stack.language)];
|
|
1170
|
+
if (stack.packageManager) sharedParts.push(formatItem(stack.packageManager));
|
|
1171
|
+
if (stack.linter) sharedParts.push(formatItem(stack.linter));
|
|
1172
|
+
if (stack.formatter) sharedParts.push(formatItem(stack.formatter));
|
|
1173
|
+
if (stack.testRunner) sharedParts.push(formatItem(stack.testRunner));
|
|
1174
|
+
lines.push(` \u2713 ${sharedParts.join(" \xB7 ")}`);
|
|
1175
|
+
lines.push("");
|
|
1176
|
+
for (const pkg of packages) {
|
|
1177
|
+
lines.push(formatPackageSummaryPlain(pkg));
|
|
1178
|
+
}
|
|
1179
|
+
const packagesWithDirs = packages.filter(
|
|
1180
|
+
(pkg) => pkg.structure.directories.some((d) => d.role !== "unknown")
|
|
1181
|
+
);
|
|
1182
|
+
if (packagesWithDirs.length > 0) {
|
|
1183
|
+
lines.push("");
|
|
1184
|
+
lines.push("Structure:");
|
|
1185
|
+
for (const pkg of packagesWithDirs) {
|
|
1186
|
+
const groups = groupByRole(pkg.structure.directories);
|
|
1187
|
+
if (groups.length === 0) continue;
|
|
1188
|
+
lines.push(` ${pkg.relativePath}:`);
|
|
1189
|
+
for (const group of groups) {
|
|
1190
|
+
lines.push(` \u2713 ${formatRoleGroup(group)}`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
lines.push(...formatConventionsText(scanResult));
|
|
1195
|
+
const pkgCount = packages.length > 1 ? packages.length : void 0;
|
|
1196
|
+
lines.push("");
|
|
1197
|
+
lines.push(formatSummary(scanResult.statistics, pkgCount));
|
|
1198
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1199
|
+
if (ext) {
|
|
1200
|
+
lines.push(ext);
|
|
1201
|
+
}
|
|
1202
|
+
lines.push(...formatRulesText(config));
|
|
1203
|
+
return lines.join("\n");
|
|
1204
|
+
}
|
|
1100
1205
|
|
|
1101
1206
|
// src/display.ts
|
|
1102
1207
|
var CONVENTION_LABELS = {
|
|
@@ -1120,7 +1225,7 @@ function displayConventions(scanResult) {
|
|
|
1120
1225
|
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1121
1226
|
if (conventionEntries.length === 0) return;
|
|
1122
1227
|
console.log(`
|
|
1123
|
-
${
|
|
1228
|
+
${import_chalk6.default.bold("Conventions:")}`);
|
|
1124
1229
|
for (const [key, convention] of conventionEntries) {
|
|
1125
1230
|
if (convention.confidence === "low") continue;
|
|
1126
1231
|
const label = CONVENTION_LABELS[key] ?? key;
|
|
@@ -1128,19 +1233,19 @@ ${import_chalk7.default.bold("Conventions:")}`);
|
|
|
1128
1233
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1129
1234
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1130
1235
|
if (allSame || pkgValues.length <= 1) {
|
|
1131
|
-
const ind = convention.confidence === "high" ?
|
|
1132
|
-
const detail =
|
|
1236
|
+
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1237
|
+
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1133
1238
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1134
1239
|
} else {
|
|
1135
|
-
console.log(` ${
|
|
1240
|
+
console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
|
|
1136
1241
|
for (const pv of pkgValues) {
|
|
1137
1242
|
const pct = Math.round(pv.convention.consistency);
|
|
1138
1243
|
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1139
1244
|
}
|
|
1140
1245
|
}
|
|
1141
1246
|
} else {
|
|
1142
|
-
const ind = convention.confidence === "high" ?
|
|
1143
|
-
const detail =
|
|
1247
|
+
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1248
|
+
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1144
1249
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1145
1250
|
}
|
|
1146
1251
|
}
|
|
@@ -1148,7 +1253,7 @@ ${import_chalk7.default.bold("Conventions:")}`);
|
|
|
1148
1253
|
function displaySummarySection(scanResult) {
|
|
1149
1254
|
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1150
1255
|
console.log(`
|
|
1151
|
-
${
|
|
1256
|
+
${import_chalk6.default.bold("Summary:")}`);
|
|
1152
1257
|
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1153
1258
|
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1154
1259
|
if (ext) {
|
|
@@ -1162,43 +1267,43 @@ function displayScanResults(scanResult) {
|
|
|
1162
1267
|
}
|
|
1163
1268
|
const { stack } = scanResult;
|
|
1164
1269
|
console.log(`
|
|
1165
|
-
${
|
|
1270
|
+
${import_chalk6.default.bold("Detected:")}`);
|
|
1166
1271
|
if (stack.framework) {
|
|
1167
|
-
console.log(` ${
|
|
1272
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1168
1273
|
}
|
|
1169
|
-
console.log(` ${
|
|
1274
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1170
1275
|
if (stack.styling) {
|
|
1171
|
-
console.log(` ${
|
|
1276
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1172
1277
|
}
|
|
1173
1278
|
if (stack.backend) {
|
|
1174
|
-
console.log(` ${
|
|
1279
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1175
1280
|
}
|
|
1176
1281
|
if (stack.orm) {
|
|
1177
|
-
console.log(` ${
|
|
1282
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1178
1283
|
}
|
|
1179
1284
|
if (stack.linter) {
|
|
1180
|
-
console.log(` ${
|
|
1285
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1181
1286
|
}
|
|
1182
1287
|
if (stack.formatter) {
|
|
1183
|
-
console.log(` ${
|
|
1288
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1184
1289
|
}
|
|
1185
1290
|
if (stack.testRunner) {
|
|
1186
|
-
console.log(` ${
|
|
1291
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1187
1292
|
}
|
|
1188
1293
|
if (stack.packageManager) {
|
|
1189
|
-
console.log(` ${
|
|
1294
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1190
1295
|
}
|
|
1191
1296
|
if (stack.libraries.length > 0) {
|
|
1192
1297
|
for (const lib of stack.libraries) {
|
|
1193
|
-
console.log(` ${
|
|
1298
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1194
1299
|
}
|
|
1195
1300
|
}
|
|
1196
1301
|
const groups = groupByRole(scanResult.structure.directories);
|
|
1197
1302
|
if (groups.length > 0) {
|
|
1198
1303
|
console.log(`
|
|
1199
|
-
${
|
|
1304
|
+
${import_chalk6.default.bold("Structure:")}`);
|
|
1200
1305
|
for (const group of groups) {
|
|
1201
|
-
console.log(` ${
|
|
1306
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1202
1307
|
}
|
|
1203
1308
|
}
|
|
1204
1309
|
displayConventions(scanResult);
|
|
@@ -1209,37 +1314,145 @@ function getConventionStr(cv) {
|
|
|
1209
1314
|
return typeof cv === "string" ? cv : cv.value;
|
|
1210
1315
|
}
|
|
1211
1316
|
function displayRulesPreview(config) {
|
|
1212
|
-
console.log(`${
|
|
1213
|
-
console.log(` ${
|
|
1317
|
+
console.log(`${import_chalk6.default.bold("Rules:")}`);
|
|
1318
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1214
1319
|
if (config.rules.requireTests && config.structure.testPattern) {
|
|
1215
1320
|
console.log(
|
|
1216
|
-
` ${
|
|
1321
|
+
` ${import_chalk6.default.dim("\u2022")} Require test files: yes (${config.structure.testPattern})`
|
|
1217
1322
|
);
|
|
1218
1323
|
} else if (config.rules.requireTests) {
|
|
1219
|
-
console.log(` ${
|
|
1324
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Require test files: yes`);
|
|
1220
1325
|
} else {
|
|
1221
|
-
console.log(` ${
|
|
1326
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Require test files: no`);
|
|
1222
1327
|
}
|
|
1223
1328
|
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1224
1329
|
console.log(
|
|
1225
|
-
` ${
|
|
1330
|
+
` ${import_chalk6.default.dim("\u2022")} Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`
|
|
1226
1331
|
);
|
|
1227
1332
|
} else {
|
|
1228
|
-
console.log(` ${
|
|
1333
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: no`);
|
|
1229
1334
|
}
|
|
1230
1335
|
console.log(
|
|
1231
|
-
` ${
|
|
1336
|
+
` ${import_chalk6.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1232
1337
|
);
|
|
1233
1338
|
console.log("");
|
|
1234
1339
|
if (config.enforcement === "enforce") {
|
|
1235
|
-
console.log(`${
|
|
1340
|
+
console.log(`${import_chalk6.default.bold("Enforcement mode:")} enforce (violations will block commits)`);
|
|
1236
1341
|
} else {
|
|
1237
1342
|
console.log(
|
|
1238
|
-
`${
|
|
1343
|
+
`${import_chalk6.default.bold("Enforcement mode:")} warn (violations shown but won't block commits)`
|
|
1239
1344
|
);
|
|
1240
1345
|
}
|
|
1241
1346
|
console.log("");
|
|
1242
1347
|
}
|
|
1348
|
+
function plainConfidenceLabel(convention) {
|
|
1349
|
+
const pct = Math.round(convention.consistency);
|
|
1350
|
+
if (convention.confidence === "high") {
|
|
1351
|
+
return `${pct}%`;
|
|
1352
|
+
}
|
|
1353
|
+
return `${pct}%, suggested only`;
|
|
1354
|
+
}
|
|
1355
|
+
function formatConventionsText(scanResult) {
|
|
1356
|
+
const lines = [];
|
|
1357
|
+
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1358
|
+
if (conventionEntries.length === 0) return lines;
|
|
1359
|
+
lines.push("");
|
|
1360
|
+
lines.push("Conventions:");
|
|
1361
|
+
for (const [key, convention] of conventionEntries) {
|
|
1362
|
+
if (convention.confidence === "low") continue;
|
|
1363
|
+
const label = CONVENTION_LABELS[key] ?? key;
|
|
1364
|
+
if (scanResult.packages.length > 1) {
|
|
1365
|
+
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1366
|
+
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1367
|
+
if (allSame || pkgValues.length <= 1) {
|
|
1368
|
+
const ind = convention.confidence === "high" ? "\u2713" : "~";
|
|
1369
|
+
lines.push(` ${ind} ${label}: ${convention.value} (${plainConfidenceLabel(convention)})`);
|
|
1370
|
+
} else {
|
|
1371
|
+
lines.push(` ~ ${label}: varies by package`);
|
|
1372
|
+
for (const pv of pkgValues) {
|
|
1373
|
+
const pct = Math.round(pv.convention.consistency);
|
|
1374
|
+
lines.push(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
} else {
|
|
1378
|
+
const ind = convention.confidence === "high" ? "\u2713" : "~";
|
|
1379
|
+
lines.push(` ${ind} ${label}: ${convention.value} (${plainConfidenceLabel(convention)})`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return lines;
|
|
1383
|
+
}
|
|
1384
|
+
function formatRulesText(config) {
|
|
1385
|
+
const lines = [];
|
|
1386
|
+
lines.push("");
|
|
1387
|
+
lines.push("Rules:");
|
|
1388
|
+
lines.push(` \u2022 Max file size: ${config.rules.maxFileLines} lines`);
|
|
1389
|
+
if (config.rules.requireTests && config.structure.testPattern) {
|
|
1390
|
+
lines.push(` \u2022 Require test files: yes (${config.structure.testPattern})`);
|
|
1391
|
+
} else if (config.rules.requireTests) {
|
|
1392
|
+
lines.push(" \u2022 Require test files: yes");
|
|
1393
|
+
} else {
|
|
1394
|
+
lines.push(" \u2022 Require test files: no");
|
|
1395
|
+
}
|
|
1396
|
+
if (config.rules.enforceNaming && config.conventions.fileNaming) {
|
|
1397
|
+
lines.push(` \u2022 Enforce file naming: ${getConventionStr(config.conventions.fileNaming)}`);
|
|
1398
|
+
} else {
|
|
1399
|
+
lines.push(" \u2022 Enforce file naming: no");
|
|
1400
|
+
}
|
|
1401
|
+
lines.push(` \u2022 Enforcement mode: ${config.enforcement}`);
|
|
1402
|
+
return lines;
|
|
1403
|
+
}
|
|
1404
|
+
function formatScanResultsText(scanResult, config) {
|
|
1405
|
+
if (scanResult.packages.length > 1) {
|
|
1406
|
+
return formatMonorepoResultsText(scanResult, config);
|
|
1407
|
+
}
|
|
1408
|
+
const lines = [];
|
|
1409
|
+
const { stack } = scanResult;
|
|
1410
|
+
lines.push("Detected:");
|
|
1411
|
+
if (stack.framework) {
|
|
1412
|
+
lines.push(` \u2713 ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1413
|
+
}
|
|
1414
|
+
lines.push(` \u2713 ${formatItem(stack.language)}`);
|
|
1415
|
+
if (stack.styling) {
|
|
1416
|
+
lines.push(` \u2713 ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1417
|
+
}
|
|
1418
|
+
if (stack.backend) {
|
|
1419
|
+
lines.push(` \u2713 ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1420
|
+
}
|
|
1421
|
+
if (stack.orm) {
|
|
1422
|
+
lines.push(` \u2713 ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1423
|
+
}
|
|
1424
|
+
const secondaryParts = [];
|
|
1425
|
+
if (stack.packageManager) secondaryParts.push(formatItem(stack.packageManager));
|
|
1426
|
+
if (stack.linter) secondaryParts.push(formatItem(stack.linter));
|
|
1427
|
+
if (stack.formatter) secondaryParts.push(formatItem(stack.formatter));
|
|
1428
|
+
if (stack.testRunner) secondaryParts.push(formatItem(stack.testRunner));
|
|
1429
|
+
if (secondaryParts.length > 0) {
|
|
1430
|
+
lines.push(` \u2713 ${secondaryParts.join(" \xB7 ")}`);
|
|
1431
|
+
}
|
|
1432
|
+
if (stack.libraries.length > 0) {
|
|
1433
|
+
for (const lib of stack.libraries) {
|
|
1434
|
+
lines.push(` \u2713 ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
const groups = groupByRole(scanResult.structure.directories);
|
|
1438
|
+
if (groups.length > 0) {
|
|
1439
|
+
lines.push("");
|
|
1440
|
+
lines.push("Structure:");
|
|
1441
|
+
for (const group of groups) {
|
|
1442
|
+
lines.push(` \u2713 ${formatRoleGroup(group)}`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
lines.push(...formatConventionsText(scanResult));
|
|
1446
|
+
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1447
|
+
lines.push("");
|
|
1448
|
+
lines.push(formatSummary(scanResult.statistics, pkgCount));
|
|
1449
|
+
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1450
|
+
if (ext) {
|
|
1451
|
+
lines.push(ext);
|
|
1452
|
+
}
|
|
1453
|
+
lines.push(...formatRulesText(config));
|
|
1454
|
+
return lines.join("\n");
|
|
1455
|
+
}
|
|
1243
1456
|
|
|
1244
1457
|
// src/utils/write-generated-files.ts
|
|
1245
1458
|
var fs10 = __toESM(require("fs"), 1);
|
|
@@ -1270,18 +1483,18 @@ function writeGeneratedFiles(projectRoot, config, scanResult) {
|
|
|
1270
1483
|
// src/commands/init-hooks.ts
|
|
1271
1484
|
var fs11 = __toESM(require("fs"), 1);
|
|
1272
1485
|
var path12 = __toESM(require("path"), 1);
|
|
1273
|
-
var
|
|
1486
|
+
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
1274
1487
|
function setupPreCommitHook(projectRoot) {
|
|
1275
1488
|
const lefthookPath = path12.join(projectRoot, "lefthook.yml");
|
|
1276
1489
|
if (fs11.existsSync(lefthookPath)) {
|
|
1277
1490
|
addLefthookPreCommit(lefthookPath);
|
|
1278
|
-
console.log(` ${
|
|
1491
|
+
console.log(` ${import_chalk7.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
1279
1492
|
return;
|
|
1280
1493
|
}
|
|
1281
1494
|
const huskyDir = path12.join(projectRoot, ".husky");
|
|
1282
1495
|
if (fs11.existsSync(huskyDir)) {
|
|
1283
1496
|
writeHuskyPreCommit(huskyDir);
|
|
1284
|
-
console.log(` ${
|
|
1497
|
+
console.log(` ${import_chalk7.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
1285
1498
|
return;
|
|
1286
1499
|
}
|
|
1287
1500
|
const gitDir = path12.join(projectRoot, ".git");
|
|
@@ -1291,7 +1504,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
1291
1504
|
fs11.mkdirSync(hooksDir, { recursive: true });
|
|
1292
1505
|
}
|
|
1293
1506
|
writeGitHookPreCommit(hooksDir);
|
|
1294
|
-
console.log(` ${
|
|
1507
|
+
console.log(` ${import_chalk7.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
1295
1508
|
}
|
|
1296
1509
|
}
|
|
1297
1510
|
function writeGitHookPreCommit(hooksDir) {
|
|
@@ -1361,7 +1574,7 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1361
1574
|
settings = JSON.parse(fs11.readFileSync(settingsPath, "utf-8"));
|
|
1362
1575
|
} catch {
|
|
1363
1576
|
console.warn(
|
|
1364
|
-
` ${
|
|
1577
|
+
` ${import_chalk7.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 resetting to add hook`
|
|
1365
1578
|
);
|
|
1366
1579
|
settings = {};
|
|
1367
1580
|
}
|
|
@@ -1386,7 +1599,7 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
1386
1599
|
settings.hooks = hooks;
|
|
1387
1600
|
fs11.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
1388
1601
|
`);
|
|
1389
|
-
console.log(` ${
|
|
1602
|
+
console.log(` ${import_chalk7.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
1390
1603
|
}
|
|
1391
1604
|
function writeHuskyPreCommit(huskyDir) {
|
|
1392
1605
|
const hookPath = path12.join(huskyDir, "pre-commit");
|
|
@@ -1416,6 +1629,14 @@ function filterHighConfidence(conventions) {
|
|
|
1416
1629
|
}
|
|
1417
1630
|
return filtered;
|
|
1418
1631
|
}
|
|
1632
|
+
function getConventionStr2(cv) {
|
|
1633
|
+
if (!cv) return void 0;
|
|
1634
|
+
return typeof cv === "string" ? cv : cv.value;
|
|
1635
|
+
}
|
|
1636
|
+
function hasConventionOverrides(config) {
|
|
1637
|
+
if (!config.packages || config.packages.length === 0) return false;
|
|
1638
|
+
return config.packages.some((pkg) => pkg.conventions && Object.keys(pkg.conventions).length > 0);
|
|
1639
|
+
}
|
|
1419
1640
|
async function initCommand(options, cwd) {
|
|
1420
1641
|
const startDir = cwd ?? process.cwd();
|
|
1421
1642
|
const projectRoot = findProjectRoot(startDir);
|
|
@@ -1427,82 +1648,137 @@ async function initCommand(options, cwd) {
|
|
|
1427
1648
|
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
1428
1649
|
if (fs12.existsSync(configPath)) {
|
|
1429
1650
|
console.log(
|
|
1430
|
-
|
|
1651
|
+
`${import_chalk8.default.yellow("!")} viberails is already initialized.
|
|
1652
|
+
Run ${import_chalk8.default.cyan("viberails sync")} to update, or delete viberails.config.json to start fresh.`
|
|
1431
1653
|
);
|
|
1432
1654
|
return;
|
|
1433
1655
|
}
|
|
1434
|
-
console.log(import_chalk9.default.dim("Scanning project..."));
|
|
1435
|
-
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
1436
|
-
const config = (0, import_config4.generateConfig)(scanResult);
|
|
1437
1656
|
if (options.yes) {
|
|
1438
|
-
|
|
1657
|
+
console.log(import_chalk8.default.dim("Scanning project..."));
|
|
1658
|
+
const scanResult2 = await (0, import_scanner.scan)(projectRoot);
|
|
1659
|
+
const config2 = (0, import_config4.generateConfig)(scanResult2);
|
|
1660
|
+
config2.conventions = filterHighConfidence(config2.conventions);
|
|
1661
|
+
displayScanResults(scanResult2);
|
|
1662
|
+
displayRulesPreview(config2);
|
|
1663
|
+
if (config2.workspace?.packages && config2.workspace.packages.length > 0) {
|
|
1664
|
+
console.log(import_chalk8.default.dim("Building import graph..."));
|
|
1665
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1666
|
+
const packages = resolveWorkspacePackages(projectRoot, config2.workspace);
|
|
1667
|
+
const graph = await buildImportGraph(projectRoot, {
|
|
1668
|
+
packages,
|
|
1669
|
+
ignore: config2.ignore
|
|
1670
|
+
});
|
|
1671
|
+
const inferred = inferBoundaries(graph);
|
|
1672
|
+
if (inferred.length > 0) {
|
|
1673
|
+
config2.boundaries = inferred;
|
|
1674
|
+
config2.rules.enforceBoundaries = true;
|
|
1675
|
+
console.log(` Inferred ${inferred.length} boundary rules`);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
fs12.writeFileSync(configPath, `${JSON.stringify(config2, null, 2)}
|
|
1679
|
+
`);
|
|
1680
|
+
writeGeneratedFiles(projectRoot, config2, scanResult2);
|
|
1681
|
+
updateGitignore(projectRoot);
|
|
1682
|
+
console.log(`
|
|
1683
|
+
Created:`);
|
|
1684
|
+
console.log(` ${import_chalk8.default.green("\u2713")} ${CONFIG_FILE4}`);
|
|
1685
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .viberails/context.md`);
|
|
1686
|
+
console.log(` ${import_chalk8.default.green("\u2713")} .viberails/scan-result.json`);
|
|
1687
|
+
return;
|
|
1439
1688
|
}
|
|
1440
|
-
|
|
1689
|
+
clack2.intro("viberails");
|
|
1690
|
+
const s = clack2.spinner();
|
|
1691
|
+
s.start("Scanning project...");
|
|
1692
|
+
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
1693
|
+
const config = (0, import_config4.generateConfig)(scanResult);
|
|
1694
|
+
s.stop("Scan complete");
|
|
1441
1695
|
if (scanResult.statistics.totalFiles === 0) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1696
|
+
clack2.log.warn(
|
|
1697
|
+
"No source files detected. viberails will generate context\nwith minimal content. Run viberails sync after adding files."
|
|
1444
1698
|
);
|
|
1445
1699
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1700
|
+
const resultsText = formatScanResultsText(scanResult, config);
|
|
1701
|
+
clack2.note(resultsText, "Scan results");
|
|
1702
|
+
const decision = await promptInitDecision();
|
|
1703
|
+
if (decision === "customize") {
|
|
1704
|
+
clack2.note(
|
|
1705
|
+
"Rules control what viberails checks for.\nYou can change these later in viberails.config.json.",
|
|
1706
|
+
"Rules"
|
|
1707
|
+
);
|
|
1708
|
+
const overrides = await promptRuleCustomization({
|
|
1709
|
+
maxFileLines: config.rules.maxFileLines,
|
|
1710
|
+
requireTests: config.rules.requireTests,
|
|
1711
|
+
enforceNaming: config.rules.enforceNaming,
|
|
1712
|
+
enforcement: config.enforcement,
|
|
1713
|
+
fileNamingValue: getConventionStr2(config.conventions.fileNaming)
|
|
1714
|
+
});
|
|
1715
|
+
config.rules.maxFileLines = overrides.maxFileLines;
|
|
1716
|
+
config.rules.requireTests = overrides.requireTests;
|
|
1717
|
+
config.rules.enforceNaming = overrides.enforceNaming;
|
|
1718
|
+
config.enforcement = overrides.enforcement;
|
|
1719
|
+
if (config.workspace?.packages && config.workspace.packages.length > 0) {
|
|
1720
|
+
clack2.note(
|
|
1721
|
+
'These rules apply globally. To customize per package,\nedit the "packages" section in viberails.config.json.',
|
|
1722
|
+
"Per-package overrides"
|
|
1723
|
+
);
|
|
1452
1724
|
}
|
|
1453
1725
|
}
|
|
1454
|
-
if (config.workspace && config.workspace.packages.length > 0) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1726
|
+
if (config.workspace?.packages && config.workspace.packages.length > 0) {
|
|
1727
|
+
clack2.note(
|
|
1728
|
+
"Boundary rules prevent packages from importing where they\nshouldn't. viberails scans your existing imports and creates\nrules based on what's already working.",
|
|
1729
|
+
"Boundaries"
|
|
1730
|
+
);
|
|
1731
|
+
const shouldInfer = await confirm2("Infer boundary rules from import patterns?");
|
|
1460
1732
|
if (shouldInfer) {
|
|
1461
|
-
|
|
1733
|
+
const bs = clack2.spinner();
|
|
1734
|
+
bs.start("Building import graph...");
|
|
1462
1735
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
1463
1736
|
const packages = resolveWorkspacePackages(projectRoot, config.workspace);
|
|
1464
|
-
const graph = await buildImportGraph(projectRoot, {
|
|
1737
|
+
const graph = await buildImportGraph(projectRoot, {
|
|
1738
|
+
packages,
|
|
1739
|
+
ignore: config.ignore
|
|
1740
|
+
});
|
|
1465
1741
|
const inferred = inferBoundaries(graph);
|
|
1466
1742
|
if (inferred.length > 0) {
|
|
1467
1743
|
config.boundaries = inferred;
|
|
1468
1744
|
config.rules.enforceBoundaries = true;
|
|
1469
|
-
|
|
1745
|
+
bs.stop(`Inferred ${inferred.length} boundary rules`);
|
|
1746
|
+
} else {
|
|
1747
|
+
bs.stop("No boundary rules inferred");
|
|
1470
1748
|
}
|
|
1471
1749
|
}
|
|
1472
1750
|
}
|
|
1473
1751
|
const hookManager = detectHookManager(projectRoot);
|
|
1474
|
-
|
|
1475
|
-
if (
|
|
1476
|
-
|
|
1477
|
-
|
|
1752
|
+
const integrations = await promptIntegrations(hookManager);
|
|
1753
|
+
if (hasConventionOverrides(config)) {
|
|
1754
|
+
clack2.note(
|
|
1755
|
+
"Some packages use different conventions. Per-package\noverrides have been saved in viberails.config.json \u2014\nreview and adjust as needed.",
|
|
1756
|
+
"Per-package conventions"
|
|
1757
|
+
);
|
|
1478
1758
|
}
|
|
1479
1759
|
fs12.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
1480
1760
|
`);
|
|
1481
1761
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
1482
1762
|
updateGitignore(projectRoot);
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1763
|
+
const createdFiles = [
|
|
1764
|
+
CONFIG_FILE4,
|
|
1765
|
+
".viberails/context.md",
|
|
1766
|
+
".viberails/scan-result.json"
|
|
1767
|
+
];
|
|
1488
1768
|
if (integrations.preCommitHook) {
|
|
1489
1769
|
setupPreCommitHook(projectRoot);
|
|
1770
|
+
const hookMgr = detectHookManager(projectRoot);
|
|
1771
|
+
if (hookMgr) {
|
|
1772
|
+
createdFiles.push(`lefthook.yml \u2014 added viberails pre-commit`);
|
|
1773
|
+
}
|
|
1490
1774
|
}
|
|
1491
1775
|
if (integrations.claudeCodeHook) {
|
|
1492
1776
|
setupClaudeCodeHook(projectRoot);
|
|
1777
|
+
createdFiles.push(".claude/settings.json \u2014 added viberails hook");
|
|
1493
1778
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
];
|
|
1498
|
-
if (integrations.claudeCodeHook) {
|
|
1499
|
-
filesToCommit.push(import_chalk9.default.cyan(".claude/settings.json"));
|
|
1500
|
-
}
|
|
1501
|
-
console.log(`
|
|
1502
|
-
${import_chalk9.default.bold("Next steps:")}`);
|
|
1503
|
-
console.log(` 1. Review ${import_chalk9.default.cyan("viberails.config.json")} and adjust rules`);
|
|
1504
|
-
console.log(` 2. Commit ${filesToCommit.join(", ")}`);
|
|
1505
|
-
console.log(` 3. Run ${import_chalk9.default.cyan("viberails check")} to verify your project passes`);
|
|
1779
|
+
clack2.log.success(`Created:
|
|
1780
|
+
${createdFiles.map((f) => ` ${f}`).join("\n")}`);
|
|
1781
|
+
clack2.outro("Done! Next: review viberails.config.json, then run viberails check");
|
|
1506
1782
|
}
|
|
1507
1783
|
function updateGitignore(projectRoot) {
|
|
1508
1784
|
const gitignorePath = path13.join(projectRoot, ".gitignore");
|
|
@@ -1523,7 +1799,7 @@ var fs13 = __toESM(require("fs"), 1);
|
|
|
1523
1799
|
var path14 = __toESM(require("path"), 1);
|
|
1524
1800
|
var import_config5 = require("@viberails/config");
|
|
1525
1801
|
var import_scanner2 = require("@viberails/scanner");
|
|
1526
|
-
var
|
|
1802
|
+
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
1527
1803
|
var CONFIG_FILE5 = "viberails.config.json";
|
|
1528
1804
|
async function syncCommand(cwd) {
|
|
1529
1805
|
const startDir = cwd ?? process.cwd();
|
|
@@ -1535,7 +1811,7 @@ async function syncCommand(cwd) {
|
|
|
1535
1811
|
}
|
|
1536
1812
|
const configPath = path14.join(projectRoot, CONFIG_FILE5);
|
|
1537
1813
|
const existing = await (0, import_config5.loadConfig)(configPath);
|
|
1538
|
-
console.log(
|
|
1814
|
+
console.log(import_chalk9.default.dim("Scanning project..."));
|
|
1539
1815
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
1540
1816
|
const merged = (0, import_config5.mergeConfig)(existing, scanResult);
|
|
1541
1817
|
const existingJson = JSON.stringify(existing, null, 2);
|
|
@@ -1543,25 +1819,25 @@ async function syncCommand(cwd) {
|
|
|
1543
1819
|
const configChanged = existingJson !== mergedJson;
|
|
1544
1820
|
if (configChanged) {
|
|
1545
1821
|
console.log(
|
|
1546
|
-
` ${
|
|
1822
|
+
` ${import_chalk9.default.yellow("!")} Config updated \u2014 review ${import_chalk9.default.cyan(CONFIG_FILE5)} for changes`
|
|
1547
1823
|
);
|
|
1548
1824
|
}
|
|
1549
1825
|
fs13.writeFileSync(configPath, `${mergedJson}
|
|
1550
1826
|
`);
|
|
1551
1827
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
1552
1828
|
console.log(`
|
|
1553
|
-
${
|
|
1829
|
+
${import_chalk9.default.bold("Synced:")}`);
|
|
1554
1830
|
if (configChanged) {
|
|
1555
|
-
console.log(` ${
|
|
1831
|
+
console.log(` ${import_chalk9.default.yellow("!")} ${CONFIG_FILE5} \u2014 updated (review changes)`);
|
|
1556
1832
|
} else {
|
|
1557
|
-
console.log(` ${
|
|
1833
|
+
console.log(` ${import_chalk9.default.green("\u2713")} ${CONFIG_FILE5} \u2014 unchanged`);
|
|
1558
1834
|
}
|
|
1559
|
-
console.log(` ${
|
|
1560
|
-
console.log(` ${
|
|
1835
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
1836
|
+
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
1561
1837
|
}
|
|
1562
1838
|
|
|
1563
1839
|
// src/index.ts
|
|
1564
|
-
var VERSION = "0.3.
|
|
1840
|
+
var VERSION = "0.3.2";
|
|
1565
1841
|
var program = new import_commander.Command();
|
|
1566
1842
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
1567
1843
|
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) => {
|
|
@@ -1569,7 +1845,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
1569
1845
|
await initCommand(options);
|
|
1570
1846
|
} catch (err) {
|
|
1571
1847
|
const message = err instanceof Error ? err.message : String(err);
|
|
1572
|
-
console.error(`${
|
|
1848
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1573
1849
|
process.exit(1);
|
|
1574
1850
|
}
|
|
1575
1851
|
});
|
|
@@ -1578,7 +1854,7 @@ program.command("sync").description("Re-scan and update generated files").action
|
|
|
1578
1854
|
await syncCommand();
|
|
1579
1855
|
} catch (err) {
|
|
1580
1856
|
const message = err instanceof Error ? err.message : String(err);
|
|
1581
|
-
console.error(`${
|
|
1857
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1582
1858
|
process.exit(1);
|
|
1583
1859
|
}
|
|
1584
1860
|
});
|
|
@@ -1593,7 +1869,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
1593
1869
|
process.exit(exitCode);
|
|
1594
1870
|
} catch (err) {
|
|
1595
1871
|
const message = err instanceof Error ? err.message : String(err);
|
|
1596
|
-
console.error(`${
|
|
1872
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1597
1873
|
process.exit(1);
|
|
1598
1874
|
}
|
|
1599
1875
|
}
|
|
@@ -1604,7 +1880,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
1604
1880
|
process.exit(exitCode);
|
|
1605
1881
|
} catch (err) {
|
|
1606
1882
|
const message = err instanceof Error ? err.message : String(err);
|
|
1607
|
-
console.error(`${
|
|
1883
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1608
1884
|
process.exit(1);
|
|
1609
1885
|
}
|
|
1610
1886
|
});
|
|
@@ -1613,7 +1889,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
1613
1889
|
await boundariesCommand(options);
|
|
1614
1890
|
} catch (err) {
|
|
1615
1891
|
const message = err instanceof Error ? err.message : String(err);
|
|
1616
|
-
console.error(`${
|
|
1892
|
+
console.error(`${import_chalk10.default.red("Error:")} ${message}`);
|
|
1617
1893
|
process.exit(1);
|
|
1618
1894
|
}
|
|
1619
1895
|
});
|