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.cjs CHANGED
@@ -66,7 +66,7 @@ var clack5 = __toESM(require("@clack/prompts"), 1);
66
66
  // src/utils/prompt-integrations.ts
67
67
  var import_node_child_process = require("child_process");
68
68
  var clack = __toESM(require("@clack/prompts"), 1);
69
- async function promptHookManagerInstall(projectRoot, packageManager) {
69
+ async function promptHookManagerInstall(projectRoot, packageManager, isWorkspace) {
70
70
  const choice = await clack.select({
71
71
  message: "No git hook manager detected. Install Lefthook for shareable pre-commit hooks?",
72
72
  options: [
@@ -85,7 +85,7 @@ async function promptHookManagerInstall(projectRoot, packageManager) {
85
85
  assertNotCancelled(choice);
86
86
  if (choice !== "install") return void 0;
87
87
  const pm = packageManager || "npm";
88
- const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? "pnpm add -D lefthook" : "npm install -D lefthook";
88
+ const installCmd = pm === "yarn" ? "yarn add -D lefthook" : pm === "pnpm" ? `pnpm add -D${isWorkspace ? " -w" : ""} lefthook` : "npm install -D lefthook";
89
89
  const s = clack.spinner();
90
90
  s.start("Installing Lefthook...");
91
91
  const result = (0, import_node_child_process.spawnSync)(installCmd, {
@@ -113,7 +113,8 @@ async function promptIntegrations(projectRoot, hookManager, tools) {
113
113
  if (!resolvedHookManager) {
114
114
  resolvedHookManager = await promptHookManagerInstall(
115
115
  projectRoot,
116
- tools?.packageManager ?? "npm"
116
+ tools?.packageManager ?? "npm",
117
+ tools?.isWorkspace
117
118
  );
118
119
  }
119
120
  const isBareHook = !resolvedHookManager;
@@ -1683,13 +1684,15 @@ function displayInitSummary(config, exemptedPackages) {
1683
1684
  console.log("");
1684
1685
  console.log(` ${import_chalk4.default.bold("Rules to apply:")}`);
1685
1686
  console.log(` ${ok} Max file size: ${import_chalk4.default.cyan(`${config.rules.maxFileLines} lines`)}`);
1686
- if (config.rules.enforceNaming && root?.conventions?.fileNaming) {
1687
- console.log(` ${ok} File naming: ${import_chalk4.default.cyan(root.conventions.fileNaming)}`);
1687
+ const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1688
+ if (config.rules.enforceNaming && fileNaming) {
1689
+ console.log(` ${ok} File naming: ${import_chalk4.default.cyan(fileNaming)}`);
1688
1690
  } else {
1689
1691
  console.log(` ${off} File naming: ${import_chalk4.default.dim("not enforced")}`);
1690
1692
  }
1691
- if (config.rules.enforceMissingTests && root?.structure?.testPattern) {
1692
- console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan(`enforced (${root.structure.testPattern})`)}`);
1693
+ const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1694
+ if (config.rules.enforceMissingTests && testPattern) {
1695
+ console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan(`enforced (${testPattern})`)}`);
1693
1696
  } else if (config.rules.enforceMissingTests) {
1694
1697
  console.log(` ${ok} Missing tests: ${import_chalk4.default.cyan("enforced")}`);
1695
1698
  } else {
@@ -2487,30 +2490,36 @@ var path14 = __toESM(require("path"), 1);
2487
2490
  var clack7 = __toESM(require("@clack/prompts"), 1);
2488
2491
  var import_chalk8 = __toESM(require("chalk"), 1);
2489
2492
  function checkCoveragePrereqs(projectRoot, scanResult) {
2490
- const testRunner = scanResult.stack.testRunner;
2491
- if (!testRunner) return [];
2492
- const runner = testRunner.name;
2493
2493
  const pm = scanResult.stack.packageManager.name;
2494
- if (runner === "vitest") {
2495
- const hasV8 = hasDependency(projectRoot, "@vitest/coverage-v8");
2496
- const hasIstanbul = hasDependency(projectRoot, "@vitest/coverage-istanbul");
2497
- const installed = hasV8 || hasIstanbul;
2498
- const addCmd = pm === "yarn" ? "yarn add -D" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2499
- return [
2500
- {
2501
- label: "@vitest/coverage-v8",
2502
- installed,
2503
- installCommand: installed ? void 0 : `${addCmd} @vitest/coverage-v8`,
2504
- reason: "Required for coverage percentage checks with vitest"
2505
- }
2506
- ];
2494
+ const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
2495
+ const hasVitest = vitestPackages.length > 0 || scanResult.stack.testRunner?.name === "vitest";
2496
+ if (!hasVitest) return [];
2497
+ let installed = hasDependency(projectRoot, "@vitest/coverage-v8") || hasDependency(projectRoot, "@vitest/coverage-istanbul");
2498
+ if (!installed && vitestPackages.length > 0) {
2499
+ installed = vitestPackages.every((rel) => {
2500
+ const pkgDir = path14.join(projectRoot, rel);
2501
+ return hasDependency(pkgDir, "@vitest/coverage-v8") || hasDependency(pkgDir, "@vitest/coverage-istanbul");
2502
+ });
2507
2503
  }
2508
- return [];
2504
+ const isWorkspace = scanResult.packages.length > 1;
2505
+ const addCmd = pm === "yarn" ? "yarn add -D" : pm === "pnpm" && isWorkspace ? "pnpm add -D -w" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2506
+ const affectedPackages = vitestPackages.length > 1 ? vitestPackages : void 0;
2507
+ const reason = affectedPackages ? `Required for coverage in: ${affectedPackages.join(", ")}` : "Required for coverage percentage checks with vitest";
2508
+ return [
2509
+ {
2510
+ label: "@vitest/coverage-v8",
2511
+ installed,
2512
+ installCommand: installed ? void 0 : `${addCmd} @vitest/coverage-v8`,
2513
+ reason,
2514
+ affectedPackages
2515
+ }
2516
+ ];
2509
2517
  }
2510
2518
  function displayMissingPrereqs(prereqs) {
2511
2519
  const missing = prereqs.filter((p) => !p.installed);
2512
2520
  for (const m of missing) {
2513
- console.log(` ${import_chalk8.default.yellow("!")} ${m.label} not installed \u2014 ${m.reason}`);
2521
+ const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2522
+ console.log(` ${import_chalk8.default.yellow("!")} ${m.label} not installed${suffix}`);
2514
2523
  if (m.installCommand) {
2515
2524
  console.log(` Install: ${import_chalk8.default.cyan(m.installCommand)}`);
2516
2525
  }
@@ -2519,15 +2528,19 @@ function displayMissingPrereqs(prereqs) {
2519
2528
  async function promptMissingPrereqs(projectRoot, prereqs) {
2520
2529
  const missing = prereqs.filter((p) => !p.installed);
2521
2530
  if (missing.length === 0) return { disableCoverage: false };
2522
- const prereqLines = prereqs.map(
2523
- (p) => `${p.installed ? "\u2713" : "\u2717"} ${p.label}${p.installed ? "" : ` \u2014 ${p.reason}`}`
2524
- ).join("\n");
2531
+ const prereqLines = prereqs.map((p) => {
2532
+ if (p.installed) return `\u2713 ${p.label}`;
2533
+ const detail = p.affectedPackages ? `needed by: ${p.affectedPackages.join(", ")}` : p.reason;
2534
+ return `\u2717 ${p.label} \u2014 ${detail}`;
2535
+ }).join("\n");
2525
2536
  clack7.note(prereqLines, "Coverage prerequisites");
2526
2537
  let disableCoverage = false;
2527
2538
  for (const m of missing) {
2528
2539
  if (!m.installCommand) continue;
2540
+ const pkgCount = m.affectedPackages?.length;
2541
+ 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.`;
2529
2542
  const choice = await clack7.select({
2530
- message: `${m.label} is not installed. It is required for coverage percentage checks.`,
2543
+ message,
2531
2544
  options: [
2532
2545
  {
2533
2546
  value: "install",
@@ -2690,7 +2703,6 @@ function addLefthookPreCommit(lefthookPath) {
2690
2703
  function detectHookManager(projectRoot) {
2691
2704
  if (fs16.existsSync(path16.join(projectRoot, "lefthook.yml"))) return "Lefthook";
2692
2705
  if (fs16.existsSync(path16.join(projectRoot, ".husky"))) return "Husky";
2693
- if (fs16.existsSync(path16.join(projectRoot, ".git"))) return "git hook";
2694
2706
  return void 0;
2695
2707
  }
2696
2708
  function setupClaudeCodeHook(projectRoot) {
@@ -2744,7 +2756,7 @@ function setupClaudeMdReference(projectRoot) {
2744
2756
  fs16.writeFileSync(claudeMdPath, prefix + ref);
2745
2757
  console.log(` ${import_chalk9.default.green("\u2713")} CLAUDE.md \u2014 added @.viberails/context.md reference`);
2746
2758
  }
2747
- function setupGithubAction(projectRoot, packageManager) {
2759
+ function setupGithubAction(projectRoot, packageManager, options) {
2748
2760
  const workflowDir = path16.join(projectRoot, ".github", "workflows");
2749
2761
  const workflowPath = path16.join(workflowDir, "viberails.yml");
2750
2762
  if (fs16.existsSync(workflowPath)) {
@@ -2780,7 +2792,16 @@ function setupGithubAction(projectRoot, packageManager) {
2780
2792
  " node-version: 22",
2781
2793
  pm !== "npm" ? ` cache: ${pm}` : "",
2782
2794
  "",
2783
- ` - run: ${installCmd}`,
2795
+ ` - run: ${installCmd}`
2796
+ );
2797
+ if (options?.typecheck) {
2798
+ lines.push(` - run: ${runPrefix} tsc --noEmit`);
2799
+ }
2800
+ if (options?.linter) {
2801
+ const lintCmd = options.linter === "biome" ? "biome check ." : "eslint .";
2802
+ lines.push(` - run: ${runPrefix} ${lintCmd}`);
2803
+ }
2804
+ lines.push(
2784
2805
  ` - run: ${runPrefix} viberails check --enforce --diff-base origin/\${{ github.event.pull_request.base.ref }}`,
2785
2806
  ""
2786
2807
  );
@@ -2901,7 +2922,10 @@ function setupSelectedIntegrations(projectRoot, integrations, opts) {
2901
2922
  created.push("CLAUDE.md \u2014 added @.viberails/context.md reference");
2902
2923
  }
2903
2924
  if (integrations.githubAction) {
2904
- const t = setupGithubAction(projectRoot, opts.packageManager ?? "npm");
2925
+ const t = setupGithubAction(projectRoot, opts.packageManager ?? "npm", {
2926
+ linter: integrations.lintHook ? opts.linter : void 0,
2927
+ typecheck: integrations.typecheckHook
2928
+ });
2905
2929
  if (t) created.push(`${t} \u2014 blocks PRs on violations`);
2906
2930
  }
2907
2931
  return created;
@@ -2969,11 +2993,15 @@ async function initNonInteractive(projectRoot, configPath) {
2969
2993
  setupClaudeMdReference(projectRoot);
2970
2994
  const rootPkg = config.packages[0];
2971
2995
  const rootPkgPm = rootPkg?.stack?.packageManager ?? "npm";
2972
- const actionTarget = setupGithubAction(projectRoot, rootPkgPm);
2996
+ const linter = rootPkg?.stack?.linter?.split("@")[0];
2997
+ const isTypeScript = rootPkg?.stack?.language === "typescript";
2998
+ const actionTarget = setupGithubAction(projectRoot, rootPkgPm, {
2999
+ linter,
3000
+ typecheck: isTypeScript
3001
+ });
2973
3002
  const hookManager = detectHookManager(projectRoot);
2974
3003
  const hasHookManager = hookManager === "Lefthook" || hookManager === "Husky";
2975
3004
  const preCommitTarget = hasHookManager ? setupPreCommitHook(projectRoot) : void 0;
2976
- const linter = rootPkg?.stack?.linter?.split("@")[0];
2977
3005
  const ok = import_chalk11.default.green("\u2713");
2978
3006
  const created = [
2979
3007
  `${ok} ${path18.basename(configPath)}`,
@@ -3066,7 +3094,8 @@ async function initInteractive(projectRoot, configPath, options) {
3066
3094
  const integrations = await promptIntegrations(projectRoot, hookManager, {
3067
3095
  isTypeScript: rootPkgStack?.language === "typescript",
3068
3096
  linter: rootPkgStack?.linter?.split("@")[0],
3069
- packageManager: rootPkgStack?.packageManager
3097
+ packageManager: rootPkgStack?.packageManager,
3098
+ isWorkspace: config.packages.length > 1
3070
3099
  });
3071
3100
  const shouldWrite = await confirm3("Write configuration and set up selected integrations?");
3072
3101
  if (!shouldWrite) {
@@ -3202,7 +3231,7 @@ ${import_chalk12.default.bold("Synced:")}`);
3202
3231
  }
3203
3232
 
3204
3233
  // src/index.ts
3205
- var VERSION = "0.5.2";
3234
+ var VERSION = "0.5.4";
3206
3235
  var program = new import_commander.Command();
3207
3236
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3208
3237
  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) => {