fix: handle EISDIR error when bin field points to directory (#10080)

close #9441
This commit is contained in:
Ryo Matsukawa
2025-10-13 17:00:06 +09:00
committed by GitHub
parent 3abd394623
commit a8797c4e59
7 changed files with 29 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/link-bins": patch
"pnpm": patch
---
Fixed EISDIR error when bin field points to a directory [#9441](https://github.com/pnpm/pnpm/issues/9441).

View File

@@ -57,6 +57,7 @@
"ebusy", "ebusy",
"ehrkoext", "ehrkoext",
"eintegrity", "eintegrity",
"eisdir",
"elifecycle", "elifecycle",
"elit", "elit",
"emfile", "emfile",

View File

@@ -284,7 +284,7 @@ async function linkBin (cmd: CommandInfo, binsDir: string, opts?: LinkBinOptions
await symlinkDir(cmd.path, externalBinPath) await symlinkDir(cmd.path, externalBinPath)
await fixBin(cmd.path, 0o755) await fixBin(cmd.path, 0o755)
} catch (err: any) { // eslint-disable-line } catch (err: any) { // eslint-disable-line
if (err.code !== 'ENOENT') { if (err.code !== 'ENOENT' && err.code !== 'EISDIR') {
throw err throw err
} }
globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message as string}`) globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message as string}`)
@@ -308,7 +308,7 @@ async function linkBin (cmd: CommandInfo, binsDir: string, opts?: LinkBinOptions
nodeExecPath: cmd.nodeExecPath, nodeExecPath: cmd.nodeExecPath,
}) })
} catch (err: any) { // eslint-disable-line } catch (err: any) { // eslint-disable-line
if (err.code !== 'ENOENT') { if (err.code !== 'ENOENT' && err.code !== 'EISDIR') {
throw err throw err
} }
globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message as string}`) globalWarn(`Failed to create bin at ${externalBinPath}. ${err.message as string}`)

View File

@@ -0,0 +1,2 @@
!**/node_modules/**/*
!/node_modules/

View File

@@ -0,0 +1 @@
This is a directory, not a file.

View File

@@ -0,0 +1,5 @@
{
"name": "invalid-bin",
"version": "1.0.0",
"bin": "./dist"
}

View File

@@ -32,6 +32,7 @@ const f = fixtures(__dirname)
beforeEach(() => { beforeEach(() => {
jest.mocked(binsConflictLogger.debug).mockClear() jest.mocked(binsConflictLogger.debug).mockClear()
jest.mocked(globalWarn).mockClear()
}) })
const POWER_SHELL_IS_SUPPORTED = isWindows() const POWER_SHELL_IS_SUPPORTED = isWindows()
@@ -535,6 +536,17 @@ testOnWindows('linkBins() should remove an existing .exe file from the target di
expect(fs.readdirSync(binTarget)).toEqual(getExpectedBins(['simple'])) expect(fs.readdirSync(binTarget)).toEqual(getExpectedBins(['simple']))
}) })
test('linkBins() should handle bin field pointing to a directory gracefully', async () => {
const binTarget = tempy.directory()
const binIsDirFixture = f.prepare('bin-is-directory')
const warn = jest.fn()
await linkBins(path.join(binIsDirFixture, 'node_modules'), binTarget, { warn })
expect(fs.readdirSync(binTarget)).toEqual([])
expect(globalWarn).toHaveBeenCalled()
})
describe('enable prefer-symlinked-executables', () => { describe('enable prefer-symlinked-executables', () => {
test('linkBins()', async () => { test('linkBins()', async () => {
const binTarget = tempy.directory() const binTarget = tempy.directory()