feat: support windows shebang (#3887)

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
vagusX
2021-10-19 17:01:09 +08:00
committed by GitHub
parent 37905fcf7c
commit 6375cdce0b
9 changed files with 99 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/link-bins": patch
---
Autofix command files with Windows line endings on the shebang line.

View File

@@ -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",

View File

@@ -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[]> {

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
console.log('crlf');

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
console.log('lf');

View File

@@ -0,0 +1,8 @@
{
"name": "windows-shebang",
"version": "1.0.0",
"bin": {
"crlf": "bin/crlf.js",
"lf": "bin/lf.js"
}
}

View File

@@ -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
View File

@@ -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
View File

@@ -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[]
}