viberails 0.5.1 → 0.5.3

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
@@ -328,11 +328,10 @@ function buildMenuOptions(state, packageCount) {
328
328
  hint: state.fileNamingValue
329
329
  });
330
330
  }
331
- options.push({
332
- value: "testCoverage",
333
- label: "Test coverage target",
334
- hint: state.testCoverage === 0 ? "0 (disabled)" : `${state.testCoverage}%`
335
- });
331
+ const isMonorepo = packageCount > 0;
332
+ const coverageLabel = isMonorepo ? "Default coverage target" : "Test coverage target";
333
+ const coverageHint = state.testCoverage === 0 ? "0 (disabled)" : isMonorepo ? `${state.testCoverage}% (per-package default)` : `${state.testCoverage}%`;
334
+ options.push({ value: "testCoverage", label: coverageLabel, hint: coverageHint });
336
335
  options.push({
337
336
  value: "enforceMissingTests",
338
337
  label: "Enforce missing tests",
@@ -342,16 +341,16 @@ function buildMenuOptions(state, packageCount) {
342
341
  options.push(
343
342
  {
344
343
  value: "coverageSummaryPath",
345
- label: "Coverage summary path",
344
+ label: isMonorepo ? "Default coverage summary path" : "Coverage summary path",
346
345
  hint: state.coverageSummaryPath
347
346
  },
348
347
  {
349
348
  value: "coverageCommand",
350
- label: "Coverage command",
349
+ label: isMonorepo ? "Default coverage command" : "Coverage command",
351
350
  hint: state.coverageCommand ?? "auto-detect from package.json test runner"
352
351
  }
353
352
  );
354
- if (packageCount > 0) {
353
+ if (isMonorepo) {
355
354
  options.push({
356
355
  value: "packageOverrides",
357
356
  label: "Per-package coverage overrides",
@@ -1655,6 +1654,58 @@ function displayRulesPreview(config) {
1655
1654
  );
1656
1655
  console.log("");
1657
1656
  }
1657
+ function displayInitSummary(config, exemptedPackages) {
1658
+ const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1659
+ const isMonorepo = config.packages.length > 1;
1660
+ const ok = chalk4.green("\u2713");
1661
+ const off = chalk4.dim("\u25CB");
1662
+ console.log("");
1663
+ console.log(` ${chalk4.bold("Rules to apply:")}`);
1664
+ console.log(` ${ok} Max file size: ${chalk4.cyan(`${config.rules.maxFileLines} lines`)}`);
1665
+ const fileNaming = root?.conventions?.fileNaming ?? config.packages.find((p) => p.conventions?.fileNaming)?.conventions?.fileNaming;
1666
+ if (config.rules.enforceNaming && fileNaming) {
1667
+ console.log(` ${ok} File naming: ${chalk4.cyan(fileNaming)}`);
1668
+ } else {
1669
+ console.log(` ${off} File naming: ${chalk4.dim("not enforced")}`);
1670
+ }
1671
+ const testPattern = root?.structure?.testPattern ?? config.packages.find((p) => p.structure?.testPattern)?.structure?.testPattern;
1672
+ if (config.rules.enforceMissingTests && testPattern) {
1673
+ console.log(` ${ok} Missing tests: ${chalk4.cyan(`enforced (${testPattern})`)}`);
1674
+ } else if (config.rules.enforceMissingTests) {
1675
+ console.log(` ${ok} Missing tests: ${chalk4.cyan("enforced")}`);
1676
+ } else {
1677
+ console.log(` ${off} Missing tests: ${chalk4.dim("not enforced")}`);
1678
+ }
1679
+ if (config.rules.testCoverage > 0) {
1680
+ if (isMonorepo) {
1681
+ const withCoverage = config.packages.filter(
1682
+ (p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
1683
+ );
1684
+ console.log(
1685
+ ` ${ok} Coverage: ${chalk4.cyan(`${config.rules.testCoverage}%`)} default ${chalk4.dim(`(${withCoverage.length}/${config.packages.length} packages)`)}`
1686
+ );
1687
+ } else {
1688
+ console.log(` ${ok} Coverage: ${chalk4.cyan(`${config.rules.testCoverage}%`)}`);
1689
+ }
1690
+ } else {
1691
+ console.log(` ${off} Coverage: ${chalk4.dim("disabled")}`);
1692
+ }
1693
+ if (exemptedPackages.length > 0) {
1694
+ console.log(
1695
+ ` ${chalk4.dim(" exempted:")} ${chalk4.dim(exemptedPackages.join(", "))} ${chalk4.dim("(types-only)")}`
1696
+ );
1697
+ }
1698
+ if (isMonorepo) {
1699
+ console.log(
1700
+ `
1701
+ ${chalk4.dim(`${config.packages.length} packages scanned \xB7 warns on violation \xB7 use --enforce in CI`)}`
1702
+ );
1703
+ } else {
1704
+ console.log(`
1705
+ ${chalk4.dim("warns on violation \xB7 use --enforce in CI to block")}`);
1706
+ }
1707
+ console.log("");
1708
+ }
1658
1709
 
1659
1710
  // src/display-text.ts
1660
1711
  function plainConfidenceLabel(convention) {
@@ -1695,10 +1746,13 @@ function formatConventionsText(scanResult) {
1695
1746
  }
1696
1747
  function formatRulesText(config) {
1697
1748
  const root = config.packages.find((p) => p.path === ".") ?? config.packages[0];
1749
+ const isMonorepo = config.packages.length > 1;
1698
1750
  const lines = [];
1699
1751
  lines.push(`Max file size: ${config.rules.maxFileLines} lines`);
1700
1752
  if (config.rules.testCoverage > 0) {
1701
- lines.push(`Test coverage target: ${config.rules.testCoverage}%`);
1753
+ const label = isMonorepo ? "Default coverage target" : "Test coverage target";
1754
+ const suffix = isMonorepo ? " (per-package)" : "";
1755
+ lines.push(`${label}: ${config.rules.testCoverage}%${suffix}`);
1702
1756
  } else {
1703
1757
  lines.push("Test coverage target: disabled");
1704
1758
  }
@@ -2414,30 +2468,35 @@ import * as path14 from "path";
2414
2468
  import * as clack7 from "@clack/prompts";
2415
2469
  import chalk8 from "chalk";
2416
2470
  function checkCoveragePrereqs(projectRoot, scanResult) {
2417
- const testRunner = scanResult.stack.testRunner;
2418
- if (!testRunner) return [];
2419
- const runner = testRunner.name;
2420
2471
  const pm = scanResult.stack.packageManager.name;
2421
- if (runner === "vitest") {
2422
- const hasV8 = hasDependency(projectRoot, "@vitest/coverage-v8");
2423
- const hasIstanbul = hasDependency(projectRoot, "@vitest/coverage-istanbul");
2424
- const installed = hasV8 || hasIstanbul;
2425
- const addCmd = pm === "yarn" ? "yarn add -D" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2426
- return [
2427
- {
2428
- label: "@vitest/coverage-v8",
2429
- installed,
2430
- installCommand: installed ? void 0 : `${addCmd} @vitest/coverage-v8`,
2431
- reason: "Required for coverage percentage checks with vitest"
2432
- }
2433
- ];
2472
+ const vitestPackages = scanResult.packages.filter((pkg) => pkg.stack.testRunner?.name === "vitest").map((pkg) => pkg.relativePath);
2473
+ const hasVitest = vitestPackages.length > 0 || scanResult.stack.testRunner?.name === "vitest";
2474
+ if (!hasVitest) return [];
2475
+ let installed = hasDependency(projectRoot, "@vitest/coverage-v8") || hasDependency(projectRoot, "@vitest/coverage-istanbul");
2476
+ if (!installed && vitestPackages.length > 0) {
2477
+ installed = vitestPackages.every((rel) => {
2478
+ const pkgDir = path14.join(projectRoot, rel);
2479
+ return hasDependency(pkgDir, "@vitest/coverage-v8") || hasDependency(pkgDir, "@vitest/coverage-istanbul");
2480
+ });
2434
2481
  }
2435
- return [];
2482
+ const addCmd = pm === "yarn" ? "yarn add -D" : pm === "npm" ? "npm install -D" : `${pm} add -D`;
2483
+ const affectedPackages = vitestPackages.length > 1 ? vitestPackages : void 0;
2484
+ const reason = affectedPackages ? `Required for coverage in: ${affectedPackages.join(", ")}` : "Required for coverage percentage checks with vitest";
2485
+ return [
2486
+ {
2487
+ label: "@vitest/coverage-v8",
2488
+ installed,
2489
+ installCommand: installed ? void 0 : `${addCmd} @vitest/coverage-v8`,
2490
+ reason,
2491
+ affectedPackages
2492
+ }
2493
+ ];
2436
2494
  }
2437
2495
  function displayMissingPrereqs(prereqs) {
2438
2496
  const missing = prereqs.filter((p) => !p.installed);
2439
2497
  for (const m of missing) {
2440
- console.log(` ${chalk8.yellow("!")} ${m.label} not installed \u2014 ${m.reason}`);
2498
+ const suffix = m.affectedPackages ? ` \u2014 needed for coverage in: ${m.affectedPackages.join(", ")}` : ` \u2014 ${m.reason}`;
2499
+ console.log(` ${chalk8.yellow("!")} ${m.label} not installed${suffix}`);
2441
2500
  if (m.installCommand) {
2442
2501
  console.log(` Install: ${chalk8.cyan(m.installCommand)}`);
2443
2502
  }
@@ -2446,15 +2505,19 @@ function displayMissingPrereqs(prereqs) {
2446
2505
  async function promptMissingPrereqs(projectRoot, prereqs) {
2447
2506
  const missing = prereqs.filter((p) => !p.installed);
2448
2507
  if (missing.length === 0) return { disableCoverage: false };
2449
- const prereqLines = prereqs.map(
2450
- (p) => `${p.installed ? "\u2713" : "\u2717"} ${p.label}${p.installed ? "" : ` \u2014 ${p.reason}`}`
2451
- ).join("\n");
2508
+ const prereqLines = prereqs.map((p) => {
2509
+ if (p.installed) return `\u2713 ${p.label}`;
2510
+ const detail = p.affectedPackages ? `needed by: ${p.affectedPackages.join(", ")}` : p.reason;
2511
+ return `\u2717 ${p.label} \u2014 ${detail}`;
2512
+ }).join("\n");
2452
2513
  clack7.note(prereqLines, "Coverage prerequisites");
2453
2514
  let disableCoverage = false;
2454
2515
  for (const m of missing) {
2455
2516
  if (!m.installCommand) continue;
2517
+ const pkgCount = m.affectedPackages?.length;
2518
+ 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.`;
2456
2519
  const choice = await clack7.select({
2457
- message: `${m.label} is not installed. It is required for coverage percentage checks.`,
2520
+ message,
2458
2521
  options: [
2459
2522
  {
2460
2523
  value: "install",
@@ -2946,11 +3009,8 @@ async function initInteractive(projectRoot, configPath, options) {
2946
3009
  );
2947
3010
  }
2948
3011
  clack8.note(formatScanResultsText(scanResult), "Scan results");
2949
- const rulesLines = formatRulesText(config);
2950
3012
  const exemptedPkgs = getExemptedPackages(config);
2951
- if (exemptedPkgs.length > 0)
2952
- rulesLines.push(`Auto-exempted from coverage: ${exemptedPkgs.join(", ")} (types-only)`);
2953
- clack8.note(rulesLines.join("\n"), "Rules");
3013
+ displayInitSummary(config, exemptedPkgs);
2954
3014
  const decision = await promptInitDecision();
2955
3015
  if (decision === "customize") {
2956
3016
  const rootPkg = config.packages.find((p) => p.path === ".") ?? config.packages[0];
@@ -3132,7 +3192,7 @@ ${chalk12.bold("Synced:")}`);
3132
3192
  }
3133
3193
 
3134
3194
  // src/index.ts
3135
- var VERSION = "0.5.1";
3195
+ var VERSION = "0.5.3";
3136
3196
  var program = new Command();
3137
3197
  program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
3138
3198
  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) => {