viberails 0.6.10 → 0.6.11
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 +480 -286
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +480 -286
- 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_chalk20 = __toESM(require("chalk"), 1);
|
|
38
38
|
var import_commander = require("commander");
|
|
39
39
|
|
|
40
40
|
// src/commands/boundaries.ts
|
|
@@ -162,12 +162,12 @@ async function promptNamingMenu(state) {
|
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
value: "componentNaming",
|
|
165
|
-
label: `${state.componentNaming ? ok : unset} Component
|
|
165
|
+
label: `${state.componentNaming ? ok : unset} Component exports`,
|
|
166
166
|
hint: state.componentNaming ?? HINT_NOT_SET
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
value: "hookNaming",
|
|
170
|
-
label: `${state.hookNaming ? ok : unset} Hook
|
|
170
|
+
label: `${state.hookNaming ? ok : unset} Hook exports`,
|
|
171
171
|
hint: state.hookNaming ?? HINT_NOT_SET
|
|
172
172
|
},
|
|
173
173
|
{
|
|
@@ -204,7 +204,7 @@ async function promptNamingMenu(state) {
|
|
|
204
204
|
}
|
|
205
205
|
if (choice === "componentNaming") {
|
|
206
206
|
const selected = await clack.select({
|
|
207
|
-
message: "Component naming
|
|
207
|
+
message: "Component export naming (e.g. UserProfile)",
|
|
208
208
|
options: [
|
|
209
209
|
...COMPONENT_NAMING_OPTIONS,
|
|
210
210
|
{ value: SENTINEL_CLEAR, label: "Clear (no convention)" }
|
|
@@ -216,7 +216,7 @@ async function promptNamingMenu(state) {
|
|
|
216
216
|
}
|
|
217
217
|
if (choice === "hookNaming") {
|
|
218
218
|
const selected = await clack.select({
|
|
219
|
-
message: "Hook naming
|
|
219
|
+
message: "Hook export naming (e.g. useAuth)",
|
|
220
220
|
options: [
|
|
221
221
|
...HOOK_NAMING_OPTIONS,
|
|
222
222
|
{ value: SENTINEL_CLEAR, label: "Clear (no convention)" }
|
|
@@ -1452,9 +1452,9 @@ async function checkCommand(options, cwd) {
|
|
|
1452
1452
|
}
|
|
1453
1453
|
const violations = [];
|
|
1454
1454
|
const severity = options.enforce ? "error" : "warn";
|
|
1455
|
-
const
|
|
1455
|
+
const log10 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk5.default.dim(msg)) : () => {
|
|
1456
1456
|
};
|
|
1457
|
-
|
|
1457
|
+
log10(" Checking files...");
|
|
1458
1458
|
for (const file of filesToCheck) {
|
|
1459
1459
|
const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
|
|
1460
1460
|
const relPath = path7.relative(projectRoot, absPath);
|
|
@@ -1487,9 +1487,9 @@ async function checkCommand(options, cwd) {
|
|
|
1487
1487
|
}
|
|
1488
1488
|
}
|
|
1489
1489
|
}
|
|
1490
|
-
|
|
1490
|
+
log10(" done\n");
|
|
1491
1491
|
if (!options.files) {
|
|
1492
|
-
|
|
1492
|
+
log10(" Checking missing tests...");
|
|
1493
1493
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
1494
1494
|
if (options.staged) {
|
|
1495
1495
|
const stagedSet = new Set(filesToCheck);
|
|
@@ -1502,14 +1502,14 @@ async function checkCommand(options, cwd) {
|
|
|
1502
1502
|
} else {
|
|
1503
1503
|
violations.push(...testViolations);
|
|
1504
1504
|
}
|
|
1505
|
-
|
|
1505
|
+
log10(" done\n");
|
|
1506
1506
|
}
|
|
1507
1507
|
if (!options.files && !options.staged && !options.diffBase) {
|
|
1508
|
-
|
|
1508
|
+
log10(" Running test coverage...\n");
|
|
1509
1509
|
const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
|
|
1510
1510
|
staged: options.staged,
|
|
1511
1511
|
enforce: options.enforce,
|
|
1512
|
-
onProgress: (pkg) =>
|
|
1512
|
+
onProgress: (pkg) => log10(` Coverage: ${pkg}...
|
|
1513
1513
|
`)
|
|
1514
1514
|
});
|
|
1515
1515
|
violations.push(...coverageViolations);
|
|
@@ -1534,7 +1534,7 @@ async function checkCommand(options, cwd) {
|
|
|
1534
1534
|
severity
|
|
1535
1535
|
});
|
|
1536
1536
|
}
|
|
1537
|
-
|
|
1537
|
+
log10(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
|
|
1538
1538
|
`);
|
|
1539
1539
|
}
|
|
1540
1540
|
if (options.format === "json") {
|
|
@@ -2816,10 +2816,10 @@ ${import_chalk10.default.yellow("!")} No safe fixes to apply. Resolve aliased im
|
|
|
2816
2816
|
// src/commands/init.ts
|
|
2817
2817
|
var fs21 = __toESM(require("fs"), 1);
|
|
2818
2818
|
var path21 = __toESM(require("path"), 1);
|
|
2819
|
-
var
|
|
2819
|
+
var clack14 = __toESM(require("@clack/prompts"), 1);
|
|
2820
2820
|
var import_config9 = require("@viberails/config");
|
|
2821
2821
|
var import_scanner3 = require("@viberails/scanner");
|
|
2822
|
-
var
|
|
2822
|
+
var import_chalk18 = __toESM(require("chalk"), 1);
|
|
2823
2823
|
|
|
2824
2824
|
// src/utils/check-prerequisites.ts
|
|
2825
2825
|
var fs14 = __toESM(require("fs"), 1);
|
|
@@ -2926,15 +2926,7 @@ async function executeDeferredInstalls(projectRoot, installs) {
|
|
|
2926
2926
|
return successCount;
|
|
2927
2927
|
}
|
|
2928
2928
|
|
|
2929
|
-
// src/utils/prompt-main-menu.ts
|
|
2930
|
-
var clack11 = __toESM(require("@clack/prompts"), 1);
|
|
2931
|
-
|
|
2932
|
-
// src/utils/prompt-main-menu-handlers.ts
|
|
2933
|
-
var clack10 = __toESM(require("@clack/prompts"), 1);
|
|
2934
|
-
|
|
2935
2929
|
// src/utils/prompt-integrations.ts
|
|
2936
|
-
var fs15 = __toESM(require("fs"), 1);
|
|
2937
|
-
var path15 = __toESM(require("path"), 1);
|
|
2938
2930
|
var clack9 = __toESM(require("@clack/prompts"), 1);
|
|
2939
2931
|
function buildLefthookInstallCommand(pm, isWorkspace) {
|
|
2940
2932
|
if (pm === "yarn") return "yarn add -D lefthook";
|
|
@@ -2942,26 +2934,24 @@ function buildLefthookInstallCommand(pm, isWorkspace) {
|
|
|
2942
2934
|
if (pm === "npm") return "npm install -D lefthook";
|
|
2943
2935
|
return `${pm} add -D lefthook`;
|
|
2944
2936
|
}
|
|
2945
|
-
async function promptIntegrationsDeferred(hookManager, tools
|
|
2937
|
+
async function promptIntegrationsDeferred(hookManager, tools) {
|
|
2938
|
+
const hasHookManager = !!hookManager;
|
|
2946
2939
|
const options = [];
|
|
2947
|
-
const
|
|
2948
|
-
|
|
2949
|
-
const pm = packageManager ?? "npm";
|
|
2950
|
-
options.push({
|
|
2951
|
-
value: "installLefthook",
|
|
2952
|
-
label: "Install Lefthook",
|
|
2953
|
-
hint: `after final confirmation \u2014 ${buildLefthookInstallCommand(pm, isWorkspace)}`
|
|
2954
|
-
});
|
|
2955
|
-
}
|
|
2956
|
-
const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
|
|
2957
|
-
const hookHint = needsLefthook ? "uses Lefthook if installed above, otherwise local git hook" : "runs viberails checks when you commit";
|
|
2940
|
+
const hookLabel = hasHookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
|
|
2941
|
+
const hookHint = hasHookManager ? "runs viberails checks when you commit" : "local hook only \u2014 use lefthook or husky to commit hooks to repo";
|
|
2958
2942
|
options.push({ value: "preCommit", label: hookLabel, hint: hookHint });
|
|
2959
|
-
if (tools?.
|
|
2943
|
+
if (tools?.typecheckLabel) {
|
|
2960
2944
|
options.push({
|
|
2961
2945
|
value: "typecheck",
|
|
2962
|
-
label:
|
|
2946
|
+
label: `Typecheck (${tools.typecheckLabel})`,
|
|
2963
2947
|
hint: "pre-commit hook + CI check"
|
|
2964
2948
|
});
|
|
2949
|
+
} else if (tools?.isTypeScript) {
|
|
2950
|
+
options.push({
|
|
2951
|
+
value: "typecheck",
|
|
2952
|
+
label: "Typecheck",
|
|
2953
|
+
hint: "needs root tsconfig.json, typecheck script, or turbo task"
|
|
2954
|
+
});
|
|
2965
2955
|
}
|
|
2966
2956
|
if (tools?.linter) {
|
|
2967
2957
|
const linterName = tools.linter === "biome" ? "Biome" : "ESLint";
|
|
@@ -2988,7 +2978,12 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
|
|
|
2988
2978
|
hint: "blocks PRs that fail viberails check"
|
|
2989
2979
|
}
|
|
2990
2980
|
);
|
|
2991
|
-
const
|
|
2981
|
+
const hasTypecheck = !!tools?.typecheckLabel;
|
|
2982
|
+
const initialValues = options.filter((o) => {
|
|
2983
|
+
if (o.value === "preCommit" && !hasHookManager) return false;
|
|
2984
|
+
if (o.value === "typecheck" && !hasTypecheck) return false;
|
|
2985
|
+
return true;
|
|
2986
|
+
}).map((o) => o.value);
|
|
2992
2987
|
const result = await clack9.multiselect({
|
|
2993
2988
|
message: "Integrations",
|
|
2994
2989
|
options,
|
|
@@ -2996,20 +2991,6 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
|
|
|
2996
2991
|
required: false
|
|
2997
2992
|
});
|
|
2998
2993
|
assertNotCancelled(result);
|
|
2999
|
-
let lefthookInstall;
|
|
3000
|
-
if (needsLefthook && result.includes("installLefthook")) {
|
|
3001
|
-
const pm = packageManager ?? "npm";
|
|
3002
|
-
lefthookInstall = {
|
|
3003
|
-
label: "Lefthook",
|
|
3004
|
-
command: buildLefthookInstallCommand(pm, isWorkspace),
|
|
3005
|
-
onSuccess: projectRoot ? () => {
|
|
3006
|
-
const ymlPath = path15.join(projectRoot, "lefthook.yml");
|
|
3007
|
-
if (!fs15.existsSync(ymlPath)) {
|
|
3008
|
-
fs15.writeFileSync(ymlPath, "# Generated by viberails\n");
|
|
3009
|
-
}
|
|
3010
|
-
} : void 0
|
|
3011
|
-
};
|
|
3012
|
-
}
|
|
3013
2994
|
return {
|
|
3014
2995
|
choice: {
|
|
3015
2996
|
preCommitHook: result.includes("preCommit"),
|
|
@@ -3018,39 +2999,16 @@ async function promptIntegrationsDeferred(hookManager, tools, packageManager, is
|
|
|
3018
2999
|
githubAction: result.includes("githubAction"),
|
|
3019
3000
|
typecheckHook: result.includes("typecheck"),
|
|
3020
3001
|
lintHook: result.includes("lint")
|
|
3021
|
-
}
|
|
3022
|
-
lefthookInstall
|
|
3002
|
+
}
|
|
3023
3003
|
};
|
|
3024
3004
|
}
|
|
3025
3005
|
|
|
3006
|
+
// src/utils/prompt-main-menu.ts
|
|
3007
|
+
var clack11 = __toESM(require("@clack/prompts"), 1);
|
|
3008
|
+
|
|
3026
3009
|
// src/utils/prompt-main-menu-handlers.ts
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
const state = {
|
|
3030
|
-
maxFileLines: config.rules.maxFileLines,
|
|
3031
|
-
maxTestFileLines: config.rules.maxTestFileLines,
|
|
3032
|
-
testCoverage: config.rules.testCoverage,
|
|
3033
|
-
enforceMissingTests: config.rules.enforceMissingTests,
|
|
3034
|
-
enforceNaming: config.rules.enforceNaming,
|
|
3035
|
-
fileNamingValue: rootPkg.conventions?.fileNaming,
|
|
3036
|
-
componentNaming: rootPkg.conventions?.componentNaming,
|
|
3037
|
-
hookNaming: rootPkg.conventions?.hookNaming,
|
|
3038
|
-
importAlias: rootPkg.conventions?.importAlias,
|
|
3039
|
-
coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
|
|
3040
|
-
coverageCommand: config.defaults?.coverage?.command
|
|
3041
|
-
};
|
|
3042
|
-
await promptNamingMenu(state);
|
|
3043
|
-
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
3044
|
-
config.rules.enforceNaming = state.enforceNaming;
|
|
3045
|
-
if (state.fileNamingValue) {
|
|
3046
|
-
rootPkg.conventions.fileNaming = state.fileNamingValue;
|
|
3047
|
-
} else {
|
|
3048
|
-
delete rootPkg.conventions.fileNaming;
|
|
3049
|
-
}
|
|
3050
|
-
rootPkg.conventions.componentNaming = state.componentNaming || void 0;
|
|
3051
|
-
rootPkg.conventions.hookNaming = state.hookNaming || void 0;
|
|
3052
|
-
rootPkg.conventions.importAlias = state.importAlias || void 0;
|
|
3053
|
-
}
|
|
3010
|
+
var clack10 = __toESM(require("@clack/prompts"), 1);
|
|
3011
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3054
3012
|
async function handleFileNaming(config, scanResult) {
|
|
3055
3013
|
const isMonorepo = config.packages.length > 1;
|
|
3056
3014
|
if (isMonorepo) {
|
|
@@ -3190,24 +3148,95 @@ async function handleBoundaries(config, state, opts) {
|
|
|
3190
3148
|
clack10.log.warn(`Boundary inference failed: ${err instanceof Error ? err.message : err}`);
|
|
3191
3149
|
}
|
|
3192
3150
|
}
|
|
3193
|
-
async function
|
|
3194
|
-
const
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3151
|
+
async function handleAiContext(config) {
|
|
3152
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3153
|
+
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
3154
|
+
while (true) {
|
|
3155
|
+
const ok = import_chalk12.default.green("\u2713");
|
|
3156
|
+
const unset = import_chalk12.default.dim("-");
|
|
3157
|
+
const options = [
|
|
3158
|
+
{
|
|
3159
|
+
value: "componentNaming",
|
|
3160
|
+
label: `${rootPkg.conventions.componentNaming ? ok : unset} Component exports`,
|
|
3161
|
+
hint: rootPkg.conventions.componentNaming ?? "not set"
|
|
3162
|
+
},
|
|
3163
|
+
{
|
|
3164
|
+
value: "hookNaming",
|
|
3165
|
+
label: `${rootPkg.conventions.hookNaming ? ok : unset} Hook exports`,
|
|
3166
|
+
hint: rootPkg.conventions.hookNaming ?? "not set"
|
|
3167
|
+
},
|
|
3168
|
+
{
|
|
3169
|
+
value: "importAlias",
|
|
3170
|
+
label: `${rootPkg.conventions.importAlias ? ok : unset} Import alias`,
|
|
3171
|
+
hint: rootPkg.conventions.importAlias ?? "not set"
|
|
3172
|
+
},
|
|
3173
|
+
{ value: "back", label: " Back" }
|
|
3174
|
+
];
|
|
3175
|
+
const choice = await clack10.select({
|
|
3176
|
+
message: "AI context \u2014 conventions written to context.md for AI tools",
|
|
3177
|
+
options
|
|
3178
|
+
});
|
|
3179
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
3180
|
+
if (choice === "componentNaming") {
|
|
3181
|
+
const selected = await clack10.select({
|
|
3182
|
+
message: "Component export naming (e.g. UserProfile)",
|
|
3183
|
+
options: [
|
|
3184
|
+
...COMPONENT_NAMING_OPTIONS,
|
|
3185
|
+
{ value: SENTINEL_CLEAR, label: "Clear (no convention)" }
|
|
3186
|
+
],
|
|
3187
|
+
initialValue: rootPkg.conventions.componentNaming ?? SENTINEL_CLEAR
|
|
3188
|
+
});
|
|
3189
|
+
if (isCancelled(selected)) continue;
|
|
3190
|
+
rootPkg.conventions.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
3191
|
+
}
|
|
3192
|
+
if (choice === "hookNaming") {
|
|
3193
|
+
const selected = await clack10.select({
|
|
3194
|
+
message: "Hook export naming (e.g. useAuth)",
|
|
3195
|
+
options: [
|
|
3196
|
+
...HOOK_NAMING_OPTIONS,
|
|
3197
|
+
{ value: SENTINEL_CLEAR, label: "Clear (no convention)" }
|
|
3198
|
+
],
|
|
3199
|
+
initialValue: rootPkg.conventions.hookNaming ?? SENTINEL_CLEAR
|
|
3200
|
+
});
|
|
3201
|
+
if (isCancelled(selected)) continue;
|
|
3202
|
+
rootPkg.conventions.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
3203
|
+
}
|
|
3204
|
+
if (choice === "importAlias") {
|
|
3205
|
+
const selected = await clack10.select({
|
|
3206
|
+
message: "Import alias pattern",
|
|
3207
|
+
options: [
|
|
3208
|
+
{ value: "@/*", label: "@/*", hint: "import { x } from '@/utils'" },
|
|
3209
|
+
{ value: "~/*", label: "~/*", hint: "import { x } from '~/utils'" },
|
|
3210
|
+
{ value: SENTINEL_CUSTOM, label: "Custom..." },
|
|
3211
|
+
{ value: SENTINEL_CLEAR, label: "Clear (no alias)" }
|
|
3212
|
+
],
|
|
3213
|
+
initialValue: rootPkg.conventions.importAlias ?? SENTINEL_CLEAR
|
|
3214
|
+
});
|
|
3215
|
+
if (isCancelled(selected)) continue;
|
|
3216
|
+
if (selected === SENTINEL_CLEAR) {
|
|
3217
|
+
rootPkg.conventions.importAlias = void 0;
|
|
3218
|
+
} else if (selected === SENTINEL_CUSTOM) {
|
|
3219
|
+
const result = await clack10.text({
|
|
3220
|
+
message: "Custom import alias (e.g. #/*)?",
|
|
3221
|
+
initialValue: rootPkg.conventions.importAlias ?? "",
|
|
3222
|
+
placeholder: "e.g. #/*",
|
|
3223
|
+
validate: (v) => {
|
|
3224
|
+
if (typeof v !== "string" || !v.trim()) return "Alias cannot be empty";
|
|
3225
|
+
if (!/^[a-zA-Z@~#$][a-zA-Z0-9@~#$_-]*\/\*$/.test(v.trim()))
|
|
3226
|
+
return "Must match pattern like @/*, ~/*, or #src/*";
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
if (isCancelled(result)) continue;
|
|
3230
|
+
rootPkg.conventions.importAlias = result.trim();
|
|
3231
|
+
} else {
|
|
3232
|
+
rootPkg.conventions.importAlias = selected;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3206
3235
|
}
|
|
3207
3236
|
}
|
|
3208
3237
|
|
|
3209
3238
|
// src/utils/prompt-main-menu-hints.ts
|
|
3210
|
-
var
|
|
3239
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
3211
3240
|
function fileLimitsHint(config) {
|
|
3212
3241
|
const max = config.rules.maxFileLines;
|
|
3213
3242
|
const test = config.rules.maxTestFileLines;
|
|
@@ -3223,7 +3252,7 @@ function fileNamingHint(config, scanResult) {
|
|
|
3223
3252
|
);
|
|
3224
3253
|
return detected ? `${naming} (detected)` : naming;
|
|
3225
3254
|
}
|
|
3226
|
-
return "
|
|
3255
|
+
return "not set \u2014 select to configure";
|
|
3227
3256
|
}
|
|
3228
3257
|
function fileNamingStatus(config) {
|
|
3229
3258
|
if (!config.rules.enforceNaming) return "unconfigured";
|
|
@@ -3250,30 +3279,27 @@ function coverageHint(config, hasTestRunner) {
|
|
|
3250
3279
|
}
|
|
3251
3280
|
return `${config.rules.testCoverage}%`;
|
|
3252
3281
|
}
|
|
3253
|
-
function
|
|
3282
|
+
function aiContextHint(config) {
|
|
3254
3283
|
const rootPkg = getRootPackage(config.packages);
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
if (
|
|
3272
|
-
if (
|
|
3273
|
-
|
|
3274
|
-
if (state.integrations.claudeMdRef) items.push(import_chalk12.default.green("CLAUDE.md"));
|
|
3275
|
-
if (state.integrations.githubAction) items.push(import_chalk12.default.green("CI"));
|
|
3276
|
-
return items.length > 0 ? items.join(import_chalk12.default.dim(" \xB7 ")) : "none selected";
|
|
3284
|
+
const count = [
|
|
3285
|
+
rootPkg.conventions?.componentNaming,
|
|
3286
|
+
rootPkg.conventions?.hookNaming,
|
|
3287
|
+
rootPkg.conventions?.importAlias
|
|
3288
|
+
].filter(Boolean).length;
|
|
3289
|
+
if (count === 3) return "all set";
|
|
3290
|
+
if (count > 0) return `${count} of 3 conventions`;
|
|
3291
|
+
return "none set \u2014 optional AI guidelines";
|
|
3292
|
+
}
|
|
3293
|
+
function aiContextStatus(config) {
|
|
3294
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3295
|
+
const count = [
|
|
3296
|
+
rootPkg.conventions?.componentNaming,
|
|
3297
|
+
rootPkg.conventions?.hookNaming,
|
|
3298
|
+
rootPkg.conventions?.importAlias
|
|
3299
|
+
].filter(Boolean).length;
|
|
3300
|
+
if (count === 3) return "ok";
|
|
3301
|
+
if (count > 0) return "partial";
|
|
3302
|
+
return "unconfigured";
|
|
3277
3303
|
}
|
|
3278
3304
|
function packageOverridesHint(config) {
|
|
3279
3305
|
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
@@ -3291,17 +3317,6 @@ function boundariesHint(config, state) {
|
|
|
3291
3317
|
const pkgCount = Object.keys(deny).length;
|
|
3292
3318
|
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3293
3319
|
}
|
|
3294
|
-
function advancedNamingStatus(config) {
|
|
3295
|
-
if (!config.rules.enforceNaming) return "unconfigured";
|
|
3296
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3297
|
-
const hasFile = !!rootPkg.conventions?.fileNaming;
|
|
3298
|
-
const hasComp = !!rootPkg.conventions?.componentNaming;
|
|
3299
|
-
const hasHook = !!rootPkg.conventions?.hookNaming;
|
|
3300
|
-
const hasAlias = !!rootPkg.conventions?.importAlias;
|
|
3301
|
-
if (hasFile && hasComp && hasHook && hasAlias) return "ok";
|
|
3302
|
-
if (hasFile || hasComp || hasHook || hasAlias) return "partial";
|
|
3303
|
-
return "unconfigured";
|
|
3304
|
-
}
|
|
3305
3320
|
function packageOverridesStatus(config) {
|
|
3306
3321
|
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3307
3322
|
const editable = config.packages.filter((p) => p.path !== ".");
|
|
@@ -3311,10 +3326,10 @@ function packageOverridesStatus(config) {
|
|
|
3311
3326
|
return customized ? "ok" : "unconfigured";
|
|
3312
3327
|
}
|
|
3313
3328
|
function statusIcon(status) {
|
|
3314
|
-
if (status === "ok") return
|
|
3315
|
-
if (status === "needs-input") return
|
|
3316
|
-
if (status === "unconfigured") return
|
|
3317
|
-
return
|
|
3329
|
+
if (status === "ok") return import_chalk13.default.green("\u2713");
|
|
3330
|
+
if (status === "needs-input") return import_chalk13.default.yellow("?");
|
|
3331
|
+
if (status === "unconfigured") return import_chalk13.default.dim("-");
|
|
3332
|
+
return import_chalk13.default.yellow("~");
|
|
3318
3333
|
}
|
|
3319
3334
|
function buildMainMenuOptions(config, scanResult, state) {
|
|
3320
3335
|
const namingStatus = fileNamingStatus(config);
|
|
@@ -3328,7 +3343,7 @@ function buildMainMenuOptions(config, scanResult, state) {
|
|
|
3328
3343
|
},
|
|
3329
3344
|
{
|
|
3330
3345
|
value: "fileNaming",
|
|
3331
|
-
label: `${statusIcon(namingStatus)}
|
|
3346
|
+
label: `${statusIcon(namingStatus)} File naming`,
|
|
3332
3347
|
hint: fileNamingHint(config, scanResult)
|
|
3333
3348
|
},
|
|
3334
3349
|
{
|
|
@@ -3342,9 +3357,9 @@ function buildMainMenuOptions(config, scanResult, state) {
|
|
|
3342
3357
|
hint: coverageHint(config, state.hasTestRunner)
|
|
3343
3358
|
},
|
|
3344
3359
|
{
|
|
3345
|
-
value: "
|
|
3346
|
-
label: `${statusIcon(
|
|
3347
|
-
hint:
|
|
3360
|
+
value: "aiContext",
|
|
3361
|
+
label: `${statusIcon(aiContextStatus(config))} AI context`,
|
|
3362
|
+
hint: aiContextHint(config)
|
|
3348
3363
|
}
|
|
3349
3364
|
];
|
|
3350
3365
|
if (config.packages.length > 1) {
|
|
@@ -3361,9 +3376,7 @@ function buildMainMenuOptions(config, scanResult, state) {
|
|
|
3361
3376
|
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3362
3377
|
);
|
|
3363
3378
|
}
|
|
3364
|
-
const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
|
|
3365
3379
|
options.push(
|
|
3366
|
-
{ value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
|
|
3367
3380
|
{ value: "reset", label: " Reset all to defaults" },
|
|
3368
3381
|
{ value: "review", label: " Review scan details", hint: "detected stack & conventions" },
|
|
3369
3382
|
{ value: "done", label: " Done \u2014 write config" }
|
|
@@ -3375,7 +3388,7 @@ function buildMainMenuOptions(config, scanResult, state) {
|
|
|
3375
3388
|
async function promptMainMenu(config, scanResult, opts) {
|
|
3376
3389
|
const originalConfig = structuredClone(config);
|
|
3377
3390
|
const state = {
|
|
3378
|
-
visited: {
|
|
3391
|
+
visited: { boundaries: false },
|
|
3379
3392
|
deferredInstalls: [],
|
|
3380
3393
|
hasTestRunner: opts.hasTestRunner,
|
|
3381
3394
|
hookManager: opts.hookManager
|
|
@@ -3402,10 +3415,9 @@ async function promptMainMenu(config, scanResult, opts) {
|
|
|
3402
3415
|
if (choice === "fileNaming") await handleFileNaming(config, scanResult);
|
|
3403
3416
|
if (choice === "missingTests") await handleMissingTests(config);
|
|
3404
3417
|
if (choice === "coverage") await handleCoverage(config, state, opts);
|
|
3405
|
-
if (choice === "
|
|
3418
|
+
if (choice === "aiContext") await handleAiContext(config);
|
|
3406
3419
|
if (choice === "packageOverrides") await handlePackageOverrides(config);
|
|
3407
3420
|
if (choice === "boundaries") await handleBoundaries(config, state, opts);
|
|
3408
|
-
if (choice === "integrations") await handleIntegrations(state, opts);
|
|
3409
3421
|
if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
|
|
3410
3422
|
if (choice === "reset") {
|
|
3411
3423
|
const confirmed = await clack11.confirm({
|
|
@@ -3416,8 +3428,7 @@ async function promptMainMenu(config, scanResult, opts) {
|
|
|
3416
3428
|
if (confirmed) {
|
|
3417
3429
|
Object.assign(config, structuredClone(originalConfig));
|
|
3418
3430
|
state.deferredInstalls = [];
|
|
3419
|
-
state.visited = {
|
|
3420
|
-
state.integrations = void 0;
|
|
3431
|
+
state.visited = { boundaries: false };
|
|
3421
3432
|
clack11.log.info("Reset all settings to scan-detected defaults.");
|
|
3422
3433
|
}
|
|
3423
3434
|
}
|
|
@@ -3425,37 +3436,26 @@ async function promptMainMenu(config, scanResult, opts) {
|
|
|
3425
3436
|
return state;
|
|
3426
3437
|
}
|
|
3427
3438
|
|
|
3428
|
-
// src/utils/
|
|
3429
|
-
var
|
|
3430
|
-
var
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
let content = "";
|
|
3434
|
-
if (fs16.existsSync(gitignorePath)) {
|
|
3435
|
-
content = fs16.readFileSync(gitignorePath, "utf-8");
|
|
3436
|
-
}
|
|
3437
|
-
if (!content.includes(".viberails/scan-result.json")) {
|
|
3438
|
-
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
3439
|
-
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
3440
|
-
`;
|
|
3441
|
-
fs16.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3439
|
+
// src/utils/prompt-prereqs.ts
|
|
3440
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
3441
|
+
var path17 = __toESM(require("path"), 1);
|
|
3442
|
+
var clack12 = __toESM(require("@clack/prompts"), 1);
|
|
3443
|
+
var import_chalk15 = __toESM(require("chalk"), 1);
|
|
3444
3444
|
|
|
3445
3445
|
// src/commands/init-hooks.ts
|
|
3446
|
-
var
|
|
3447
|
-
var
|
|
3448
|
-
var
|
|
3446
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
3447
|
+
var path16 = __toESM(require("path"), 1);
|
|
3448
|
+
var import_chalk14 = __toESM(require("chalk"), 1);
|
|
3449
3449
|
var import_yaml = require("yaml");
|
|
3450
3450
|
|
|
3451
3451
|
// src/commands/resolve-typecheck.ts
|
|
3452
|
-
var
|
|
3453
|
-
var
|
|
3452
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
3453
|
+
var path15 = __toESM(require("path"), 1);
|
|
3454
3454
|
function hasTurboTask(projectRoot, taskName) {
|
|
3455
|
-
const turboPath =
|
|
3456
|
-
if (!
|
|
3455
|
+
const turboPath = path15.join(projectRoot, "turbo.json");
|
|
3456
|
+
if (!fs15.existsSync(turboPath)) return false;
|
|
3457
3457
|
try {
|
|
3458
|
-
const turbo = JSON.parse(
|
|
3458
|
+
const turbo = JSON.parse(fs15.readFileSync(turboPath, "utf-8"));
|
|
3459
3459
|
const tasks = turbo.tasks ?? turbo.pipeline ?? {};
|
|
3460
3460
|
return taskName in tasks;
|
|
3461
3461
|
} catch {
|
|
@@ -3466,10 +3466,10 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3466
3466
|
if (hasTurboTask(projectRoot, "typecheck")) {
|
|
3467
3467
|
return { command: "npx turbo typecheck", label: "turbo typecheck" };
|
|
3468
3468
|
}
|
|
3469
|
-
const pkgJsonPath =
|
|
3470
|
-
if (
|
|
3469
|
+
const pkgJsonPath = path15.join(projectRoot, "package.json");
|
|
3470
|
+
if (fs15.existsSync(pkgJsonPath)) {
|
|
3471
3471
|
try {
|
|
3472
|
-
const pkg = JSON.parse(
|
|
3472
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgJsonPath, "utf-8"));
|
|
3473
3473
|
if (pkg.scripts?.typecheck) {
|
|
3474
3474
|
const pm = packageManager ?? "npm";
|
|
3475
3475
|
return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
|
|
@@ -3477,7 +3477,7 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3477
3477
|
} catch {
|
|
3478
3478
|
}
|
|
3479
3479
|
}
|
|
3480
|
-
if (
|
|
3480
|
+
if (fs15.existsSync(path15.join(projectRoot, "tsconfig.json"))) {
|
|
3481
3481
|
return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
|
|
3482
3482
|
}
|
|
3483
3483
|
return {
|
|
@@ -3487,36 +3487,36 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3487
3487
|
|
|
3488
3488
|
// src/commands/init-hooks.ts
|
|
3489
3489
|
function setupPreCommitHook(projectRoot) {
|
|
3490
|
-
const lefthookPath =
|
|
3491
|
-
if (
|
|
3490
|
+
const lefthookPath = path16.join(projectRoot, "lefthook.yml");
|
|
3491
|
+
if (fs16.existsSync(lefthookPath)) {
|
|
3492
3492
|
addLefthookPreCommit(lefthookPath);
|
|
3493
|
-
console.log(` ${
|
|
3493
|
+
console.log(` ${import_chalk14.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
3494
3494
|
return "lefthook.yml";
|
|
3495
3495
|
}
|
|
3496
|
-
const huskyDir =
|
|
3497
|
-
if (
|
|
3496
|
+
const huskyDir = path16.join(projectRoot, ".husky");
|
|
3497
|
+
if (fs16.existsSync(huskyDir)) {
|
|
3498
3498
|
writeHuskyPreCommit(huskyDir);
|
|
3499
|
-
console.log(` ${
|
|
3499
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
3500
3500
|
return ".husky/pre-commit";
|
|
3501
3501
|
}
|
|
3502
|
-
const gitDir =
|
|
3503
|
-
if (
|
|
3504
|
-
const hooksDir =
|
|
3505
|
-
if (!
|
|
3506
|
-
|
|
3502
|
+
const gitDir = path16.join(projectRoot, ".git");
|
|
3503
|
+
if (fs16.existsSync(gitDir)) {
|
|
3504
|
+
const hooksDir = path16.join(gitDir, "hooks");
|
|
3505
|
+
if (!fs16.existsSync(hooksDir)) {
|
|
3506
|
+
fs16.mkdirSync(hooksDir, { recursive: true });
|
|
3507
3507
|
}
|
|
3508
3508
|
writeGitHookPreCommit(hooksDir);
|
|
3509
|
-
console.log(` ${
|
|
3509
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
3510
3510
|
return ".git/hooks/pre-commit";
|
|
3511
3511
|
}
|
|
3512
3512
|
return void 0;
|
|
3513
3513
|
}
|
|
3514
3514
|
function writeGitHookPreCommit(hooksDir) {
|
|
3515
|
-
const hookPath =
|
|
3516
|
-
if (
|
|
3517
|
-
const existing =
|
|
3515
|
+
const hookPath = path16.join(hooksDir, "pre-commit");
|
|
3516
|
+
if (fs16.existsSync(hookPath)) {
|
|
3517
|
+
const existing = fs16.readFileSync(hookPath, "utf-8");
|
|
3518
3518
|
if (existing.includes("viberails")) return;
|
|
3519
|
-
|
|
3519
|
+
fs16.writeFileSync(
|
|
3520
3520
|
hookPath,
|
|
3521
3521
|
`${existing.trimEnd()}
|
|
3522
3522
|
|
|
@@ -3533,10 +3533,10 @@ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails chec
|
|
|
3533
3533
|
"if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
|
|
3534
3534
|
""
|
|
3535
3535
|
].join("\n");
|
|
3536
|
-
|
|
3536
|
+
fs16.writeFileSync(hookPath, script, { mode: 493 });
|
|
3537
3537
|
}
|
|
3538
3538
|
function addLefthookPreCommit(lefthookPath) {
|
|
3539
|
-
const content =
|
|
3539
|
+
const content = fs16.readFileSync(lefthookPath, "utf-8");
|
|
3540
3540
|
if (content.includes("viberails")) return;
|
|
3541
3541
|
const doc = (0, import_yaml.parse)(content) ?? {};
|
|
3542
3542
|
if (!doc["pre-commit"]) {
|
|
@@ -3548,28 +3548,28 @@ function addLefthookPreCommit(lefthookPath) {
|
|
|
3548
3548
|
doc["pre-commit"].commands.viberails = {
|
|
3549
3549
|
run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
|
|
3550
3550
|
};
|
|
3551
|
-
|
|
3551
|
+
fs16.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
|
|
3552
3552
|
}
|
|
3553
3553
|
function detectHookManager(projectRoot) {
|
|
3554
|
-
if (
|
|
3555
|
-
if (
|
|
3554
|
+
if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
3555
|
+
if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
|
|
3556
3556
|
return void 0;
|
|
3557
3557
|
}
|
|
3558
3558
|
function setupClaudeCodeHook(projectRoot) {
|
|
3559
|
-
const claudeDir =
|
|
3560
|
-
if (!
|
|
3561
|
-
|
|
3559
|
+
const claudeDir = path16.join(projectRoot, ".claude");
|
|
3560
|
+
if (!fs16.existsSync(claudeDir)) {
|
|
3561
|
+
fs16.mkdirSync(claudeDir, { recursive: true });
|
|
3562
3562
|
}
|
|
3563
|
-
const settingsPath =
|
|
3563
|
+
const settingsPath = path16.join(claudeDir, "settings.json");
|
|
3564
3564
|
let settings = {};
|
|
3565
|
-
if (
|
|
3565
|
+
if (fs16.existsSync(settingsPath)) {
|
|
3566
3566
|
try {
|
|
3567
|
-
settings = JSON.parse(
|
|
3567
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
3568
3568
|
} catch {
|
|
3569
3569
|
console.warn(
|
|
3570
|
-
` ${
|
|
3570
|
+
` ${import_chalk14.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
3571
3571
|
);
|
|
3572
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
3572
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk14.default.cyan("viberails init --force")}`);
|
|
3573
3573
|
return;
|
|
3574
3574
|
}
|
|
3575
3575
|
}
|
|
@@ -3590,30 +3590,30 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3590
3590
|
}
|
|
3591
3591
|
];
|
|
3592
3592
|
settings.hooks = hooks;
|
|
3593
|
-
|
|
3593
|
+
fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
3594
3594
|
`);
|
|
3595
|
-
console.log(` ${
|
|
3595
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
3596
3596
|
}
|
|
3597
3597
|
function setupClaudeMdReference(projectRoot) {
|
|
3598
|
-
const claudeMdPath =
|
|
3598
|
+
const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
|
|
3599
3599
|
let content = "";
|
|
3600
|
-
if (
|
|
3601
|
-
content =
|
|
3600
|
+
if (fs16.existsSync(claudeMdPath)) {
|
|
3601
|
+
content = fs16.readFileSync(claudeMdPath, "utf-8");
|
|
3602
3602
|
}
|
|
3603
3603
|
if (content.includes("@.viberails/context.md")) return;
|
|
3604
3604
|
const ref = "\n@.viberails/context.md\n";
|
|
3605
3605
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
3606
|
-
|
|
3607
|
-
console.log(` ${
|
|
3606
|
+
fs16.writeFileSync(claudeMdPath, prefix + ref);
|
|
3607
|
+
console.log(` ${import_chalk14.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
3608
3608
|
}
|
|
3609
3609
|
function setupGithubAction(projectRoot, packageManager, options) {
|
|
3610
|
-
const workflowDir =
|
|
3611
|
-
const workflowPath =
|
|
3612
|
-
if (
|
|
3613
|
-
const existing =
|
|
3610
|
+
const workflowDir = path16.join(projectRoot, ".github", "workflows");
|
|
3611
|
+
const workflowPath = path16.join(workflowDir, "viberails.yml");
|
|
3612
|
+
if (fs16.existsSync(workflowPath)) {
|
|
3613
|
+
const existing = fs16.readFileSync(workflowPath, "utf-8");
|
|
3614
3614
|
if (existing.includes("viberails")) return void 0;
|
|
3615
3615
|
}
|
|
3616
|
-
|
|
3616
|
+
fs16.mkdirSync(workflowDir, { recursive: true });
|
|
3617
3617
|
const pm = packageManager || "npm";
|
|
3618
3618
|
const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
|
|
3619
3619
|
const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
|
|
@@ -3667,30 +3667,212 @@ function setupGithubAction(projectRoot, packageManager, options) {
|
|
|
3667
3667
|
""
|
|
3668
3668
|
);
|
|
3669
3669
|
const content = lines.filter((l) => l !== void 0).join("\n");
|
|
3670
|
-
|
|
3670
|
+
fs16.writeFileSync(workflowPath, content);
|
|
3671
3671
|
return ".github/workflows/viberails.yml";
|
|
3672
3672
|
}
|
|
3673
3673
|
function writeHuskyPreCommit(huskyDir) {
|
|
3674
|
-
const hookPath =
|
|
3674
|
+
const hookPath = path16.join(huskyDir, "pre-commit");
|
|
3675
3675
|
const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
|
|
3676
|
-
if (
|
|
3677
|
-
const existing =
|
|
3676
|
+
if (fs16.existsSync(hookPath)) {
|
|
3677
|
+
const existing = fs16.readFileSync(hookPath, "utf-8");
|
|
3678
3678
|
if (!existing.includes("viberails")) {
|
|
3679
|
-
|
|
3679
|
+
fs16.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
3680
3680
|
${cmd}
|
|
3681
3681
|
`);
|
|
3682
3682
|
}
|
|
3683
3683
|
return;
|
|
3684
3684
|
}
|
|
3685
|
-
|
|
3685
|
+
fs16.writeFileSync(hookPath, `#!/bin/sh
|
|
3686
3686
|
${cmd}
|
|
3687
3687
|
`, { mode: 493 });
|
|
3688
3688
|
}
|
|
3689
3689
|
|
|
3690
|
+
// src/utils/prompt-prereqs.ts
|
|
3691
|
+
function buildVitestInstallCommand(pm, isWorkspace) {
|
|
3692
|
+
if (pm === "yarn") return "yarn add -D vitest";
|
|
3693
|
+
if (pm === "npm") return "npm install -D vitest";
|
|
3694
|
+
return isWorkspace ? "pnpm add -D -w vitest" : "pnpm add -D vitest";
|
|
3695
|
+
}
|
|
3696
|
+
function statusIcon2(status) {
|
|
3697
|
+
if (status === "ok") return import_chalk15.default.green("\u2713");
|
|
3698
|
+
if (status === "missing") return import_chalk15.default.yellow("!");
|
|
3699
|
+
if (status === "skipped") return import_chalk15.default.dim("\u2717");
|
|
3700
|
+
return import_chalk15.default.dim("-");
|
|
3701
|
+
}
|
|
3702
|
+
function buildReadinessNote(state) {
|
|
3703
|
+
const lines = [];
|
|
3704
|
+
const tr = state.testRunner;
|
|
3705
|
+
lines.push(
|
|
3706
|
+
`${statusIcon2(tr.status)} Test runner ${tr.label ?? (tr.status === "skipped" ? "skipped" : "not detected")}`
|
|
3707
|
+
);
|
|
3708
|
+
const hm = state.hookManager;
|
|
3709
|
+
lines.push(
|
|
3710
|
+
`${statusIcon2(hm.status)} Hook manager ${hm.label ?? (hm.status === "skipped" ? "skipped" : "not detected")}`
|
|
3711
|
+
);
|
|
3712
|
+
const li = state.linter;
|
|
3713
|
+
lines.push(`${statusIcon2(li.status)} Linter ${li.label ?? "none"}`);
|
|
3714
|
+
const tc = state.typecheck;
|
|
3715
|
+
if (tc.status === "ok") {
|
|
3716
|
+
lines.push(`${statusIcon2("ok")} Typecheck ${tc.label}`);
|
|
3717
|
+
} else if (tc.status === "skipped") {
|
|
3718
|
+
lines.push(`${statusIcon2("skipped")} Typecheck skipped`);
|
|
3719
|
+
} else {
|
|
3720
|
+
lines.push(
|
|
3721
|
+
`${statusIcon2("missing")} Typecheck needs root tsconfig.json, typecheck script, or turbo task`
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
return lines.join("\n");
|
|
3725
|
+
}
|
|
3726
|
+
function hasMissing(state) {
|
|
3727
|
+
return state.testRunner.status === "missing" || state.hookManager.status === "missing" || state.typecheck.status === "missing";
|
|
3728
|
+
}
|
|
3729
|
+
async function promptPrereqs(projectRoot, scanResult, hookManager, packageManager, isWorkspace) {
|
|
3730
|
+
let hasTestRunner = !!scanResult.stack.testRunner;
|
|
3731
|
+
let currentHookManager = hookManager;
|
|
3732
|
+
let skipCoverage = false;
|
|
3733
|
+
let skipHooks = false;
|
|
3734
|
+
const linterName = scanResult.stack.linter?.name;
|
|
3735
|
+
const linterLabel = linterName === "biome" ? "Biome" : linterName === "eslint" ? "ESLint" : linterName;
|
|
3736
|
+
const typecheckResolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3737
|
+
const state = {
|
|
3738
|
+
testRunner: hasTestRunner ? { status: "ok", label: scanResult.stack.testRunner?.name } : { status: "missing" },
|
|
3739
|
+
hookManager: currentHookManager ? { status: "ok", label: currentHookManager } : { status: "missing" },
|
|
3740
|
+
linter: linterName ? { status: "ok", label: linterLabel } : { status: "none" },
|
|
3741
|
+
typecheck: typecheckResolved.label ? { status: "ok", label: typecheckResolved.label } : { status: "missing", reason: typecheckResolved.reason }
|
|
3742
|
+
};
|
|
3743
|
+
if (!hasMissing(state)) {
|
|
3744
|
+
return {
|
|
3745
|
+
hasTestRunner,
|
|
3746
|
+
hookManager: currentHookManager,
|
|
3747
|
+
skipCoverage,
|
|
3748
|
+
skipHooks,
|
|
3749
|
+
typecheckLabel: typecheckResolved.label
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
if (state.testRunner.status === "missing") {
|
|
3753
|
+
clack12.note(buildReadinessNote(state), "Project readiness");
|
|
3754
|
+
const cmd = buildVitestInstallCommand(packageManager, isWorkspace);
|
|
3755
|
+
const choice = await clack12.select({
|
|
3756
|
+
message: "No test runner detected. Coverage checks require one.",
|
|
3757
|
+
options: [
|
|
3758
|
+
{ value: "install", label: "Install vitest", hint: cmd },
|
|
3759
|
+
{ value: "skip", label: "Skip \u2014 disable coverage checks" },
|
|
3760
|
+
{ value: "exit", label: "Exit" }
|
|
3761
|
+
]
|
|
3762
|
+
});
|
|
3763
|
+
assertNotCancelled(choice);
|
|
3764
|
+
if (choice === "install") {
|
|
3765
|
+
const s = clack12.spinner();
|
|
3766
|
+
s.start("Installing vitest...");
|
|
3767
|
+
const result = await spawnAsync(cmd, projectRoot);
|
|
3768
|
+
if (result.status === 0) {
|
|
3769
|
+
s.stop("Installed vitest");
|
|
3770
|
+
hasTestRunner = true;
|
|
3771
|
+
state.testRunner = { status: "ok", label: "vitest" };
|
|
3772
|
+
} else {
|
|
3773
|
+
s.stop("Failed to install vitest");
|
|
3774
|
+
clack12.log.warn(`Install manually: ${cmd}`);
|
|
3775
|
+
skipCoverage = true;
|
|
3776
|
+
state.testRunner = { status: "skipped" };
|
|
3777
|
+
}
|
|
3778
|
+
} else if (choice === "skip") {
|
|
3779
|
+
skipCoverage = true;
|
|
3780
|
+
state.testRunner = { status: "skipped" };
|
|
3781
|
+
} else {
|
|
3782
|
+
clack12.outro("Aborted.");
|
|
3783
|
+
process.exit(0);
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
if (state.hookManager.status === "missing") {
|
|
3787
|
+
clack12.note(buildReadinessNote(state), "Project readiness");
|
|
3788
|
+
const cmd = buildLefthookInstallCommand(packageManager, isWorkspace);
|
|
3789
|
+
const choice = await clack12.select({
|
|
3790
|
+
message: "No git hook manager detected. Pre-commit integration requires one.",
|
|
3791
|
+
options: [
|
|
3792
|
+
{ value: "install", label: "Install lefthook", hint: cmd },
|
|
3793
|
+
{ value: "skip", label: "Skip \u2014 no pre-commit integration" },
|
|
3794
|
+
{ value: "exit", label: "Exit" }
|
|
3795
|
+
]
|
|
3796
|
+
});
|
|
3797
|
+
assertNotCancelled(choice);
|
|
3798
|
+
if (choice === "install") {
|
|
3799
|
+
const s = clack12.spinner();
|
|
3800
|
+
s.start("Installing lefthook...");
|
|
3801
|
+
const result = await spawnAsync(cmd, projectRoot);
|
|
3802
|
+
if (result.status === 0) {
|
|
3803
|
+
s.stop("Installed lefthook");
|
|
3804
|
+
const ymlPath = path17.join(projectRoot, "lefthook.yml");
|
|
3805
|
+
if (!fs17.existsSync(ymlPath)) {
|
|
3806
|
+
fs17.writeFileSync(ymlPath, "# Managed by viberails\npre-commit:\n commands: {}\n");
|
|
3807
|
+
}
|
|
3808
|
+
currentHookManager = detectHookManager(projectRoot);
|
|
3809
|
+
state.hookManager = { status: "ok", label: currentHookManager ?? "lefthook" };
|
|
3810
|
+
} else {
|
|
3811
|
+
s.stop("Failed to install lefthook");
|
|
3812
|
+
clack12.log.warn(`Install manually: ${cmd}`);
|
|
3813
|
+
skipHooks = true;
|
|
3814
|
+
state.hookManager = { status: "skipped" };
|
|
3815
|
+
}
|
|
3816
|
+
} else if (choice === "skip") {
|
|
3817
|
+
skipHooks = true;
|
|
3818
|
+
state.hookManager = { status: "skipped" };
|
|
3819
|
+
} else {
|
|
3820
|
+
clack12.outro("Aborted.");
|
|
3821
|
+
process.exit(0);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
if (state.typecheck.status === "missing") {
|
|
3825
|
+
clack12.note(buildReadinessNote(state), "Project readiness");
|
|
3826
|
+
const choice = await clack12.select({
|
|
3827
|
+
message: "No typecheck command found. Without this, pre-commit and CI typecheck hooks will be unavailable.",
|
|
3828
|
+
options: [
|
|
3829
|
+
{
|
|
3830
|
+
value: "continue",
|
|
3831
|
+
label: "Continue without typecheck",
|
|
3832
|
+
hint: "add a root tsconfig.json or typecheck script later, then re-run viberails"
|
|
3833
|
+
},
|
|
3834
|
+
{ value: "exit", label: "Exit \u2014 fix this first" }
|
|
3835
|
+
]
|
|
3836
|
+
});
|
|
3837
|
+
assertNotCancelled(choice);
|
|
3838
|
+
if (choice === "exit") {
|
|
3839
|
+
clack12.outro(
|
|
3840
|
+
"Add a root tsconfig.json, a typecheck script, or a turbo typecheck task, then re-run viberails."
|
|
3841
|
+
);
|
|
3842
|
+
process.exit(0);
|
|
3843
|
+
}
|
|
3844
|
+
state.typecheck = { status: "skipped" };
|
|
3845
|
+
}
|
|
3846
|
+
return {
|
|
3847
|
+
hasTestRunner,
|
|
3848
|
+
hookManager: currentHookManager,
|
|
3849
|
+
skipCoverage,
|
|
3850
|
+
skipHooks,
|
|
3851
|
+
typecheckLabel: typecheckResolved.label
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
// src/utils/update-gitignore.ts
|
|
3856
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
3857
|
+
var path18 = __toESM(require("path"), 1);
|
|
3858
|
+
function updateGitignore(projectRoot) {
|
|
3859
|
+
const gitignorePath = path18.join(projectRoot, ".gitignore");
|
|
3860
|
+
let content = "";
|
|
3861
|
+
if (fs18.existsSync(gitignorePath)) {
|
|
3862
|
+
content = fs18.readFileSync(gitignorePath, "utf-8");
|
|
3863
|
+
}
|
|
3864
|
+
if (!content.includes(".viberails/scan-result.json")) {
|
|
3865
|
+
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
3866
|
+
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
3867
|
+
`;
|
|
3868
|
+
fs18.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3690
3872
|
// src/commands/init-hooks-extra.ts
|
|
3691
3873
|
var fs19 = __toESM(require("fs"), 1);
|
|
3692
3874
|
var path19 = __toESM(require("path"), 1);
|
|
3693
|
-
var
|
|
3875
|
+
var import_chalk16 = __toESM(require("chalk"), 1);
|
|
3694
3876
|
var import_yaml2 = require("yaml");
|
|
3695
3877
|
function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
|
|
3696
3878
|
const lefthookPath = path19.join(projectRoot, "lefthook.yml");
|
|
@@ -3750,12 +3932,12 @@ ${command}
|
|
|
3750
3932
|
function setupTypecheckHook(projectRoot, packageManager) {
|
|
3751
3933
|
const resolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3752
3934
|
if (!resolved.command) {
|
|
3753
|
-
console.log(` ${
|
|
3935
|
+
console.log(` ${import_chalk16.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
|
|
3754
3936
|
return void 0;
|
|
3755
3937
|
}
|
|
3756
3938
|
const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
|
|
3757
3939
|
if (target) {
|
|
3758
|
-
console.log(` ${
|
|
3940
|
+
console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
|
|
3759
3941
|
}
|
|
3760
3942
|
return target;
|
|
3761
3943
|
}
|
|
@@ -3777,7 +3959,7 @@ function setupLintHook(projectRoot, linter) {
|
|
|
3777
3959
|
}
|
|
3778
3960
|
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
3779
3961
|
if (target) {
|
|
3780
|
-
console.log(` ${
|
|
3962
|
+
console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
3781
3963
|
}
|
|
3782
3964
|
return target;
|
|
3783
3965
|
}
|
|
@@ -3786,7 +3968,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3786
3968
|
if (integrations.preCommitHook) {
|
|
3787
3969
|
const t = setupPreCommitHook(projectRoot);
|
|
3788
3970
|
if (t && opts.lefthookExpected && !t.includes("lefthook")) {
|
|
3789
|
-
console.log(` ${
|
|
3971
|
+
console.log(` ${import_chalk16.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
|
|
3790
3972
|
}
|
|
3791
3973
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
3792
3974
|
}
|
|
@@ -3819,10 +4001,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3819
4001
|
// src/commands/init-non-interactive.ts
|
|
3820
4002
|
var fs20 = __toESM(require("fs"), 1);
|
|
3821
4003
|
var path20 = __toESM(require("path"), 1);
|
|
3822
|
-
var
|
|
4004
|
+
var clack13 = __toESM(require("@clack/prompts"), 1);
|
|
3823
4005
|
var import_config8 = require("@viberails/config");
|
|
3824
4006
|
var import_scanner2 = require("@viberails/scanner");
|
|
3825
|
-
var
|
|
4007
|
+
var import_chalk17 = __toESM(require("chalk"), 1);
|
|
3826
4008
|
|
|
3827
4009
|
// src/utils/filter-confidence.ts
|
|
3828
4010
|
function filterHighConfidence(conventions, meta) {
|
|
@@ -3843,7 +4025,7 @@ function getExemptedPackages(config) {
|
|
|
3843
4025
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3844
4026
|
}
|
|
3845
4027
|
async function initNonInteractive(projectRoot, configPath) {
|
|
3846
|
-
const s =
|
|
4028
|
+
const s = clack13.spinner();
|
|
3847
4029
|
s.start("Scanning project...");
|
|
3848
4030
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3849
4031
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
@@ -3858,11 +4040,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3858
4040
|
const exempted = getExemptedPackages(config);
|
|
3859
4041
|
if (exempted.length > 0) {
|
|
3860
4042
|
console.log(
|
|
3861
|
-
` ${
|
|
4043
|
+
` ${import_chalk17.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk17.default.dim("(types-only)")}`
|
|
3862
4044
|
);
|
|
3863
4045
|
}
|
|
3864
4046
|
if (config.packages.length > 1) {
|
|
3865
|
-
const bs =
|
|
4047
|
+
const bs = clack13.spinner();
|
|
3866
4048
|
bs.start("Building import graph...");
|
|
3867
4049
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3868
4050
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -3895,14 +4077,14 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3895
4077
|
const hookManager = detectHookManager(projectRoot);
|
|
3896
4078
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3897
4079
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3898
|
-
const ok =
|
|
4080
|
+
const ok = import_chalk17.default.green("\u2713");
|
|
3899
4081
|
const created = [
|
|
3900
4082
|
`${ok} ${path20.basename(configPath)}`,
|
|
3901
4083
|
`${ok} .viberails/context.md`,
|
|
3902
4084
|
`${ok} .viberails/scan-result.json`,
|
|
3903
4085
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3904
4086
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3905
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
4087
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk17.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3906
4088
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3907
4089
|
].filter(Boolean);
|
|
3908
4090
|
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
@@ -3927,8 +4109,8 @@ async function initCommand(options, cwd) {
|
|
|
3927
4109
|
return initInteractive(projectRoot, configPath, options);
|
|
3928
4110
|
}
|
|
3929
4111
|
console.log(
|
|
3930
|
-
`${
|
|
3931
|
-
Run ${
|
|
4112
|
+
`${import_chalk18.default.yellow("!")} viberails is already initialized.
|
|
4113
|
+
Run ${import_chalk18.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk18.default.cyan("viberails sync")} to update generated files, or ${import_chalk18.default.cyan("viberails init --force")} to replace it.`
|
|
3932
4114
|
);
|
|
3933
4115
|
return;
|
|
3934
4116
|
}
|
|
@@ -3936,11 +4118,11 @@ async function initCommand(options, cwd) {
|
|
|
3936
4118
|
await initInteractive(projectRoot, configPath, options);
|
|
3937
4119
|
}
|
|
3938
4120
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3939
|
-
|
|
4121
|
+
clack14.intro("viberails");
|
|
3940
4122
|
if (fs21.existsSync(configPath) && !options.force) {
|
|
3941
4123
|
const action = await promptExistingConfigAction(path21.basename(configPath));
|
|
3942
4124
|
if (action === "cancel") {
|
|
3943
|
-
|
|
4125
|
+
clack14.outro("Aborted. No files were written.");
|
|
3944
4126
|
return;
|
|
3945
4127
|
}
|
|
3946
4128
|
if (action === "edit") {
|
|
@@ -3954,45 +4136,60 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3954
4136
|
`${path21.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3955
4137
|
);
|
|
3956
4138
|
if (!replace) {
|
|
3957
|
-
|
|
4139
|
+
clack14.outro("Aborted. No files were written.");
|
|
3958
4140
|
return;
|
|
3959
4141
|
}
|
|
3960
4142
|
}
|
|
3961
|
-
const s =
|
|
4143
|
+
const s = clack14.spinner();
|
|
3962
4144
|
s.start("Scanning project...");
|
|
3963
4145
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3964
4146
|
const config = (0, import_config9.generateConfig)(scanResult);
|
|
3965
4147
|
s.stop("Scan complete");
|
|
3966
4148
|
if (scanResult.statistics.totalFiles === 0) {
|
|
3967
|
-
|
|
4149
|
+
clack14.log.warn(
|
|
3968
4150
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3969
4151
|
);
|
|
3970
4152
|
}
|
|
3971
|
-
const hasTestRunner = !!scanResult.stack.testRunner;
|
|
3972
|
-
const hookManager = detectHookManager(projectRoot);
|
|
3973
|
-
const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
|
|
3974
4153
|
const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
|
|
4154
|
+
const packageManager = rootPkgStack?.packageManager?.split("@")[0] ?? "npm";
|
|
4155
|
+
const isWorkspace = config.packages.length > 1;
|
|
4156
|
+
const prereqs = await promptPrereqs(
|
|
4157
|
+
projectRoot,
|
|
4158
|
+
scanResult,
|
|
4159
|
+
detectHookManager(projectRoot),
|
|
4160
|
+
packageManager,
|
|
4161
|
+
isWorkspace
|
|
4162
|
+
);
|
|
4163
|
+
if (prereqs.skipCoverage) config.rules.testCoverage = 0;
|
|
4164
|
+
const coveragePrereqs = prereqs.hasTestRunner ? checkCoveragePrereqs(projectRoot, scanResult) : [];
|
|
3975
4165
|
const state = await promptMainMenu(config, scanResult, {
|
|
3976
|
-
hasTestRunner,
|
|
3977
|
-
hookManager,
|
|
4166
|
+
hasTestRunner: prereqs.hasTestRunner,
|
|
4167
|
+
hookManager: prereqs.hookManager,
|
|
3978
4168
|
coveragePrereqs,
|
|
3979
4169
|
projectRoot,
|
|
3980
4170
|
tools: {
|
|
3981
4171
|
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
3982
4172
|
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3983
|
-
packageManager
|
|
3984
|
-
isWorkspace
|
|
4173
|
+
packageManager,
|
|
4174
|
+
isWorkspace
|
|
3985
4175
|
}
|
|
3986
4176
|
});
|
|
3987
4177
|
const shouldWrite = await confirm3("Apply this setup?");
|
|
3988
4178
|
if (!shouldWrite) {
|
|
3989
|
-
|
|
4179
|
+
clack14.outro("Aborted. No files were written.");
|
|
3990
4180
|
return;
|
|
3991
4181
|
}
|
|
4182
|
+
const integrations = await promptIntegrationsDeferred(prereqs.hookManager, {
|
|
4183
|
+
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
4184
|
+
typecheckLabel: prereqs.typecheckLabel,
|
|
4185
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
4186
|
+
packageManager,
|
|
4187
|
+
isWorkspace
|
|
4188
|
+
});
|
|
3992
4189
|
if (state.deferredInstalls.length > 0) {
|
|
3993
4190
|
await executeDeferredInstalls(projectRoot, state.deferredInstalls);
|
|
3994
4191
|
}
|
|
3995
|
-
const ws =
|
|
4192
|
+
const ws = clack14.spinner();
|
|
3996
4193
|
ws.start("Writing configuration...");
|
|
3997
4194
|
const compacted = (0, import_config9.compactConfig)(config);
|
|
3998
4195
|
fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
@@ -4000,31 +4197,28 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
4000
4197
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
4001
4198
|
updateGitignore(projectRoot);
|
|
4002
4199
|
ws.stop("Configuration written");
|
|
4003
|
-
const ok =
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
});
|
|
4014
|
-
}
|
|
4015
|
-
clack13.outro(
|
|
4200
|
+
const ok = import_chalk18.default.green("\u2713");
|
|
4201
|
+
clack14.log.step(`${ok} ${path21.basename(configPath)}`);
|
|
4202
|
+
clack14.log.step(`${ok} .viberails/context.md`);
|
|
4203
|
+
clack14.log.step(`${ok} .viberails/scan-result.json`);
|
|
4204
|
+
setupSelectedIntegrations(projectRoot, integrations.choice, {
|
|
4205
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
4206
|
+
packageManager,
|
|
4207
|
+
lefthookExpected: prereqs.hookManager === "lefthook"
|
|
4208
|
+
});
|
|
4209
|
+
clack14.outro(
|
|
4016
4210
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
4017
|
-
${
|
|
4211
|
+
${import_chalk18.default.dim("Tip: use")} ${import_chalk18.default.cyan("viberails check --enforce")} ${import_chalk18.default.dim("in CI to block PRs on violations.")}`
|
|
4018
4212
|
);
|
|
4019
4213
|
}
|
|
4020
4214
|
|
|
4021
4215
|
// src/commands/sync.ts
|
|
4022
4216
|
var fs22 = __toESM(require("fs"), 1);
|
|
4023
4217
|
var path22 = __toESM(require("path"), 1);
|
|
4024
|
-
var
|
|
4218
|
+
var clack15 = __toESM(require("@clack/prompts"), 1);
|
|
4025
4219
|
var import_config11 = require("@viberails/config");
|
|
4026
4220
|
var import_scanner4 = require("@viberails/scanner");
|
|
4027
|
-
var
|
|
4221
|
+
var import_chalk19 = __toESM(require("chalk"), 1);
|
|
4028
4222
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
4029
4223
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
4030
4224
|
function loadPreviousStats(projectRoot) {
|
|
@@ -4050,7 +4244,7 @@ async function syncCommand(options, cwd) {
|
|
|
4050
4244
|
const configPath = path22.join(projectRoot, CONFIG_FILE6);
|
|
4051
4245
|
const existing = await (0, import_config11.loadConfig)(configPath);
|
|
4052
4246
|
const previousStats = loadPreviousStats(projectRoot);
|
|
4053
|
-
const s =
|
|
4247
|
+
const s = clack15.spinner();
|
|
4054
4248
|
s.start("Scanning project...");
|
|
4055
4249
|
const scanResult = await (0, import_scanner4.scan)(projectRoot);
|
|
4056
4250
|
s.stop("Scan complete");
|
|
@@ -4065,19 +4259,19 @@ async function syncCommand(options, cwd) {
|
|
|
4065
4259
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
4066
4260
|
if (changes.length > 0 || statsDelta) {
|
|
4067
4261
|
console.log(`
|
|
4068
|
-
${
|
|
4262
|
+
${import_chalk19.default.bold("Changes:")}`);
|
|
4069
4263
|
for (const change of changes) {
|
|
4070
|
-
const icon = change.type === "removed" ?
|
|
4264
|
+
const icon = change.type === "removed" ? import_chalk19.default.red("-") : import_chalk19.default.green("+");
|
|
4071
4265
|
console.log(` ${icon} ${change.description}`);
|
|
4072
4266
|
}
|
|
4073
4267
|
if (statsDelta) {
|
|
4074
|
-
console.log(` ${
|
|
4268
|
+
console.log(` ${import_chalk19.default.dim(statsDelta)}`);
|
|
4075
4269
|
}
|
|
4076
4270
|
}
|
|
4077
4271
|
if (options?.interactive) {
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
const decision = await
|
|
4272
|
+
clack15.intro("viberails sync (interactive)");
|
|
4273
|
+
clack15.note(formatRulesText(merged).join("\n"), "Rules after sync");
|
|
4274
|
+
const decision = await clack15.select({
|
|
4081
4275
|
message: "How would you like to proceed?",
|
|
4082
4276
|
options: [
|
|
4083
4277
|
{ value: "accept", label: "Accept changes" },
|
|
@@ -4087,7 +4281,7 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4087
4281
|
});
|
|
4088
4282
|
assertNotCancelled(decision);
|
|
4089
4283
|
if (decision === "cancel") {
|
|
4090
|
-
|
|
4284
|
+
clack15.outro("Sync cancelled. No files were written.");
|
|
4091
4285
|
return;
|
|
4092
4286
|
}
|
|
4093
4287
|
if (decision === "customize") {
|
|
@@ -4111,8 +4305,8 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4111
4305
|
fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
4112
4306
|
`);
|
|
4113
4307
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4114
|
-
|
|
4115
|
-
|
|
4308
|
+
clack15.log.success("Updated config with your customizations.");
|
|
4309
|
+
clack15.outro("Done! Run viberails check to verify.");
|
|
4116
4310
|
return;
|
|
4117
4311
|
}
|
|
4118
4312
|
}
|
|
@@ -4120,18 +4314,18 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4120
4314
|
`);
|
|
4121
4315
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4122
4316
|
console.log(`
|
|
4123
|
-
${
|
|
4317
|
+
${import_chalk19.default.bold("Synced:")}`);
|
|
4124
4318
|
if (configChanged) {
|
|
4125
|
-
console.log(` ${
|
|
4319
|
+
console.log(` ${import_chalk19.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
4126
4320
|
} else {
|
|
4127
|
-
console.log(` ${
|
|
4321
|
+
console.log(` ${import_chalk19.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
4128
4322
|
}
|
|
4129
|
-
console.log(` ${
|
|
4130
|
-
console.log(` ${
|
|
4323
|
+
console.log(` ${import_chalk19.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
4324
|
+
console.log(` ${import_chalk19.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
4131
4325
|
}
|
|
4132
4326
|
|
|
4133
4327
|
// src/index.ts
|
|
4134
|
-
var VERSION = "0.6.
|
|
4328
|
+
var VERSION = "0.6.11";
|
|
4135
4329
|
var program = new import_commander.Command();
|
|
4136
4330
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4137
4331
|
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) => {
|
|
@@ -4139,7 +4333,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
4139
4333
|
await initCommand(options);
|
|
4140
4334
|
} catch (err) {
|
|
4141
4335
|
const message = err instanceof Error ? err.message : String(err);
|
|
4142
|
-
console.error(`${
|
|
4336
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4143
4337
|
process.exit(1);
|
|
4144
4338
|
}
|
|
4145
4339
|
});
|
|
@@ -4148,7 +4342,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
4148
4342
|
await syncCommand(options);
|
|
4149
4343
|
} catch (err) {
|
|
4150
4344
|
const message = err instanceof Error ? err.message : String(err);
|
|
4151
|
-
console.error(`${
|
|
4345
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4152
4346
|
process.exit(1);
|
|
4153
4347
|
}
|
|
4154
4348
|
});
|
|
@@ -4157,7 +4351,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
4157
4351
|
await configCommand(options);
|
|
4158
4352
|
} catch (err) {
|
|
4159
4353
|
const message = err instanceof Error ? err.message : String(err);
|
|
4160
|
-
console.error(`${
|
|
4354
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4161
4355
|
process.exit(1);
|
|
4162
4356
|
}
|
|
4163
4357
|
});
|
|
@@ -4178,7 +4372,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
4178
4372
|
process.exit(exitCode);
|
|
4179
4373
|
} catch (err) {
|
|
4180
4374
|
const message = err instanceof Error ? err.message : String(err);
|
|
4181
|
-
console.error(`${
|
|
4375
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4182
4376
|
process.exit(1);
|
|
4183
4377
|
}
|
|
4184
4378
|
}
|
|
@@ -4189,7 +4383,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
4189
4383
|
process.exit(exitCode);
|
|
4190
4384
|
} catch (err) {
|
|
4191
4385
|
const message = err instanceof Error ? err.message : String(err);
|
|
4192
|
-
console.error(`${
|
|
4386
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4193
4387
|
process.exit(1);
|
|
4194
4388
|
}
|
|
4195
4389
|
});
|
|
@@ -4198,7 +4392,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
4198
4392
|
await boundariesCommand(options);
|
|
4199
4393
|
} catch (err) {
|
|
4200
4394
|
const message = err instanceof Error ? err.message : String(err);
|
|
4201
|
-
console.error(`${
|
|
4395
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4202
4396
|
process.exit(1);
|
|
4203
4397
|
}
|
|
4204
4398
|
});
|