viberails 0.6.6 → 0.6.8
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 +433 -388
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +433 -388
- package/dist/index.js.map +1 -1
- package/package.json +6 -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_chalk17 = __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_chalk2 = __toESM(require("chalk"), 1);
|
|
45
45
|
|
|
46
46
|
// src/utils/find-project-root.ts
|
|
47
47
|
var fs = __toESM(require("fs"), 1);
|
|
@@ -84,9 +84,13 @@ var SENTINEL_CUSTOM = "__custom__";
|
|
|
84
84
|
var SENTINEL_NONE = "__none__";
|
|
85
85
|
var SENTINEL_INHERIT = "__inherit__";
|
|
86
86
|
var SENTINEL_SKIP = "__skip__";
|
|
87
|
+
var HINT_NOT_SET = "not set";
|
|
88
|
+
var HINT_NO_OVERRIDES = "no overrides";
|
|
89
|
+
var HINT_AUTO_DETECT = "auto-detect";
|
|
87
90
|
|
|
88
91
|
// src/utils/prompt-submenus.ts
|
|
89
92
|
var clack = __toESM(require("@clack/prompts"), 1);
|
|
93
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
90
94
|
var FILE_NAMING_OPTIONS = [
|
|
91
95
|
{ value: "kebab-case", label: "kebab-case" },
|
|
92
96
|
{ value: "camelCase", label: "camelCase" },
|
|
@@ -115,8 +119,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
115
119
|
{ value: "back", label: "Back" }
|
|
116
120
|
]
|
|
117
121
|
});
|
|
118
|
-
|
|
119
|
-
if (choice === "back") return;
|
|
122
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
120
123
|
if (choice === "maxFileLines") {
|
|
121
124
|
const result = await clack.text({
|
|
122
125
|
message: "Maximum lines per source file?",
|
|
@@ -127,7 +130,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
127
130
|
if (Number.isNaN(n) || n < 1) return "Enter a positive number";
|
|
128
131
|
}
|
|
129
132
|
});
|
|
130
|
-
|
|
133
|
+
if (isCancelled(result)) continue;
|
|
131
134
|
state.maxFileLines = Number.parseInt(result, 10);
|
|
132
135
|
}
|
|
133
136
|
if (choice === "maxTestFileLines") {
|
|
@@ -140,7 +143,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
140
143
|
if (Number.isNaN(n) || n < 0) return "Enter a number (0 or positive)";
|
|
141
144
|
}
|
|
142
145
|
});
|
|
143
|
-
|
|
146
|
+
if (isCancelled(result)) continue;
|
|
144
147
|
state.maxTestFileLines = Number.parseInt(result, 10);
|
|
145
148
|
}
|
|
146
149
|
}
|
|
@@ -151,49 +154,48 @@ async function promptNamingMenu(state) {
|
|
|
151
154
|
{
|
|
152
155
|
value: "enforceNaming",
|
|
153
156
|
label: "Enforce file naming",
|
|
154
|
-
hint: state.enforceNaming ? "yes" : "no"
|
|
157
|
+
hint: state.enforceNaming ? import_chalk.default.green("yes") : import_chalk.default.dim("no")
|
|
155
158
|
}
|
|
156
159
|
];
|
|
157
160
|
if (state.enforceNaming) {
|
|
158
161
|
options.push({
|
|
159
162
|
value: "fileNaming",
|
|
160
163
|
label: "File naming convention",
|
|
161
|
-
hint: state.fileNamingValue ??
|
|
164
|
+
hint: state.fileNamingValue ?? HINT_NOT_SET
|
|
162
165
|
});
|
|
163
166
|
}
|
|
164
167
|
options.push(
|
|
165
168
|
{
|
|
166
169
|
value: "componentNaming",
|
|
167
170
|
label: "Component naming",
|
|
168
|
-
hint: state.componentNaming ??
|
|
171
|
+
hint: state.componentNaming ?? HINT_NOT_SET
|
|
169
172
|
},
|
|
170
173
|
{
|
|
171
174
|
value: "hookNaming",
|
|
172
175
|
label: "Hook naming",
|
|
173
|
-
hint: state.hookNaming ??
|
|
176
|
+
hint: state.hookNaming ?? HINT_NOT_SET
|
|
174
177
|
},
|
|
175
178
|
{
|
|
176
179
|
value: "importAlias",
|
|
177
180
|
label: "Import alias",
|
|
178
|
-
hint: state.importAlias ??
|
|
181
|
+
hint: state.importAlias ?? HINT_NOT_SET
|
|
179
182
|
},
|
|
180
183
|
{ value: "back", label: "Back" }
|
|
181
184
|
);
|
|
182
185
|
const choice = await clack.select({ message: "Naming & conventions", options });
|
|
183
|
-
|
|
184
|
-
if (choice === "back") return;
|
|
186
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
185
187
|
if (choice === "enforceNaming") {
|
|
186
188
|
const result = await clack.confirm({
|
|
187
189
|
message: state.fileNamingValue ? `Enforce file naming? (detected: ${state.fileNamingValue})` : "Enforce file naming?",
|
|
188
190
|
initialValue: state.enforceNaming
|
|
189
191
|
});
|
|
190
|
-
|
|
192
|
+
if (isCancelled(result)) continue;
|
|
191
193
|
if (result && !state.fileNamingValue) {
|
|
192
194
|
const selected = await clack.select({
|
|
193
195
|
message: "Which file naming convention should be enforced?",
|
|
194
196
|
options: [...FILE_NAMING_OPTIONS]
|
|
195
197
|
});
|
|
196
|
-
|
|
198
|
+
if (isCancelled(selected)) continue;
|
|
197
199
|
state.fileNamingValue = selected;
|
|
198
200
|
}
|
|
199
201
|
state.enforceNaming = result;
|
|
@@ -204,7 +206,7 @@ async function promptNamingMenu(state) {
|
|
|
204
206
|
options: [...FILE_NAMING_OPTIONS],
|
|
205
207
|
initialValue: state.fileNamingValue
|
|
206
208
|
});
|
|
207
|
-
|
|
209
|
+
if (isCancelled(selected)) continue;
|
|
208
210
|
state.fileNamingValue = selected;
|
|
209
211
|
}
|
|
210
212
|
if (choice === "componentNaming") {
|
|
@@ -216,7 +218,7 @@ async function promptNamingMenu(state) {
|
|
|
216
218
|
],
|
|
217
219
|
initialValue: state.componentNaming ?? SENTINEL_CLEAR
|
|
218
220
|
});
|
|
219
|
-
|
|
221
|
+
if (isCancelled(selected)) continue;
|
|
220
222
|
state.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
221
223
|
}
|
|
222
224
|
if (choice === "hookNaming") {
|
|
@@ -228,7 +230,7 @@ async function promptNamingMenu(state) {
|
|
|
228
230
|
],
|
|
229
231
|
initialValue: state.hookNaming ?? SENTINEL_CLEAR
|
|
230
232
|
});
|
|
231
|
-
|
|
233
|
+
if (isCancelled(selected)) continue;
|
|
232
234
|
state.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
233
235
|
}
|
|
234
236
|
if (choice === "importAlias") {
|
|
@@ -242,7 +244,7 @@ async function promptNamingMenu(state) {
|
|
|
242
244
|
],
|
|
243
245
|
initialValue: state.importAlias ?? SENTINEL_CLEAR
|
|
244
246
|
});
|
|
245
|
-
|
|
247
|
+
if (isCancelled(selected)) continue;
|
|
246
248
|
if (selected === SENTINEL_CLEAR) {
|
|
247
249
|
state.importAlias = void 0;
|
|
248
250
|
} else if (selected === SENTINEL_CUSTOM) {
|
|
@@ -256,7 +258,7 @@ async function promptNamingMenu(state) {
|
|
|
256
258
|
return "Must match pattern like @/*, ~/*, or #src/*";
|
|
257
259
|
}
|
|
258
260
|
});
|
|
259
|
-
|
|
261
|
+
if (isCancelled(result)) continue;
|
|
260
262
|
state.importAlias = result.trim();
|
|
261
263
|
} else {
|
|
262
264
|
state.importAlias = selected;
|
|
@@ -270,7 +272,7 @@ async function promptTestingMenu(state) {
|
|
|
270
272
|
{
|
|
271
273
|
value: "enforceMissingTests",
|
|
272
274
|
label: "Enforce missing tests",
|
|
273
|
-
hint: state.enforceMissingTests ? "yes" : "no"
|
|
275
|
+
hint: state.enforceMissingTests ? import_chalk.default.green("yes") : import_chalk.default.dim("no")
|
|
274
276
|
},
|
|
275
277
|
{
|
|
276
278
|
value: "testCoverage",
|
|
@@ -294,14 +296,13 @@ async function promptTestingMenu(state) {
|
|
|
294
296
|
}
|
|
295
297
|
options.push({ value: "back", label: "Back" });
|
|
296
298
|
const choice = await clack.select({ message: "Testing & coverage", options });
|
|
297
|
-
|
|
298
|
-
if (choice === "back") return;
|
|
299
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
299
300
|
if (choice === "enforceMissingTests") {
|
|
300
301
|
const result = await clack.confirm({
|
|
301
302
|
message: "Require every source file to have a corresponding test file?",
|
|
302
303
|
initialValue: state.enforceMissingTests
|
|
303
304
|
});
|
|
304
|
-
|
|
305
|
+
if (isCancelled(result)) continue;
|
|
305
306
|
state.enforceMissingTests = result;
|
|
306
307
|
}
|
|
307
308
|
if (choice === "testCoverage") {
|
|
@@ -314,7 +315,7 @@ async function promptTestingMenu(state) {
|
|
|
314
315
|
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
315
316
|
}
|
|
316
317
|
});
|
|
317
|
-
|
|
318
|
+
if (isCancelled(result)) continue;
|
|
318
319
|
state.testCoverage = Number.parseInt(result, 10);
|
|
319
320
|
}
|
|
320
321
|
if (choice === "coverageSummaryPath") {
|
|
@@ -325,7 +326,7 @@ async function promptTestingMenu(state) {
|
|
|
325
326
|
if (typeof v !== "string" || v.trim().length === 0) return "Path cannot be empty";
|
|
326
327
|
}
|
|
327
328
|
});
|
|
328
|
-
|
|
329
|
+
if (isCancelled(result)) continue;
|
|
329
330
|
state.coverageSummaryPath = result.trim();
|
|
330
331
|
}
|
|
331
332
|
if (choice === "coverageCommand") {
|
|
@@ -334,7 +335,7 @@ async function promptTestingMenu(state) {
|
|
|
334
335
|
initialValue: state.coverageCommand ?? "",
|
|
335
336
|
placeholder: "(auto-detect from package.json test runner)"
|
|
336
337
|
});
|
|
337
|
-
|
|
338
|
+
if (isCancelled(result)) continue;
|
|
338
339
|
const trimmed = result.trim();
|
|
339
340
|
state.coverageCommand = trimmed.length > 0 ? trimmed : void 0;
|
|
340
341
|
}
|
|
@@ -378,7 +379,7 @@ function packageOverrideHint(pkg, defaults) {
|
|
|
378
379
|
const hasCommandOverride = pkg.coverage?.command !== void 0 && pkg.coverage.command !== defaultCommand;
|
|
379
380
|
if (hasSummaryOverride) tags.push("summary override");
|
|
380
381
|
if (hasCommandOverride) tags.push("command override");
|
|
381
|
-
return tags.length > 0 ? tags.join(", ") :
|
|
382
|
+
return tags.length > 0 ? tags.join(", ") : HINT_NO_OVERRIDES;
|
|
382
383
|
}
|
|
383
384
|
async function promptPackageOverrides(packages, defaults) {
|
|
384
385
|
const editablePackages = packages.filter((pkg) => pkg.path !== ".");
|
|
@@ -395,8 +396,7 @@ async function promptPackageOverrides(packages, defaults) {
|
|
|
395
396
|
{ value: SENTINEL_DONE, label: "Done" }
|
|
396
397
|
]
|
|
397
398
|
});
|
|
398
|
-
|
|
399
|
-
if (selectedPath === SENTINEL_DONE) break;
|
|
399
|
+
if (isCancelled(selectedPath) || selectedPath === SENTINEL_DONE) break;
|
|
400
400
|
const target = editablePackages.find((pkg) => pkg.path === selectedPath);
|
|
401
401
|
if (!target) continue;
|
|
402
402
|
await promptSinglePackageOverrides(target, defaults);
|
|
@@ -410,11 +410,11 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
410
410
|
const effectiveMaxLines = target.rules?.maxFileLines ?? defaults.maxFileLines;
|
|
411
411
|
const effectiveCoverage = target.rules?.testCoverage ?? defaults.testCoverage;
|
|
412
412
|
const effectiveSummary = target.coverage?.summaryPath ?? defaults.coverageSummaryPath;
|
|
413
|
-
const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ??
|
|
413
|
+
const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ?? HINT_AUTO_DETECT;
|
|
414
414
|
const hasNamingOverride = target.conventions?.fileNaming !== void 0 && target.conventions.fileNaming !== defaults.fileNamingValue;
|
|
415
415
|
const hasMaxLinesOverride = target.rules?.maxFileLines !== void 0 && target.rules.maxFileLines !== defaults.maxFileLines;
|
|
416
|
-
const namingHint = hasNamingOverride ? String(effectiveNaming) : `
|
|
417
|
-
const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `
|
|
416
|
+
const namingHint = hasNamingOverride ? String(effectiveNaming) : `inherits: ${effectiveNaming ?? "not set"}`;
|
|
417
|
+
const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `inherits: ${effectiveMaxLines}`;
|
|
418
418
|
const choice = await clack2.select({
|
|
419
419
|
message: `Edit overrides for ${target.path}`,
|
|
420
420
|
options: [
|
|
@@ -427,8 +427,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
427
427
|
{ value: "back", label: "Back to package list" }
|
|
428
428
|
]
|
|
429
429
|
});
|
|
430
|
-
|
|
431
|
-
if (choice === "back") break;
|
|
430
|
+
if (isCancelled(choice) || choice === "back") break;
|
|
432
431
|
if (choice === "fileNaming") {
|
|
433
432
|
const selected = await clack2.select({
|
|
434
433
|
message: `File naming for ${target.path}`,
|
|
@@ -442,7 +441,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
442
441
|
],
|
|
443
442
|
initialValue: target.conventions?.fileNaming ?? SENTINEL_INHERIT
|
|
444
443
|
});
|
|
445
|
-
|
|
444
|
+
if (isCancelled(selected)) continue;
|
|
446
445
|
if (selected === SENTINEL_INHERIT) {
|
|
447
446
|
if (target.conventions) delete target.conventions.fileNaming;
|
|
448
447
|
} else if (selected === SENTINEL_NONE) {
|
|
@@ -457,7 +456,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
457
456
|
initialValue: target.rules?.maxFileLines !== void 0 ? String(target.rules.maxFileLines) : "",
|
|
458
457
|
placeholder: String(defaults.maxFileLines)
|
|
459
458
|
});
|
|
460
|
-
|
|
459
|
+
if (isCancelled(result)) continue;
|
|
461
460
|
const value = result.trim();
|
|
462
461
|
if (value.length === 0 || Number.parseInt(value, 10) === defaults.maxFileLines) {
|
|
463
462
|
if (target.rules) delete target.rules.maxFileLines;
|
|
@@ -475,7 +474,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
475
474
|
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
476
475
|
}
|
|
477
476
|
});
|
|
478
|
-
|
|
477
|
+
if (isCancelled(result)) continue;
|
|
479
478
|
const nextCoverage = Number.parseInt(result, 10);
|
|
480
479
|
if (nextCoverage === defaults.testCoverage) {
|
|
481
480
|
if (target.rules) delete target.rules.testCoverage;
|
|
@@ -489,7 +488,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
489
488
|
initialValue: target.coverage?.summaryPath !== void 0 ? target.coverage.summaryPath : "",
|
|
490
489
|
placeholder: defaults.coverageSummaryPath
|
|
491
490
|
});
|
|
492
|
-
|
|
491
|
+
if (isCancelled(result)) continue;
|
|
493
492
|
const value = result.trim();
|
|
494
493
|
if (value.length === 0 || value === defaults.coverageSummaryPath) {
|
|
495
494
|
if (target.coverage) delete target.coverage.summaryPath;
|
|
@@ -503,7 +502,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
503
502
|
initialValue: target.coverage?.command !== void 0 ? target.coverage.command : "",
|
|
504
503
|
placeholder: defaults.coverageCommand ?? "(auto-detect from package.json test runner)"
|
|
505
504
|
});
|
|
506
|
-
|
|
505
|
+
if (isCancelled(result)) continue;
|
|
507
506
|
const value = result.trim();
|
|
508
507
|
const defaultCommand = defaults.coverageCommand ?? "";
|
|
509
508
|
if (value.length === 0 || value === defaultCommand) {
|
|
@@ -674,6 +673,9 @@ function assertNotCancelled(value) {
|
|
|
674
673
|
process.exit(0);
|
|
675
674
|
}
|
|
676
675
|
}
|
|
676
|
+
function isCancelled(value) {
|
|
677
|
+
return clack5.isCancel(value);
|
|
678
|
+
}
|
|
677
679
|
async function confirm3(message) {
|
|
678
680
|
const result = await clack5.confirm({ message, initialValue: true });
|
|
679
681
|
assertNotCancelled(result);
|
|
@@ -766,48 +768,48 @@ async function boundariesCommand(options, cwd) {
|
|
|
766
768
|
}
|
|
767
769
|
function displayRules(config) {
|
|
768
770
|
if (!config.boundaries || Object.keys(config.boundaries.deny).length === 0) {
|
|
769
|
-
console.log(
|
|
770
|
-
console.log(`Run ${
|
|
771
|
+
console.log(import_chalk2.default.yellow("No boundary rules configured."));
|
|
772
|
+
console.log(`Run ${import_chalk2.default.cyan("viberails boundaries --infer")} to generate rules.`);
|
|
771
773
|
return;
|
|
772
774
|
}
|
|
773
775
|
const { deny } = config.boundaries;
|
|
774
776
|
const sources = Object.keys(deny).filter((k) => deny[k].length > 0);
|
|
775
777
|
const totalRules = sources.reduce((sum, k) => sum + deny[k].length, 0);
|
|
776
778
|
console.log(`
|
|
777
|
-
${
|
|
779
|
+
${import_chalk2.default.bold(`Boundary rules (${totalRules} deny rules):`)}
|
|
778
780
|
`);
|
|
779
781
|
for (const source of sources) {
|
|
780
782
|
for (const target of deny[source]) {
|
|
781
|
-
console.log(` ${
|
|
783
|
+
console.log(` ${import_chalk2.default.red("\u2717")} ${source} \u2192 ${target}`);
|
|
782
784
|
}
|
|
783
785
|
}
|
|
784
786
|
console.log(
|
|
785
787
|
`
|
|
786
|
-
Enforcement: ${config.rules.enforceBoundaries ?
|
|
788
|
+
Enforcement: ${config.rules.enforceBoundaries ? import_chalk2.default.green("on") : import_chalk2.default.yellow("off")}`
|
|
787
789
|
);
|
|
788
790
|
}
|
|
789
791
|
async function inferAndDisplay(projectRoot, config, configPath) {
|
|
790
|
-
console.log(
|
|
792
|
+
console.log(import_chalk2.default.dim("Analyzing imports..."));
|
|
791
793
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
792
794
|
const packages = config.packages.length > 1 ? resolveWorkspacePackages(projectRoot, config.packages) : void 0;
|
|
793
795
|
const graph = await buildImportGraph(projectRoot, {
|
|
794
796
|
packages,
|
|
795
797
|
ignore: config.ignore
|
|
796
798
|
});
|
|
797
|
-
console.log(
|
|
799
|
+
console.log(import_chalk2.default.dim(`${graph.nodes.length} files, ${graph.edges.length} edges`));
|
|
798
800
|
const inferred = inferBoundaries(graph);
|
|
799
801
|
const sources = Object.keys(inferred.deny).filter((k) => inferred.deny[k].length > 0);
|
|
800
802
|
const totalRules = sources.reduce((sum, k) => sum + inferred.deny[k].length, 0);
|
|
801
803
|
if (totalRules === 0) {
|
|
802
|
-
console.log(
|
|
804
|
+
console.log(import_chalk2.default.yellow("No boundary rules could be inferred."));
|
|
803
805
|
return;
|
|
804
806
|
}
|
|
805
807
|
console.log(`
|
|
806
|
-
${
|
|
808
|
+
${import_chalk2.default.bold("Inferred boundary rules:")}
|
|
807
809
|
`);
|
|
808
810
|
for (const source of sources) {
|
|
809
811
|
for (const target of inferred.deny[source]) {
|
|
810
|
-
console.log(` ${
|
|
812
|
+
console.log(` ${import_chalk2.default.red("\u2717")} ${source} \u2192 ${target}`);
|
|
811
813
|
}
|
|
812
814
|
}
|
|
813
815
|
console.log(`
|
|
@@ -819,11 +821,11 @@ ${import_chalk.default.bold("Inferred boundary rules:")}
|
|
|
819
821
|
config.rules.enforceBoundaries = true;
|
|
820
822
|
fs3.writeFileSync(configPath, `${JSON.stringify((0, import_config.compactConfig)(config), null, 2)}
|
|
821
823
|
`);
|
|
822
|
-
console.log(`${
|
|
824
|
+
console.log(`${import_chalk2.default.green("\u2713")} Saved ${totalRules} rules`);
|
|
823
825
|
}
|
|
824
826
|
}
|
|
825
827
|
async function showGraph(projectRoot, config) {
|
|
826
|
-
console.log(
|
|
828
|
+
console.log(import_chalk2.default.dim("Building import graph..."));
|
|
827
829
|
const { buildImportGraph } = await import("@viberails/graph");
|
|
828
830
|
const packages = config.packages.length > 1 ? resolveWorkspacePackages(projectRoot, config.packages) : void 0;
|
|
829
831
|
const graph = await buildImportGraph(projectRoot, {
|
|
@@ -831,20 +833,20 @@ async function showGraph(projectRoot, config) {
|
|
|
831
833
|
ignore: config.ignore
|
|
832
834
|
});
|
|
833
835
|
console.log(`
|
|
834
|
-
${
|
|
836
|
+
${import_chalk2.default.bold("Import dependency graph:")}
|
|
835
837
|
`);
|
|
836
838
|
console.log(` ${graph.nodes.length} files, ${graph.edges.length} imports
|
|
837
839
|
`);
|
|
838
840
|
if (graph.packages.length > 0) {
|
|
839
841
|
for (const pkg of graph.packages) {
|
|
840
842
|
const deps = pkg.internalDeps.length > 0 ? `
|
|
841
|
-
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` :
|
|
843
|
+
${pkg.internalDeps.map((d) => ` \u2192 ${d}`).join("\n")}` : import_chalk2.default.dim(" (no internal deps)");
|
|
842
844
|
console.log(` ${pkg.name}${deps}`);
|
|
843
845
|
}
|
|
844
846
|
}
|
|
845
847
|
if (graph.cycles.length > 0) {
|
|
846
848
|
console.log(`
|
|
847
|
-
${
|
|
849
|
+
${import_chalk2.default.yellow("Cycles detected:")}`);
|
|
848
850
|
for (const cycle of graph.cycles) {
|
|
849
851
|
const paths = cycle.map((f) => path3.relative(projectRoot, f));
|
|
850
852
|
console.log(` ${paths.join(" \u2192 ")}`);
|
|
@@ -856,7 +858,7 @@ ${import_chalk.default.yellow("Cycles detected:")}`);
|
|
|
856
858
|
var fs7 = __toESM(require("fs"), 1);
|
|
857
859
|
var path7 = __toESM(require("path"), 1);
|
|
858
860
|
var import_config5 = require("@viberails/config");
|
|
859
|
-
var
|
|
861
|
+
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
860
862
|
|
|
861
863
|
// src/commands/check-config.ts
|
|
862
864
|
var import_config2 = require("@viberails/config");
|
|
@@ -1248,7 +1250,7 @@ function collectSourceFiles(dir, projectRoot) {
|
|
|
1248
1250
|
}
|
|
1249
1251
|
|
|
1250
1252
|
// src/commands/check-print.ts
|
|
1251
|
-
var
|
|
1253
|
+
var import_chalk3 = __toESM(require("chalk"), 1);
|
|
1252
1254
|
function printGroupedViolations(violations, limit) {
|
|
1253
1255
|
const groups = /* @__PURE__ */ new Map();
|
|
1254
1256
|
for (const v of violations) {
|
|
@@ -1276,12 +1278,12 @@ function printGroupedViolations(violations, limit) {
|
|
|
1276
1278
|
const toShow = group.slice(0, remaining);
|
|
1277
1279
|
const hidden = group.length - toShow.length;
|
|
1278
1280
|
for (const v of toShow) {
|
|
1279
|
-
const icon = v.severity === "error" ?
|
|
1280
|
-
console.log(`${icon} ${
|
|
1281
|
+
const icon = v.severity === "error" ? import_chalk3.default.red("\u2717") : import_chalk3.default.yellow("!");
|
|
1282
|
+
console.log(`${icon} ${import_chalk3.default.dim(v.rule)} ${v.file}: ${v.message}`);
|
|
1281
1283
|
}
|
|
1282
1284
|
totalShown += toShow.length;
|
|
1283
1285
|
if (hidden > 0) {
|
|
1284
|
-
console.log(
|
|
1286
|
+
console.log(import_chalk3.default.dim(` ... and ${hidden} more ${rule} violations`));
|
|
1285
1287
|
}
|
|
1286
1288
|
}
|
|
1287
1289
|
}
|
|
@@ -1371,13 +1373,13 @@ async function checkCommand(options, cwd) {
|
|
|
1371
1373
|
const startDir = cwd ?? process.cwd();
|
|
1372
1374
|
const projectRoot = findProjectRoot(startDir);
|
|
1373
1375
|
if (!projectRoot) {
|
|
1374
|
-
console.error(`${
|
|
1376
|
+
console.error(`${import_chalk4.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
1375
1377
|
return 1;
|
|
1376
1378
|
}
|
|
1377
1379
|
const configPath = path7.join(projectRoot, CONFIG_FILE2);
|
|
1378
1380
|
if (!fs7.existsSync(configPath)) {
|
|
1379
1381
|
console.error(
|
|
1380
|
-
`${
|
|
1382
|
+
`${import_chalk4.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
1381
1383
|
);
|
|
1382
1384
|
return 1;
|
|
1383
1385
|
}
|
|
@@ -1391,7 +1393,7 @@ async function checkCommand(options, cwd) {
|
|
|
1391
1393
|
} else if (options.diffBase) {
|
|
1392
1394
|
const diff = getDiffFiles(projectRoot, options.diffBase);
|
|
1393
1395
|
if (diff.error && options.enforce) {
|
|
1394
|
-
console.error(`${
|
|
1396
|
+
console.error(`${import_chalk4.default.red("Error:")} ${diff.error}`);
|
|
1395
1397
|
return 1;
|
|
1396
1398
|
}
|
|
1397
1399
|
filesToCheck = diff.all.filter((f) => SOURCE_EXTS.has(path7.extname(f)));
|
|
@@ -1406,15 +1408,15 @@ async function checkCommand(options, cwd) {
|
|
|
1406
1408
|
if (options.format === "json") {
|
|
1407
1409
|
console.log(JSON.stringify({ violations: [], checkedFiles: 0 }));
|
|
1408
1410
|
} else {
|
|
1409
|
-
console.log(`${
|
|
1411
|
+
console.log(`${import_chalk4.default.green("\u2713")} No files to check.`);
|
|
1410
1412
|
}
|
|
1411
1413
|
return 0;
|
|
1412
1414
|
}
|
|
1413
1415
|
const violations = [];
|
|
1414
1416
|
const severity = options.enforce ? "error" : "warn";
|
|
1415
|
-
const
|
|
1417
|
+
const log9 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk4.default.dim(msg)) : () => {
|
|
1416
1418
|
};
|
|
1417
|
-
|
|
1419
|
+
log9(" Checking files...");
|
|
1418
1420
|
for (const file of filesToCheck) {
|
|
1419
1421
|
const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
|
|
1420
1422
|
const relPath = path7.relative(projectRoot, absPath);
|
|
@@ -1447,9 +1449,9 @@ async function checkCommand(options, cwd) {
|
|
|
1447
1449
|
}
|
|
1448
1450
|
}
|
|
1449
1451
|
}
|
|
1450
|
-
|
|
1452
|
+
log9(" done\n");
|
|
1451
1453
|
if (!options.files) {
|
|
1452
|
-
|
|
1454
|
+
log9(" Checking missing tests...");
|
|
1453
1455
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
1454
1456
|
if (options.staged) {
|
|
1455
1457
|
const stagedSet = new Set(filesToCheck);
|
|
@@ -1462,14 +1464,14 @@ async function checkCommand(options, cwd) {
|
|
|
1462
1464
|
} else {
|
|
1463
1465
|
violations.push(...testViolations);
|
|
1464
1466
|
}
|
|
1465
|
-
|
|
1467
|
+
log9(" done\n");
|
|
1466
1468
|
}
|
|
1467
1469
|
if (!options.files && !options.staged && !options.diffBase) {
|
|
1468
|
-
|
|
1470
|
+
log9(" Running test coverage...\n");
|
|
1469
1471
|
const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
|
|
1470
1472
|
staged: options.staged,
|
|
1471
1473
|
enforce: options.enforce,
|
|
1472
|
-
onProgress: (pkg) =>
|
|
1474
|
+
onProgress: (pkg) => log9(` Coverage: ${pkg}...
|
|
1473
1475
|
`)
|
|
1474
1476
|
});
|
|
1475
1477
|
violations.push(...coverageViolations);
|
|
@@ -1494,7 +1496,7 @@ async function checkCommand(options, cwd) {
|
|
|
1494
1496
|
severity
|
|
1495
1497
|
});
|
|
1496
1498
|
}
|
|
1497
|
-
|
|
1499
|
+
log9(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
|
|
1498
1500
|
`);
|
|
1499
1501
|
}
|
|
1500
1502
|
if (options.format === "json") {
|
|
@@ -1507,7 +1509,7 @@ async function checkCommand(options, cwd) {
|
|
|
1507
1509
|
return options.enforce && violations.length > 0 ? 1 : 0;
|
|
1508
1510
|
}
|
|
1509
1511
|
if (violations.length === 0) {
|
|
1510
|
-
console.log(`${
|
|
1512
|
+
console.log(`${import_chalk4.default.green("\u2713")} ${filesToCheck.length} files checked \u2014 no violations`);
|
|
1511
1513
|
return 0;
|
|
1512
1514
|
}
|
|
1513
1515
|
if (!options.quiet) {
|
|
@@ -1515,7 +1517,7 @@ async function checkCommand(options, cwd) {
|
|
|
1515
1517
|
}
|
|
1516
1518
|
printSummary(violations);
|
|
1517
1519
|
if (options.enforce) {
|
|
1518
|
-
console.log(
|
|
1520
|
+
console.log(import_chalk4.default.red("Fix violations before committing."));
|
|
1519
1521
|
return 1;
|
|
1520
1522
|
}
|
|
1521
1523
|
return 0;
|
|
@@ -1573,14 +1575,14 @@ var path9 = __toESM(require("path"), 1);
|
|
|
1573
1575
|
var clack6 = __toESM(require("@clack/prompts"), 1);
|
|
1574
1576
|
var import_config6 = require("@viberails/config");
|
|
1575
1577
|
var import_scanner = require("@viberails/scanner");
|
|
1576
|
-
var
|
|
1578
|
+
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
1577
1579
|
|
|
1578
1580
|
// src/display-text.ts
|
|
1579
1581
|
var import_types4 = require("@viberails/types");
|
|
1580
1582
|
|
|
1581
1583
|
// src/display.ts
|
|
1582
1584
|
var import_types3 = require("@viberails/types");
|
|
1583
|
-
var
|
|
1585
|
+
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
1584
1586
|
|
|
1585
1587
|
// src/display-helpers.ts
|
|
1586
1588
|
var import_types = require("@viberails/types");
|
|
@@ -1633,7 +1635,7 @@ function formatRoleGroup(group) {
|
|
|
1633
1635
|
|
|
1634
1636
|
// src/display-monorepo.ts
|
|
1635
1637
|
var import_types2 = require("@viberails/types");
|
|
1636
|
-
var
|
|
1638
|
+
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1637
1639
|
function formatPackageSummary(pkg) {
|
|
1638
1640
|
const parts = [];
|
|
1639
1641
|
if (pkg.stack.framework) {
|
|
@@ -1650,23 +1652,23 @@ function formatPackageSummary(pkg) {
|
|
|
1650
1652
|
function displayMonorepoResults(scanResult) {
|
|
1651
1653
|
const { stack, packages } = scanResult;
|
|
1652
1654
|
console.log(`
|
|
1653
|
-
${
|
|
1654
|
-
console.log(` ${
|
|
1655
|
+
${import_chalk5.default.bold(`Detected: (monorepo, ${packages.length} packages)`)}`);
|
|
1656
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1655
1657
|
if (stack.packageManager) {
|
|
1656
|
-
console.log(` ${
|
|
1658
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1657
1659
|
}
|
|
1658
1660
|
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1659
|
-
console.log(` ${
|
|
1661
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
|
|
1660
1662
|
} else {
|
|
1661
1663
|
if (stack.linter) {
|
|
1662
|
-
console.log(` ${
|
|
1664
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1663
1665
|
}
|
|
1664
1666
|
if (stack.formatter) {
|
|
1665
|
-
console.log(` ${
|
|
1667
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1666
1668
|
}
|
|
1667
1669
|
}
|
|
1668
1670
|
if (stack.testRunner) {
|
|
1669
|
-
console.log(` ${
|
|
1671
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1670
1672
|
}
|
|
1671
1673
|
console.log("");
|
|
1672
1674
|
for (const pkg of packages) {
|
|
@@ -1677,13 +1679,13 @@ ${import_chalk4.default.bold(`Detected: (monorepo, ${packages.length} packages)`
|
|
|
1677
1679
|
);
|
|
1678
1680
|
if (packagesWithDirs.length > 0) {
|
|
1679
1681
|
console.log(`
|
|
1680
|
-
${
|
|
1682
|
+
${import_chalk5.default.bold("Structure:")}`);
|
|
1681
1683
|
for (const pkg of packagesWithDirs) {
|
|
1682
1684
|
const groups = groupByRole(pkg.structure.directories);
|
|
1683
1685
|
if (groups.length === 0) continue;
|
|
1684
1686
|
console.log(` ${pkg.relativePath}:`);
|
|
1685
1687
|
for (const group of groups) {
|
|
1686
|
-
console.log(` ${
|
|
1688
|
+
console.log(` ${import_chalk5.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1687
1689
|
}
|
|
1688
1690
|
}
|
|
1689
1691
|
}
|
|
@@ -1764,7 +1766,7 @@ function displayConventions(scanResult) {
|
|
|
1764
1766
|
const conventionEntries = Object.entries(scanResult.conventions);
|
|
1765
1767
|
if (conventionEntries.length === 0) return;
|
|
1766
1768
|
console.log(`
|
|
1767
|
-
${
|
|
1769
|
+
${import_chalk6.default.bold("Conventions:")}`);
|
|
1768
1770
|
for (const [key, convention] of conventionEntries) {
|
|
1769
1771
|
if (convention.confidence === "low") continue;
|
|
1770
1772
|
const label = import_types3.CONVENTION_LABELS[key] ?? key;
|
|
@@ -1772,19 +1774,19 @@ ${import_chalk5.default.bold("Conventions:")}`);
|
|
|
1772
1774
|
const pkgValues = scanResult.packages.filter((pkg) => pkg.conventions[key] && pkg.conventions[key].confidence !== "low").map((pkg) => ({ relativePath: pkg.relativePath, convention: pkg.conventions[key] }));
|
|
1773
1775
|
const allSame = pkgValues.every((pv) => pv.convention.value === convention.value);
|
|
1774
1776
|
if (allSame || pkgValues.length <= 1) {
|
|
1775
|
-
const ind = convention.confidence === "high" ?
|
|
1776
|
-
const detail =
|
|
1777
|
+
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1778
|
+
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1777
1779
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1778
1780
|
} else {
|
|
1779
|
-
console.log(` ${
|
|
1781
|
+
console.log(` ${import_chalk6.default.yellow("~")} ${label}: varies by package`);
|
|
1780
1782
|
for (const pv of pkgValues) {
|
|
1781
1783
|
const pct = Math.round(pv.convention.consistency);
|
|
1782
1784
|
console.log(` ${pv.relativePath}: ${pv.convention.value} (${pct}%)`);
|
|
1783
1785
|
}
|
|
1784
1786
|
}
|
|
1785
1787
|
} else {
|
|
1786
|
-
const ind = convention.confidence === "high" ?
|
|
1787
|
-
const detail =
|
|
1788
|
+
const ind = convention.confidence === "high" ? import_chalk6.default.green("\u2713") : import_chalk6.default.yellow("~");
|
|
1789
|
+
const detail = import_chalk6.default.dim(`(${confidenceLabel(convention)})`);
|
|
1788
1790
|
console.log(` ${ind} ${label}: ${convention.value} ${detail}`);
|
|
1789
1791
|
}
|
|
1790
1792
|
}
|
|
@@ -1792,7 +1794,7 @@ ${import_chalk5.default.bold("Conventions:")}`);
|
|
|
1792
1794
|
function displaySummarySection(scanResult) {
|
|
1793
1795
|
const pkgCount = scanResult.packages.length > 1 ? scanResult.packages.length : void 0;
|
|
1794
1796
|
console.log(`
|
|
1795
|
-
${
|
|
1797
|
+
${import_chalk6.default.bold("Summary:")}`);
|
|
1796
1798
|
console.log(` ${formatSummary(scanResult.statistics, pkgCount)}`);
|
|
1797
1799
|
const ext = formatExtensions(scanResult.statistics.filesByExtension);
|
|
1798
1800
|
if (ext) {
|
|
@@ -1806,47 +1808,47 @@ function displayScanResults(scanResult) {
|
|
|
1806
1808
|
}
|
|
1807
1809
|
const { stack } = scanResult;
|
|
1808
1810
|
console.log(`
|
|
1809
|
-
${
|
|
1811
|
+
${import_chalk6.default.bold("Detected:")}`);
|
|
1810
1812
|
if (stack.framework) {
|
|
1811
|
-
console.log(` ${
|
|
1813
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.framework, import_types3.FRAMEWORK_NAMES)}`);
|
|
1812
1814
|
}
|
|
1813
|
-
console.log(` ${
|
|
1815
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.language)}`);
|
|
1814
1816
|
if (stack.styling) {
|
|
1815
|
-
console.log(` ${
|
|
1817
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.styling, import_types3.STYLING_NAMES)}`);
|
|
1816
1818
|
}
|
|
1817
1819
|
if (stack.backend) {
|
|
1818
|
-
console.log(` ${
|
|
1820
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.backend, import_types3.FRAMEWORK_NAMES)}`);
|
|
1819
1821
|
}
|
|
1820
1822
|
if (stack.orm) {
|
|
1821
|
-
console.log(` ${
|
|
1823
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.orm, import_types3.ORM_NAMES)}`);
|
|
1822
1824
|
}
|
|
1823
1825
|
if (stack.linter && stack.formatter && stack.linter.name === stack.formatter.name) {
|
|
1824
|
-
console.log(` ${
|
|
1826
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)} (lint + format)`);
|
|
1825
1827
|
} else {
|
|
1826
1828
|
if (stack.linter) {
|
|
1827
|
-
console.log(` ${
|
|
1829
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.linter)}`);
|
|
1828
1830
|
}
|
|
1829
1831
|
if (stack.formatter) {
|
|
1830
|
-
console.log(` ${
|
|
1832
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.formatter)}`);
|
|
1831
1833
|
}
|
|
1832
1834
|
}
|
|
1833
1835
|
if (stack.testRunner) {
|
|
1834
|
-
console.log(` ${
|
|
1836
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.testRunner)}`);
|
|
1835
1837
|
}
|
|
1836
1838
|
if (stack.packageManager) {
|
|
1837
|
-
console.log(` ${
|
|
1839
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(stack.packageManager)}`);
|
|
1838
1840
|
}
|
|
1839
1841
|
if (stack.libraries.length > 0) {
|
|
1840
1842
|
for (const lib of stack.libraries) {
|
|
1841
|
-
console.log(` ${
|
|
1843
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatItem(lib, import_types3.LIBRARY_NAMES)}`);
|
|
1842
1844
|
}
|
|
1843
1845
|
}
|
|
1844
1846
|
const groups = groupByRole(scanResult.structure.directories);
|
|
1845
1847
|
if (groups.length > 0) {
|
|
1846
1848
|
console.log(`
|
|
1847
|
-
${
|
|
1849
|
+
${import_chalk6.default.bold("Structure:")}`);
|
|
1848
1850
|
for (const group of groups) {
|
|
1849
|
-
console.log(` ${
|
|
1851
|
+
console.log(` ${import_chalk6.default.green("\u2713")} ${formatRoleGroup(group)}`);
|
|
1850
1852
|
}
|
|
1851
1853
|
}
|
|
1852
1854
|
displayConventions(scanResult);
|
|
@@ -1856,25 +1858,25 @@ ${import_chalk5.default.bold("Structure:")}`);
|
|
|
1856
1858
|
function displayRulesPreview(config) {
|
|
1857
1859
|
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1858
1860
|
console.log(
|
|
1859
|
-
`${
|
|
1861
|
+
`${import_chalk6.default.bold("Rules:")} ${import_chalk6.default.dim("(warns on violation; use --enforce in CI to block)")}`
|
|
1860
1862
|
);
|
|
1861
|
-
console.log(` ${
|
|
1863
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Max file size: ${config.rules.maxFileLines} lines`);
|
|
1862
1864
|
if (config.rules.testCoverage > 0 && root?.structure?.testPattern) {
|
|
1863
1865
|
console.log(
|
|
1864
|
-
` ${
|
|
1866
|
+
` ${import_chalk6.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}% (${root.structure.testPattern})`
|
|
1865
1867
|
);
|
|
1866
1868
|
} else if (config.rules.testCoverage > 0) {
|
|
1867
|
-
console.log(` ${
|
|
1869
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Test coverage target: ${config.rules.testCoverage}%`);
|
|
1868
1870
|
} else {
|
|
1869
|
-
console.log(` ${
|
|
1871
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Test coverage target: disabled`);
|
|
1870
1872
|
}
|
|
1871
1873
|
if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
|
|
1872
|
-
console.log(` ${
|
|
1874
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: ${root.conventions.fileNaming}`);
|
|
1873
1875
|
} else {
|
|
1874
|
-
console.log(` ${
|
|
1876
|
+
console.log(` ${import_chalk6.default.dim("\u2022")} Enforce file naming: no`);
|
|
1875
1877
|
}
|
|
1876
1878
|
console.log(
|
|
1877
|
-
` ${
|
|
1879
|
+
` ${import_chalk6.default.dim("\u2022")} Enforce boundaries: ${config.rules.enforceBoundaries ? "yes" : "no"}`
|
|
1878
1880
|
);
|
|
1879
1881
|
console.log("");
|
|
1880
1882
|
}
|
|
@@ -2199,7 +2201,7 @@ async function configCommand(options, cwd) {
|
|
|
2199
2201
|
}
|
|
2200
2202
|
const configPath = path9.join(projectRoot, CONFIG_FILE3);
|
|
2201
2203
|
if (!fs10.existsSync(configPath)) {
|
|
2202
|
-
console.log(`${
|
|
2204
|
+
console.log(`${import_chalk7.default.yellow("!")} No config found. Run ${import_chalk7.default.cyan("viberails")} first.`);
|
|
2203
2205
|
return;
|
|
2204
2206
|
}
|
|
2205
2207
|
if (!options.suppressIntro) {
|
|
@@ -2290,22 +2292,22 @@ async function rescanAndMerge(projectRoot, config) {
|
|
|
2290
2292
|
var fs13 = __toESM(require("fs"), 1);
|
|
2291
2293
|
var path13 = __toESM(require("path"), 1);
|
|
2292
2294
|
var import_config7 = require("@viberails/config");
|
|
2293
|
-
var
|
|
2295
|
+
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
2294
2296
|
|
|
2295
2297
|
// src/commands/fix-helpers.ts
|
|
2296
2298
|
var import_node_child_process3 = require("child_process");
|
|
2297
|
-
var
|
|
2299
|
+
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
2298
2300
|
function printPlan(renames, stubs) {
|
|
2299
2301
|
if (renames.length > 0) {
|
|
2300
|
-
console.log(
|
|
2302
|
+
console.log(import_chalk8.default.bold("\nFile renames:"));
|
|
2301
2303
|
for (const r of renames) {
|
|
2302
|
-
console.log(` ${
|
|
2304
|
+
console.log(` ${import_chalk8.default.red(r.oldPath)} \u2192 ${import_chalk8.default.green(r.newPath)}`);
|
|
2303
2305
|
}
|
|
2304
2306
|
}
|
|
2305
2307
|
if (stubs.length > 0) {
|
|
2306
|
-
console.log(
|
|
2308
|
+
console.log(import_chalk8.default.bold("\nTest stubs to create:"));
|
|
2307
2309
|
for (const s of stubs) {
|
|
2308
|
-
console.log(` ${
|
|
2310
|
+
console.log(` ${import_chalk8.default.green("+")} ${s.path}`);
|
|
2309
2311
|
}
|
|
2310
2312
|
}
|
|
2311
2313
|
}
|
|
@@ -2633,13 +2635,13 @@ async function fixCommand(options, cwd) {
|
|
|
2633
2635
|
const startDir = cwd ?? process.cwd();
|
|
2634
2636
|
const projectRoot = findProjectRoot(startDir);
|
|
2635
2637
|
if (!projectRoot) {
|
|
2636
|
-
console.error(`${
|
|
2638
|
+
console.error(`${import_chalk9.default.red("Error:")} No package.json found. Are you in a JS/TS project?`);
|
|
2637
2639
|
return 1;
|
|
2638
2640
|
}
|
|
2639
2641
|
const configPath = path13.join(projectRoot, CONFIG_FILE4);
|
|
2640
2642
|
if (!fs13.existsSync(configPath)) {
|
|
2641
2643
|
console.error(
|
|
2642
|
-
`${
|
|
2644
|
+
`${import_chalk9.default.red("Error:")} No viberails.config.json found. Run \`viberails init\` first.`
|
|
2643
2645
|
);
|
|
2644
2646
|
return 1;
|
|
2645
2647
|
}
|
|
@@ -2648,7 +2650,7 @@ async function fixCommand(options, cwd) {
|
|
|
2648
2650
|
const isDirty = checkGitDirty(projectRoot);
|
|
2649
2651
|
if (isDirty) {
|
|
2650
2652
|
console.log(
|
|
2651
|
-
|
|
2653
|
+
import_chalk9.default.yellow("Warning: You have uncommitted changes. Consider committing first.")
|
|
2652
2654
|
);
|
|
2653
2655
|
}
|
|
2654
2656
|
}
|
|
@@ -2695,41 +2697,41 @@ async function fixCommand(options, cwd) {
|
|
|
2695
2697
|
return blockedOldBareNames.has(bare);
|
|
2696
2698
|
});
|
|
2697
2699
|
if (safeRenames.length === 0 && testStubs.length === 0 && skippedRenames.length === 0) {
|
|
2698
|
-
console.log(`${
|
|
2700
|
+
console.log(`${import_chalk9.default.green("\u2713")} No fixable violations found.`);
|
|
2699
2701
|
return 0;
|
|
2700
2702
|
}
|
|
2701
2703
|
printPlan(safeRenames, testStubs);
|
|
2702
2704
|
if (skippedRenames.length > 0) {
|
|
2703
2705
|
console.log("");
|
|
2704
2706
|
console.log(
|
|
2705
|
-
|
|
2707
|
+
import_chalk9.default.yellow(
|
|
2706
2708
|
`Skipping ${skippedRenames.length} rename${skippedRenames.length > 1 ? "s" : ""} \u2014 aliased imports would break:`
|
|
2707
2709
|
)
|
|
2708
2710
|
);
|
|
2709
2711
|
for (const r of skippedRenames.slice(0, 5)) {
|
|
2710
|
-
console.log(
|
|
2712
|
+
console.log(import_chalk9.default.dim(` ${r.oldPath} \u2192 ${r.newPath}`));
|
|
2711
2713
|
}
|
|
2712
2714
|
if (skippedRenames.length > 5) {
|
|
2713
|
-
console.log(
|
|
2715
|
+
console.log(import_chalk9.default.dim(` ... and ${skippedRenames.length - 5} more`));
|
|
2714
2716
|
}
|
|
2715
2717
|
console.log("");
|
|
2716
|
-
console.log(
|
|
2718
|
+
console.log(import_chalk9.default.yellow("Affected aliased imports:"));
|
|
2717
2719
|
for (const alias of aliasImports.slice(0, 5)) {
|
|
2718
2720
|
const relFile = path13.relative(projectRoot, alias.file);
|
|
2719
|
-
console.log(
|
|
2721
|
+
console.log(import_chalk9.default.dim(` ${relFile}:${alias.line} \u2014 ${alias.specifier}`));
|
|
2720
2722
|
}
|
|
2721
2723
|
if (aliasImports.length > 5) {
|
|
2722
|
-
console.log(
|
|
2724
|
+
console.log(import_chalk9.default.dim(` ... and ${aliasImports.length - 5} more`));
|
|
2723
2725
|
}
|
|
2724
|
-
console.log(
|
|
2726
|
+
console.log(import_chalk9.default.dim(" Update these imports to relative paths first, then re-run fix."));
|
|
2725
2727
|
}
|
|
2726
2728
|
if (safeRenames.length === 0 && testStubs.length === 0) {
|
|
2727
2729
|
console.log(`
|
|
2728
|
-
${
|
|
2730
|
+
${import_chalk9.default.yellow("!")} No safe fixes to apply. Resolve aliased imports first.`);
|
|
2729
2731
|
return 0;
|
|
2730
2732
|
}
|
|
2731
2733
|
if (options.dryRun) {
|
|
2732
|
-
console.log(
|
|
2734
|
+
console.log(import_chalk9.default.dim("\nDry run \u2014 no changes applied."));
|
|
2733
2735
|
return 0;
|
|
2734
2736
|
}
|
|
2735
2737
|
if (!options.yes) {
|
|
@@ -2760,15 +2762,15 @@ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imp
|
|
|
2760
2762
|
}
|
|
2761
2763
|
console.log("");
|
|
2762
2764
|
if (renameCount > 0) {
|
|
2763
|
-
console.log(`${
|
|
2765
|
+
console.log(`${import_chalk9.default.green("\u2713")} Renamed ${renameCount} file${renameCount > 1 ? "s" : ""}`);
|
|
2764
2766
|
}
|
|
2765
2767
|
if (importUpdateCount > 0) {
|
|
2766
2768
|
console.log(
|
|
2767
|
-
`${
|
|
2769
|
+
`${import_chalk9.default.green("\u2713")} Updated ${importUpdateCount} import${importUpdateCount > 1 ? "s" : ""}`
|
|
2768
2770
|
);
|
|
2769
2771
|
}
|
|
2770
2772
|
if (stubCount > 0) {
|
|
2771
|
-
console.log(`${
|
|
2773
|
+
console.log(`${import_chalk9.default.green("\u2713")} Generated ${stubCount} test stub${stubCount > 1 ? "s" : ""}`);
|
|
2772
2774
|
}
|
|
2773
2775
|
return 0;
|
|
2774
2776
|
}
|
|
@@ -2776,16 +2778,16 @@ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imp
|
|
|
2776
2778
|
// src/commands/init.ts
|
|
2777
2779
|
var fs21 = __toESM(require("fs"), 1);
|
|
2778
2780
|
var path21 = __toESM(require("path"), 1);
|
|
2779
|
-
var
|
|
2781
|
+
var clack13 = __toESM(require("@clack/prompts"), 1);
|
|
2780
2782
|
var import_config9 = require("@viberails/config");
|
|
2781
2783
|
var import_scanner3 = require("@viberails/scanner");
|
|
2782
|
-
var
|
|
2784
|
+
var import_chalk15 = __toESM(require("chalk"), 1);
|
|
2783
2785
|
|
|
2784
2786
|
// src/utils/check-prerequisites.ts
|
|
2785
2787
|
var fs14 = __toESM(require("fs"), 1);
|
|
2786
2788
|
var path14 = __toESM(require("path"), 1);
|
|
2787
2789
|
var clack7 = __toESM(require("@clack/prompts"), 1);
|
|
2788
|
-
var
|
|
2790
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
2789
2791
|
|
|
2790
2792
|
// src/utils/spawn-async.ts
|
|
2791
2793
|
var import_node_child_process4 = require("child_process");
|
|
@@ -2840,9 +2842,9 @@ function displayMissingPrereqs(prereqs) {
|
|
|
2840
2842
|
const missing = prereqs.filter((p) => !p.installed);
|
|
2841
2843
|
for (const m of missing) {
|
|
2842
2844
|
const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
|
|
2843
|
-
console.log(` ${
|
|
2845
|
+
console.log(` ${import_chalk10.default.yellow("!")} ${m.label} not installed${suffix}`);
|
|
2844
2846
|
if (m.installCommand) {
|
|
2845
|
-
console.log(` Install: ${
|
|
2847
|
+
console.log(` Install: ${import_chalk10.default.cyan(m.installCommand)}`);
|
|
2846
2848
|
}
|
|
2847
2849
|
}
|
|
2848
2850
|
}
|
|
@@ -2887,6 +2889,9 @@ async function executeDeferredInstalls(projectRoot, installs) {
|
|
|
2887
2889
|
}
|
|
2888
2890
|
|
|
2889
2891
|
// src/utils/prompt-main-menu.ts
|
|
2892
|
+
var clack11 = __toESM(require("@clack/prompts"), 1);
|
|
2893
|
+
|
|
2894
|
+
// src/utils/prompt-main-menu-handlers.ts
|
|
2890
2895
|
var clack10 = __toESM(require("@clack/prompts"), 1);
|
|
2891
2896
|
|
|
2892
2897
|
// src/utils/prompt-integrations.ts
|
|
@@ -2980,140 +2985,7 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
|
|
|
2980
2985
|
};
|
|
2981
2986
|
}
|
|
2982
2987
|
|
|
2983
|
-
// src/utils/prompt-main-menu-
|
|
2984
|
-
function fileLimitsHint(config) {
|
|
2985
|
-
const max = config.rules.maxFileLines;
|
|
2986
|
-
const test = config.rules.maxTestFileLines;
|
|
2987
|
-
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
2988
|
-
}
|
|
2989
|
-
function fileNamingHint(config, scanResult) {
|
|
2990
|
-
const rootPkg = getRootPackage(config.packages);
|
|
2991
|
-
const naming = rootPkg.conventions?.fileNaming;
|
|
2992
|
-
if (!config.rules.enforceNaming) return "not enforced";
|
|
2993
|
-
if (naming) {
|
|
2994
|
-
const detected = scanResult.packages.some(
|
|
2995
|
-
(p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
|
|
2996
|
-
);
|
|
2997
|
-
return detected ? `${naming} (detected)` : naming;
|
|
2998
|
-
}
|
|
2999
|
-
return "mixed \u2014 will not enforce if skipped";
|
|
3000
|
-
}
|
|
3001
|
-
function fileNamingStatus(config) {
|
|
3002
|
-
if (!config.rules.enforceNaming) return "disabled";
|
|
3003
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3004
|
-
return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
|
|
3005
|
-
}
|
|
3006
|
-
function missingTestsHint(config) {
|
|
3007
|
-
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3008
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3009
|
-
const pattern = rootPkg.structure?.testPattern;
|
|
3010
|
-
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3011
|
-
}
|
|
3012
|
-
function coverageHint(config, hasTestRunner) {
|
|
3013
|
-
if (config.rules.testCoverage === 0) return "disabled";
|
|
3014
|
-
if (!hasTestRunner)
|
|
3015
|
-
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3016
|
-
const isMonorepo = config.packages.length > 1;
|
|
3017
|
-
if (isMonorepo) {
|
|
3018
|
-
const withCov = config.packages.filter(
|
|
3019
|
-
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3020
|
-
);
|
|
3021
|
-
const exempt = config.packages.length - withCov.length;
|
|
3022
|
-
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3023
|
-
}
|
|
3024
|
-
return `${config.rules.testCoverage}%`;
|
|
3025
|
-
}
|
|
3026
|
-
function advancedNamingHint(config) {
|
|
3027
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3028
|
-
const parts = [];
|
|
3029
|
-
if (rootPkg.conventions?.componentNaming)
|
|
3030
|
-
parts.push(`${rootPkg.conventions.componentNaming} components`);
|
|
3031
|
-
if (rootPkg.conventions?.hookNaming) parts.push(`${rootPkg.conventions.hookNaming} hooks`);
|
|
3032
|
-
if (rootPkg.conventions?.importAlias) parts.push(rootPkg.conventions.importAlias);
|
|
3033
|
-
return parts.length > 0 ? parts.join(", ") : "component, hook, and alias conventions";
|
|
3034
|
-
}
|
|
3035
|
-
function integrationsHint(state) {
|
|
3036
|
-
if (!state.visited.integrations || !state.integrations)
|
|
3037
|
-
return "not configured \u2014 select to set up";
|
|
3038
|
-
const items = [];
|
|
3039
|
-
if (state.integrations.preCommitHook) items.push("pre-commit");
|
|
3040
|
-
if (state.integrations.typecheckHook) items.push("typecheck");
|
|
3041
|
-
if (state.integrations.lintHook) items.push("lint");
|
|
3042
|
-
if (state.integrations.claudeCodeHook) items.push("Claude");
|
|
3043
|
-
if (state.integrations.claudeMdRef) items.push("CLAUDE.md");
|
|
3044
|
-
if (state.integrations.githubAction) items.push("CI");
|
|
3045
|
-
return items.length > 0 ? items.join(" \xB7 ") : "none selected";
|
|
3046
|
-
}
|
|
3047
|
-
function packageOverridesHint(config) {
|
|
3048
|
-
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3049
|
-
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3050
|
-
const customized = editable.filter(
|
|
3051
|
-
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3052
|
-
).length;
|
|
3053
|
-
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3054
|
-
}
|
|
3055
|
-
function boundariesHint(config, state) {
|
|
3056
|
-
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3057
|
-
const deny = config.boundaries?.deny;
|
|
3058
|
-
if (!deny) return "enabled";
|
|
3059
|
-
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3060
|
-
const pkgCount = Object.keys(deny).length;
|
|
3061
|
-
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3062
|
-
}
|
|
3063
|
-
function statusIcon(status) {
|
|
3064
|
-
if (status === "ok") return "\u2713";
|
|
3065
|
-
if (status === "needs-input") return "?";
|
|
3066
|
-
return "~";
|
|
3067
|
-
}
|
|
3068
|
-
function buildMainMenuOptions(config, scanResult, state) {
|
|
3069
|
-
const namingStatus = fileNamingStatus(config);
|
|
3070
|
-
const coverageStatus = config.rules.testCoverage === 0 ? "disabled" : !state.hasTestRunner ? "disabled" : "ok";
|
|
3071
|
-
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "disabled";
|
|
3072
|
-
const options = [
|
|
3073
|
-
{
|
|
3074
|
-
value: "fileLimits",
|
|
3075
|
-
label: `${statusIcon("ok")} Max file size`,
|
|
3076
|
-
hint: fileLimitsHint(config)
|
|
3077
|
-
},
|
|
3078
|
-
{
|
|
3079
|
-
value: "fileNaming",
|
|
3080
|
-
label: `${statusIcon(namingStatus)} File naming`,
|
|
3081
|
-
hint: fileNamingHint(config, scanResult)
|
|
3082
|
-
},
|
|
3083
|
-
{
|
|
3084
|
-
value: "missingTests",
|
|
3085
|
-
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3086
|
-
hint: missingTestsHint(config)
|
|
3087
|
-
},
|
|
3088
|
-
{
|
|
3089
|
-
value: "coverage",
|
|
3090
|
-
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3091
|
-
hint: coverageHint(config, state.hasTestRunner)
|
|
3092
|
-
},
|
|
3093
|
-
{ value: "advancedNaming", label: " Advanced naming", hint: advancedNamingHint(config) }
|
|
3094
|
-
];
|
|
3095
|
-
if (config.packages.length > 1) {
|
|
3096
|
-
const bIcon = state.visited.boundaries && config.rules.enforceBoundaries ? statusIcon("ok") : " ";
|
|
3097
|
-
options.push(
|
|
3098
|
-
{
|
|
3099
|
-
value: "packageOverrides",
|
|
3100
|
-
label: " Per-package overrides",
|
|
3101
|
-
hint: packageOverridesHint(config)
|
|
3102
|
-
},
|
|
3103
|
-
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3104
|
-
);
|
|
3105
|
-
}
|
|
3106
|
-
const iIcon = state.visited.integrations ? statusIcon("ok") : " ";
|
|
3107
|
-
options.push(
|
|
3108
|
-
{ value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
|
|
3109
|
-
{ value: "reset", label: " Reset all to defaults" },
|
|
3110
|
-
{ value: "review", label: " Review scan details" },
|
|
3111
|
-
{ value: "done", label: " Done \u2014 write config" }
|
|
3112
|
-
);
|
|
3113
|
-
return options;
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
// src/utils/prompt-main-menu.ts
|
|
2988
|
+
// src/utils/prompt-main-menu-handlers.ts
|
|
3117
2989
|
async function handleAdvancedNaming(config) {
|
|
3118
2990
|
const rootPkg = getRootPackage(config.packages);
|
|
3119
2991
|
const state = {
|
|
@@ -3141,51 +3013,6 @@ async function handleAdvancedNaming(config) {
|
|
|
3141
3013
|
rootPkg.conventions.hookNaming = state.hookNaming || void 0;
|
|
3142
3014
|
rootPkg.conventions.importAlias = state.importAlias || void 0;
|
|
3143
3015
|
}
|
|
3144
|
-
async function promptMainMenu(config, scanResult, opts) {
|
|
3145
|
-
const originalConfig = structuredClone(config);
|
|
3146
|
-
const state = {
|
|
3147
|
-
visited: { integrations: false, boundaries: false },
|
|
3148
|
-
deferredInstalls: [],
|
|
3149
|
-
hasTestRunner: opts.hasTestRunner,
|
|
3150
|
-
hookManager: opts.hookManager
|
|
3151
|
-
};
|
|
3152
|
-
while (true) {
|
|
3153
|
-
const options = buildMainMenuOptions(config, scanResult, state);
|
|
3154
|
-
const choice = await clack10.select({ message: "Configure viberails", options });
|
|
3155
|
-
assertNotCancelled(choice);
|
|
3156
|
-
if (choice === "done") {
|
|
3157
|
-
if (config.rules.enforceNaming && !getRootPackage(config.packages).conventions?.fileNaming) {
|
|
3158
|
-
config.rules.enforceNaming = false;
|
|
3159
|
-
}
|
|
3160
|
-
break;
|
|
3161
|
-
}
|
|
3162
|
-
if (choice === "fileLimits") {
|
|
3163
|
-
const s = {
|
|
3164
|
-
maxFileLines: config.rules.maxFileLines,
|
|
3165
|
-
maxTestFileLines: config.rules.maxTestFileLines
|
|
3166
|
-
};
|
|
3167
|
-
await promptFileLimitsMenu(s);
|
|
3168
|
-
config.rules.maxFileLines = s.maxFileLines;
|
|
3169
|
-
config.rules.maxTestFileLines = s.maxTestFileLines;
|
|
3170
|
-
}
|
|
3171
|
-
if (choice === "fileNaming") await handleFileNaming(config, scanResult);
|
|
3172
|
-
if (choice === "missingTests") await handleMissingTests(config);
|
|
3173
|
-
if (choice === "coverage") await handleCoverage(config, state, opts);
|
|
3174
|
-
if (choice === "advancedNaming") await handleAdvancedNaming(config);
|
|
3175
|
-
if (choice === "packageOverrides") await handlePackageOverrides(config);
|
|
3176
|
-
if (choice === "boundaries") await handleBoundaries(config, state, opts);
|
|
3177
|
-
if (choice === "integrations") await handleIntegrations(state, opts);
|
|
3178
|
-
if (choice === "review") clack10.note(formatScanResultsText(scanResult), "Scan details");
|
|
3179
|
-
if (choice === "reset") {
|
|
3180
|
-
Object.assign(config, structuredClone(originalConfig));
|
|
3181
|
-
state.deferredInstalls = [];
|
|
3182
|
-
state.visited = { integrations: false, boundaries: false };
|
|
3183
|
-
state.integrations = void 0;
|
|
3184
|
-
clack10.log.info("Reset all settings to scan-detected defaults.");
|
|
3185
|
-
}
|
|
3186
|
-
}
|
|
3187
|
-
return state;
|
|
3188
|
-
}
|
|
3189
3016
|
async function handleFileNaming(config, scanResult) {
|
|
3190
3017
|
const isMonorepo = config.packages.length > 1;
|
|
3191
3018
|
if (isMonorepo) {
|
|
@@ -3214,7 +3041,7 @@ async function handleFileNaming(config, scanResult) {
|
|
|
3214
3041
|
options: [...namingOptions, { value: SENTINEL_SKIP, label: "Don't enforce" }],
|
|
3215
3042
|
initialValue: rootPkg.conventions?.fileNaming ?? SENTINEL_SKIP
|
|
3216
3043
|
});
|
|
3217
|
-
|
|
3044
|
+
if (isCancelled(selected)) return;
|
|
3218
3045
|
if (selected === SENTINEL_SKIP) {
|
|
3219
3046
|
config.rules.enforceNaming = false;
|
|
3220
3047
|
if (rootPkg.conventions) delete rootPkg.conventions.fileNaming;
|
|
@@ -3229,12 +3056,15 @@ async function handleMissingTests(config) {
|
|
|
3229
3056
|
message: "Require every source file to have a test file?",
|
|
3230
3057
|
initialValue: config.rules.enforceMissingTests
|
|
3231
3058
|
});
|
|
3232
|
-
|
|
3059
|
+
if (isCancelled(result)) return;
|
|
3233
3060
|
config.rules.enforceMissingTests = result;
|
|
3234
3061
|
}
|
|
3235
3062
|
async function handleCoverage(config, state, opts) {
|
|
3236
3063
|
if (!opts.hasTestRunner) {
|
|
3237
|
-
clack10.
|
|
3064
|
+
clack10.note(
|
|
3065
|
+
"No test runner (vitest, jest, etc.) was detected.\nInstall one, then re-run viberails init to configure coverage.",
|
|
3066
|
+
"Coverage inactive"
|
|
3067
|
+
);
|
|
3238
3068
|
return;
|
|
3239
3069
|
}
|
|
3240
3070
|
const planned = planCoverageInstall(opts.coveragePrereqs);
|
|
@@ -3255,7 +3085,7 @@ async function handleCoverage(config, state, opts) {
|
|
|
3255
3085
|
}
|
|
3256
3086
|
]
|
|
3257
3087
|
});
|
|
3258
|
-
|
|
3088
|
+
if (isCancelled(choice)) return;
|
|
3259
3089
|
state.deferredInstalls = state.deferredInstalls.filter((d) => d.command !== planned.command);
|
|
3260
3090
|
if (choice === "install") {
|
|
3261
3091
|
planned.onFailure = () => {
|
|
@@ -3276,7 +3106,7 @@ async function handleCoverage(config, state, opts) {
|
|
|
3276
3106
|
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
3277
3107
|
}
|
|
3278
3108
|
});
|
|
3279
|
-
|
|
3109
|
+
if (isCancelled(result)) return;
|
|
3280
3110
|
config.rules.testCoverage = Number.parseInt(result, 10);
|
|
3281
3111
|
}
|
|
3282
3112
|
async function handlePackageOverrides(config) {
|
|
@@ -3295,7 +3125,7 @@ async function handleBoundaries(config, state, opts) {
|
|
|
3295
3125
|
message: "Infer boundary rules from current import patterns?",
|
|
3296
3126
|
initialValue: false
|
|
3297
3127
|
});
|
|
3298
|
-
|
|
3128
|
+
if (isCancelled(shouldInfer)) return;
|
|
3299
3129
|
state.visited.boundaries = true;
|
|
3300
3130
|
if (!shouldInfer) {
|
|
3301
3131
|
config.rules.enforceBoundaries = false;
|
|
@@ -3338,6 +3168,221 @@ async function handleIntegrations(state, opts) {
|
|
|
3338
3168
|
}
|
|
3339
3169
|
}
|
|
3340
3170
|
|
|
3171
|
+
// src/utils/prompt-main-menu-hints.ts
|
|
3172
|
+
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
3173
|
+
function fileLimitsHint(config) {
|
|
3174
|
+
const max = config.rules.maxFileLines;
|
|
3175
|
+
const test = config.rules.maxTestFileLines;
|
|
3176
|
+
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
3177
|
+
}
|
|
3178
|
+
function fileNamingHint(config, scanResult) {
|
|
3179
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3180
|
+
const naming = rootPkg.conventions?.fileNaming;
|
|
3181
|
+
if (!config.rules.enforceNaming) return "not enforced";
|
|
3182
|
+
if (naming) {
|
|
3183
|
+
const detected = scanResult.packages.some(
|
|
3184
|
+
(p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
|
|
3185
|
+
);
|
|
3186
|
+
return detected ? `${naming} (detected)` : naming;
|
|
3187
|
+
}
|
|
3188
|
+
return "mixed \u2014 will not enforce if skipped";
|
|
3189
|
+
}
|
|
3190
|
+
function fileNamingStatus(config) {
|
|
3191
|
+
if (!config.rules.enforceNaming) return "disabled";
|
|
3192
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3193
|
+
return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
|
|
3194
|
+
}
|
|
3195
|
+
function missingTestsHint(config) {
|
|
3196
|
+
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3197
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3198
|
+
const pattern = rootPkg.structure?.testPattern;
|
|
3199
|
+
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3200
|
+
}
|
|
3201
|
+
function coverageHint(config, hasTestRunner) {
|
|
3202
|
+
if (config.rules.testCoverage === 0) return "disabled";
|
|
3203
|
+
if (!hasTestRunner)
|
|
3204
|
+
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3205
|
+
const isMonorepo = config.packages.length > 1;
|
|
3206
|
+
if (isMonorepo) {
|
|
3207
|
+
const withCov = config.packages.filter(
|
|
3208
|
+
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3209
|
+
);
|
|
3210
|
+
const exempt = config.packages.length - withCov.length;
|
|
3211
|
+
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3212
|
+
}
|
|
3213
|
+
return `${config.rules.testCoverage}%`;
|
|
3214
|
+
}
|
|
3215
|
+
function advancedNamingHint(config) {
|
|
3216
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3217
|
+
if (!config.rules.enforceNaming) return "not enforced";
|
|
3218
|
+
const parts = [];
|
|
3219
|
+
const naming = rootPkg.conventions?.fileNaming;
|
|
3220
|
+
if (naming) parts.push(import_chalk11.default.green(naming));
|
|
3221
|
+
const comp = rootPkg.conventions?.componentNaming;
|
|
3222
|
+
parts.push(comp ? import_chalk11.default.green(`${comp} components`) : import_chalk11.default.dim("components"));
|
|
3223
|
+
const hook = rootPkg.conventions?.hookNaming;
|
|
3224
|
+
parts.push(hook ? import_chalk11.default.green(`${hook} hooks`) : import_chalk11.default.dim("hooks"));
|
|
3225
|
+
const alias = rootPkg.conventions?.importAlias;
|
|
3226
|
+
parts.push(alias ? import_chalk11.default.green(alias) : import_chalk11.default.dim("alias"));
|
|
3227
|
+
return parts.join(import_chalk11.default.dim(", "));
|
|
3228
|
+
}
|
|
3229
|
+
function integrationsHint(state) {
|
|
3230
|
+
if (!state.visited.integrations || !state.integrations)
|
|
3231
|
+
return "not configured \u2014 select to set up";
|
|
3232
|
+
const items = [];
|
|
3233
|
+
if (state.integrations.preCommitHook) items.push(import_chalk11.default.green("pre-commit"));
|
|
3234
|
+
if (state.integrations.typecheckHook) items.push(import_chalk11.default.green("typecheck"));
|
|
3235
|
+
if (state.integrations.lintHook) items.push(import_chalk11.default.green("lint"));
|
|
3236
|
+
if (state.integrations.claudeCodeHook) items.push(import_chalk11.default.green("Claude"));
|
|
3237
|
+
if (state.integrations.claudeMdRef) items.push(import_chalk11.default.green("CLAUDE.md"));
|
|
3238
|
+
if (state.integrations.githubAction) items.push(import_chalk11.default.green("CI"));
|
|
3239
|
+
return items.length > 0 ? items.join(import_chalk11.default.dim(" \xB7 ")) : "none selected";
|
|
3240
|
+
}
|
|
3241
|
+
function packageOverridesHint(config) {
|
|
3242
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3243
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3244
|
+
const customized = editable.filter(
|
|
3245
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3246
|
+
).length;
|
|
3247
|
+
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3248
|
+
}
|
|
3249
|
+
function boundariesHint(config, state) {
|
|
3250
|
+
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3251
|
+
const deny = config.boundaries?.deny;
|
|
3252
|
+
if (!deny) return "enabled";
|
|
3253
|
+
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3254
|
+
const pkgCount = Object.keys(deny).length;
|
|
3255
|
+
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3256
|
+
}
|
|
3257
|
+
function advancedNamingStatus(config) {
|
|
3258
|
+
if (!config.rules.enforceNaming) return "disabled";
|
|
3259
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3260
|
+
const hasAny = !!rootPkg.conventions?.fileNaming || !!rootPkg.conventions?.componentNaming || !!rootPkg.conventions?.hookNaming || !!rootPkg.conventions?.importAlias;
|
|
3261
|
+
return hasAny ? "ok" : "unconfigured";
|
|
3262
|
+
}
|
|
3263
|
+
function packageOverridesStatus(config) {
|
|
3264
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3265
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3266
|
+
const customized = editable.some(
|
|
3267
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3268
|
+
);
|
|
3269
|
+
return customized ? "ok" : "unconfigured";
|
|
3270
|
+
}
|
|
3271
|
+
function statusIcon(status) {
|
|
3272
|
+
if (status === "ok") return import_chalk11.default.green("\u2713");
|
|
3273
|
+
if (status === "needs-input") return import_chalk11.default.yellow("?");
|
|
3274
|
+
if (status === "unconfigured") return import_chalk11.default.dim("-");
|
|
3275
|
+
return import_chalk11.default.yellow("~");
|
|
3276
|
+
}
|
|
3277
|
+
function buildMainMenuOptions(config, scanResult, state) {
|
|
3278
|
+
const namingStatus = fileNamingStatus(config);
|
|
3279
|
+
const coverageStatus = config.rules.testCoverage === 0 ? "disabled" : !state.hasTestRunner ? "disabled" : "ok";
|
|
3280
|
+
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "disabled";
|
|
3281
|
+
const options = [
|
|
3282
|
+
{
|
|
3283
|
+
value: "fileLimits",
|
|
3284
|
+
label: `${statusIcon("ok")} Max file size`,
|
|
3285
|
+
hint: fileLimitsHint(config)
|
|
3286
|
+
},
|
|
3287
|
+
{
|
|
3288
|
+
value: "fileNaming",
|
|
3289
|
+
label: `${statusIcon(namingStatus)} Default file naming`,
|
|
3290
|
+
hint: fileNamingHint(config, scanResult)
|
|
3291
|
+
},
|
|
3292
|
+
{
|
|
3293
|
+
value: "missingTests",
|
|
3294
|
+
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3295
|
+
hint: missingTestsHint(config)
|
|
3296
|
+
},
|
|
3297
|
+
{
|
|
3298
|
+
value: "coverage",
|
|
3299
|
+
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3300
|
+
hint: coverageHint(config, state.hasTestRunner)
|
|
3301
|
+
},
|
|
3302
|
+
{
|
|
3303
|
+
value: "advancedNaming",
|
|
3304
|
+
label: `${statusIcon(advancedNamingStatus(config))} Advanced naming`,
|
|
3305
|
+
hint: advancedNamingHint(config)
|
|
3306
|
+
}
|
|
3307
|
+
];
|
|
3308
|
+
if (config.packages.length > 1) {
|
|
3309
|
+
const bIcon = statusIcon(
|
|
3310
|
+
state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
|
|
3311
|
+
);
|
|
3312
|
+
const poIcon = statusIcon(packageOverridesStatus(config));
|
|
3313
|
+
options.push(
|
|
3314
|
+
{
|
|
3315
|
+
value: "packageOverrides",
|
|
3316
|
+
label: `${poIcon} Per-package overrides`,
|
|
3317
|
+
hint: packageOverridesHint(config)
|
|
3318
|
+
},
|
|
3319
|
+
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
|
|
3323
|
+
options.push(
|
|
3324
|
+
{ value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
|
|
3325
|
+
{ value: "reset", label: " Reset all to defaults" },
|
|
3326
|
+
{ value: "review", label: " Review scan details", hint: "detected stack & conventions" },
|
|
3327
|
+
{ value: "done", label: " Done \u2014 write config" }
|
|
3328
|
+
);
|
|
3329
|
+
return options;
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
// src/utils/prompt-main-menu.ts
|
|
3333
|
+
async function promptMainMenu(config, scanResult, opts) {
|
|
3334
|
+
const originalConfig = structuredClone(config);
|
|
3335
|
+
const state = {
|
|
3336
|
+
visited: { integrations: false, boundaries: false },
|
|
3337
|
+
deferredInstalls: [],
|
|
3338
|
+
hasTestRunner: opts.hasTestRunner,
|
|
3339
|
+
hookManager: opts.hookManager
|
|
3340
|
+
};
|
|
3341
|
+
while (true) {
|
|
3342
|
+
const options = buildMainMenuOptions(config, scanResult, state);
|
|
3343
|
+
const choice = await clack11.select({ message: "Configure viberails", options });
|
|
3344
|
+
assertNotCancelled(choice);
|
|
3345
|
+
if (choice === "done") {
|
|
3346
|
+
if (config.rules.enforceNaming && !getRootPackage(config.packages).conventions?.fileNaming) {
|
|
3347
|
+
config.rules.enforceNaming = false;
|
|
3348
|
+
}
|
|
3349
|
+
break;
|
|
3350
|
+
}
|
|
3351
|
+
if (choice === "fileLimits") {
|
|
3352
|
+
const s = {
|
|
3353
|
+
maxFileLines: config.rules.maxFileLines,
|
|
3354
|
+
maxTestFileLines: config.rules.maxTestFileLines
|
|
3355
|
+
};
|
|
3356
|
+
await promptFileLimitsMenu(s);
|
|
3357
|
+
config.rules.maxFileLines = s.maxFileLines;
|
|
3358
|
+
config.rules.maxTestFileLines = s.maxTestFileLines;
|
|
3359
|
+
}
|
|
3360
|
+
if (choice === "fileNaming") await handleFileNaming(config, scanResult);
|
|
3361
|
+
if (choice === "missingTests") await handleMissingTests(config);
|
|
3362
|
+
if (choice === "coverage") await handleCoverage(config, state, opts);
|
|
3363
|
+
if (choice === "advancedNaming") await handleAdvancedNaming(config);
|
|
3364
|
+
if (choice === "packageOverrides") await handlePackageOverrides(config);
|
|
3365
|
+
if (choice === "boundaries") await handleBoundaries(config, state, opts);
|
|
3366
|
+
if (choice === "integrations") await handleIntegrations(state, opts);
|
|
3367
|
+
if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
|
|
3368
|
+
if (choice === "reset") {
|
|
3369
|
+
const confirmed = await clack11.confirm({
|
|
3370
|
+
message: "Reset all settings to scan-detected defaults?",
|
|
3371
|
+
initialValue: false
|
|
3372
|
+
});
|
|
3373
|
+
assertNotCancelled(confirmed);
|
|
3374
|
+
if (confirmed) {
|
|
3375
|
+
Object.assign(config, structuredClone(originalConfig));
|
|
3376
|
+
state.deferredInstalls = [];
|
|
3377
|
+
state.visited = { integrations: false, boundaries: false };
|
|
3378
|
+
state.integrations = void 0;
|
|
3379
|
+
clack11.log.info("Reset all settings to scan-detected defaults.");
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
return state;
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3341
3386
|
// src/utils/update-gitignore.ts
|
|
3342
3387
|
var fs16 = __toESM(require("fs"), 1);
|
|
3343
3388
|
var path16 = __toESM(require("path"), 1);
|
|
@@ -3358,7 +3403,7 @@ function updateGitignore(projectRoot) {
|
|
|
3358
3403
|
// src/commands/init-hooks.ts
|
|
3359
3404
|
var fs18 = __toESM(require("fs"), 1);
|
|
3360
3405
|
var path18 = __toESM(require("path"), 1);
|
|
3361
|
-
var
|
|
3406
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3362
3407
|
var import_yaml = require("yaml");
|
|
3363
3408
|
|
|
3364
3409
|
// src/commands/resolve-typecheck.ts
|
|
@@ -3403,13 +3448,13 @@ function setupPreCommitHook(projectRoot) {
|
|
|
3403
3448
|
const lefthookPath = path18.join(projectRoot, "lefthook.yml");
|
|
3404
3449
|
if (fs18.existsSync(lefthookPath)) {
|
|
3405
3450
|
addLefthookPreCommit(lefthookPath);
|
|
3406
|
-
console.log(` ${
|
|
3451
|
+
console.log(` ${import_chalk12.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
3407
3452
|
return "lefthook.yml";
|
|
3408
3453
|
}
|
|
3409
3454
|
const huskyDir = path18.join(projectRoot, ".husky");
|
|
3410
3455
|
if (fs18.existsSync(huskyDir)) {
|
|
3411
3456
|
writeHuskyPreCommit(huskyDir);
|
|
3412
|
-
console.log(` ${
|
|
3457
|
+
console.log(` ${import_chalk12.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
3413
3458
|
return ".husky/pre-commit";
|
|
3414
3459
|
}
|
|
3415
3460
|
const gitDir = path18.join(projectRoot, ".git");
|
|
@@ -3419,7 +3464,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
3419
3464
|
fs18.mkdirSync(hooksDir, { recursive: true });
|
|
3420
3465
|
}
|
|
3421
3466
|
writeGitHookPreCommit(hooksDir);
|
|
3422
|
-
console.log(` ${
|
|
3467
|
+
console.log(` ${import_chalk12.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
3423
3468
|
return ".git/hooks/pre-commit";
|
|
3424
3469
|
}
|
|
3425
3470
|
return void 0;
|
|
@@ -3480,9 +3525,9 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3480
3525
|
settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
|
|
3481
3526
|
} catch {
|
|
3482
3527
|
console.warn(
|
|
3483
|
-
` ${
|
|
3528
|
+
` ${import_chalk12.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
3484
3529
|
);
|
|
3485
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
3530
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk12.default.cyan("viberails init --force")}`);
|
|
3486
3531
|
return;
|
|
3487
3532
|
}
|
|
3488
3533
|
}
|
|
@@ -3505,7 +3550,7 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3505
3550
|
settings.hooks = hooks;
|
|
3506
3551
|
fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
3507
3552
|
`);
|
|
3508
|
-
console.log(` ${
|
|
3553
|
+
console.log(` ${import_chalk12.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
3509
3554
|
}
|
|
3510
3555
|
function setupClaudeMdReference(projectRoot) {
|
|
3511
3556
|
const claudeMdPath = path18.join(projectRoot, "CLAUDE.md");
|
|
@@ -3517,7 +3562,7 @@ function setupClaudeMdReference(projectRoot) {
|
|
|
3517
3562
|
const ref = "\n@.viberails/context.md\n";
|
|
3518
3563
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
3519
3564
|
fs18.writeFileSync(claudeMdPath, prefix + ref);
|
|
3520
|
-
console.log(` ${
|
|
3565
|
+
console.log(` ${import_chalk12.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
3521
3566
|
}
|
|
3522
3567
|
function setupGithubAction(projectRoot, packageManager, options) {
|
|
3523
3568
|
const workflowDir = path18.join(projectRoot, ".github", "workflows");
|
|
@@ -3603,7 +3648,7 @@ ${cmd}
|
|
|
3603
3648
|
// src/commands/init-hooks-extra.ts
|
|
3604
3649
|
var fs19 = __toESM(require("fs"), 1);
|
|
3605
3650
|
var path19 = __toESM(require("path"), 1);
|
|
3606
|
-
var
|
|
3651
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
3607
3652
|
var import_yaml2 = require("yaml");
|
|
3608
3653
|
function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
|
|
3609
3654
|
const lefthookPath = path19.join(projectRoot, "lefthook.yml");
|
|
@@ -3663,12 +3708,12 @@ ${command}
|
|
|
3663
3708
|
function setupTypecheckHook(projectRoot, packageManager) {
|
|
3664
3709
|
const resolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3665
3710
|
if (!resolved.command) {
|
|
3666
|
-
console.log(` ${
|
|
3711
|
+
console.log(` ${import_chalk13.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
|
|
3667
3712
|
return void 0;
|
|
3668
3713
|
}
|
|
3669
3714
|
const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
|
|
3670
3715
|
if (target) {
|
|
3671
|
-
console.log(` ${
|
|
3716
|
+
console.log(` ${import_chalk13.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
|
|
3672
3717
|
}
|
|
3673
3718
|
return target;
|
|
3674
3719
|
}
|
|
@@ -3689,7 +3734,7 @@ function setupLintHook(projectRoot, linter) {
|
|
|
3689
3734
|
}
|
|
3690
3735
|
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
3691
3736
|
if (target) {
|
|
3692
|
-
console.log(` ${
|
|
3737
|
+
console.log(` ${import_chalk13.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
3693
3738
|
}
|
|
3694
3739
|
return target;
|
|
3695
3740
|
}
|
|
@@ -3698,7 +3743,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3698
3743
|
if (integrations.preCommitHook) {
|
|
3699
3744
|
const t = setupPreCommitHook(projectRoot);
|
|
3700
3745
|
if (t && opts.lefthookExpected && !t.includes("lefthook")) {
|
|
3701
|
-
console.log(` ${
|
|
3746
|
+
console.log(` ${import_chalk13.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
|
|
3702
3747
|
}
|
|
3703
3748
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
3704
3749
|
}
|
|
@@ -3731,10 +3776,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3731
3776
|
// src/commands/init-non-interactive.ts
|
|
3732
3777
|
var fs20 = __toESM(require("fs"), 1);
|
|
3733
3778
|
var path20 = __toESM(require("path"), 1);
|
|
3734
|
-
var
|
|
3779
|
+
var clack12 = __toESM(require("@clack/prompts"), 1);
|
|
3735
3780
|
var import_config8 = require("@viberails/config");
|
|
3736
3781
|
var import_scanner2 = require("@viberails/scanner");
|
|
3737
|
-
var
|
|
3782
|
+
var import_chalk14 = __toESM(require("chalk"), 1);
|
|
3738
3783
|
|
|
3739
3784
|
// src/utils/filter-confidence.ts
|
|
3740
3785
|
function filterHighConfidence(conventions, meta) {
|
|
@@ -3755,7 +3800,7 @@ function getExemptedPackages(config) {
|
|
|
3755
3800
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3756
3801
|
}
|
|
3757
3802
|
async function initNonInteractive(projectRoot, configPath) {
|
|
3758
|
-
const s =
|
|
3803
|
+
const s = clack12.spinner();
|
|
3759
3804
|
s.start("Scanning project...");
|
|
3760
3805
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3761
3806
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
@@ -3770,11 +3815,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3770
3815
|
const exempted = getExemptedPackages(config);
|
|
3771
3816
|
if (exempted.length > 0) {
|
|
3772
3817
|
console.log(
|
|
3773
|
-
` ${
|
|
3818
|
+
` ${import_chalk14.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk14.default.dim("(types-only)")}`
|
|
3774
3819
|
);
|
|
3775
3820
|
}
|
|
3776
3821
|
if (config.packages.length > 1) {
|
|
3777
|
-
const bs =
|
|
3822
|
+
const bs = clack12.spinner();
|
|
3778
3823
|
bs.start("Building import graph...");
|
|
3779
3824
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3780
3825
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -3807,14 +3852,14 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3807
3852
|
const hookManager = detectHookManager(projectRoot);
|
|
3808
3853
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3809
3854
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3810
|
-
const ok =
|
|
3855
|
+
const ok = import_chalk14.default.green("\u2713");
|
|
3811
3856
|
const created = [
|
|
3812
3857
|
`${ok} ${path20.basename(configPath)}`,
|
|
3813
3858
|
`${ok} .viberails/context.md`,
|
|
3814
3859
|
`${ok} .viberails/scan-result.json`,
|
|
3815
3860
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3816
3861
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3817
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
3862
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk14.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3818
3863
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3819
3864
|
].filter(Boolean);
|
|
3820
3865
|
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
@@ -3839,8 +3884,8 @@ async function initCommand(options, cwd) {
|
|
|
3839
3884
|
return initInteractive(projectRoot, configPath, options);
|
|
3840
3885
|
}
|
|
3841
3886
|
console.log(
|
|
3842
|
-
`${
|
|
3843
|
-
Run ${
|
|
3887
|
+
`${import_chalk15.default.yellow("!")} viberails is already initialized.
|
|
3888
|
+
Run ${import_chalk15.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk15.default.cyan("viberails sync")} to update generated files, or ${import_chalk15.default.cyan("viberails init --force")} to replace it.`
|
|
3844
3889
|
);
|
|
3845
3890
|
return;
|
|
3846
3891
|
}
|
|
@@ -3848,11 +3893,11 @@ async function initCommand(options, cwd) {
|
|
|
3848
3893
|
await initInteractive(projectRoot, configPath, options);
|
|
3849
3894
|
}
|
|
3850
3895
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3851
|
-
|
|
3896
|
+
clack13.intro("viberails");
|
|
3852
3897
|
if (fs21.existsSync(configPath) && !options.force) {
|
|
3853
3898
|
const action = await promptExistingConfigAction(path21.basename(configPath));
|
|
3854
3899
|
if (action === "cancel") {
|
|
3855
|
-
|
|
3900
|
+
clack13.outro("Aborted. No files were written.");
|
|
3856
3901
|
return;
|
|
3857
3902
|
}
|
|
3858
3903
|
if (action === "edit") {
|
|
@@ -3866,23 +3911,23 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3866
3911
|
`${path21.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3867
3912
|
);
|
|
3868
3913
|
if (!replace) {
|
|
3869
|
-
|
|
3914
|
+
clack13.outro("Aborted. No files were written.");
|
|
3870
3915
|
return;
|
|
3871
3916
|
}
|
|
3872
3917
|
}
|
|
3873
|
-
const s =
|
|
3918
|
+
const s = clack13.spinner();
|
|
3874
3919
|
s.start("Scanning project...");
|
|
3875
3920
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3876
3921
|
const config = (0, import_config9.generateConfig)(scanResult);
|
|
3877
3922
|
s.stop("Scan complete");
|
|
3878
3923
|
if (scanResult.statistics.totalFiles === 0) {
|
|
3879
|
-
|
|
3924
|
+
clack13.log.warn(
|
|
3880
3925
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3881
3926
|
);
|
|
3882
3927
|
}
|
|
3883
3928
|
const hasTestRunner = !!scanResult.stack.testRunner;
|
|
3884
3929
|
if (!hasTestRunner) {
|
|
3885
|
-
|
|
3930
|
+
clack13.log.info(
|
|
3886
3931
|
"No test runner detected. Coverage checks are inactive until a test runner is installed.\nInstall a test runner (e.g. vitest) and re-run viberails init."
|
|
3887
3932
|
);
|
|
3888
3933
|
}
|
|
@@ -3903,13 +3948,13 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3903
3948
|
});
|
|
3904
3949
|
const shouldWrite = await confirm3("Apply this setup?");
|
|
3905
3950
|
if (!shouldWrite) {
|
|
3906
|
-
|
|
3951
|
+
clack13.outro("Aborted. No files were written.");
|
|
3907
3952
|
return;
|
|
3908
3953
|
}
|
|
3909
3954
|
if (state.deferredInstalls.length > 0) {
|
|
3910
3955
|
await executeDeferredInstalls(projectRoot, state.deferredInstalls);
|
|
3911
3956
|
}
|
|
3912
|
-
const ws =
|
|
3957
|
+
const ws = clack13.spinner();
|
|
3913
3958
|
ws.start("Writing configuration...");
|
|
3914
3959
|
const compacted = (0, import_config9.compactConfig)(config);
|
|
3915
3960
|
fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
@@ -3917,10 +3962,10 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3917
3962
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
3918
3963
|
updateGitignore(projectRoot);
|
|
3919
3964
|
ws.stop("Configuration written");
|
|
3920
|
-
const ok =
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3965
|
+
const ok = import_chalk15.default.green("\u2713");
|
|
3966
|
+
clack13.log.step(`${ok} ${path21.basename(configPath)}`);
|
|
3967
|
+
clack13.log.step(`${ok} .viberails/context.md`);
|
|
3968
|
+
clack13.log.step(`${ok} .viberails/scan-result.json`);
|
|
3924
3969
|
if (state.visited.integrations && state.integrations) {
|
|
3925
3970
|
const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
|
|
3926
3971
|
setupSelectedIntegrations(projectRoot, state.integrations, {
|
|
@@ -3929,19 +3974,19 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3929
3974
|
lefthookExpected
|
|
3930
3975
|
});
|
|
3931
3976
|
}
|
|
3932
|
-
|
|
3977
|
+
clack13.outro(
|
|
3933
3978
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
3934
|
-
${
|
|
3979
|
+
${import_chalk15.default.dim("Tip: use")} ${import_chalk15.default.cyan("viberails check --enforce")} ${import_chalk15.default.dim("in CI to block PRs on violations.")}`
|
|
3935
3980
|
);
|
|
3936
3981
|
}
|
|
3937
3982
|
|
|
3938
3983
|
// src/commands/sync.ts
|
|
3939
3984
|
var fs22 = __toESM(require("fs"), 1);
|
|
3940
3985
|
var path22 = __toESM(require("path"), 1);
|
|
3941
|
-
var
|
|
3986
|
+
var clack14 = __toESM(require("@clack/prompts"), 1);
|
|
3942
3987
|
var import_config11 = require("@viberails/config");
|
|
3943
3988
|
var import_scanner4 = require("@viberails/scanner");
|
|
3944
|
-
var
|
|
3989
|
+
var import_chalk16 = __toESM(require("chalk"), 1);
|
|
3945
3990
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
3946
3991
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
3947
3992
|
function loadPreviousStats(projectRoot) {
|
|
@@ -3967,7 +4012,7 @@ async function syncCommand(options, cwd) {
|
|
|
3967
4012
|
const configPath = path22.join(projectRoot, CONFIG_FILE6);
|
|
3968
4013
|
const existing = await (0, import_config11.loadConfig)(configPath);
|
|
3969
4014
|
const previousStats = loadPreviousStats(projectRoot);
|
|
3970
|
-
const s =
|
|
4015
|
+
const s = clack14.spinner();
|
|
3971
4016
|
s.start("Scanning project...");
|
|
3972
4017
|
const scanResult = await (0, import_scanner4.scan)(projectRoot);
|
|
3973
4018
|
s.stop("Scan complete");
|
|
@@ -3982,19 +4027,19 @@ async function syncCommand(options, cwd) {
|
|
|
3982
4027
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
3983
4028
|
if (changes.length > 0 || statsDelta) {
|
|
3984
4029
|
console.log(`
|
|
3985
|
-
${
|
|
4030
|
+
${import_chalk16.default.bold("Changes:")}`);
|
|
3986
4031
|
for (const change of changes) {
|
|
3987
|
-
const icon = change.type === "removed" ?
|
|
4032
|
+
const icon = change.type === "removed" ? import_chalk16.default.red("-") : import_chalk16.default.green("+");
|
|
3988
4033
|
console.log(` ${icon} ${change.description}`);
|
|
3989
4034
|
}
|
|
3990
4035
|
if (statsDelta) {
|
|
3991
|
-
console.log(` ${
|
|
4036
|
+
console.log(` ${import_chalk16.default.dim(statsDelta)}`);
|
|
3992
4037
|
}
|
|
3993
4038
|
}
|
|
3994
4039
|
if (options?.interactive) {
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
const decision = await
|
|
4040
|
+
clack14.intro("viberails sync (interactive)");
|
|
4041
|
+
clack14.note(formatRulesText(merged).join("\n"), "Rules after sync");
|
|
4042
|
+
const decision = await clack14.select({
|
|
3998
4043
|
message: "How would you like to proceed?",
|
|
3999
4044
|
options: [
|
|
4000
4045
|
{ value: "accept", label: "Accept changes" },
|
|
@@ -4004,7 +4049,7 @@ ${import_chalk14.default.bold("Changes:")}`);
|
|
|
4004
4049
|
});
|
|
4005
4050
|
assertNotCancelled(decision);
|
|
4006
4051
|
if (decision === "cancel") {
|
|
4007
|
-
|
|
4052
|
+
clack14.outro("Sync cancelled. No files were written.");
|
|
4008
4053
|
return;
|
|
4009
4054
|
}
|
|
4010
4055
|
if (decision === "customize") {
|
|
@@ -4028,8 +4073,8 @@ ${import_chalk14.default.bold("Changes:")}`);
|
|
|
4028
4073
|
fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
4029
4074
|
`);
|
|
4030
4075
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4031
|
-
|
|
4032
|
-
|
|
4076
|
+
clack14.log.success("Updated config with your customizations.");
|
|
4077
|
+
clack14.outro("Done! Run viberails check to verify.");
|
|
4033
4078
|
return;
|
|
4034
4079
|
}
|
|
4035
4080
|
}
|
|
@@ -4037,18 +4082,18 @@ ${import_chalk14.default.bold("Changes:")}`);
|
|
|
4037
4082
|
`);
|
|
4038
4083
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4039
4084
|
console.log(`
|
|
4040
|
-
${
|
|
4085
|
+
${import_chalk16.default.bold("Synced:")}`);
|
|
4041
4086
|
if (configChanged) {
|
|
4042
|
-
console.log(` ${
|
|
4087
|
+
console.log(` ${import_chalk16.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
4043
4088
|
} else {
|
|
4044
|
-
console.log(` ${
|
|
4089
|
+
console.log(` ${import_chalk16.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
4045
4090
|
}
|
|
4046
|
-
console.log(` ${
|
|
4047
|
-
console.log(` ${
|
|
4091
|
+
console.log(` ${import_chalk16.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
4092
|
+
console.log(` ${import_chalk16.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
4048
4093
|
}
|
|
4049
4094
|
|
|
4050
4095
|
// src/index.ts
|
|
4051
|
-
var VERSION = "0.6.
|
|
4096
|
+
var VERSION = "0.6.8";
|
|
4052
4097
|
var program = new import_commander.Command();
|
|
4053
4098
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4054
4099
|
program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {
|
|
@@ -4056,7 +4101,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
4056
4101
|
await initCommand(options);
|
|
4057
4102
|
} catch (err) {
|
|
4058
4103
|
const message = err instanceof Error ? err.message : String(err);
|
|
4059
|
-
console.error(`${
|
|
4104
|
+
console.error(`${import_chalk17.default.red("Error:")} ${message}`);
|
|
4060
4105
|
process.exit(1);
|
|
4061
4106
|
}
|
|
4062
4107
|
});
|
|
@@ -4065,7 +4110,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
4065
4110
|
await syncCommand(options);
|
|
4066
4111
|
} catch (err) {
|
|
4067
4112
|
const message = err instanceof Error ? err.message : String(err);
|
|
4068
|
-
console.error(`${
|
|
4113
|
+
console.error(`${import_chalk17.default.red("Error:")} ${message}`);
|
|
4069
4114
|
process.exit(1);
|
|
4070
4115
|
}
|
|
4071
4116
|
});
|
|
@@ -4074,7 +4119,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
4074
4119
|
await configCommand(options);
|
|
4075
4120
|
} catch (err) {
|
|
4076
4121
|
const message = err instanceof Error ? err.message : String(err);
|
|
4077
|
-
console.error(`${
|
|
4122
|
+
console.error(`${import_chalk17.default.red("Error:")} ${message}`);
|
|
4078
4123
|
process.exit(1);
|
|
4079
4124
|
}
|
|
4080
4125
|
});
|
|
@@ -4095,7 +4140,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
4095
4140
|
process.exit(exitCode);
|
|
4096
4141
|
} catch (err) {
|
|
4097
4142
|
const message = err instanceof Error ? err.message : String(err);
|
|
4098
|
-
console.error(`${
|
|
4143
|
+
console.error(`${import_chalk17.default.red("Error:")} ${message}`);
|
|
4099
4144
|
process.exit(1);
|
|
4100
4145
|
}
|
|
4101
4146
|
}
|
|
@@ -4106,7 +4151,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
4106
4151
|
process.exit(exitCode);
|
|
4107
4152
|
} catch (err) {
|
|
4108
4153
|
const message = err instanceof Error ? err.message : String(err);
|
|
4109
|
-
console.error(`${
|
|
4154
|
+
console.error(`${import_chalk17.default.red("Error:")} ${message}`);
|
|
4110
4155
|
process.exit(1);
|
|
4111
4156
|
}
|
|
4112
4157
|
});
|
|
@@ -4115,7 +4160,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
4115
4160
|
await boundariesCommand(options);
|
|
4116
4161
|
} catch (err) {
|
|
4117
4162
|
const message = err instanceof Error ? err.message : String(err);
|
|
4118
|
-
console.error(`${
|
|
4163
|
+
console.error(`${import_chalk17.default.red("Error:")} ${message}`);
|
|
4119
4164
|
process.exit(1);
|
|
4120
4165
|
}
|
|
4121
4166
|
});
|