Skip to content

nest build silently produces no output with TypeScript 6 + incremental: true #3312

@ReemX

Description

@ReemX

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

nest build exits with code 0 but produces no dist/ directory, no errors, no warnings. The build appears to succeed silently but emits zero files.

The behavior is flaky — sometimes it works (when no .tsbuildinfo exists), sometimes it silently skips emit (when a stale .tsbuildinfo is present). deleteOutDir: true in nest-cli.json makes it worse because it removes dist/ before emit, but the incremental compiler thinks nothing changed.

This affects nest build and nest start --watch (which reports "Found 0 errors" but dist/main.js doesn't exist).

Running tsc --project tsconfig.build.json directly works every time.

Minimum reproduction code

https://gist.github.com/ReemX/c9ad51f31a9abd5ed0d9b8678ddbc832

Steps to reproduce

  1. Create a new NestJS project: nest new repro --strict
  2. Install TypeScript 6: npm i typescript@6
  3. Add "incremental": true to tsconfig.build.json compilerOptions (or inherit it from an extended config)
  4. Run npx nest build
  5. Check dist/ — it does not exist

Expected behavior

nest build should emit compiled JavaScript files to dist/ (same as tsc -p tsconfig.build.json).

Package version

11.0.17

NestJS version

11.1.17

Node.js version

22.18.0

In which operating systems have you tested?

  • Windows
  • macOS
  • Linux

Other

Root cause: In lib/compiler/compiler.js (line 21), the CLI prefers ts.createIncrementalProgram over ts.createProgram:

const createProgram = tsBinary.createIncrementalProgram || tsBinary.createProgram;

With TypeScript 6, createIncrementalProgram with incremental: true returns a program whose .emit() reports emitSkipped: false and diagnostics: [] but writes zero files to disk. The same call with createProgram works correctly.

Verification script (save as verify.js):

const ts = require('typescript');
const configPath = require('path').join(process.cwd(), 'tsconfig.build.json');
const { options, fileNames, projectReferences } = ts.getParsedCommandLineOfConfigFile(configPath, undefined, ts.sys);

const prog = ts.createIncrementalProgram({ rootNames: fileNames, projectReferences, options });
const result = prog.emit();
console.log('emitSkipped:', result.emitSkipped);   // false
console.log('emittedFiles:', result.emittedFiles);  // [] — empty!
console.log('dist exists:', require('fs').existsSync('dist'));  // false

Workaround: Set "incremental": false in tsconfig.build.json.

This may also warrant a bug report on the TypeScript side (microsoft/TypeScript) since createIncrementalProgram silently succeeding with no output seems like a TS 6 regression.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions