Skip to content

Commit

Permalink
fix #3001: EINVAL => ENOTDIR for readdir
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 17, 2023
1 parent 495216d commit deb93e9
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Work around an issue with `NODE_PATH` and Go's WebAssembly internals ([#3001](https://github.com/evanw/esbuild/issues/3001))

Go's WebAssembly implementation returns `EINVAL` instead of `ENOTDIR` when using the `readdir` syscall on a file. This messes up esbuild's implementation of node's module resolution algorithm since encountering `ENOTDIR` causes esbuild to continue its search (since it's a normal condition) while other encountering other errors causes esbuild to fail with an I/O error (since it's an unexpected condition). You can encounter this issue in practice if you use node's legacy `NODE_PATH` feature to tell esbuild to resolve node modules in a custom directory that was not installed by npm. This release works around this problem by converting `EINVAL` into `ENOTDIR` for the `readdir` syscall.

## 0.17.12

* Fix a crash when parsing inline TypeScript decorators ([#2991](https://github.com/evanw/esbuild/issues/2991))
Expand Down
8 changes: 8 additions & 0 deletions internal/fs/fs_real.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,14 @@ func (fs *realFS) readdir(dirname string) (entries []string, canonicalError erro
// Don't convert ENOTDIR to ENOENT here. ENOTDIR is a legitimate error
// condition for Readdirnames() on non-Windows platforms.

// Go's WebAssembly implementation returns EINVAL instead of ENOTDIR if we
// call "readdir" on a file. Canonicalize this to ENOTDIR so esbuild's path
// resolution code continues traversing instead of failing with an error.
// https://github.com/golang/go/blob/2449bbb5e614954ce9e99c8a481ea2ee73d72d61/src/syscall/fs_js.go#L144
if pathErr, ok := canonicalError.(*os.PathError); ok && pathErr.Unwrap() == syscall.EINVAL {
canonicalError = syscall.ENOTDIR
}

return entries, canonicalError, originalError
}

Expand Down
20 changes: 20 additions & 0 deletions scripts/wasm-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,26 @@ const tests = {
})();
`)
},

// https://github.com/evanw/esbuild/issues/3001
nodePathsReaddirEINVAL({ testDir, esbuildPathWASM }) {
const libDir = path.join(testDir, 'lib');
const libFile = path.join(libDir, 'file.js');
fs.mkdirSync(libDir, { recursive: true });
fs.writeFileSync(libFile, 'foo()');
const stdout = child_process.execFileSync('node', [
esbuildPathWASM,
'--bundle',
'--format=esm',
], {
stdio: ['pipe', 'pipe', 'inherit'],
cwd: testDir,
input: `import "file.js"`,
env: { ...process.env, NODE_PATH: libDir },
}).toString();

assert.deepStrictEqual(stdout, '// lib/file.js\nfoo();\n');
},
};

function runTest({ testDir, esbuildPathNative, esbuildPathWASM, test }) {
Expand Down

0 comments on commit deb93e9

Please sign in to comment.