lacuna-cli 0.1.5 → 0.1.7

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.
Files changed (107) hide show
  1. package/README.md +83 -21
  2. package/dist/agent/context.d.ts +2 -0
  3. package/dist/agent/context.d.ts.map +1 -1
  4. package/dist/agent/context.js +314 -1
  5. package/dist/agent/context.js.map +1 -1
  6. package/dist/agent/fix-loop.d.ts +5 -0
  7. package/dist/agent/fix-loop.d.ts.map +1 -1
  8. package/dist/agent/fix-loop.js +376 -41
  9. package/dist/agent/fix-loop.js.map +1 -1
  10. package/dist/agent/generator.d.ts +8 -1
  11. package/dist/agent/generator.d.ts.map +1 -1
  12. package/dist/agent/generator.js +89 -11
  13. package/dist/agent/generator.js.map +1 -1
  14. package/dist/agent/loop.d.ts +8 -0
  15. package/dist/agent/loop.d.ts.map +1 -1
  16. package/dist/agent/loop.js +108 -46
  17. package/dist/agent/loop.js.map +1 -1
  18. package/dist/agent/{prompts.d.ts → prompts/index.d.ts} +12 -2
  19. package/dist/agent/prompts/index.d.ts.map +1 -0
  20. package/dist/agent/prompts/index.js +668 -0
  21. package/dist/agent/prompts/index.js.map +1 -0
  22. package/dist/agent/prompts/nextjs.d.ts +12 -0
  23. package/dist/agent/prompts/nextjs.d.ts.map +1 -0
  24. package/dist/agent/prompts/nextjs.js +138 -0
  25. package/dist/agent/prompts/nextjs.js.map +1 -0
  26. package/dist/agent/prompts/react-native.d.ts +4 -0
  27. package/dist/agent/prompts/react-native.d.ts.map +1 -0
  28. package/dist/agent/prompts/react-native.js +82 -0
  29. package/dist/agent/prompts/react-native.js.map +1 -0
  30. package/dist/agent/prompts/react.d.ts +2 -0
  31. package/dist/agent/prompts/react.d.ts.map +1 -0
  32. package/dist/agent/prompts/react.js +48 -0
  33. package/dist/agent/prompts/react.js.map +1 -0
  34. package/dist/agent/prompts/runners/js-common.d.ts +2 -0
  35. package/dist/agent/prompts/runners/js-common.d.ts.map +1 -0
  36. package/dist/agent/prompts/runners/js-common.js +13 -0
  37. package/dist/agent/prompts/runners/js-common.js.map +1 -0
  38. package/dist/agent/prompts/runners/typescript.d.ts +2 -0
  39. package/dist/agent/prompts/runners/typescript.d.ts.map +1 -0
  40. package/dist/agent/prompts/runners/typescript.js +12 -0
  41. package/dist/agent/prompts/runners/typescript.js.map +1 -0
  42. package/dist/agent/prompts/runners/vitest.d.ts +2 -0
  43. package/dist/agent/prompts/runners/vitest.d.ts.map +1 -0
  44. package/dist/agent/prompts/runners/vitest.js +23 -0
  45. package/dist/agent/prompts/runners/vitest.js.map +1 -0
  46. package/dist/agent/prompts/vue.d.ts +3 -0
  47. package/dist/agent/prompts/vue.d.ts.map +1 -0
  48. package/dist/agent/prompts/vue.js +29 -0
  49. package/dist/agent/prompts/vue.js.map +1 -0
  50. package/dist/commands/analyze.d.ts.map +1 -1
  51. package/dist/commands/analyze.js +43 -32
  52. package/dist/commands/analyze.js.map +1 -1
  53. package/dist/commands/fix.d.ts +2 -0
  54. package/dist/commands/fix.d.ts.map +1 -1
  55. package/dist/commands/fix.js +32 -3
  56. package/dist/commands/fix.js.map +1 -1
  57. package/dist/commands/init.d.ts.map +1 -1
  58. package/dist/commands/init.js +208 -32
  59. package/dist/commands/init.js.map +1 -1
  60. package/dist/lib/config.d.ts +3 -3
  61. package/dist/lib/config.d.ts.map +1 -1
  62. package/dist/lib/config.js +3 -1
  63. package/dist/lib/config.js.map +1 -1
  64. package/dist/lib/coverage/gaps.d.ts +2 -2
  65. package/dist/lib/coverage/gaps.d.ts.map +1 -1
  66. package/dist/lib/coverage/gaps.js +35 -5
  67. package/dist/lib/coverage/gaps.js.map +1 -1
  68. package/dist/lib/coverage/index.d.ts +1 -1
  69. package/dist/lib/coverage/index.d.ts.map +1 -1
  70. package/dist/lib/coverage/index.js +1 -1
  71. package/dist/lib/coverage/index.js.map +1 -1
  72. package/dist/lib/detector.d.ts +1 -0
  73. package/dist/lib/detector.d.ts.map +1 -1
  74. package/dist/lib/detector.js +41 -8
  75. package/dist/lib/detector.js.map +1 -1
  76. package/dist/lib/providers/anthropic.d.ts.map +1 -1
  77. package/dist/lib/providers/anthropic.js +46 -3
  78. package/dist/lib/providers/anthropic.js.map +1 -1
  79. package/dist/lib/providers/openai-compatible.d.ts +1 -1
  80. package/dist/lib/providers/openai-compatible.d.ts.map +1 -1
  81. package/dist/lib/providers/openai-compatible.js +43 -2
  82. package/dist/lib/providers/openai-compatible.js.map +1 -1
  83. package/dist/lib/providers/types.d.ts +4 -0
  84. package/dist/lib/providers/types.d.ts.map +1 -1
  85. package/dist/lib/providers/types.js +10 -0
  86. package/dist/lib/providers/types.js.map +1 -1
  87. package/dist/lib/skeleton.d.ts +4 -0
  88. package/dist/lib/skeleton.d.ts.map +1 -1
  89. package/dist/lib/skeleton.js +220 -0
  90. package/dist/lib/skeleton.js.map +1 -1
  91. package/dist/lib/validate.d.ts +1 -0
  92. package/dist/lib/validate.d.ts.map +1 -1
  93. package/dist/lib/validate.js +179 -15
  94. package/dist/lib/validate.js.map +1 -1
  95. package/dist/lib/worker-display.d.ts +10 -0
  96. package/dist/lib/worker-display.d.ts.map +1 -1
  97. package/dist/lib/worker-display.js +53 -5
  98. package/dist/lib/worker-display.js.map +1 -1
  99. package/oclif.manifest.json +16 -2
  100. package/package.json +1 -1
  101. package/dist/agent/prompts.d.ts.map +0 -1
  102. package/dist/agent/prompts.js +0 -632
  103. package/dist/agent/prompts.js.map +0 -1
  104. package/dist/lib/report-upload.d.ts +0 -3
  105. package/dist/lib/report-upload.d.ts.map +0 -1
  106. package/dist/lib/report-upload.js +0 -15
  107. package/dist/lib/report-upload.js.map +0 -1
@@ -5,8 +5,9 @@ import { loadConfig } from '../lib/config.js';
5
5
  import { detectEnvironment } from '../lib/detector.js';
6
6
  import { runCommand } from '../lib/runner.js';
7
7
  import { startCoverageSpinner } from '../lib/coverage-spinner.js';
8
- import { loadCoverage, extractGaps, filterTestableGaps, findUncoveredFiles } from '../lib/coverage/index.js';
8
+ import { loadCoverage, extractGaps, filterTestableGaps, findUncoveredFiles, findTestFiles } from '../lib/coverage/index.js';
9
9
  import { reportTerminal, buildJsonReport, buildMarkdownReport, getExitCode } from '../lib/reporter.js';
10
+ const EMPTY_REPORT = { files: [], totalLineRate: 0, totalFunctionRate: 0 };
10
11
  export default class Analyze extends Command {
11
12
  static description = 'Analyze test coverage and show gaps — no files are changed';
12
13
  static examples = [
@@ -52,38 +53,48 @@ export default class Analyze extends Command {
52
53
  this.log(`${chalk.dim('Detected:')} ${chalk.cyan(env.testRunner)} (${env.language})`);
53
54
  this.log(`${chalk.dim('Threshold:')} ${threshold}%\n`);
54
55
  }
55
- const spinner = startCoverageSpinner(chalk.dim(` Running: ${env.coverageCommand}`), env.testRunner);
56
- const result = await runCommand(env.coverageCommand, process.cwd(), config.coverageTimeout * 1000, spinner.onLine);
57
- spinner.stop();
58
- if (result.timedOut) {
59
- this.log(chalk.red(`\nTest suite timed out after ${config.coverageTimeout}s.`));
60
- this.log(chalk.yellow('\nThis usually means a test has an open handle (unclosed server, timer, or connection).'));
61
- this.log(chalk.dim(`\nIncrease the timeout in .lacuna.json: { "coverageTimeout": ${config.coverageTimeout * 2} }`));
62
- this.exit(2);
63
- }
64
- // only bail if literally zero tests ran (suites crashed on load)
65
- const zeroTests = /Tests:\s+0 total|no tests found/i.test(result.stdout + result.stderr);
66
- if (zeroTests) {
67
- this.log(chalk.red('\nYour test suites are failing before any tests run.'));
68
- this.log(chalk.yellow('\nThis usually means:'));
69
- this.log(' • A missing environment variable (check .env / .env.test)');
70
- this.log(' • A broken import or missing module');
71
- this.log(' • A setup file failing (DB connection, mock config, etc.)\n');
72
- this.log(chalk.dim('Run this to see the actual error:'));
73
- this.log(chalk.cyan(` ${env.testCommand} 2>&1 | head -80`));
74
- this.exit(2);
75
- }
76
- // partial failures are fine — coverage is still collected for passing tests
77
- let report;
78
- try {
79
- report = await loadCoverage(config);
56
+ // Check if there are any test files before running the coverage command
57
+ const existingTests = await findTestFiles(process.cwd(), {}, config);
58
+ const hasTests = existingTests.length > 0;
59
+ let report = EMPTY_REPORT;
60
+ if (!hasTests) {
61
+ if (flags.format === 'terminal') {
62
+ this.log(chalk.dim(' No test files yet scanning source files for coverage gaps.\n'));
63
+ }
80
64
  }
81
- catch {
82
- this.log(chalk.red(`Could not read coverage report from ./${config.coverageDir}/\n`));
83
- this.log(chalk.yellow('Make sure your vitest config has coverage enabled:'));
84
- this.log(chalk.dim(' // vitest.config.ts'));
85
- this.log(chalk.dim(' test: { coverage: { reporter: ["lcov", "text-summary"] } }'));
86
- this.exit(2);
65
+ else {
66
+ const spinner = startCoverageSpinner(chalk.dim(` Running: ${env.coverageCommand}`), env.testRunner);
67
+ const result = await runCommand(env.coverageCommand, process.cwd(), config.coverageTimeout * 1000, spinner.onLine);
68
+ spinner.stop();
69
+ if (result.timedOut) {
70
+ this.log(chalk.red(`\nTest suite timed out after ${config.coverageTimeout}s.`));
71
+ this.log(chalk.yellow('\nThis usually means a test has an open handle (unclosed server, timer, or connection).'));
72
+ this.log(chalk.dim(`\nIncrease the timeout in .lacuna.json: { "coverageTimeout": ${config.coverageTimeout * 2} }`));
73
+ this.exit(2);
74
+ }
75
+ // bail if suites crashed on load (test files exist but zero tests ran)
76
+ const combined = result.stdout + result.stderr;
77
+ if (/Tests:\s+0 total/i.test(combined)) {
78
+ this.log(chalk.red('\nYour test suites are failing before any tests run.'));
79
+ this.log(chalk.yellow('\nThis usually means:'));
80
+ this.log(' • A missing environment variable (check .env / .env.test)');
81
+ this.log(' • A broken import or missing module');
82
+ this.log(' • A setup file failing (DB connection, mock config, etc.)\n');
83
+ this.log(chalk.dim('Run this to see the actual error:'));
84
+ this.log(chalk.cyan(` ${env.testCommand} 2>&1 | head -80`));
85
+ this.exit(2);
86
+ }
87
+ // partial failures are fine — coverage is still collected for passing tests
88
+ try {
89
+ report = await loadCoverage(config);
90
+ }
91
+ catch {
92
+ this.log(chalk.red(`Could not read coverage report from ./${config.coverageDir}/\n`));
93
+ this.log(chalk.yellow('Make sure your vitest config has coverage enabled:'));
94
+ this.log(chalk.dim(' // vitest.config.ts'));
95
+ this.log(chalk.dim(' test: { coverage: { reporter: ["lcov", "text-summary"] } }'));
96
+ this.exit(2);
97
+ }
87
98
  }
88
99
  const gaps = await filterTestableGaps(extractGaps(report, threshold), config.ignore);
89
100
  // append files that never appeared in the coverage report (never imported by any test)
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC3H,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAItG,MAAM,YAAY,GAAmB,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAA;AAE1F,MAAM,CAAC,OAAO,OAAO,OAAQ,SAAQ,OAAO;IAC1C,MAAM,CAAC,WAAW,GAAG,4DAA4D,CAAA;IAEjF,MAAM,CAAC,QAAQ,GAAG;QAChB,kBAAkB;QAClB,iCAAiC;QACjC,gCAAgC;QAChC,oCAAoC;KACrC,CAAA;IAED,MAAM,CAAC,KAAK,GAAG;QACb,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC;YACvB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,6BAA6B;SAC3C,CAAC;QACF,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,eAAe;YAC5B,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC;YACzC,OAAO,EAAE,UAAU;SACpB,CAAC;QACF,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,wCAAwC;SACtD,CAAC;QACF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,GAAG;YACT,WAAW,EAAE,sCAAsC;YACnD,OAAO,EAAE,KAAK;SACf,CAAC;KACH,CAAA;IAED,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC3C,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAA;QACjC,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;QACrE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CAAA;QAErD,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAA;QAC5C,CAAC;QAED,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAA;YAC1E,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACd,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAA;YACtF,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,KAAK,CAAC,CAAA;QACxD,CAAC;QAED,wEAAwE;QACxE,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;QACpE,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAA;QAEzC,IAAI,MAAM,GAAmB,YAAY,CAAA;QAEzC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAChC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC,CAAA;YACzF,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,GAAG,CAAC,eAAe,EAAE,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAA;YACpG,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,eAAe,GAAG,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;YAClH,OAAO,CAAC,IAAI,EAAE,CAAA;YAEd,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACpB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gCAAgC,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC,CAAA;gBAC/E,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,yFAAyF,CAAC,CAAC,CAAA;gBACjH,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gEAAgE,MAAM,CAAC,eAAe,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;gBACnH,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACd,CAAC;YAED,uEAAuE;YACvE,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;YAC9C,IAAI,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC,CAAA;gBAC3E,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAA;gBAC/C,IAAI,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAA;gBACvE,IAAI,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAA;gBACjD,IAAI,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAA;gBACzE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC,CAAA;gBACxD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,WAAW,kBAAkB,CAAC,CAAC,CAAA;gBAC5D,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACd,CAAC;YACD,4EAA4E;YAE5E,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yCAAyC,MAAM,CAAC,WAAW,KAAK,CAAC,CAAC,CAAA;gBACrF,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,oDAAoD,CAAC,CAAC,CAAA;gBAC5E,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAA;gBAC5C,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC,CAAA;gBACnF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACd,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QAEpF,uFAAuF;QACvF,MAAM,cAAc,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QACvG,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;QAC1D,IAAI,cAAc,GAAG,CAAC,CAAA;QACtB,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;YAC/B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;gBACZ,cAAc,EAAE,CAAA;YAClB,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,GAAG,GAAG,CAAA;QAC9C,MAAM,mBAAmB,GAAG,MAAM,CAAC,iBAAiB,GAAG,GAAG,CAAA;QAC1D,kFAAkF;QAClF,iFAAiF;QACjF,MAAM,MAAM,GAAG,WAAW,IAAI,SAAS,IAAI,cAAc,KAAK,CAAC,CAAA;QAE/D,MAAM,KAAK,GAAgB;YACzB,IAAI,EAAE,SAAS;YACf,SAAS;YACT,cAAc;YACd,OAAO,EAAE;gBACP,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,SAAS;gBACT,WAAW;gBACX,mBAAmB;gBACnB,IAAI;gBACJ,cAAc;gBACd,MAAM;aACP;SACF,CAAA;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;YAC3D,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjB,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;gBAC3C,IAAI,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;YAC/C,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACf,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;YACtC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjB,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAA;gBAC3C,IAAI,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;YAC/C,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACf,CAAC;QACH,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,KAAK,CAAC,CAAA;YACrB,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACvB,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAClC,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC,CAAA;wBAC3D,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,WAAW,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;oBACpI,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IAC/B,CAAC"}
@@ -9,6 +9,8 @@ export default class Fix extends Command {
9
9
  model: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  workers: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
11
  fresh: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ 'regenerate-on-failure': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ 'fix-polluters': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
14
  };
13
15
  run(): Promise<void>;
14
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fix.d.ts","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAA;AAM5C,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,OAAO;IACtC,MAAM,CAAC,WAAW,SAAgG;IAElH,MAAM,CAAC,QAAQ,WAKd;IAED,MAAM,CAAC,KAAK;;;;;;;MA2BX;IAEK,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAiE3B"}
1
+ {"version":3,"file":"fix.d.ts","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAA;AAM5C,MAAM,CAAC,OAAO,OAAO,GAAI,SAAQ,OAAO;IACtC,MAAM,CAAC,WAAW,SAAgG;IAElH,MAAM,CAAC,QAAQ,WAOd;IAED,MAAM,CAAC,KAAK;;;;;;;;;MAoCX;IAEK,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAqF3B"}
@@ -10,6 +10,8 @@ export default class Fix extends Command {
10
10
  '$ lacuna fix --workers 4',
11
11
  '$ lacuna fix --file src/utils/math.test.ts',
12
12
  '$ lacuna fix --dry-run',
13
+ '$ lacuna fix --regenerate-on-failure',
14
+ '$ lacuna fix --fix-polluters',
13
15
  ];
14
16
  static flags = {
15
17
  'dry-run': Flags.boolean({
@@ -38,6 +40,15 @@ export default class Fix extends Command {
38
40
  description: 'Re-run the full test suite even if a recent failing-files cache exists',
39
41
  default: false,
40
42
  }),
43
+ 'regenerate-on-failure': Flags.boolean({
44
+ description: 'If fix exhausts all retries, delete the test and regenerate it from scratch (default: on). Use --no-regenerate-on-failure to disable.',
45
+ default: true,
46
+ allowNo: true,
47
+ }),
48
+ 'fix-polluters': Flags.boolean({
49
+ description: 'After fixing, bisect the test suite to identify files that corrupt shared state, then use AI to add cleanup',
50
+ default: false,
51
+ }),
41
52
  };
42
53
  async run() {
43
54
  const { flags } = await this.parse(Fix);
@@ -69,6 +80,8 @@ export default class Fix extends Command {
69
80
  targetFile: flags.file,
70
81
  workers: flags.workers,
71
82
  fresh: flags.fresh,
83
+ regenerateOnFailure: flags['regenerate-on-failure'],
84
+ fixPolluters: flags['fix-polluters'],
72
85
  log: (msg) => this.log(msg),
73
86
  });
74
87
  }
@@ -79,21 +92,37 @@ export default class Fix extends Command {
79
92
  this.log(chalk.bold('Results'));
80
93
  this.log(` ${chalk.dim('Files processed:')} ${result.filesProcessed}`);
81
94
  this.log(` ${chalk.dim('Files fixed:')} ${chalk.green(String(result.filesFixed))}`);
82
- const stillFailing = result.filesProcessed - result.filesFixed;
95
+ if (result.filesAlreadyPassing > 0) {
96
+ this.log(` ${chalk.dim('Already passing:')} ${chalk.dim(String(result.filesAlreadyPassing))}`);
97
+ }
98
+ if (result.pollutersFixed > 0) {
99
+ this.log(` ${chalk.dim('Polluters fixed:')} ${chalk.green(String(result.pollutersFixed))}`);
100
+ }
101
+ if (result.victimsRegenerated > 0) {
102
+ this.log(` ${chalk.dim('Victims regen:')} ${chalk.green(String(result.victimsRegenerated))}`);
103
+ }
104
+ const stillFailing = result.filesProcessed - result.filesFixed - result.filesAlreadyPassing;
83
105
  if (stillFailing > 0) {
84
106
  this.log(` ${chalk.dim('Still failing:')} ${chalk.red(String(stillFailing))}`);
85
107
  }
108
+ if (result.filesAlreadyPassing > 0 && !flags['fix-polluters']) {
109
+ this.log(chalk.dim(`\n ${result.filesAlreadyPassing} file(s) passed in isolation but fail in the suite. Use --fix-polluters to bisect + regenerate them.`));
110
+ }
86
111
  if (result.errors.length > 0) {
87
112
  this.log(chalk.red(`\n ${result.errors.length} error(s):`));
88
113
  for (const err of result.errors) {
89
- const lines = err.split('\n').slice(0, 8);
114
+ const lines = err.split('\n').slice(0, 15);
90
115
  this.log(chalk.dim(' ' + lines.join('\n ')));
91
116
  }
92
117
  }
93
118
  if (result.filesProcessed === 0) {
94
119
  this.exit(0);
95
120
  }
96
- else if (result.filesFixed === result.filesProcessed) {
121
+ else if (stillFailing === 0) {
122
+ if (result.filesAlreadyPassing > 0 && result.filesFixed === 0) {
123
+ this.log(chalk.yellow(`\n No tests were repaired — all skipped as already passing. Run lacuna fix --fresh to re-scan.`));
124
+ this.exit(1);
125
+ }
97
126
  this.log(chalk.green('\n All failing tests fixed.'));
98
127
  this.exit(0);
99
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAkcrC,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,OAAO;IACvC,MAAM,CAAC,WAAW,SAAiE;IACnF,MAAM,CAAC,QAAQ,WAAoB;IAE7B,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA2K3B"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAwnBrC,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,OAAO;IACvC,MAAM,CAAC,WAAW,SAAiE;IACnF,MAAM,CAAC,QAAQ,WAAoB;IAE7B,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CA2K3B"}
@@ -26,9 +26,8 @@ async function readProjectMeta(cwd) {
26
26
  return { isReact: false, isReactNative: false, isExpo: false, isNextJs: false, isTypeScript: false, isVue: false, isAngular: false, isSvelte: false, isNestJs: false };
27
27
  }
28
28
  }
29
- async function isPackageInstalled(pkg, cwd) {
30
- // Check all dependency fields in package.json (covers workspaces where the
31
- // package is declared in the root manifest but installed at workspace level)
29
+ async function checkPackageInstallState(pkg, cwd) {
30
+ // Check package.json first the source of truth for declared dependencies
32
31
  try {
33
32
  const json = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf-8'));
34
33
  const all = {
@@ -38,17 +37,46 @@ async function isPackageInstalled(pkg, cwd) {
38
37
  ...json['optionalDependencies'],
39
38
  };
40
39
  if (pkg in all)
41
- return true;
40
+ return 'declared';
42
41
  }
43
- catch { /* fall through to node_modules check */ }
44
- // Fallback: check if the package is present in node_modules (pnpm workspaces,
45
- // hoisted installs, or cases where the root manifest differs from what's installed)
42
+ catch { /* fall through */ }
43
+ // Check node_modules present but undeclared means it was installed on a different
44
+ // branch or manually, and won't survive a fresh CI checkout
46
45
  try {
47
46
  await access(join(cwd, 'node_modules', pkg));
48
- return true;
47
+ return 'undeclared';
49
48
  }
50
49
  catch { /* not found */ }
51
- return false;
50
+ return 'missing';
51
+ }
52
+ // Convenience wrapper used for checking individual extra packages (setupFilePackages)
53
+ async function isPackageInstalled(pkg, cwd) {
54
+ return (await checkPackageInstallState(pkg, cwd)) !== 'missing';
55
+ }
56
+ // Resolves the jest version range that a preset (e.g. jest-expo) declares as its
57
+ // peer dependency, so we install a compatible jest rather than whatever is latest.
58
+ // Falls back to bare 'jest' if the lookup fails (offline, registry unavailable, etc).
59
+ function resolveJestVersionForPreset(preset, cwd) {
60
+ // 1. Check if preset is already installed locally — no network needed
61
+ try {
62
+ const localPkg = JSON.parse(execSync(`cat node_modules/${preset}/package.json 2>/dev/null`, { cwd, encoding: 'utf-8', stdio: 'pipe' }));
63
+ const range = localPkg?.peerDependencies?.jest;
64
+ const major = range?.match(/\d+/)?.[0];
65
+ if (major)
66
+ return { pkg: `jest@${major}`, warned: false };
67
+ }
68
+ catch { /* not installed yet */ }
69
+ // 2. Fall back to npm registry lookup
70
+ try {
71
+ const out = execSync(`npm info ${preset} peerDependencies.jest 2>/dev/null`, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
72
+ const cleaned = out.replace(/^['"]|['"]$/g, '');
73
+ const major = cleaned.match(/\d+/)?.[0];
74
+ if (major)
75
+ return { pkg: `jest@${major}`, warned: false };
76
+ }
77
+ catch { /* registry unreachable */ }
78
+ // 3. Could not resolve — warn so the user knows to check manually
79
+ return { pkg: 'jest', warned: true };
52
80
  }
53
81
  async function writeFileWithDir(filePath, content) {
54
82
  await mkdir(dirname(filePath), { recursive: true });
@@ -91,34 +119,121 @@ async function findProjectRoot(startDir) {
91
119
  }
92
120
  }
93
121
  }
94
- function buildSetupFileContent(variant) {
122
+ function buildSetupFileContent(variant, runner, isExpo = false) {
123
+ // Mock cleanup — prevents spy state from leaking across tests and test files.
124
+ // beforeEach: restores any globalThis spies left by previous files in the same worker
125
+ // (works in concert with restoreMocks: true in vitest.config.ts)
126
+ // afterEach: belt-and-suspenders cleanup within the file
127
+ const vitestCleanup = [
128
+ ``,
129
+ `// ── Mock cleanup ──────────────────────────────────────────────────────────`,
130
+ `// restoreMocks/clearMocks in vitest.config.ts handle this automatically,`,
131
+ `// but explicit hooks here guard against any gaps.`,
132
+ `// vi is available globally (globals: true in vitest.config.ts).`,
133
+ ``,
134
+ `beforeEach(() => {`,
135
+ ` vi.restoreAllMocks()`,
136
+ `})`,
137
+ ``,
138
+ `afterEach(() => {`,
139
+ ` vi.restoreAllMocks()`,
140
+ ` vi.clearAllMocks()`,
141
+ `})`,
142
+ ].join('\n');
143
+ const jestCleanup = [
144
+ ``,
145
+ `// ── Mock cleanup ──────────────────────────────────────────────────────────`,
146
+ `// Runs after every test to prevent mock state leaking across test files.`,
147
+ `afterEach(() => {`,
148
+ ` jest.restoreAllMocks()`,
149
+ ` jest.clearAllMocks()`,
150
+ `})`,
151
+ ].join('\n');
152
+ const cleanup = runner === 'vitest' ? vitestCleanup : jestCleanup;
95
153
  if (variant === 'react-native') {
154
+ const expoMocks = isExpo ? [
155
+ ``,
156
+ `// ── Expo module mocks ─────────────────────────────────────────────────────`,
157
+ `jest.mock('expo-constants', () => ({`,
158
+ ` default: { expoConfig: { name: 'App', slug: 'app' } },`,
159
+ `}))`,
160
+ ``,
161
+ `jest.mock('expo-router', () => ({`,
162
+ ` useRouter: jest.fn(() => ({ push: jest.fn(), replace: jest.fn(), back: jest.fn() })),`,
163
+ ` useLocalSearchParams: jest.fn(() => ({})),`,
164
+ ` usePathname: jest.fn(() => '/'),`,
165
+ ` useSegments: jest.fn(() => []),`,
166
+ ` Link: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
167
+ ` router: { push: jest.fn(), replace: jest.fn(), back: jest.fn() },`,
168
+ `}))`,
169
+ ``,
170
+ `jest.mock('expo-status-bar', () => ({`,
171
+ ` StatusBar: jest.fn(() => null),`,
172
+ `}))`,
173
+ ] : [];
96
174
  return [
97
175
  `// React Native / Expo test setup`,
98
- `// @testing-library/react-native matchers`,
99
- `import '@testing-library/react-native/extend-expect'`,
176
+ `import React from 'react'`,
177
+ ``,
178
+ `// ── Native module mocks ───────────────────────────────────────────────────`,
179
+ `// These modules rely on native code that is unavailable in the Jest environment.`,
100
180
  ``,
101
- `import { jest } from '@jest/globals'`,
181
+ `// Safe area context provides insets/frame for components that use useSafeAreaInsets`,
182
+ `jest.mock('react-native-safe-area-context', () => ({`,
183
+ ` SafeAreaProvider: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
184
+ ` SafeAreaView: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
185
+ ` useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0, left: 0, right: 0 })),`,
186
+ ` useSafeAreaFrame: jest.fn(() => ({ x: 0, y: 0, width: 390, height: 844 })),`,
187
+ `}))`,
102
188
  ``,
103
- `// Silence the native animated warning in tests`,
104
- `jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper')`,
189
+ `// Gesture handler required by react-navigation and many UI libraries`,
190
+ `jest.mock('react-native-gesture-handler', () => {`,
191
+ ` const RN = jest.requireActual('react-native')`,
192
+ ` return {`,
193
+ ` ...RN,`,
194
+ ` GestureHandlerRootView: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
195
+ ` PanGestureHandler: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
196
+ ` TapGestureHandler: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
197
+ ` Swipeable: jest.fn(({ children }: { children: React.ReactNode }) => children),`,
198
+ ` }`,
199
+ `})`,
200
+ ``,
201
+ `// AsyncStorage — native async key-value store`,
202
+ `jest.mock('@react-native-async-storage/async-storage', () =>`,
203
+ ` jest.requireActual('@react-native-async-storage/async-storage/jest/async-storage-mock')`,
204
+ `)`,
205
+ ``,
206
+ `// React Navigation — mock the navigation hooks to avoid needing a real NavigationContainer`,
207
+ `jest.mock('@react-navigation/native', () => ({`,
208
+ ` ...jest.requireActual('@react-navigation/native'),`,
209
+ ` useNavigation: jest.fn(() => ({ navigate: jest.fn(), goBack: jest.fn(), push: jest.fn(), replace: jest.fn() })),`,
210
+ ` useRoute: jest.fn(() => ({ params: {} })),`,
211
+ ` useFocusEffect: jest.fn((cb: () => void) => cb()),`,
212
+ ` useIsFocused: jest.fn(() => true),`,
213
+ `}))`,
214
+ ...expoMocks,
215
+ jestCleanup,
105
216
  ].join('\n') + '\n';
106
217
  }
107
218
  if (variant === 'angular') {
108
- return `import 'jest-preset-angular/setup-jest'\n`;
219
+ return `import 'jest-preset-angular/setup-jest'\n` + jestCleanup + '\n';
109
220
  }
110
221
  if (variant === 'nest') {
111
- return `// NestJS test setup — no DOM environment needed\n`;
222
+ return `// NestJS test setup — no DOM environment needed\n` + jestCleanup + '\n';
112
223
  }
113
224
  if (variant === 'vue') {
114
- return `import '@testing-library/jest-dom'\n`;
225
+ return `import '@testing-library/jest-dom'\n` + cleanup + '\n';
115
226
  }
116
227
  if (variant === 'svelte') {
117
- return `import '@testing-library/jest-dom'\n`;
228
+ return `import '@testing-library/jest-dom'\n` + cleanup + '\n';
118
229
  }
119
230
  const lines = [`import '@testing-library/jest-dom'`];
120
231
  if (variant === 'nextjs') {
121
- lines.push(``, `// ── Next.js global mocks ──────────────────────────────────────────────────`, `// These run before every test so individual test files don't need to mock them.`, ``, `import { vi } from 'vitest'`, ``, `// next/navigation — useRouter, usePathname, etc. are server-side and fail in jsdom`, `vi.mock('next/navigation', () => ({`, ` useRouter: vi.fn(() => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn(), forward: vi.fn(), prefetch: vi.fn() })),`, ` usePathname: vi.fn(() => '/'),`, ` useSearchParams: vi.fn(() => new URLSearchParams()),`, ` useParams: vi.fn(() => ({})),`, ` redirect: vi.fn(),`, ` notFound: vi.fn(),`, `}))`, ``, `// next/headers — server-only, throws in jsdom`, `vi.mock('next/headers', () => ({`, ` cookies: vi.fn(() => ({ get: vi.fn(), set: vi.fn(), delete: vi.fn(), has: vi.fn(), getAll: vi.fn(() => []) })),`, ` headers: vi.fn(() => new Headers()),`, `}))`, ``, `// next/cache — no-ops in tests`, `vi.mock('next/cache', () => ({`, ` revalidatePath: vi.fn(),`, ` revalidateTag: vi.fn(),`, ` unstable_cache: vi.fn((fn: () => unknown) => fn),`, `}))`, ``, `// next/image — uses Next.js image optimization which breaks in jsdom`, `vi.mock('next/image', () => ({`, ` default: vi.fn(({ src, alt, ...props }: Record<string, unknown>) => null),`, `}))`, ``, `// next/font — font loading tries to fetch/read files at import time, fails in tests`, `// Add any fonts your project uses that aren't listed here`, `vi.mock('next/font/google', () => new Proxy({}, {`, ` get: (_: object, fontName: string) =>`, ` () => ({ className: \`font-\${fontName.toLowerCase()}\`, style: { fontFamily: fontName } }),`, `}))`, `vi.mock('next/font/local', () => ({`, ` default: vi.fn(() => ({ className: 'font-local', style: { fontFamily: 'local' } })),`, `}))`);
232
+ lines.push(``, `// ── Next.js global mocks ──────────────────────────────────────────────────`, `// These run before every test so individual test files don't need to mock them.`, ``, `import { vi, beforeEach, afterEach } from 'vitest'`, ``, `// next/navigation — useRouter, usePathname, etc. are server-side and fail in jsdom`, `vi.mock('next/navigation', () => ({`, ` useRouter: vi.fn(() => ({ push: vi.fn(), replace: vi.fn(), back: vi.fn(), forward: vi.fn(), prefetch: vi.fn() })),`, ` usePathname: vi.fn(() => '/'),`, ` useSearchParams: vi.fn(() => new URLSearchParams()),`, ` useParams: vi.fn(() => ({})),`, ` redirect: vi.fn(),`, ` notFound: vi.fn(),`, `}))`, ``, `// next/headers — server-only, throws in jsdom`, `vi.mock('next/headers', () => ({`, ` cookies: vi.fn(() => ({ get: vi.fn(), set: vi.fn(), delete: vi.fn(), has: vi.fn(), getAll: vi.fn(() => []) })),`, ` headers: vi.fn(() => new Headers()),`, `}))`, ``, `// next/cache — no-ops in tests`, `vi.mock('next/cache', () => ({`, ` revalidatePath: vi.fn(),`, ` revalidateTag: vi.fn(),`, ` unstable_cache: vi.fn((fn: () => unknown) => fn),`, `}))`, ``, `// next/image — uses Next.js image optimization which breaks in jsdom`, `vi.mock('next/image', () => ({`, ` default: vi.fn(({ src, alt, ...props }: Record<string, unknown>) => null),`, `}))`, ``, `// next/font — font loading tries to fetch/read files at import time, fails in tests`, `// Add any fonts your project uses that aren't listed here`, `vi.mock('next/font/google', () => new Proxy({}, {`, ` get: (_: object, fontName: string) =>`, ` () => ({ className: \`font-\${fontName.toLowerCase()}\`, style: { fontFamily: fontName } }),`, `}))`, `vi.mock('next/font/local', () => ({`, ` default: vi.fn(() => ({ className: 'font-local', style: { fontFamily: 'local' } })),`, `}))`, vitestCleanup);
233
+ }
234
+ else {
235
+ // plain react
236
+ lines.push(cleanup);
122
237
  }
123
238
  return lines.join('\n') + '\n';
124
239
  }
@@ -146,7 +261,7 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
146
261
  return undefined;
147
262
  }
148
263
  const meta = await readProjectMeta(cwd);
149
- const alreadyInstalled = await isPackageInstalled(runner, cwd);
264
+ const installState = await checkPackageInstallState(runner, cwd);
150
265
  // ── Determine packages to install ─────────────────────────────────────────
151
266
  const basePackages = [];
152
267
  const setupFilePackages = [];
@@ -173,11 +288,22 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
173
288
  // @vitejs/plugin-react is NOT needed for Vitest — esbuild handles JSX/TSX natively.
174
289
  }
175
290
  else if (runner === 'jest') {
176
- if (meta.isTypeScript) {
291
+ if (meta.isReactNative) {
292
+ // Use the jest version that the preset actually supports — resolved at init time
293
+ // so this stays correct when jest-expo bumps its peer dep in the future.
294
+ const rnPreset = meta.isExpo ? 'jest-expo' : '@react-native/jest-preset';
295
+ const { pkg: jestPkg, warned } = resolveJestVersionForPreset(rnPreset, cwd);
296
+ if (warned) {
297
+ log(chalk.yellow(` ⚠ Could not resolve compatible jest version for ${rnPreset}. Installing latest — if tests fail with version mismatch errors, pin jest to the version in ${rnPreset}'s peerDependencies.`));
298
+ }
299
+ const jestMajor = jestPkg.includes('@') ? jestPkg.split('@')[1] : '';
300
+ basePackages.push(jestPkg, jestMajor ? `@types/jest@${jestMajor}` : '@types/jest');
301
+ }
302
+ else if (meta.isTypeScript) {
177
303
  basePackages.push('jest', '@types/jest', 'ts-jest');
178
304
  }
179
305
  else {
180
- basePackages.push('jest');
306
+ basePackages.push('jest', '@types/jest');
181
307
  }
182
308
  if (meta.isReactNative) {
183
309
  // Don't add jest-environment-jsdom for RN
@@ -227,7 +353,7 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
227
353
  return undefined;
228
354
  })();
229
355
  // ── Install missing packages ───────────────────────────────────────────────
230
- if (!alreadyInstalled) {
356
+ if (installState === 'missing') {
231
357
  const allPackages = [...basePackages, ...setupFilePackages];
232
358
  log(chalk.yellow(`\n ${runner} is not installed.`));
233
359
  log(chalk.dim(` Packages: ${allPackages.join(', ')}`));
@@ -245,6 +371,30 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
245
371
  }
246
372
  }
247
373
  }
374
+ else if (installState === 'undeclared') {
375
+ // Package exists in node_modules but is NOT declared in package.json.
376
+ // This usually means it was installed on a different branch and won't survive
377
+ // a fresh CI checkout — node_modules is not committed to git.
378
+ const allPackages = [...basePackages, ...setupFilePackages];
379
+ log(chalk.yellow(`\n ${runner} was found in node_modules but is not declared in package.json.`));
380
+ log(chalk.dim(` This works locally but will break CI — a fresh checkout won't have node_modules.`));
381
+ const doAdd = await confirm({
382
+ message: `Add ${allPackages.join(', ')} to package.json? (recommended for CI)`,
383
+ default: true,
384
+ });
385
+ if (!doAdd) {
386
+ log(chalk.dim(` Skipped. Add manually: npm install -D ${allPackages.join(' ')}`));
387
+ }
388
+ else {
389
+ log(chalk.dim(`\n Adding to package.json...`));
390
+ try {
391
+ execSync(`npm install -D ${allPackages.join(' ')}`, { cwd, stdio: 'inherit' });
392
+ }
393
+ catch {
394
+ log(chalk.red(` Failed. Run manually: npm install -D ${allPackages.join(' ')}`));
395
+ }
396
+ }
397
+ }
248
398
  else if ((meta.isNextJs || meta.isReact) && setupFilePackages.length > 0) {
249
399
  // Runner is installed — check if the extra testing-library packages are present.
250
400
  // Must use a for-loop: Array.filter ignores async callbacks (the Promise is always truthy).
@@ -280,11 +430,23 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
280
430
  : meta.isVue ? 'vue'
281
431
  : meta.isSvelte ? 'svelte'
282
432
  : 'react';
283
- const setupContent = buildSetupFileContent(setupVariant);
433
+ const setupContent = buildSetupFileContent(setupVariant, runner, meta.isExpo);
284
434
  await writeFileWithDir(absSetup, setupContent);
285
435
  log(chalk.green(` ✓ Created ${setupFilePath}`));
286
- if (meta.isNextJs)
436
+ if (meta.isNextJs) {
287
437
  log(chalk.dim(` Includes global mocks for next/navigation, next/headers, next/cache`));
438
+ // Create the empty module that the server-only alias points to.
439
+ // Without this file, Vitest crashes when any source file imports 'server-only'.
440
+ const emptyModulePath = resolve(cwd, 'test/empty-module.ts');
441
+ try {
442
+ await access(emptyModulePath);
443
+ }
444
+ catch {
445
+ await writeFileWithDir(emptyModulePath, 'export default {}\n');
446
+ log(chalk.green(` ✓ Created test/empty-module.ts`));
447
+ log(chalk.dim(` Used as the server-only alias target in vitest.config.ts`));
448
+ }
449
+ }
288
450
  createdSetupFile = setupFilePath;
289
451
  }
290
452
  }
@@ -306,8 +468,14 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
306
468
  // to stay consistent with whatever the project has configured.
307
469
  // No React plugin needed: Vitest uses esbuild which handles JSX/TSX natively.
308
470
  const aliasTarget = meta.isNextJs ? await resolveAtAlias(cwd) : null;
471
+ // Next.js: add server-only alias so Vitest doesn't crash on Next.js server-only imports.
472
+ // server-only is a Next.js guard that throws at build time if server code leaks to the client;
473
+ // in Vitest it just needs to resolve to something harmless.
474
+ const serverOnlyAlias = meta.isNextJs
475
+ ? `,\n 'server-only': path.resolve(__dirname, './test/empty-module.ts')`
476
+ : '';
309
477
  const aliasBlock = aliasTarget
310
- ? `\n resolve: {\n alias: { '@': path.resolve(__dirname, '${aliasTarget}') },\n },`
478
+ ? `\n resolve: {\n alias: { '@': path.resolve(__dirname, '${aliasTarget}')${serverOnlyAlias} },\n },`
311
479
  : '';
312
480
  const pathImport = aliasTarget ? `import path from 'path'\n` : '';
313
481
  const vuePlugin = meta.isVue ? `\nimport vue from '@vitejs/plugin-vue'` : '';
@@ -323,6 +491,12 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
323
491
  `export default defineConfig({${aliasBlock}${pluginsBlock}`,
324
492
  ` test: {`,
325
493
  ` globals: true,${envLine}${setupLine}`,
494
+ ` // Restore and clear all mocks automatically before each test.`,
495
+ ` // restoreMocks runs at the Vitest worker level and can restore globalThis spies`,
496
+ ` // that the module-level vi instance cannot see — preventing cross-file contamination`,
497
+ ` // when multiple test files share the same worker thread.`,
498
+ ` restoreMocks: true,`,
499
+ ` clearMocks: true,`,
326
500
  ` coverage: {`,
327
501
  ` provider: 'v8',`,
328
502
  ` reporter: ['lcov', 'text-summary'],`,
@@ -345,17 +519,19 @@ async function ensureTestRunnerSetup(runner, sourceDir, cwd, log) {
345
519
  }
346
520
  catch {
347
521
  const setupLine = createdSetupFile
348
- ? `\n setupFilesAfterFramework: ['<rootDir>/${createdSetupFile}'],`
522
+ ? `\n setupFilesAfterEnv: ['<rootDir>/${createdSetupFile}'],`
349
523
  : '';
350
524
  const needsJsdom = (meta.isReact || meta.isVue || meta.isSvelte) && !meta.isReactNative && !meta.isAngular && !meta.isNestJs;
351
525
  const envLine = needsJsdom ? `\n testEnvironment: 'jsdom',` : '';
352
- const tsLines = meta.isTypeScript
526
+ // React Native / Expo: babel-preset-expo already handles TypeScript — don't override transform
527
+ // or it replaces the preset's JS transform and setup.js files fail to parse.
528
+ const tsLines = meta.isTypeScript && !meta.isReactNative
353
529
  ? `\n transform: { '^.+\\\\.tsx?$': 'ts-jest' },`
354
530
  : '';
355
531
  const rnPreset = meta.isExpo ? 'jest-expo' : 'react-native';
356
532
  const presetLine = meta.isReactNative ? `\n preset: '${rnPreset}',` : '';
357
533
  const transformIgnoreLine = meta.isReactNative
358
- ? `\n transformIgnorePatterns: ['node_modules/(?!(react-native|@react-native|@react-navigation|expo|@expo|@testing-library)/)',],`
534
+ ? `\n transformIgnorePatterns: ['node_modules/(?!(.pnpm/[^/]*/node_modules/)?(react-native(-[^/]+)?|@react-native|@react-navigation|expo(-[^/]+)?|@expo|@testing-library)/)'],`
359
535
  : '';
360
536
  const angularPreset = meta.isAngular ? `\n preset: 'jest-preset-angular',` : '';
361
537
  const content = [
@@ -488,7 +664,7 @@ export default class Init extends Command {
488
664
  if (hasMocks) {
489
665
  mocksFile = await input({
490
666
  message: 'Path to shared mock file:',
491
- default: `${sourceDir}/test/mocks.ts`,
667
+ default: (await readProjectMeta(cwd)).isReactNative ? `${sourceDir}/test/mock.tsx` : `${sourceDir}/test/mocks.ts`,
492
668
  });
493
669
  }
494
670
  // ── Coverage threshold ────────────────────────────────────────────────────
@@ -502,7 +678,7 @@ export default class Init extends Command {
502
678
  testRunner: testRunner,
503
679
  coverageFormat: 'lcov',
504
680
  coverageDir: 'coverage',
505
- sourceDir,
681
+ sourceDir: [sourceDir],
506
682
  threshold,
507
683
  maxIterations: 3,
508
684
  };