concurrently icon indicating copy to clipboard operation
concurrently copied to clipboard

escaping pass-through arguments is too eager

Open soletan opened this issue 1 year ago • 7 comments

Background

When trying to use concurrently with --passthrough-arguments option, any passed arguments get escaped too eagerly.

The issue has been observed while developing an application when trying to concurrently run a local web server and gherkin-testcafe for testing with the latter requring tags to start with @ character.

Since that application can't be shared here for legal reasons I've tried a simpler scenario which is reproducing the issue, too.

Scenario

  • windows platform
  • npm 10.5.2
  • node 20.13.1
  • concurrently 8.2.2
  • files:
    • package.json contains scripts:
      {"scripts": {
          "foo": "concurrently -k -n \"bar,baz\" -P npm:foo:bar \"npm:foo:baz -- {@}\"",
          "foo:bar": "node block.js",
          "foo:baz": "node log.js",
      }}
      
    • file block.js looks like this:
      setTimeout(() => console.log("delayed"), 1000);
      
    • file log.js looks like this:
      console.dir(process.argv);
      

Test

invoke without arguments

$ npm run foo

> [email protected] foo
> concurrently -k -n "bar,baz" -P npm:foo:bar "npm:foo:baz -- {@}"

[bar] 
[bar] > [email protected] foo:bar
[bar] > node block.js
[bar]
[baz]
[baz] > [email protected] foo:baz
[baz] > node log.js
[baz]
[baz] [
[baz]   'C:\\Program Files\\nodejs\\node.exe',
[baz]   'C:\\Users\\cepharum\\PhpstormProjects\\UID\\Logistics\\WebClient\\Source\\UiNew\\log.js'
[baz] ]
[baz] npm run foo:baz --  exited with code 0
--> Sending SIGTERM to other processes..
[bar] npm run foo:bar exited with code 1

looks good ... setup is working as expected

invoke with arguments to pass

$ npm run foo -- -- --tags=@foo

> [email protected] foo
> concurrently -k -n "bar,baz" -P npm:foo:bar "npm:foo:baz -- {@}" -- --tags=@foo

[bar]
[bar] > [email protected] foo:bar
[bar] > node block.js
[bar]
[baz]
[baz] > [email protected] foo:baz
[baz] > node log.js --tags\=\@foo
[baz]
[baz] [
[baz]   'C:\\Program Files\\nodejs\\node.exe',
[baz]   'C:\\Users\\cepharum\\PhpstormProjects\\UID\\Logistics\\WebClient\\Source\\UiNew\\log.js',
[baz]   '--tags\\=\\@foo'
[baz] ]
[baz] npm run foo:baz -- --tags\=\@foo exited with code 0
--> Sending SIGTERM to other processes..
[bar] npm run foo:bar exited with code 1

Problem here:

  • the assignment operator gets escaped
  • the @ gets escaped

Escaping seems to be eligible due to cmd.exe being involved. But eventually that escaping is found in invoked sub-process, too. Trying to fix it in that sub-process is not an option for it is a third-party application (gherkin-testcafe).

gherkin-testcafe illustrates the provision of tags without assignment operator, so I tried that one, too:

$ npm run foo -- -- --tags @foo

> [email protected] foo
> concurrently -k -n "bar,baz" -P npm:foo:bar "npm:foo:baz -- {@}" -- --tags @foo

[baz] 
[baz] > [email protected] foo:baz
[baz] > node log.js --tags \@foo
[baz] 
[bar] 
[bar] > [email protected] foo:bar
[bar] > node block.js
[bar] 
[baz] [
[baz]   'C:\\Program Files\\nodejs\\node.exe',
[baz]   'C:\\Users\\cepharum\\PhpstormProjects\\UID\\Logistics\\WebClient\\Source\\UiNew\\log.js',
[baz]   '--tags',
[baz]   '\\@foo'
[baz] ]
[baz] npm run foo:baz -- --tags \@foo exited with code 0
--> Sending SIGTERM to other processes..
[bar] npm run foo:bar exited with code 1

The @ character still ends up escaped.

I've also tried using {*} on invoking concurrently. So, the script in package.json is now:

"foo": "concurrently -k -n \"bar,baz\" -P npm:foo:bar \"npm:foo:baz -- {*}\"",

Invoking as before prevents the escaping, but yields a different issue:

$ npm run foo -- -- --tags @foo

> [email protected] foo
> concurrently -k -n "bar,baz" -P npm:foo:bar "npm:foo:baz -- {*}" -- --tags @foo

[baz] 
[baz] > [email protected] foo:baz
[baz] > node log.js '--tags @foo'
[baz]
[bar]
[bar] > [email protected] foo:bar
[bar] > node block.js
[bar]
[baz] [
[baz]   'C:\\Program Files\\nodejs\\node.exe',
[baz]   'C:\\Users\\cepharum\\PhpstormProjects\\UID\\Logistics\\WebClient\\Source\\UiNew\\log.js',
[baz]   "'--tags",
[baz]   "@foo'"
[baz] ]
[baz] npm run foo:baz -- '--tags @foo' exited with code 0
--> Sending SIGTERM to other processes..
[bar] npm run foo:bar exited with code 1

So, the {*} seems to wrap all additional arguments in a single pair quotes. This may be intentional, but it definitely doesn't help here:

$ npm run foo -- -- --tags @foo --debug-on-fail

> [email protected] foo
> concurrently -k -n "bar,baz" -P npm:foo:bar "npm:foo:baz -- {*}" -- --tags @foo --debug-on-fail

[baz] 
[baz] > [email protected] foo:baz
[baz] > node log.js '--tags @foo --debug-on-fail'
[baz]
[bar]
[bar] > [email protected] foo:bar
[bar] > node block.js
[bar]
[baz] [
[baz]   'C:\\Program Files\\nodejs\\node.exe',
[baz]   'C:\\Users\\cepharum\\PhpstormProjects\\UID\\Logistics\\WebClient\\Source\\UiNew\\log.js',
[baz]   "'--tags",
[baz]   '@foo',
[baz]   "--debug-on-fail'"
[baz] ]
[baz] npm run foo:baz -- '--tags @foo --debug-on-fail' exited with code 0
--> Sending SIGTERM to other processes..
[bar] npm run foo:bar exited with code 1

soletan avatar Jun 14 '24 13:06 soletan

I've just cloned concurrently locally to create a build off current main branch, then linked it into that other project.

$ git clone https://github.com/open-cli-tools/concurrently.git
$ cd concurrently
$ npm i
$ npm build
$ npm link

Then, in my project:

$ npm link concurrently

The outcome is the same. ~~I thought it might have been related to that windowVerbatimArguments option set. But nope.~~ I'm a fool ...

soletan avatar Jun 14 '24 14:06 soletan

I've kept on trying to nail down the issue. From concurrently perspective, relying on shell-quote to do the job seems to cause this issue. As pointed out in the linked issue (comment1 comment2), maybe shell-quote isn't meant to support Windows' cmd.exe. What about concurrently? ... just asking.

soletan avatar Jun 14 '24 20:06 soletan

Hey, thanks for the report. Yes, concurrently should work on Windows. We have, however, been naive not to test with certain special characters such as @ and =, like you did.

I'm willing to be more lenient with escaping/wrapping in quotes than the shell-quote package if it means having less bugs like yours. E.g. arguments with a space need wrapping, unless they're already wrapped, of course. But I'm not sure about the others, might need to go through the list below and reimplement it in concurrently:

https://github.com/ljharb/shell-quote/blob/9eecafc0486c9321be223415cf3fb76a5bd07dda/quote.js#L14

(that said, I'm admittedly ignorant of risks of getting this wrong 😅 )

gustavohenke avatar Jun 22 '24 06:06 gustavohenke

Well, I was hesitant to provide a solution I found based on my follow-up investigations as documented in the related issue at shell-quote repository for I was hoping to get a solution done there. However, since I wasn't able to wait for that with the original issue I was facing I've created a patched version of concurrently with shell-quote being replaced with my package. Maybe, I should provide it as a PR here.

soletan avatar Jun 22 '24 10:06 soletan

I ran into this same issue.

This is the concurrently command I have in my package.json:

"build": "npx concurrently --passthrough-arguments \"npm:build-cms -- {@}\" \"npm:build-lms -- {@}\" -- ",

When used with the command npm run build -- --env=baseUrl='/' I get the following output:

❯  npm run build -- --env=baseUrl='/'

> [email protected] build
> npx concurrently --passthrough-arguments "npm:build-cms -- {@}" "npm:build-lms -- {@}" -- --env=baseUrl=/

[build-cms]
[build-cms] > [email protected] build-cms
[build-cms] > cd ./CMS && npx webpack --env\=baseUrl\=/
[build-cms]
[build-lms]
[build-lms] > [email protected] build-lms
[build-lms] > cd ./LMS && npx webpack --env\=baseUrl\=/
[build-lms]
[build-cms] [webpack-cli] Error: Unknown option '--env\=baseUrl\=/'
[build-lms] [webpack-cli] Error: Unknown option '--env\=baseUrl\=/'
[build-cms] [webpack-cli] Did you mean '--env'?
[build-lms] [webpack-cli] Did you mean '--env'?
[build-cms] [webpack-cli] Run 'webpack --help' to see available commands and options
[build-lms] [webpack-cli] Run 'webpack --help' to see available commands and options
[build-lms] npm run build-lms -- --env\=baseUrl\=/ exited with code 2
[build-cms] npm run build-cms -- --env\=baseUrl\=/ exited with code 2

Note how = is escaped and the ' characters are removed.

~~In my case, wrapping the pass-through argurments in double quotes results in everything being passed-through as expected:~~

Wrapping the pass-through arguments in double quotes results in everything looking like it would be ok:

❯  npm run build -- "--env=baseUrl='/'"

> [email protected] build
> npx concurrently --passthrough-arguments "npm:build-cms -- {@}" "npm:build-lms -- {@}" -- --env=baseUrl='/'

[build-lms]
[build-lms] > [email protected] build-lms
[build-lms] > cd ./LMS && npx webpack --env=baseUrl='/'
[build-lms]
[build-cms]
[build-cms] > [email protected] build-cms
[build-cms] > cd ./CMS && npx webpack --env=baseUrl='/'
[build-cms]
[build-cms] Base URL has been set to: '/'
[build-cms] API URL has been set to: '/'api

but the single quotes are erroneously being passed to the sub-command. When I run the webpack command directly in the relevant directory you can see the correct usage of the slash:

❯  npx webpack --env=baseUrl='/'
Base URL has been set to: /
API URL has been set to: /api

Version information:

Node: 18.20.8 npm: 10.8.2 concurrently: 8.2.2 PowerShell 7.4.7 Windows Terminal 1.22.10731.0

nathanpovo avatar Apr 30 '25 07:04 nathanpovo

I haven't had much time since reporting this issue and there is a PR I have to take another look into though basically it is working as I have tested myself in a fork which has been in use at our site since then.

soletan avatar Apr 30 '25 07:04 soletan

No problem. I just found this github issue while investigating my issue and wanted to document the issue on my side.

I actually updated my comment because my workaround didn't actually resolve the problem.

nathanpovo avatar Apr 30 '25 08:04 nathanpovo