denoland/deno · error

Entry point must be a file: {}

Error message

Entry point must be a file: {}

What it means

During declaration emission, each entrypoint specifier is converted to a local file path to place the rolled-up .d.ts (Url::to_file_path, cli/tools/bundle/mod.rs:490-496). The conversion fails for any non-file:// scheme, so remote (https://) or data: entrypoints cannot produce declarations.

Source

Thrown at cli/tools/bundle/mod.rs:492

  // Index emitted .d.ts files by their original source specifier
  // TSC emits keys like "file:///path/to/file.d.ts" - map them back to source paths
  let mut dts_by_source: std::collections::HashMap<String, String> =
    std::collections::HashMap::new();
  for (file_name, content) in &result.emitted_files {
    // Map "file:///path/to/mod.d.ts" -> normalized source path
    if let Ok(specifier) = Url::parse(file_name)
      && let Ok(file_path) = specifier.to_file_path()
    {
      let source_path = file_path.to_string_lossy().to_string();
      dts_by_source.insert(source_path, content.clone());
    }
  }

  // For each entry point, produce a single rolled-up .d.ts
  for entry_specifier in &entrypoint_specifiers {
    let entry_path = entry_specifier.to_file_path().map_err(|_| {
      deno_core::anyhow::anyhow!(
        "Entry point must be a file: {}",
        entry_specifier
      )
    })?;

    // Find the .d.ts for this entry point
    let dts_path = to_dts_path(&entry_path);
    let entry_dts = dts_by_source
      .get(&dts_path.to_string_lossy().to_string())
      .ok_or_else(|| {
        deno_core::anyhow::anyhow!(
          "No declaration file emitted for entry point: {}",
          entry_specifier
        )
      })?;

    // Flatten: resolve all `export ... from "..."` re-exports by inlining
    // the referenced declarations from other .d.ts files.

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Download/mirror the remote module into the project and pass the local file as the entrypoint
  2. If the remote module is small, vendor it (deno vendor-style copy or deno add for jsr) and import locally
  3. Drop --declaration when the entrypoint must stay remote - declaration emission is local-files-only

Example fix

# before
deno bundle --declaration https://example.com/mod.ts --outdir dist
# after: local entry
Deno.writeTextFileSync('mod.ts', await (await fetch('https://example.com/mod.ts')).text());
deno bundle --declaration mod.ts --outdir dist
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure every entrypoint is a local file before declaration bundling
function assertLocalEntries(entries: string[]) {
  for (const e of entries) {
    if (new URL(e, import.meta.url).protocol !== 'file:') {
      throw new Error(`declaration bundling requires a local entry: ${e}`);
    }
  }
}

Type guard

function isFileUrl(specifier: string): specifier is `file://${string}` {
  try { return new URL(specifier).protocol === 'file:'; } catch { return false; }
}

Prevention

When it happens

Trigger: 'deno bundle --declaration https://example.com/mod.ts ...' or any entry that resolves to a remote/data specifier; piping an entry via a URL shortener/CDN in a build script.

Common situations: Build scripts that consume modules straight from a URL (common with jsr/std links); migrating a remote-entry workflow to --declaration.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/bc40810bbbe1646b. Report an issue: GitHub.