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
- Set entry to a concrete existing file path, e.g. "./src/index.js"
- If the entry is dynamic, validate it is non-empty and resolves to a real file before starting the bundler
- 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
- 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
- Never feed raw env vars or CLI strings into entry without an emptiness check
- Log the fully resolved entry path right before invoking the bundler
- Prefer the { name: './file.js' } map form so output names never depend on path parsing
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
- Duplicated methods (${element.key}) can't be decorated.
- failed to parse jsx option {}: '{}' is not an expression
- module string names unimplemented
- module string names unimplemented
- module string names unimplemented
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/0c206508ec94f603.
Report an issue: GitHub.