Skip to content

Commit

Permalink
feat: add proper support for PowerShell
Browse files Browse the repository at this point in the history
* Add proper support for PowerShell

  PowerShell can’t properly pass string arguments containing the `&`
symbol to Windows Command Prompt scripts, if the string containing
the ampersand doesn’t have spaces, due to how the cmd prompt parses
the `&` as a command delimiter, even in a string.

  This patch adds a workaround by generating a third script
specifically for PowerShell.

* Add option to allow disabling PowerShell shim script generation

BREAKING CHANGE: Make Node 6 the minimum supported version, same as pnpm@2

PR #5
  • Loading branch information
ExE-Boss authored and zkochan committed Jul 8, 2018
1 parent 006f380 commit 13f5517
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 7 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: node_js
node_js:
- 4
- 6
- 7
before_install:
Expand Down
1 change: 0 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
environment:
matrix:
- nodejs_version: 4
- nodejs_version: 6
- nodejs_version: 7
install:
Expand Down
64 changes: 60 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ function cmdShim (src, to, opts) {
}

function cmdShim_ (src, to, opts) {
return Promise.all([rm(to), opts.createCmdFile && rm(`${to}.cmd`)])
.then(() => writeShim(src, to, opts))
return Promise.all([
rm(to),
rm(`${to}.ps1`),
opts.createCmdFile && rm(`${to}.cmd`)
])
.then(() => writeShim(src, to, opts))
}

function writeShim (src, to, opts) {
Expand All @@ -70,23 +74,29 @@ function writeShim (src, to, opts) {

function writeShim_ (src, to, opts) {
opts = opts || {}
// Create PowerShell file by default if the option hasn't been specified
opts.createPwshFile = (typeof opts.createPwshFile !== 'undefined' ? opts.createPwshFile : true)
let shTarget = path.relative(path.dirname(to), src)
let target = shTarget.split('/').join('\\')
let longProg
let prog = opts.prog
let shProg = prog && prog.split('\\').join('/')
let shLongProg
let pwshProg = shProg && `"${shProg}$exe"`
let pwshLongProg
shTarget = shTarget.split('\\').join('/')
let args = opts.args || ''
if (!prog) {
prog = `"%~dp0\\${target}"`
shProg = `"$basedir/${shTarget}"`
pwshProg = shProg
args = ''
target = ''
shTarget = ''
} else {
longProg = `"%~dp0\\${prog}.exe"`
shLongProg = '"$basedir/' + prog + '"'
pwshLongProg = `"$basedir/${prog}$exe"`
target = `"%~dp0\\${target}"`
shTarget = `"$basedir/${shTarget}"`
}
Expand Down Expand Up @@ -155,16 +165,62 @@ function writeShim_ (src, to, opts) {
'exit $?\n'
}

// #!/usr/bin/env pwsh
// $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
//
// $ret=0
// $exe = ""
// if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
// # Fix case when both the Windows and Linux builds of Node
// # are installed in the same directory
// $exe = ".exe"
// }
// if (Test-Path "$basedir/node") {
// & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
// $ret=$LASTEXITCODE
// } else {
// & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args
// $ret=$LASTEXITCODE
// }
// exit $ret
let pwsh = '#!/usr/bin/env pwsh\n' +
'$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n' +
'\n' +
'$exe=""\n' +
'if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {\n' +
' # Fix case when both the Windows and Linux builds of Node\n' +
' # are installed in the same directory\n' +
' $exe=".exe"\n' +
'}\n'
if (shLongProg) {
pwsh = pwsh +
'$ret=0\n' +
`if (Test-Path ${pwshLongProg}) {\n` +
` & ${pwshLongProg} ${args} ${shTarget} $args\n` +
' $ret=$LASTEXITCODE\n' +
'} else {\n' +
` & ${pwshProg} ${args} ${shTarget} $args\n` +
' $ret=$LASTEXITCODE\n' +
'}\n' +
'exit $ret\n'
} else {
pwsh = pwsh +
`& ${pwshProg} ${args} ${shTarget} $args\n` +
'exit $LASTEXITCODE\n'
}

return Promise.all([
opts.createCmdFile && fs.writeFile(to + '.cmd', cmd, 'utf8'),
opts.createPwshFile && fs.writeFile(`${to}.ps1`, pwsh, 'utf8'),
fs.writeFile(to, sh, 'utf8')
])
.then(() => chmodShim(to, opts.createCmdFile))
.then(() => chmodShim(to, opts))
}

function chmodShim (to, createCmdFile) {
function chmodShim (to, {createCmdFile, createPwshFile}) {
return Promise.all([
fs.chmod(to, 0o755),
createPwshFile && fs.chmod(`${to}.ps1`, 0o755),
createCmdFile && fs.chmod(`${to}.cmd`, 0o755)
])
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"tape-promise": "^2.0.0"
},
"engines": {
"node": ">=4"
"node": ">=6"
},
"mos": {
"plugins": [
Expand Down
112 changes: 112 additions & 0 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ test('no cmd file', function (t) {
'\n\n' +
'"$basedir/src.exe" "$@"\nexit $?\n')
t.throws(() => fs.readFileSync(to + '.cmd', 'utf8'), 'cmd file not created')
t.equal(fs.readFileSync(`${to}.ps1`, 'utf8'),
'#!/usr/bin/env pwsh' +
'\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent' +
'\n' +
'\n$exe=""' +
'\nif ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {' +
'\n # Fix case when both the Windows and Linux builds of Node' +
'\n # are installed in the same directory' +
'\n $exe=".exe"' +
'\n}' +
'\n& "$basedir/src.exe" $args' +
'\nexit $LASTEXITCODE' +
'\n')
})
})

Expand All @@ -42,6 +55,19 @@ test('no shebang', function (t) {
'"$basedir/src.exe" "$@"\nexit $?\n')
t.equal(fs.readFileSync(to + '.cmd', 'utf8'),
'@"%~dp0\\src.exe" %*\r\n')
t.equal(fs.readFileSync(`${to}.ps1`, 'utf8'),
'#!/usr/bin/env pwsh' +
'\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent' +
'\n' +
'\n$exe=""' +
'\nif ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {' +
'\n # Fix case when both the Windows and Linux builds of Node' +
'\n # are installed in the same directory' +
'\n $exe=".exe"' +
'\n}' +
'\n& "$basedir/src.exe" $args' +
'\nexit $LASTEXITCODE' +
'\n')
})
})

Expand All @@ -52,6 +78,7 @@ test('env shebang', function (t) {
.then(() => {
console.error('%j', fs.readFileSync(to, 'utf8'))
console.error('%j', fs.readFileSync(to + '.cmd', 'utf8'))
console.error('%j', fs.readFileSync(`${to}.ps1`, 'utf8'))

t.equal(fs.readFileSync(to, 'utf8'),
'#!/bin/sh' +
Expand All @@ -78,6 +105,26 @@ test('env shebang', function (t) {
'\n @SET PATHEXT=%PATHEXT:;.JS;=;%\r' +
'\n node "%~dp0\\src.env" %*\r' +
'\n)')
t.equal(fs.readFileSync(`${to}.ps1`, 'utf8'),
'#!/usr/bin/env pwsh' +
'\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent' +
'\n' +
'\n$exe=""' +
'\nif ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {' +
'\n # Fix case when both the Windows and Linux builds of Node' +
'\n # are installed in the same directory' +
'\n $exe=".exe"' +
'\n}' +
'\n$ret=0' +
'\nif (Test-Path "$basedir/node$exe") {' +
'\n & "$basedir/node$exe" "$basedir/src.env" $args' +
'\n $ret=$LASTEXITCODE' +
'\n} else {' +
'\n & "node$exe" "$basedir/src.env" $args' +
'\n $ret=$LASTEXITCODE' +
'\n}' +
'\nexit $ret' +
'\n')
t.end()
})
})
Expand Down Expand Up @@ -164,6 +211,7 @@ test('env shebang with args', function (t) {
.then(() => {
console.error('%j', fs.readFileSync(to, 'utf8'))
console.error('%j', fs.readFileSync(to + '.cmd', 'utf8'))
console.error('%j', fs.readFileSync(`${to}.ps1`, 'utf8'))

t.equal(fs.readFileSync(to, 'utf8'),
'#!/bin/sh' +
Expand All @@ -190,6 +238,26 @@ test('env shebang with args', function (t) {
'\n @SET PATHEXT=%PATHEXT:;.JS;=;%\r' +
'\n node --expose_gc "%~dp0\\src.env.args" %*\r' +
'\n)')
t.equal(fs.readFileSync(`${to}.ps1`, 'utf8'),
'#!/usr/bin/env pwsh' +
'\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent' +
'\n' +
'\n$exe=""' +
'\nif ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {' +
'\n # Fix case when both the Windows and Linux builds of Node' +
'\n # are installed in the same directory' +
'\n $exe=".exe"' +
'\n}' +
'\n$ret=0' +
'\nif (Test-Path "$basedir/node$exe") {' +
'\n & "$basedir/node$exe" --expose_gc "$basedir/src.env.args" $args' +
'\n $ret=$LASTEXITCODE' +
'\n} else {' +
'\n & "node$exe" --expose_gc "$basedir/src.env.args" $args' +
'\n $ret=$LASTEXITCODE' +
'\n}' +
'\nexit $ret' +
'\n')
t.end()
})
})
Expand All @@ -201,6 +269,7 @@ test('explicit shebang', function (t) {
.then(() => {
console.error('%j', fs.readFileSync(to, 'utf8'))
console.error('%j', fs.readFileSync(to + '.cmd', 'utf8'))
console.error('%j', fs.readFileSync(`${to}.ps1`, 'utf8'))

t.equal(fs.readFileSync(to, 'utf8'),
'#!/bin/sh' +
Expand Down Expand Up @@ -228,6 +297,27 @@ test('explicit shebang', function (t) {
'\n @SET PATHEXT=%PATHEXT:;.JS;=;%\r' +
'\n /usr/bin/sh "%~dp0\\src.sh" %*\r' +
'\n)')

t.equal(fs.readFileSync(`${to}.ps1`, 'utf8'),
'#!/usr/bin/env pwsh' +
'\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent' +
'\n' +
'\n$exe=""' +
'\nif ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {' +
'\n # Fix case when both the Windows and Linux builds of Node' +
'\n # are installed in the same directory' +
'\n $exe=".exe"' +
'\n}' +
'\n$ret=0' +
'\nif (Test-Path "$basedir//usr/bin/sh$exe") {' +
'\n & "$basedir//usr/bin/sh$exe" "$basedir/src.sh" $args' +
'\n $ret=$LASTEXITCODE' +
'\n} else {' +
'\n & "/usr/bin/sh$exe" "$basedir/src.sh" $args' +
'\n $ret=$LASTEXITCODE' +
'\n}' +
'\nexit $ret' +
'\n')
t.end()
})
})
Expand All @@ -239,6 +329,7 @@ test('explicit shebang with args', function (t) {
.then(() => {
console.error('%j', fs.readFileSync(to, 'utf8'))
console.error('%j', fs.readFileSync(to + '.cmd', 'utf8'))
console.error('%j', fs.readFileSync(`${to}.ps1`, 'utf8'))

t.equal(fs.readFileSync(to, 'utf8'),
'#!/bin/sh' +
Expand Down Expand Up @@ -266,6 +357,27 @@ test('explicit shebang with args', function (t) {
'\n @SET PATHEXT=%PATHEXT:;.JS;=;%\r' +
'\n /usr/bin/sh -x "%~dp0\\src.sh.args" %*\r' +
'\n)')

t.equal(fs.readFileSync(`${to}.ps1`, 'utf8'),
'#!/usr/bin/env pwsh' +
'\n$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent' +
'\n' +
'\n$exe=""' +
'\nif ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {' +
'\n # Fix case when both the Windows and Linux builds of Node' +
'\n # are installed in the same directory' +
'\n $exe=".exe"' +
'\n}' +
'\n$ret=0' +
'\nif (Test-Path "$basedir//usr/bin/sh$exe") {' +
'\n & "$basedir//usr/bin/sh$exe" -x "$basedir/src.sh.args" $args' +
'\n $ret=$LASTEXITCODE' +
'\n} else {' +
'\n & "/usr/bin/sh$exe" -x "$basedir/src.sh.args" $args' +
'\n $ret=$LASTEXITCODE' +
'\n}' +
'\nexit $ret' +
'\n')
t.end()
})
})

0 comments on commit 13f5517

Please sign in to comment.