fix(powershell): use Invoke-Expression to pass args#7458
fix(powershell): use Invoke-Expression to pass args#7458lukekarrys wants to merge 1 commit intolatestfrom
Conversation
3ac0758 to
65cb3de
Compare
Co-authored-by: noseratio <noseratio@gmail.com>
65cb3de to
acf6bfe
Compare
| } | ||
|
|
||
| $NPM_ARGS = $MyInvocation.Line.Substring($MyInvocation.InvocationName.Length).Trim() | ||
| $INVOKE_NPM = "$($NODE_EXE -Replace ' ', '` ') $($NPM_CLI_JS -Replace ' ', '` ') $NPM_ARGS".Trim() |
There was a problem hiding this comment.
We should be absolutely sure this won't overlap w/ npm's escaping when it spawns scripts.
There was a problem hiding this comment.
Converted to draft as I’m still experimenting with this.
There was a problem hiding this comment.
@lukekarrys also please don't use MyInvocation.Line, use MyInvocation.Statement instead. Here's why:
If npm.ps1 is invoked as part of a multi-statement one-liner, like echo "Hello" && npm start -- commmand --arg=value, then MyInvocation props would look like this:
"Line": "echo \"Hello\" && npm start -- commmand --arg=value",
"Statement": "npm start -- commmand --arg=value",
"InvocationName": "npm",
PS Thanks for a shoutout! 🙂
There was a problem hiding this comment.
Thanks for the tip @noseratio. I used MyInvocation.Line in a force-push after I couldn't get MyInvocation.Statement working. In the tests these files are called directly via child_process.spawn so I'm not sure if that is affecting them. I will need to do some more research to see why both Line and Statement are not available on MyInvocation in tests.
There was a problem hiding this comment.
But MyInvocation.Statement did work for me locally.
There was a problem hiding this comment.
Also interestingly, if npm is typed in a Git-Bash prompt on Windows, the Bash version - "C:\Program Files\nodejs\npm" - will get invoked.
There was a problem hiding this comment.
Last but not least, npm.cmd remains being super important, as it will be invoked for nested npm scripts (like startapp below), regardless of the outer shell:
package.json:
{
"name": "cli-test",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js",
"startapp": "npm start -- ",
"showcli": "echo"
}
}C:\temp\cli-test>pwsh –noprofile PowerShell 7.4.2
PS C:\temp\cli-test> npm run startapp -- command --arg=value
Hello from npm.ps1
> cli-test@1.0.0 startapp
> npm start -- command --arg=value
Hello from npm.bat
> cli-test@1.0.0 start
> node index.js command --arg=value
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\temp\\cli-test\\index.js',
'command',
'--arg=value'
]
PS C:\temp\cli-test> & "C:\Program Files\Git\bin\bash.exe"
Master@i3msi MINGW64 /c/temp/cli-test
$ npm run startapp -- command --arg=value
Hello from npm[.sh]
> cli-test@1.0.0 startapp
> npm start -- command --arg=value
Hello from npm.bat
> cli-test@1.0.0 start
> node index.js command --arg=value
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\temp\\cli-test\\index.js',
'command',
'--arg=value'
]
That can probably only be changed by overriding the shell in .npmrc:
shell = "pwsh"There was a problem hiding this comment.
also please don't use MyInvocation.Line, use MyInvocation.Statement instead.
On my Windows PowerShell, MyInvocation.Statement doesn't exist.
In #8267, I chose to use MyInvocation.Line due to that
There was a problem hiding this comment.
This needs to be conditional. "Line" does not work when multiple statements are joined which is common like " echo 'test' & npm run my-script".
An additional challenge is how we test using spawn. In my quick try, neither $MyInnvocation.Statement nor Line are populated.
We can get this done but it will take some tries.
|
@lukekarrys Node v22.1.0 was out, and I had to patch My current version: #!/usr/bin/env pwsh
$NODE_EXE="$PSScriptRoot/node.exe"
if (-not (Test-Path $NODE_EXE)) {
$NODE_EXE="$PSScriptRoot/node"
}
if (-not (Test-Path $NODE_EXE)) {
$NODE_EXE="node"
}
$NPM_PREFIX_JS="$PSScriptRoot/node_modules/npm/bin/npm-prefix.js"
$NPM_CLI_JS="$PSScriptRoot/node_modules/npm/bin/npm-cli.js"
$NPM_PREFIX=(& $NODE_EXE $NPM_PREFIX_JS)
if ($LASTEXITCODE -ne 0) {
Write-Host "Could not determine Node.js install directory"
exit 1
}
$NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js"
if (Test-Path $NPM_PREFIX_NPM_CLI_JS) {
$NPM_CLI_JS=$NPM_PREFIX_NPM_CLI_JS
}
function Normalize {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
[string]$Path
)
$Path = [System.IO.Path]::GetFullPath($Path)
# remove trailing " or ' quotes (if any) and put back " quotes around the path
$Path = $Path -replace '^\s*"\s*(.*?)\s*"\s*$', '$1'
$Path = $Path -replace "^\s*'\s*(.*?)\s*'\s*$", "$1"
return """$Path"""
}
$NPM_ARGS = $MyInvocation.Statement.Substring($MyInvocation.InvocationName.Length).Trim()
$INVOKE_NPM = "& $(Normalize $NODE_EXE) $(Normalize $NPM_CLI_JS) $NPM_ARGS"
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | Invoke-Expression $INVOKE_NPM
} else {
Invoke-Expression $INVOKE_NPM
}
exit $LASTEXITCODE |
|
Andrew's solution is working well. Let's merge the last version above and fix this annoying bug. The logs expired. Luke @lukekarrys, could you please update the PR and trigger CI so we see what's missing? Thank you 🙏 |
|
I won't be able to push this over the finish line. It's probably best if a new PR is opened with the proposed solution. |
@mbtools , thanks for vouching! I currently re-apply this patch semi-automatically everytime I upgrade node.js on Windows: I'm sorry I don't have capacity right now to re-opon this PR, but I'd be grateful if anyone could pick it up. |
|
@noseratio I used your newer patch to open #8267, and did the same for npx.ps1 EDIT: I changed |
|
There are differences between Windows PowerShell, PowerShell before 7.3 and PowerShell >= 7.3. We need to be clear what works and what doesn't (see my comment in the PR) |
Co-authored-by: @noseratio noseratio@gmail.com