All files / lib/detectors node-detector.js

100% Statements 34/34
100% Branches 22/22
100% Functions 3/3
100% Lines 32/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96      2x                 2x             2x               11x   11x 1x     10x 10x     10x 10x   10x 23x 9x 9x 9x         10x 2x     10x 1x       9x 9x 41x 1x 1x         9x 9x 27x 6x 6x         9x 9x   9x                                
import fs from 'fs-extra';
import path from 'path';
 
const FRAMEWORK_PATTERNS = {
  'next.js': { dep: 'next', stack: 'next.js' },
  'react': { dep: 'react', stack: 'react' },
  'vite': { dep: 'vite', stack: 'vite' },
  'express': { dep: 'express', stack: 'node-api' },
  'nestjs': { dep: '@nestjs/core', stack: 'node-api' },
  'fastify': { dep: 'fastify', stack: 'node-api' },
};
 
const TEST_RUNNER_PATTERNS = {
  'jest': ['jest', '@types/jest'],
  'vitest': ['vitest'],
  'mocha': ['mocha'],
  'jasmine': ['jasmine'],
};
 
const BUILD_TOOL_PATTERNS = {
  'vite': ['vite'],
  'webpack': ['webpack'],
  'turbopack': ['next'], // Next.js uses Turbopack
  'tsc': ['typescript'],
};
 
export async function detectNodeStack(cwd) {
  const pkgPath = path.join(cwd, 'package.json');
 
  if (!(await fs.pathExists(pkgPath))) {
    return { detected: false };
  }
 
  const pkg = await fs.readJson(pkgPath);
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
 
  // Detect framework
  let framework = null;
  let stack = null;
 
  for (const [name, { dep, stack: stackName }] of Object.entries(FRAMEWORK_PATTERNS)) {
    if (deps[dep]) {
      framework = name;
      stack = stackName;
      break;
    }
  }
 
  // Determine stack for react+vite
  if (framework === 'react' && deps['vite']) {
    stack = 'react-vite';
  }
 
  if (!framework) {
    return { detected: false };
  }
 
  // Detect test runner
  let testRunner = null;
  for (const [runner, patterns] of Object.entries(TEST_RUNNER_PATTERNS)) {
    if (patterns.some(p => deps[p])) {
      testRunner = runner;
      break;
    }
  }
 
  // Detect build tool
  let buildTool = null;
  for (const [tool, patterns] of Object.entries(BUILD_TOOL_PATTERNS)) {
    if (patterns.some(p => deps[p])) {
      buildTool = tool;
      break;
    }
  }
 
  // Detect linter/formatter
  const linter = deps['eslint'] ? 'eslint' : null;
  const formatter = deps['prettier'] ? 'prettier' : null;
 
  return {
    detected: true,
    stack,
    confidence: 'high',
    language: deps['typescript'] ? 'typescript' : 'javascript',
    framework,
    testRunner,
    linter,
    formatter,
    buildTool,
    evidence: {
      files: ['package.json'],
      dependencies: Object.keys(deps).slice(0, 5),
      scripts: pkg.scripts || {},
    },
  };
}