viberails 0.6.9 → 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 +484 -289
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +484 -289
- 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
|
}
|
|
@@ -3765,18 +3947,19 @@ function setupLintHook(projectRoot, linter) {
|
|
|
3765
3947
|
let command;
|
|
3766
3948
|
let lefthookExtra;
|
|
3767
3949
|
if (isLefthook) {
|
|
3768
|
-
command = linter === "biome" ? "npx biome check {staged_files}" : "npx eslint {staged_files}";
|
|
3950
|
+
command = linter === "biome" ? "npx biome check --write {staged_files}" : "npx eslint --fix {staged_files}";
|
|
3769
3951
|
lefthookExtra = {
|
|
3770
|
-
glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}"
|
|
3952
|
+
glob: linter === "biome" ? "*.{js,ts,jsx,tsx,json,css}" : "*.{js,ts,jsx,tsx}",
|
|
3953
|
+
stage_fixed: "true"
|
|
3771
3954
|
};
|
|
3772
3955
|
} else {
|
|
3773
3956
|
const exts = linter === "biome" ? "'*.js' '*.ts' '*.jsx' '*.tsx' '*.json' '*.css'" : "'*.js' '*.ts' '*.jsx' '*.tsx'";
|
|
3774
|
-
const lintCmd = linter === "biome" ? "biome check" : "eslint";
|
|
3957
|
+
const lintCmd = linter === "biome" ? "biome check --write" : "eslint --fix";
|
|
3775
3958
|
command = `git diff --cached --name-only --diff-filter=ACMR -- ${exts} | xargs npx ${lintCmd}`;
|
|
3776
3959
|
}
|
|
3777
3960
|
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
3778
3961
|
if (target) {
|
|
3779
|
-
console.log(` ${
|
|
3962
|
+
console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
3780
3963
|
}
|
|
3781
3964
|
return target;
|
|
3782
3965
|
}
|
|
@@ -3785,7 +3968,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3785
3968
|
if (integrations.preCommitHook) {
|
|
3786
3969
|
const t = setupPreCommitHook(projectRoot);
|
|
3787
3970
|
if (t && opts.lefthookExpected && !t.includes("lefthook")) {
|
|
3788
|
-
console.log(` ${
|
|
3971
|
+
console.log(` ${import_chalk16.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
|
|
3789
3972
|
}
|
|
3790
3973
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
3791
3974
|
}
|
|
@@ -3818,10 +4001,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3818
4001
|
// src/commands/init-non-interactive.ts
|
|
3819
4002
|
var fs20 = __toESM(require("fs"), 1);
|
|
3820
4003
|
var path20 = __toESM(require("path"), 1);
|
|
3821
|
-
var
|
|
4004
|
+
var clack13 = __toESM(require("@clack/prompts"), 1);
|
|
3822
4005
|
var import_config8 = require("@viberails/config");
|
|
3823
4006
|
var import_scanner2 = require("@viberails/scanner");
|
|
3824
|
-
var
|
|
4007
|
+
var import_chalk17 = __toESM(require("chalk"), 1);
|
|
3825
4008
|
|
|
3826
4009
|
// src/utils/filter-confidence.ts
|
|
3827
4010
|
function filterHighConfidence(conventions, meta) {
|
|
@@ -3842,7 +4025,7 @@ function getExemptedPackages(config) {
|
|
|
3842
4025
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3843
4026
|
}
|
|
3844
4027
|
async function initNonInteractive(projectRoot, configPath) {
|
|
3845
|
-
const s =
|
|
4028
|
+
const s = clack13.spinner();
|
|
3846
4029
|
s.start("Scanning project...");
|
|
3847
4030
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3848
4031
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
@@ -3857,11 +4040,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3857
4040
|
const exempted = getExemptedPackages(config);
|
|
3858
4041
|
if (exempted.length > 0) {
|
|
3859
4042
|
console.log(
|
|
3860
|
-
` ${
|
|
4043
|
+
` ${import_chalk17.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk17.default.dim("(types-only)")}`
|
|
3861
4044
|
);
|
|
3862
4045
|
}
|
|
3863
4046
|
if (config.packages.length > 1) {
|
|
3864
|
-
const bs =
|
|
4047
|
+
const bs = clack13.spinner();
|
|
3865
4048
|
bs.start("Building import graph...");
|
|
3866
4049
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3867
4050
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -3894,14 +4077,14 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3894
4077
|
const hookManager = detectHookManager(projectRoot);
|
|
3895
4078
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3896
4079
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3897
|
-
const ok =
|
|
4080
|
+
const ok = import_chalk17.default.green("\u2713");
|
|
3898
4081
|
const created = [
|
|
3899
4082
|
`${ok} ${path20.basename(configPath)}`,
|
|
3900
4083
|
`${ok} .viberails/context.md`,
|
|
3901
4084
|
`${ok} .viberails/scan-result.json`,
|
|
3902
4085
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3903
4086
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3904
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
4087
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk17.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3905
4088
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3906
4089
|
].filter(Boolean);
|
|
3907
4090
|
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
@@ -3926,8 +4109,8 @@ async function initCommand(options, cwd) {
|
|
|
3926
4109
|
return initInteractive(projectRoot, configPath, options);
|
|
3927
4110
|
}
|
|
3928
4111
|
console.log(
|
|
3929
|
-
`${
|
|
3930
|
-
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.`
|
|
3931
4114
|
);
|
|
3932
4115
|
return;
|
|
3933
4116
|
}
|
|
@@ -3935,11 +4118,11 @@ async function initCommand(options, cwd) {
|
|
|
3935
4118
|
await initInteractive(projectRoot, configPath, options);
|
|
3936
4119
|
}
|
|
3937
4120
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3938
|
-
|
|
4121
|
+
clack14.intro("viberails");
|
|
3939
4122
|
if (fs21.existsSync(configPath) && !options.force) {
|
|
3940
4123
|
const action = await promptExistingConfigAction(path21.basename(configPath));
|
|
3941
4124
|
if (action === "cancel") {
|
|
3942
|
-
|
|
4125
|
+
clack14.outro("Aborted. No files were written.");
|
|
3943
4126
|
return;
|
|
3944
4127
|
}
|
|
3945
4128
|
if (action === "edit") {
|
|
@@ -3953,45 +4136,60 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3953
4136
|
`${path21.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3954
4137
|
);
|
|
3955
4138
|
if (!replace) {
|
|
3956
|
-
|
|
4139
|
+
clack14.outro("Aborted. No files were written.");
|
|
3957
4140
|
return;
|
|
3958
4141
|
}
|
|
3959
4142
|
}
|
|
3960
|
-
const s =
|
|
4143
|
+
const s = clack14.spinner();
|
|
3961
4144
|
s.start("Scanning project...");
|
|
3962
4145
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3963
4146
|
const config = (0, import_config9.generateConfig)(scanResult);
|
|
3964
4147
|
s.stop("Scan complete");
|
|
3965
4148
|
if (scanResult.statistics.totalFiles === 0) {
|
|
3966
|
-
|
|
4149
|
+
clack14.log.warn(
|
|
3967
4150
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3968
4151
|
);
|
|
3969
4152
|
}
|
|
3970
|
-
const hasTestRunner = !!scanResult.stack.testRunner;
|
|
3971
|
-
const hookManager = detectHookManager(projectRoot);
|
|
3972
|
-
const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
|
|
3973
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) : [];
|
|
3974
4165
|
const state = await promptMainMenu(config, scanResult, {
|
|
3975
|
-
hasTestRunner,
|
|
3976
|
-
hookManager,
|
|
4166
|
+
hasTestRunner: prereqs.hasTestRunner,
|
|
4167
|
+
hookManager: prereqs.hookManager,
|
|
3977
4168
|
coveragePrereqs,
|
|
3978
4169
|
projectRoot,
|
|
3979
4170
|
tools: {
|
|
3980
4171
|
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
3981
4172
|
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3982
|
-
packageManager
|
|
3983
|
-
isWorkspace
|
|
4173
|
+
packageManager,
|
|
4174
|
+
isWorkspace
|
|
3984
4175
|
}
|
|
3985
4176
|
});
|
|
3986
4177
|
const shouldWrite = await confirm3("Apply this setup?");
|
|
3987
4178
|
if (!shouldWrite) {
|
|
3988
|
-
|
|
4179
|
+
clack14.outro("Aborted. No files were written.");
|
|
3989
4180
|
return;
|
|
3990
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
|
+
});
|
|
3991
4189
|
if (state.deferredInstalls.length > 0) {
|
|
3992
4190
|
await executeDeferredInstalls(projectRoot, state.deferredInstalls);
|
|
3993
4191
|
}
|
|
3994
|
-
const ws =
|
|
4192
|
+
const ws = clack14.spinner();
|
|
3995
4193
|
ws.start("Writing configuration...");
|
|
3996
4194
|
const compacted = (0, import_config9.compactConfig)(config);
|
|
3997
4195
|
fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
@@ -3999,31 +4197,28 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3999
4197
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
4000
4198
|
updateGitignore(projectRoot);
|
|
4001
4199
|
ws.stop("Configuration written");
|
|
4002
|
-
const ok =
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
});
|
|
4013
|
-
}
|
|
4014
|
-
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(
|
|
4015
4210
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
4016
|
-
${
|
|
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.")}`
|
|
4017
4212
|
);
|
|
4018
4213
|
}
|
|
4019
4214
|
|
|
4020
4215
|
// src/commands/sync.ts
|
|
4021
4216
|
var fs22 = __toESM(require("fs"), 1);
|
|
4022
4217
|
var path22 = __toESM(require("path"), 1);
|
|
4023
|
-
var
|
|
4218
|
+
var clack15 = __toESM(require("@clack/prompts"), 1);
|
|
4024
4219
|
var import_config11 = require("@viberails/config");
|
|
4025
4220
|
var import_scanner4 = require("@viberails/scanner");
|
|
4026
|
-
var
|
|
4221
|
+
var import_chalk19 = __toESM(require("chalk"), 1);
|
|
4027
4222
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
4028
4223
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
4029
4224
|
function loadPreviousStats(projectRoot) {
|
|
@@ -4049,7 +4244,7 @@ async function syncCommand(options, cwd) {
|
|
|
4049
4244
|
const configPath = path22.join(projectRoot, CONFIG_FILE6);
|
|
4050
4245
|
const existing = await (0, import_config11.loadConfig)(configPath);
|
|
4051
4246
|
const previousStats = loadPreviousStats(projectRoot);
|
|
4052
|
-
const s =
|
|
4247
|
+
const s = clack15.spinner();
|
|
4053
4248
|
s.start("Scanning project...");
|
|
4054
4249
|
const scanResult = await (0, import_scanner4.scan)(projectRoot);
|
|
4055
4250
|
s.stop("Scan complete");
|
|
@@ -4064,19 +4259,19 @@ async function syncCommand(options, cwd) {
|
|
|
4064
4259
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
4065
4260
|
if (changes.length > 0 || statsDelta) {
|
|
4066
4261
|
console.log(`
|
|
4067
|
-
${
|
|
4262
|
+
${import_chalk19.default.bold("Changes:")}`);
|
|
4068
4263
|
for (const change of changes) {
|
|
4069
|
-
const icon = change.type === "removed" ?
|
|
4264
|
+
const icon = change.type === "removed" ? import_chalk19.default.red("-") : import_chalk19.default.green("+");
|
|
4070
4265
|
console.log(` ${icon} ${change.description}`);
|
|
4071
4266
|
}
|
|
4072
4267
|
if (statsDelta) {
|
|
4073
|
-
console.log(` ${
|
|
4268
|
+
console.log(` ${import_chalk19.default.dim(statsDelta)}`);
|
|
4074
4269
|
}
|
|
4075
4270
|
}
|
|
4076
4271
|
if (options?.interactive) {
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
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({
|
|
4080
4275
|
message: "How would you like to proceed?",
|
|
4081
4276
|
options: [
|
|
4082
4277
|
{ value: "accept", label: "Accept changes" },
|
|
@@ -4086,7 +4281,7 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4086
4281
|
});
|
|
4087
4282
|
assertNotCancelled(decision);
|
|
4088
4283
|
if (decision === "cancel") {
|
|
4089
|
-
|
|
4284
|
+
clack15.outro("Sync cancelled. No files were written.");
|
|
4090
4285
|
return;
|
|
4091
4286
|
}
|
|
4092
4287
|
if (decision === "customize") {
|
|
@@ -4110,8 +4305,8 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4110
4305
|
fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
4111
4306
|
`);
|
|
4112
4307
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4113
|
-
|
|
4114
|
-
|
|
4308
|
+
clack15.log.success("Updated config with your customizations.");
|
|
4309
|
+
clack15.outro("Done! Run viberails check to verify.");
|
|
4115
4310
|
return;
|
|
4116
4311
|
}
|
|
4117
4312
|
}
|
|
@@ -4119,18 +4314,18 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4119
4314
|
`);
|
|
4120
4315
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4121
4316
|
console.log(`
|
|
4122
|
-
${
|
|
4317
|
+
${import_chalk19.default.bold("Synced:")}`);
|
|
4123
4318
|
if (configChanged) {
|
|
4124
|
-
console.log(` ${
|
|
4319
|
+
console.log(` ${import_chalk19.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
4125
4320
|
} else {
|
|
4126
|
-
console.log(` ${
|
|
4321
|
+
console.log(` ${import_chalk19.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
4127
4322
|
}
|
|
4128
|
-
console.log(` ${
|
|
4129
|
-
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`);
|
|
4130
4325
|
}
|
|
4131
4326
|
|
|
4132
4327
|
// src/index.ts
|
|
4133
|
-
var VERSION = "0.6.
|
|
4328
|
+
var VERSION = "0.6.11";
|
|
4134
4329
|
var program = new import_commander.Command();
|
|
4135
4330
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4136
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) => {
|
|
@@ -4138,7 +4333,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
4138
4333
|
await initCommand(options);
|
|
4139
4334
|
} catch (err) {
|
|
4140
4335
|
const message = err instanceof Error ? err.message : String(err);
|
|
4141
|
-
console.error(`${
|
|
4336
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4142
4337
|
process.exit(1);
|
|
4143
4338
|
}
|
|
4144
4339
|
});
|
|
@@ -4147,7 +4342,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
4147
4342
|
await syncCommand(options);
|
|
4148
4343
|
} catch (err) {
|
|
4149
4344
|
const message = err instanceof Error ? err.message : String(err);
|
|
4150
|
-
console.error(`${
|
|
4345
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4151
4346
|
process.exit(1);
|
|
4152
4347
|
}
|
|
4153
4348
|
});
|
|
@@ -4156,7 +4351,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
4156
4351
|
await configCommand(options);
|
|
4157
4352
|
} catch (err) {
|
|
4158
4353
|
const message = err instanceof Error ? err.message : String(err);
|
|
4159
|
-
console.error(`${
|
|
4354
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4160
4355
|
process.exit(1);
|
|
4161
4356
|
}
|
|
4162
4357
|
});
|
|
@@ -4177,7 +4372,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
4177
4372
|
process.exit(exitCode);
|
|
4178
4373
|
} catch (err) {
|
|
4179
4374
|
const message = err instanceof Error ? err.message : String(err);
|
|
4180
|
-
console.error(`${
|
|
4375
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4181
4376
|
process.exit(1);
|
|
4182
4377
|
}
|
|
4183
4378
|
}
|
|
@@ -4188,7 +4383,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
4188
4383
|
process.exit(exitCode);
|
|
4189
4384
|
} catch (err) {
|
|
4190
4385
|
const message = err instanceof Error ? err.message : String(err);
|
|
4191
|
-
console.error(`${
|
|
4386
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4192
4387
|
process.exit(1);
|
|
4193
4388
|
}
|
|
4194
4389
|
});
|
|
@@ -4197,7 +4392,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
4197
4392
|
await boundariesCommand(options);
|
|
4198
4393
|
} catch (err) {
|
|
4199
4394
|
const message = err instanceof Error ? err.message : String(err);
|
|
4200
|
-
console.error(`${
|
|
4395
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4201
4396
|
process.exit(1);
|
|
4202
4397
|
}
|
|
4203
4398
|
});
|