From 13f5517f005bf543c4328a967c47f47dbd1c1d57 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Mon, 9 Jul 2018 01:47:42 +0200 Subject: [PATCH] feat: add proper support for PowerShell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .travis.yml | 1 - appveyor.yml | 1 - index.js | 64 +++++++++++++++++++++++++++-- package.json | 2 +- test/basic.js | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c720c99..4e847ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: - - 4 - 6 - 7 before_install: diff --git a/appveyor.yml b/appveyor.yml index 57e6953..05781fb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,5 @@ environment: matrix: - - nodejs_version: 4 - nodejs_version: 6 - nodejs_version: 7 install: diff --git a/index.js b/index.js index 278132d..93a1cab 100644 --- a/index.js +++ b/index.js @@ -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) { @@ -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}"` } @@ -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) ]) } diff --git a/package.json b/package.json index d249ea5..f2962ab 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "tape-promise": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" }, "mos": { "plugins": [ diff --git a/test/basic.js b/test/basic.js index 887c16c..160aa8f 100755 --- a/test/basic.js +++ b/test/basic.js @@ -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') }) }) @@ -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') }) }) @@ -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' + @@ -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() }) }) @@ -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' + @@ -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() }) }) @@ -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' + @@ -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() }) }) @@ -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' + @@ -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() }) })