viberails 0.6.6 → 0.6.7
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 +330 -292
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +330 -292
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
VERSION: () => VERSION
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
-
var
|
|
37
|
+
var import_chalk16 = __toESM(require("chalk"), 1);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/boundaries.ts
|
|
@@ -84,6 +84,9 @@ 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);
|
|
@@ -115,8 +118,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
115
118
|
{ value: "back", label: "Back" }
|
|
116
119
|
]
|
|
117
120
|
});
|
|
118
|
-
|
|
119
|
-
if (choice === "back") return;
|
|
121
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
120
122
|
if (choice === "maxFileLines") {
|
|
121
123
|
const result = await clack.text({
|
|
122
124
|
message: "Maximum lines per source file?",
|
|
@@ -127,7 +129,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
127
129
|
if (Number.isNaN(n) || n < 1) return "Enter a positive number";
|
|
128
130
|
}
|
|
129
131
|
});
|
|
130
|
-
|
|
132
|
+
if (isCancelled(result)) continue;
|
|
131
133
|
state.maxFileLines = Number.parseInt(result, 10);
|
|
132
134
|
}
|
|
133
135
|
if (choice === "maxTestFileLines") {
|
|
@@ -140,7 +142,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
140
142
|
if (Number.isNaN(n) || n < 0) return "Enter a number (0 or positive)";
|
|
141
143
|
}
|
|
142
144
|
});
|
|
143
|
-
|
|
145
|
+
if (isCancelled(result)) continue;
|
|
144
146
|
state.maxTestFileLines = Number.parseInt(result, 10);
|
|
145
147
|
}
|
|
146
148
|
}
|
|
@@ -158,42 +160,41 @@ async function promptNamingMenu(state) {
|
|
|
158
160
|
options.push({
|
|
159
161
|
value: "fileNaming",
|
|
160
162
|
label: "File naming convention",
|
|
161
|
-
hint: state.fileNamingValue ??
|
|
163
|
+
hint: state.fileNamingValue ?? HINT_NOT_SET
|
|
162
164
|
});
|
|
163
165
|
}
|
|
164
166
|
options.push(
|
|
165
167
|
{
|
|
166
168
|
value: "componentNaming",
|
|
167
169
|
label: "Component naming",
|
|
168
|
-
hint: state.componentNaming ??
|
|
170
|
+
hint: state.componentNaming ?? HINT_NOT_SET
|
|
169
171
|
},
|
|
170
172
|
{
|
|
171
173
|
value: "hookNaming",
|
|
172
174
|
label: "Hook naming",
|
|
173
|
-
hint: state.hookNaming ??
|
|
175
|
+
hint: state.hookNaming ?? HINT_NOT_SET
|
|
174
176
|
},
|
|
175
177
|
{
|
|
176
178
|
value: "importAlias",
|
|
177
179
|
label: "Import alias",
|
|
178
|
-
hint: state.importAlias ??
|
|
180
|
+
hint: state.importAlias ?? HINT_NOT_SET
|
|
179
181
|
},
|
|
180
182
|
{ value: "back", label: "Back" }
|
|
181
183
|
);
|
|
182
184
|
const choice = await clack.select({ message: "Naming & conventions", options });
|
|
183
|
-
|
|
184
|
-
if (choice === "back") return;
|
|
185
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
185
186
|
if (choice === "enforceNaming") {
|
|
186
187
|
const result = await clack.confirm({
|
|
187
188
|
message: state.fileNamingValue ? `Enforce file naming? (detected: ${state.fileNamingValue})` : "Enforce file naming?",
|
|
188
189
|
initialValue: state.enforceNaming
|
|
189
190
|
});
|
|
190
|
-
|
|
191
|
+
if (isCancelled(result)) continue;
|
|
191
192
|
if (result && !state.fileNamingValue) {
|
|
192
193
|
const selected = await clack.select({
|
|
193
194
|
message: "Which file naming convention should be enforced?",
|
|
194
195
|
options: [...FILE_NAMING_OPTIONS]
|
|
195
196
|
});
|
|
196
|
-
|
|
197
|
+
if (isCancelled(selected)) continue;
|
|
197
198
|
state.fileNamingValue = selected;
|
|
198
199
|
}
|
|
199
200
|
state.enforceNaming = result;
|
|
@@ -204,7 +205,7 @@ async function promptNamingMenu(state) {
|
|
|
204
205
|
options: [...FILE_NAMING_OPTIONS],
|
|
205
206
|
initialValue: state.fileNamingValue
|
|
206
207
|
});
|
|
207
|
-
|
|
208
|
+
if (isCancelled(selected)) continue;
|
|
208
209
|
state.fileNamingValue = selected;
|
|
209
210
|
}
|
|
210
211
|
if (choice === "componentNaming") {
|
|
@@ -216,7 +217,7 @@ async function promptNamingMenu(state) {
|
|
|
216
217
|
],
|
|
217
218
|
initialValue: state.componentNaming ?? SENTINEL_CLEAR
|
|
218
219
|
});
|
|
219
|
-
|
|
220
|
+
if (isCancelled(selected)) continue;
|
|
220
221
|
state.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
221
222
|
}
|
|
222
223
|
if (choice === "hookNaming") {
|
|
@@ -228,7 +229,7 @@ async function promptNamingMenu(state) {
|
|
|
228
229
|
],
|
|
229
230
|
initialValue: state.hookNaming ?? SENTINEL_CLEAR
|
|
230
231
|
});
|
|
231
|
-
|
|
232
|
+
if (isCancelled(selected)) continue;
|
|
232
233
|
state.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
233
234
|
}
|
|
234
235
|
if (choice === "importAlias") {
|
|
@@ -242,7 +243,7 @@ async function promptNamingMenu(state) {
|
|
|
242
243
|
],
|
|
243
244
|
initialValue: state.importAlias ?? SENTINEL_CLEAR
|
|
244
245
|
});
|
|
245
|
-
|
|
246
|
+
if (isCancelled(selected)) continue;
|
|
246
247
|
if (selected === SENTINEL_CLEAR) {
|
|
247
248
|
state.importAlias = void 0;
|
|
248
249
|
} else if (selected === SENTINEL_CUSTOM) {
|
|
@@ -256,7 +257,7 @@ async function promptNamingMenu(state) {
|
|
|
256
257
|
return "Must match pattern like @/*, ~/*, or #src/*";
|
|
257
258
|
}
|
|
258
259
|
});
|
|
259
|
-
|
|
260
|
+
if (isCancelled(result)) continue;
|
|
260
261
|
state.importAlias = result.trim();
|
|
261
262
|
} else {
|
|
262
263
|
state.importAlias = selected;
|
|
@@ -294,14 +295,13 @@ async function promptTestingMenu(state) {
|
|
|
294
295
|
}
|
|
295
296
|
options.push({ value: "back", label: "Back" });
|
|
296
297
|
const choice = await clack.select({ message: "Testing & coverage", options });
|
|
297
|
-
|
|
298
|
-
if (choice === "back") return;
|
|
298
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
299
299
|
if (choice === "enforceMissingTests") {
|
|
300
300
|
const result = await clack.confirm({
|
|
301
301
|
message: "Require every source file to have a corresponding test file?",
|
|
302
302
|
initialValue: state.enforceMissingTests
|
|
303
303
|
});
|
|
304
|
-
|
|
304
|
+
if (isCancelled(result)) continue;
|
|
305
305
|
state.enforceMissingTests = result;
|
|
306
306
|
}
|
|
307
307
|
if (choice === "testCoverage") {
|
|
@@ -314,7 +314,7 @@ async function promptTestingMenu(state) {
|
|
|
314
314
|
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
315
315
|
}
|
|
316
316
|
});
|
|
317
|
-
|
|
317
|
+
if (isCancelled(result)) continue;
|
|
318
318
|
state.testCoverage = Number.parseInt(result, 10);
|
|
319
319
|
}
|
|
320
320
|
if (choice === "coverageSummaryPath") {
|
|
@@ -325,7 +325,7 @@ async function promptTestingMenu(state) {
|
|
|
325
325
|
if (typeof v !== "string" || v.trim().length === 0) return "Path cannot be empty";
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
|
-
|
|
328
|
+
if (isCancelled(result)) continue;
|
|
329
329
|
state.coverageSummaryPath = result.trim();
|
|
330
330
|
}
|
|
331
331
|
if (choice === "coverageCommand") {
|
|
@@ -334,7 +334,7 @@ async function promptTestingMenu(state) {
|
|
|
334
334
|
initialValue: state.coverageCommand ?? "",
|
|
335
335
|
placeholder: "(auto-detect from package.json test runner)"
|
|
336
336
|
});
|
|
337
|
-
|
|
337
|
+
if (isCancelled(result)) continue;
|
|
338
338
|
const trimmed = result.trim();
|
|
339
339
|
state.coverageCommand = trimmed.length > 0 ? trimmed : void 0;
|
|
340
340
|
}
|
|
@@ -378,7 +378,7 @@ function packageOverrideHint(pkg, defaults) {
|
|
|
378
378
|
const hasCommandOverride = pkg.coverage?.command !== void 0 && pkg.coverage.command !== defaultCommand;
|
|
379
379
|
if (hasSummaryOverride) tags.push("summary override");
|
|
380
380
|
if (hasCommandOverride) tags.push("command override");
|
|
381
|
-
return tags.length > 0 ? tags.join(", ") :
|
|
381
|
+
return tags.length > 0 ? tags.join(", ") : HINT_NO_OVERRIDES;
|
|
382
382
|
}
|
|
383
383
|
async function promptPackageOverrides(packages, defaults) {
|
|
384
384
|
const editablePackages = packages.filter((pkg) => pkg.path !== ".");
|
|
@@ -395,8 +395,7 @@ async function promptPackageOverrides(packages, defaults) {
|
|
|
395
395
|
{ value: SENTINEL_DONE, label: "Done" }
|
|
396
396
|
]
|
|
397
397
|
});
|
|
398
|
-
|
|
399
|
-
if (selectedPath === SENTINEL_DONE) break;
|
|
398
|
+
if (isCancelled(selectedPath) || selectedPath === SENTINEL_DONE) break;
|
|
400
399
|
const target = editablePackages.find((pkg) => pkg.path === selectedPath);
|
|
401
400
|
if (!target) continue;
|
|
402
401
|
await promptSinglePackageOverrides(target, defaults);
|
|
@@ -410,11 +409,11 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
410
409
|
const effectiveMaxLines = target.rules?.maxFileLines ?? defaults.maxFileLines;
|
|
411
410
|
const effectiveCoverage = target.rules?.testCoverage ?? defaults.testCoverage;
|
|
412
411
|
const effectiveSummary = target.coverage?.summaryPath ?? defaults.coverageSummaryPath;
|
|
413
|
-
const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ??
|
|
412
|
+
const effectiveCommand = target.coverage?.command ?? defaults.coverageCommand ?? HINT_AUTO_DETECT;
|
|
414
413
|
const hasNamingOverride = target.conventions?.fileNaming !== void 0 && target.conventions.fileNaming !== defaults.fileNamingValue;
|
|
415
414
|
const hasMaxLinesOverride = target.rules?.maxFileLines !== void 0 && target.rules.maxFileLines !== defaults.maxFileLines;
|
|
416
|
-
const namingHint = hasNamingOverride ? String(effectiveNaming) : `
|
|
417
|
-
const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `
|
|
415
|
+
const namingHint = hasNamingOverride ? String(effectiveNaming) : `inherits: ${effectiveNaming ?? "not set"}`;
|
|
416
|
+
const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `inherits: ${effectiveMaxLines}`;
|
|
418
417
|
const choice = await clack2.select({
|
|
419
418
|
message: `Edit overrides for ${target.path}`,
|
|
420
419
|
options: [
|
|
@@ -427,8 +426,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
427
426
|
{ value: "back", label: "Back to package list" }
|
|
428
427
|
]
|
|
429
428
|
});
|
|
430
|
-
|
|
431
|
-
if (choice === "back") break;
|
|
429
|
+
if (isCancelled(choice) || choice === "back") break;
|
|
432
430
|
if (choice === "fileNaming") {
|
|
433
431
|
const selected = await clack2.select({
|
|
434
432
|
message: `File naming for ${target.path}`,
|
|
@@ -442,7 +440,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
442
440
|
],
|
|
443
441
|
initialValue: target.conventions?.fileNaming ?? SENTINEL_INHERIT
|
|
444
442
|
});
|
|
445
|
-
|
|
443
|
+
if (isCancelled(selected)) continue;
|
|
446
444
|
if (selected === SENTINEL_INHERIT) {
|
|
447
445
|
if (target.conventions) delete target.conventions.fileNaming;
|
|
448
446
|
} else if (selected === SENTINEL_NONE) {
|
|
@@ -457,7 +455,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
457
455
|
initialValue: target.rules?.maxFileLines !== void 0 ? String(target.rules.maxFileLines) : "",
|
|
458
456
|
placeholder: String(defaults.maxFileLines)
|
|
459
457
|
});
|
|
460
|
-
|
|
458
|
+
if (isCancelled(result)) continue;
|
|
461
459
|
const value = result.trim();
|
|
462
460
|
if (value.length === 0 || Number.parseInt(value, 10) === defaults.maxFileLines) {
|
|
463
461
|
if (target.rules) delete target.rules.maxFileLines;
|
|
@@ -475,7 +473,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
475
473
|
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
476
474
|
}
|
|
477
475
|
});
|
|
478
|
-
|
|
476
|
+
if (isCancelled(result)) continue;
|
|
479
477
|
const nextCoverage = Number.parseInt(result, 10);
|
|
480
478
|
if (nextCoverage === defaults.testCoverage) {
|
|
481
479
|
if (target.rules) delete target.rules.testCoverage;
|
|
@@ -489,7 +487,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
489
487
|
initialValue: target.coverage?.summaryPath !== void 0 ? target.coverage.summaryPath : "",
|
|
490
488
|
placeholder: defaults.coverageSummaryPath
|
|
491
489
|
});
|
|
492
|
-
|
|
490
|
+
if (isCancelled(result)) continue;
|
|
493
491
|
const value = result.trim();
|
|
494
492
|
if (value.length === 0 || value === defaults.coverageSummaryPath) {
|
|
495
493
|
if (target.coverage) delete target.coverage.summaryPath;
|
|
@@ -503,7 +501,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
503
501
|
initialValue: target.coverage?.command !== void 0 ? target.coverage.command : "",
|
|
504
502
|
placeholder: defaults.coverageCommand ?? "(auto-detect from package.json test runner)"
|
|
505
503
|
});
|
|
506
|
-
|
|
504
|
+
if (isCancelled(result)) continue;
|
|
507
505
|
const value = result.trim();
|
|
508
506
|
const defaultCommand = defaults.coverageCommand ?? "";
|
|
509
507
|
if (value.length === 0 || value === defaultCommand) {
|
|
@@ -674,6 +672,9 @@ function assertNotCancelled(value) {
|
|
|
674
672
|
process.exit(0);
|
|
675
673
|
}
|
|
676
674
|
}
|
|
675
|
+
function isCancelled(value) {
|
|
676
|
+
return clack5.isCancel(value);
|
|
677
|
+
}
|
|
677
678
|
async function confirm3(message) {
|
|
678
679
|
const result = await clack5.confirm({ message, initialValue: true });
|
|
679
680
|
assertNotCancelled(result);
|
|
@@ -1412,9 +1413,9 @@ async function checkCommand(options, cwd) {
|
|
|
1412
1413
|
}
|
|
1413
1414
|
const violations = [];
|
|
1414
1415
|
const severity = options.enforce ? "error" : "warn";
|
|
1415
|
-
const
|
|
1416
|
+
const log9 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk3.default.dim(msg)) : () => {
|
|
1416
1417
|
};
|
|
1417
|
-
|
|
1418
|
+
log9(" Checking files...");
|
|
1418
1419
|
for (const file of filesToCheck) {
|
|
1419
1420
|
const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
|
|
1420
1421
|
const relPath = path7.relative(projectRoot, absPath);
|
|
@@ -1447,9 +1448,9 @@ async function checkCommand(options, cwd) {
|
|
|
1447
1448
|
}
|
|
1448
1449
|
}
|
|
1449
1450
|
}
|
|
1450
|
-
|
|
1451
|
+
log9(" done\n");
|
|
1451
1452
|
if (!options.files) {
|
|
1452
|
-
|
|
1453
|
+
log9(" Checking missing tests...");
|
|
1453
1454
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
1454
1455
|
if (options.staged) {
|
|
1455
1456
|
const stagedSet = new Set(filesToCheck);
|
|
@@ -1462,14 +1463,14 @@ async function checkCommand(options, cwd) {
|
|
|
1462
1463
|
} else {
|
|
1463
1464
|
violations.push(...testViolations);
|
|
1464
1465
|
}
|
|
1465
|
-
|
|
1466
|
+
log9(" done\n");
|
|
1466
1467
|
}
|
|
1467
1468
|
if (!options.files && !options.staged && !options.diffBase) {
|
|
1468
|
-
|
|
1469
|
+
log9(" Running test coverage...\n");
|
|
1469
1470
|
const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
|
|
1470
1471
|
staged: options.staged,
|
|
1471
1472
|
enforce: options.enforce,
|
|
1472
|
-
onProgress: (pkg) =>
|
|
1473
|
+
onProgress: (pkg) => log9(` Coverage: ${pkg}...
|
|
1473
1474
|
`)
|
|
1474
1475
|
});
|
|
1475
1476
|
violations.push(...coverageViolations);
|
|
@@ -1494,7 +1495,7 @@ async function checkCommand(options, cwd) {
|
|
|
1494
1495
|
severity
|
|
1495
1496
|
});
|
|
1496
1497
|
}
|
|
1497
|
-
|
|
1498
|
+
log9(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
|
|
1498
1499
|
`);
|
|
1499
1500
|
}
|
|
1500
1501
|
if (options.format === "json") {
|
|
@@ -2776,10 +2777,10 @@ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imp
|
|
|
2776
2777
|
// src/commands/init.ts
|
|
2777
2778
|
var fs21 = __toESM(require("fs"), 1);
|
|
2778
2779
|
var path21 = __toESM(require("path"), 1);
|
|
2779
|
-
var
|
|
2780
|
+
var clack13 = __toESM(require("@clack/prompts"), 1);
|
|
2780
2781
|
var import_config9 = require("@viberails/config");
|
|
2781
2782
|
var import_scanner3 = require("@viberails/scanner");
|
|
2782
|
-
var
|
|
2783
|
+
var import_chalk14 = __toESM(require("chalk"), 1);
|
|
2783
2784
|
|
|
2784
2785
|
// src/utils/check-prerequisites.ts
|
|
2785
2786
|
var fs14 = __toESM(require("fs"), 1);
|
|
@@ -2887,6 +2888,9 @@ async function executeDeferredInstalls(projectRoot, installs) {
|
|
|
2887
2888
|
}
|
|
2888
2889
|
|
|
2889
2890
|
// src/utils/prompt-main-menu.ts
|
|
2891
|
+
var clack11 = __toESM(require("@clack/prompts"), 1);
|
|
2892
|
+
|
|
2893
|
+
// src/utils/prompt-main-menu-handlers.ts
|
|
2890
2894
|
var clack10 = __toESM(require("@clack/prompts"), 1);
|
|
2891
2895
|
|
|
2892
2896
|
// src/utils/prompt-integrations.ts
|
|
@@ -2980,140 +2984,7 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
|
|
|
2980
2984
|
};
|
|
2981
2985
|
}
|
|
2982
2986
|
|
|
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
|
|
2987
|
+
// src/utils/prompt-main-menu-handlers.ts
|
|
3117
2988
|
async function handleAdvancedNaming(config) {
|
|
3118
2989
|
const rootPkg = getRootPackage(config.packages);
|
|
3119
2990
|
const state = {
|
|
@@ -3141,51 +3012,6 @@ async function handleAdvancedNaming(config) {
|
|
|
3141
3012
|
rootPkg.conventions.hookNaming = state.hookNaming || void 0;
|
|
3142
3013
|
rootPkg.conventions.importAlias = state.importAlias || void 0;
|
|
3143
3014
|
}
|
|
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
3015
|
async function handleFileNaming(config, scanResult) {
|
|
3190
3016
|
const isMonorepo = config.packages.length > 1;
|
|
3191
3017
|
if (isMonorepo) {
|
|
@@ -3214,7 +3040,7 @@ async function handleFileNaming(config, scanResult) {
|
|
|
3214
3040
|
options: [...namingOptions, { value: SENTINEL_SKIP, label: "Don't enforce" }],
|
|
3215
3041
|
initialValue: rootPkg.conventions?.fileNaming ?? SENTINEL_SKIP
|
|
3216
3042
|
});
|
|
3217
|
-
|
|
3043
|
+
if (isCancelled(selected)) return;
|
|
3218
3044
|
if (selected === SENTINEL_SKIP) {
|
|
3219
3045
|
config.rules.enforceNaming = false;
|
|
3220
3046
|
if (rootPkg.conventions) delete rootPkg.conventions.fileNaming;
|
|
@@ -3229,12 +3055,15 @@ async function handleMissingTests(config) {
|
|
|
3229
3055
|
message: "Require every source file to have a test file?",
|
|
3230
3056
|
initialValue: config.rules.enforceMissingTests
|
|
3231
3057
|
});
|
|
3232
|
-
|
|
3058
|
+
if (isCancelled(result)) return;
|
|
3233
3059
|
config.rules.enforceMissingTests = result;
|
|
3234
3060
|
}
|
|
3235
3061
|
async function handleCoverage(config, state, opts) {
|
|
3236
3062
|
if (!opts.hasTestRunner) {
|
|
3237
|
-
clack10.
|
|
3063
|
+
clack10.note(
|
|
3064
|
+
"No test runner (vitest, jest, etc.) was detected.\nInstall one, then re-run viberails init to configure coverage.",
|
|
3065
|
+
"Coverage inactive"
|
|
3066
|
+
);
|
|
3238
3067
|
return;
|
|
3239
3068
|
}
|
|
3240
3069
|
const planned = planCoverageInstall(opts.coveragePrereqs);
|
|
@@ -3255,7 +3084,7 @@ async function handleCoverage(config, state, opts) {
|
|
|
3255
3084
|
}
|
|
3256
3085
|
]
|
|
3257
3086
|
});
|
|
3258
|
-
|
|
3087
|
+
if (isCancelled(choice)) return;
|
|
3259
3088
|
state.deferredInstalls = state.deferredInstalls.filter((d) => d.command !== planned.command);
|
|
3260
3089
|
if (choice === "install") {
|
|
3261
3090
|
planned.onFailure = () => {
|
|
@@ -3276,7 +3105,7 @@ async function handleCoverage(config, state, opts) {
|
|
|
3276
3105
|
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
3277
3106
|
}
|
|
3278
3107
|
});
|
|
3279
|
-
|
|
3108
|
+
if (isCancelled(result)) return;
|
|
3280
3109
|
config.rules.testCoverage = Number.parseInt(result, 10);
|
|
3281
3110
|
}
|
|
3282
3111
|
async function handlePackageOverrides(config) {
|
|
@@ -3295,7 +3124,7 @@ async function handleBoundaries(config, state, opts) {
|
|
|
3295
3124
|
message: "Infer boundary rules from current import patterns?",
|
|
3296
3125
|
initialValue: false
|
|
3297
3126
|
});
|
|
3298
|
-
|
|
3127
|
+
if (isCancelled(shouldInfer)) return;
|
|
3299
3128
|
state.visited.boundaries = true;
|
|
3300
3129
|
if (!shouldInfer) {
|
|
3301
3130
|
config.rules.enforceBoundaries = false;
|
|
@@ -3338,6 +3167,215 @@ async function handleIntegrations(state, opts) {
|
|
|
3338
3167
|
}
|
|
3339
3168
|
}
|
|
3340
3169
|
|
|
3170
|
+
// src/utils/prompt-main-menu-hints.ts
|
|
3171
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
3172
|
+
function fileLimitsHint(config) {
|
|
3173
|
+
const max = config.rules.maxFileLines;
|
|
3174
|
+
const test = config.rules.maxTestFileLines;
|
|
3175
|
+
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
3176
|
+
}
|
|
3177
|
+
function fileNamingHint(config, scanResult) {
|
|
3178
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3179
|
+
const naming = rootPkg.conventions?.fileNaming;
|
|
3180
|
+
if (!config.rules.enforceNaming) return "not enforced";
|
|
3181
|
+
if (naming) {
|
|
3182
|
+
const detected = scanResult.packages.some(
|
|
3183
|
+
(p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
|
|
3184
|
+
);
|
|
3185
|
+
return detected ? `${naming} (detected)` : naming;
|
|
3186
|
+
}
|
|
3187
|
+
return "mixed \u2014 will not enforce if skipped";
|
|
3188
|
+
}
|
|
3189
|
+
function fileNamingStatus(config) {
|
|
3190
|
+
if (!config.rules.enforceNaming) return "disabled";
|
|
3191
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3192
|
+
return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
|
|
3193
|
+
}
|
|
3194
|
+
function missingTestsHint(config) {
|
|
3195
|
+
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3196
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3197
|
+
const pattern = rootPkg.structure?.testPattern;
|
|
3198
|
+
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3199
|
+
}
|
|
3200
|
+
function coverageHint(config, hasTestRunner) {
|
|
3201
|
+
if (config.rules.testCoverage === 0) return "disabled";
|
|
3202
|
+
if (!hasTestRunner)
|
|
3203
|
+
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3204
|
+
const isMonorepo = config.packages.length > 1;
|
|
3205
|
+
if (isMonorepo) {
|
|
3206
|
+
const withCov = config.packages.filter(
|
|
3207
|
+
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3208
|
+
);
|
|
3209
|
+
const exempt = config.packages.length - withCov.length;
|
|
3210
|
+
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3211
|
+
}
|
|
3212
|
+
return `${config.rules.testCoverage}%`;
|
|
3213
|
+
}
|
|
3214
|
+
function advancedNamingHint(config) {
|
|
3215
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3216
|
+
const parts = [];
|
|
3217
|
+
if (rootPkg.conventions?.componentNaming)
|
|
3218
|
+
parts.push(`${rootPkg.conventions.componentNaming} components`);
|
|
3219
|
+
if (rootPkg.conventions?.hookNaming) parts.push(`${rootPkg.conventions.hookNaming} hooks`);
|
|
3220
|
+
if (rootPkg.conventions?.importAlias) parts.push(rootPkg.conventions.importAlias);
|
|
3221
|
+
return parts.length > 0 ? parts.join(", ") : "component, hook, and alias conventions";
|
|
3222
|
+
}
|
|
3223
|
+
function integrationsHint(state) {
|
|
3224
|
+
if (!state.visited.integrations || !state.integrations)
|
|
3225
|
+
return "not configured \u2014 select to set up";
|
|
3226
|
+
const items = [];
|
|
3227
|
+
if (state.integrations.preCommitHook) items.push("pre-commit");
|
|
3228
|
+
if (state.integrations.typecheckHook) items.push("typecheck");
|
|
3229
|
+
if (state.integrations.lintHook) items.push("lint");
|
|
3230
|
+
if (state.integrations.claudeCodeHook) items.push("Claude");
|
|
3231
|
+
if (state.integrations.claudeMdRef) items.push("CLAUDE.md");
|
|
3232
|
+
if (state.integrations.githubAction) items.push("CI");
|
|
3233
|
+
return items.length > 0 ? items.join(" \xB7 ") : "none selected";
|
|
3234
|
+
}
|
|
3235
|
+
function packageOverridesHint(config) {
|
|
3236
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3237
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3238
|
+
const customized = editable.filter(
|
|
3239
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3240
|
+
).length;
|
|
3241
|
+
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3242
|
+
}
|
|
3243
|
+
function boundariesHint(config, state) {
|
|
3244
|
+
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3245
|
+
const deny = config.boundaries?.deny;
|
|
3246
|
+
if (!deny) return "enabled";
|
|
3247
|
+
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3248
|
+
const pkgCount = Object.keys(deny).length;
|
|
3249
|
+
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3250
|
+
}
|
|
3251
|
+
function advancedNamingStatus(config) {
|
|
3252
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3253
|
+
const hasAny = !!rootPkg.conventions?.componentNaming || !!rootPkg.conventions?.hookNaming || !!rootPkg.conventions?.importAlias;
|
|
3254
|
+
return hasAny ? "ok" : "unconfigured";
|
|
3255
|
+
}
|
|
3256
|
+
function packageOverridesStatus(config) {
|
|
3257
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3258
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3259
|
+
const customized = editable.some(
|
|
3260
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3261
|
+
);
|
|
3262
|
+
return customized ? "ok" : "unconfigured";
|
|
3263
|
+
}
|
|
3264
|
+
function statusIcon(status) {
|
|
3265
|
+
if (status === "ok") return import_chalk10.default.green("\u2713");
|
|
3266
|
+
if (status === "needs-input") return import_chalk10.default.yellow("?");
|
|
3267
|
+
if (status === "unconfigured") return import_chalk10.default.dim("-");
|
|
3268
|
+
return import_chalk10.default.yellow("~");
|
|
3269
|
+
}
|
|
3270
|
+
function buildMainMenuOptions(config, scanResult, state) {
|
|
3271
|
+
const namingStatus = fileNamingStatus(config);
|
|
3272
|
+
const coverageStatus = config.rules.testCoverage === 0 ? "disabled" : !state.hasTestRunner ? "disabled" : "ok";
|
|
3273
|
+
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "disabled";
|
|
3274
|
+
const options = [
|
|
3275
|
+
{
|
|
3276
|
+
value: "fileLimits",
|
|
3277
|
+
label: `${statusIcon("ok")} Max file size`,
|
|
3278
|
+
hint: fileLimitsHint(config)
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
value: "fileNaming",
|
|
3282
|
+
label: `${statusIcon(namingStatus)} Default file naming`,
|
|
3283
|
+
hint: fileNamingHint(config, scanResult)
|
|
3284
|
+
},
|
|
3285
|
+
{
|
|
3286
|
+
value: "missingTests",
|
|
3287
|
+
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3288
|
+
hint: missingTestsHint(config)
|
|
3289
|
+
},
|
|
3290
|
+
{
|
|
3291
|
+
value: "coverage",
|
|
3292
|
+
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3293
|
+
hint: coverageHint(config, state.hasTestRunner)
|
|
3294
|
+
},
|
|
3295
|
+
{
|
|
3296
|
+
value: "advancedNaming",
|
|
3297
|
+
label: `${statusIcon(advancedNamingStatus(config))} Advanced naming`,
|
|
3298
|
+
hint: advancedNamingHint(config)
|
|
3299
|
+
}
|
|
3300
|
+
];
|
|
3301
|
+
if (config.packages.length > 1) {
|
|
3302
|
+
const bIcon = statusIcon(
|
|
3303
|
+
state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
|
|
3304
|
+
);
|
|
3305
|
+
const poIcon = statusIcon(packageOverridesStatus(config));
|
|
3306
|
+
options.push(
|
|
3307
|
+
{
|
|
3308
|
+
value: "packageOverrides",
|
|
3309
|
+
label: `${poIcon} Per-package overrides`,
|
|
3310
|
+
hint: packageOverridesHint(config)
|
|
3311
|
+
},
|
|
3312
|
+
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3313
|
+
);
|
|
3314
|
+
}
|
|
3315
|
+
const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
|
|
3316
|
+
options.push(
|
|
3317
|
+
{ value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
|
|
3318
|
+
{ value: "reset", label: " Reset all to defaults" },
|
|
3319
|
+
{ value: "review", label: " Review scan details" },
|
|
3320
|
+
{ value: "done", label: " Done \u2014 write config" }
|
|
3321
|
+
);
|
|
3322
|
+
return options;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
// src/utils/prompt-main-menu.ts
|
|
3326
|
+
async function promptMainMenu(config, scanResult, opts) {
|
|
3327
|
+
const originalConfig = structuredClone(config);
|
|
3328
|
+
const state = {
|
|
3329
|
+
visited: { integrations: false, boundaries: false },
|
|
3330
|
+
deferredInstalls: [],
|
|
3331
|
+
hasTestRunner: opts.hasTestRunner,
|
|
3332
|
+
hookManager: opts.hookManager
|
|
3333
|
+
};
|
|
3334
|
+
while (true) {
|
|
3335
|
+
const options = buildMainMenuOptions(config, scanResult, state);
|
|
3336
|
+
const choice = await clack11.select({ message: "Configure viberails", options });
|
|
3337
|
+
assertNotCancelled(choice);
|
|
3338
|
+
if (choice === "done") {
|
|
3339
|
+
if (config.rules.enforceNaming && !getRootPackage(config.packages).conventions?.fileNaming) {
|
|
3340
|
+
config.rules.enforceNaming = false;
|
|
3341
|
+
}
|
|
3342
|
+
break;
|
|
3343
|
+
}
|
|
3344
|
+
if (choice === "fileLimits") {
|
|
3345
|
+
const s = {
|
|
3346
|
+
maxFileLines: config.rules.maxFileLines,
|
|
3347
|
+
maxTestFileLines: config.rules.maxTestFileLines
|
|
3348
|
+
};
|
|
3349
|
+
await promptFileLimitsMenu(s);
|
|
3350
|
+
config.rules.maxFileLines = s.maxFileLines;
|
|
3351
|
+
config.rules.maxTestFileLines = s.maxTestFileLines;
|
|
3352
|
+
}
|
|
3353
|
+
if (choice === "fileNaming") await handleFileNaming(config, scanResult);
|
|
3354
|
+
if (choice === "missingTests") await handleMissingTests(config);
|
|
3355
|
+
if (choice === "coverage") await handleCoverage(config, state, opts);
|
|
3356
|
+
if (choice === "advancedNaming") await handleAdvancedNaming(config);
|
|
3357
|
+
if (choice === "packageOverrides") await handlePackageOverrides(config);
|
|
3358
|
+
if (choice === "boundaries") await handleBoundaries(config, state, opts);
|
|
3359
|
+
if (choice === "integrations") await handleIntegrations(state, opts);
|
|
3360
|
+
if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
|
|
3361
|
+
if (choice === "reset") {
|
|
3362
|
+
const confirmed = await clack11.confirm({
|
|
3363
|
+
message: "Reset all settings to scan-detected defaults?",
|
|
3364
|
+
initialValue: false
|
|
3365
|
+
});
|
|
3366
|
+
assertNotCancelled(confirmed);
|
|
3367
|
+
if (confirmed) {
|
|
3368
|
+
Object.assign(config, structuredClone(originalConfig));
|
|
3369
|
+
state.deferredInstalls = [];
|
|
3370
|
+
state.visited = { integrations: false, boundaries: false };
|
|
3371
|
+
state.integrations = void 0;
|
|
3372
|
+
clack11.log.info("Reset all settings to scan-detected defaults.");
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
return state;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3341
3379
|
// src/utils/update-gitignore.ts
|
|
3342
3380
|
var fs16 = __toESM(require("fs"), 1);
|
|
3343
3381
|
var path16 = __toESM(require("path"), 1);
|
|
@@ -3358,7 +3396,7 @@ function updateGitignore(projectRoot) {
|
|
|
3358
3396
|
// src/commands/init-hooks.ts
|
|
3359
3397
|
var fs18 = __toESM(require("fs"), 1);
|
|
3360
3398
|
var path18 = __toESM(require("path"), 1);
|
|
3361
|
-
var
|
|
3399
|
+
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
3362
3400
|
var import_yaml = require("yaml");
|
|
3363
3401
|
|
|
3364
3402
|
// src/commands/resolve-typecheck.ts
|
|
@@ -3403,13 +3441,13 @@ function setupPreCommitHook(projectRoot) {
|
|
|
3403
3441
|
const lefthookPath = path18.join(projectRoot, "lefthook.yml");
|
|
3404
3442
|
if (fs18.existsSync(lefthookPath)) {
|
|
3405
3443
|
addLefthookPreCommit(lefthookPath);
|
|
3406
|
-
console.log(` ${
|
|
3444
|
+
console.log(` ${import_chalk11.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
3407
3445
|
return "lefthook.yml";
|
|
3408
3446
|
}
|
|
3409
3447
|
const huskyDir = path18.join(projectRoot, ".husky");
|
|
3410
3448
|
if (fs18.existsSync(huskyDir)) {
|
|
3411
3449
|
writeHuskyPreCommit(huskyDir);
|
|
3412
|
-
console.log(` ${
|
|
3450
|
+
console.log(` ${import_chalk11.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
3413
3451
|
return ".husky/pre-commit";
|
|
3414
3452
|
}
|
|
3415
3453
|
const gitDir = path18.join(projectRoot, ".git");
|
|
@@ -3419,7 +3457,7 @@ function setupPreCommitHook(projectRoot) {
|
|
|
3419
3457
|
fs18.mkdirSync(hooksDir, { recursive: true });
|
|
3420
3458
|
}
|
|
3421
3459
|
writeGitHookPreCommit(hooksDir);
|
|
3422
|
-
console.log(` ${
|
|
3460
|
+
console.log(` ${import_chalk11.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
3423
3461
|
return ".git/hooks/pre-commit";
|
|
3424
3462
|
}
|
|
3425
3463
|
return void 0;
|
|
@@ -3480,9 +3518,9 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3480
3518
|
settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
|
|
3481
3519
|
} catch {
|
|
3482
3520
|
console.warn(
|
|
3483
|
-
` ${
|
|
3521
|
+
` ${import_chalk11.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
3484
3522
|
);
|
|
3485
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
3523
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk11.default.cyan("viberails init --force")}`);
|
|
3486
3524
|
return;
|
|
3487
3525
|
}
|
|
3488
3526
|
}
|
|
@@ -3505,7 +3543,7 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3505
3543
|
settings.hooks = hooks;
|
|
3506
3544
|
fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
3507
3545
|
`);
|
|
3508
|
-
console.log(` ${
|
|
3546
|
+
console.log(` ${import_chalk11.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
3509
3547
|
}
|
|
3510
3548
|
function setupClaudeMdReference(projectRoot) {
|
|
3511
3549
|
const claudeMdPath = path18.join(projectRoot, "CLAUDE.md");
|
|
@@ -3517,7 +3555,7 @@ function setupClaudeMdReference(projectRoot) {
|
|
|
3517
3555
|
const ref = "\n@.viberails/context.md\n";
|
|
3518
3556
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
3519
3557
|
fs18.writeFileSync(claudeMdPath, prefix + ref);
|
|
3520
|
-
console.log(` ${
|
|
3558
|
+
console.log(` ${import_chalk11.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
3521
3559
|
}
|
|
3522
3560
|
function setupGithubAction(projectRoot, packageManager, options) {
|
|
3523
3561
|
const workflowDir = path18.join(projectRoot, ".github", "workflows");
|
|
@@ -3603,7 +3641,7 @@ ${cmd}
|
|
|
3603
3641
|
// src/commands/init-hooks-extra.ts
|
|
3604
3642
|
var fs19 = __toESM(require("fs"), 1);
|
|
3605
3643
|
var path19 = __toESM(require("path"), 1);
|
|
3606
|
-
var
|
|
3644
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3607
3645
|
var import_yaml2 = require("yaml");
|
|
3608
3646
|
function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
|
|
3609
3647
|
const lefthookPath = path19.join(projectRoot, "lefthook.yml");
|
|
@@ -3663,12 +3701,12 @@ ${command}
|
|
|
3663
3701
|
function setupTypecheckHook(projectRoot, packageManager) {
|
|
3664
3702
|
const resolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3665
3703
|
if (!resolved.command) {
|
|
3666
|
-
console.log(` ${
|
|
3704
|
+
console.log(` ${import_chalk12.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
|
|
3667
3705
|
return void 0;
|
|
3668
3706
|
}
|
|
3669
3707
|
const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
|
|
3670
3708
|
if (target) {
|
|
3671
|
-
console.log(` ${
|
|
3709
|
+
console.log(` ${import_chalk12.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
|
|
3672
3710
|
}
|
|
3673
3711
|
return target;
|
|
3674
3712
|
}
|
|
@@ -3689,7 +3727,7 @@ function setupLintHook(projectRoot, linter) {
|
|
|
3689
3727
|
}
|
|
3690
3728
|
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
3691
3729
|
if (target) {
|
|
3692
|
-
console.log(` ${
|
|
3730
|
+
console.log(` ${import_chalk12.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
3693
3731
|
}
|
|
3694
3732
|
return target;
|
|
3695
3733
|
}
|
|
@@ -3698,7 +3736,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3698
3736
|
if (integrations.preCommitHook) {
|
|
3699
3737
|
const t = setupPreCommitHook(projectRoot);
|
|
3700
3738
|
if (t && opts.lefthookExpected && !t.includes("lefthook")) {
|
|
3701
|
-
console.log(` ${
|
|
3739
|
+
console.log(` ${import_chalk12.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
|
|
3702
3740
|
}
|
|
3703
3741
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
3704
3742
|
}
|
|
@@ -3731,10 +3769,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3731
3769
|
// src/commands/init-non-interactive.ts
|
|
3732
3770
|
var fs20 = __toESM(require("fs"), 1);
|
|
3733
3771
|
var path20 = __toESM(require("path"), 1);
|
|
3734
|
-
var
|
|
3772
|
+
var clack12 = __toESM(require("@clack/prompts"), 1);
|
|
3735
3773
|
var import_config8 = require("@viberails/config");
|
|
3736
3774
|
var import_scanner2 = require("@viberails/scanner");
|
|
3737
|
-
var
|
|
3775
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
3738
3776
|
|
|
3739
3777
|
// src/utils/filter-confidence.ts
|
|
3740
3778
|
function filterHighConfidence(conventions, meta) {
|
|
@@ -3755,7 +3793,7 @@ function getExemptedPackages(config) {
|
|
|
3755
3793
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3756
3794
|
}
|
|
3757
3795
|
async function initNonInteractive(projectRoot, configPath) {
|
|
3758
|
-
const s =
|
|
3796
|
+
const s = clack12.spinner();
|
|
3759
3797
|
s.start("Scanning project...");
|
|
3760
3798
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3761
3799
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
@@ -3770,11 +3808,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3770
3808
|
const exempted = getExemptedPackages(config);
|
|
3771
3809
|
if (exempted.length > 0) {
|
|
3772
3810
|
console.log(
|
|
3773
|
-
` ${
|
|
3811
|
+
` ${import_chalk13.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk13.default.dim("(types-only)")}`
|
|
3774
3812
|
);
|
|
3775
3813
|
}
|
|
3776
3814
|
if (config.packages.length > 1) {
|
|
3777
|
-
const bs =
|
|
3815
|
+
const bs = clack12.spinner();
|
|
3778
3816
|
bs.start("Building import graph...");
|
|
3779
3817
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3780
3818
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -3807,14 +3845,14 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3807
3845
|
const hookManager = detectHookManager(projectRoot);
|
|
3808
3846
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3809
3847
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3810
|
-
const ok =
|
|
3848
|
+
const ok = import_chalk13.default.green("\u2713");
|
|
3811
3849
|
const created = [
|
|
3812
3850
|
`${ok} ${path20.basename(configPath)}`,
|
|
3813
3851
|
`${ok} .viberails/context.md`,
|
|
3814
3852
|
`${ok} .viberails/scan-result.json`,
|
|
3815
3853
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3816
3854
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3817
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
3855
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk13.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3818
3856
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3819
3857
|
].filter(Boolean);
|
|
3820
3858
|
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
@@ -3839,8 +3877,8 @@ async function initCommand(options, cwd) {
|
|
|
3839
3877
|
return initInteractive(projectRoot, configPath, options);
|
|
3840
3878
|
}
|
|
3841
3879
|
console.log(
|
|
3842
|
-
`${
|
|
3843
|
-
Run ${
|
|
3880
|
+
`${import_chalk14.default.yellow("!")} viberails is already initialized.
|
|
3881
|
+
Run ${import_chalk14.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk14.default.cyan("viberails sync")} to update generated files, or ${import_chalk14.default.cyan("viberails init --force")} to replace it.`
|
|
3844
3882
|
);
|
|
3845
3883
|
return;
|
|
3846
3884
|
}
|
|
@@ -3848,11 +3886,11 @@ async function initCommand(options, cwd) {
|
|
|
3848
3886
|
await initInteractive(projectRoot, configPath, options);
|
|
3849
3887
|
}
|
|
3850
3888
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3851
|
-
|
|
3889
|
+
clack13.intro("viberails");
|
|
3852
3890
|
if (fs21.existsSync(configPath) && !options.force) {
|
|
3853
3891
|
const action = await promptExistingConfigAction(path21.basename(configPath));
|
|
3854
3892
|
if (action === "cancel") {
|
|
3855
|
-
|
|
3893
|
+
clack13.outro("Aborted. No files were written.");
|
|
3856
3894
|
return;
|
|
3857
3895
|
}
|
|
3858
3896
|
if (action === "edit") {
|
|
@@ -3866,23 +3904,23 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3866
3904
|
`${path21.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3867
3905
|
);
|
|
3868
3906
|
if (!replace) {
|
|
3869
|
-
|
|
3907
|
+
clack13.outro("Aborted. No files were written.");
|
|
3870
3908
|
return;
|
|
3871
3909
|
}
|
|
3872
3910
|
}
|
|
3873
|
-
const s =
|
|
3911
|
+
const s = clack13.spinner();
|
|
3874
3912
|
s.start("Scanning project...");
|
|
3875
3913
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3876
3914
|
const config = (0, import_config9.generateConfig)(scanResult);
|
|
3877
3915
|
s.stop("Scan complete");
|
|
3878
3916
|
if (scanResult.statistics.totalFiles === 0) {
|
|
3879
|
-
|
|
3917
|
+
clack13.log.warn(
|
|
3880
3918
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3881
3919
|
);
|
|
3882
3920
|
}
|
|
3883
3921
|
const hasTestRunner = !!scanResult.stack.testRunner;
|
|
3884
3922
|
if (!hasTestRunner) {
|
|
3885
|
-
|
|
3923
|
+
clack13.log.info(
|
|
3886
3924
|
"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
3925
|
);
|
|
3888
3926
|
}
|
|
@@ -3903,13 +3941,13 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3903
3941
|
});
|
|
3904
3942
|
const shouldWrite = await confirm3("Apply this setup?");
|
|
3905
3943
|
if (!shouldWrite) {
|
|
3906
|
-
|
|
3944
|
+
clack13.outro("Aborted. No files were written.");
|
|
3907
3945
|
return;
|
|
3908
3946
|
}
|
|
3909
3947
|
if (state.deferredInstalls.length > 0) {
|
|
3910
3948
|
await executeDeferredInstalls(projectRoot, state.deferredInstalls);
|
|
3911
3949
|
}
|
|
3912
|
-
const ws =
|
|
3950
|
+
const ws = clack13.spinner();
|
|
3913
3951
|
ws.start("Writing configuration...");
|
|
3914
3952
|
const compacted = (0, import_config9.compactConfig)(config);
|
|
3915
3953
|
fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
@@ -3917,10 +3955,10 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3917
3955
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
3918
3956
|
updateGitignore(projectRoot);
|
|
3919
3957
|
ws.stop("Configuration written");
|
|
3920
|
-
const ok =
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3958
|
+
const ok = import_chalk14.default.green("\u2713");
|
|
3959
|
+
clack13.log.step(`${ok} ${path21.basename(configPath)}`);
|
|
3960
|
+
clack13.log.step(`${ok} .viberails/context.md`);
|
|
3961
|
+
clack13.log.step(`${ok} .viberails/scan-result.json`);
|
|
3924
3962
|
if (state.visited.integrations && state.integrations) {
|
|
3925
3963
|
const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
|
|
3926
3964
|
setupSelectedIntegrations(projectRoot, state.integrations, {
|
|
@@ -3929,19 +3967,19 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3929
3967
|
lefthookExpected
|
|
3930
3968
|
});
|
|
3931
3969
|
}
|
|
3932
|
-
|
|
3970
|
+
clack13.outro(
|
|
3933
3971
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
3934
|
-
${
|
|
3972
|
+
${import_chalk14.default.dim("Tip: use")} ${import_chalk14.default.cyan("viberails check --enforce")} ${import_chalk14.default.dim("in CI to block PRs on violations.")}`
|
|
3935
3973
|
);
|
|
3936
3974
|
}
|
|
3937
3975
|
|
|
3938
3976
|
// src/commands/sync.ts
|
|
3939
3977
|
var fs22 = __toESM(require("fs"), 1);
|
|
3940
3978
|
var path22 = __toESM(require("path"), 1);
|
|
3941
|
-
var
|
|
3979
|
+
var clack14 = __toESM(require("@clack/prompts"), 1);
|
|
3942
3980
|
var import_config11 = require("@viberails/config");
|
|
3943
3981
|
var import_scanner4 = require("@viberails/scanner");
|
|
3944
|
-
var
|
|
3982
|
+
var import_chalk15 = __toESM(require("chalk"), 1);
|
|
3945
3983
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
3946
3984
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
3947
3985
|
function loadPreviousStats(projectRoot) {
|
|
@@ -3967,7 +4005,7 @@ async function syncCommand(options, cwd) {
|
|
|
3967
4005
|
const configPath = path22.join(projectRoot, CONFIG_FILE6);
|
|
3968
4006
|
const existing = await (0, import_config11.loadConfig)(configPath);
|
|
3969
4007
|
const previousStats = loadPreviousStats(projectRoot);
|
|
3970
|
-
const s =
|
|
4008
|
+
const s = clack14.spinner();
|
|
3971
4009
|
s.start("Scanning project...");
|
|
3972
4010
|
const scanResult = await (0, import_scanner4.scan)(projectRoot);
|
|
3973
4011
|
s.stop("Scan complete");
|
|
@@ -3982,19 +4020,19 @@ async function syncCommand(options, cwd) {
|
|
|
3982
4020
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
3983
4021
|
if (changes.length > 0 || statsDelta) {
|
|
3984
4022
|
console.log(`
|
|
3985
|
-
${
|
|
4023
|
+
${import_chalk15.default.bold("Changes:")}`);
|
|
3986
4024
|
for (const change of changes) {
|
|
3987
|
-
const icon = change.type === "removed" ?
|
|
4025
|
+
const icon = change.type === "removed" ? import_chalk15.default.red("-") : import_chalk15.default.green("+");
|
|
3988
4026
|
console.log(` ${icon} ${change.description}`);
|
|
3989
4027
|
}
|
|
3990
4028
|
if (statsDelta) {
|
|
3991
|
-
console.log(` ${
|
|
4029
|
+
console.log(` ${import_chalk15.default.dim(statsDelta)}`);
|
|
3992
4030
|
}
|
|
3993
4031
|
}
|
|
3994
4032
|
if (options?.interactive) {
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
const decision = await
|
|
4033
|
+
clack14.intro("viberails sync (interactive)");
|
|
4034
|
+
clack14.note(formatRulesText(merged).join("\n"), "Rules after sync");
|
|
4035
|
+
const decision = await clack14.select({
|
|
3998
4036
|
message: "How would you like to proceed?",
|
|
3999
4037
|
options: [
|
|
4000
4038
|
{ value: "accept", label: "Accept changes" },
|
|
@@ -4004,7 +4042,7 @@ ${import_chalk14.default.bold("Changes:")}`);
|
|
|
4004
4042
|
});
|
|
4005
4043
|
assertNotCancelled(decision);
|
|
4006
4044
|
if (decision === "cancel") {
|
|
4007
|
-
|
|
4045
|
+
clack14.outro("Sync cancelled. No files were written.");
|
|
4008
4046
|
return;
|
|
4009
4047
|
}
|
|
4010
4048
|
if (decision === "customize") {
|
|
@@ -4028,8 +4066,8 @@ ${import_chalk14.default.bold("Changes:")}`);
|
|
|
4028
4066
|
fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
4029
4067
|
`);
|
|
4030
4068
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4031
|
-
|
|
4032
|
-
|
|
4069
|
+
clack14.log.success("Updated config with your customizations.");
|
|
4070
|
+
clack14.outro("Done! Run viberails check to verify.");
|
|
4033
4071
|
return;
|
|
4034
4072
|
}
|
|
4035
4073
|
}
|
|
@@ -4037,18 +4075,18 @@ ${import_chalk14.default.bold("Changes:")}`);
|
|
|
4037
4075
|
`);
|
|
4038
4076
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4039
4077
|
console.log(`
|
|
4040
|
-
${
|
|
4078
|
+
${import_chalk15.default.bold("Synced:")}`);
|
|
4041
4079
|
if (configChanged) {
|
|
4042
|
-
console.log(` ${
|
|
4080
|
+
console.log(` ${import_chalk15.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
4043
4081
|
} else {
|
|
4044
|
-
console.log(` ${
|
|
4082
|
+
console.log(` ${import_chalk15.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
4045
4083
|
}
|
|
4046
|
-
console.log(` ${
|
|
4047
|
-
console.log(` ${
|
|
4084
|
+
console.log(` ${import_chalk15.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
4085
|
+
console.log(` ${import_chalk15.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
4048
4086
|
}
|
|
4049
4087
|
|
|
4050
4088
|
// src/index.ts
|
|
4051
|
-
var VERSION = "0.6.
|
|
4089
|
+
var VERSION = "0.6.7";
|
|
4052
4090
|
var program = new import_commander.Command();
|
|
4053
4091
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4054
4092
|
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 +4094,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
4056
4094
|
await initCommand(options);
|
|
4057
4095
|
} catch (err) {
|
|
4058
4096
|
const message = err instanceof Error ? err.message : String(err);
|
|
4059
|
-
console.error(`${
|
|
4097
|
+
console.error(`${import_chalk16.default.red("Error:")} ${message}`);
|
|
4060
4098
|
process.exit(1);
|
|
4061
4099
|
}
|
|
4062
4100
|
});
|
|
@@ -4065,7 +4103,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
4065
4103
|
await syncCommand(options);
|
|
4066
4104
|
} catch (err) {
|
|
4067
4105
|
const message = err instanceof Error ? err.message : String(err);
|
|
4068
|
-
console.error(`${
|
|
4106
|
+
console.error(`${import_chalk16.default.red("Error:")} ${message}`);
|
|
4069
4107
|
process.exit(1);
|
|
4070
4108
|
}
|
|
4071
4109
|
});
|
|
@@ -4074,7 +4112,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
4074
4112
|
await configCommand(options);
|
|
4075
4113
|
} catch (err) {
|
|
4076
4114
|
const message = err instanceof Error ? err.message : String(err);
|
|
4077
|
-
console.error(`${
|
|
4115
|
+
console.error(`${import_chalk16.default.red("Error:")} ${message}`);
|
|
4078
4116
|
process.exit(1);
|
|
4079
4117
|
}
|
|
4080
4118
|
});
|
|
@@ -4095,7 +4133,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
4095
4133
|
process.exit(exitCode);
|
|
4096
4134
|
} catch (err) {
|
|
4097
4135
|
const message = err instanceof Error ? err.message : String(err);
|
|
4098
|
-
console.error(`${
|
|
4136
|
+
console.error(`${import_chalk16.default.red("Error:")} ${message}`);
|
|
4099
4137
|
process.exit(1);
|
|
4100
4138
|
}
|
|
4101
4139
|
}
|
|
@@ -4106,7 +4144,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
4106
4144
|
process.exit(exitCode);
|
|
4107
4145
|
} catch (err) {
|
|
4108
4146
|
const message = err instanceof Error ? err.message : String(err);
|
|
4109
|
-
console.error(`${
|
|
4147
|
+
console.error(`${import_chalk16.default.red("Error:")} ${message}`);
|
|
4110
4148
|
process.exit(1);
|
|
4111
4149
|
}
|
|
4112
4150
|
});
|
|
@@ -4115,7 +4153,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
4115
4153
|
await boundariesCommand(options);
|
|
4116
4154
|
} catch (err) {
|
|
4117
4155
|
const message = err instanceof Error ? err.message : String(err);
|
|
4118
|
-
console.error(`${
|
|
4156
|
+
console.error(`${import_chalk16.default.red("Error:")} ${message}`);
|
|
4119
4157
|
process.exit(1);
|
|
4120
4158
|
}
|
|
4121
4159
|
});
|