Skip to content

fix(powershell): use Invoke-Expression to pass args #8267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 44 commits into from

Conversation

alexsch01
Copy link
Contributor

@alexsch01 alexsch01 commented May 1, 2025

Reopening of #7458

@mbtools
Copy link
Contributor

mbtools commented May 1, 2025

Hi Alex,

Any change here should go along with new tests that prove what's not working (before the change) but working after. This includes executing npm run xxx from the command line and npm run xxx in a script in package.json.

I have updated test/bin/windows-shims.js to cover passing regular and named parameters to scripts.

windows-shims.txt

Please include this in the PR.

On my machine (pwsh 7.5.0, npm 10.9.2), all parameters are passed correctly with the current npm.ps1.

Do you have other tests that fail? Please add them.

@alexsch01
Copy link
Contributor Author

@mbtools thanks! I'll work on new tests for this PR later today

@alexsch01
Copy link
Contributor Author

alexsch01 commented May 2, 2025

@mbtools

On my machine (pwsh 7.5.0, npm 10.9.2), all parameters are passed correctly with the current npm.ps1.

npm test -- --param1 -p doesn't work in Windows PowerShell and PowerShell 7.5.1 (pwsh) using npm 10.9.2

  • it doesn't pass --param1 and -p
  • this PR fixes that

@alexsch01
Copy link
Contributor Author

This PR is ready for review

@alexsch01
Copy link
Contributor Author

alexsch01 commented May 3, 2025

Below "Output" is from running the windows-shims.js from upstream WITHOUT my PR

This should pass before adding my PR changes


@mbtools Ok I have an issue running the tests on my Windows machine

Without my PR, running node .\test\bin\windows-shims.js gives 2 failures

line number 240 and line number 253 from https://github.com/npm/cli/blob/latest/test/bin/windows-shims.js

  • commit 2d1d8d0 if this "latest" file changes

Output

PS C:\Users\FakeUser\Downloads\cli> node .\test\bin\windows-shims.js
TAP version 14

Subtest: shim contents

1..3
# Subtest: bash
    ok 1 - should be equivalent strictly
    ok 2 - all other changes are m->x
    1..2
ok 1 - bash # time=4.053ms

# Subtest: cmd
    ok 1 - should be equivalent strictly
    ok 2 - all other changes are m->x
    1..2
ok 2 - cmd # time=1.276ms

# Subtest: pwsh
    ok 1 - should be equivalent strictly
    ok 2 - all other changes are m->x
    1..2
ok 3 - pwsh # time=2.412ms

ok 1 - shim contents # time=12.605ms

Subtest: node-gyp

ok 1 - node-gyp contains env var
ok 2 - node-gyp contains path
ok 3 - node-gyp.cmd contains env var
ok 4 - node-gyp.cmd contains path
1..4

ok 2 - node-gyp # time=0.827ms

Subtest: run shims

1..6
# Subtest: cmd
    1..2
    not ok 1 - npm.cmd npm
      ---
      diff: |
        --- expected
        +++ actual
        @@ -1,4 +1,4 @@
         Object {
        -  "status": 0,
        -  "stdout": "[email protected] C:\\Users\\FakeUser\\Downloads\\cli",
        +  "status": 1,
        +  "stdout": "",
         }
      at:
        fileName: test\bin\windows-shims.js
        lineNumber: 240
        columnNumber: 7
        functionName: matchCmd
        isToplevel: true
      stack: |
        matchCmd (test/bin/windows-shims.js:240:7)
        Test.<anonymous> (test/bin/windows-shims.js:263:7)
        Test.<anonymous> (test/bin/windows-shims.js:253:7)
        Object.<anonymous> (test/bin/windows-shims.js:77:3)
      source: "    const result = spawnPath(cmd, [...args, isNpm ? 'help' :
        '--version'], opts)\r

        \r

        \    t.match(result, {\r

        ------^

        \      status: 0,\r

        \      signal: null,\n"
      ...

    not ok 2 - npx.cmd npx
      ---
      diff: |
        --- expected
        +++ actual
        @@ -1,4 +1,4 @@
         Object {
        -  "status": 0,
        -  "stdout": "11.3.0",
        +  "status": 1,
        +  "stdout": "",
         }
      at:
        fileName: test\bin\windows-shims.js
        lineNumber: 240
        columnNumber: 7
        functionName: matchCmd
        isToplevel: true
      stack: |
        matchCmd (test/bin/windows-shims.js:240:7)
        Test.<anonymous> (test/bin/windows-shims.js:264:7)
        Test.<anonymous> (test/bin/windows-shims.js:253:7)
        Object.<anonymous> (test/bin/windows-shims.js:77:3)
      source: "    const result = spawnPath(cmd, [...args, isNpm ? 'help' :
        '--version'], opts)\r

        \r

        \    t.match(result, {\r

        ------^

        \      status: 0,\r

        \      signal: null,\n"
      ...

not ok 1 - cmd # time=1434.598ms
  ---
  at:
    fileName: test\bin\windows-shims.js
    lineNumber: 253
    columnNumber: 7
    typeName: Test
  source: "\r

    \  for (const { cmd, skip, name, match } of shells) {\r

    \    t.test(name, t => {\r

    ------^

    \      if (skip.reason) {\r

    \        if (skip.fail) {\n"
  ...

# Subtest: pwsh
    ok 1 - pwsh - not installed # SKIP
    1..1
ok 2 - pwsh # time=0.61ms

# Subtest: git bash
    ok 1 - git bash - not installed # SKIP
    1..1
ok 3 - git bash # time=0.507ms

# Subtest: user git bash
    ok 1 - user git bash - not installed # SKIP
    1..1
ok 4 - user git bash # time=1.478ms

# Subtest: wsl bash
    ok 1 - wsl bash - not installed # SKIP
    1..1
ok 5 - wsl bash # time=1.499ms

# Subtest: cygwin bash
    ok 1 - cygwin bash - not installed # SKIP
    1..1
ok 6 - cygwin bash # time=1.304ms

not ok 3 - run shims # time=1907.767ms

at:
fileName: test\bin\windows-shims.js
lineNumber: 77
columnNumber: 3
typeName: Object
source: "})\r

\r

t.test('run shims', t => {\r

--^

\  const path = t.testdir({\r

\    ...SHIMS,\n"

...

1..3

{ total: 17, pass: 10, fail: 2, skip: 5 }

time=1953.236ms

PS C:\Users\FakeUser\Downloads\cli>

@mbtools
Copy link
Contributor

mbtools commented May 3, 2025

This is not the same js as in this PR:

Your Log: const result = spawnPath(cmd, [...args, isNpm ? 'help' : '--version'], opts)\r
PR: const result = spawnPath(cmd, [...args, ...params], opts)

@alexsch01
Copy link
Contributor Author

This is not the same js as in this PR:

Your Log: const result = spawnPath(cmd, [...args, isNpm ? 'help' : '--version'], opts)\r PR: const result = spawnPath(cmd, [...args, ...params], opts)

Right...I meant I'm getting 2 failing tests before adding my PR

@mbtools
Copy link
Contributor

mbtools commented May 3, 2025

That's with Windows PowerShell? I'm not sure if this ever was an option (or tested). Here it's about cmd, pwsh, and bash. If we can make it work for the older Windows PowerShell as well, why not?

@noseratio
Copy link

My 2c. If it's possible to make it work for both legacy (v5.0 and below) and modern (v6+) PowerShell with little efforts, that's great. Otherwise, I'd say specifying #Requires -Version 6.0 should be enough. For Windows people, it's been long overdue to move over to the modern portable PowerShell.

@alexsch01
Copy link
Contributor Author

I manually tested that echo 'test'; npm help a=1,b=2,c=3 now works with this PR (locally not the test) in both Windows PowerShell and pwsh

@alexsch01
Copy link
Contributor Author

the 2 failing tests I get on my machine do not appear in GitHub Actions CI so it must be my setup issue

@alexsch01
Copy link
Contributor Author

alexsch01 commented May 6, 2025

Thanks for the feedback! I'll look into the not so good part tomorrow

I also just made the ps1 files simpler by going back to the old logic if the grave key is found in the command....too many edge cases with that one

@alexsch01
Copy link
Contributor Author

Ok this PR looks complete to me, but if there's anything else to change please let me know

@wraithgar
Copy link
Member

Letting CI run. Will let @mbtools weigh in here before landing. Node 24 is now live with npm 11 so it will be good to get this landed.

@mbtools
Copy link
Contributor

mbtools commented May 7, 2025

👍

Let's add single quoted parameters to the test:

npm run test -- hello -p1 world -p2 "hello world" -p3 'hello world' --q1=hello world --q2="hello world" --q3='hello world'

hello
-p1
world
-p2
hello world
-p3
hello world
--q1=hello
world
--q2=hello world
--q3=hello world

I'm not sure how you came up with the $NPM_OG_COMMAND syntax but it works like a charm. Thank you!

Just a bit of styling to keep "exit" at the end:

if { 
  #...

  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | Invoke-Expression "& `"$NODE_EXE`" `"$NPM_CLI_JS`" $NPM_ARGS"
  } else {
    Invoke-Expression "& `"$NODE_EXE`" `"$NPM_CLI_JS`" $NPM_ARGS"
  }
} else {
  # Support pipeline input
  if ($MyInvocation.ExpectingInput) {
    $input | & $NODE_EXE $NPM_CLI_JS $args
  } else {
    & $NODE_EXE $NPM_CLI_JS $args
  }
}

exit $LASTEXITCODE

@mbtools
Copy link
Contributor

mbtools commented May 7, 2025

with single quotes, it works on the command line but fails the spawn test with cmd and pwsh:

image

@alexsch01
Copy link
Contributor Author

alexsch01 commented May 7, 2025

@mbtools

CMD treats single-quotes as literals - output on my machine

hello
-p1
world
-p2
hello world
-p3
'hello
world'
--q1=hello
world
--q2=hello world
--q3='hello
world'

Don't know why pwsh7 is not passing the test

@alexsch01
Copy link
Contributor Author

alexsch01 commented May 7, 2025

It almost feels like pwsh7 and cmd output always match with spawnSync ??

const { spawnSync } = require('child_process')

const cmd = 'pwsh'
const result = spawnSync(`"${cmd}"`, [`.\\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3`], {
	shell: true,
        cwd: __dirname,
})

if(result.stderr.length > 0) throw new Error(result.stderr.toString().trim())
console.log(result.stdout.toString().trim())
>node script.js
hello world
'hi
world'
a=1,b=2,c=3

const { spawnSync } = require('child_process')

const cmd = 'powershell'
const result = spawnSync(`"${cmd}"`, [`.\\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3`], {
	shell: true,
        cwd: __dirname,
})

if(result.stderr.length > 0) throw new Error(result.stderr.toString().trim())
console.log(result.stdout.toString().trim())
>node script.js
hello
world
hi world
a=1
b=2
c=3

@mbtools
Copy link
Contributor

mbtools commented May 7, 2025

Let's remove the single quote tests for now. It doesn't look related to the ps1 scripts. We will figure it out after (new issue).

This PR is hard to parse now. Could you create a new PR with a single commit and summary of the change (co-pilot 😉), please?

@alexsch01
Copy link
Contributor Author

Continuing in #8278

@alexsch01 alexsch01 closed this May 7, 2025
@alexsch01 alexsch01 deleted the latest branch May 7, 2025 11:23
@mbtools
Copy link
Contributor

mbtools commented May 7, 2025

PS: Set $env:NODE_DEBUG="*" and run node script.js. Then you see how spawn will map it back to cmd.exe and sets args (due to shell: true). So in the end it's always cmd.exe.

@alexsch01
Copy link
Contributor Author

alexsch01 commented May 7, 2025

ahhhh cmd /d /s /c "pwsh -Command .\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3" gives the expected output

hello
world
hi world
a=1
b=2
c=3

EDIT: I updated the new PR

@alexsch01
Copy link
Contributor Author

@mbtools with the single-quote tests which I'm not merging into my branch.....we now get this https://github.com/alexsch01/cli/actions/runs/14887289051/job/41810161408

Only CMD is now failing!!! which is expected for CMD

@ehoogeveen-medweb
Copy link

Yeah, cmd /d /s /c "pwsh -Command .\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3" should evaluate pwsh -Command .\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3 which would pass "hello world" as a single argument but split 'hi world' into 'hi and world' since CMD has no special handling for single quotes, right? You said it's expected, so just confirming since you seemed to report success in your previous comment.

@alexsch01
Copy link
Contributor Author

Yeah, cmd /d /s /c "pwsh -Command .\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3" should evaluate pwsh -Command .\abc.ps1 "hello world" 'hi world' a=1,b=2,c=3 which would pass "hello world" as a single argument but split 'hi world' into 'hi and world' since CMD has no special handling for single quotes, right? You said it's expected, so just confirming since you seemed to report success in your previous comment.

yes the test is good that's what I meant
We aren't going to use the single-quote test

wraithgar pushed a commit that referenced this pull request May 12, 2025
Continuation of #8267 @mbtools 

---

This fixes the command `npm test -- hello -p1 world -p2 "hello world"
--q1=hello world --q2="hello world"` in Windows PowerShell and pwsh7
- where the "test" script prints all the arguments passed after the
first "--" in the command above

Before this change

```
PS> npm test -- hello -p1 world -p2 "hello world" --q1=hello world --q2="hello world"
npm warn "world" is being parsed as a normal command line argument.
npm warn "hello world" is being parsed as a normal command line argument.
npm warn Unknown cli config "--p1". This will stop working in the next major version of npm.
npm warn Unknown cli config "--p2". This will stop working in the next major version of npm.
npm warn Unknown cli config "--q1". This will stop working in the next major version of npm.
npm warn Unknown cli config "--q2". This will stop working in the next major version of npm.

> [email protected] test
> node args.js hello world hello world world

hello
world
hello world
world
```

With this change

```
PS> npm test -- hello -p1 world -p2 "hello world" --q1=hello world --q2="hello world"

> [email protected] test
> node args.js hello -p1 world -p2 hello world --q1=hello world --q2=hello world

hello
-p1
world
-p2
hello world
--q1=hello
world
--q2=hello world
```

---

Also, fixes comma-separated values in Windows PowerShell and pwsh7

Before this change

```
PS> npm help a=1,b=2,c=3
No matches in help for: a=1 b=2 c=3
```

With this change

```
PS> npm help a=1,b=2,c=3
No matches in help for: a=1,b=2,c=3
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants