swc-project/swc · error

entry must be a file, instead of a directory

Error message

entry must be a file, instead of a directory

What it means

Thrown by swc_node_bundler v1 when a single-file `entry` string is converted into the internal entry map. `PathBuf::file_name()` returns None for paths whose last component is not a normal file name - i.e. the empty string "", the root "/" (or a drive root), and any path ending in ".." - and the code panics with this message instead of returning an error. Despite the wording, a path like "src/" (trailing slash on a real name) is fine; only root/empty/parent-component paths trip it.

Source

Thrown at crates/swc_node_bundler/src/v1/mod.rs:98

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged, rename = "Entry")]
pub enum EntryConfig {
    File(String),
    Multiple(Vec<String>),
    Files(FxHashMap<String, PathBuf>),
}

impl From<EntryConfig> for HashMap<String, FileName> {
    fn from(c: EntryConfig) -> Self {
        let mut m = HashMap::default();

        match c {
            EntryConfig::File(f) => {
                let path = PathBuf::from(f);
                let file_name = path
                    .file_name()
                    .expect("entry must be a file, instead of a directory");
                m.insert(file_name.to_string_lossy().into(), FileName::Real(path));
            }
            EntryConfig::Multiple(files) => {
                for f in files {
                    let path = PathBuf::from(f);
                    let file_name = path
                        .file_name()
                        .expect("entry must be a file, instead of a directory");
                    m.insert(file_name.to_string_lossy().into(), FileName::Real(path));
                }
            }
            EntryConfig::Files(f) => {
                return f.into_iter().map(|(k, v)| (k, FileName::Real(v))).collect()
            }
        }

        m
    }

View on GitHub (pinned to 5176682b65)

Solutions

  1. Set entry to a concrete existing file path, e.g. "./src/index.js"
  2. If the entry is dynamic, validate it is non-empty and resolves to a real file before starting the bundler
  3. Use the map form of the config (Entry as an object) so you control the output name explicitly instead of deriving it from the path
  4. Check for stray leading/trailing slashes or '..' segments produced by your path composition logic

Example fix

// before
const config = { entry: process.env.ENTRY }; // ENTRY unset -> ""

// after
const entry = process.env.ENTRY;
if (!entry || entry === '/' || entry.endsWith('..')) {
  throw new Error(`invalid entry: '${entry}'`);
}
const config = { entry }; // e.g. './src/index.js'
// or name it explicitly: { entry: { main: './src/index.js' } }
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function entryIsBindable(entry) {
  if (typeof entry !== 'string' || entry === '') return false;
  const norm = path.normalize(entry);
  const name = path.basename(norm);
  return name !== '' && name !== '.' && name !== '..' && name !== '/' && !name.startsWith('..') === false ? true : name !== '..';
}
// simpler + stricter:
function entryIsBindableStrict(entry) {
  return typeof entry === 'string' && entry !== '' && path.basename(path.normalize(entry)) !== '..';
}
if (!entryIsBindableStrict(config.entry)) throw new Error(`invalid entry: '${config.entry}'`);

Type guard

// Rust-side guard mirroring Path::file_name semantics
fn entry_bindable(entry: &str) -> bool {
    std::path::Path::new(entry).file_name().map(|f| f != "..").unwrap_or(false)
}

Try / catch

// The panic happens before any build work; instead of catching it,
// wrap bundler startup in a config check and report a friendly error:
try {
  assertEntryOk(config.entry);
  bundle(config);
} catch (e) {
  if (String(e.message).includes('entry must be a file')) {
    console.error(`entry '${config.entry}' must point to a file, not a root or parent directory`);
  }
}

Prevention

When it happens

Trigger: Building with swc_node_bundler v1 where the `entry` config deserializes to EntryConfig::File with value "", "/", "..", "a/..", or another path whose final component is '..'.

Common situations: Entry paths built from environment variables or CLI flags that are unset (interpolated to ""); config templates producing a root path; path.join/resolve mistakes that collapse to '..' after normalization; passing a bare root like "/" or "C:\\".

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/0c206508ec94f603. Report an issue: GitHub.