mirror of
https://github.com/pnpm/pnpm.git
synced 2026-01-09 23:48:28 -05:00
feat: support windows shebang (#3887)
Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
5
.changeset/fast-dancers-fetch.md
Normal file
5
.changeset/fast-dancers-fetch.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/link-bins": patch
|
||||
---
|
||||
|
||||
Autofix command files with Windows line endings on the shebang line.
|
||||
@@ -41,6 +41,7 @@
|
||||
"@pnpm/read-project-manifest": "workspace:2.0.5",
|
||||
"@pnpm/types": "workspace:7.4.0",
|
||||
"@zkochan/cmd-shim": "^5.2.1",
|
||||
"bin-links": "^2.3.0",
|
||||
"is-subdir": "^1.1.1",
|
||||
"is-windows": "^1.0.2",
|
||||
"normalize-path": "^3.0.0",
|
||||
|
||||
@@ -19,6 +19,7 @@ import isEmpty from 'ramda/src/isEmpty'
|
||||
import union from 'ramda/src/union'
|
||||
import unnest from 'ramda/src/unnest'
|
||||
import partition from 'ramda/src/partition'
|
||||
import fixBin from 'bin-links/lib/fix-bin'
|
||||
|
||||
const IS_WINDOWS = isWindows()
|
||||
const EXECUTABLE_SHEBANG_SUPPORTED = !IS_WINDOWS
|
||||
@@ -196,9 +197,6 @@ async function getPackageBinsFromManifest (manifest: DependencyManifest, pkgDir:
|
||||
async function linkBin (cmd: CommandInfo, binsDir: string, opts?: { extendNodePath?: boolean }) {
|
||||
const externalBinPath = path.join(binsDir, cmd.name)
|
||||
|
||||
if (EXECUTABLE_SHEBANG_SUPPORTED) {
|
||||
await fs.chmod(cmd.path, 0o755)
|
||||
}
|
||||
let nodePath: string[] | undefined
|
||||
if (opts?.extendNodePath !== false) {
|
||||
nodePath = await getBinNodePaths(cmd.path)
|
||||
@@ -207,11 +205,16 @@ async function linkBin (cmd: CommandInfo, binsDir: string, opts?: { extendNodePa
|
||||
nodePath = union(nodePath, await getBinNodePaths(binsParentDir))
|
||||
}
|
||||
}
|
||||
return cmdShim(cmd.path, externalBinPath, {
|
||||
await cmdShim(cmd.path, externalBinPath, {
|
||||
createPwshFile: cmd.makePowerShellShim,
|
||||
nodePath,
|
||||
nodeExecPath: cmd.nodeExecPath,
|
||||
})
|
||||
// ensure that bin are executable and not containing
|
||||
// windows line-endings(CRLF) on the hashbang line
|
||||
if (EXECUTABLE_SHEBANG_SUPPORTED) {
|
||||
await fixBin(cmd.path, 0o755)
|
||||
}
|
||||
}
|
||||
|
||||
async function getBinNodePaths (target: string): Promise<string[]> {
|
||||
|
||||
2
packages/link-bins/test/fixtures/bin-window-shebang/node_modules/crlf/bin/crlf.js
generated
vendored
Normal file
2
packages/link-bins/test/fixtures/bin-window-shebang/node_modules/crlf/bin/crlf.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
console.log('crlf');
|
||||
2
packages/link-bins/test/fixtures/bin-window-shebang/node_modules/crlf/bin/lf.js
generated
vendored
Normal file
2
packages/link-bins/test/fixtures/bin-window-shebang/node_modules/crlf/bin/lf.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
console.log('lf');
|
||||
8
packages/link-bins/test/fixtures/bin-window-shebang/node_modules/crlf/package.json
generated
vendored
Normal file
8
packages/link-bins/test/fixtures/bin-window-shebang/node_modules/crlf/package.json
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "windows-shebang",
|
||||
"version": "1.0.0",
|
||||
"bin": {
|
||||
"crlf": "bin/crlf.js",
|
||||
"lf": "bin/lf.js"
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ const foobarFixture = path.join(fixtures, 'foobar')
|
||||
const exoticManifestFixture = path.join(fixtures, 'exotic-manifest')
|
||||
const noNameFixture = path.join(fixtures, 'no-name')
|
||||
const noBinFixture = path.join(fixtures, 'no-bin')
|
||||
const windowShebangFixture = path.join(fixtures, 'bin-window-shebang')
|
||||
|
||||
const POWER_SHELL_IS_SUPPORTED = isWindows()
|
||||
const IS_WINDOWS = isWindows()
|
||||
@@ -300,3 +301,36 @@ test('linkBins() links commands from bin directory with a subdirectory', async (
|
||||
|
||||
expect(await fs.readdir(binTarget)).toEqual(getExpectedBins(['index.js']))
|
||||
})
|
||||
|
||||
test('linkBins() fix window shebang line', async () => {
|
||||
const binTarget = tempy.directory()
|
||||
const warn = jest.fn()
|
||||
|
||||
await linkBins(path.join(windowShebangFixture, 'node_modules'), binTarget, { warn })
|
||||
|
||||
expect(warn).not.toHaveBeenCalled()
|
||||
expect(await fs.readdir(binTarget)).toEqual(getExpectedBins(['crlf', 'lf']))
|
||||
|
||||
const lfBinLoc = path.join(binTarget, 'lf')
|
||||
const crlfBinLoc = path.join(binTarget, 'crlf')
|
||||
for (const binLocation of [lfBinLoc, crlfBinLoc]) {
|
||||
expect(await exists(binLocation)).toBe(true)
|
||||
}
|
||||
|
||||
if (EXECUTABLE_SHEBANG_SUPPORTED) {
|
||||
const lfFilePath = path.join(windowShebangFixture, 'node_modules', 'crlf/bin/lf.js')
|
||||
const crlfFilePath = path.join(windowShebangFixture, 'node_modules', 'crlf/bin/crlf.js')
|
||||
|
||||
for (const filePath of [lfFilePath, crlfFilePath]) {
|
||||
const content = await fs.readFile(filePath, 'utf8')
|
||||
expect(content.startsWith('#!/usr/bin/env node\n')).toBeTruthy()
|
||||
}
|
||||
|
||||
const lfStat = await fs.stat(lfBinLoc)
|
||||
const crlfStat = await fs.stat(crlfBinLoc)
|
||||
for (const stat of [lfStat, crlfStat]) {
|
||||
expect(stat.mode).toBe(parseInt('100755', 8))
|
||||
expect(stat.isFile()).toBe(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -1176,6 +1176,7 @@ importers:
|
||||
'@types/normalize-path': ^3.0.0
|
||||
'@types/ramda': 0.27.39
|
||||
'@zkochan/cmd-shim': ^5.2.1
|
||||
bin-links: ^2.3.0
|
||||
cmd-extension: ^1.0.2
|
||||
is-subdir: ^1.1.1
|
||||
is-windows: ^1.0.2
|
||||
@@ -1194,6 +1195,7 @@ importers:
|
||||
'@pnpm/read-project-manifest': link:../read-project-manifest
|
||||
'@pnpm/types': link:../types
|
||||
'@zkochan/cmd-shim': 5.2.1
|
||||
bin-links: 2.3.0
|
||||
is-subdir: 1.2.0
|
||||
is-windows: 1.0.2
|
||||
normalize-path: 3.0.0
|
||||
@@ -6368,6 +6370,18 @@ packages:
|
||||
dependencies:
|
||||
is-windows: 1.0.2
|
||||
|
||||
/bin-links/2.3.0:
|
||||
resolution: {integrity: sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
cmd-shim: 4.1.0
|
||||
mkdirp-infer-owner: 2.0.0
|
||||
npm-normalize-package-bin: 1.0.1
|
||||
read-cmd-shim: 2.0.0
|
||||
rimraf: 3.0.2
|
||||
write-file-atomic: 3.0.3
|
||||
dev: false
|
||||
|
||||
/bl/4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
dependencies:
|
||||
@@ -6889,6 +6903,13 @@ packages:
|
||||
resolution: {integrity: sha512-iWDjmP8kvsMdBmLTHxFaqXikO8EdFRDfim7k6vUHglY/2xJ5jLrPsnQGijdfp4U+sr/BeecG0wKm02dSIAeQ1g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/cmd-shim/4.1.0:
|
||||
resolution: {integrity: sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
mkdirp-infer-owner: 2.0.0
|
||||
dev: false
|
||||
|
||||
/co/4.6.0:
|
||||
resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=}
|
||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||
@@ -11447,6 +11468,15 @@ packages:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
dev: true
|
||||
|
||||
/mkdirp-infer-owner/2.0.0:
|
||||
resolution: {integrity: sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
chownr: 2.0.0
|
||||
infer-owner: 1.0.4
|
||||
mkdirp: 1.0.4
|
||||
dev: false
|
||||
|
||||
/mkdirp/0.5.5:
|
||||
resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
|
||||
hasBin: true
|
||||
@@ -12809,6 +12839,10 @@ packages:
|
||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||
dev: true
|
||||
|
||||
/read-cmd-shim/2.0.0:
|
||||
resolution: {integrity: sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw==}
|
||||
dev: false
|
||||
|
||||
/read-ini-file/3.1.0:
|
||||
resolution: {integrity: sha512-yfpbqijWt63cx6FWJyOxwUPm7v24Xchr6oROjhYhB0Ca0lbDLHSXDm6k5yMGJU/q1xfzcV4XbgQ98kk65ilNkQ==}
|
||||
engines: {node: '>=10.13'}
|
||||
|
||||
6
typings/typed.d.ts
vendored
6
typings/typed.d.ts
vendored
@@ -51,6 +51,12 @@ declare module 'split-cmd' {
|
||||
export function splitToObject (cmd: string): { command: string, args: string[] }
|
||||
}
|
||||
|
||||
|
||||
declare module 'bin-links/lib/fix-bin' {
|
||||
function fixBin (path: string, execMode: number): Promise<void>;
|
||||
export = fixBin;
|
||||
}
|
||||
|
||||
declare namespace NodeJS.Module {
|
||||
function _nodeModulePaths(from: string): string[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user