{"record":{"id":"0c206508ec94f603","repo":"swc-project/swc","slug":"entry-must-be-a-file-instead-of-a-directory","errorCode":null,"errorMessage":"entry must be a file, instead of a directory","messagePattern":"entry must be a file, instead of a directory","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/swc_node_bundler/src/v1/mod.rs","lineNumber":98,"sourceCode":"\n#[derive(Debug, Clone, Deserialize)]\n#[serde(untagged, rename = \"Entry\")]\npub enum EntryConfig {\n    File(String),\n    Multiple(Vec<String>),\n    Files(FxHashMap<String, PathBuf>),\n}\n\nimpl From<EntryConfig> for HashMap<String, FileName> {\n    fn from(c: EntryConfig) -> Self {\n        let mut m = HashMap::default();\n\n        match c {\n            EntryConfig::File(f) => {\n                let path = PathBuf::from(f);\n                let file_name = path\n                    .file_name()\n                    .expect(\"entry must be a file, instead of a directory\");\n                m.insert(file_name.to_string_lossy().into(), FileName::Real(path));\n            }\n            EntryConfig::Multiple(files) => {\n                for f in files {\n                    let path = PathBuf::from(f);\n                    let file_name = path\n                        .file_name()\n                        .expect(\"entry must be a file, instead of a directory\");\n                    m.insert(file_name.to_string_lossy().into(), FileName::Real(path));\n                }\n            }\n            EntryConfig::Files(f) => {\n                return f.into_iter().map(|(k, v)| (k, FileName::Real(v))).collect()\n            }\n        }\n\n        m\n    }","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/crates/swc_node_bundler/src/v1/mod.rs#L80-L116","documentation":"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.","triggerScenarios":"Building with swc_node_bundler v1 where the `entry` config deserializes to EntryConfig::File with value \"\", \"/\", \"..\", \"a/..\", or another path whose final component is '..'.","commonSituations":"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:\\\\\".","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"],"exampleFix":"// before\nconst config = { entry: process.env.ENTRY }; // ENTRY unset -> \"\"\n\n// after\nconst entry = process.env.ENTRY;\nif (!entry || entry === '/' || entry.endsWith('..')) {\n  throw new Error(`invalid entry: '${entry}'`);\n}\nconst config = { entry }; // e.g. './src/index.js'\n// or name it explicitly: { entry: { main: './src/index.js' } }","handlingStrategy":"validation","validationCode":"const path = require('path');\nfunction entryIsBindable(entry) {\n  if (typeof entry !== 'string' || entry === '') return false;\n  const norm = path.normalize(entry);\n  const name = path.basename(norm);\n  return name !== '' && name !== '.' && name !== '..' && name !== '/' && !name.startsWith('..') === false ? true : name !== '..';\n}\n// simpler + stricter:\nfunction entryIsBindableStrict(entry) {\n  return typeof entry === 'string' && entry !== '' && path.basename(path.normalize(entry)) !== '..';\n}\nif (!entryIsBindableStrict(config.entry)) throw new Error(`invalid entry: '${config.entry}'`);","typeGuard":"// Rust-side guard mirroring Path::file_name semantics\nfn entry_bindable(entry: &str) -> bool {\n    std::path::Path::new(entry).file_name().map(|f| f != \"..\").unwrap_or(false)\n}","tryCatchPattern":"// The panic happens before any build work; instead of catching it,\n// wrap bundler startup in a config check and report a friendly error:\ntry {\n  assertEntryOk(config.entry);\n  bundle(config);\n} catch (e) {\n  if (String(e.message).includes('entry must be a file')) {\n    console.error(`entry '${config.entry}' must point to a file, not a root or parent directory`);\n  }\n}","preventionTips":["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"],"tags":["config","entry","path","bundler","panic","swc-node-bundler"],"backgroundTag":"invalid-config-file-path","analyzedSha":"5176682b65416c6b5de6b47379ae1588ea3ecb3f","analyzedAt":"2026-08-17T16:16:52.067Z","contentChangedAt":"2026-08-17T16:16:52.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}