From 23a061deab248c1a5a21cf95b5aa1b566e62e7bb Mon Sep 17 00:00:00 2001 From: martin Date: Mon, 28 Sep 2026 15:35:48 -0700 Subject: [PATCH] fix: normalize bin metadata in getPaths ## Summary `lib/link-bins.js` already uses `npm-normalize-package-bin` before iterating over package bin entries. `getPaths()` derives the corresponding paths but previously operated on the package metadata directly, which could produce behavior inconsistent with bin creation for non-canonical entries. This change applies the same normalization in `getPaths()` so both APIs interpret bin metadata consistently. It normalizes a shallow copy to avoid modifying the caller's package object. The tests cover normalized bin names on POSIX and Windows and verify that the original package metadata remains unchanged. ## Testing - targeted `get-paths` tests - ESLint --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3ff4d6b3-8027-46d4-9024-3bb9e4fc495c (cherry picked from commit 7f9f97ae2a41bd1ffea516993971b6291b5b0aca) --- lib/get-paths.js | 2 ++ test/get-paths.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/get-paths.js b/lib/get-paths.js index b93e698..871a9c4 100644 --- a/lib/get-paths.js +++ b/lib/get-paths.js @@ -5,11 +5,13 @@ const binTarget = require('./bin-target.js') const manTarget = require('./man-target.js') const { resolve, basename, extname } = require('path') const isWindows = require('./is-windows.js') +const normalize = require('npm-normalize-package-bin') module.exports = ({ path, pkg, global, top }) => { if (top && !global) { return [] } + pkg = normalize({ ...pkg }) const binSet = [] const binTarg = binTarget({ path, top }) if (pkg.bin) { diff --git a/test/get-paths.js b/test/get-paths.js index 6871815..36a9255 100644 --- a/test/get-paths.js +++ b/test/get-paths.js @@ -77,3 +77,45 @@ for (const isWindows of [true, false]) { } }) } + +for (const isWindows of [true, false]) { + t.test(`${isWindows ? 'win32' : 'posix'} normalizes bin keys`, t => { + const path = require('path')[isWindows ? 'win32' : 'posix'] + const getPaths = requireInject('../lib/get-paths.js', { + path, + '../lib/is-windows.js': isWindows, + }) + const pkg = { + name: 'evil', + bin: { + '../../../sentinel': 'bin/traversal.js', + '/tmp/absolute': 'bin/absolute.js', + '..\\..\\windows': 'bin/windows.js', + 'C:\\outside\\drive': 'bin/drive.js', + invalid: 123, + }, + } + const originalBin = { ...pkg.bin } + const packagePath = isWindows + ? 'c:\\path\\to\\project\\node_modules\\evil' + : '/path/to/project/node_modules/evil' + const binTarget = isWindows + ? 'c:\\path\\to\\project\\node_modules\\.bin' + : '/path/to/project/node_modules/.bin' + const expected = ['sentinel', 'absolute', 'windows', 'drive'] + .map(bin => path.resolve(binTarget, bin)) + .flatMap(bin => [ + bin, + ...(isWindows ? [`${bin}.cmd`, `${bin}.ps1`] : []), + ]) + + t.strictSame(getPaths({ + path: packagePath, + pkg, + global: false, + top: false, + }), expected) + t.strictSame(pkg.bin, originalBin, 'does not mutate package metadata') + t.end() + }) +}