diff --git a/.changeset/new-ads-relax.md b/.changeset/new-ads-relax.md new file mode 100644 index 0000000000..766aaee643 --- /dev/null +++ b/.changeset/new-ads-relax.md @@ -0,0 +1,5 @@ +--- +"@pnpm/global-bin-dir": patch +--- + +A directory is considered a valid global executable directory for pnpm, if it contains a node, or npm, or pnpm executable, not directory. diff --git a/packages/global-bin-dir/src/index.ts b/packages/global-bin-dir/src/index.ts index 8ce1740ba7..9fdd206e0b 100644 --- a/packages/global-bin-dir/src/index.ts +++ b/packages/global-bin-dir/src/index.ts @@ -61,8 +61,9 @@ const NODE_RELATED_COMMANDS = new Set(['pnpm', 'npm', 'node']) function dirHasNodeRelatedCommand (dir: string) { try { - const files = fs.readdirSync(dir) - return files.map((file) => file.toLowerCase()) + return fs.readdirSync(dir, { withFileTypes: true }) + .filter((entry) => entry.isFile()) + .map(({ name }) => name.toLowerCase()) .some((file) => NODE_RELATED_COMMANDS.has(file.split('.')[0])) } catch (err) { return false diff --git a/packages/global-bin-dir/test/index.ts b/packages/global-bin-dir/test/index.ts index 6c9f84f9fc..60d2a13008 100644 --- a/packages/global-bin-dir/test/index.ts +++ b/packages/global-bin-dir/test/index.ts @@ -12,9 +12,17 @@ const makePath = : (...paths: string[]) => `/${path.join(...paths)}` let canWriteToDir!: typeof _canWriteToDir -let readdirSync = (dir: string) => [] as string[] +let readdirSync = (dir: string) => [] as Array<{ name: string, isFile: () => boolean }> const FAKE_PATH = 'FAKE_PATH' +function makeFileEntry (name: string) { + return { name, isFile: () => true } +} + +function makeDirEntry (name: string) { + return { name, isFile: () => false } +} + const globalBinDir = proxiquire('../lib/index.js', { 'can-write-to-dir': { sync: (dir: string) => canWriteToDir(dir), @@ -152,13 +160,31 @@ test('select a directory that has a node command in it', (t) => { ].join(path.delimiter) canWriteToDir = () => true - readdirSync = (dir) => dir === dir2 ? ['node'] : [] + readdirSync = (dir) => dir === dir2 ? [makeFileEntry('node')] : [] t.equal(globalBinDir(), dir2) process.env[FAKE_PATH] = pathEnv t.end() }) +test('do not select a directory that has a node directory in it', (t) => { + const dir1 = makePath('foo') + const dir2 = makePath('bar') + const pathEnv = process.env[FAKE_PATH] + process.env[FAKE_PATH] = [ + dir1, + dir2, + ].join(path.delimiter) + + canWriteToDir = () => true + readdirSync = (dir) => dir === dir2 ? [makeDirEntry('node')] : [] + + t.throws(() => globalBinDir(), /Couldn't find a suitable/) + + process.env[FAKE_PATH] = pathEnv + t.end() +}) + test('select a directory that has a node.bat command in it', (t) => { const dir1 = makePath('foo') const dir2 = makePath('bar') @@ -169,7 +195,7 @@ test('select a directory that has a node.bat command in it', (t) => { ].join(path.delimiter) canWriteToDir = () => true - readdirSync = (dir) => dir === dir2 ? ['node.bat'] : [] + readdirSync = (dir) => dir === dir2 ? [makeFileEntry('node.bat')] : [] t.equal(globalBinDir(), dir2) process.env[FAKE_PATH] = pathEnv