viberails 0.6.10 → 0.6.12
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 +610 -402
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +610 -402
- 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
|
|
@@ -99,8 +99,8 @@ var FILE_NAMING_OPTIONS = [
|
|
|
99
99
|
{ value: "snake_case", label: "snake_case" }
|
|
100
100
|
];
|
|
101
101
|
var COMPONENT_NAMING_OPTIONS = [
|
|
102
|
-
{ value: "PascalCase", label: "PascalCase", hint: "MyComponent
|
|
103
|
-
{ value: "camelCase", label: "camelCase", hint: "myComponent
|
|
102
|
+
{ value: "PascalCase", label: "PascalCase", hint: "e.g. MyComponent, UserProfile" },
|
|
103
|
+
{ value: "camelCase", label: "camelCase", hint: "e.g. myComponent, userProfile" }
|
|
104
104
|
];
|
|
105
105
|
var HOOK_NAMING_OPTIONS = [
|
|
106
106
|
{ value: "useXxx", label: "useXxx", hint: "useAuth, useFormData" },
|
|
@@ -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,178 @@ 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
|
-
|
|
3010
|
+
var clack10 = __toESM(require("@clack/prompts"), 1);
|
|
3011
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
3012
|
+
|
|
3013
|
+
// src/utils/prompt-main-menu-hints.ts
|
|
3014
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3015
|
+
function fileLimitsHint(config) {
|
|
3016
|
+
const max = config.rules.maxFileLines;
|
|
3017
|
+
const test = config.rules.maxTestFileLines;
|
|
3018
|
+
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
3019
|
+
}
|
|
3020
|
+
function getEffectiveFileNaming(config) {
|
|
3028
3021
|
const rootPkg = getRootPackage(config.packages);
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3022
|
+
if (rootPkg.conventions?.fileNaming) {
|
|
3023
|
+
return { naming: rootPkg.conventions.fileNaming, source: "root" };
|
|
3024
|
+
}
|
|
3025
|
+
if (config.packages.length > 1) {
|
|
3026
|
+
const namingValues = config.packages.map((p) => p.conventions?.fileNaming).filter((n) => !!n);
|
|
3027
|
+
if (namingValues.length > 0 && new Set(namingValues).size === 1) {
|
|
3028
|
+
return { naming: namingValues[0], source: "consensus" };
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
return void 0;
|
|
3032
|
+
}
|
|
3033
|
+
function fileNamingHint(config, scanResult) {
|
|
3034
|
+
if (!config.rules.enforceNaming) return "not enforced";
|
|
3035
|
+
const effective = getEffectiveFileNaming(config);
|
|
3036
|
+
if (effective) {
|
|
3037
|
+
const detected = scanResult.packages.some(
|
|
3038
|
+
(p) => p.conventions.fileNaming?.value === effective.naming && p.conventions.fileNaming.confidence !== "low"
|
|
3039
|
+
);
|
|
3040
|
+
return detected ? `${effective.naming} (detected)` : effective.naming;
|
|
3041
|
+
}
|
|
3042
|
+
return "not set \u2014 select to configure";
|
|
3043
|
+
}
|
|
3044
|
+
function fileNamingStatus(config) {
|
|
3045
|
+
if (!config.rules.enforceNaming) return "unconfigured";
|
|
3046
|
+
return getEffectiveFileNaming(config) ? "ok" : "needs-input";
|
|
3047
|
+
}
|
|
3048
|
+
function missingTestsHint(config) {
|
|
3049
|
+
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3050
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3051
|
+
const pattern = rootPkg.structure?.testPattern;
|
|
3052
|
+
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3053
|
+
}
|
|
3054
|
+
function coverageHint(config, hasTestRunner) {
|
|
3055
|
+
if (config.rules.testCoverage === 0) return "disabled";
|
|
3056
|
+
if (!hasTestRunner)
|
|
3057
|
+
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3058
|
+
const isMonorepo = config.packages.length > 1;
|
|
3059
|
+
if (isMonorepo) {
|
|
3060
|
+
const withCov = config.packages.filter(
|
|
3061
|
+
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3062
|
+
);
|
|
3063
|
+
const exempt = config.packages.length - withCov.length;
|
|
3064
|
+
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3065
|
+
}
|
|
3066
|
+
return `${config.rules.testCoverage}%`;
|
|
3067
|
+
}
|
|
3068
|
+
function aiContextHint(config) {
|
|
3069
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3070
|
+
const count = [
|
|
3071
|
+
rootPkg.conventions?.componentNaming,
|
|
3072
|
+
rootPkg.conventions?.hookNaming,
|
|
3073
|
+
rootPkg.conventions?.importAlias
|
|
3074
|
+
].filter(Boolean).length;
|
|
3075
|
+
if (count === 3) return "all set";
|
|
3076
|
+
if (count > 0) return `${count} of 3 conventions`;
|
|
3077
|
+
return "none set \u2014 optional AI guidelines";
|
|
3078
|
+
}
|
|
3079
|
+
function aiContextStatus(config) {
|
|
3080
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3081
|
+
const count = [
|
|
3082
|
+
rootPkg.conventions?.componentNaming,
|
|
3083
|
+
rootPkg.conventions?.hookNaming,
|
|
3084
|
+
rootPkg.conventions?.importAlias
|
|
3085
|
+
].filter(Boolean).length;
|
|
3086
|
+
if (count === 3) return "ok";
|
|
3087
|
+
if (count > 0) return "partial";
|
|
3088
|
+
return "unconfigured";
|
|
3089
|
+
}
|
|
3090
|
+
function packageOverridesHint(config) {
|
|
3091
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3092
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3093
|
+
const customized = editable.filter(
|
|
3094
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3095
|
+
).length;
|
|
3096
|
+
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3097
|
+
}
|
|
3098
|
+
function boundariesHint(config, state) {
|
|
3099
|
+
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3100
|
+
const deny = config.boundaries?.deny;
|
|
3101
|
+
if (!deny) return "enabled";
|
|
3102
|
+
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3103
|
+
const pkgCount = Object.keys(deny).length;
|
|
3104
|
+
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3105
|
+
}
|
|
3106
|
+
function packageOverridesStatus(config) {
|
|
3107
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3108
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3109
|
+
const customized = editable.some(
|
|
3110
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3111
|
+
);
|
|
3112
|
+
return customized ? "ok" : "unconfigured";
|
|
3113
|
+
}
|
|
3114
|
+
function statusIcon(status) {
|
|
3115
|
+
if (status === "ok") return import_chalk12.default.green("\u2713");
|
|
3116
|
+
if (status === "needs-input") return import_chalk12.default.yellow("?");
|
|
3117
|
+
if (status === "unconfigured") return import_chalk12.default.dim("-");
|
|
3118
|
+
return import_chalk12.default.yellow("~");
|
|
3119
|
+
}
|
|
3120
|
+
function buildMainMenuOptions(config, scanResult, state) {
|
|
3121
|
+
const namingStatus = fileNamingStatus(config);
|
|
3122
|
+
const coverageStatus = config.rules.testCoverage === 0 ? "unconfigured" : !state.hasTestRunner ? "partial" : "ok";
|
|
3123
|
+
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "unconfigured";
|
|
3124
|
+
const options = [
|
|
3125
|
+
{
|
|
3126
|
+
value: "fileLimits",
|
|
3127
|
+
label: `${statusIcon("ok")} Max file size`,
|
|
3128
|
+
hint: fileLimitsHint(config)
|
|
3129
|
+
},
|
|
3130
|
+
{
|
|
3131
|
+
value: "fileNaming",
|
|
3132
|
+
label: `${statusIcon(namingStatus)} File naming`,
|
|
3133
|
+
hint: fileNamingHint(config, scanResult)
|
|
3134
|
+
},
|
|
3135
|
+
{
|
|
3136
|
+
value: "missingTests",
|
|
3137
|
+
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3138
|
+
hint: missingTestsHint(config)
|
|
3139
|
+
},
|
|
3140
|
+
{
|
|
3141
|
+
value: "coverage",
|
|
3142
|
+
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3143
|
+
hint: coverageHint(config, state.hasTestRunner)
|
|
3144
|
+
},
|
|
3145
|
+
{
|
|
3146
|
+
value: "aiContext",
|
|
3147
|
+
label: `${statusIcon(aiContextStatus(config))} AI context`,
|
|
3148
|
+
hint: aiContextHint(config)
|
|
3149
|
+
}
|
|
3150
|
+
];
|
|
3151
|
+
if (config.packages.length > 1) {
|
|
3152
|
+
const bIcon = statusIcon(
|
|
3153
|
+
state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
|
|
3154
|
+
);
|
|
3155
|
+
const poIcon = statusIcon(packageOverridesStatus(config));
|
|
3156
|
+
options.push(
|
|
3157
|
+
{
|
|
3158
|
+
value: "packageOverrides",
|
|
3159
|
+
label: `${poIcon} Per-package overrides`,
|
|
3160
|
+
hint: packageOverridesHint(config)
|
|
3161
|
+
},
|
|
3162
|
+
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3163
|
+
);
|
|
3049
3164
|
}
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3165
|
+
options.push(
|
|
3166
|
+
{ value: "reset", label: " Reset all to defaults" },
|
|
3167
|
+
{ value: "review", label: " Review scan details", hint: "detected stack & conventions" },
|
|
3168
|
+
{ value: "done", label: " Done \u2014 write config" }
|
|
3169
|
+
);
|
|
3170
|
+
return options;
|
|
3053
3171
|
}
|
|
3172
|
+
|
|
3173
|
+
// src/utils/prompt-main-menu-handlers.ts
|
|
3054
3174
|
async function handleFileNaming(config, scanResult) {
|
|
3055
3175
|
const isMonorepo = config.packages.length > 1;
|
|
3056
3176
|
if (isMonorepo) {
|
|
@@ -3074,10 +3194,11 @@ async function handleFileNaming(config, scanResult) {
|
|
|
3074
3194
|
return { value: opt.value, label: opt.label };
|
|
3075
3195
|
});
|
|
3076
3196
|
const rootPkg = getRootPackage(config.packages);
|
|
3197
|
+
const effective = getEffectiveFileNaming(config);
|
|
3077
3198
|
const selected = await clack10.select({
|
|
3078
3199
|
message: isMonorepo ? "Default file naming convention" : "File naming convention",
|
|
3079
3200
|
options: [...namingOptions, { value: SENTINEL_SKIP, label: "Don't enforce" }],
|
|
3080
|
-
initialValue:
|
|
3201
|
+
initialValue: effective?.naming ?? SENTINEL_SKIP
|
|
3081
3202
|
});
|
|
3082
3203
|
if (isCancelled(selected)) return;
|
|
3083
3204
|
if (selected === SENTINEL_SKIP) {
|
|
@@ -3190,192 +3311,98 @@ async function handleBoundaries(config, state, opts) {
|
|
|
3190
3311
|
clack10.log.warn(`Boundary inference failed: ${err instanceof Error ? err.message : err}`);
|
|
3191
3312
|
}
|
|
3192
3313
|
}
|
|
3193
|
-
async function
|
|
3194
|
-
const result = await promptIntegrationsDeferred(
|
|
3195
|
-
state.hookManager,
|
|
3196
|
-
opts.tools,
|
|
3197
|
-
opts.tools.packageManager,
|
|
3198
|
-
opts.tools.isWorkspace,
|
|
3199
|
-
opts.projectRoot
|
|
3200
|
-
);
|
|
3201
|
-
state.visited.integrations = true;
|
|
3202
|
-
state.integrations = result.choice;
|
|
3203
|
-
state.deferredInstalls = state.deferredInstalls.filter((d) => !d.command.includes("lefthook"));
|
|
3204
|
-
if (result.lefthookInstall) {
|
|
3205
|
-
state.deferredInstalls.push(result.lefthookInstall);
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
|
|
3209
|
-
// src/utils/prompt-main-menu-hints.ts
|
|
3210
|
-
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3211
|
-
function fileLimitsHint(config) {
|
|
3212
|
-
const max = config.rules.maxFileLines;
|
|
3213
|
-
const test = config.rules.maxTestFileLines;
|
|
3214
|
-
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
3215
|
-
}
|
|
3216
|
-
function fileNamingHint(config, scanResult) {
|
|
3217
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3218
|
-
const naming = rootPkg.conventions?.fileNaming;
|
|
3219
|
-
if (!config.rules.enforceNaming) return "not enforced";
|
|
3220
|
-
if (naming) {
|
|
3221
|
-
const detected = scanResult.packages.some(
|
|
3222
|
-
(p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
|
|
3223
|
-
);
|
|
3224
|
-
return detected ? `${naming} (detected)` : naming;
|
|
3225
|
-
}
|
|
3226
|
-
return "mixed \u2014 will not enforce if skipped";
|
|
3227
|
-
}
|
|
3228
|
-
function fileNamingStatus(config) {
|
|
3229
|
-
if (!config.rules.enforceNaming) return "unconfigured";
|
|
3230
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3231
|
-
return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
|
|
3232
|
-
}
|
|
3233
|
-
function missingTestsHint(config) {
|
|
3234
|
-
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3235
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3236
|
-
const pattern = rootPkg.structure?.testPattern;
|
|
3237
|
-
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3238
|
-
}
|
|
3239
|
-
function coverageHint(config, hasTestRunner) {
|
|
3240
|
-
if (config.rules.testCoverage === 0) return "disabled";
|
|
3241
|
-
if (!hasTestRunner)
|
|
3242
|
-
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3243
|
-
const isMonorepo = config.packages.length > 1;
|
|
3244
|
-
if (isMonorepo) {
|
|
3245
|
-
const withCov = config.packages.filter(
|
|
3246
|
-
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3247
|
-
);
|
|
3248
|
-
const exempt = config.packages.length - withCov.length;
|
|
3249
|
-
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3250
|
-
}
|
|
3251
|
-
return `${config.rules.testCoverage}%`;
|
|
3252
|
-
}
|
|
3253
|
-
function advancedNamingHint(config) {
|
|
3254
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3255
|
-
if (!config.rules.enforceNaming) return "not enforced";
|
|
3256
|
-
const ok = import_chalk12.default.green("\u2713");
|
|
3257
|
-
const no = import_chalk12.default.dim("\u2717");
|
|
3258
|
-
const parts = [
|
|
3259
|
-
`${rootPkg.conventions?.fileNaming ? ok : no} file naming`,
|
|
3260
|
-
`${rootPkg.conventions?.componentNaming ? ok : no} components`,
|
|
3261
|
-
`${rootPkg.conventions?.hookNaming ? ok : no} hooks`,
|
|
3262
|
-
`${rootPkg.conventions?.importAlias ? ok : no} alias`
|
|
3263
|
-
];
|
|
3264
|
-
return parts.join(import_chalk12.default.dim(", "));
|
|
3265
|
-
}
|
|
3266
|
-
function integrationsHint(state) {
|
|
3267
|
-
if (!state.visited.integrations || !state.integrations)
|
|
3268
|
-
return "not configured \u2014 select to set up";
|
|
3269
|
-
const items = [];
|
|
3270
|
-
if (state.integrations.preCommitHook) items.push(import_chalk12.default.green("pre-commit"));
|
|
3271
|
-
if (state.integrations.typecheckHook) items.push(import_chalk12.default.green("typecheck"));
|
|
3272
|
-
if (state.integrations.lintHook) items.push(import_chalk12.default.green("lint"));
|
|
3273
|
-
if (state.integrations.claudeCodeHook) items.push(import_chalk12.default.green("Claude"));
|
|
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";
|
|
3277
|
-
}
|
|
3278
|
-
function packageOverridesHint(config) {
|
|
3279
|
-
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3280
|
-
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3281
|
-
const customized = editable.filter(
|
|
3282
|
-
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3283
|
-
).length;
|
|
3284
|
-
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3285
|
-
}
|
|
3286
|
-
function boundariesHint(config, state) {
|
|
3287
|
-
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3288
|
-
const deny = config.boundaries?.deny;
|
|
3289
|
-
if (!deny) return "enabled";
|
|
3290
|
-
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3291
|
-
const pkgCount = Object.keys(deny).length;
|
|
3292
|
-
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3293
|
-
}
|
|
3294
|
-
function advancedNamingStatus(config) {
|
|
3295
|
-
if (!config.rules.enforceNaming) return "unconfigured";
|
|
3314
|
+
async function handleAiContext(config) {
|
|
3296
3315
|
const rootPkg = getRootPackage(config.packages);
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
if (hasFile || hasComp || hasHook || hasAlias) return "partial";
|
|
3303
|
-
return "unconfigured";
|
|
3304
|
-
}
|
|
3305
|
-
function packageOverridesStatus(config) {
|
|
3306
|
-
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3307
|
-
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3308
|
-
const customized = editable.some(
|
|
3309
|
-
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3310
|
-
);
|
|
3311
|
-
return customized ? "ok" : "unconfigured";
|
|
3312
|
-
}
|
|
3313
|
-
function statusIcon(status) {
|
|
3314
|
-
if (status === "ok") return import_chalk12.default.green("\u2713");
|
|
3315
|
-
if (status === "needs-input") return import_chalk12.default.yellow("?");
|
|
3316
|
-
if (status === "unconfigured") return import_chalk12.default.dim("-");
|
|
3317
|
-
return import_chalk12.default.yellow("~");
|
|
3318
|
-
}
|
|
3319
|
-
function buildMainMenuOptions(config, scanResult, state) {
|
|
3320
|
-
const namingStatus = fileNamingStatus(config);
|
|
3321
|
-
const coverageStatus = config.rules.testCoverage === 0 ? "unconfigured" : !state.hasTestRunner ? "partial" : "ok";
|
|
3322
|
-
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "unconfigured";
|
|
3323
|
-
const options = [
|
|
3324
|
-
{
|
|
3325
|
-
value: "fileLimits",
|
|
3326
|
-
label: `${statusIcon("ok")} Max file size`,
|
|
3327
|
-
hint: fileLimitsHint(config)
|
|
3328
|
-
},
|
|
3329
|
-
{
|
|
3330
|
-
value: "fileNaming",
|
|
3331
|
-
label: `${statusIcon(namingStatus)} Default file naming`,
|
|
3332
|
-
hint: fileNamingHint(config, scanResult)
|
|
3333
|
-
},
|
|
3334
|
-
{
|
|
3335
|
-
value: "missingTests",
|
|
3336
|
-
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3337
|
-
hint: missingTestsHint(config)
|
|
3338
|
-
},
|
|
3339
|
-
{
|
|
3340
|
-
value: "coverage",
|
|
3341
|
-
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3342
|
-
hint: coverageHint(config, state.hasTestRunner)
|
|
3343
|
-
},
|
|
3344
|
-
{
|
|
3345
|
-
value: "advancedNaming",
|
|
3346
|
-
label: `${statusIcon(advancedNamingStatus(config))} Advanced naming`,
|
|
3347
|
-
hint: advancedNamingHint(config)
|
|
3348
|
-
}
|
|
3349
|
-
];
|
|
3350
|
-
if (config.packages.length > 1) {
|
|
3351
|
-
const bIcon = statusIcon(
|
|
3352
|
-
state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
|
|
3353
|
-
);
|
|
3354
|
-
const poIcon = statusIcon(packageOverridesStatus(config));
|
|
3355
|
-
options.push(
|
|
3316
|
+
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
3317
|
+
while (true) {
|
|
3318
|
+
const ok = import_chalk13.default.green("\u2713");
|
|
3319
|
+
const unset = import_chalk13.default.dim("-");
|
|
3320
|
+
const options = [
|
|
3356
3321
|
{
|
|
3357
|
-
value: "
|
|
3358
|
-
label: `${
|
|
3359
|
-
hint:
|
|
3322
|
+
value: "componentNaming",
|
|
3323
|
+
label: `${rootPkg.conventions.componentNaming ? ok : unset} Component exports`,
|
|
3324
|
+
hint: rootPkg.conventions.componentNaming ?? "not set"
|
|
3360
3325
|
},
|
|
3361
|
-
{
|
|
3362
|
-
|
|
3326
|
+
{
|
|
3327
|
+
value: "hookNaming",
|
|
3328
|
+
label: `${rootPkg.conventions.hookNaming ? ok : unset} Hook exports`,
|
|
3329
|
+
hint: rootPkg.conventions.hookNaming ?? "not set"
|
|
3330
|
+
},
|
|
3331
|
+
{
|
|
3332
|
+
value: "importAlias",
|
|
3333
|
+
label: `${rootPkg.conventions.importAlias ? ok : unset} Import alias`,
|
|
3334
|
+
hint: rootPkg.conventions.importAlias ?? "not set"
|
|
3335
|
+
},
|
|
3336
|
+
{ value: "back", label: " Back" }
|
|
3337
|
+
];
|
|
3338
|
+
const choice = await clack10.select({
|
|
3339
|
+
message: "AI context \u2014 conventions written to context.md for AI tools",
|
|
3340
|
+
options
|
|
3341
|
+
});
|
|
3342
|
+
if (isCancelled(choice) || choice === "back") return;
|
|
3343
|
+
if (choice === "componentNaming") {
|
|
3344
|
+
const selected = await clack10.select({
|
|
3345
|
+
message: "Component export naming (e.g. UserProfile)",
|
|
3346
|
+
options: [
|
|
3347
|
+
...COMPONENT_NAMING_OPTIONS,
|
|
3348
|
+
{ value: SENTINEL_CLEAR, label: "Clear (no convention)" }
|
|
3349
|
+
],
|
|
3350
|
+
initialValue: rootPkg.conventions.componentNaming ?? SENTINEL_CLEAR
|
|
3351
|
+
});
|
|
3352
|
+
if (isCancelled(selected)) continue;
|
|
3353
|
+
rootPkg.conventions.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
3354
|
+
}
|
|
3355
|
+
if (choice === "hookNaming") {
|
|
3356
|
+
const selected = await clack10.select({
|
|
3357
|
+
message: "Hook export naming (e.g. useAuth)",
|
|
3358
|
+
options: [
|
|
3359
|
+
...HOOK_NAMING_OPTIONS,
|
|
3360
|
+
{ value: SENTINEL_CLEAR, label: "Clear (no convention)" }
|
|
3361
|
+
],
|
|
3362
|
+
initialValue: rootPkg.conventions.hookNaming ?? SENTINEL_CLEAR
|
|
3363
|
+
});
|
|
3364
|
+
if (isCancelled(selected)) continue;
|
|
3365
|
+
rootPkg.conventions.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
3366
|
+
}
|
|
3367
|
+
if (choice === "importAlias") {
|
|
3368
|
+
const selected = await clack10.select({
|
|
3369
|
+
message: "Import alias pattern",
|
|
3370
|
+
options: [
|
|
3371
|
+
{ value: "@/*", label: "@/*", hint: "import { x } from '@/utils'" },
|
|
3372
|
+
{ value: "~/*", label: "~/*", hint: "import { x } from '~/utils'" },
|
|
3373
|
+
{ value: SENTINEL_CUSTOM, label: "Custom..." },
|
|
3374
|
+
{ value: SENTINEL_CLEAR, label: "Clear (no alias)" }
|
|
3375
|
+
],
|
|
3376
|
+
initialValue: rootPkg.conventions.importAlias ?? SENTINEL_CLEAR
|
|
3377
|
+
});
|
|
3378
|
+
if (isCancelled(selected)) continue;
|
|
3379
|
+
if (selected === SENTINEL_CLEAR) {
|
|
3380
|
+
rootPkg.conventions.importAlias = void 0;
|
|
3381
|
+
} else if (selected === SENTINEL_CUSTOM) {
|
|
3382
|
+
const result = await clack10.text({
|
|
3383
|
+
message: "Custom import alias (e.g. #/*)?",
|
|
3384
|
+
initialValue: rootPkg.conventions.importAlias ?? "",
|
|
3385
|
+
placeholder: "e.g. #/*",
|
|
3386
|
+
validate: (v) => {
|
|
3387
|
+
if (typeof v !== "string" || !v.trim()) return "Alias cannot be empty";
|
|
3388
|
+
if (!/^[a-zA-Z@~#$][a-zA-Z0-9@~#$_-]*\/\*$/.test(v.trim()))
|
|
3389
|
+
return "Must match pattern like @/*, ~/*, or #src/*";
|
|
3390
|
+
}
|
|
3391
|
+
});
|
|
3392
|
+
if (isCancelled(result)) continue;
|
|
3393
|
+
rootPkg.conventions.importAlias = result.trim();
|
|
3394
|
+
} else {
|
|
3395
|
+
rootPkg.conventions.importAlias = selected;
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3363
3398
|
}
|
|
3364
|
-
const iIcon = state.visited.integrations ? statusIcon("ok") : statusIcon("unconfigured");
|
|
3365
|
-
options.push(
|
|
3366
|
-
{ value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
|
|
3367
|
-
{ value: "reset", label: " Reset all to defaults" },
|
|
3368
|
-
{ value: "review", label: " Review scan details", hint: "detected stack & conventions" },
|
|
3369
|
-
{ value: "done", label: " Done \u2014 write config" }
|
|
3370
|
-
);
|
|
3371
|
-
return options;
|
|
3372
3399
|
}
|
|
3373
3400
|
|
|
3374
3401
|
// src/utils/prompt-main-menu.ts
|
|
3375
3402
|
async function promptMainMenu(config, scanResult, opts) {
|
|
3376
3403
|
const originalConfig = structuredClone(config);
|
|
3377
3404
|
const state = {
|
|
3378
|
-
visited: {
|
|
3405
|
+
visited: { boundaries: false },
|
|
3379
3406
|
deferredInstalls: [],
|
|
3380
3407
|
hasTestRunner: opts.hasTestRunner,
|
|
3381
3408
|
hookManager: opts.hookManager
|
|
@@ -3402,10 +3429,9 @@ async function promptMainMenu(config, scanResult, opts) {
|
|
|
3402
3429
|
if (choice === "fileNaming") await handleFileNaming(config, scanResult);
|
|
3403
3430
|
if (choice === "missingTests") await handleMissingTests(config);
|
|
3404
3431
|
if (choice === "coverage") await handleCoverage(config, state, opts);
|
|
3405
|
-
if (choice === "
|
|
3432
|
+
if (choice === "aiContext") await handleAiContext(config);
|
|
3406
3433
|
if (choice === "packageOverrides") await handlePackageOverrides(config);
|
|
3407
3434
|
if (choice === "boundaries") await handleBoundaries(config, state, opts);
|
|
3408
|
-
if (choice === "integrations") await handleIntegrations(state, opts);
|
|
3409
3435
|
if (choice === "review") clack11.note(formatScanResultsText(scanResult), "Scan details");
|
|
3410
3436
|
if (choice === "reset") {
|
|
3411
3437
|
const confirmed = await clack11.confirm({
|
|
@@ -3416,8 +3442,7 @@ async function promptMainMenu(config, scanResult, opts) {
|
|
|
3416
3442
|
if (confirmed) {
|
|
3417
3443
|
Object.assign(config, structuredClone(originalConfig));
|
|
3418
3444
|
state.deferredInstalls = [];
|
|
3419
|
-
state.visited = {
|
|
3420
|
-
state.integrations = void 0;
|
|
3445
|
+
state.visited = { boundaries: false };
|
|
3421
3446
|
clack11.log.info("Reset all settings to scan-detected defaults.");
|
|
3422
3447
|
}
|
|
3423
3448
|
}
|
|
@@ -3425,37 +3450,26 @@ async function promptMainMenu(config, scanResult, opts) {
|
|
|
3425
3450
|
return state;
|
|
3426
3451
|
}
|
|
3427
3452
|
|
|
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
|
-
}
|
|
3453
|
+
// src/utils/prompt-prereqs.ts
|
|
3454
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
3455
|
+
var path17 = __toESM(require("path"), 1);
|
|
3456
|
+
var clack12 = __toESM(require("@clack/prompts"), 1);
|
|
3457
|
+
var import_chalk15 = __toESM(require("chalk"), 1);
|
|
3444
3458
|
|
|
3445
3459
|
// src/commands/init-hooks.ts
|
|
3446
|
-
var
|
|
3447
|
-
var
|
|
3448
|
-
var
|
|
3460
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
3461
|
+
var path16 = __toESM(require("path"), 1);
|
|
3462
|
+
var import_chalk14 = __toESM(require("chalk"), 1);
|
|
3449
3463
|
var import_yaml = require("yaml");
|
|
3450
3464
|
|
|
3451
3465
|
// src/commands/resolve-typecheck.ts
|
|
3452
|
-
var
|
|
3453
|
-
var
|
|
3466
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
3467
|
+
var path15 = __toESM(require("path"), 1);
|
|
3454
3468
|
function hasTurboTask(projectRoot, taskName) {
|
|
3455
|
-
const turboPath =
|
|
3456
|
-
if (!
|
|
3469
|
+
const turboPath = path15.join(projectRoot, "turbo.json");
|
|
3470
|
+
if (!fs15.existsSync(turboPath)) return false;
|
|
3457
3471
|
try {
|
|
3458
|
-
const turbo = JSON.parse(
|
|
3472
|
+
const turbo = JSON.parse(fs15.readFileSync(turboPath, "utf-8"));
|
|
3459
3473
|
const tasks = turbo.tasks ?? turbo.pipeline ?? {};
|
|
3460
3474
|
return taskName in tasks;
|
|
3461
3475
|
} catch {
|
|
@@ -3466,10 +3480,10 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3466
3480
|
if (hasTurboTask(projectRoot, "typecheck")) {
|
|
3467
3481
|
return { command: "npx turbo typecheck", label: "turbo typecheck" };
|
|
3468
3482
|
}
|
|
3469
|
-
const pkgJsonPath =
|
|
3470
|
-
if (
|
|
3483
|
+
const pkgJsonPath = path15.join(projectRoot, "package.json");
|
|
3484
|
+
if (fs15.existsSync(pkgJsonPath)) {
|
|
3471
3485
|
try {
|
|
3472
|
-
const pkg = JSON.parse(
|
|
3486
|
+
const pkg = JSON.parse(fs15.readFileSync(pkgJsonPath, "utf-8"));
|
|
3473
3487
|
if (pkg.scripts?.typecheck) {
|
|
3474
3488
|
const pm = packageManager ?? "npm";
|
|
3475
3489
|
return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
|
|
@@ -3477,7 +3491,7 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3477
3491
|
} catch {
|
|
3478
3492
|
}
|
|
3479
3493
|
}
|
|
3480
|
-
if (
|
|
3494
|
+
if (fs15.existsSync(path15.join(projectRoot, "tsconfig.json"))) {
|
|
3481
3495
|
return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
|
|
3482
3496
|
}
|
|
3483
3497
|
return {
|
|
@@ -3487,36 +3501,36 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3487
3501
|
|
|
3488
3502
|
// src/commands/init-hooks.ts
|
|
3489
3503
|
function setupPreCommitHook(projectRoot) {
|
|
3490
|
-
const lefthookPath =
|
|
3491
|
-
if (
|
|
3504
|
+
const lefthookPath = path16.join(projectRoot, "lefthook.yml");
|
|
3505
|
+
if (fs16.existsSync(lefthookPath)) {
|
|
3492
3506
|
addLefthookPreCommit(lefthookPath);
|
|
3493
|
-
console.log(` ${
|
|
3507
|
+
console.log(` ${import_chalk14.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
3494
3508
|
return "lefthook.yml";
|
|
3495
3509
|
}
|
|
3496
|
-
const huskyDir =
|
|
3497
|
-
if (
|
|
3510
|
+
const huskyDir = path16.join(projectRoot, ".husky");
|
|
3511
|
+
if (fs16.existsSync(huskyDir)) {
|
|
3498
3512
|
writeHuskyPreCommit(huskyDir);
|
|
3499
|
-
console.log(` ${
|
|
3513
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
3500
3514
|
return ".husky/pre-commit";
|
|
3501
3515
|
}
|
|
3502
|
-
const gitDir =
|
|
3503
|
-
if (
|
|
3504
|
-
const hooksDir =
|
|
3505
|
-
if (!
|
|
3506
|
-
|
|
3516
|
+
const gitDir = path16.join(projectRoot, ".git");
|
|
3517
|
+
if (fs16.existsSync(gitDir)) {
|
|
3518
|
+
const hooksDir = path16.join(gitDir, "hooks");
|
|
3519
|
+
if (!fs16.existsSync(hooksDir)) {
|
|
3520
|
+
fs16.mkdirSync(hooksDir, { recursive: true });
|
|
3507
3521
|
}
|
|
3508
3522
|
writeGitHookPreCommit(hooksDir);
|
|
3509
|
-
console.log(` ${
|
|
3523
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
3510
3524
|
return ".git/hooks/pre-commit";
|
|
3511
3525
|
}
|
|
3512
3526
|
return void 0;
|
|
3513
3527
|
}
|
|
3514
3528
|
function writeGitHookPreCommit(hooksDir) {
|
|
3515
|
-
const hookPath =
|
|
3516
|
-
if (
|
|
3517
|
-
const existing =
|
|
3529
|
+
const hookPath = path16.join(hooksDir, "pre-commit");
|
|
3530
|
+
if (fs16.existsSync(hookPath)) {
|
|
3531
|
+
const existing = fs16.readFileSync(hookPath, "utf-8");
|
|
3518
3532
|
if (existing.includes("viberails")) return;
|
|
3519
|
-
|
|
3533
|
+
fs16.writeFileSync(
|
|
3520
3534
|
hookPath,
|
|
3521
3535
|
`${existing.trimEnd()}
|
|
3522
3536
|
|
|
@@ -3533,10 +3547,10 @@ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails chec
|
|
|
3533
3547
|
"if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
|
|
3534
3548
|
""
|
|
3535
3549
|
].join("\n");
|
|
3536
|
-
|
|
3550
|
+
fs16.writeFileSync(hookPath, script, { mode: 493 });
|
|
3537
3551
|
}
|
|
3538
3552
|
function addLefthookPreCommit(lefthookPath) {
|
|
3539
|
-
const content =
|
|
3553
|
+
const content = fs16.readFileSync(lefthookPath, "utf-8");
|
|
3540
3554
|
if (content.includes("viberails")) return;
|
|
3541
3555
|
const doc = (0, import_yaml.parse)(content) ?? {};
|
|
3542
3556
|
if (!doc["pre-commit"]) {
|
|
@@ -3548,28 +3562,28 @@ function addLefthookPreCommit(lefthookPath) {
|
|
|
3548
3562
|
doc["pre-commit"].commands.viberails = {
|
|
3549
3563
|
run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
|
|
3550
3564
|
};
|
|
3551
|
-
|
|
3565
|
+
fs16.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
|
|
3552
3566
|
}
|
|
3553
3567
|
function detectHookManager(projectRoot) {
|
|
3554
|
-
if (
|
|
3555
|
-
if (
|
|
3568
|
+
if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
3569
|
+
if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
|
|
3556
3570
|
return void 0;
|
|
3557
3571
|
}
|
|
3558
3572
|
function setupClaudeCodeHook(projectRoot) {
|
|
3559
|
-
const claudeDir =
|
|
3560
|
-
if (!
|
|
3561
|
-
|
|
3573
|
+
const claudeDir = path16.join(projectRoot, ".claude");
|
|
3574
|
+
if (!fs16.existsSync(claudeDir)) {
|
|
3575
|
+
fs16.mkdirSync(claudeDir, { recursive: true });
|
|
3562
3576
|
}
|
|
3563
|
-
const settingsPath =
|
|
3577
|
+
const settingsPath = path16.join(claudeDir, "settings.json");
|
|
3564
3578
|
let settings = {};
|
|
3565
|
-
if (
|
|
3579
|
+
if (fs16.existsSync(settingsPath)) {
|
|
3566
3580
|
try {
|
|
3567
|
-
settings = JSON.parse(
|
|
3581
|
+
settings = JSON.parse(fs16.readFileSync(settingsPath, "utf-8"));
|
|
3568
3582
|
} catch {
|
|
3569
3583
|
console.warn(
|
|
3570
|
-
` ${
|
|
3584
|
+
` ${import_chalk14.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
3571
3585
|
);
|
|
3572
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
3586
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk14.default.cyan("viberails init --force")}`);
|
|
3573
3587
|
return;
|
|
3574
3588
|
}
|
|
3575
3589
|
}
|
|
@@ -3590,30 +3604,30 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3590
3604
|
}
|
|
3591
3605
|
];
|
|
3592
3606
|
settings.hooks = hooks;
|
|
3593
|
-
|
|
3607
|
+
fs16.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
3594
3608
|
`);
|
|
3595
|
-
console.log(` ${
|
|
3609
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
3596
3610
|
}
|
|
3597
3611
|
function setupClaudeMdReference(projectRoot) {
|
|
3598
|
-
const claudeMdPath =
|
|
3612
|
+
const claudeMdPath = path16.join(projectRoot, "CLAUDE.md");
|
|
3599
3613
|
let content = "";
|
|
3600
|
-
if (
|
|
3601
|
-
content =
|
|
3614
|
+
if (fs16.existsSync(claudeMdPath)) {
|
|
3615
|
+
content = fs16.readFileSync(claudeMdPath, "utf-8");
|
|
3602
3616
|
}
|
|
3603
3617
|
if (content.includes("@.viberails/context.md")) return;
|
|
3604
3618
|
const ref = "\n@.viberails/context.md\n";
|
|
3605
3619
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
3606
|
-
|
|
3607
|
-
console.log(` ${
|
|
3620
|
+
fs16.writeFileSync(claudeMdPath, prefix + ref);
|
|
3621
|
+
console.log(` ${import_chalk14.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
3608
3622
|
}
|
|
3609
3623
|
function setupGithubAction(projectRoot, packageManager, options) {
|
|
3610
|
-
const workflowDir =
|
|
3611
|
-
const workflowPath =
|
|
3612
|
-
if (
|
|
3613
|
-
const existing =
|
|
3624
|
+
const workflowDir = path16.join(projectRoot, ".github", "workflows");
|
|
3625
|
+
const workflowPath = path16.join(workflowDir, "viberails.yml");
|
|
3626
|
+
if (fs16.existsSync(workflowPath)) {
|
|
3627
|
+
const existing = fs16.readFileSync(workflowPath, "utf-8");
|
|
3614
3628
|
if (existing.includes("viberails")) return void 0;
|
|
3615
3629
|
}
|
|
3616
|
-
|
|
3630
|
+
fs16.mkdirSync(workflowDir, { recursive: true });
|
|
3617
3631
|
const pm = packageManager || "npm";
|
|
3618
3632
|
const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
|
|
3619
3633
|
const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
|
|
@@ -3667,30 +3681,212 @@ function setupGithubAction(projectRoot, packageManager, options) {
|
|
|
3667
3681
|
""
|
|
3668
3682
|
);
|
|
3669
3683
|
const content = lines.filter((l) => l !== void 0).join("\n");
|
|
3670
|
-
|
|
3684
|
+
fs16.writeFileSync(workflowPath, content);
|
|
3671
3685
|
return ".github/workflows/viberails.yml";
|
|
3672
3686
|
}
|
|
3673
3687
|
function writeHuskyPreCommit(huskyDir) {
|
|
3674
|
-
const hookPath =
|
|
3688
|
+
const hookPath = path16.join(huskyDir, "pre-commit");
|
|
3675
3689
|
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 =
|
|
3690
|
+
if (fs16.existsSync(hookPath)) {
|
|
3691
|
+
const existing = fs16.readFileSync(hookPath, "utf-8");
|
|
3678
3692
|
if (!existing.includes("viberails")) {
|
|
3679
|
-
|
|
3693
|
+
fs16.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
3680
3694
|
${cmd}
|
|
3681
3695
|
`);
|
|
3682
3696
|
}
|
|
3683
3697
|
return;
|
|
3684
3698
|
}
|
|
3685
|
-
|
|
3699
|
+
fs16.writeFileSync(hookPath, `#!/bin/sh
|
|
3686
3700
|
${cmd}
|
|
3687
3701
|
`, { mode: 493 });
|
|
3688
3702
|
}
|
|
3689
3703
|
|
|
3704
|
+
// src/utils/prompt-prereqs.ts
|
|
3705
|
+
function buildVitestInstallCommand(pm, isWorkspace) {
|
|
3706
|
+
if (pm === "yarn") return "yarn add -D vitest";
|
|
3707
|
+
if (pm === "npm") return "npm install -D vitest";
|
|
3708
|
+
return isWorkspace ? "pnpm add -D -w vitest" : "pnpm add -D vitest";
|
|
3709
|
+
}
|
|
3710
|
+
function statusIcon2(status) {
|
|
3711
|
+
if (status === "ok") return import_chalk15.default.green("\u2713");
|
|
3712
|
+
if (status === "missing") return import_chalk15.default.yellow("!");
|
|
3713
|
+
if (status === "skipped") return import_chalk15.default.dim("\u2717");
|
|
3714
|
+
return import_chalk15.default.dim("-");
|
|
3715
|
+
}
|
|
3716
|
+
function buildReadinessNote(state) {
|
|
3717
|
+
const lines = [];
|
|
3718
|
+
const tr = state.testRunner;
|
|
3719
|
+
lines.push(
|
|
3720
|
+
`${statusIcon2(tr.status)} Test runner ${tr.label ?? (tr.status === "skipped" ? "skipped" : "not detected")}`
|
|
3721
|
+
);
|
|
3722
|
+
const hm = state.hookManager;
|
|
3723
|
+
lines.push(
|
|
3724
|
+
`${statusIcon2(hm.status)} Hook manager ${hm.label ?? (hm.status === "skipped" ? "skipped" : "not detected")}`
|
|
3725
|
+
);
|
|
3726
|
+
const li = state.linter;
|
|
3727
|
+
lines.push(`${statusIcon2(li.status)} Linter ${li.label ?? "none"}`);
|
|
3728
|
+
const tc = state.typecheck;
|
|
3729
|
+
if (tc.status === "ok") {
|
|
3730
|
+
lines.push(`${statusIcon2("ok")} Typecheck ${tc.label}`);
|
|
3731
|
+
} else if (tc.status === "skipped") {
|
|
3732
|
+
lines.push(`${statusIcon2("skipped")} Typecheck skipped`);
|
|
3733
|
+
} else {
|
|
3734
|
+
lines.push(
|
|
3735
|
+
`${statusIcon2("missing")} Typecheck needs root tsconfig.json, typecheck script, or turbo task`
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
return lines.join("\n");
|
|
3739
|
+
}
|
|
3740
|
+
function hasMissing(state) {
|
|
3741
|
+
return state.testRunner.status === "missing" || state.hookManager.status === "missing" || state.typecheck.status === "missing";
|
|
3742
|
+
}
|
|
3743
|
+
async function promptPrereqs(projectRoot, scanResult, hookManager, packageManager, isWorkspace) {
|
|
3744
|
+
let hasTestRunner = !!scanResult.stack.testRunner;
|
|
3745
|
+
let currentHookManager = hookManager;
|
|
3746
|
+
let skipCoverage = false;
|
|
3747
|
+
let skipHooks = false;
|
|
3748
|
+
const linterName = scanResult.stack.linter?.name;
|
|
3749
|
+
const linterLabel = linterName === "biome" ? "Biome" : linterName === "eslint" ? "ESLint" : linterName;
|
|
3750
|
+
const typecheckResolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3751
|
+
const state = {
|
|
3752
|
+
testRunner: hasTestRunner ? { status: "ok", label: scanResult.stack.testRunner?.name } : { status: "missing" },
|
|
3753
|
+
hookManager: currentHookManager ? { status: "ok", label: currentHookManager } : { status: "missing" },
|
|
3754
|
+
linter: linterName ? { status: "ok", label: linterLabel } : { status: "none" },
|
|
3755
|
+
typecheck: typecheckResolved.label ? { status: "ok", label: typecheckResolved.label } : { status: "missing", reason: typecheckResolved.reason }
|
|
3756
|
+
};
|
|
3757
|
+
if (!hasMissing(state)) {
|
|
3758
|
+
return {
|
|
3759
|
+
hasTestRunner,
|
|
3760
|
+
hookManager: currentHookManager,
|
|
3761
|
+
skipCoverage,
|
|
3762
|
+
skipHooks,
|
|
3763
|
+
typecheckLabel: typecheckResolved.label
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
if (state.testRunner.status === "missing") {
|
|
3767
|
+
clack12.note(buildReadinessNote(state), "Project readiness");
|
|
3768
|
+
const cmd = buildVitestInstallCommand(packageManager, isWorkspace);
|
|
3769
|
+
const choice = await clack12.select({
|
|
3770
|
+
message: "No test runner detected. Coverage checks require one.",
|
|
3771
|
+
options: [
|
|
3772
|
+
{ value: "install", label: "Install vitest", hint: cmd },
|
|
3773
|
+
{ value: "skip", label: "Skip \u2014 disable coverage checks" },
|
|
3774
|
+
{ value: "exit", label: "Exit" }
|
|
3775
|
+
]
|
|
3776
|
+
});
|
|
3777
|
+
assertNotCancelled(choice);
|
|
3778
|
+
if (choice === "install") {
|
|
3779
|
+
const s = clack12.spinner();
|
|
3780
|
+
s.start("Installing vitest...");
|
|
3781
|
+
const result = await spawnAsync(cmd, projectRoot);
|
|
3782
|
+
if (result.status === 0) {
|
|
3783
|
+
s.stop("Installed vitest");
|
|
3784
|
+
hasTestRunner = true;
|
|
3785
|
+
state.testRunner = { status: "ok", label: "vitest" };
|
|
3786
|
+
} else {
|
|
3787
|
+
s.stop("Failed to install vitest");
|
|
3788
|
+
clack12.log.warn(`Install manually: ${cmd}`);
|
|
3789
|
+
skipCoverage = true;
|
|
3790
|
+
state.testRunner = { status: "skipped" };
|
|
3791
|
+
}
|
|
3792
|
+
} else if (choice === "skip") {
|
|
3793
|
+
skipCoverage = true;
|
|
3794
|
+
state.testRunner = { status: "skipped" };
|
|
3795
|
+
} else {
|
|
3796
|
+
clack12.outro("Aborted.");
|
|
3797
|
+
process.exit(0);
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
if (state.hookManager.status === "missing") {
|
|
3801
|
+
clack12.note(buildReadinessNote(state), "Project readiness");
|
|
3802
|
+
const cmd = buildLefthookInstallCommand(packageManager, isWorkspace);
|
|
3803
|
+
const choice = await clack12.select({
|
|
3804
|
+
message: "No git hook manager detected. Pre-commit integration requires one.",
|
|
3805
|
+
options: [
|
|
3806
|
+
{ value: "install", label: "Install lefthook", hint: cmd },
|
|
3807
|
+
{ value: "skip", label: "Skip \u2014 no pre-commit integration" },
|
|
3808
|
+
{ value: "exit", label: "Exit" }
|
|
3809
|
+
]
|
|
3810
|
+
});
|
|
3811
|
+
assertNotCancelled(choice);
|
|
3812
|
+
if (choice === "install") {
|
|
3813
|
+
const s = clack12.spinner();
|
|
3814
|
+
s.start("Installing lefthook...");
|
|
3815
|
+
const result = await spawnAsync(cmd, projectRoot);
|
|
3816
|
+
if (result.status === 0) {
|
|
3817
|
+
s.stop("Installed lefthook");
|
|
3818
|
+
const ymlPath = path17.join(projectRoot, "lefthook.yml");
|
|
3819
|
+
if (!fs17.existsSync(ymlPath)) {
|
|
3820
|
+
fs17.writeFileSync(ymlPath, "# Managed by viberails\npre-commit:\n commands: {}\n");
|
|
3821
|
+
}
|
|
3822
|
+
currentHookManager = detectHookManager(projectRoot);
|
|
3823
|
+
state.hookManager = { status: "ok", label: currentHookManager ?? "lefthook" };
|
|
3824
|
+
} else {
|
|
3825
|
+
s.stop("Failed to install lefthook");
|
|
3826
|
+
clack12.log.warn(`Install manually: ${cmd}`);
|
|
3827
|
+
skipHooks = true;
|
|
3828
|
+
state.hookManager = { status: "skipped" };
|
|
3829
|
+
}
|
|
3830
|
+
} else if (choice === "skip") {
|
|
3831
|
+
skipHooks = true;
|
|
3832
|
+
state.hookManager = { status: "skipped" };
|
|
3833
|
+
} else {
|
|
3834
|
+
clack12.outro("Aborted.");
|
|
3835
|
+
process.exit(0);
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
if (state.typecheck.status === "missing") {
|
|
3839
|
+
clack12.note(buildReadinessNote(state), "Project readiness");
|
|
3840
|
+
const choice = await clack12.select({
|
|
3841
|
+
message: "No typecheck command found. Without this, pre-commit and CI typecheck hooks will be unavailable.",
|
|
3842
|
+
options: [
|
|
3843
|
+
{
|
|
3844
|
+
value: "continue",
|
|
3845
|
+
label: "Continue without typecheck",
|
|
3846
|
+
hint: "add a root tsconfig.json or typecheck script later, then re-run viberails"
|
|
3847
|
+
},
|
|
3848
|
+
{ value: "exit", label: "Exit \u2014 fix this first" }
|
|
3849
|
+
]
|
|
3850
|
+
});
|
|
3851
|
+
assertNotCancelled(choice);
|
|
3852
|
+
if (choice === "exit") {
|
|
3853
|
+
clack12.outro(
|
|
3854
|
+
"Add a root tsconfig.json, a typecheck script, or a turbo typecheck task, then re-run viberails."
|
|
3855
|
+
);
|
|
3856
|
+
process.exit(0);
|
|
3857
|
+
}
|
|
3858
|
+
state.typecheck = { status: "skipped" };
|
|
3859
|
+
}
|
|
3860
|
+
return {
|
|
3861
|
+
hasTestRunner,
|
|
3862
|
+
hookManager: currentHookManager,
|
|
3863
|
+
skipCoverage,
|
|
3864
|
+
skipHooks,
|
|
3865
|
+
typecheckLabel: typecheckResolved.label
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
// src/utils/update-gitignore.ts
|
|
3870
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
3871
|
+
var path18 = __toESM(require("path"), 1);
|
|
3872
|
+
function updateGitignore(projectRoot) {
|
|
3873
|
+
const gitignorePath = path18.join(projectRoot, ".gitignore");
|
|
3874
|
+
let content = "";
|
|
3875
|
+
if (fs18.existsSync(gitignorePath)) {
|
|
3876
|
+
content = fs18.readFileSync(gitignorePath, "utf-8");
|
|
3877
|
+
}
|
|
3878
|
+
if (!content.includes(".viberails/scan-result.json")) {
|
|
3879
|
+
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
3880
|
+
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
3881
|
+
`;
|
|
3882
|
+
fs18.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3690
3886
|
// src/commands/init-hooks-extra.ts
|
|
3691
3887
|
var fs19 = __toESM(require("fs"), 1);
|
|
3692
3888
|
var path19 = __toESM(require("path"), 1);
|
|
3693
|
-
var
|
|
3889
|
+
var import_chalk16 = __toESM(require("chalk"), 1);
|
|
3694
3890
|
var import_yaml2 = require("yaml");
|
|
3695
3891
|
function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
|
|
3696
3892
|
const lefthookPath = path19.join(projectRoot, "lefthook.yml");
|
|
@@ -3750,12 +3946,12 @@ ${command}
|
|
|
3750
3946
|
function setupTypecheckHook(projectRoot, packageManager) {
|
|
3751
3947
|
const resolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3752
3948
|
if (!resolved.command) {
|
|
3753
|
-
console.log(` ${
|
|
3949
|
+
console.log(` ${import_chalk16.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
|
|
3754
3950
|
return void 0;
|
|
3755
3951
|
}
|
|
3756
3952
|
const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
|
|
3757
3953
|
if (target) {
|
|
3758
|
-
console.log(` ${
|
|
3954
|
+
console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
|
|
3759
3955
|
}
|
|
3760
3956
|
return target;
|
|
3761
3957
|
}
|
|
@@ -3777,7 +3973,7 @@ function setupLintHook(projectRoot, linter) {
|
|
|
3777
3973
|
}
|
|
3778
3974
|
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
3779
3975
|
if (target) {
|
|
3780
|
-
console.log(` ${
|
|
3976
|
+
console.log(` ${import_chalk16.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
3781
3977
|
}
|
|
3782
3978
|
return target;
|
|
3783
3979
|
}
|
|
@@ -3786,7 +3982,7 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3786
3982
|
if (integrations.preCommitHook) {
|
|
3787
3983
|
const t = setupPreCommitHook(projectRoot);
|
|
3788
3984
|
if (t && opts.lefthookExpected && !t.includes("lefthook")) {
|
|
3789
|
-
console.log(` ${
|
|
3985
|
+
console.log(` ${import_chalk16.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
|
|
3790
3986
|
}
|
|
3791
3987
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
3792
3988
|
}
|
|
@@ -3819,10 +4015,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3819
4015
|
// src/commands/init-non-interactive.ts
|
|
3820
4016
|
var fs20 = __toESM(require("fs"), 1);
|
|
3821
4017
|
var path20 = __toESM(require("path"), 1);
|
|
3822
|
-
var
|
|
4018
|
+
var clack13 = __toESM(require("@clack/prompts"), 1);
|
|
3823
4019
|
var import_config8 = require("@viberails/config");
|
|
3824
4020
|
var import_scanner2 = require("@viberails/scanner");
|
|
3825
|
-
var
|
|
4021
|
+
var import_chalk17 = __toESM(require("chalk"), 1);
|
|
3826
4022
|
|
|
3827
4023
|
// src/utils/filter-confidence.ts
|
|
3828
4024
|
function filterHighConfidence(conventions, meta) {
|
|
@@ -3843,7 +4039,7 @@ function getExemptedPackages(config) {
|
|
|
3843
4039
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3844
4040
|
}
|
|
3845
4041
|
async function initNonInteractive(projectRoot, configPath) {
|
|
3846
|
-
const s =
|
|
4042
|
+
const s = clack13.spinner();
|
|
3847
4043
|
s.start("Scanning project...");
|
|
3848
4044
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3849
4045
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
@@ -3858,11 +4054,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3858
4054
|
const exempted = getExemptedPackages(config);
|
|
3859
4055
|
if (exempted.length > 0) {
|
|
3860
4056
|
console.log(
|
|
3861
|
-
` ${
|
|
4057
|
+
` ${import_chalk17.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk17.default.dim("(types-only)")}`
|
|
3862
4058
|
);
|
|
3863
4059
|
}
|
|
3864
4060
|
if (config.packages.length > 1) {
|
|
3865
|
-
const bs =
|
|
4061
|
+
const bs = clack13.spinner();
|
|
3866
4062
|
bs.start("Building import graph...");
|
|
3867
4063
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3868
4064
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -3895,14 +4091,14 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3895
4091
|
const hookManager = detectHookManager(projectRoot);
|
|
3896
4092
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3897
4093
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3898
|
-
const ok =
|
|
4094
|
+
const ok = import_chalk17.default.green("\u2713");
|
|
3899
4095
|
const created = [
|
|
3900
4096
|
`${ok} ${path20.basename(configPath)}`,
|
|
3901
4097
|
`${ok} .viberails/context.md`,
|
|
3902
4098
|
`${ok} .viberails/scan-result.json`,
|
|
3903
4099
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3904
4100
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3905
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
4101
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk17.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3906
4102
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3907
4103
|
].filter(Boolean);
|
|
3908
4104
|
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
@@ -3927,8 +4123,8 @@ async function initCommand(options, cwd) {
|
|
|
3927
4123
|
return initInteractive(projectRoot, configPath, options);
|
|
3928
4124
|
}
|
|
3929
4125
|
console.log(
|
|
3930
|
-
`${
|
|
3931
|
-
Run ${
|
|
4126
|
+
`${import_chalk18.default.yellow("!")} viberails is already initialized.
|
|
4127
|
+
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
4128
|
);
|
|
3933
4129
|
return;
|
|
3934
4130
|
}
|
|
@@ -3936,11 +4132,11 @@ async function initCommand(options, cwd) {
|
|
|
3936
4132
|
await initInteractive(projectRoot, configPath, options);
|
|
3937
4133
|
}
|
|
3938
4134
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3939
|
-
|
|
4135
|
+
clack14.intro("viberails");
|
|
3940
4136
|
if (fs21.existsSync(configPath) && !options.force) {
|
|
3941
4137
|
const action = await promptExistingConfigAction(path21.basename(configPath));
|
|
3942
4138
|
if (action === "cancel") {
|
|
3943
|
-
|
|
4139
|
+
clack14.outro("Aborted. No files were written.");
|
|
3944
4140
|
return;
|
|
3945
4141
|
}
|
|
3946
4142
|
if (action === "edit") {
|
|
@@ -3954,45 +4150,60 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3954
4150
|
`${path21.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3955
4151
|
);
|
|
3956
4152
|
if (!replace) {
|
|
3957
|
-
|
|
4153
|
+
clack14.outro("Aborted. No files were written.");
|
|
3958
4154
|
return;
|
|
3959
4155
|
}
|
|
3960
4156
|
}
|
|
3961
|
-
const s =
|
|
4157
|
+
const s = clack14.spinner();
|
|
3962
4158
|
s.start("Scanning project...");
|
|
3963
4159
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3964
4160
|
const config = (0, import_config9.generateConfig)(scanResult);
|
|
3965
4161
|
s.stop("Scan complete");
|
|
3966
4162
|
if (scanResult.statistics.totalFiles === 0) {
|
|
3967
|
-
|
|
4163
|
+
clack14.log.warn(
|
|
3968
4164
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3969
4165
|
);
|
|
3970
4166
|
}
|
|
3971
|
-
const hasTestRunner = !!scanResult.stack.testRunner;
|
|
3972
|
-
const hookManager = detectHookManager(projectRoot);
|
|
3973
|
-
const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
|
|
3974
4167
|
const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
|
|
4168
|
+
const packageManager = rootPkgStack?.packageManager?.split("@")[0] ?? "npm";
|
|
4169
|
+
const isWorkspace = config.packages.length > 1;
|
|
4170
|
+
const prereqs = await promptPrereqs(
|
|
4171
|
+
projectRoot,
|
|
4172
|
+
scanResult,
|
|
4173
|
+
detectHookManager(projectRoot),
|
|
4174
|
+
packageManager,
|
|
4175
|
+
isWorkspace
|
|
4176
|
+
);
|
|
4177
|
+
if (prereqs.skipCoverage) config.rules.testCoverage = 0;
|
|
4178
|
+
const coveragePrereqs = prereqs.hasTestRunner ? checkCoveragePrereqs(projectRoot, scanResult) : [];
|
|
3975
4179
|
const state = await promptMainMenu(config, scanResult, {
|
|
3976
|
-
hasTestRunner,
|
|
3977
|
-
hookManager,
|
|
4180
|
+
hasTestRunner: prereqs.hasTestRunner,
|
|
4181
|
+
hookManager: prereqs.hookManager,
|
|
3978
4182
|
coveragePrereqs,
|
|
3979
4183
|
projectRoot,
|
|
3980
4184
|
tools: {
|
|
3981
4185
|
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
3982
4186
|
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3983
|
-
packageManager
|
|
3984
|
-
isWorkspace
|
|
4187
|
+
packageManager,
|
|
4188
|
+
isWorkspace
|
|
3985
4189
|
}
|
|
3986
4190
|
});
|
|
3987
4191
|
const shouldWrite = await confirm3("Apply this setup?");
|
|
3988
4192
|
if (!shouldWrite) {
|
|
3989
|
-
|
|
4193
|
+
clack14.outro("Aborted. No files were written.");
|
|
3990
4194
|
return;
|
|
3991
4195
|
}
|
|
4196
|
+
const integrations = await promptIntegrationsDeferred(prereqs.hookManager, {
|
|
4197
|
+
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
4198
|
+
typecheckLabel: prereqs.typecheckLabel,
|
|
4199
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
4200
|
+
packageManager,
|
|
4201
|
+
isWorkspace
|
|
4202
|
+
});
|
|
3992
4203
|
if (state.deferredInstalls.length > 0) {
|
|
3993
4204
|
await executeDeferredInstalls(projectRoot, state.deferredInstalls);
|
|
3994
4205
|
}
|
|
3995
|
-
const ws =
|
|
4206
|
+
const ws = clack14.spinner();
|
|
3996
4207
|
ws.start("Writing configuration...");
|
|
3997
4208
|
const compacted = (0, import_config9.compactConfig)(config);
|
|
3998
4209
|
fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
@@ -4000,31 +4211,28 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
4000
4211
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
4001
4212
|
updateGitignore(projectRoot);
|
|
4002
4213
|
ws.stop("Configuration written");
|
|
4003
|
-
const ok =
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
});
|
|
4014
|
-
}
|
|
4015
|
-
clack13.outro(
|
|
4214
|
+
const ok = import_chalk18.default.green("\u2713");
|
|
4215
|
+
clack14.log.step(`${ok} ${path21.basename(configPath)}`);
|
|
4216
|
+
clack14.log.step(`${ok} .viberails/context.md`);
|
|
4217
|
+
clack14.log.step(`${ok} .viberails/scan-result.json`);
|
|
4218
|
+
setupSelectedIntegrations(projectRoot, integrations.choice, {
|
|
4219
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
4220
|
+
packageManager,
|
|
4221
|
+
lefthookExpected: prereqs.hookManager === "lefthook"
|
|
4222
|
+
});
|
|
4223
|
+
clack14.outro(
|
|
4016
4224
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
4017
|
-
${
|
|
4225
|
+
${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
4226
|
);
|
|
4019
4227
|
}
|
|
4020
4228
|
|
|
4021
4229
|
// src/commands/sync.ts
|
|
4022
4230
|
var fs22 = __toESM(require("fs"), 1);
|
|
4023
4231
|
var path22 = __toESM(require("path"), 1);
|
|
4024
|
-
var
|
|
4232
|
+
var clack15 = __toESM(require("@clack/prompts"), 1);
|
|
4025
4233
|
var import_config11 = require("@viberails/config");
|
|
4026
4234
|
var import_scanner4 = require("@viberails/scanner");
|
|
4027
|
-
var
|
|
4235
|
+
var import_chalk19 = __toESM(require("chalk"), 1);
|
|
4028
4236
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
4029
4237
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
4030
4238
|
function loadPreviousStats(projectRoot) {
|
|
@@ -4050,7 +4258,7 @@ async function syncCommand(options, cwd) {
|
|
|
4050
4258
|
const configPath = path22.join(projectRoot, CONFIG_FILE6);
|
|
4051
4259
|
const existing = await (0, import_config11.loadConfig)(configPath);
|
|
4052
4260
|
const previousStats = loadPreviousStats(projectRoot);
|
|
4053
|
-
const s =
|
|
4261
|
+
const s = clack15.spinner();
|
|
4054
4262
|
s.start("Scanning project...");
|
|
4055
4263
|
const scanResult = await (0, import_scanner4.scan)(projectRoot);
|
|
4056
4264
|
s.stop("Scan complete");
|
|
@@ -4065,19 +4273,19 @@ async function syncCommand(options, cwd) {
|
|
|
4065
4273
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
4066
4274
|
if (changes.length > 0 || statsDelta) {
|
|
4067
4275
|
console.log(`
|
|
4068
|
-
${
|
|
4276
|
+
${import_chalk19.default.bold("Changes:")}`);
|
|
4069
4277
|
for (const change of changes) {
|
|
4070
|
-
const icon = change.type === "removed" ?
|
|
4278
|
+
const icon = change.type === "removed" ? import_chalk19.default.red("-") : import_chalk19.default.green("+");
|
|
4071
4279
|
console.log(` ${icon} ${change.description}`);
|
|
4072
4280
|
}
|
|
4073
4281
|
if (statsDelta) {
|
|
4074
|
-
console.log(` ${
|
|
4282
|
+
console.log(` ${import_chalk19.default.dim(statsDelta)}`);
|
|
4075
4283
|
}
|
|
4076
4284
|
}
|
|
4077
4285
|
if (options?.interactive) {
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
const decision = await
|
|
4286
|
+
clack15.intro("viberails sync (interactive)");
|
|
4287
|
+
clack15.note(formatRulesText(merged).join("\n"), "Rules after sync");
|
|
4288
|
+
const decision = await clack15.select({
|
|
4081
4289
|
message: "How would you like to proceed?",
|
|
4082
4290
|
options: [
|
|
4083
4291
|
{ value: "accept", label: "Accept changes" },
|
|
@@ -4087,7 +4295,7 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4087
4295
|
});
|
|
4088
4296
|
assertNotCancelled(decision);
|
|
4089
4297
|
if (decision === "cancel") {
|
|
4090
|
-
|
|
4298
|
+
clack15.outro("Sync cancelled. No files were written.");
|
|
4091
4299
|
return;
|
|
4092
4300
|
}
|
|
4093
4301
|
if (decision === "customize") {
|
|
@@ -4111,8 +4319,8 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4111
4319
|
fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
4112
4320
|
`);
|
|
4113
4321
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4114
|
-
|
|
4115
|
-
|
|
4322
|
+
clack15.log.success("Updated config with your customizations.");
|
|
4323
|
+
clack15.outro("Done! Run viberails check to verify.");
|
|
4116
4324
|
return;
|
|
4117
4325
|
}
|
|
4118
4326
|
}
|
|
@@ -4120,18 +4328,18 @@ ${import_chalk17.default.bold("Changes:")}`);
|
|
|
4120
4328
|
`);
|
|
4121
4329
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4122
4330
|
console.log(`
|
|
4123
|
-
${
|
|
4331
|
+
${import_chalk19.default.bold("Synced:")}`);
|
|
4124
4332
|
if (configChanged) {
|
|
4125
|
-
console.log(` ${
|
|
4333
|
+
console.log(` ${import_chalk19.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
4126
4334
|
} else {
|
|
4127
|
-
console.log(` ${
|
|
4335
|
+
console.log(` ${import_chalk19.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
4128
4336
|
}
|
|
4129
|
-
console.log(` ${
|
|
4130
|
-
console.log(` ${
|
|
4337
|
+
console.log(` ${import_chalk19.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
4338
|
+
console.log(` ${import_chalk19.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
4131
4339
|
}
|
|
4132
4340
|
|
|
4133
4341
|
// src/index.ts
|
|
4134
|
-
var VERSION = "0.6.
|
|
4342
|
+
var VERSION = "0.6.12";
|
|
4135
4343
|
var program = new import_commander.Command();
|
|
4136
4344
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4137
4345
|
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 +4347,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
4139
4347
|
await initCommand(options);
|
|
4140
4348
|
} catch (err) {
|
|
4141
4349
|
const message = err instanceof Error ? err.message : String(err);
|
|
4142
|
-
console.error(`${
|
|
4350
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4143
4351
|
process.exit(1);
|
|
4144
4352
|
}
|
|
4145
4353
|
});
|
|
@@ -4148,7 +4356,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
4148
4356
|
await syncCommand(options);
|
|
4149
4357
|
} catch (err) {
|
|
4150
4358
|
const message = err instanceof Error ? err.message : String(err);
|
|
4151
|
-
console.error(`${
|
|
4359
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4152
4360
|
process.exit(1);
|
|
4153
4361
|
}
|
|
4154
4362
|
});
|
|
@@ -4157,7 +4365,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
4157
4365
|
await configCommand(options);
|
|
4158
4366
|
} catch (err) {
|
|
4159
4367
|
const message = err instanceof Error ? err.message : String(err);
|
|
4160
|
-
console.error(`${
|
|
4368
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4161
4369
|
process.exit(1);
|
|
4162
4370
|
}
|
|
4163
4371
|
});
|
|
@@ -4178,7 +4386,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
4178
4386
|
process.exit(exitCode);
|
|
4179
4387
|
} catch (err) {
|
|
4180
4388
|
const message = err instanceof Error ? err.message : String(err);
|
|
4181
|
-
console.error(`${
|
|
4389
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4182
4390
|
process.exit(1);
|
|
4183
4391
|
}
|
|
4184
4392
|
}
|
|
@@ -4189,7 +4397,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
4189
4397
|
process.exit(exitCode);
|
|
4190
4398
|
} catch (err) {
|
|
4191
4399
|
const message = err instanceof Error ? err.message : String(err);
|
|
4192
|
-
console.error(`${
|
|
4400
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4193
4401
|
process.exit(1);
|
|
4194
4402
|
}
|
|
4195
4403
|
});
|
|
@@ -4198,7 +4406,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
4198
4406
|
await boundariesCommand(options);
|
|
4199
4407
|
} catch (err) {
|
|
4200
4408
|
const message = err instanceof Error ? err.message : String(err);
|
|
4201
|
-
console.error(`${
|
|
4409
|
+
console.error(`${import_chalk20.default.red("Error:")} ${message}`);
|
|
4202
4410
|
process.exit(1);
|
|
4203
4411
|
}
|
|
4204
4412
|
});
|