PowerShell, NPM Scripts, and Silently Dropped Arguments

@lloydjatkinson
2,027
Apparently PowerShell has a black logo since PowerShell Core/7.0
Apparently PowerShell has a black logo since PowerShell Core/7.0

When writing some TypeScript based NPM scripts to aid in the development of my site, I found that some of the arguments appeared to be vanishing somewhere between invoking the script and the values being passed into the Node process. On Linux/WSL this issue does not exist. It also does not exist when using cmd.exe. Only when using PowerShell, which has been the default shell since the release of Windows 10. Interestingly, this issue only came about recently. As shown in the linked GitHub issue, this is a regression in Node.

I wrote this post because I spent a few minutes trying to understand it and I’m hoping to save a lot of debugging time and frustration for other people encountering this problem.

My use case is the following:

npm run article:create -- --title "Using Jira does not make you agile" --draft

Double hyphens

There’s two parts to this. Firstly, any arguments supplied inside the scripts section work as expected. The second part, and the point of this post, is supplying arguments to npm run xyz. This is achieved using double hyphens. According to the POSIX standard this is a “delimiter indicating the end of options” and is commonly used with Bash and other shells. Without the double hyphen any arguments given to NPM here will be silently ignored.

But that isn’t what this post is about. This post is about how this behaviour is currently broken on Windows when using Powershell and Node 16 (but probably other versions too).

Demonstration

I will demonstrate the problem using Node 16, Linux, Windows with cmd.exe, and Windows with PowerShell. In package.json I have the following script. I’m using tsx to run the TypeScript without needing to compile it to a file first 1.

{
    "scripts": {
        "cli": "tsx ./tools/cli.ts"
    }
}

For demonstration purposes the code simply logs the provided arguments. Node always provides the first two arguments, the path to the Node executable and the path to the script being run. The rest are the arguments provided to the script.

console.log(process.argv)

Now when I run the script with npm run cli I get the following output on the mentioned platforms:

# Linux
$ npm run cli

> lloydatkinson.net@0.0.1 cli
> tsx ./tools/cli "--hello" "--world" "123"

[
  '/home/lloyd/.nvm/versions/node/v16.14.0/bin/node',
  '/mnt/c/Users/Lloyd/source/repos/lloydatkinson.net/tools/cli',
  '--hello',
  '--world',
  '123'
]

# Windows with cmd.exe
npm run cli -- --hello --world 123

> lloydatkinson.net@0.0.1 cli
> tsx ./tools/cli

[
  'C:\\Program Files\\nodejs\\node.exe',
  'C:\\Users\\Lloyd\\source\\repos\\lloydatkinson.net\\tools\\cli',
  '--hello',
  '--world',
  '123' 
]

# Windows with PowerShell
npm run cli -- --hello --world 123     

> lloydatkinson.net@0.0.1 cli
> tsx ./tools/cli

[
  'C:\\Program Files\\nodejs\\node.exe',
  'C:\\Users\\Lloyd\\source\\repos\\lloydatkinson.net\\tools\\cli',
  '123' <-- Missing arguments!
]

Why does this happen and how do we fix it?

Fortunately the “fix” is straightforward for PowerShell users. Add an extra double hyphen to the command or a triple hyphen.

# Windows with PowerShell but with correct arguments
npm run cli -- -- --hello --world 123

> lloydatkinson.net@0.0.1 cli
> tsx ./tools/cli

[
  'C:\\Program Files\\nodejs\\node.exe',
  'C:\\Users\\Lloyd\\source\\repos\\lloydatkinson.net\\tools\\cli',
  '--hello',
  '--world',
  '123'
]

I put fix in quotes because this feels like a bug that should be fixed. I was not entirely sure where the fault lies though. As this Stack Overflow answer explains PowerShell supports the expected behaviour of the double hyphen as we have seen with Bash.

I eventually found a bug report on NPM’s GitHub repository that has been open since April 2021 with many users having the same problem. The further down I read the more I realise this seemingly simple bug has a lot of hidden complexity and probably some technical debt too.

On a more positive note, though, an easy workaround is available! I hope this post helps someone else who is encountering this problem.

Consequences

This affects tools within the Node ecosystem for Windows users and the documentation or guides for any tools showing the usage of double hyphen need to be updated. I have seen numerous examples of ESLint with the arguments being provided via the terminal instead of NPM scripts.

This is usually done to demonstrate how to use it before the user then adds it to their NPM scripts. I have been caught out by this myself. Until now I did not understand why eslint -- --fix did absolutely nothing - because I was using Node 16 on Windows with PowerShell. Not a great experience for users new to a tool believing their usage of the tool is incorrect.


Footnotes

Footnotes

  1. Not to be confused with TypeScript’s JSX support, tsx is a modern replacement for ts-node but with sane defaults such as ESM support by default. I also found it was able to import source files from ../src whereas with ts-node I encountered problem after problem.

Share:

Need help with your software project? Let's talk

Stay up to date

Subscribe to my newsletter to stay up to date on my articles and projects

Support me on Kofi