Skip to content

TypeScript single file build fails with 'Cannot use both outfile and outdir' error #1

@StfBauer

Description

@StfBauer

Description

The TypeScript builder's buildFile method fails when processing individual TypeScript files during watch mode, throwing the error:

Cannot use both "outfile" and "outdir"

Additionally, the typescript.outDir configuration option is not respected for single file builds.

Root Causes

Issue 1: Conflicting esbuild options in buildFile method

Location: dist/builders/typescript.js:198-206

The buildFile method passes both outdir and outfile to esbuild, which is not allowed:

const buildOptions = {
    entryPoints: [filePath],
    outdir: path.dirname(outputPath),  // ❌ This line causes the conflict
    outfile: outputPath,
    format: 'esm',
    ...this.config.esbuild,
    tsconfig: await this.findTsConfig(),
    plugins,
};

Issue 2: Invalid path transformation in transformOutputPath plugin

Location: dist/builders/typescript.js:170-190

The outfile-transformer plugin returns relative paths instead of absolute paths, causing a secondary error:

Plugin "outfile-transformer" returned a non-absolute path: ./source/js/wf-block-overflow.ts

Issue 3: typescript.outDir not respected

Location: dist/builders/typescript.js:139-150

The getOutputPath method only uses config.output.path and ignores config.typescript.outDir, meaning the TypeScript-specific output directory configuration has no effect.

Steps to Reproduce

  1. Create a TypeScript file in your source directory (e.g., source/ts/example.ts)
  2. Configure ice-build with a TypeScript output directory:
    // ice.config.js
    module.exports = {
      typescript: {
        outDir: 'public/js',
      }
    }
  3. Run ice-build watch
  4. Save the TypeScript file to trigger a rebuild

Expected: File compiles successfully to public/js/example.js
Actual: Build fails with error: Cannot use both "outfile" and "outdir"

Proposed Fixes

Fix 1: Remove outdir from buildFile options

// dist/builders/typescript.js:198-206
const buildOptions = {
    entryPoints: [filePath],
    outfile: outputPath,  // Only use outfile for single file builds
    format: 'esm',
    ...this.config.esbuild,
    tsconfig: await this.findTsConfig(),
    plugins,
};

Fix 2: Simplify transformOutputPath method

The transformOutputPath method is unnecessary for single file builds since the output path is already calculated by getOutputPath. Replace it with a no-op:

// dist/builders/typescript.js:170-174
transformOutputPath(buildOptions) {
    // Path transformation handled by getOutputPath
    return buildOptions;
}

Fix 3: Respect typescript.outDir configuration

// dist/builders/typescript.js:139-154
getOutputPath(filePath) {
    const inputFileBaseName = path.basename(filePath, path.extname(filePath));
    let filenamePattern = '[name].js';
    if (this.config.output?.filenames?.js) {
        filenamePattern = this.config.output.filenames.js;
    }
    const outputFileNameWithSubDir = filenamePattern.replace('[name]', inputFileBaseName);

    // Use typescript.outDir if specified, otherwise fall back to this.outputPath
    let outputDir = this.outputPath;
    if (this.config.typescript?.outDir) {
        outputDir = this.config.typescript.outDir;
    }

    return path.join(outputDir, outputFileNameWithSubDir);
}

Environment

  • ice-build version: 0.7.0
  • Node version: 25.2.0
  • Platform: macOS (Darwin)

Impact

This bug prevents TypeScript files from being compiled during watch mode, effectively breaking the development workflow for TypeScript projects using ice-build.

Workaround

Currently, the only workaround is to manually patch the node_modules/@n8d/ice-build/dist/builders/typescript.js file with the fixes described above.

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