viberails 0.5.2 → 0.5.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.js CHANGED
@@ -33,7 +33,7 @@ import * as clack5 from "@clack/prompts";
33
33
  // src/utils/prompt-integrations.ts
34
34
  import { spawnSync } from "child_process";
35
35
  import * as clack from "@clack/prompts";
36
- async function promptHookManagerInstall(projectRoot, packageManager) {
36
+ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace) {
37
37
  const choice = await clack.select({
38
38
  message: "No git hook manager detected. Install Lefthook for shareable pre-commit hooks?",
39
39
  options: [
@@ -52,7 +52,7 @@ async function promptHookManagerInstall(projectRoot, packageManager) {
52
52
  assertNotCancelled(choice);
53
53
  if (choice !== "install") return void 0;
54
54
  const pm = packageManager || "npm";
55
- const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? "pnpm add -D lefthook" : "npm install -D lefthook";
55
+ const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
56
56
  const s = clack.spinner();
57
57
  s.start("Installing Lefthook...");
58
58
  const result = spawnSync(installCmd, {
@@ -80,7 +80,8 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
80
80
  if (!resolvedHookManager) {
81
81
  resolvedHookManager = await promptHookManagerInstall(
82
82
  projectRoot,
83
- tools?.packageManager ?? "npm"
83
+ tools?.packageManager ?? "npm",
84
+ tools?.isWorkspace
84
85
  );
85
86
  }
86
87
  const isBareHook = !resolvedHookManager;
@@ -1662,13 +1663,15 @@ function displayInitSummary(config, exemptedPackages) {
1662
1663
  console.log("");
1663
1664
  console.log(` ${chalk4.bold("Rules to apply:")}`);
1664
1665
  console.log(` ${ok} Max file size: ${chalk4.cyan(`${config.rules.maxFileLines} lines`)}`);
1665
- if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
1666
- console.log(` ${ok} File naming: ${chalk4.cyan(root.conventions.fileNaming)}`);
1666
+ const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1667
+ if (config.rules.enforceNaming && fileNaming) {
1668
+ console.log(` ${ok} File naming: ${chalk4.cyan(fileNaming)}`);
1667
1669
  } else {
1668
1670
  console.log(` ${off} File naming: ${chalk4.dim("not enforced")}`);
1669
1671
  }
1670
- if (config.rules.enforceMissingTests && root?.structure?.testPattern) {
1671
- console.log(` ${ok} Missing tests: ${chalk4.cyan(`enforced (${root.structure.testPattern})`)}`);
1672
+ const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1673
+ if (config.rules.enforceMissingTests && testPattern) {
1674
+ console.log(` ${ok} Missing tests: ${chalk4.cyan(`enforced (${testPattern})`)}`);
1672
1675
  } else if (config.rules.enforceMissingTests) {
1673
1676
  console.log(` ${ok} Missing tests: ${chalk4.cyan("enforced")}`);
1674
1677
  } else {
@@ -2466,30 +2469,36 @@ import * as path14 from "path";
2466
2469
  import * as clack7 from "@clack/prompts";
2467
2470
  import chalk8 from "chalk";
2468
2471
  function checkCoveragePrereqs(projectRoot, scanResult) {
2469
- const testRunner = scanResult.stack.testRunner;
2470
- if (!testRunner) return [];
2471
- const runner = testRunner.name;
2472
2472
  const pm = scanResult.stack.packageManager.name;
2473
- if (runner === "vitest") {
2474
- const hasV8 = hasDependency(projectRoot, "@vitest/coverage-v8");
2475
- const hasIstanbul = hasDependency(projectRoot, "@vitest/coverage-istanbul");
2476
- const installed = hasV8 || hasIstanbul;
2477
- const addCmd = pm === "yarn" ? "yarn add -D" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2478
- return [
2479
- {
2480
- label: "@vitest/coverage-v8",
2481
- installed,
2482
- installCommand: installed ? void 0 : `${addCmd} @vitest/coverage-v8`,
2483
- reason: "Required for coverage percentage checks with vitest"
2484
- }
2485
- ];
2473
+ const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
2474
+ const hasVitest = vitestPackages.length > 0 || scanResult.stack.testRunner?.name === "vitest";
2475
+ if (!hasVitest) return [];
2476
+ let installed = hasDependency(projectRoot, "@vitest/coverage-v8") || hasDependency(projectRoot, "@vitest/coverage-istanbul");
2477
+ if (!installed && vitestPackages.length > 0) {
2478
+ installed = vitestPackages.every((rel) => {
2479
+ const pkgDir = path14.join(projectRoot, rel);
2480
+ return hasDependency(pkgDir, "@vitest/coverage-v8") || hasDependency(pkgDir, "@vitest/coverage-istanbul");
2481
+ });
2486
2482
  }
2487
- return [];
2483
+ const isWorkspace = scanResult.packages.length > 1;
2484
+ const addCmd = pm === "yarn" ? "yarn add -D" : pm === "pnpm" && isWorkspace ? "pnpm add -D -w" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2485
+ const affectedPackages = vitestPackages.length > 1 ? vitestPackages : void 0;
2486
+ const reason = affectedPackages ? `Required for coverage in: ${affectedPackages.join(", ")}` : "Required for coverage percentage checks with vitest";
2487
+ return [
2488
+ {
2489
+ label: "@vitest/coverage-v8",
2490
+ installed,
2491
+ installCommand: installed ? void 0 : `${addCmd} @vitest/coverage-v8`,
2492
+ reason,
2493
+ affectedPackages
2494
+ }
2495
+ ];
2488
2496
  }
2489
2497
  function displayMissingPrereqs(prereqs) {
2490
2498
  const missing = prereqs.filter((p) => !p.installed);
2491
2499
  for (const m of missing) {
2492
- console.log(` ${chalk8.yellow("!")} ${m.label} not installed \u2014 ${m.reason}`);
2500
+ const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2501
+ console.log(` ${chalk8.yellow("!")} ${m.label} not installed${suffix}`);
2493
2502
  if (m.installCommand) {
2494
2503
  console.log(` Install: ${chalk8.cyan(m.installCommand)}`);
2495
2504
  }
@@ -2498,15 +2507,19 @@ function displayMissingPrereqs(prereqs) {
2498
2507
  async function promptMissingPrereqs(projectRoot, prereqs) {
2499
2508
  const missing = prereqs.filter((p) => !p.installed);
2500
2509
  if (missing.length === 0) return { disableCoverage: false };
2501
- const prereqLines = prereqs.map(
2502
- (p) => `${p.installed ? "\u2713" : "\u2717"} ${p.label}${p.installed ? "" : ` \u2014 ${p.reason}`}`
2503
- ).join("\n");
2510
+ const prereqLines = prereqs.map((p) => {
2511
+ if (p.installed) return `\u2713 ${p.label}`;
2512
+ const detail = p.affectedPackages ? `needed by: ${p.affectedPackages.join(", ")}` : p.reason;
2513
+ return `\u2717 ${p.label} \u2014 ${detail}`;
2514
+ }).join("\n");
2504
2515
  clack7.note(prereqLines, "Coverage prerequisites");
2505
2516
  let disableCoverage = false;
2506
2517
  for (const m of missing) {
2507
2518
  if (!m.installCommand) continue;
2519
+ const pkgCount = m.affectedPackages?.length;
2520
+ const message = pkgCount ? `${m.label} is not installed. Required for coverage in ${pkgCount} packages using vitest.` : `${m.label} is not installed. It is required for coverage percentage checks.`;
2508
2521
  const choice = await clack7.select({
2509
- message: `${m.label} is not installed. It is required for coverage percentage checks.`,
2522
+ message,
2510
2523
  options: [
2511
2524
  {
2512
2525
  value: "install",
@@ -2669,7 +2682,6 @@ function addLefthookPreCommit(lefthookPath) {
2669
2682
  function detectHookManager(projectRoot) {
2670
2683
  if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2671
2684
  if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
2672
- if (fs16.existsSync(path16.join(projectRoot, ".git"))) return "git hook";
2673
2685
  return void 0;
2674
2686
  }
2675
2687
  function setupClaudeCodeHook(projectRoot) {
@@ -2723,7 +2735,7 @@ function setupClaudeMdReference(projectRoot) {
2723
2735
  fs16.writeFileSync(claudeMdPath, prefix + ref);
2724
2736
  console.log(` ${chalk9.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2725
2737
  }
2726
- function setupGithubAction(projectRoot, packageManager) {
2738
+ function setupGithubAction(projectRoot, packageManager, options) {
2727
2739
  const workflowDir = path16.join(projectRoot, ".github", "workflows");
2728
2740
  const workflowPath = path16.join(workflowDir, "viberails.yml");
2729
2741
  if (fs16.existsSync(workflowPath)) {
@@ -2759,7 +2771,16 @@ function setupGithubAction(projectRoot, packageManager) {
2759
2771
  " node-version: 22",
2760
2772
  pm !== "npm" ? ` cache: ${pm}` : "",
2761
2773
  "",
2762
- ` - run: ${installCmd}`,
2774
+ ` - run: ${installCmd}`
2775
+ );
2776
+ if (options?.typecheck) {
2777
+ lines.push(` - run: ${runPrefix} tsc --noEmit`);
2778
+ }
2779
+ if (options?.linter) {
2780
+ const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
2781
+ lines.push(` - run: ${runPrefix} ${lintCmd}`);
2782
+ }
2783
+ lines.push(
2763
2784
  ` - run: ${runPrefix} viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
2764
2785
  ""
2765
2786
  );
@@ -2880,7 +2901,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
2880
2901
  created.push("CLAUDE.md \u2014 added @.viberails/context.md reference");
2881
2902
  }
2882
2903
  if (integrations.githubAction) {
2883
- const t = setupGithubAction(projectRoot, opts.packageManager ?? "npm");
2904
+ const t = setupGithubAction(projectRoot, opts.packageManager ?? "npm", {
2905
+ linter: integrations.lintHook ? opts.linter : void 0,
2906
+ typecheck: integrations.typecheckHook
2907
+ });
2884
2908
  if (t) created.push(`${t} \u2014 blocks PRs on violations`);
2885
2909
  }
2886
2910
  return created;
@@ -2948,11 +2972,15 @@ async function initNonInteractive(projectRoot, configPath) {
2948
2972
  setupClaudeMdReference(projectRoot);
2949
2973
  const rootPkg = config.packages[0];
2950
2974
  const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
2951
- const actionTarget = setupGithubAction(projectRoot, rootPkgPm);
2975
+ const linter = rootPkg?.stack?.linter?.split("@")[0];
2976
+ const isTypeScript = rootPkg?.stack?.language === "typescript";
2977
+ const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
2978
+ linter,
2979
+ typecheck: isTypeScript
2980
+ });
2952
2981
  const hookManager = detectHookManager(projectRoot);
2953
2982
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
2954
2983
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
2955
- const linter = rootPkg?.stack?.linter?.split("@")[0];
2956
2984
  const ok = chalk11.green("\u2713");
2957
2985
  const created = [
2958
2986
  `${ok} ${path18.basename(configPath)}`,
@@ -3045,7 +3073,8 @@ async function initInteractive(projectRoot, configPath, options) {
3045
3073
  const integrations = await promptIntegrations(projectRoot, hookManager, {
3046
3074
  isTypeScript: rootPkgStack?.language === "typescript",
3047
3075
  linter: rootPkgStack?.linter?.split("@")[0],
3048
- packageManager: rootPkgStack?.packageManager
3076
+ packageManager: rootPkgStack?.packageManager,
3077
+ isWorkspace: config.packages.length > 1
3049
3078
  });
3050
3079
  const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
3051
3080
  if (!shouldWrite) {
@@ -3181,7 +3210,7 @@ ${chalk12.bold("Synced:")}`);
3181
3210
  }
3182
3211
 
3183
3212
  // src/index.ts
3184
- var VERSION = "0.5.2";
3213
+ var VERSION = "0.5.4";
3185
3214
  var program = new Command();
3186
3215
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3187
3216
  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) => {