# 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 || {},
        },
      };
    }