viberails 0.6.2 → 0.6.4
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 +215 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +212 -52
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -31,11 +31,34 @@ function findProjectRoot(startDir) {
|
|
|
31
31
|
import * as clack5 from "@clack/prompts";
|
|
32
32
|
|
|
33
33
|
// src/utils/prompt-integrations.ts
|
|
34
|
-
import { spawnSync } from "child_process";
|
|
35
34
|
import * as clack from "@clack/prompts";
|
|
35
|
+
|
|
36
|
+
// src/utils/spawn-async.ts
|
|
37
|
+
import { spawn } from "child_process";
|
|
38
|
+
function spawnAsync(command, cwd) {
|
|
39
|
+
return new Promise((resolve4) => {
|
|
40
|
+
const child = spawn(command, { cwd, shell: true, stdio: "pipe" });
|
|
41
|
+
let stdout = "";
|
|
42
|
+
let stderr = "";
|
|
43
|
+
child.stdout.on("data", (d) => {
|
|
44
|
+
stdout += d.toString();
|
|
45
|
+
});
|
|
46
|
+
child.stderr.on("data", (d) => {
|
|
47
|
+
stderr += d.toString();
|
|
48
|
+
});
|
|
49
|
+
child.on("close", (status) => {
|
|
50
|
+
resolve4({ status, stdout, stderr });
|
|
51
|
+
});
|
|
52
|
+
child.on("error", () => {
|
|
53
|
+
resolve4({ status: 1, stdout, stderr });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/utils/prompt-integrations.ts
|
|
36
59
|
async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace) {
|
|
37
60
|
const choice = await clack.select({
|
|
38
|
-
message: "No git hook manager detected. Install Lefthook
|
|
61
|
+
message: "No shared git hook manager detected. Install Lefthook?",
|
|
39
62
|
options: [
|
|
40
63
|
{
|
|
41
64
|
value: "install",
|
|
@@ -55,12 +78,7 @@ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace
|
|
|
55
78
|
const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
|
|
56
79
|
const s = clack.spinner();
|
|
57
80
|
s.start("Installing Lefthook...");
|
|
58
|
-
const result =
|
|
59
|
-
cwd: projectRoot,
|
|
60
|
-
shell: true,
|
|
61
|
-
encoding: "utf-8",
|
|
62
|
-
stdio: "pipe"
|
|
63
|
-
});
|
|
81
|
+
const result = await spawnAsync(installCmd, projectRoot);
|
|
64
82
|
if (result.status === 0) {
|
|
65
83
|
const fs21 = await import("fs");
|
|
66
84
|
const path21 = await import("path");
|
|
@@ -128,7 +146,7 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
|
|
|
128
146
|
);
|
|
129
147
|
const initialValues = isBareHook ? options.filter((o) => o.value !== "preCommit").map((o) => o.value) : options.map((o) => o.value);
|
|
130
148
|
const result = await clack.multiselect({
|
|
131
|
-
message: "
|
|
149
|
+
message: "Optional integrations",
|
|
132
150
|
options,
|
|
133
151
|
initialValues,
|
|
134
152
|
required: false
|
|
@@ -535,16 +553,49 @@ async function confirmDangerous(message) {
|
|
|
535
553
|
assertNotCancelled(result);
|
|
536
554
|
return result;
|
|
537
555
|
}
|
|
556
|
+
async function promptExistingConfigAction(configFile) {
|
|
557
|
+
const result = await clack5.select({
|
|
558
|
+
message: `${configFile} already exists. What do you want to do?`,
|
|
559
|
+
options: [
|
|
560
|
+
{
|
|
561
|
+
value: "edit",
|
|
562
|
+
label: "Edit existing config",
|
|
563
|
+
hint: "open the current rules and save updates in place"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
value: "replace",
|
|
567
|
+
label: "Replace with a fresh scan",
|
|
568
|
+
hint: "re-scan the project and overwrite the current config"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
value: "cancel",
|
|
572
|
+
label: "Cancel",
|
|
573
|
+
hint: "leave the current setup unchanged"
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
});
|
|
577
|
+
assertNotCancelled(result);
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
538
580
|
async function promptInitDecision() {
|
|
539
581
|
const result = await clack5.select({
|
|
540
|
-
message: "
|
|
582
|
+
message: "How do you want to proceed?",
|
|
541
583
|
options: [
|
|
542
584
|
{
|
|
543
585
|
value: "accept",
|
|
544
|
-
label: "
|
|
545
|
-
hint: "
|
|
586
|
+
label: "Accept defaults",
|
|
587
|
+
hint: "writes the config with these defaults; use --enforce in CI to block"
|
|
546
588
|
},
|
|
547
|
-
{
|
|
589
|
+
{
|
|
590
|
+
value: "customize",
|
|
591
|
+
label: "Customize rules",
|
|
592
|
+
hint: "edit limits, naming, test coverage, and package overrides"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
value: "review",
|
|
596
|
+
label: "Review detected details",
|
|
597
|
+
hint: "show the full scan report with package and structure details"
|
|
598
|
+
}
|
|
548
599
|
]
|
|
549
600
|
});
|
|
550
601
|
assertNotCancelled(result);
|
|
@@ -742,7 +793,7 @@ function resolveIgnoreForFile(relPath, config) {
|
|
|
742
793
|
}
|
|
743
794
|
|
|
744
795
|
// src/commands/check-coverage.ts
|
|
745
|
-
import { spawnSync
|
|
796
|
+
import { spawnSync } from "child_process";
|
|
746
797
|
import * as fs4 from "fs";
|
|
747
798
|
import * as path4 from "path";
|
|
748
799
|
import { inferCoverageCommand } from "@viberails/config";
|
|
@@ -785,7 +836,7 @@ function readCoveragePercentage(summaryPath) {
|
|
|
785
836
|
}
|
|
786
837
|
}
|
|
787
838
|
function runCoverageCommand(pkgRoot, command) {
|
|
788
|
-
const result =
|
|
839
|
+
const result = spawnSync(command, {
|
|
789
840
|
cwd: pkgRoot,
|
|
790
841
|
shell: true,
|
|
791
842
|
encoding: "utf-8",
|
|
@@ -1603,6 +1654,15 @@ function formatMonorepoResultsText(scanResult) {
|
|
|
1603
1654
|
}
|
|
1604
1655
|
|
|
1605
1656
|
// src/display.ts
|
|
1657
|
+
var INIT_OVERVIEW_NAMES = {
|
|
1658
|
+
typescript: "TypeScript",
|
|
1659
|
+
javascript: "JavaScript",
|
|
1660
|
+
eslint: "ESLint",
|
|
1661
|
+
prettier: "Prettier",
|
|
1662
|
+
jest: "Jest",
|
|
1663
|
+
vitest: "Vitest",
|
|
1664
|
+
biome: "Biome"
|
|
1665
|
+
};
|
|
1606
1666
|
function formatItem(item, nameMap) {
|
|
1607
1667
|
const name = nameMap?.[item.name] ?? item.name;
|
|
1608
1668
|
return item.version ? `${name} ${item.version}` : name;
|
|
@@ -1732,11 +1792,40 @@ function displayRulesPreview(config) {
|
|
|
1732
1792
|
);
|
|
1733
1793
|
console.log("");
|
|
1734
1794
|
}
|
|
1735
|
-
function
|
|
1795
|
+
function formatDetectedOverview(scanResult) {
|
|
1796
|
+
const { stack } = scanResult;
|
|
1797
|
+
const primaryParts = [];
|
|
1798
|
+
const secondaryParts = [];
|
|
1799
|
+
const formatOverviewItem = (item, nameMap) => formatItem(item, { ...INIT_OVERVIEW_NAMES, ...nameMap });
|
|
1800
|
+
if (scanResult.packages.length > 1) {
|
|
1801
|
+
primaryParts.push("monorepo");
|
|
1802
|
+
primaryParts.push(`${scanResult.packages.length} packages`);
|
|
1803
|
+
} else if (stack.framework) {
|
|
1804
|
+
primaryParts.push(formatItem(stack.framework, FRAMEWORK_NAMES2));
|
|
1805
|
+
} else {
|
|
1806
|
+
primaryParts.push("single package");
|
|
1807
|
+
}
|
|
1808
|
+
primaryParts.push(formatOverviewItem(stack.language));
|
|
1809
|
+
if (stack.styling) {
|
|
1810
|
+
primaryParts.push(formatOverviewItem(stack.styling, STYLING_NAMES2));
|
|
1811
|
+
}
|
|
1812
|
+
if (stack.packageManager) secondaryParts.push(formatOverviewItem(stack.packageManager));
|
|
1813
|
+
if (stack.linter) secondaryParts.push(formatOverviewItem(stack.linter));
|
|
1814
|
+
if (stack.formatter) secondaryParts.push(formatOverviewItem(stack.formatter));
|
|
1815
|
+
if (stack.testRunner) secondaryParts.push(formatOverviewItem(stack.testRunner));
|
|
1816
|
+
const primary = primaryParts.map((part) => chalk5.cyan(part)).join(chalk5.dim(" \xB7 "));
|
|
1817
|
+
const secondary = secondaryParts.join(chalk5.dim(" \xB7 "));
|
|
1818
|
+
return secondary ? `${primary}
|
|
1819
|
+
${chalk5.dim(secondary)}` : primary;
|
|
1820
|
+
}
|
|
1821
|
+
function displayInitOverview(scanResult, config, exemptedPackages) {
|
|
1736
1822
|
const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
1737
1823
|
const isMonorepo = config.packages.length > 1;
|
|
1738
1824
|
const ok = chalk5.green("\u2713");
|
|
1739
|
-
const
|
|
1825
|
+
const info = chalk5.yellow("~");
|
|
1826
|
+
console.log("");
|
|
1827
|
+
console.log(` ${chalk5.bold("Ready to initialize:")}`);
|
|
1828
|
+
console.log(` ${formatDetectedOverview(scanResult)}`);
|
|
1740
1829
|
console.log("");
|
|
1741
1830
|
console.log(` ${chalk5.bold("Rules to apply:")}`);
|
|
1742
1831
|
console.log(` ${ok} Max file size: ${chalk5.cyan(`${config.rules.maxFileLines} lines`)}`);
|
|
@@ -1744,7 +1833,7 @@ function displayInitSummary(config, exemptedPackages) {
|
|
|
1744
1833
|
if (config.rules.enforceNaming && fileNaming) {
|
|
1745
1834
|
console.log(` ${ok} File naming: ${chalk5.cyan(fileNaming)}`);
|
|
1746
1835
|
} else {
|
|
1747
|
-
console.log(` ${
|
|
1836
|
+
console.log(` ${info} File naming: ${chalk5.dim("not enforced")}`);
|
|
1748
1837
|
}
|
|
1749
1838
|
const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
|
|
1750
1839
|
if (config.rules.enforceMissingTests && testPattern) {
|
|
@@ -1752,7 +1841,7 @@ function displayInitSummary(config, exemptedPackages) {
|
|
|
1752
1841
|
} else if (config.rules.enforceMissingTests) {
|
|
1753
1842
|
console.log(` ${ok} Missing tests: ${chalk5.cyan("enforced")}`);
|
|
1754
1843
|
} else {
|
|
1755
|
-
console.log(` ${
|
|
1844
|
+
console.log(` ${info} Missing tests: ${chalk5.dim("not enforced")}`);
|
|
1756
1845
|
}
|
|
1757
1846
|
if (config.rules.testCoverage > 0) {
|
|
1758
1847
|
if (isMonorepo) {
|
|
@@ -1766,21 +1855,68 @@ function displayInitSummary(config, exemptedPackages) {
|
|
|
1766
1855
|
console.log(` ${ok} Coverage: ${chalk5.cyan(`${config.rules.testCoverage}%`)}`);
|
|
1767
1856
|
}
|
|
1768
1857
|
} else {
|
|
1769
|
-
console.log(` ${
|
|
1858
|
+
console.log(` ${info} Coverage: ${chalk5.dim("disabled")}`);
|
|
1770
1859
|
}
|
|
1771
1860
|
if (exemptedPackages.length > 0) {
|
|
1772
1861
|
console.log(
|
|
1773
1862
|
` ${chalk5.dim(" exempted:")} ${chalk5.dim(exemptedPackages.join(", "))} ${chalk5.dim("(types-only)")}`
|
|
1774
1863
|
);
|
|
1775
1864
|
}
|
|
1865
|
+
console.log("");
|
|
1866
|
+
console.log(` ${chalk5.bold("Also available:")}`);
|
|
1776
1867
|
if (isMonorepo) {
|
|
1777
|
-
console.log(
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1868
|
+
console.log(` ${info} Infer boundaries from current imports`);
|
|
1869
|
+
}
|
|
1870
|
+
console.log(` ${info} Set up hooks, Claude integration, and CI checks`);
|
|
1871
|
+
console.log(
|
|
1872
|
+
`
|
|
1873
|
+
${chalk5.dim("Defaults warn locally. Use --enforce in CI when you want failures to block.")}`
|
|
1874
|
+
);
|
|
1875
|
+
console.log("");
|
|
1876
|
+
}
|
|
1877
|
+
function summarizeSelectedIntegrations(integrations, opts) {
|
|
1878
|
+
const lines = [];
|
|
1879
|
+
if (opts.hasBoundaries) {
|
|
1880
|
+
lines.push("\u2713 Boundary rules: inferred from current imports");
|
|
1781
1881
|
} else {
|
|
1782
|
-
|
|
1783
|
-
|
|
1882
|
+
lines.push("~ Boundary rules: not enabled");
|
|
1883
|
+
}
|
|
1884
|
+
if (opts.hasCoverage) {
|
|
1885
|
+
lines.push("\u2713 Coverage checks: enabled");
|
|
1886
|
+
} else {
|
|
1887
|
+
lines.push("~ Coverage checks: disabled");
|
|
1888
|
+
}
|
|
1889
|
+
const selectedIntegrations = [
|
|
1890
|
+
integrations.preCommitHook ? "pre-commit hook" : void 0,
|
|
1891
|
+
integrations.typecheckHook ? "typecheck" : void 0,
|
|
1892
|
+
integrations.lintHook ? "lint check" : void 0,
|
|
1893
|
+
integrations.claudeCodeHook ? "Claude Code hook" : void 0,
|
|
1894
|
+
integrations.claudeMdRef ? "CLAUDE.md reference" : void 0,
|
|
1895
|
+
integrations.githubAction ? "GitHub Actions workflow" : void 0
|
|
1896
|
+
].filter(Boolean);
|
|
1897
|
+
if (selectedIntegrations.length > 0) {
|
|
1898
|
+
lines.push(`\u2713 Integrations: ${selectedIntegrations.join(" \xB7 ")}`);
|
|
1899
|
+
} else {
|
|
1900
|
+
lines.push("~ Integrations: none selected");
|
|
1901
|
+
}
|
|
1902
|
+
return lines;
|
|
1903
|
+
}
|
|
1904
|
+
function displaySetupPlan(config, integrations, opts = {}) {
|
|
1905
|
+
const configFile = opts.configFile ?? "viberails.config.json";
|
|
1906
|
+
const lines = summarizeSelectedIntegrations(integrations, {
|
|
1907
|
+
hasBoundaries: config.rules.enforceBoundaries,
|
|
1908
|
+
hasCoverage: config.rules.testCoverage > 0
|
|
1909
|
+
});
|
|
1910
|
+
console.log("");
|
|
1911
|
+
console.log(` ${chalk5.bold("Ready to write:")}`);
|
|
1912
|
+
console.log(
|
|
1913
|
+
` ${opts.replacingExistingConfig ? chalk5.yellow("!") : chalk5.green("\u2713")} ${configFile}${opts.replacingExistingConfig ? chalk5.dim(" (replacing existing config)") : ""}`
|
|
1914
|
+
);
|
|
1915
|
+
console.log(` ${chalk5.green("\u2713")} .viberails/context.md`);
|
|
1916
|
+
console.log(` ${chalk5.green("\u2713")} .viberails/scan-result.json`);
|
|
1917
|
+
for (const line of lines) {
|
|
1918
|
+
const icon = line.startsWith("\u2713") ? chalk5.green("\u2713") : chalk5.yellow("~");
|
|
1919
|
+
console.log(` ${icon} ${line.slice(2)}`);
|
|
1784
1920
|
}
|
|
1785
1921
|
console.log("");
|
|
1786
1922
|
}
|
|
@@ -2092,10 +2228,12 @@ async function configCommand(options, cwd) {
|
|
|
2092
2228
|
}
|
|
2093
2229
|
const configPath = path9.join(projectRoot, CONFIG_FILE3);
|
|
2094
2230
|
if (!fs10.existsSync(configPath)) {
|
|
2095
|
-
console.log(`${chalk6.yellow("!")} No config found. Run ${chalk6.cyan("viberails
|
|
2231
|
+
console.log(`${chalk6.yellow("!")} No config found. Run ${chalk6.cyan("viberails")} first.`);
|
|
2096
2232
|
return;
|
|
2097
2233
|
}
|
|
2098
|
-
|
|
2234
|
+
if (!options.suppressIntro) {
|
|
2235
|
+
clack6.intro("viberails config");
|
|
2236
|
+
}
|
|
2099
2237
|
const config = await loadConfig3(configPath);
|
|
2100
2238
|
let scanResult = options.rescan ? await rescanAndMerge(projectRoot, config) : void 0;
|
|
2101
2239
|
clack6.note(formatRulesText(config).join("\n"), "Current rules");
|
|
@@ -2669,7 +2807,6 @@ import { scan as scan2 } from "@viberails/scanner";
|
|
|
2669
2807
|
import chalk12 from "chalk";
|
|
2670
2808
|
|
|
2671
2809
|
// src/utils/check-prerequisites.ts
|
|
2672
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
2673
2810
|
import * as fs14 from "fs";
|
|
2674
2811
|
import * as path14 from "path";
|
|
2675
2812
|
import * as clack7 from "@clack/prompts";
|
|
@@ -2718,7 +2855,7 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
|
|
|
2718
2855
|
const detail = p.affectedPackages ? `needed by: ${p.affectedPackages.join(", ")}` : p.reason;
|
|
2719
2856
|
return `\u2717 ${p.label} \u2014 ${detail}`;
|
|
2720
2857
|
}).join("\n");
|
|
2721
|
-
clack7.note(prereqLines, "Coverage
|
|
2858
|
+
clack7.note(prereqLines, "Coverage support");
|
|
2722
2859
|
let disableCoverage = false;
|
|
2723
2860
|
for (const m of missing) {
|
|
2724
2861
|
if (!m.installCommand) continue;
|
|
@@ -2729,13 +2866,13 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
|
|
|
2729
2866
|
options: [
|
|
2730
2867
|
{
|
|
2731
2868
|
value: "install",
|
|
2732
|
-
label:
|
|
2869
|
+
label: "Install now",
|
|
2733
2870
|
hint: m.installCommand
|
|
2734
2871
|
},
|
|
2735
2872
|
{
|
|
2736
2873
|
value: "disable",
|
|
2737
|
-
label: "
|
|
2738
|
-
hint: "missing-test checks still active"
|
|
2874
|
+
label: "Disable coverage checks",
|
|
2875
|
+
hint: "missing-test checks still stay active"
|
|
2739
2876
|
},
|
|
2740
2877
|
{
|
|
2741
2878
|
value: "skip",
|
|
@@ -2748,12 +2885,7 @@ async function promptMissingPrereqs(projectRoot, prereqs) {
|
|
|
2748
2885
|
if (choice === "install") {
|
|
2749
2886
|
const is = clack7.spinner();
|
|
2750
2887
|
is.start(`Installing ${m.label}...`);
|
|
2751
|
-
const result =
|
|
2752
|
-
cwd: projectRoot,
|
|
2753
|
-
shell: true,
|
|
2754
|
-
encoding: "utf-8",
|
|
2755
|
-
stdio: "pipe"
|
|
2756
|
-
});
|
|
2888
|
+
const result = await spawnAsync(m.installCommand, projectRoot);
|
|
2757
2889
|
if (result.status === 0) {
|
|
2758
2890
|
is.stop(`Installed ${m.label}`);
|
|
2759
2891
|
} else {
|
|
@@ -3200,9 +3332,12 @@ async function initCommand(options, cwd) {
|
|
|
3200
3332
|
}
|
|
3201
3333
|
const configPath = path19.join(projectRoot, CONFIG_FILE5);
|
|
3202
3334
|
if (fs19.existsSync(configPath) && !options.force) {
|
|
3335
|
+
if (!options.yes) {
|
|
3336
|
+
return initInteractive(projectRoot, configPath, options);
|
|
3337
|
+
}
|
|
3203
3338
|
console.log(
|
|
3204
3339
|
`${chalk12.yellow("!")} viberails is already initialized.
|
|
3205
|
-
Run ${chalk12.cyan("viberails
|
|
3340
|
+
Run ${chalk12.cyan("viberails")} to review or edit the existing setup, ${chalk12.cyan("viberails sync")} to update generated files, or ${chalk12.cyan("viberails init --force")} to replace it.`
|
|
3206
3341
|
);
|
|
3207
3342
|
return;
|
|
3208
3343
|
}
|
|
@@ -3280,6 +3415,19 @@ ${created.map((f) => ` ${f}`).join("\n")}`);
|
|
|
3280
3415
|
}
|
|
3281
3416
|
async function initInteractive(projectRoot, configPath, options) {
|
|
3282
3417
|
clack8.intro("viberails");
|
|
3418
|
+
const replacingExistingConfig = fs19.existsSync(configPath);
|
|
3419
|
+
if (fs19.existsSync(configPath) && !options.force) {
|
|
3420
|
+
const action = await promptExistingConfigAction(path19.basename(configPath));
|
|
3421
|
+
if (action === "cancel") {
|
|
3422
|
+
clack8.outro("Aborted. No files were written.");
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
if (action === "edit") {
|
|
3426
|
+
await configCommand({ suppressIntro: true }, projectRoot);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
options.force = true;
|
|
3430
|
+
}
|
|
3283
3431
|
if (fs19.existsSync(configPath) && options.force) {
|
|
3284
3432
|
const replace = await confirmDangerous(
|
|
3285
3433
|
`${path19.basename(configPath)} already exists and will be replaced. Continue?`
|
|
@@ -3299,10 +3447,18 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3299
3447
|
"No source files detected. Try running from the project root,\nor check that source files exist. Run viberails sync after adding files."
|
|
3300
3448
|
);
|
|
3301
3449
|
}
|
|
3302
|
-
clack8.note(formatScanResultsText(scanResult), "Scan results");
|
|
3303
3450
|
const exemptedPkgs = getExemptedPackages(config);
|
|
3304
|
-
|
|
3305
|
-
|
|
3451
|
+
let decision;
|
|
3452
|
+
while (true) {
|
|
3453
|
+
displayInitOverview(scanResult, config, exemptedPkgs);
|
|
3454
|
+
const nextDecision = await promptInitDecision();
|
|
3455
|
+
if (nextDecision === "review") {
|
|
3456
|
+
clack8.note(formatScanResultsText(scanResult), "Detected details");
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3459
|
+
decision = nextDecision;
|
|
3460
|
+
break;
|
|
3461
|
+
}
|
|
3306
3462
|
if (decision === "customize") {
|
|
3307
3463
|
const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
|
|
3308
3464
|
const overrides = await promptRuleMenu({
|
|
@@ -3319,10 +3475,10 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3319
3475
|
}
|
|
3320
3476
|
if (config.packages.length > 1) {
|
|
3321
3477
|
clack8.note(
|
|
3322
|
-
"
|
|
3478
|
+
"Optional for monorepos. viberails can infer package boundaries\nfrom imports that already work today, so you start with rules\nthat match the current codebase.",
|
|
3323
3479
|
"Boundaries"
|
|
3324
3480
|
);
|
|
3325
|
-
const shouldInfer = await confirm3("Infer boundary rules from import patterns?");
|
|
3481
|
+
const shouldInfer = await confirm3("Infer boundary rules from current import patterns?");
|
|
3326
3482
|
if (shouldInfer) {
|
|
3327
3483
|
const bs = clack8.spinner();
|
|
3328
3484
|
bs.start("Building import graph...");
|
|
@@ -3358,27 +3514,31 @@ async function initInteractive(projectRoot, configPath, options) {
|
|
|
3358
3514
|
packageManager: rootPkgStack?.packageManager?.split("@")[0],
|
|
3359
3515
|
isWorkspace: config.packages.length > 1
|
|
3360
3516
|
});
|
|
3361
|
-
|
|
3517
|
+
displaySetupPlan(config, integrations, {
|
|
3518
|
+
replacingExistingConfig,
|
|
3519
|
+
configFile: path19.basename(configPath)
|
|
3520
|
+
});
|
|
3521
|
+
const shouldWrite = await confirm3("Apply this setup?");
|
|
3362
3522
|
if (!shouldWrite) {
|
|
3363
3523
|
clack8.outro("Aborted. No files were written.");
|
|
3364
3524
|
return;
|
|
3365
3525
|
}
|
|
3366
3526
|
const ws = clack8.spinner();
|
|
3367
|
-
ws.start("Writing configuration
|
|
3527
|
+
ws.start("Writing configuration...");
|
|
3368
3528
|
const compacted = compactConfig3(config);
|
|
3369
3529
|
fs19.writeFileSync(configPath, `${JSON.stringify(compacted, null, 2)}
|
|
3370
3530
|
`);
|
|
3371
3531
|
writeGeneratedFiles(projectRoot, config, scanResult);
|
|
3372
3532
|
updateGitignore(projectRoot);
|
|
3373
|
-
setupSelectedIntegrations(projectRoot, integrations, {
|
|
3374
|
-
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3375
|
-
packageManager: rootPkgStack?.packageManager?.split("@")[0]
|
|
3376
|
-
});
|
|
3377
3533
|
ws.stop("Configuration written");
|
|
3378
3534
|
const ok = chalk12.green("\u2713");
|
|
3379
3535
|
clack8.log.step(`${ok} ${path19.basename(configPath)}`);
|
|
3380
3536
|
clack8.log.step(`${ok} .viberails/context.md`);
|
|
3381
3537
|
clack8.log.step(`${ok} .viberails/scan-result.json`);
|
|
3538
|
+
setupSelectedIntegrations(projectRoot, integrations, {
|
|
3539
|
+
linter: rootPkgStack?.linter?.split("@")[0],
|
|
3540
|
+
packageManager: rootPkgStack?.packageManager?.split("@")[0]
|
|
3541
|
+
});
|
|
3382
3542
|
clack8.outro(
|
|
3383
3543
|
`Done! Next: review viberails.config.json, then run viberails check
|
|
3384
3544
|
${chalk12.dim("Tip: use")} ${chalk12.cyan("viberails check --enforce")} ${chalk12.dim("in CI to block PRs on violations.")}`
|
|
@@ -3494,7 +3654,7 @@ ${chalk13.bold("Synced:")}`);
|
|
|
3494
3654
|
}
|
|
3495
3655
|
|
|
3496
3656
|
// src/index.ts
|
|
3497
|
-
var VERSION = "0.6.
|
|
3657
|
+
var VERSION = "0.6.4";
|
|
3498
3658
|
var program = new Command();
|
|
3499
3659
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
3500
3660
|
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) => {
|