viberails 0.6.5 → 0.6.6
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 +809 -844
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1361 -468
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-XQKOK3FU.js +0 -821
- package/dist/chunk-XQKOK3FU.js.map +0 -1
- package/dist/prompt-naming-default-AH54HEBC.js +0 -57
- package/dist/prompt-naming-default-AH54HEBC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -6,9 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __esm = (fn, res) => function __init() {
|
|
10
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
-
};
|
|
12
9
|
var __export = (target, all) => {
|
|
13
10
|
for (var name in all)
|
|
14
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -31,178 +28,82 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
28
|
));
|
|
32
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
30
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
let stdout = "";
|
|
39
|
-
let stderr = "";
|
|
40
|
-
child.stdout.on("data", (d) => {
|
|
41
|
-
stdout += d.toString();
|
|
42
|
-
});
|
|
43
|
-
child.stderr.on("data", (d) => {
|
|
44
|
-
stderr += d.toString();
|
|
45
|
-
});
|
|
46
|
-
child.on("close", (status) => {
|
|
47
|
-
resolve4({ status, stdout, stderr });
|
|
48
|
-
});
|
|
49
|
-
child.on("error", () => {
|
|
50
|
-
resolve4({ status: 1, stdout, stderr });
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
var import_node_child_process;
|
|
55
|
-
var init_spawn_async = __esm({
|
|
56
|
-
"src/utils/spawn-async.ts"() {
|
|
57
|
-
"use strict";
|
|
58
|
-
import_node_child_process = require("child_process");
|
|
59
|
-
}
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
VERSION: () => VERSION
|
|
60
35
|
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_chalk15 = __toESM(require("chalk"), 1);
|
|
38
|
+
var import_commander = require("commander");
|
|
61
39
|
|
|
62
|
-
// src/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
assertNotCancelled(choice);
|
|
80
|
-
if (choice !== "install") return void 0;
|
|
81
|
-
const pm = packageManager || "npm";
|
|
82
|
-
const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
|
|
83
|
-
const s = clack.spinner();
|
|
84
|
-
s.start("Installing Lefthook...");
|
|
85
|
-
const result = await spawnAsync(installCmd, projectRoot);
|
|
86
|
-
if (result.status === 0) {
|
|
87
|
-
const fs22 = await import("fs");
|
|
88
|
-
const path22 = await import("path");
|
|
89
|
-
const lefthookPath = path22.join(projectRoot, "lefthook.yml");
|
|
90
|
-
if (!fs22.existsSync(lefthookPath)) {
|
|
91
|
-
fs22.writeFileSync(lefthookPath, "# Managed by viberails \u2014 https://viberails.sh\n");
|
|
92
|
-
}
|
|
93
|
-
s.stop("Installed Lefthook");
|
|
94
|
-
return "Lefthook";
|
|
95
|
-
}
|
|
96
|
-
s.stop("Failed to install Lefthook");
|
|
97
|
-
clack.log.warn(`Install manually: ${installCmd}`);
|
|
98
|
-
return void 0;
|
|
99
|
-
}
|
|
100
|
-
async function promptIntegrations(projectRoot, hookManager, tools) {
|
|
101
|
-
let resolvedHookManager = hookManager;
|
|
102
|
-
if (!resolvedHookManager) {
|
|
103
|
-
resolvedHookManager = await promptHookManagerInstall(
|
|
104
|
-
projectRoot,
|
|
105
|
-
tools?.packageManager ?? "npm",
|
|
106
|
-
tools?.isWorkspace
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
const isBareHook = !resolvedHookManager;
|
|
110
|
-
const hookLabel = resolvedHookManager ? `Pre-commit hook (${resolvedHookManager})` : "Pre-commit hook (git hook \u2014 local only)";
|
|
111
|
-
const hookHint = isBareHook ? "local only \u2014 will NOT be committed or shared with collaborators" : "runs viberails checks when you commit";
|
|
112
|
-
const options = [
|
|
113
|
-
{
|
|
114
|
-
value: "preCommit",
|
|
115
|
-
label: hookLabel,
|
|
116
|
-
hint: hookHint
|
|
40
|
+
// src/commands/boundaries.ts
|
|
41
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
42
|
+
var path3 = __toESM(require("path"), 1);
|
|
43
|
+
var import_config = require("@viberails/config");
|
|
44
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
45
|
+
|
|
46
|
+
// src/utils/find-project-root.ts
|
|
47
|
+
var fs = __toESM(require("fs"), 1);
|
|
48
|
+
var path = __toESM(require("path"), 1);
|
|
49
|
+
function findProjectRoot(startDir) {
|
|
50
|
+
let dir = path.resolve(startDir);
|
|
51
|
+
while (true) {
|
|
52
|
+
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
53
|
+
return dir;
|
|
117
54
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
value: "typecheck",
|
|
122
|
-
label: "Typecheck (tsc --noEmit)",
|
|
123
|
-
hint: "pre-commit hook + CI check"
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
if (tools?.linter) {
|
|
127
|
-
const linterName = tools.linter === "biome" ? "Biome" : "ESLint";
|
|
128
|
-
options.push({
|
|
129
|
-
value: "lint",
|
|
130
|
-
label: `Lint check (${linterName})`,
|
|
131
|
-
hint: "pre-commit hook + CI check"
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
options.push(
|
|
135
|
-
{
|
|
136
|
-
value: "claude",
|
|
137
|
-
label: "Claude Code hook",
|
|
138
|
-
hint: "checks files when Claude edits them"
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
value: "claudeMd",
|
|
142
|
-
label: "CLAUDE.md reference",
|
|
143
|
-
hint: "appends @.viberails/context.md so Claude loads rules automatically"
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
value: "githubAction",
|
|
147
|
-
label: "GitHub Actions workflow",
|
|
148
|
-
hint: "blocks PRs that fail viberails check"
|
|
55
|
+
const parent = path.dirname(dir);
|
|
56
|
+
if (parent === dir) {
|
|
57
|
+
return null;
|
|
149
58
|
}
|
|
150
|
-
|
|
151
|
-
const initialValues = isBareHook ? options.filter((o) => o.value !== "preCommit").map((o) => o.value) : options.map((o) => o.value);
|
|
152
|
-
const result = await clack.multiselect({
|
|
153
|
-
message: "Optional integrations",
|
|
154
|
-
options,
|
|
155
|
-
initialValues,
|
|
156
|
-
required: false
|
|
157
|
-
});
|
|
158
|
-
assertNotCancelled(result);
|
|
159
|
-
return {
|
|
160
|
-
preCommitHook: result.includes("preCommit"),
|
|
161
|
-
claudeCodeHook: result.includes("claude"),
|
|
162
|
-
claudeMdRef: result.includes("claudeMd"),
|
|
163
|
-
githubAction: result.includes("githubAction"),
|
|
164
|
-
typecheckHook: result.includes("typecheck"),
|
|
165
|
-
lintHook: result.includes("lint")
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
var clack;
|
|
169
|
-
var init_prompt_integrations = __esm({
|
|
170
|
-
"src/utils/prompt-integrations.ts"() {
|
|
171
|
-
"use strict";
|
|
172
|
-
clack = __toESM(require("@clack/prompts"), 1);
|
|
173
|
-
init_prompt();
|
|
174
|
-
init_spawn_async();
|
|
59
|
+
dir = parent;
|
|
175
60
|
}
|
|
176
|
-
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/utils/prompt.ts
|
|
64
|
+
var clack5 = __toESM(require("@clack/prompts"), 1);
|
|
65
|
+
|
|
66
|
+
// src/utils/prompt-rules.ts
|
|
67
|
+
var clack4 = __toESM(require("@clack/prompts"), 1);
|
|
177
68
|
|
|
178
69
|
// src/utils/get-root-package.ts
|
|
179
70
|
function getRootPackage(packages) {
|
|
180
71
|
return packages.find((pkg) => pkg.path === ".") ?? packages[0];
|
|
181
72
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
73
|
+
|
|
74
|
+
// src/utils/prompt-menu-handlers.ts
|
|
75
|
+
var clack3 = __toESM(require("@clack/prompts"), 1);
|
|
76
|
+
|
|
77
|
+
// src/utils/prompt-package-overrides.ts
|
|
78
|
+
var clack2 = __toESM(require("@clack/prompts"), 1);
|
|
187
79
|
|
|
188
80
|
// src/utils/prompt-constants.ts
|
|
189
|
-
var SENTINEL_DONE
|
|
190
|
-
var
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
SENTINEL_CUSTOM = "__custom__";
|
|
196
|
-
SENTINEL_NONE = "__none__";
|
|
197
|
-
SENTINEL_INHERIT = "__inherit__";
|
|
198
|
-
SENTINEL_SKIP = "__skip__";
|
|
199
|
-
}
|
|
200
|
-
});
|
|
81
|
+
var SENTINEL_DONE = "__done__";
|
|
82
|
+
var SENTINEL_CLEAR = "__clear__";
|
|
83
|
+
var SENTINEL_CUSTOM = "__custom__";
|
|
84
|
+
var SENTINEL_NONE = "__none__";
|
|
85
|
+
var SENTINEL_INHERIT = "__inherit__";
|
|
86
|
+
var SENTINEL_SKIP = "__skip__";
|
|
201
87
|
|
|
202
88
|
// src/utils/prompt-submenus.ts
|
|
89
|
+
var clack = __toESM(require("@clack/prompts"), 1);
|
|
90
|
+
var FILE_NAMING_OPTIONS = [
|
|
91
|
+
{ value: "kebab-case", label: "kebab-case" },
|
|
92
|
+
{ value: "camelCase", label: "camelCase" },
|
|
93
|
+
{ value: "PascalCase", label: "PascalCase" },
|
|
94
|
+
{ value: "snake_case", label: "snake_case" }
|
|
95
|
+
];
|
|
96
|
+
var COMPONENT_NAMING_OPTIONS = [
|
|
97
|
+
{ value: "PascalCase", label: "PascalCase", hint: "MyComponent.tsx" },
|
|
98
|
+
{ value: "camelCase", label: "camelCase", hint: "myComponent.tsx" }
|
|
99
|
+
];
|
|
100
|
+
var HOOK_NAMING_OPTIONS = [
|
|
101
|
+
{ value: "useXxx", label: "useXxx", hint: "useAuth, useFormData" },
|
|
102
|
+
{ value: "use-*", label: "use-*", hint: "use-auth, use-form-data" }
|
|
103
|
+
];
|
|
203
104
|
async function promptFileLimitsMenu(state) {
|
|
204
105
|
while (true) {
|
|
205
|
-
const choice = await
|
|
106
|
+
const choice = await clack.select({
|
|
206
107
|
message: "File limits",
|
|
207
108
|
options: [
|
|
208
109
|
{ value: "maxFileLines", label: "Max file lines", hint: String(state.maxFileLines) },
|
|
@@ -217,7 +118,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
217
118
|
assertNotCancelled(choice);
|
|
218
119
|
if (choice === "back") return;
|
|
219
120
|
if (choice === "maxFileLines") {
|
|
220
|
-
const result = await
|
|
121
|
+
const result = await clack.text({
|
|
221
122
|
message: "Maximum lines per source file?",
|
|
222
123
|
initialValue: String(state.maxFileLines),
|
|
223
124
|
validate: (v) => {
|
|
@@ -230,7 +131,7 @@ async function promptFileLimitsMenu(state) {
|
|
|
230
131
|
state.maxFileLines = Number.parseInt(result, 10);
|
|
231
132
|
}
|
|
232
133
|
if (choice === "maxTestFileLines") {
|
|
233
|
-
const result = await
|
|
134
|
+
const result = await clack.text({
|
|
234
135
|
message: "Maximum lines per test file (0 to disable)?",
|
|
235
136
|
initialValue: String(state.maxTestFileLines),
|
|
236
137
|
validate: (v) => {
|
|
@@ -278,17 +179,17 @@ async function promptNamingMenu(state) {
|
|
|
278
179
|
},
|
|
279
180
|
{ value: "back", label: "Back" }
|
|
280
181
|
);
|
|
281
|
-
const choice = await
|
|
182
|
+
const choice = await clack.select({ message: "Naming & conventions", options });
|
|
282
183
|
assertNotCancelled(choice);
|
|
283
184
|
if (choice === "back") return;
|
|
284
185
|
if (choice === "enforceNaming") {
|
|
285
|
-
const result = await
|
|
186
|
+
const result = await clack.confirm({
|
|
286
187
|
message: state.fileNamingValue ? `Enforce file naming? (detected: ${state.fileNamingValue})` : "Enforce file naming?",
|
|
287
188
|
initialValue: state.enforceNaming
|
|
288
189
|
});
|
|
289
190
|
assertNotCancelled(result);
|
|
290
191
|
if (result && !state.fileNamingValue) {
|
|
291
|
-
const selected = await
|
|
192
|
+
const selected = await clack.select({
|
|
292
193
|
message: "Which file naming convention should be enforced?",
|
|
293
194
|
options: [...FILE_NAMING_OPTIONS]
|
|
294
195
|
});
|
|
@@ -298,7 +199,7 @@ async function promptNamingMenu(state) {
|
|
|
298
199
|
state.enforceNaming = result;
|
|
299
200
|
}
|
|
300
201
|
if (choice === "fileNaming") {
|
|
301
|
-
const selected = await
|
|
202
|
+
const selected = await clack.select({
|
|
302
203
|
message: "Which file naming convention should be enforced?",
|
|
303
204
|
options: [...FILE_NAMING_OPTIONS],
|
|
304
205
|
initialValue: state.fileNamingValue
|
|
@@ -307,7 +208,7 @@ async function promptNamingMenu(state) {
|
|
|
307
208
|
state.fileNamingValue = selected;
|
|
308
209
|
}
|
|
309
210
|
if (choice === "componentNaming") {
|
|
310
|
-
const selected = await
|
|
211
|
+
const selected = await clack.select({
|
|
311
212
|
message: "Component naming convention",
|
|
312
213
|
options: [
|
|
313
214
|
...COMPONENT_NAMING_OPTIONS,
|
|
@@ -319,7 +220,7 @@ async function promptNamingMenu(state) {
|
|
|
319
220
|
state.componentNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
320
221
|
}
|
|
321
222
|
if (choice === "hookNaming") {
|
|
322
|
-
const selected = await
|
|
223
|
+
const selected = await clack.select({
|
|
323
224
|
message: "Hook naming convention",
|
|
324
225
|
options: [
|
|
325
226
|
...HOOK_NAMING_OPTIONS,
|
|
@@ -331,7 +232,7 @@ async function promptNamingMenu(state) {
|
|
|
331
232
|
state.hookNaming = selected === SENTINEL_CLEAR ? void 0 : selected;
|
|
332
233
|
}
|
|
333
234
|
if (choice === "importAlias") {
|
|
334
|
-
const selected = await
|
|
235
|
+
const selected = await clack.select({
|
|
335
236
|
message: "Import alias pattern",
|
|
336
237
|
options: [
|
|
337
238
|
{ value: "@/*", label: "@/*", hint: "import { x } from '@/utils'" },
|
|
@@ -345,7 +246,7 @@ async function promptNamingMenu(state) {
|
|
|
345
246
|
if (selected === SENTINEL_CLEAR) {
|
|
346
247
|
state.importAlias = void 0;
|
|
347
248
|
} else if (selected === SENTINEL_CUSTOM) {
|
|
348
|
-
const result = await
|
|
249
|
+
const result = await clack.text({
|
|
349
250
|
message: "Custom import alias (e.g. #/*)?",
|
|
350
251
|
initialValue: state.importAlias ?? "",
|
|
351
252
|
placeholder: "e.g. #/*",
|
|
@@ -392,11 +293,11 @@ async function promptTestingMenu(state) {
|
|
|
392
293
|
);
|
|
393
294
|
}
|
|
394
295
|
options.push({ value: "back", label: "Back" });
|
|
395
|
-
const choice = await
|
|
296
|
+
const choice = await clack.select({ message: "Testing & coverage", options });
|
|
396
297
|
assertNotCancelled(choice);
|
|
397
298
|
if (choice === "back") return;
|
|
398
299
|
if (choice === "enforceMissingTests") {
|
|
399
|
-
const result = await
|
|
300
|
+
const result = await clack.confirm({
|
|
400
301
|
message: "Require every source file to have a corresponding test file?",
|
|
401
302
|
initialValue: state.enforceMissingTests
|
|
402
303
|
});
|
|
@@ -404,7 +305,7 @@ async function promptTestingMenu(state) {
|
|
|
404
305
|
state.enforceMissingTests = result;
|
|
405
306
|
}
|
|
406
307
|
if (choice === "testCoverage") {
|
|
407
|
-
const result = await
|
|
308
|
+
const result = await clack.text({
|
|
408
309
|
message: "Test coverage target (0 disables coverage checks)?",
|
|
409
310
|
initialValue: String(state.testCoverage),
|
|
410
311
|
validate: (v) => {
|
|
@@ -417,7 +318,7 @@ async function promptTestingMenu(state) {
|
|
|
417
318
|
state.testCoverage = Number.parseInt(result, 10);
|
|
418
319
|
}
|
|
419
320
|
if (choice === "coverageSummaryPath") {
|
|
420
|
-
const result = await
|
|
321
|
+
const result = await clack.text({
|
|
421
322
|
message: "Coverage summary path (relative to package root)?",
|
|
422
323
|
initialValue: state.coverageSummaryPath,
|
|
423
324
|
validate: (v) => {
|
|
@@ -428,7 +329,7 @@ async function promptTestingMenu(state) {
|
|
|
428
329
|
state.coverageSummaryPath = result.trim();
|
|
429
330
|
}
|
|
430
331
|
if (choice === "coverageCommand") {
|
|
431
|
-
const result = await
|
|
332
|
+
const result = await clack.text({
|
|
432
333
|
message: "Coverage command (blank to auto-detect from package.json)?",
|
|
433
334
|
initialValue: state.coverageCommand ?? "",
|
|
434
335
|
placeholder: "(auto-detect from package.json test runner)"
|
|
@@ -439,29 +340,6 @@ async function promptTestingMenu(state) {
|
|
|
439
340
|
}
|
|
440
341
|
}
|
|
441
342
|
}
|
|
442
|
-
var clack2, FILE_NAMING_OPTIONS, COMPONENT_NAMING_OPTIONS, HOOK_NAMING_OPTIONS;
|
|
443
|
-
var init_prompt_submenus = __esm({
|
|
444
|
-
"src/utils/prompt-submenus.ts"() {
|
|
445
|
-
"use strict";
|
|
446
|
-
clack2 = __toESM(require("@clack/prompts"), 1);
|
|
447
|
-
init_prompt();
|
|
448
|
-
init_prompt_constants();
|
|
449
|
-
FILE_NAMING_OPTIONS = [
|
|
450
|
-
{ value: "kebab-case", label: "kebab-case" },
|
|
451
|
-
{ value: "camelCase", label: "camelCase" },
|
|
452
|
-
{ value: "PascalCase", label: "PascalCase" },
|
|
453
|
-
{ value: "snake_case", label: "snake_case" }
|
|
454
|
-
];
|
|
455
|
-
COMPONENT_NAMING_OPTIONS = [
|
|
456
|
-
{ value: "PascalCase", label: "PascalCase", hint: "MyComponent.tsx" },
|
|
457
|
-
{ value: "camelCase", label: "camelCase", hint: "myComponent.tsx" }
|
|
458
|
-
];
|
|
459
|
-
HOOK_NAMING_OPTIONS = [
|
|
460
|
-
{ value: "useXxx", label: "useXxx", hint: "useAuth, useFormData" },
|
|
461
|
-
{ value: "use-*", label: "use-*", hint: "use-auth, use-form-data" }
|
|
462
|
-
];
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
343
|
|
|
466
344
|
// src/utils/prompt-package-overrides.ts
|
|
467
345
|
function normalizePackageOverrides(packages) {
|
|
@@ -506,7 +384,7 @@ async function promptPackageOverrides(packages, defaults) {
|
|
|
506
384
|
const editablePackages = packages.filter((pkg) => pkg.path !== ".");
|
|
507
385
|
if (editablePackages.length === 0) return packages;
|
|
508
386
|
while (true) {
|
|
509
|
-
const selectedPath = await
|
|
387
|
+
const selectedPath = await clack2.select({
|
|
510
388
|
message: "Select package to edit overrides",
|
|
511
389
|
options: [
|
|
512
390
|
...editablePackages.map((pkg) => ({
|
|
@@ -537,7 +415,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
537
415
|
const hasMaxLinesOverride = target.rules?.maxFileLines !== void 0 && target.rules.maxFileLines !== defaults.maxFileLines;
|
|
538
416
|
const namingHint = hasNamingOverride ? String(effectiveNaming) : `(inherits: ${effectiveNaming ?? "not set"})`;
|
|
539
417
|
const maxLinesHint = hasMaxLinesOverride ? String(effectiveMaxLines) : `(inherits: ${effectiveMaxLines})`;
|
|
540
|
-
const choice = await
|
|
418
|
+
const choice = await clack2.select({
|
|
541
419
|
message: `Edit overrides for ${target.path}`,
|
|
542
420
|
options: [
|
|
543
421
|
{ value: "fileNaming", label: "File naming", hint: namingHint },
|
|
@@ -552,7 +430,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
552
430
|
assertNotCancelled(choice);
|
|
553
431
|
if (choice === "back") break;
|
|
554
432
|
if (choice === "fileNaming") {
|
|
555
|
-
const selected = await
|
|
433
|
+
const selected = await clack2.select({
|
|
556
434
|
message: `File naming for ${target.path}`,
|
|
557
435
|
options: [
|
|
558
436
|
...FILE_NAMING_OPTIONS,
|
|
@@ -574,7 +452,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
574
452
|
}
|
|
575
453
|
}
|
|
576
454
|
if (choice === "maxFileLines") {
|
|
577
|
-
const result = await
|
|
455
|
+
const result = await clack2.text({
|
|
578
456
|
message: `Max file lines for ${target.path} (blank to inherit default)?`,
|
|
579
457
|
initialValue: target.rules?.maxFileLines !== void 0 ? String(target.rules.maxFileLines) : "",
|
|
580
458
|
placeholder: String(defaults.maxFileLines)
|
|
@@ -588,7 +466,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
588
466
|
}
|
|
589
467
|
}
|
|
590
468
|
if (choice === "testCoverage") {
|
|
591
|
-
const result = await
|
|
469
|
+
const result = await clack2.text({
|
|
592
470
|
message: "Package testCoverage (0 to exempt package)?",
|
|
593
471
|
initialValue: String(effectiveCoverage),
|
|
594
472
|
validate: (v) => {
|
|
@@ -606,7 +484,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
606
484
|
}
|
|
607
485
|
}
|
|
608
486
|
if (choice === "summaryPath") {
|
|
609
|
-
const result = await
|
|
487
|
+
const result = await clack2.text({
|
|
610
488
|
message: "Path to coverage summary file (blank to inherit default)?",
|
|
611
489
|
initialValue: target.coverage?.summaryPath !== void 0 ? target.coverage.summaryPath : "",
|
|
612
490
|
placeholder: defaults.coverageSummaryPath
|
|
@@ -620,7 +498,7 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
620
498
|
}
|
|
621
499
|
}
|
|
622
500
|
if (choice === "command") {
|
|
623
|
-
const result = await
|
|
501
|
+
const result = await clack2.text({
|
|
624
502
|
message: "Coverage command (blank to auto-detect)?",
|
|
625
503
|
initialValue: target.coverage?.command !== void 0 ? target.coverage.command : "",
|
|
626
504
|
placeholder: defaults.coverageCommand ?? "(auto-detect from package.json test runner)"
|
|
@@ -644,16 +522,6 @@ async function promptSinglePackageOverrides(target, defaults) {
|
|
|
644
522
|
}
|
|
645
523
|
}
|
|
646
524
|
}
|
|
647
|
-
var clack3;
|
|
648
|
-
var init_prompt_package_overrides = __esm({
|
|
649
|
-
"src/utils/prompt-package-overrides.ts"() {
|
|
650
|
-
"use strict";
|
|
651
|
-
clack3 = __toESM(require("@clack/prompts"), 1);
|
|
652
|
-
init_prompt();
|
|
653
|
-
init_prompt_constants();
|
|
654
|
-
init_prompt_submenus();
|
|
655
|
-
}
|
|
656
|
-
});
|
|
657
525
|
|
|
658
526
|
// src/utils/prompt-menu-handlers.ts
|
|
659
527
|
function getPackageDiffs(pkg, root) {
|
|
@@ -695,11 +563,11 @@ function getPackageDiffs(pkg, root) {
|
|
|
695
563
|
return diffs;
|
|
696
564
|
}
|
|
697
565
|
function buildMenuOptions(state, packageCount) {
|
|
698
|
-
const
|
|
566
|
+
const fileLimitsHint2 = state.maxTestFileLines > 0 ? `max ${state.maxFileLines} lines, tests ${state.maxTestFileLines}` : `max ${state.maxFileLines} lines, test files unlimited`;
|
|
699
567
|
const namingHint = state.enforceNaming ? `${state.fileNamingValue ?? "not set"} (enforced)` : "not enforced";
|
|
700
568
|
const testingHint = state.testCoverage > 0 ? `${state.testCoverage}% coverage, missing tests ${state.enforceMissingTests ? "enforced" : "not enforced"}` : `coverage disabled, missing tests ${state.enforceMissingTests ? "enforced" : "not enforced"}`;
|
|
701
569
|
const options = [
|
|
702
|
-
{ value: "fileLimits", label: "File limits", hint:
|
|
570
|
+
{ value: "fileLimits", label: "File limits", hint: fileLimitsHint2 },
|
|
703
571
|
{ value: "naming", label: "Naming & conventions", hint: namingHint },
|
|
704
572
|
{ value: "testing", label: "Testing & coverage", hint: testingHint }
|
|
705
573
|
];
|
|
@@ -733,7 +601,7 @@ async function handleMenuChoice(choice, state, defaults, root) {
|
|
|
733
601
|
state.coverageSummaryPath = defaults.coverageSummaryPath;
|
|
734
602
|
state.coverageCommand = defaults.coverageCommand;
|
|
735
603
|
state.packageOverrides = clonePackages(defaults.packageOverrides);
|
|
736
|
-
|
|
604
|
+
clack3.log.info("Reset all rules to detected defaults.");
|
|
737
605
|
return;
|
|
738
606
|
}
|
|
739
607
|
if (choice === "fileLimits") {
|
|
@@ -761,21 +629,12 @@ async function handleMenuChoice(choice, state, defaults, root) {
|
|
|
761
629
|
const lines = packageDiffs.map((entry) => `${entry.pkg.path}
|
|
762
630
|
${entry.diffs.join(", ")}`);
|
|
763
631
|
if (lines.length > 0) {
|
|
764
|
-
|
|
632
|
+
clack3.note(lines.join("\n\n"), "Existing package differences");
|
|
765
633
|
}
|
|
766
634
|
}
|
|
767
635
|
return;
|
|
768
636
|
}
|
|
769
637
|
}
|
|
770
|
-
var clack4;
|
|
771
|
-
var init_prompt_menu_handlers = __esm({
|
|
772
|
-
"src/utils/prompt-menu-handlers.ts"() {
|
|
773
|
-
"use strict";
|
|
774
|
-
clack4 = __toESM(require("@clack/prompts"), 1);
|
|
775
|
-
init_prompt_package_overrides();
|
|
776
|
-
init_prompt_submenus();
|
|
777
|
-
}
|
|
778
|
-
});
|
|
779
638
|
|
|
780
639
|
// src/utils/prompt-rules.ts
|
|
781
640
|
async function promptRuleMenu(defaults) {
|
|
@@ -787,7 +646,7 @@ async function promptRuleMenu(defaults) {
|
|
|
787
646
|
const packageCount = state.packageOverrides?.filter((pkg) => pkg.path !== ".").length ?? 0;
|
|
788
647
|
while (true) {
|
|
789
648
|
const options = buildMenuOptions(state, packageCount);
|
|
790
|
-
const choice = await
|
|
649
|
+
const choice = await clack4.select({ message: "Customize rules", options });
|
|
791
650
|
assertNotCancelled(choice);
|
|
792
651
|
if (choice === "done") break;
|
|
793
652
|
await handleMenuChoice(choice, state, defaults, root);
|
|
@@ -807,36 +666,26 @@ async function promptRuleMenu(defaults) {
|
|
|
807
666
|
packageOverrides: state.packageOverrides
|
|
808
667
|
};
|
|
809
668
|
}
|
|
810
|
-
var clack5;
|
|
811
|
-
var init_prompt_rules = __esm({
|
|
812
|
-
"src/utils/prompt-rules.ts"() {
|
|
813
|
-
"use strict";
|
|
814
|
-
clack5 = __toESM(require("@clack/prompts"), 1);
|
|
815
|
-
init_get_root_package();
|
|
816
|
-
init_prompt();
|
|
817
|
-
init_prompt_menu_handlers();
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
669
|
|
|
821
670
|
// src/utils/prompt.ts
|
|
822
671
|
function assertNotCancelled(value) {
|
|
823
|
-
if (
|
|
824
|
-
|
|
672
|
+
if (clack5.isCancel(value)) {
|
|
673
|
+
clack5.cancel("Setup cancelled.");
|
|
825
674
|
process.exit(0);
|
|
826
675
|
}
|
|
827
676
|
}
|
|
828
677
|
async function confirm3(message) {
|
|
829
|
-
const result = await
|
|
678
|
+
const result = await clack5.confirm({ message, initialValue: true });
|
|
830
679
|
assertNotCancelled(result);
|
|
831
680
|
return result;
|
|
832
681
|
}
|
|
833
682
|
async function confirmDangerous(message) {
|
|
834
|
-
const result = await
|
|
683
|
+
const result = await clack5.confirm({ message, initialValue: false });
|
|
835
684
|
assertNotCancelled(result);
|
|
836
685
|
return result;
|
|
837
686
|
}
|
|
838
687
|
async function promptExistingConfigAction(configFile) {
|
|
839
|
-
const result = await
|
|
688
|
+
const result = await clack5.select({
|
|
840
689
|
message: `${configFile} already exists. What do you want to do?`,
|
|
841
690
|
options: [
|
|
842
691
|
{
|
|
@@ -859,134 +708,6 @@ async function promptExistingConfigAction(configFile) {
|
|
|
859
708
|
assertNotCancelled(result);
|
|
860
709
|
return result;
|
|
861
710
|
}
|
|
862
|
-
async function promptInitDecision() {
|
|
863
|
-
const result = await clack6.select({
|
|
864
|
-
message: "How do you want to proceed?",
|
|
865
|
-
options: [
|
|
866
|
-
{
|
|
867
|
-
value: "accept",
|
|
868
|
-
label: "Accept defaults",
|
|
869
|
-
hint: "writes the config with these defaults; use --enforce in CI to block"
|
|
870
|
-
},
|
|
871
|
-
{
|
|
872
|
-
value: "customize",
|
|
873
|
-
label: "Customize rules",
|
|
874
|
-
hint: "edit limits, naming, test coverage, and package overrides"
|
|
875
|
-
},
|
|
876
|
-
{
|
|
877
|
-
value: "review",
|
|
878
|
-
label: "Review detected details",
|
|
879
|
-
hint: "show the full scan report with package and structure details"
|
|
880
|
-
}
|
|
881
|
-
]
|
|
882
|
-
});
|
|
883
|
-
assertNotCancelled(result);
|
|
884
|
-
return result;
|
|
885
|
-
}
|
|
886
|
-
var clack6;
|
|
887
|
-
var init_prompt = __esm({
|
|
888
|
-
"src/utils/prompt.ts"() {
|
|
889
|
-
"use strict";
|
|
890
|
-
clack6 = __toESM(require("@clack/prompts"), 1);
|
|
891
|
-
init_prompt_integrations();
|
|
892
|
-
init_prompt_rules();
|
|
893
|
-
}
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
// src/utils/prompt-naming-default.ts
|
|
897
|
-
var prompt_naming_default_exports = {};
|
|
898
|
-
__export(prompt_naming_default_exports, {
|
|
899
|
-
resolveNamingDefault: () => resolveNamingDefault
|
|
900
|
-
});
|
|
901
|
-
async function resolveNamingDefault(config, scanResult) {
|
|
902
|
-
const rootPkg = getRootPackage(config.packages);
|
|
903
|
-
if (!config.rules.enforceNaming || rootPkg?.conventions?.fileNaming) return false;
|
|
904
|
-
const isMonorepo = config.packages.length > 1;
|
|
905
|
-
const pkgNamingData = isMonorepo ? scanResult.packages.filter((p) => p.conventions.fileNaming && p.conventions.fileNaming.confidence !== "low").map((p) => ({
|
|
906
|
-
path: p.relativePath,
|
|
907
|
-
naming: p.conventions.fileNaming
|
|
908
|
-
})) : [];
|
|
909
|
-
const chosen = await promptNamingDefault(pkgNamingData, isMonorepo);
|
|
910
|
-
if (chosen === SENTINEL_SKIP) {
|
|
911
|
-
config.rules.enforceNaming = false;
|
|
912
|
-
} else if (rootPkg) {
|
|
913
|
-
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
914
|
-
rootPkg.conventions.fileNaming = chosen;
|
|
915
|
-
}
|
|
916
|
-
return true;
|
|
917
|
-
}
|
|
918
|
-
async function promptNamingDefault(pkgNamingData, isMonorepo) {
|
|
919
|
-
if (isMonorepo && pkgNamingData.length > 0) {
|
|
920
|
-
const lines = pkgNamingData.map(
|
|
921
|
-
(p) => `${p.path}: ${p.naming.value} (${Math.round(p.naming.consistency)}%)`
|
|
922
|
-
);
|
|
923
|
-
clack10.note(lines.join("\n"), "Per-package file naming detected");
|
|
924
|
-
}
|
|
925
|
-
const message = isMonorepo ? "Which convention should be the default? You can override per-package later." : "Which file naming convention should be used?";
|
|
926
|
-
const options = FILE_NAMING_OPTIONS.map((opt) => {
|
|
927
|
-
if (isMonorepo && pkgNamingData.length > 0) {
|
|
928
|
-
const count = pkgNamingData.filter((p) => p.naming.value === opt.value).length;
|
|
929
|
-
return {
|
|
930
|
-
value: opt.value,
|
|
931
|
-
label: opt.label,
|
|
932
|
-
hint: count > 0 ? `${count} package${count > 1 ? "s" : ""}` : void 0
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
return { value: opt.value, label: opt.label };
|
|
936
|
-
});
|
|
937
|
-
const selected = await clack10.select({
|
|
938
|
-
message,
|
|
939
|
-
options: [...options, { value: SENTINEL_SKIP, label: "Don't enforce naming" }]
|
|
940
|
-
});
|
|
941
|
-
assertNotCancelled(selected);
|
|
942
|
-
return selected;
|
|
943
|
-
}
|
|
944
|
-
var clack10;
|
|
945
|
-
var init_prompt_naming_default = __esm({
|
|
946
|
-
"src/utils/prompt-naming-default.ts"() {
|
|
947
|
-
"use strict";
|
|
948
|
-
clack10 = __toESM(require("@clack/prompts"), 1);
|
|
949
|
-
init_get_root_package();
|
|
950
|
-
init_prompt();
|
|
951
|
-
init_prompt_constants();
|
|
952
|
-
init_prompt_submenus();
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
// src/index.ts
|
|
957
|
-
var index_exports = {};
|
|
958
|
-
__export(index_exports, {
|
|
959
|
-
VERSION: () => VERSION
|
|
960
|
-
});
|
|
961
|
-
module.exports = __toCommonJS(index_exports);
|
|
962
|
-
var import_chalk16 = __toESM(require("chalk"), 1);
|
|
963
|
-
var import_commander = require("commander");
|
|
964
|
-
|
|
965
|
-
// src/commands/boundaries.ts
|
|
966
|
-
var fs3 = __toESM(require("fs"), 1);
|
|
967
|
-
var path3 = __toESM(require("path"), 1);
|
|
968
|
-
var import_config = require("@viberails/config");
|
|
969
|
-
var import_chalk = __toESM(require("chalk"), 1);
|
|
970
|
-
|
|
971
|
-
// src/utils/find-project-root.ts
|
|
972
|
-
var fs = __toESM(require("fs"), 1);
|
|
973
|
-
var path = __toESM(require("path"), 1);
|
|
974
|
-
function findProjectRoot(startDir) {
|
|
975
|
-
let dir = path.resolve(startDir);
|
|
976
|
-
while (true) {
|
|
977
|
-
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
978
|
-
return dir;
|
|
979
|
-
}
|
|
980
|
-
const parent = path.dirname(dir);
|
|
981
|
-
if (parent === dir) {
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
dir = parent;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// src/commands/boundaries.ts
|
|
989
|
-
init_prompt();
|
|
990
711
|
|
|
991
712
|
// src/utils/resolve-workspace-packages.ts
|
|
992
713
|
var fs2 = __toESM(require("fs"), 1);
|
|
@@ -1179,7 +900,7 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
1179
900
|
}
|
|
1180
901
|
|
|
1181
902
|
// src/commands/check-coverage.ts
|
|
1182
|
-
var
|
|
903
|
+
var import_node_child_process = require("child_process");
|
|
1183
904
|
var fs4 = __toESM(require("fs"), 1);
|
|
1184
905
|
var path4 = __toESM(require("path"), 1);
|
|
1185
906
|
var import_config3 = require("@viberails/config");
|
|
@@ -1222,7 +943,7 @@ function readCoveragePercentage(summaryPath) {
|
|
|
1222
943
|
}
|
|
1223
944
|
}
|
|
1224
945
|
function runCoverageCommand(pkgRoot, command) {
|
|
1225
|
-
const result = (0,
|
|
946
|
+
const result = (0, import_node_child_process.spawnSync)(command, {
|
|
1226
947
|
cwd: pkgRoot,
|
|
1227
948
|
shell: true,
|
|
1228
949
|
encoding: "utf-8",
|
|
@@ -1317,7 +1038,7 @@ function checkCoverage(projectRoot, config, filesToCheck, options) {
|
|
|
1317
1038
|
}
|
|
1318
1039
|
|
|
1319
1040
|
// src/commands/check-files.ts
|
|
1320
|
-
var
|
|
1041
|
+
var import_node_child_process2 = require("child_process");
|
|
1321
1042
|
var fs5 = __toESM(require("fs"), 1);
|
|
1322
1043
|
var path5 = __toESM(require("path"), 1);
|
|
1323
1044
|
var import_config4 = require("@viberails/config");
|
|
@@ -1390,7 +1111,7 @@ function checkNaming(relPath, conventions) {
|
|
|
1390
1111
|
}
|
|
1391
1112
|
function getStagedFiles(projectRoot) {
|
|
1392
1113
|
try {
|
|
1393
|
-
const output = (0,
|
|
1114
|
+
const output = (0, import_node_child_process2.execSync)("git diff --cached --name-only --diff-filter=ACMR", {
|
|
1394
1115
|
cwd: projectRoot,
|
|
1395
1116
|
encoding: "utf-8",
|
|
1396
1117
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1402,12 +1123,12 @@ function getStagedFiles(projectRoot) {
|
|
|
1402
1123
|
}
|
|
1403
1124
|
function getDiffFiles(projectRoot, base) {
|
|
1404
1125
|
try {
|
|
1405
|
-
const allOutput = (0,
|
|
1126
|
+
const allOutput = (0, import_node_child_process2.execSync)(`git diff --name-only --diff-filter=ACMR ${base}...HEAD`, {
|
|
1406
1127
|
cwd: projectRoot,
|
|
1407
1128
|
encoding: "utf-8",
|
|
1408
1129
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1409
1130
|
});
|
|
1410
|
-
const addedOutput = (0,
|
|
1131
|
+
const addedOutput = (0, import_node_child_process2.execSync)(`git diff --name-only --diff-filter=A ${base}...HEAD`, {
|
|
1411
1132
|
cwd: projectRoot,
|
|
1412
1133
|
encoding: "utf-8",
|
|
1413
1134
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1453,7 +1174,7 @@ function deletedTestFileToSourceFile(deletedTestFile, config) {
|
|
|
1453
1174
|
}
|
|
1454
1175
|
function getStagedDeletedTestSourceFiles(projectRoot, config) {
|
|
1455
1176
|
try {
|
|
1456
|
-
const output = (0,
|
|
1177
|
+
const output = (0, import_node_child_process2.execSync)("git diff --cached --name-only --diff-filter=D", {
|
|
1457
1178
|
cwd: projectRoot,
|
|
1458
1179
|
encoding: "utf-8",
|
|
1459
1180
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1465,7 +1186,7 @@ function getStagedDeletedTestSourceFiles(projectRoot, config) {
|
|
|
1465
1186
|
}
|
|
1466
1187
|
function getDiffDeletedTestSourceFiles(projectRoot, base, config) {
|
|
1467
1188
|
try {
|
|
1468
|
-
const output = (0,
|
|
1189
|
+
const output = (0, import_node_child_process2.execSync)(`git diff --name-only --diff-filter=D ${base}...HEAD`, {
|
|
1469
1190
|
cwd: projectRoot,
|
|
1470
1191
|
encoding: "utf-8",
|
|
1471
1192
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1691,9 +1412,9 @@ async function checkCommand(options, cwd) {
|
|
|
1691
1412
|
}
|
|
1692
1413
|
const violations = [];
|
|
1693
1414
|
const severity = options.enforce ? "error" : "warn";
|
|
1694
|
-
const
|
|
1415
|
+
const log8 = options.format !== "json" && !options.hook && !options.quiet ? (msg) => process.stderr.write(import_chalk3.default.dim(msg)) : () => {
|
|
1695
1416
|
};
|
|
1696
|
-
|
|
1417
|
+
log8(" Checking files...");
|
|
1697
1418
|
for (const file of filesToCheck) {
|
|
1698
1419
|
const absPath = path7.isAbsolute(file) ? file : path7.join(projectRoot, file);
|
|
1699
1420
|
const relPath = path7.relative(projectRoot, absPath);
|
|
@@ -1726,9 +1447,9 @@ async function checkCommand(options, cwd) {
|
|
|
1726
1447
|
}
|
|
1727
1448
|
}
|
|
1728
1449
|
}
|
|
1729
|
-
|
|
1450
|
+
log8(" done\n");
|
|
1730
1451
|
if (!options.files) {
|
|
1731
|
-
|
|
1452
|
+
log8(" Checking missing tests...");
|
|
1732
1453
|
const testViolations = checkMissingTests(projectRoot, config, severity);
|
|
1733
1454
|
if (options.staged) {
|
|
1734
1455
|
const stagedSet = new Set(filesToCheck);
|
|
@@ -1741,14 +1462,14 @@ async function checkCommand(options, cwd) {
|
|
|
1741
1462
|
} else {
|
|
1742
1463
|
violations.push(...testViolations);
|
|
1743
1464
|
}
|
|
1744
|
-
|
|
1465
|
+
log8(" done\n");
|
|
1745
1466
|
}
|
|
1746
1467
|
if (!options.files && !options.staged && !options.diffBase) {
|
|
1747
|
-
|
|
1468
|
+
log8(" Running test coverage...\n");
|
|
1748
1469
|
const coverageViolations = checkCoverage(projectRoot, config, filesToCheck, {
|
|
1749
1470
|
staged: options.staged,
|
|
1750
1471
|
enforce: options.enforce,
|
|
1751
|
-
onProgress: (pkg) =>
|
|
1472
|
+
onProgress: (pkg) => log8(` Coverage: ${pkg}...
|
|
1752
1473
|
`)
|
|
1753
1474
|
});
|
|
1754
1475
|
violations.push(...coverageViolations);
|
|
@@ -1773,7 +1494,7 @@ async function checkCommand(options, cwd) {
|
|
|
1773
1494
|
severity
|
|
1774
1495
|
});
|
|
1775
1496
|
}
|
|
1776
|
-
|
|
1497
|
+
log8(` Boundary check: ${graph.nodes.length} files in ${Date.now() - startTime}ms
|
|
1777
1498
|
`);
|
|
1778
1499
|
}
|
|
1779
1500
|
if (options.format === "json") {
|
|
@@ -1849,7 +1570,7 @@ async function hookCheckCommand(cwd) {
|
|
|
1849
1570
|
// src/commands/config.ts
|
|
1850
1571
|
var fs10 = __toESM(require("fs"), 1);
|
|
1851
1572
|
var path9 = __toESM(require("path"), 1);
|
|
1852
|
-
var
|
|
1573
|
+
var clack6 = __toESM(require("@clack/prompts"), 1);
|
|
1853
1574
|
var import_config6 = require("@viberails/config");
|
|
1854
1575
|
var import_scanner = require("@viberails/scanner");
|
|
1855
1576
|
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
@@ -2273,7 +1994,6 @@ function formatScanResultsText(scanResult) {
|
|
|
2273
1994
|
}
|
|
2274
1995
|
|
|
2275
1996
|
// src/utils/apply-rule-overrides.ts
|
|
2276
|
-
init_get_root_package();
|
|
2277
1997
|
function applyRuleOverrides(config, overrides) {
|
|
2278
1998
|
if (overrides.packageOverrides) config.packages = overrides.packageOverrides;
|
|
2279
1999
|
const rootPkg = getRootPackage(config.packages);
|
|
@@ -2444,9 +2164,6 @@ function formatStatsDelta(oldStats, newStats) {
|
|
|
2444
2164
|
return `${parts.join(", ")} since last sync`;
|
|
2445
2165
|
}
|
|
2446
2166
|
|
|
2447
|
-
// src/commands/config.ts
|
|
2448
|
-
init_prompt();
|
|
2449
|
-
|
|
2450
2167
|
// src/utils/write-generated-files.ts
|
|
2451
2168
|
var fs9 = __toESM(require("fs"), 1);
|
|
2452
2169
|
var path8 = __toESM(require("path"), 1);
|
|
@@ -2486,11 +2203,11 @@ async function configCommand(options, cwd) {
|
|
|
2486
2203
|
return;
|
|
2487
2204
|
}
|
|
2488
2205
|
if (!options.suppressIntro) {
|
|
2489
|
-
|
|
2206
|
+
clack6.intro("viberails config");
|
|
2490
2207
|
}
|
|
2491
2208
|
const config = await (0, import_config6.loadConfig)(configPath);
|
|
2492
2209
|
let scanResult = options.rescan ? await rescanAndMerge(projectRoot, config) : void 0;
|
|
2493
|
-
|
|
2210
|
+
clack6.note(formatRulesText(config).join("\n"), "Current rules");
|
|
2494
2211
|
const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
2495
2212
|
const overrides = await promptRuleMenu({
|
|
2496
2213
|
maxFileLines: config.rules.maxFileLines,
|
|
@@ -2510,7 +2227,7 @@ async function configCommand(options, cwd) {
|
|
|
2510
2227
|
if (options.rescan && config.packages.length > 1) {
|
|
2511
2228
|
const shouldInfer = await confirm3("Re-infer boundary rules from import patterns?");
|
|
2512
2229
|
if (shouldInfer) {
|
|
2513
|
-
const bs =
|
|
2230
|
+
const bs = clack6.spinner();
|
|
2514
2231
|
bs.start("Building import graph...");
|
|
2515
2232
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
2516
2233
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -2528,29 +2245,29 @@ async function configCommand(options, cwd) {
|
|
|
2528
2245
|
}
|
|
2529
2246
|
const shouldWrite = await confirm3("Save updated configuration?");
|
|
2530
2247
|
if (!shouldWrite) {
|
|
2531
|
-
|
|
2248
|
+
clack6.outro("No changes written.");
|
|
2532
2249
|
return;
|
|
2533
2250
|
}
|
|
2534
2251
|
const compacted = (0, import_config6.compactConfig)(config);
|
|
2535
2252
|
fs10.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
2536
2253
|
`);
|
|
2537
2254
|
if (!scanResult) {
|
|
2538
|
-
const s =
|
|
2255
|
+
const s = clack6.spinner();
|
|
2539
2256
|
s.start("Scanning for context generation...");
|
|
2540
2257
|
scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
2541
2258
|
s.stop("Scan complete");
|
|
2542
2259
|
}
|
|
2543
2260
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
2544
|
-
|
|
2261
|
+
clack6.log.success(
|
|
2545
2262
|
`Updated:
|
|
2546
2263
|
${CONFIG_FILE3}
|
|
2547
2264
|
.viberails/context.md
|
|
2548
2265
|
.viberails/scan-result.json`
|
|
2549
2266
|
);
|
|
2550
|
-
|
|
2267
|
+
clack6.outro("Done! Run viberails check to verify.");
|
|
2551
2268
|
}
|
|
2552
2269
|
async function rescanAndMerge(projectRoot, config) {
|
|
2553
|
-
const s =
|
|
2270
|
+
const s = clack6.spinner();
|
|
2554
2271
|
s.start("Re-scanning project...");
|
|
2555
2272
|
const scanResult = await (0, import_scanner.scan)(projectRoot);
|
|
2556
2273
|
const merged = (0, import_config6.mergeConfig)(config, scanResult);
|
|
@@ -2561,9 +2278,9 @@ async function rescanAndMerge(projectRoot, config) {
|
|
|
2561
2278
|
const icon = c.type === "removed" ? "-" : "+";
|
|
2562
2279
|
return `${icon} ${c.description}`;
|
|
2563
2280
|
}).join("\n");
|
|
2564
|
-
|
|
2281
|
+
clack6.note(changeLines, "Changes detected");
|
|
2565
2282
|
} else {
|
|
2566
|
-
|
|
2283
|
+
clack6.log.info("No new changes detected from scan.");
|
|
2567
2284
|
}
|
|
2568
2285
|
Object.assign(config, merged);
|
|
2569
2286
|
return scanResult;
|
|
@@ -2574,10 +2291,9 @@ var fs13 = __toESM(require("fs"), 1);
|
|
|
2574
2291
|
var path13 = __toESM(require("path"), 1);
|
|
2575
2292
|
var import_config7 = require("@viberails/config");
|
|
2576
2293
|
var import_chalk8 = __toESM(require("chalk"), 1);
|
|
2577
|
-
init_prompt();
|
|
2578
2294
|
|
|
2579
2295
|
// src/commands/fix-helpers.ts
|
|
2580
|
-
var
|
|
2296
|
+
var import_node_child_process3 = require("child_process");
|
|
2581
2297
|
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
2582
2298
|
function printPlan(renames, stubs) {
|
|
2583
2299
|
if (renames.length > 0) {
|
|
@@ -2595,7 +2311,7 @@ function printPlan(renames, stubs) {
|
|
|
2595
2311
|
}
|
|
2596
2312
|
function checkGitDirty(projectRoot) {
|
|
2597
2313
|
try {
|
|
2598
|
-
const output = (0,
|
|
2314
|
+
const output = (0, import_node_child_process3.execSync)("git status --porcelain", {
|
|
2599
2315
|
cwd: projectRoot,
|
|
2600
2316
|
encoding: "utf-8",
|
|
2601
2317
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -3058,161 +2774,42 @@ ${import_chalk8.default.yellow("!")} No safe fixes to apply. Resolve aliased imp
|
|
|
3058
2774
|
}
|
|
3059
2775
|
|
|
3060
2776
|
// src/commands/init.ts
|
|
3061
|
-
var
|
|
3062
|
-
var
|
|
3063
|
-
var
|
|
2777
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
2778
|
+
var path21 = __toESM(require("path"), 1);
|
|
2779
|
+
var clack12 = __toESM(require("@clack/prompts"), 1);
|
|
3064
2780
|
var import_config9 = require("@viberails/config");
|
|
3065
2781
|
var import_scanner3 = require("@viberails/scanner");
|
|
3066
|
-
var
|
|
2782
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
3067
2783
|
|
|
3068
|
-
// src/
|
|
3069
|
-
var
|
|
2784
|
+
// src/utils/check-prerequisites.ts
|
|
2785
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
2786
|
+
var path14 = __toESM(require("path"), 1);
|
|
2787
|
+
var clack7 = __toESM(require("@clack/prompts"), 1);
|
|
3070
2788
|
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
primaryParts.push("single package");
|
|
3092
|
-
}
|
|
3093
|
-
primaryParts.push(formatOverviewItem(stack.language));
|
|
3094
|
-
if (stack.styling) {
|
|
3095
|
-
primaryParts.push(formatOverviewItem(stack.styling, import_types6.STYLING_NAMES));
|
|
3096
|
-
}
|
|
3097
|
-
if (stack.packageManager) secondaryParts.push(formatOverviewItem(stack.packageManager));
|
|
3098
|
-
if (stack.linter) secondaryParts.push(formatOverviewItem(stack.linter));
|
|
3099
|
-
if (stack.formatter) secondaryParts.push(formatOverviewItem(stack.formatter));
|
|
3100
|
-
if (stack.testRunner) secondaryParts.push(formatOverviewItem(stack.testRunner));
|
|
3101
|
-
const primary = primaryParts.map((part) => import_chalk9.default.cyan(part)).join(import_chalk9.default.dim(" \xB7 "));
|
|
3102
|
-
const secondary = secondaryParts.join(import_chalk9.default.dim(" \xB7 "));
|
|
3103
|
-
return secondary ? `${primary}
|
|
3104
|
-
${import_chalk9.default.dim(secondary)}` : primary;
|
|
3105
|
-
}
|
|
3106
|
-
function displayInitOverview(scanResult, config, exemptedPackages) {
|
|
3107
|
-
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
3108
|
-
const isMonorepo = config.packages.length > 1;
|
|
3109
|
-
const ok = import_chalk9.default.green("\u2713");
|
|
3110
|
-
const info = import_chalk9.default.yellow("~");
|
|
3111
|
-
console.log("");
|
|
3112
|
-
console.log(` ${import_chalk9.default.bold("Ready to initialize:")}`);
|
|
3113
|
-
console.log(` ${formatDetectedOverview(scanResult)}`);
|
|
3114
|
-
console.log("");
|
|
3115
|
-
console.log(` ${import_chalk9.default.bold("Rules to apply:")}`);
|
|
3116
|
-
console.log(` ${ok} Max file size: ${import_chalk9.default.cyan(`${config.rules.maxFileLines} lines`)}`);
|
|
3117
|
-
const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
|
|
3118
|
-
if (config.rules.enforceNaming && fileNaming) {
|
|
3119
|
-
console.log(` ${ok} File naming: ${import_chalk9.default.cyan(fileNaming)}`);
|
|
3120
|
-
} else {
|
|
3121
|
-
console.log(` ${info} File naming: ${import_chalk9.default.dim("not enforced")}`);
|
|
3122
|
-
}
|
|
3123
|
-
const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
|
|
3124
|
-
if (config.rules.enforceMissingTests && testPattern) {
|
|
3125
|
-
console.log(` ${ok} Missing tests: ${import_chalk9.default.cyan(`enforced (${testPattern})`)}`);
|
|
3126
|
-
} else if (config.rules.enforceMissingTests) {
|
|
3127
|
-
console.log(` ${ok} Missing tests: ${import_chalk9.default.cyan("enforced")}`);
|
|
3128
|
-
} else {
|
|
3129
|
-
console.log(` ${info} Missing tests: ${import_chalk9.default.dim("not enforced")}`);
|
|
3130
|
-
}
|
|
3131
|
-
if (config.rules.testCoverage > 0) {
|
|
3132
|
-
if (isMonorepo) {
|
|
3133
|
-
const withCoverage = config.packages.filter(
|
|
3134
|
-
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3135
|
-
);
|
|
3136
|
-
console.log(
|
|
3137
|
-
` ${ok} Coverage: ${import_chalk9.default.cyan(`${config.rules.testCoverage}%`)} default ${import_chalk9.default.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
|
|
3138
|
-
);
|
|
3139
|
-
} else {
|
|
3140
|
-
console.log(` ${ok} Coverage: ${import_chalk9.default.cyan(`${config.rules.testCoverage}%`)}`);
|
|
3141
|
-
}
|
|
3142
|
-
} else {
|
|
3143
|
-
console.log(` ${info} Coverage: ${import_chalk9.default.dim("disabled")}`);
|
|
3144
|
-
}
|
|
3145
|
-
if (exemptedPackages.length > 0) {
|
|
3146
|
-
console.log(
|
|
3147
|
-
` ${import_chalk9.default.dim(" exempted:")} ${import_chalk9.default.dim(exemptedPackages.join(", "))} ${import_chalk9.default.dim("(types-only)")}`
|
|
3148
|
-
);
|
|
3149
|
-
}
|
|
3150
|
-
console.log("");
|
|
3151
|
-
console.log(` ${import_chalk9.default.bold("Also available:")}`);
|
|
3152
|
-
if (isMonorepo) {
|
|
3153
|
-
console.log(` ${info} Infer boundaries from current imports`);
|
|
3154
|
-
}
|
|
3155
|
-
console.log(` ${info} Set up hooks, Claude integration, and CI checks`);
|
|
3156
|
-
console.log(
|
|
3157
|
-
`
|
|
3158
|
-
${import_chalk9.default.dim("Defaults warn locally. Use --enforce in CI when you want failures to block.")}`
|
|
3159
|
-
);
|
|
3160
|
-
console.log("");
|
|
3161
|
-
}
|
|
3162
|
-
function summarizeSelectedIntegrations(integrations, opts) {
|
|
3163
|
-
const lines = [];
|
|
3164
|
-
if (opts.hasBoundaries) {
|
|
3165
|
-
lines.push("\u2713 Boundary rules: inferred from current imports");
|
|
3166
|
-
} else {
|
|
3167
|
-
lines.push("~ Boundary rules: not enabled");
|
|
3168
|
-
}
|
|
3169
|
-
if (opts.hasCoverage) {
|
|
3170
|
-
lines.push("\u2713 Coverage checks: enabled");
|
|
3171
|
-
} else {
|
|
3172
|
-
lines.push("~ Coverage checks: disabled");
|
|
3173
|
-
}
|
|
3174
|
-
const selectedIntegrations = [
|
|
3175
|
-
integrations.preCommitHook ? "pre-commit hook" : void 0,
|
|
3176
|
-
integrations.typecheckHook ? "typecheck" : void 0,
|
|
3177
|
-
integrations.lintHook ? "lint check" : void 0,
|
|
3178
|
-
integrations.claudeCodeHook ? "Claude Code hook" : void 0,
|
|
3179
|
-
integrations.claudeMdRef ? "CLAUDE.md reference" : void 0,
|
|
3180
|
-
integrations.githubAction ? "GitHub Actions workflow" : void 0
|
|
3181
|
-
].filter(Boolean);
|
|
3182
|
-
if (selectedIntegrations.length > 0) {
|
|
3183
|
-
lines.push(`\u2713 Integrations: ${selectedIntegrations.join(" \xB7 ")}`);
|
|
3184
|
-
} else {
|
|
3185
|
-
lines.push("~ Integrations: none selected");
|
|
3186
|
-
}
|
|
3187
|
-
return lines;
|
|
3188
|
-
}
|
|
3189
|
-
function displaySetupPlan(config, integrations, opts = {}) {
|
|
3190
|
-
const configFile = opts.configFile ?? "viberails.config.json";
|
|
3191
|
-
const lines = summarizeSelectedIntegrations(integrations, {
|
|
3192
|
-
hasBoundaries: config.rules.enforceBoundaries,
|
|
3193
|
-
hasCoverage: config.rules.testCoverage > 0
|
|
2789
|
+
|
|
2790
|
+
// src/utils/spawn-async.ts
|
|
2791
|
+
var import_node_child_process4 = require("child_process");
|
|
2792
|
+
function spawnAsync(command, cwd) {
|
|
2793
|
+
return new Promise((resolve4) => {
|
|
2794
|
+
const child = (0, import_node_child_process4.spawn)(command, { cwd, shell: true, stdio: "pipe" });
|
|
2795
|
+
let stdout = "";
|
|
2796
|
+
let stderr = "";
|
|
2797
|
+
child.stdout.on("data", (d) => {
|
|
2798
|
+
stdout += d.toString();
|
|
2799
|
+
});
|
|
2800
|
+
child.stderr.on("data", (d) => {
|
|
2801
|
+
stderr += d.toString();
|
|
2802
|
+
});
|
|
2803
|
+
child.on("close", (status) => {
|
|
2804
|
+
resolve4({ status, stdout, stderr });
|
|
2805
|
+
});
|
|
2806
|
+
child.on("error", () => {
|
|
2807
|
+
resolve4({ status: 1, stdout, stderr });
|
|
2808
|
+
});
|
|
3194
2809
|
});
|
|
3195
|
-
console.log("");
|
|
3196
|
-
console.log(` ${import_chalk9.default.bold("Ready to write:")}`);
|
|
3197
|
-
console.log(
|
|
3198
|
-
` ${opts.replacingExistingConfig ? import_chalk9.default.yellow("!") : import_chalk9.default.green("\u2713")} ${configFile}${opts.replacingExistingConfig ? import_chalk9.default.dim(" (replacing existing config)") : ""}`
|
|
3199
|
-
);
|
|
3200
|
-
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/context.md`);
|
|
3201
|
-
console.log(` ${import_chalk9.default.green("\u2713")} .viberails/scan-result.json`);
|
|
3202
|
-
for (const line of lines) {
|
|
3203
|
-
const icon = line.startsWith("\u2713") ? import_chalk9.default.green("\u2713") : import_chalk9.default.yellow("~");
|
|
3204
|
-
console.log(` ${icon} ${line.slice(2)}`);
|
|
3205
|
-
}
|
|
3206
|
-
console.log("");
|
|
3207
2810
|
}
|
|
3208
2811
|
|
|
3209
2812
|
// src/utils/check-prerequisites.ts
|
|
3210
|
-
var fs14 = __toESM(require("fs"), 1);
|
|
3211
|
-
var path14 = __toESM(require("path"), 1);
|
|
3212
|
-
var clack8 = __toESM(require("@clack/prompts"), 1);
|
|
3213
|
-
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
3214
|
-
init_prompt();
|
|
3215
|
-
init_spawn_async();
|
|
3216
2813
|
function checkCoveragePrereqs(projectRoot, scanResult) {
|
|
3217
2814
|
const pm = scanResult.stack.packageManager.name;
|
|
3218
2815
|
const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
|
|
@@ -3243,116 +2840,535 @@ function displayMissingPrereqs(prereqs) {
|
|
|
3243
2840
|
const missing = prereqs.filter((p) => !p.installed);
|
|
3244
2841
|
for (const m of missing) {
|
|
3245
2842
|
const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
|
|
3246
|
-
console.log(` ${
|
|
2843
|
+
console.log(` ${import_chalk9.default.yellow("!")} ${m.label} not installed${suffix}`);
|
|
3247
2844
|
if (m.installCommand) {
|
|
3248
|
-
console.log(` Install: ${
|
|
2845
|
+
console.log(` Install: ${import_chalk9.default.cyan(m.installCommand)}`);
|
|
3249
2846
|
}
|
|
3250
2847
|
}
|
|
3251
2848
|
}
|
|
3252
|
-
|
|
3253
|
-
const missing = prereqs.
|
|
3254
|
-
if (missing
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
2849
|
+
function planCoverageInstall(prereqs) {
|
|
2850
|
+
const missing = prereqs.find((p) => !p.installed && p.installCommand);
|
|
2851
|
+
if (!missing?.installCommand) return void 0;
|
|
2852
|
+
return {
|
|
2853
|
+
label: missing.label,
|
|
2854
|
+
command: missing.installCommand
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
function hasDependency(projectRoot, name) {
|
|
2858
|
+
try {
|
|
2859
|
+
const pkgPath = path14.join(projectRoot, "package.json");
|
|
2860
|
+
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
2861
|
+
return !!(pkg.devDependencies?.[name] || pkg.dependencies?.[name]);
|
|
2862
|
+
} catch {
|
|
2863
|
+
return false;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// src/utils/deferred-install.ts
|
|
2868
|
+
var clack8 = __toESM(require("@clack/prompts"), 1);
|
|
2869
|
+
async function executeDeferredInstalls(projectRoot, installs) {
|
|
2870
|
+
if (installs.length === 0) return 0;
|
|
2871
|
+
let successCount = 0;
|
|
2872
|
+
for (const install of installs) {
|
|
2873
|
+
const s = clack8.spinner();
|
|
2874
|
+
s.start(`Installing ${install.label}...`);
|
|
2875
|
+
const result = await spawnAsync(install.command, projectRoot);
|
|
2876
|
+
if (result.status === 0) {
|
|
2877
|
+
s.stop(`Installed ${install.label}`);
|
|
2878
|
+
install.onSuccess?.();
|
|
2879
|
+
successCount++;
|
|
2880
|
+
} else {
|
|
2881
|
+
s.stop(`Failed to install ${install.label}`);
|
|
2882
|
+
clack8.log.warn(`Install manually: ${install.command}`);
|
|
2883
|
+
install.onFailure?.();
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return successCount;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// src/utils/prompt-main-menu.ts
|
|
2890
|
+
var clack10 = __toESM(require("@clack/prompts"), 1);
|
|
2891
|
+
|
|
2892
|
+
// src/utils/prompt-integrations.ts
|
|
2893
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
2894
|
+
var path15 = __toESM(require("path"), 1);
|
|
2895
|
+
var clack9 = __toESM(require("@clack/prompts"), 1);
|
|
2896
|
+
function buildLefthookInstallCommand(pm, isWorkspace) {
|
|
2897
|
+
if (pm === "yarn") return "yarn add -D lefthook";
|
|
2898
|
+
if (pm === "pnpm") return `pnpm add -D${isWorkspace ? " -w" : ""} lefthook`;
|
|
2899
|
+
if (pm === "npm") return "npm install -D lefthook";
|
|
2900
|
+
return `${pm} add -D lefthook`;
|
|
2901
|
+
}
|
|
2902
|
+
async function promptIntegrationsDeferred(hookManager, tools, packageManager, isWorkspace, projectRoot) {
|
|
2903
|
+
const options = [];
|
|
2904
|
+
const needsLefthook = !hookManager;
|
|
2905
|
+
if (needsLefthook) {
|
|
2906
|
+
const pm = packageManager ?? "npm";
|
|
2907
|
+
options.push({
|
|
2908
|
+
value: "installLefthook",
|
|
2909
|
+
label: "Install Lefthook",
|
|
2910
|
+
hint: `after final confirmation \u2014 ${buildLefthookInstallCommand(pm, isWorkspace)}`
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
const hookLabel = hookManager ? `Pre-commit hook (${hookManager})` : "Pre-commit hook";
|
|
2914
|
+
const hookHint = needsLefthook ? "uses Lefthook if installed above, otherwise local git hook" : "runs viberails checks when you commit";
|
|
2915
|
+
options.push({ value: "preCommit", label: hookLabel, hint: hookHint });
|
|
2916
|
+
if (tools?.isTypeScript) {
|
|
2917
|
+
options.push({
|
|
2918
|
+
value: "typecheck",
|
|
2919
|
+
label: "Typecheck (tsc --noEmit)",
|
|
2920
|
+
hint: "pre-commit hook + CI check"
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
if (tools?.linter) {
|
|
2924
|
+
const linterName = tools.linter === "biome" ? "Biome" : "ESLint";
|
|
2925
|
+
options.push({
|
|
2926
|
+
value: "lint",
|
|
2927
|
+
label: `Lint check (${linterName})`,
|
|
2928
|
+
hint: "pre-commit hook + CI check"
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
options.push(
|
|
2932
|
+
{
|
|
2933
|
+
value: "claude",
|
|
2934
|
+
label: "Claude Code hook",
|
|
2935
|
+
hint: "checks files when Claude edits them"
|
|
2936
|
+
},
|
|
2937
|
+
{
|
|
2938
|
+
value: "claudeMd",
|
|
2939
|
+
label: "CLAUDE.md reference",
|
|
2940
|
+
hint: "appends @.viberails/context.md so Claude loads rules automatically"
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
value: "githubAction",
|
|
2944
|
+
label: "GitHub Actions workflow",
|
|
2945
|
+
hint: "blocks PRs that fail viberails check"
|
|
2946
|
+
}
|
|
2947
|
+
);
|
|
2948
|
+
const initialValues = options.map((o) => o.value);
|
|
2949
|
+
const result = await clack9.multiselect({
|
|
2950
|
+
message: "Integrations",
|
|
2951
|
+
options,
|
|
2952
|
+
initialValues,
|
|
2953
|
+
required: false
|
|
2954
|
+
});
|
|
2955
|
+
assertNotCancelled(result);
|
|
2956
|
+
let lefthookInstall;
|
|
2957
|
+
if (needsLefthook && result.includes("installLefthook")) {
|
|
2958
|
+
const pm = packageManager ?? "npm";
|
|
2959
|
+
lefthookInstall = {
|
|
2960
|
+
label: "Lefthook",
|
|
2961
|
+
command: buildLefthookInstallCommand(pm, isWorkspace),
|
|
2962
|
+
onSuccess: projectRoot ? () => {
|
|
2963
|
+
const ymlPath = path15.join(projectRoot, "lefthook.yml");
|
|
2964
|
+
if (!fs15.existsSync(ymlPath)) {
|
|
2965
|
+
fs15.writeFileSync(ymlPath, "# Generated by viberails\n");
|
|
2966
|
+
}
|
|
2967
|
+
} : void 0
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
return {
|
|
2971
|
+
choice: {
|
|
2972
|
+
preCommitHook: result.includes("preCommit"),
|
|
2973
|
+
claudeCodeHook: result.includes("claude"),
|
|
2974
|
+
claudeMdRef: result.includes("claudeMd"),
|
|
2975
|
+
githubAction: result.includes("githubAction"),
|
|
2976
|
+
typecheckHook: result.includes("typecheck"),
|
|
2977
|
+
lintHook: result.includes("lint")
|
|
2978
|
+
},
|
|
2979
|
+
lefthookInstall
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
// src/utils/prompt-main-menu-hints.ts
|
|
2984
|
+
function fileLimitsHint(config) {
|
|
2985
|
+
const max = config.rules.maxFileLines;
|
|
2986
|
+
const test = config.rules.maxTestFileLines;
|
|
2987
|
+
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
2988
|
+
}
|
|
2989
|
+
function fileNamingHint(config, scanResult) {
|
|
2990
|
+
const rootPkg = getRootPackage(config.packages);
|
|
2991
|
+
const naming = rootPkg.conventions?.fileNaming;
|
|
2992
|
+
if (!config.rules.enforceNaming) return "not enforced";
|
|
2993
|
+
if (naming) {
|
|
2994
|
+
const detected = scanResult.packages.some(
|
|
2995
|
+
(p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
|
|
2996
|
+
);
|
|
2997
|
+
return detected ? `${naming} (detected)` : naming;
|
|
2998
|
+
}
|
|
2999
|
+
return "mixed \u2014 will not enforce if skipped";
|
|
3000
|
+
}
|
|
3001
|
+
function fileNamingStatus(config) {
|
|
3002
|
+
if (!config.rules.enforceNaming) return "disabled";
|
|
3003
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3004
|
+
return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
|
|
3005
|
+
}
|
|
3006
|
+
function missingTestsHint(config) {
|
|
3007
|
+
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3008
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3009
|
+
const pattern = rootPkg.structure?.testPattern;
|
|
3010
|
+
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3011
|
+
}
|
|
3012
|
+
function coverageHint(config, hasTestRunner) {
|
|
3013
|
+
if (config.rules.testCoverage === 0) return "disabled";
|
|
3014
|
+
if (!hasTestRunner)
|
|
3015
|
+
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3016
|
+
const isMonorepo = config.packages.length > 1;
|
|
3017
|
+
if (isMonorepo) {
|
|
3018
|
+
const withCov = config.packages.filter(
|
|
3019
|
+
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3020
|
+
);
|
|
3021
|
+
const exempt = config.packages.length - withCov.length;
|
|
3022
|
+
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3023
|
+
}
|
|
3024
|
+
return `${config.rules.testCoverage}%`;
|
|
3025
|
+
}
|
|
3026
|
+
function advancedNamingHint(config) {
|
|
3027
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3028
|
+
const parts = [];
|
|
3029
|
+
if (rootPkg.conventions?.componentNaming)
|
|
3030
|
+
parts.push(`${rootPkg.conventions.componentNaming} components`);
|
|
3031
|
+
if (rootPkg.conventions?.hookNaming) parts.push(`${rootPkg.conventions.hookNaming} hooks`);
|
|
3032
|
+
if (rootPkg.conventions?.importAlias) parts.push(rootPkg.conventions.importAlias);
|
|
3033
|
+
return parts.length > 0 ? parts.join(", ") : "component, hook, and alias conventions";
|
|
3034
|
+
}
|
|
3035
|
+
function integrationsHint(state) {
|
|
3036
|
+
if (!state.visited.integrations || !state.integrations)
|
|
3037
|
+
return "not configured \u2014 select to set up";
|
|
3038
|
+
const items = [];
|
|
3039
|
+
if (state.integrations.preCommitHook) items.push("pre-commit");
|
|
3040
|
+
if (state.integrations.typecheckHook) items.push("typecheck");
|
|
3041
|
+
if (state.integrations.lintHook) items.push("lint");
|
|
3042
|
+
if (state.integrations.claudeCodeHook) items.push("Claude");
|
|
3043
|
+
if (state.integrations.claudeMdRef) items.push("CLAUDE.md");
|
|
3044
|
+
if (state.integrations.githubAction) items.push("CI");
|
|
3045
|
+
return items.length > 0 ? items.join(" \xB7 ") : "none selected";
|
|
3046
|
+
}
|
|
3047
|
+
function packageOverridesHint(config) {
|
|
3048
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3049
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3050
|
+
const customized = editable.filter(
|
|
3051
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3052
|
+
).length;
|
|
3053
|
+
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3054
|
+
}
|
|
3055
|
+
function boundariesHint(config, state) {
|
|
3056
|
+
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3057
|
+
const deny = config.boundaries?.deny;
|
|
3058
|
+
if (!deny) return "enabled";
|
|
3059
|
+
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3060
|
+
const pkgCount = Object.keys(deny).length;
|
|
3061
|
+
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3062
|
+
}
|
|
3063
|
+
function statusIcon(status) {
|
|
3064
|
+
if (status === "ok") return "\u2713";
|
|
3065
|
+
if (status === "needs-input") return "?";
|
|
3066
|
+
return "~";
|
|
3067
|
+
}
|
|
3068
|
+
function buildMainMenuOptions(config, scanResult, state) {
|
|
3069
|
+
const namingStatus = fileNamingStatus(config);
|
|
3070
|
+
const coverageStatus = config.rules.testCoverage === 0 ? "disabled" : !state.hasTestRunner ? "disabled" : "ok";
|
|
3071
|
+
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "disabled";
|
|
3072
|
+
const options = [
|
|
3073
|
+
{
|
|
3074
|
+
value: "fileLimits",
|
|
3075
|
+
label: `${statusIcon("ok")} Max file size`,
|
|
3076
|
+
hint: fileLimitsHint(config)
|
|
3077
|
+
},
|
|
3078
|
+
{
|
|
3079
|
+
value: "fileNaming",
|
|
3080
|
+
label: `${statusIcon(namingStatus)} File naming`,
|
|
3081
|
+
hint: fileNamingHint(config, scanResult)
|
|
3082
|
+
},
|
|
3083
|
+
{
|
|
3084
|
+
value: "missingTests",
|
|
3085
|
+
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3086
|
+
hint: missingTestsHint(config)
|
|
3087
|
+
},
|
|
3088
|
+
{
|
|
3089
|
+
value: "coverage",
|
|
3090
|
+
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3091
|
+
hint: coverageHint(config, state.hasTestRunner)
|
|
3092
|
+
},
|
|
3093
|
+
{ value: "advancedNaming", label: " Advanced naming", hint: advancedNamingHint(config) }
|
|
3094
|
+
];
|
|
3095
|
+
if (config.packages.length > 1) {
|
|
3096
|
+
const bIcon = state.visited.boundaries && config.rules.enforceBoundaries ? statusIcon("ok") : " ";
|
|
3097
|
+
options.push(
|
|
3098
|
+
{
|
|
3099
|
+
value: "packageOverrides",
|
|
3100
|
+
label: " Per-package overrides",
|
|
3101
|
+
hint: packageOverridesHint(config)
|
|
3102
|
+
},
|
|
3103
|
+
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
const iIcon = state.visited.integrations ? statusIcon("ok") : " ";
|
|
3107
|
+
options.push(
|
|
3108
|
+
{ value: "integrations", label: `${iIcon} Integrations`, hint: integrationsHint(state) },
|
|
3109
|
+
{ value: "reset", label: " Reset all to defaults" },
|
|
3110
|
+
{ value: "review", label: " Review scan details" },
|
|
3111
|
+
{ value: "done", label: " Done \u2014 write config" }
|
|
3112
|
+
);
|
|
3113
|
+
return options;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/utils/prompt-main-menu.ts
|
|
3117
|
+
async function handleAdvancedNaming(config) {
|
|
3118
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3119
|
+
const state = {
|
|
3120
|
+
maxFileLines: config.rules.maxFileLines,
|
|
3121
|
+
maxTestFileLines: config.rules.maxTestFileLines,
|
|
3122
|
+
testCoverage: config.rules.testCoverage,
|
|
3123
|
+
enforceMissingTests: config.rules.enforceMissingTests,
|
|
3124
|
+
enforceNaming: config.rules.enforceNaming,
|
|
3125
|
+
fileNamingValue: rootPkg.conventions?.fileNaming,
|
|
3126
|
+
componentNaming: rootPkg.conventions?.componentNaming,
|
|
3127
|
+
hookNaming: rootPkg.conventions?.hookNaming,
|
|
3128
|
+
importAlias: rootPkg.conventions?.importAlias,
|
|
3129
|
+
coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
|
|
3130
|
+
coverageCommand: config.defaults?.coverage?.command
|
|
3131
|
+
};
|
|
3132
|
+
await promptNamingMenu(state);
|
|
3133
|
+
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
3134
|
+
config.rules.enforceNaming = state.enforceNaming;
|
|
3135
|
+
if (state.fileNamingValue) {
|
|
3136
|
+
rootPkg.conventions.fileNaming = state.fileNamingValue;
|
|
3137
|
+
} else {
|
|
3138
|
+
delete rootPkg.conventions.fileNaming;
|
|
3139
|
+
}
|
|
3140
|
+
rootPkg.conventions.componentNaming = state.componentNaming || void 0;
|
|
3141
|
+
rootPkg.conventions.hookNaming = state.hookNaming || void 0;
|
|
3142
|
+
rootPkg.conventions.importAlias = state.importAlias || void 0;
|
|
3143
|
+
}
|
|
3144
|
+
async function promptMainMenu(config, scanResult, opts) {
|
|
3145
|
+
const originalConfig = structuredClone(config);
|
|
3146
|
+
const state = {
|
|
3147
|
+
visited: { integrations: false, boundaries: false },
|
|
3148
|
+
deferredInstalls: [],
|
|
3149
|
+
hasTestRunner: opts.hasTestRunner,
|
|
3150
|
+
hookManager: opts.hookManager
|
|
3151
|
+
};
|
|
3152
|
+
while (true) {
|
|
3153
|
+
const options = buildMainMenuOptions(config, scanResult, state);
|
|
3154
|
+
const choice = await clack10.select({ message: "Configure viberails", options });
|
|
3155
|
+
assertNotCancelled(choice);
|
|
3156
|
+
if (choice === "done") {
|
|
3157
|
+
if (config.rules.enforceNaming && !getRootPackage(config.packages).conventions?.fileNaming) {
|
|
3158
|
+
config.rules.enforceNaming = false;
|
|
3159
|
+
}
|
|
3160
|
+
break;
|
|
3161
|
+
}
|
|
3162
|
+
if (choice === "fileLimits") {
|
|
3163
|
+
const s = {
|
|
3164
|
+
maxFileLines: config.rules.maxFileLines,
|
|
3165
|
+
maxTestFileLines: config.rules.maxTestFileLines
|
|
3166
|
+
};
|
|
3167
|
+
await promptFileLimitsMenu(s);
|
|
3168
|
+
config.rules.maxFileLines = s.maxFileLines;
|
|
3169
|
+
config.rules.maxTestFileLines = s.maxTestFileLines;
|
|
3170
|
+
}
|
|
3171
|
+
if (choice === "fileNaming") await handleFileNaming(config, scanResult);
|
|
3172
|
+
if (choice === "missingTests") await handleMissingTests(config);
|
|
3173
|
+
if (choice === "coverage") await handleCoverage(config, state, opts);
|
|
3174
|
+
if (choice === "advancedNaming") await handleAdvancedNaming(config);
|
|
3175
|
+
if (choice === "packageOverrides") await handlePackageOverrides(config);
|
|
3176
|
+
if (choice === "boundaries") await handleBoundaries(config, state, opts);
|
|
3177
|
+
if (choice === "integrations") await handleIntegrations(state, opts);
|
|
3178
|
+
if (choice === "review") clack10.note(formatScanResultsText(scanResult), "Scan details");
|
|
3179
|
+
if (choice === "reset") {
|
|
3180
|
+
Object.assign(config, structuredClone(originalConfig));
|
|
3181
|
+
state.deferredInstalls = [];
|
|
3182
|
+
state.visited = { integrations: false, boundaries: false };
|
|
3183
|
+
state.integrations = void 0;
|
|
3184
|
+
clack10.log.info("Reset all settings to scan-detected defaults.");
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
return state;
|
|
3188
|
+
}
|
|
3189
|
+
async function handleFileNaming(config, scanResult) {
|
|
3190
|
+
const isMonorepo = config.packages.length > 1;
|
|
3191
|
+
if (isMonorepo) {
|
|
3192
|
+
const pkgData = scanResult.packages.filter((p) => p.conventions.fileNaming && p.conventions.fileNaming.confidence !== "low").map((p) => ({
|
|
3193
|
+
path: p.relativePath,
|
|
3194
|
+
naming: p.conventions.fileNaming
|
|
3195
|
+
}));
|
|
3196
|
+
if (pkgData.length > 0) {
|
|
3197
|
+
const lines = pkgData.map(
|
|
3198
|
+
(p) => `${p.path}: ${p.naming.value} (${Math.round(p.naming.consistency)}%)`
|
|
3199
|
+
);
|
|
3200
|
+
clack10.note(lines.join("\n"), "Per-package file naming detected");
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
const namingOptions = FILE_NAMING_OPTIONS.map((opt) => {
|
|
3204
|
+
if (isMonorepo) {
|
|
3205
|
+
const pkgs = scanResult.packages.filter((p) => p.conventions.fileNaming?.value === opt.value);
|
|
3206
|
+
const hint = pkgs.length > 0 ? `${pkgs.length} package${pkgs.length > 1 ? "s" : ""}` : void 0;
|
|
3207
|
+
return { value: opt.value, label: opt.label, hint };
|
|
3208
|
+
}
|
|
3209
|
+
return { value: opt.value, label: opt.label };
|
|
3210
|
+
});
|
|
3211
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3212
|
+
const selected = await clack10.select({
|
|
3213
|
+
message: isMonorepo ? "Default file naming convention" : "File naming convention",
|
|
3214
|
+
options: [...namingOptions, { value: SENTINEL_SKIP, label: "Don't enforce" }],
|
|
3215
|
+
initialValue: rootPkg.conventions?.fileNaming ?? SENTINEL_SKIP
|
|
3216
|
+
});
|
|
3217
|
+
assertNotCancelled(selected);
|
|
3218
|
+
if (selected === SENTINEL_SKIP) {
|
|
3219
|
+
config.rules.enforceNaming = false;
|
|
3220
|
+
if (rootPkg.conventions) delete rootPkg.conventions.fileNaming;
|
|
3221
|
+
} else {
|
|
3222
|
+
config.rules.enforceNaming = true;
|
|
3223
|
+
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
3224
|
+
rootPkg.conventions.fileNaming = selected;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
async function handleMissingTests(config) {
|
|
3228
|
+
const result = await clack10.confirm({
|
|
3229
|
+
message: "Require every source file to have a test file?",
|
|
3230
|
+
initialValue: config.rules.enforceMissingTests
|
|
3231
|
+
});
|
|
3232
|
+
assertNotCancelled(result);
|
|
3233
|
+
config.rules.enforceMissingTests = result;
|
|
3234
|
+
}
|
|
3235
|
+
async function handleCoverage(config, state, opts) {
|
|
3236
|
+
if (!opts.hasTestRunner) {
|
|
3237
|
+
clack10.log.info("Coverage checks are inactive \u2014 no test runner detected.");
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
const planned = planCoverageInstall(opts.coveragePrereqs);
|
|
3241
|
+
if (planned) {
|
|
3242
|
+
const choice = await clack10.select({
|
|
3243
|
+
message: `${planned.label} is not installed. Needed for coverage checks.`,
|
|
3268
3244
|
options: [
|
|
3269
3245
|
{
|
|
3270
3246
|
value: "install",
|
|
3271
|
-
label: "Install
|
|
3272
|
-
hint:
|
|
3273
|
-
},
|
|
3274
|
-
{
|
|
3275
|
-
value: "disable",
|
|
3276
|
-
label: "Disable coverage checks",
|
|
3277
|
-
hint: "missing-test checks still stay active"
|
|
3247
|
+
label: "Install (after final confirmation)",
|
|
3248
|
+
hint: planned.command
|
|
3278
3249
|
},
|
|
3250
|
+
{ value: "disable", label: "Disable coverage checks" },
|
|
3279
3251
|
{
|
|
3280
3252
|
value: "skip",
|
|
3281
3253
|
label: "Skip for now",
|
|
3282
|
-
hint: `install later: ${
|
|
3254
|
+
hint: `install later: ${planned.command}`
|
|
3283
3255
|
}
|
|
3284
3256
|
]
|
|
3285
3257
|
});
|
|
3286
3258
|
assertNotCancelled(choice);
|
|
3259
|
+
state.deferredInstalls = state.deferredInstalls.filter((d) => d.command !== planned.command);
|
|
3287
3260
|
if (choice === "install") {
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
is.stop(`Installed ${m.label}`);
|
|
3293
|
-
} else {
|
|
3294
|
-
is.stop(`Failed to install ${m.label}`);
|
|
3295
|
-
clack8.log.warn(
|
|
3296
|
-
`Install manually: ${m.installCommand}
|
|
3297
|
-
Coverage percentage checks will not work until the dependency is installed.`
|
|
3298
|
-
);
|
|
3299
|
-
}
|
|
3261
|
+
planned.onFailure = () => {
|
|
3262
|
+
config.rules.testCoverage = 0;
|
|
3263
|
+
};
|
|
3264
|
+
state.deferredInstalls.push(planned);
|
|
3300
3265
|
} else if (choice === "disable") {
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
} else {
|
|
3304
|
-
clack8.log.info(
|
|
3305
|
-
`Coverage percentage checks will fail until ${m.label} is installed.
|
|
3306
|
-
Install later: ${m.installCommand}`
|
|
3307
|
-
);
|
|
3266
|
+
config.rules.testCoverage = 0;
|
|
3267
|
+
return;
|
|
3308
3268
|
}
|
|
3309
3269
|
}
|
|
3310
|
-
|
|
3270
|
+
const result = await clack10.text({
|
|
3271
|
+
message: "Test coverage target (0 = disable)?",
|
|
3272
|
+
initialValue: String(config.rules.testCoverage),
|
|
3273
|
+
validate: (v) => {
|
|
3274
|
+
if (typeof v !== "string") return "Enter a number between 0 and 100";
|
|
3275
|
+
const n = Number.parseInt(v, 10);
|
|
3276
|
+
if (Number.isNaN(n) || n < 0 || n > 100) return "Enter a number between 0 and 100";
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
assertNotCancelled(result);
|
|
3280
|
+
config.rules.testCoverage = Number.parseInt(result, 10);
|
|
3311
3281
|
}
|
|
3312
|
-
function
|
|
3282
|
+
async function handlePackageOverrides(config) {
|
|
3283
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3284
|
+
config.packages = await promptPackageOverrides(config.packages, {
|
|
3285
|
+
fileNamingValue: rootPkg.conventions?.fileNaming,
|
|
3286
|
+
maxFileLines: config.rules.maxFileLines,
|
|
3287
|
+
testCoverage: config.rules.testCoverage,
|
|
3288
|
+
coverageSummaryPath: rootPkg.coverage?.summaryPath ?? "coverage/coverage-summary.json",
|
|
3289
|
+
coverageCommand: config.defaults?.coverage?.command
|
|
3290
|
+
});
|
|
3291
|
+
normalizePackageOverrides(config.packages);
|
|
3292
|
+
}
|
|
3293
|
+
async function handleBoundaries(config, state, opts) {
|
|
3294
|
+
const shouldInfer = await clack10.confirm({
|
|
3295
|
+
message: "Infer boundary rules from current import patterns?",
|
|
3296
|
+
initialValue: false
|
|
3297
|
+
});
|
|
3298
|
+
assertNotCancelled(shouldInfer);
|
|
3299
|
+
state.visited.boundaries = true;
|
|
3300
|
+
if (!shouldInfer) {
|
|
3301
|
+
config.rules.enforceBoundaries = false;
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
const bs = clack10.spinner();
|
|
3305
|
+
bs.start("Building import graph...");
|
|
3313
3306
|
try {
|
|
3314
|
-
const
|
|
3315
|
-
const
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3307
|
+
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3308
|
+
const packages = resolveWorkspacePackages(opts.projectRoot, config.packages);
|
|
3309
|
+
const graph = await buildImportGraph(opts.projectRoot, { packages, ignore: config.ignore });
|
|
3310
|
+
const inferred = inferBoundaries(graph);
|
|
3311
|
+
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
3312
|
+
if (denyCount > 0) {
|
|
3313
|
+
config.boundaries = inferred;
|
|
3314
|
+
config.rules.enforceBoundaries = true;
|
|
3315
|
+
const pkgCount = Object.keys(inferred.deny).length;
|
|
3316
|
+
bs.stop(`Inferred ${denyCount} boundary rules across ${pkgCount} packages`);
|
|
3317
|
+
} else {
|
|
3318
|
+
bs.stop("No boundary rules inferred");
|
|
3319
|
+
}
|
|
3320
|
+
} catch (err) {
|
|
3321
|
+
bs.stop("Failed to build import graph");
|
|
3322
|
+
clack10.log.warn(`Boundary inference failed: ${err instanceof Error ? err.message : err}`);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
async function handleIntegrations(state, opts) {
|
|
3326
|
+
const result = await promptIntegrationsDeferred(
|
|
3327
|
+
state.hookManager,
|
|
3328
|
+
opts.tools,
|
|
3329
|
+
opts.tools.packageManager,
|
|
3330
|
+
opts.tools.isWorkspace,
|
|
3331
|
+
opts.projectRoot
|
|
3332
|
+
);
|
|
3333
|
+
state.visited.integrations = true;
|
|
3334
|
+
state.integrations = result.choice;
|
|
3335
|
+
state.deferredInstalls = state.deferredInstalls.filter((d) => !d.command.includes("lefthook"));
|
|
3336
|
+
if (result.lefthookInstall) {
|
|
3337
|
+
state.deferredInstalls.push(result.lefthookInstall);
|
|
3319
3338
|
}
|
|
3320
3339
|
}
|
|
3321
|
-
|
|
3322
|
-
// src/commands/init.ts
|
|
3323
|
-
init_prompt();
|
|
3324
3340
|
|
|
3325
3341
|
// src/utils/update-gitignore.ts
|
|
3326
|
-
var
|
|
3327
|
-
var
|
|
3342
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
3343
|
+
var path16 = __toESM(require("path"), 1);
|
|
3328
3344
|
function updateGitignore(projectRoot) {
|
|
3329
|
-
const gitignorePath =
|
|
3345
|
+
const gitignorePath = path16.join(projectRoot, ".gitignore");
|
|
3330
3346
|
let content = "";
|
|
3331
|
-
if (
|
|
3332
|
-
content =
|
|
3347
|
+
if (fs16.existsSync(gitignorePath)) {
|
|
3348
|
+
content = fs16.readFileSync(gitignorePath, "utf-8");
|
|
3333
3349
|
}
|
|
3334
3350
|
if (!content.includes(".viberails/scan-result.json")) {
|
|
3335
3351
|
const block = "\n# viberails\n.viberails/scan-result.json\n";
|
|
3336
3352
|
const prefix = content.length === 0 ? "" : `${content.trimEnd()}
|
|
3337
3353
|
`;
|
|
3338
|
-
|
|
3354
|
+
fs16.writeFileSync(gitignorePath, `${prefix}${block}`);
|
|
3339
3355
|
}
|
|
3340
3356
|
}
|
|
3341
3357
|
|
|
3342
3358
|
// src/commands/init-hooks.ts
|
|
3343
|
-
var
|
|
3344
|
-
var
|
|
3345
|
-
var
|
|
3359
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
3360
|
+
var path18 = __toESM(require("path"), 1);
|
|
3361
|
+
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
3346
3362
|
var import_yaml = require("yaml");
|
|
3347
3363
|
|
|
3348
3364
|
// src/commands/resolve-typecheck.ts
|
|
3349
|
-
var
|
|
3350
|
-
var
|
|
3365
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
3366
|
+
var path17 = __toESM(require("path"), 1);
|
|
3351
3367
|
function hasTurboTask(projectRoot, taskName) {
|
|
3352
|
-
const turboPath =
|
|
3353
|
-
if (!
|
|
3368
|
+
const turboPath = path17.join(projectRoot, "turbo.json");
|
|
3369
|
+
if (!fs17.existsSync(turboPath)) return false;
|
|
3354
3370
|
try {
|
|
3355
|
-
const turbo = JSON.parse(
|
|
3371
|
+
const turbo = JSON.parse(fs17.readFileSync(turboPath, "utf-8"));
|
|
3356
3372
|
const tasks = turbo.tasks ?? turbo.pipeline ?? {};
|
|
3357
3373
|
return taskName in tasks;
|
|
3358
3374
|
} catch {
|
|
@@ -3363,10 +3379,10 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3363
3379
|
if (hasTurboTask(projectRoot, "typecheck")) {
|
|
3364
3380
|
return { command: "npx turbo typecheck", label: "turbo typecheck" };
|
|
3365
3381
|
}
|
|
3366
|
-
const pkgJsonPath =
|
|
3367
|
-
if (
|
|
3382
|
+
const pkgJsonPath = path17.join(projectRoot, "package.json");
|
|
3383
|
+
if (fs17.existsSync(pkgJsonPath)) {
|
|
3368
3384
|
try {
|
|
3369
|
-
const pkg = JSON.parse(
|
|
3385
|
+
const pkg = JSON.parse(fs17.readFileSync(pkgJsonPath, "utf-8"));
|
|
3370
3386
|
if (pkg.scripts?.typecheck) {
|
|
3371
3387
|
const pm = packageManager ?? "npm";
|
|
3372
3388
|
return { command: `${pm} run typecheck`, label: `${pm} run typecheck` };
|
|
@@ -3374,7 +3390,7 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3374
3390
|
} catch {
|
|
3375
3391
|
}
|
|
3376
3392
|
}
|
|
3377
|
-
if (
|
|
3393
|
+
if (fs17.existsSync(path17.join(projectRoot, "tsconfig.json"))) {
|
|
3378
3394
|
return { command: "npx tsc --noEmit", label: "tsc --noEmit" };
|
|
3379
3395
|
}
|
|
3380
3396
|
return {
|
|
@@ -3384,36 +3400,36 @@ function resolveTypecheckCommand(projectRoot, packageManager) {
|
|
|
3384
3400
|
|
|
3385
3401
|
// src/commands/init-hooks.ts
|
|
3386
3402
|
function setupPreCommitHook(projectRoot) {
|
|
3387
|
-
const lefthookPath =
|
|
3388
|
-
if (
|
|
3403
|
+
const lefthookPath = path18.join(projectRoot, "lefthook.yml");
|
|
3404
|
+
if (fs18.existsSync(lefthookPath)) {
|
|
3389
3405
|
addLefthookPreCommit(lefthookPath);
|
|
3390
|
-
console.log(` ${
|
|
3406
|
+
console.log(` ${import_chalk10.default.green("\u2713")} lefthook.yml \u2014 added viberails pre-commit`);
|
|
3391
3407
|
return "lefthook.yml";
|
|
3392
3408
|
}
|
|
3393
|
-
const huskyDir =
|
|
3394
|
-
if (
|
|
3409
|
+
const huskyDir = path18.join(projectRoot, ".husky");
|
|
3410
|
+
if (fs18.existsSync(huskyDir)) {
|
|
3395
3411
|
writeHuskyPreCommit(huskyDir);
|
|
3396
|
-
console.log(` ${
|
|
3412
|
+
console.log(` ${import_chalk10.default.green("\u2713")} .husky/pre-commit \u2014 added viberails check`);
|
|
3397
3413
|
return ".husky/pre-commit";
|
|
3398
3414
|
}
|
|
3399
|
-
const gitDir =
|
|
3400
|
-
if (
|
|
3401
|
-
const hooksDir =
|
|
3402
|
-
if (!
|
|
3403
|
-
|
|
3415
|
+
const gitDir = path18.join(projectRoot, ".git");
|
|
3416
|
+
if (fs18.existsSync(gitDir)) {
|
|
3417
|
+
const hooksDir = path18.join(gitDir, "hooks");
|
|
3418
|
+
if (!fs18.existsSync(hooksDir)) {
|
|
3419
|
+
fs18.mkdirSync(hooksDir, { recursive: true });
|
|
3404
3420
|
}
|
|
3405
3421
|
writeGitHookPreCommit(hooksDir);
|
|
3406
|
-
console.log(` ${
|
|
3422
|
+
console.log(` ${import_chalk10.default.green("\u2713")} .git/hooks/pre-commit`);
|
|
3407
3423
|
return ".git/hooks/pre-commit";
|
|
3408
3424
|
}
|
|
3409
3425
|
return void 0;
|
|
3410
3426
|
}
|
|
3411
3427
|
function writeGitHookPreCommit(hooksDir) {
|
|
3412
|
-
const hookPath =
|
|
3413
|
-
if (
|
|
3414
|
-
const existing =
|
|
3428
|
+
const hookPath = path18.join(hooksDir, "pre-commit");
|
|
3429
|
+
if (fs18.existsSync(hookPath)) {
|
|
3430
|
+
const existing = fs18.readFileSync(hookPath, "utf-8");
|
|
3415
3431
|
if (existing.includes("viberails")) return;
|
|
3416
|
-
|
|
3432
|
+
fs18.writeFileSync(
|
|
3417
3433
|
hookPath,
|
|
3418
3434
|
`${existing.trimEnd()}
|
|
3419
3435
|
|
|
@@ -3430,10 +3446,10 @@ if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails chec
|
|
|
3430
3446
|
"if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi",
|
|
3431
3447
|
""
|
|
3432
3448
|
].join("\n");
|
|
3433
|
-
|
|
3449
|
+
fs18.writeFileSync(hookPath, script, { mode: 493 });
|
|
3434
3450
|
}
|
|
3435
3451
|
function addLefthookPreCommit(lefthookPath) {
|
|
3436
|
-
const content =
|
|
3452
|
+
const content = fs18.readFileSync(lefthookPath, "utf-8");
|
|
3437
3453
|
if (content.includes("viberails")) return;
|
|
3438
3454
|
const doc = (0, import_yaml.parse)(content) ?? {};
|
|
3439
3455
|
if (!doc["pre-commit"]) {
|
|
@@ -3445,28 +3461,28 @@ function addLefthookPreCommit(lefthookPath) {
|
|
|
3445
3461
|
doc["pre-commit"].commands.viberails = {
|
|
3446
3462
|
run: "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi"
|
|
3447
3463
|
};
|
|
3448
|
-
|
|
3464
|
+
fs18.writeFileSync(lefthookPath, (0, import_yaml.stringify)(doc));
|
|
3449
3465
|
}
|
|
3450
3466
|
function detectHookManager(projectRoot) {
|
|
3451
|
-
if (
|
|
3452
|
-
if (
|
|
3467
|
+
if (fs18.existsSync(path18.join(projectRoot, "lefthook.yml"))) return "Lefthook";
|
|
3468
|
+
if (fs18.existsSync(path18.join(projectRoot, ".husky"))) return "Husky";
|
|
3453
3469
|
return void 0;
|
|
3454
3470
|
}
|
|
3455
3471
|
function setupClaudeCodeHook(projectRoot) {
|
|
3456
|
-
const claudeDir =
|
|
3457
|
-
if (!
|
|
3458
|
-
|
|
3472
|
+
const claudeDir = path18.join(projectRoot, ".claude");
|
|
3473
|
+
if (!fs18.existsSync(claudeDir)) {
|
|
3474
|
+
fs18.mkdirSync(claudeDir, { recursive: true });
|
|
3459
3475
|
}
|
|
3460
|
-
const settingsPath =
|
|
3476
|
+
const settingsPath = path18.join(claudeDir, "settings.json");
|
|
3461
3477
|
let settings = {};
|
|
3462
|
-
if (
|
|
3478
|
+
if (fs18.existsSync(settingsPath)) {
|
|
3463
3479
|
try {
|
|
3464
|
-
settings = JSON.parse(
|
|
3480
|
+
settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
|
|
3465
3481
|
} catch {
|
|
3466
3482
|
console.warn(
|
|
3467
|
-
` ${
|
|
3483
|
+
` ${import_chalk10.default.yellow("!")} .claude/settings.json contains invalid JSON \u2014 skipping hook setup`
|
|
3468
3484
|
);
|
|
3469
|
-
console.warn(` Fix the JSON manually, then re-run ${
|
|
3485
|
+
console.warn(` Fix the JSON manually, then re-run ${import_chalk10.default.cyan("viberails init --force")}`);
|
|
3470
3486
|
return;
|
|
3471
3487
|
}
|
|
3472
3488
|
}
|
|
@@ -3487,30 +3503,30 @@ function setupClaudeCodeHook(projectRoot) {
|
|
|
3487
3503
|
}
|
|
3488
3504
|
];
|
|
3489
3505
|
settings.hooks = hooks;
|
|
3490
|
-
|
|
3506
|
+
fs18.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
3491
3507
|
`);
|
|
3492
|
-
console.log(` ${
|
|
3508
|
+
console.log(` ${import_chalk10.default.green("\u2713")} .claude/settings.json \u2014 added viberails PostToolUse hook`);
|
|
3493
3509
|
}
|
|
3494
3510
|
function setupClaudeMdReference(projectRoot) {
|
|
3495
|
-
const claudeMdPath =
|
|
3511
|
+
const claudeMdPath = path18.join(projectRoot, "CLAUDE.md");
|
|
3496
3512
|
let content = "";
|
|
3497
|
-
if (
|
|
3498
|
-
content =
|
|
3513
|
+
if (fs18.existsSync(claudeMdPath)) {
|
|
3514
|
+
content = fs18.readFileSync(claudeMdPath, "utf-8");
|
|
3499
3515
|
}
|
|
3500
3516
|
if (content.includes("@.viberails/context.md")) return;
|
|
3501
3517
|
const ref = "\n@.viberails/context.md\n";
|
|
3502
3518
|
const prefix = content.length === 0 ? "" : content.trimEnd();
|
|
3503
|
-
|
|
3504
|
-
console.log(` ${
|
|
3519
|
+
fs18.writeFileSync(claudeMdPath, prefix + ref);
|
|
3520
|
+
console.log(` ${import_chalk10.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
|
|
3505
3521
|
}
|
|
3506
3522
|
function setupGithubAction(projectRoot, packageManager, options) {
|
|
3507
|
-
const workflowDir =
|
|
3508
|
-
const workflowPath =
|
|
3509
|
-
if (
|
|
3510
|
-
const existing =
|
|
3523
|
+
const workflowDir = path18.join(projectRoot, ".github", "workflows");
|
|
3524
|
+
const workflowPath = path18.join(workflowDir, "viberails.yml");
|
|
3525
|
+
if (fs18.existsSync(workflowPath)) {
|
|
3526
|
+
const existing = fs18.readFileSync(workflowPath, "utf-8");
|
|
3511
3527
|
if (existing.includes("viberails")) return void 0;
|
|
3512
3528
|
}
|
|
3513
|
-
|
|
3529
|
+
fs18.mkdirSync(workflowDir, { recursive: true });
|
|
3514
3530
|
const pm = packageManager || "npm";
|
|
3515
3531
|
const installCmd = pm === "yarn" ? "yarn install --frozen-lockfile" : pm === "pnpm" ? "pnpm install --frozen-lockfile" : "npm ci";
|
|
3516
3532
|
const runPrefix = pm === "npm" ? "npx" : `${pm} exec`;
|
|
@@ -3564,74 +3580,74 @@ function setupGithubAction(projectRoot, packageManager, options) {
|
|
|
3564
3580
|
""
|
|
3565
3581
|
);
|
|
3566
3582
|
const content = lines.filter((l) => l !== void 0).join("\n");
|
|
3567
|
-
|
|
3583
|
+
fs18.writeFileSync(workflowPath, content);
|
|
3568
3584
|
return ".github/workflows/viberails.yml";
|
|
3569
3585
|
}
|
|
3570
3586
|
function writeHuskyPreCommit(huskyDir) {
|
|
3571
|
-
const hookPath =
|
|
3587
|
+
const hookPath = path18.join(huskyDir, "pre-commit");
|
|
3572
3588
|
const cmd = "if [ -x ./node_modules/.bin/viberails ]; then ./node_modules/.bin/viberails check --staged; else npx viberails check --staged; fi";
|
|
3573
|
-
if (
|
|
3574
|
-
const existing =
|
|
3589
|
+
if (fs18.existsSync(hookPath)) {
|
|
3590
|
+
const existing = fs18.readFileSync(hookPath, "utf-8");
|
|
3575
3591
|
if (!existing.includes("viberails")) {
|
|
3576
|
-
|
|
3592
|
+
fs18.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
3577
3593
|
${cmd}
|
|
3578
3594
|
`);
|
|
3579
3595
|
}
|
|
3580
3596
|
return;
|
|
3581
3597
|
}
|
|
3582
|
-
|
|
3598
|
+
fs18.writeFileSync(hookPath, `#!/bin/sh
|
|
3583
3599
|
${cmd}
|
|
3584
3600
|
`, { mode: 493 });
|
|
3585
3601
|
}
|
|
3586
3602
|
|
|
3587
3603
|
// src/commands/init-hooks-extra.ts
|
|
3588
|
-
var
|
|
3589
|
-
var
|
|
3590
|
-
var
|
|
3604
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
3605
|
+
var path19 = __toESM(require("path"), 1);
|
|
3606
|
+
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
3591
3607
|
var import_yaml2 = require("yaml");
|
|
3592
3608
|
function addPreCommitStep(projectRoot, name, command, marker, lefthookExtra) {
|
|
3593
|
-
const lefthookPath =
|
|
3594
|
-
if (
|
|
3595
|
-
const content =
|
|
3609
|
+
const lefthookPath = path19.join(projectRoot, "lefthook.yml");
|
|
3610
|
+
if (fs19.existsSync(lefthookPath)) {
|
|
3611
|
+
const content = fs19.readFileSync(lefthookPath, "utf-8");
|
|
3596
3612
|
if (content.includes(marker)) return void 0;
|
|
3597
3613
|
const doc = (0, import_yaml2.parse)(content) ?? {};
|
|
3598
3614
|
if (!doc["pre-commit"]) doc["pre-commit"] = { commands: {} };
|
|
3599
3615
|
if (!doc["pre-commit"].commands) doc["pre-commit"].commands = {};
|
|
3600
3616
|
doc["pre-commit"].commands[name] = { run: command, ...lefthookExtra };
|
|
3601
|
-
|
|
3617
|
+
fs19.writeFileSync(lefthookPath, (0, import_yaml2.stringify)(doc));
|
|
3602
3618
|
return "lefthook.yml";
|
|
3603
3619
|
}
|
|
3604
|
-
const huskyDir =
|
|
3605
|
-
if (
|
|
3606
|
-
const hookPath =
|
|
3607
|
-
if (
|
|
3608
|
-
const existing =
|
|
3620
|
+
const huskyDir = path19.join(projectRoot, ".husky");
|
|
3621
|
+
if (fs19.existsSync(huskyDir)) {
|
|
3622
|
+
const hookPath = path19.join(huskyDir, "pre-commit");
|
|
3623
|
+
if (fs19.existsSync(hookPath)) {
|
|
3624
|
+
const existing = fs19.readFileSync(hookPath, "utf-8");
|
|
3609
3625
|
if (existing.includes(marker)) return void 0;
|
|
3610
|
-
|
|
3626
|
+
fs19.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
3611
3627
|
${command}
|
|
3612
3628
|
`);
|
|
3613
3629
|
} else {
|
|
3614
|
-
|
|
3630
|
+
fs19.writeFileSync(hookPath, `#!/bin/sh
|
|
3615
3631
|
${command}
|
|
3616
3632
|
`, { mode: 493 });
|
|
3617
3633
|
}
|
|
3618
3634
|
return ".husky/pre-commit";
|
|
3619
3635
|
}
|
|
3620
|
-
const gitDir =
|
|
3621
|
-
if (
|
|
3622
|
-
const hooksDir =
|
|
3623
|
-
if (!
|
|
3624
|
-
const hookPath =
|
|
3625
|
-
if (
|
|
3626
|
-
const existing =
|
|
3636
|
+
const gitDir = path19.join(projectRoot, ".git");
|
|
3637
|
+
if (fs19.existsSync(gitDir)) {
|
|
3638
|
+
const hooksDir = path19.join(gitDir, "hooks");
|
|
3639
|
+
if (!fs19.existsSync(hooksDir)) fs19.mkdirSync(hooksDir, { recursive: true });
|
|
3640
|
+
const hookPath = path19.join(hooksDir, "pre-commit");
|
|
3641
|
+
if (fs19.existsSync(hookPath)) {
|
|
3642
|
+
const existing = fs19.readFileSync(hookPath, "utf-8");
|
|
3627
3643
|
if (existing.includes(marker)) return void 0;
|
|
3628
|
-
|
|
3644
|
+
fs19.writeFileSync(hookPath, `${existing.trimEnd()}
|
|
3629
3645
|
|
|
3630
3646
|
# ${name}
|
|
3631
3647
|
${command}
|
|
3632
3648
|
`);
|
|
3633
3649
|
} else {
|
|
3634
|
-
|
|
3650
|
+
fs19.writeFileSync(hookPath, `#!/bin/sh
|
|
3635
3651
|
# Generated by viberails
|
|
3636
3652
|
|
|
3637
3653
|
# ${name}
|
|
@@ -3647,17 +3663,17 @@ ${command}
|
|
|
3647
3663
|
function setupTypecheckHook(projectRoot, packageManager) {
|
|
3648
3664
|
const resolved = resolveTypecheckCommand(projectRoot, packageManager);
|
|
3649
3665
|
if (!resolved.command) {
|
|
3650
|
-
console.log(` ${
|
|
3666
|
+
console.log(` ${import_chalk11.default.yellow("!")} Skipped typecheck hook: ${resolved.reason}`);
|
|
3651
3667
|
return void 0;
|
|
3652
3668
|
}
|
|
3653
3669
|
const target = addPreCommitStep(projectRoot, "typecheck", resolved.command, "typecheck");
|
|
3654
3670
|
if (target) {
|
|
3655
|
-
console.log(` ${
|
|
3671
|
+
console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added typecheck (${resolved.label})`);
|
|
3656
3672
|
}
|
|
3657
3673
|
return target;
|
|
3658
3674
|
}
|
|
3659
3675
|
function setupLintHook(projectRoot, linter) {
|
|
3660
|
-
const isLefthook =
|
|
3676
|
+
const isLefthook = fs19.existsSync(path19.join(projectRoot, "lefthook.yml"));
|
|
3661
3677
|
const linterName = linter === "biome" ? "Biome" : "ESLint";
|
|
3662
3678
|
let command;
|
|
3663
3679
|
let lefthookExtra;
|
|
@@ -3673,7 +3689,7 @@ function setupLintHook(projectRoot, linter) {
|
|
|
3673
3689
|
}
|
|
3674
3690
|
const target = addPreCommitStep(projectRoot, "lint", command, linter, lefthookExtra);
|
|
3675
3691
|
if (target) {
|
|
3676
|
-
console.log(` ${
|
|
3692
|
+
console.log(` ${import_chalk11.default.green("\u2713")} ${target} \u2014 added ${linterName} lint check`);
|
|
3677
3693
|
}
|
|
3678
3694
|
return target;
|
|
3679
3695
|
}
|
|
@@ -3681,6 +3697,9 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3681
3697
|
const created = [];
|
|
3682
3698
|
if (integrations.preCommitHook) {
|
|
3683
3699
|
const t = setupPreCommitHook(projectRoot);
|
|
3700
|
+
if (t && opts.lefthookExpected && !t.includes("lefthook")) {
|
|
3701
|
+
console.log(` ${import_chalk11.default.yellow("!")} Lefthook install failed \u2014 fell back to ${t}`);
|
|
3702
|
+
}
|
|
3684
3703
|
created.push(t ? `${t} \u2014 added viberails pre-commit` : "pre-commit hook skipped");
|
|
3685
3704
|
}
|
|
3686
3705
|
if (integrations.typecheckHook) {
|
|
@@ -3710,12 +3729,12 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
|
|
|
3710
3729
|
}
|
|
3711
3730
|
|
|
3712
3731
|
// src/commands/init-non-interactive.ts
|
|
3713
|
-
var
|
|
3714
|
-
var
|
|
3715
|
-
var
|
|
3732
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
3733
|
+
var path20 = __toESM(require("path"), 1);
|
|
3734
|
+
var clack11 = __toESM(require("@clack/prompts"), 1);
|
|
3716
3735
|
var import_config8 = require("@viberails/config");
|
|
3717
3736
|
var import_scanner2 = require("@viberails/scanner");
|
|
3718
|
-
var
|
|
3737
|
+
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
3719
3738
|
|
|
3720
3739
|
// src/utils/filter-confidence.ts
|
|
3721
3740
|
function filterHighConfidence(conventions, meta) {
|
|
@@ -3736,7 +3755,7 @@ function getExemptedPackages(config) {
|
|
|
3736
3755
|
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3737
3756
|
}
|
|
3738
3757
|
async function initNonInteractive(projectRoot, configPath) {
|
|
3739
|
-
const s =
|
|
3758
|
+
const s = clack11.spinner();
|
|
3740
3759
|
s.start("Scanning project...");
|
|
3741
3760
|
const scanResult = await (0, import_scanner2.scan)(projectRoot);
|
|
3742
3761
|
const config = (0, import_config8.generateConfig)(scanResult);
|
|
@@ -3751,11 +3770,11 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3751
3770
|
const exempted = getExemptedPackages(config);
|
|
3752
3771
|
if (exempted.length > 0) {
|
|
3753
3772
|
console.log(
|
|
3754
|
-
` ${
|
|
3773
|
+
` ${import_chalk12.default.dim("Auto-exempted from coverage:")} ${exempted.join(", ")} ${import_chalk12.default.dim("(types-only)")}`
|
|
3755
3774
|
);
|
|
3756
3775
|
}
|
|
3757
3776
|
if (config.packages.length > 1) {
|
|
3758
|
-
const bs =
|
|
3777
|
+
const bs = clack11.spinner();
|
|
3759
3778
|
bs.start("Building import graph...");
|
|
3760
3779
|
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3761
3780
|
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
@@ -3771,7 +3790,7 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3771
3790
|
}
|
|
3772
3791
|
}
|
|
3773
3792
|
const compacted = (0, import_config8.compactConfig)(config);
|
|
3774
|
-
|
|
3793
|
+
fs20.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
3775
3794
|
`);
|
|
3776
3795
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
3777
3796
|
updateGitignore(projectRoot);
|
|
@@ -3788,14 +3807,14 @@ async function initNonInteractive(projectRoot, configPath) {
|
|
|
3788
3807
|
const hookManager = detectHookManager(projectRoot);
|
|
3789
3808
|
const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
|
|
3790
3809
|
const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
|
|
3791
|
-
const ok =
|
|
3810
|
+
const ok = import_chalk12.default.green("\u2713");
|
|
3792
3811
|
const created = [
|
|
3793
|
-
`${ok} ${
|
|
3812
|
+
`${ok} ${path20.basename(configPath)}`,
|
|
3794
3813
|
`${ok} .viberails/context.md`,
|
|
3795
3814
|
`${ok} .viberails/scan-result.json`,
|
|
3796
3815
|
`${ok} .claude/settings.json \u2014 added viberails hook`,
|
|
3797
3816
|
`${ok} CLAUDE.md \u2014 added @.viberails/context.md reference`,
|
|
3798
|
-
preCommitTarget ? `${ok} ${preCommitTarget}` : `${
|
|
3817
|
+
preCommitTarget ? `${ok} ${preCommitTarget}` : `${import_chalk12.default.yellow("!")} pre-commit hook skipped (install lefthook or husky)`,
|
|
3799
3818
|
actionTarget ? `${ok} ${actionTarget} \u2014 blocks PRs on violations` : ""
|
|
3800
3819
|
].filter(Boolean);
|
|
3801
3820
|
if (hasHookManager && isTypeScript) setupTypecheckHook(projectRoot, rootPkgPm);
|
|
@@ -3807,9 +3826,6 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
|
|
|
3807
3826
|
|
|
3808
3827
|
// src/commands/init.ts
|
|
3809
3828
|
var CONFIG_FILE5 = "viberails.config.json";
|
|
3810
|
-
function getExemptedPackages2(config) {
|
|
3811
|
-
return config.packages.filter((pkg) => pkg.rules?.testCoverage === 0 && pkg.path !== ".").map((pkg) => pkg.path);
|
|
3812
|
-
}
|
|
3813
3829
|
async function initCommand(options, cwd) {
|
|
3814
3830
|
const projectRoot = findProjectRoot(cwd ?? process.cwd());
|
|
3815
3831
|
if (!projectRoot) {
|
|
@@ -3817,14 +3833,14 @@ async function initCommand(options, cwd) {
|
|
|
3817
3833
|
"No package.json found. Make sure you are inside a JS/TS project, then run:\n npx viberails"
|
|
3818
3834
|
);
|
|
3819
3835
|
}
|
|
3820
|
-
const configPath =
|
|
3821
|
-
if (
|
|
3836
|
+
const configPath = path21.join(projectRoot, CONFIG_FILE5);
|
|
3837
|
+
if (fs21.existsSync(configPath) && !options.force) {
|
|
3822
3838
|
if (!options.yes) {
|
|
3823
3839
|
return initInteractive(projectRoot, configPath, options);
|
|
3824
3840
|
}
|
|
3825
3841
|
console.log(
|
|
3826
|
-
`${
|
|
3827
|
-
Run ${
|
|
3842
|
+
`${import_chalk13.default.yellow("!")} viberails is already initialized.
|
|
3843
|
+
Run ${import_chalk13.default.cyan("viberails")} to review or edit the existing setup, ${import_chalk13.default.cyan("viberails sync")} to update generated files, or ${import_chalk13.default.cyan("viberails init --force")} to replace it.`
|
|
3828
3844
|
);
|
|
3829
3845
|
return;
|
|
3830
3846
|
}
|
|
@@ -3832,12 +3848,11 @@ async function initCommand(options, cwd) {
|
|
|
3832
3848
|
await initInteractive(projectRoot, configPath, options);
|
|
3833
3849
|
}
|
|
3834
3850
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
const action = await promptExistingConfigAction(path20.basename(configPath));
|
|
3851
|
+
clack12.intro("viberails");
|
|
3852
|
+
if (fs21.existsSync(configPath) && !options.force) {
|
|
3853
|
+
const action = await promptExistingConfigAction(path21.basename(configPath));
|
|
3839
3854
|
if (action === "cancel") {
|
|
3840
|
-
|
|
3855
|
+
clack12.outro("Aborted. No files were written.");
|
|
3841
3856
|
return;
|
|
3842
3857
|
}
|
|
3843
3858
|
if (action === "edit") {
|
|
@@ -3846,143 +3861,93 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3846
3861
|
}
|
|
3847
3862
|
options.force = true;
|
|
3848
3863
|
}
|
|
3849
|
-
if (
|
|
3864
|
+
if (fs21.existsSync(configPath) && options.force) {
|
|
3850
3865
|
const replace = await confirmDangerous(
|
|
3851
|
-
`${
|
|
3866
|
+
`${path21.basename(configPath)} already exists and will be replaced. Continue?`
|
|
3852
3867
|
);
|
|
3853
3868
|
if (!replace) {
|
|
3854
|
-
|
|
3869
|
+
clack12.outro("Aborted. No files were written.");
|
|
3855
3870
|
return;
|
|
3856
3871
|
}
|
|
3857
3872
|
}
|
|
3858
|
-
const s =
|
|
3873
|
+
const s = clack12.spinner();
|
|
3859
3874
|
s.start("Scanning project...");
|
|
3860
3875
|
const scanResult = await (0, import_scanner3.scan)(projectRoot);
|
|
3861
3876
|
const config = (0, import_config9.generateConfig)(scanResult);
|
|
3862
3877
|
s.stop("Scan complete");
|
|
3863
3878
|
if (scanResult.statistics.totalFiles === 0) {
|
|
3864
|
-
|
|
3879
|
+
clack12.log.warn(
|
|
3865
3880
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3866
3881
|
);
|
|
3867
3882
|
}
|
|
3868
|
-
const
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
const nextDecision = await promptInitDecision();
|
|
3873
|
-
if (nextDecision === "review") {
|
|
3874
|
-
clack11.note(formatScanResultsText(scanResult), "Detected details");
|
|
3875
|
-
continue;
|
|
3876
|
-
}
|
|
3877
|
-
decision = nextDecision;
|
|
3878
|
-
break;
|
|
3879
|
-
}
|
|
3880
|
-
if (decision === "customize") {
|
|
3881
|
-
const { resolveNamingDefault: resolveNamingDefault2 } = await Promise.resolve().then(() => (init_prompt_naming_default(), prompt_naming_default_exports));
|
|
3882
|
-
await resolveNamingDefault2(config, scanResult);
|
|
3883
|
-
const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
3884
|
-
const overrides = await promptRuleMenu({
|
|
3885
|
-
maxFileLines: config.rules.maxFileLines,
|
|
3886
|
-
maxTestFileLines: config.rules.maxTestFileLines,
|
|
3887
|
-
testCoverage: config.rules.testCoverage,
|
|
3888
|
-
enforceMissingTests: config.rules.enforceMissingTests,
|
|
3889
|
-
enforceNaming: config.rules.enforceNaming,
|
|
3890
|
-
fileNamingValue: rootPkg.conventions?.fileNaming,
|
|
3891
|
-
componentNaming: rootPkg.conventions?.componentNaming,
|
|
3892
|
-
hookNaming: rootPkg.conventions?.hookNaming,
|
|
3893
|
-
importAlias: rootPkg.conventions?.importAlias,
|
|
3894
|
-
coverageSummaryPath: "coverage/coverage-summary.json",
|
|
3895
|
-
coverageCommand: config.defaults?.coverage?.command,
|
|
3896
|
-
packageOverrides: config.packages
|
|
3897
|
-
});
|
|
3898
|
-
applyRuleOverrides(config, overrides);
|
|
3899
|
-
}
|
|
3900
|
-
if (config.packages.length > 1) {
|
|
3901
|
-
clack11.note(
|
|
3902
|
-
"Optional for monorepos. viberails can infer package boundaries\nfrom imports that already work today, so you start with rules\nthat match the current codebase.",
|
|
3903
|
-
"Boundaries"
|
|
3883
|
+
const hasTestRunner = !!scanResult.stack.testRunner;
|
|
3884
|
+
if (!hasTestRunner) {
|
|
3885
|
+
clack12.log.info(
|
|
3886
|
+
"No test runner detected. Coverage checks are inactive until a test runner is installed.\nInstall a test runner (e.g. vitest) and re-run viberails init."
|
|
3904
3887
|
);
|
|
3905
|
-
const shouldInfer = await confirm3("Infer boundary rules from current import patterns?");
|
|
3906
|
-
if (shouldInfer) {
|
|
3907
|
-
const bs = clack11.spinner();
|
|
3908
|
-
bs.start("Building import graph...");
|
|
3909
|
-
const { buildImportGraph, inferBoundaries } = await import("@viberails/graph");
|
|
3910
|
-
const packages = resolveWorkspacePackages(projectRoot, config.packages);
|
|
3911
|
-
const graph = await buildImportGraph(projectRoot, { packages, ignore: config.ignore });
|
|
3912
|
-
const inferred = inferBoundaries(graph);
|
|
3913
|
-
const denyCount = Object.values(inferred.deny).reduce((sum, arr) => sum + arr.length, 0);
|
|
3914
|
-
if (denyCount > 0) {
|
|
3915
|
-
config.boundaries = inferred;
|
|
3916
|
-
config.rules.enforceBoundaries = true;
|
|
3917
|
-
const pkgCount = Object.keys(inferred.deny).length;
|
|
3918
|
-
bs.stop(`Inferred ${denyCount} boundary rules across ${pkgCount} packages`);
|
|
3919
|
-
} else {
|
|
3920
|
-
bs.stop("No boundary rules inferred");
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
3888
|
}
|
|
3924
3889
|
const hookManager = detectHookManager(projectRoot);
|
|
3925
3890
|
const coveragePrereqs = checkCoveragePrereqs(projectRoot, scanResult);
|
|
3926
|
-
const hasMissingPrereqs = coveragePrereqs.some((p) => !p.installed) || !hookManager;
|
|
3927
|
-
if (hasMissingPrereqs) {
|
|
3928
|
-
clack11.log.info("Some dependencies are needed for full functionality.");
|
|
3929
|
-
}
|
|
3930
|
-
const prereqResult = await promptMissingPrereqs(projectRoot, coveragePrereqs);
|
|
3931
|
-
if (prereqResult.disableCoverage) {
|
|
3932
|
-
config.rules.testCoverage = 0;
|
|
3933
|
-
}
|
|
3934
3891
|
const rootPkgStack = (config.packages.find((p) => p.path === ".") ?? config.packages[0])?.stack;
|
|
3935
|
-
const
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3892
|
+
const state = await promptMainMenu(config, scanResult, {
|
|
3893
|
+
hasTestRunner,
|
|
3894
|
+
hookManager,
|
|
3895
|
+
coveragePrereqs,
|
|
3896
|
+
projectRoot,
|
|
3897
|
+
tools: {
|
|
3898
|
+
isTypeScript: rootPkgStack?.language?.split("@")[0] === "typescript",
|
|
3899
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3900
|
+
packageManager: rootPkgStack?.packageManager?.split("@")[0],
|
|
3901
|
+
isWorkspace: config.packages.length > 1
|
|
3902
|
+
}
|
|
3944
3903
|
});
|
|
3945
3904
|
const shouldWrite = await confirm3("Apply this setup?");
|
|
3946
3905
|
if (!shouldWrite) {
|
|
3947
|
-
|
|
3906
|
+
clack12.outro("Aborted. No files were written.");
|
|
3948
3907
|
return;
|
|
3949
3908
|
}
|
|
3950
|
-
|
|
3909
|
+
if (state.deferredInstalls.length > 0) {
|
|
3910
|
+
await executeDeferredInstalls(projectRoot, state.deferredInstalls);
|
|
3911
|
+
}
|
|
3912
|
+
const ws = clack12.spinner();
|
|
3951
3913
|
ws.start("Writing configuration...");
|
|
3952
3914
|
const compacted = (0, import_config9.compactConfig)(config);
|
|
3953
|
-
|
|
3915
|
+
fs21.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
3954
3916
|
`);
|
|
3955
3917
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
3956
3918
|
updateGitignore(projectRoot);
|
|
3957
3919
|
ws.stop("Configuration written");
|
|
3958
|
-
const ok =
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3920
|
+
const ok = import_chalk13.default.green("\u2713");
|
|
3921
|
+
clack12.log.step(`${ok} ${path21.basename(configPath)}`);
|
|
3922
|
+
clack12.log.step(`${ok} .viberails/context.md`);
|
|
3923
|
+
clack12.log.step(`${ok} .viberails/scan-result.json`);
|
|
3924
|
+
if (state.visited.integrations && state.integrations) {
|
|
3925
|
+
const lefthookExpected = state.deferredInstalls.some((d) => d.command.includes("lefthook"));
|
|
3926
|
+
setupSelectedIntegrations(projectRoot, state.integrations, {
|
|
3927
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3928
|
+
packageManager: rootPkgStack?.packageManager?.split("@")[0],
|
|
3929
|
+
lefthookExpected
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
clack12.outro(
|
|
3967
3933
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
3968
|
-
${
|
|
3934
|
+
${import_chalk13.default.dim("Tip: use")} ${import_chalk13.default.cyan("viberails check --enforce")} ${import_chalk13.default.dim("in CI to block PRs on violations.")}`
|
|
3969
3935
|
);
|
|
3970
3936
|
}
|
|
3971
3937
|
|
|
3972
3938
|
// src/commands/sync.ts
|
|
3973
|
-
var
|
|
3974
|
-
var
|
|
3975
|
-
var
|
|
3939
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
3940
|
+
var path22 = __toESM(require("path"), 1);
|
|
3941
|
+
var clack13 = __toESM(require("@clack/prompts"), 1);
|
|
3976
3942
|
var import_config11 = require("@viberails/config");
|
|
3977
3943
|
var import_scanner4 = require("@viberails/scanner");
|
|
3978
|
-
var
|
|
3979
|
-
init_prompt();
|
|
3944
|
+
var import_chalk14 = __toESM(require("chalk"), 1);
|
|
3980
3945
|
var CONFIG_FILE6 = "viberails.config.json";
|
|
3981
3946
|
var SCAN_RESULT_FILE2 = ".viberails/scan-result.json";
|
|
3982
3947
|
function loadPreviousStats(projectRoot) {
|
|
3983
|
-
const scanResultPath =
|
|
3948
|
+
const scanResultPath = path22.join(projectRoot, SCAN_RESULT_FILE2);
|
|
3984
3949
|
try {
|
|
3985
|
-
const raw =
|
|
3950
|
+
const raw = fs22.readFileSync(scanResultPath, "utf-8");
|
|
3986
3951
|
const parsed = JSON.parse(raw);
|
|
3987
3952
|
if (parsed?.statistics?.totalFiles !== void 0) {
|
|
3988
3953
|
return parsed.statistics;
|
|
@@ -3999,17 +3964,17 @@ async function syncCommand(options, cwd) {
|
|
|
3999
3964
|
"No package.json found in this directory or any parent.\n\nMake sure you are inside a JavaScript or TypeScript project, then run:\n npx viberails"
|
|
4000
3965
|
);
|
|
4001
3966
|
}
|
|
4002
|
-
const configPath =
|
|
3967
|
+
const configPath = path22.join(projectRoot, CONFIG_FILE6);
|
|
4003
3968
|
const existing = await (0, import_config11.loadConfig)(configPath);
|
|
4004
3969
|
const previousStats = loadPreviousStats(projectRoot);
|
|
4005
|
-
const s =
|
|
3970
|
+
const s = clack13.spinner();
|
|
4006
3971
|
s.start("Scanning project...");
|
|
4007
3972
|
const scanResult = await (0, import_scanner4.scan)(projectRoot);
|
|
4008
3973
|
s.stop("Scan complete");
|
|
4009
3974
|
const merged = (0, import_config11.mergeConfig)(existing, scanResult);
|
|
4010
3975
|
const compacted = (0, import_config11.compactConfig)(merged);
|
|
4011
3976
|
const compactedJson = JSON.stringify(compacted, null, 2);
|
|
4012
|
-
const rawDisk =
|
|
3977
|
+
const rawDisk = fs22.readFileSync(configPath, "utf-8").trim();
|
|
4013
3978
|
const diskWithoutSync = rawDisk.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
|
|
4014
3979
|
const mergedWithoutSync = compactedJson.replace(/"lastSync":\s*"[^"]*"/, '"lastSync": ""');
|
|
4015
3980
|
const configChanged = diskWithoutSync !== mergedWithoutSync;
|
|
@@ -4017,19 +3982,19 @@ async function syncCommand(options, cwd) {
|
|
|
4017
3982
|
const statsDelta = previousStats ? formatStatsDelta(previousStats, scanResult.statistics) : void 0;
|
|
4018
3983
|
if (changes.length > 0 || statsDelta) {
|
|
4019
3984
|
console.log(`
|
|
4020
|
-
${
|
|
3985
|
+
${import_chalk14.default.bold("Changes:")}`);
|
|
4021
3986
|
for (const change of changes) {
|
|
4022
|
-
const icon = change.type === "removed" ?
|
|
3987
|
+
const icon = change.type === "removed" ? import_chalk14.default.red("-") : import_chalk14.default.green("+");
|
|
4023
3988
|
console.log(` ${icon} ${change.description}`);
|
|
4024
3989
|
}
|
|
4025
3990
|
if (statsDelta) {
|
|
4026
|
-
console.log(` ${
|
|
3991
|
+
console.log(` ${import_chalk14.default.dim(statsDelta)}`);
|
|
4027
3992
|
}
|
|
4028
3993
|
}
|
|
4029
3994
|
if (options?.interactive) {
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
const decision = await
|
|
3995
|
+
clack13.intro("viberails sync (interactive)");
|
|
3996
|
+
clack13.note(formatRulesText(merged).join("\n"), "Rules after sync");
|
|
3997
|
+
const decision = await clack13.select({
|
|
4033
3998
|
message: "How would you like to proceed?",
|
|
4034
3999
|
options: [
|
|
4035
4000
|
{ value: "accept", label: "Accept changes" },
|
|
@@ -4039,7 +4004,7 @@ ${import_chalk15.default.bold("Changes:")}`);
|
|
|
4039
4004
|
});
|
|
4040
4005
|
assertNotCancelled(decision);
|
|
4041
4006
|
if (decision === "cancel") {
|
|
4042
|
-
|
|
4007
|
+
clack13.outro("Sync cancelled. No files were written.");
|
|
4043
4008
|
return;
|
|
4044
4009
|
}
|
|
4045
4010
|
if (decision === "customize") {
|
|
@@ -4060,30 +4025,30 @@ ${import_chalk15.default.bold("Changes:")}`);
|
|
|
4060
4025
|
});
|
|
4061
4026
|
applyRuleOverrides(merged, overrides);
|
|
4062
4027
|
const recompacted = (0, import_config11.compactConfig)(merged);
|
|
4063
|
-
|
|
4028
|
+
fs22.writeFileSync(configPath, `${JSON.stringify(recompacted, null, 2)}
|
|
4064
4029
|
`);
|
|
4065
4030
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4066
|
-
|
|
4067
|
-
|
|
4031
|
+
clack13.log.success("Updated config with your customizations.");
|
|
4032
|
+
clack13.outro("Done! Run viberails check to verify.");
|
|
4068
4033
|
return;
|
|
4069
4034
|
}
|
|
4070
4035
|
}
|
|
4071
|
-
|
|
4036
|
+
fs22.writeFileSync(configPath, `${compactedJson}
|
|
4072
4037
|
`);
|
|
4073
4038
|
writeGeneratedFiles(projectRoot, merged, scanResult);
|
|
4074
4039
|
console.log(`
|
|
4075
|
-
${
|
|
4040
|
+
${import_chalk14.default.bold("Synced:")}`);
|
|
4076
4041
|
if (configChanged) {
|
|
4077
|
-
console.log(` ${
|
|
4042
|
+
console.log(` ${import_chalk14.default.yellow("!")} ${CONFIG_FILE6} \u2014 updated (review changes)`);
|
|
4078
4043
|
} else {
|
|
4079
|
-
console.log(` ${
|
|
4044
|
+
console.log(` ${import_chalk14.default.green("\u2713")} ${CONFIG_FILE6} \u2014 unchanged`);
|
|
4080
4045
|
}
|
|
4081
|
-
console.log(` ${
|
|
4082
|
-
console.log(` ${
|
|
4046
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .viberails/context.md \u2014 regenerated`);
|
|
4047
|
+
console.log(` ${import_chalk14.default.green("\u2713")} .viberails/scan-result.json \u2014 updated`);
|
|
4083
4048
|
}
|
|
4084
4049
|
|
|
4085
4050
|
// src/index.ts
|
|
4086
|
-
var VERSION = "0.6.
|
|
4051
|
+
var VERSION = "0.6.6";
|
|
4087
4052
|
var program = new import_commander.Command();
|
|
4088
4053
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4089
4054
|
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) => {
|
|
@@ -4091,7 +4056,7 @@ program.command("init", { isDefault: true }).description("Scan your project and
|
|
|
4091
4056
|
await initCommand(options);
|
|
4092
4057
|
} catch (err) {
|
|
4093
4058
|
const message = err instanceof Error ? err.message : String(err);
|
|
4094
|
-
console.error(`${
|
|
4059
|
+
console.error(`${import_chalk15.default.red("Error:")} ${message}`);
|
|
4095
4060
|
process.exit(1);
|
|
4096
4061
|
}
|
|
4097
4062
|
});
|
|
@@ -4100,7 +4065,7 @@ program.command("sync").description("Re-scan and update generated files").option
|
|
|
4100
4065
|
await syncCommand(options);
|
|
4101
4066
|
} catch (err) {
|
|
4102
4067
|
const message = err instanceof Error ? err.message : String(err);
|
|
4103
|
-
console.error(`${
|
|
4068
|
+
console.error(`${import_chalk15.default.red("Error:")} ${message}`);
|
|
4104
4069
|
process.exit(1);
|
|
4105
4070
|
}
|
|
4106
4071
|
});
|
|
@@ -4109,7 +4074,7 @@ program.command("config").description("Interactively edit existing config rules"
|
|
|
4109
4074
|
await configCommand(options);
|
|
4110
4075
|
} catch (err) {
|
|
4111
4076
|
const message = err instanceof Error ? err.message : String(err);
|
|
4112
|
-
console.error(`${
|
|
4077
|
+
console.error(`${import_chalk15.default.red("Error:")} ${message}`);
|
|
4113
4078
|
process.exit(1);
|
|
4114
4079
|
}
|
|
4115
4080
|
});
|
|
@@ -4130,7 +4095,7 @@ program.command("check").description("Check files against enforced rules").optio
|
|
|
4130
4095
|
process.exit(exitCode);
|
|
4131
4096
|
} catch (err) {
|
|
4132
4097
|
const message = err instanceof Error ? err.message : String(err);
|
|
4133
|
-
console.error(`${
|
|
4098
|
+
console.error(`${import_chalk15.default.red("Error:")} ${message}`);
|
|
4134
4099
|
process.exit(1);
|
|
4135
4100
|
}
|
|
4136
4101
|
}
|
|
@@ -4141,7 +4106,7 @@ program.command("fix").description("Auto-fix file naming violations and generate
|
|
|
4141
4106
|
process.exit(exitCode);
|
|
4142
4107
|
} catch (err) {
|
|
4143
4108
|
const message = err instanceof Error ? err.message : String(err);
|
|
4144
|
-
console.error(`${
|
|
4109
|
+
console.error(`${import_chalk15.default.red("Error:")} ${message}`);
|
|
4145
4110
|
process.exit(1);
|
|
4146
4111
|
}
|
|
4147
4112
|
});
|
|
@@ -4150,7 +4115,7 @@ program.command("boundaries").description("Display, infer, or inspect import bou
|
|
|
4150
4115
|
await boundariesCommand(options);
|
|
4151
4116
|
} catch (err) {
|
|
4152
4117
|
const message = err instanceof Error ? err.message : String(err);
|
|
4153
|
-
console.error(`${
|
|
4118
|
+
console.error(`${import_chalk15.default.red("Error:")} ${message}`);
|
|
4154
4119
|
process.exit(1);
|
|
4155
4120
|
}
|
|
4156
4121
|
});
|