From b18d468c96b6636baab5bda6302972165b6aea94 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 23 Jun 2026 11:30:57 +0200 Subject: [PATCH 1/9] src: fix escaping of single quotes in task runner Signed-off-by: Antoine du Hamel --- src/node_task_runner.cc | 4 ++-- test/fixtures/run-script/package.json | 1 + test/fixtures/run-script/repeat-args.js | 3 +++ test/parallel/test-node-run.js | 14 ++++++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100755 test/fixtures/run-script/repeat-args.js diff --git a/src/node_task_runner.cc b/src/node_task_runner.cc index 3e15a70e72660c..22c02e83e12ed6 100644 --- a/src/node_task_runner.cc +++ b/src/node_task_runner.cc @@ -171,10 +171,10 @@ std::string EscapeShell(const std::string_view input) { escaped = std::regex_replace(escaped, leadingQuotePairs, ""); escaped = std::regex_replace(escaped, tripleSingleQuote, "\\\""); #else - // Replace single quotes("'") with "\\'" and wrap the result + // Replace single quotes("'") with `'"'"'` and wrap the result // in single quotes. std::string escaped = - std::regex_replace(std::string(input), std::regex("'"), "\\'"); + std::regex_replace(std::string(input), std::regex("'"), "'\"'\"'"); escaped = "'" + escaped + "'"; // Remove excessive quote pairs and handle edge cases static const std::regex tripleSingleQuote("\\\\'''"); diff --git a/test/fixtures/run-script/package.json b/test/fixtures/run-script/package.json index 138f47f2f97408..1a6b66475ca5cf 100644 --- a/test/fixtures/run-script/package.json +++ b/test/fixtures/run-script/package.json @@ -9,6 +9,7 @@ "custom-env-windows": "custom-env.bat", "path-env": "path-env", "path-env-windows": "path-env.bat", + "repeat-args": "node repeat-args.js", "special-env-variables": "special-env-variables", "special-env-variables-windows": "special-env-variables.bat", "pwd": "pwd", diff --git a/test/fixtures/run-script/repeat-args.js b/test/fixtures/run-script/repeat-args.js new file mode 100755 index 00000000000000..0a5cf7fd80a6b9 --- /dev/null +++ b/test/fixtures/run-script/repeat-args.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +console.log(JSON.stringify(process.argv.slice(2))); diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index 26295256849702..34597078d59e42 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -156,6 +156,20 @@ describe('node --run [command]', () => { assert.strictEqual(child.code, 0); }); + it('handles positional arguments with quotes', async () => { + const child = await common.spawnPromisified( + process.execPath, + [ '--run', 'repeat-args', '--', 'I think therefore I\'m'], + { cwd: fixtures.path('run-script') }, + ); + assert.deepStrictEqual(child, { + stdout: `["I think therefore I'm"]\n`, + stderr: '', + code: 0, + signal: null, + }); + }); + it('should set PATH environment variable with paths appended with node_modules/.bin', async () => { const child = await common.spawnPromisified( process.execPath, From 5725120c408a51595b6ab914d5a4cef3c5973a00 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 23 Jun 2026 11:45:30 +0200 Subject: [PATCH 2/9] fixup! src: fix escaping of single quotes in task runner --- test/cctest/test_node_task_runner.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cctest/test_node_task_runner.cc b/test/cctest/test_node_task_runner.cc index a2c520d2709f2e..007522ba854687 100644 --- a/test/cctest/test_node_task_runner.cc +++ b/test/cctest/test_node_task_runner.cc @@ -32,8 +32,8 @@ TEST_F(TaskRunnerTest, EscapeShell) { {"\\$1", "'\\$1'"}, {"--arg=\"$1\"", "'--arg=\"$1\"'"}, {"--arg=node exec -c \"$1\"", "'--arg=node exec -c \"$1\"'"}, - {"--arg=node exec -c '$1'", "'--arg=node exec -c \\'$1\\''"}, - {"'--arg=node exec -c \"$1\"'", "'\\'--arg=node exec -c \"$1\"\\''"} + {"--arg=node exec -c '$1'", "'--arg=node exec -c '\"'\"'$1'\"'\"''"}, + {"'--arg=node exec -c \"$1\"'", "'\\'--arg=node exec -c \"$1\"'\"'\"''"} #endif }; From 7bd8005bb549e14d36daef3b105267f030846467 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 23 Jun 2026 12:02:50 +0200 Subject: [PATCH 3/9] fixup! src: fix escaping of single quotes in task runner --- test/cctest/test_node_task_runner.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cctest/test_node_task_runner.cc b/test/cctest/test_node_task_runner.cc index 007522ba854687..a103563384de8a 100644 --- a/test/cctest/test_node_task_runner.cc +++ b/test/cctest/test_node_task_runner.cc @@ -28,12 +28,12 @@ TEST_F(TaskRunnerTest, EscapeShell) { {"test words", "'test words'"}, {"$1", "'$1'"}, {"\"$1\"", "'\"$1\"'"}, - {"'$1'", "'\\'$1\\''"}, + {"'$1'", "\"'\"'$1'\"'\""}, {"\\$1", "'\\$1'"}, {"--arg=\"$1\"", "'--arg=\"$1\"'"}, {"--arg=node exec -c \"$1\"", "'--arg=node exec -c \"$1\"'"}, {"--arg=node exec -c '$1'", "'--arg=node exec -c '\"'\"'$1'\"'\"''"}, - {"'--arg=node exec -c \"$1\"'", "'\\'--arg=node exec -c \"$1\"'\"'\"''"} + {"'--arg=node exec -c \"$1\"'", "\"'\"'--arg=node exec -c \"$1\"'\"'\""} #endif }; From 6786cf07b61bbc035985fa7dfe2f1b83a1e3849f Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 23 Jun 2026 17:08:31 +0200 Subject: [PATCH 4/9] fixup! src: fix escaping of single quotes in task runner --- test/cctest/test_node_task_runner.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cctest/test_node_task_runner.cc b/test/cctest/test_node_task_runner.cc index a103563384de8a..1c715124bd88ee 100644 --- a/test/cctest/test_node_task_runner.cc +++ b/test/cctest/test_node_task_runner.cc @@ -28,12 +28,12 @@ TEST_F(TaskRunnerTest, EscapeShell) { {"test words", "'test words'"}, {"$1", "'$1'"}, {"\"$1\"", "'\"$1\"'"}, - {"'$1'", "\"'\"'$1'\"'\""}, + {"'$1'", "\"'\"'$1'\"'\"''"}, {"\\$1", "'\\$1'"}, {"--arg=\"$1\"", "'--arg=\"$1\"'"}, {"--arg=node exec -c \"$1\"", "'--arg=node exec -c \"$1\"'"}, {"--arg=node exec -c '$1'", "'--arg=node exec -c '\"'\"'$1'\"'\"''"}, - {"'--arg=node exec -c \"$1\"'", "\"'\"'--arg=node exec -c \"$1\"'\"'\""} + {"'--arg=node exec -c \"$1\"'", "\"'\"'--arg=node exec -c \"$1\"'\"'\"''"} #endif }; From bb0f717c9d6c69bee60d267bca9ba63ef6a1b141 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 25 Jun 2026 18:43:11 +0200 Subject: [PATCH 5/9] fixup! src: fix escaping of single quotes in task runner --- test/message/node_run_non_existent.out | 1 + test/parallel/test-node-run.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/message/node_run_non_existent.out b/test/message/node_run_non_existent.out index 06b2ab0bb16a9a..0e450623704fd2 100644 --- a/test/message/node_run_non_existent.out +++ b/test/message/node_run_non_existent.out @@ -10,6 +10,7 @@ Available scripts are: custom-env-windows: custom-env.bat path-env: path-env path-env-windows: path-env.bat + repeat-args: node repeat-args.js special-env-variables: special-env-variables special-env-variables-windows: special-env-variables.bat pwd: pwd diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index 34597078d59e42..4b5aced61848c1 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -5,6 +5,7 @@ common.requireNoPackageJSONAbove(); const { it, describe } = require('node:test'); const assert = require('node:assert'); +const path = require('node:path'); const fixtures = require('../common/fixtures'); const envSuffix = common.isWindows ? '-windows' : ''; @@ -160,7 +161,10 @@ describe('node --run [command]', () => { const child = await common.spawnPromisified( process.execPath, [ '--run', 'repeat-args', '--', 'I think therefore I\'m'], - { cwd: fixtures.path('run-script') }, + { + cwd: fixtures.path('run-script'), + env: { ...process.env, PATH: `${path.dirname(process.execPath)}:${process.env.PATH}` }, + }, ); assert.deepStrictEqual(child, { stdout: `["I think therefore I'm"]\n`, From a9bfe5c93b32479b46e3cacd8422979457f36805 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 26 Jun 2026 11:53:03 +0200 Subject: [PATCH 6/9] fixup! src: fix escaping of single quotes in task runner --- .../node_modules/.bin/positional-args | 2 +- .../node_modules/.bin/positional-args.bat | 2 +- test/fixtures/run-script/repeat-args.js | 3 --- test/message/node_run_non_existent.out | 1 - test/parallel/test-node-run.js | 26 +++---------------- 5 files changed, 6 insertions(+), 28 deletions(-) delete mode 100755 test/fixtures/run-script/repeat-args.js diff --git a/test/fixtures/run-script/node_modules/.bin/positional-args b/test/fixtures/run-script/node_modules/.bin/positional-args index 2d8092378ba1da..18c7bfa0e466a7 100755 --- a/test/fixtures/run-script/node_modules/.bin/positional-args +++ b/test/fixtures/run-script/node_modules/.bin/positional-args @@ -1,3 +1,3 @@ #!/bin/bash echo "Arguments: '$@'" -echo "The total number of arguments are: $#" +echo "The total number of arguments is: $#" diff --git a/test/fixtures/run-script/node_modules/.bin/positional-args.bat b/test/fixtures/run-script/node_modules/.bin/positional-args.bat index 4b94ed34d51daf..35c80ceda496ea 100755 --- a/test/fixtures/run-script/node_modules/.bin/positional-args.bat +++ b/test/fixtures/run-script/node_modules/.bin/positional-args.bat @@ -12,4 +12,4 @@ for %%x in (%*) do ( ) @echo Raw '%*' @echo Arguments: '%output%' -@echo The total number of arguments are: %argv% +@echo The total number of arguments is: %argv% diff --git a/test/fixtures/run-script/repeat-args.js b/test/fixtures/run-script/repeat-args.js deleted file mode 100755 index 0a5cf7fd80a6b9..00000000000000 --- a/test/fixtures/run-script/repeat-args.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -console.log(JSON.stringify(process.argv.slice(2))); diff --git a/test/message/node_run_non_existent.out b/test/message/node_run_non_existent.out index 0e450623704fd2..06b2ab0bb16a9a 100644 --- a/test/message/node_run_non_existent.out +++ b/test/message/node_run_non_existent.out @@ -10,7 +10,6 @@ Available scripts are: custom-env-windows: custom-env.bat path-env: path-env path-env-windows: path-env.bat - repeat-args: node repeat-args.js special-env-variables: special-env-variables special-env-variables-windows: special-env-variables.bat pwd: pwd diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index 4b5aced61848c1..4a6e7db5c57e8e 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -5,7 +5,6 @@ common.requireNoPackageJSONAbove(); const { it, describe } = require('node:test'); const assert = require('node:assert'); -const path = require('node:path'); const fixtures = require('../common/fixtures'); const envSuffix = common.isWindows ? '-windows' : ''; @@ -144,36 +143,19 @@ describe('node --run [command]', () => { it('appends positional arguments', async () => { const child = await common.spawnPromisified( process.execPath, - [ '--run', `positional-args${envSuffix}`, '--', '--help "hello world test"', 'A', 'B', 'C'], + [ '--run', `positional-args${envSuffix}`, '--', '--help "hello world test"', 'A', 'B', 'C', 'I think therefore I\'m'], { cwd: fixtures.path('run-script') }, ); if (common.isWindows) { - assert.match(child.stdout, /Arguments: '--help ""hello world test"" A B C'/); + assert.match(child.stdout, /Arguments: '--help ""hello world test"" A B C "I think therefore I'm"'/); } else { - assert.match(child.stdout, /Arguments: '--help "hello world test" A B C'/); + assert.match(child.stdout, /Arguments: '--help "hello world test" A B C I think therefore I'm'/); } - assert.match(child.stdout, /The total number of arguments are: 4/); + assert.match(child.stdout, /The total number of arguments is: 5/); assert.strictEqual(child.stderr, ''); assert.strictEqual(child.code, 0); }); - it('handles positional arguments with quotes', async () => { - const child = await common.spawnPromisified( - process.execPath, - [ '--run', 'repeat-args', '--', 'I think therefore I\'m'], - { - cwd: fixtures.path('run-script'), - env: { ...process.env, PATH: `${path.dirname(process.execPath)}:${process.env.PATH}` }, - }, - ); - assert.deepStrictEqual(child, { - stdout: `["I think therefore I'm"]\n`, - stderr: '', - code: 0, - signal: null, - }); - }); - it('should set PATH environment variable with paths appended with node_modules/.bin', async () => { const child = await common.spawnPromisified( process.execPath, From eaa760589e3d32ff83211347ed23dba2c051d7d2 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 26 Jun 2026 12:30:37 +0200 Subject: [PATCH 7/9] fixup! src: fix escaping of single quotes in task runner --- test/fixtures/run-script/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/test/fixtures/run-script/package.json b/test/fixtures/run-script/package.json index 1a6b66475ca5cf..138f47f2f97408 100644 --- a/test/fixtures/run-script/package.json +++ b/test/fixtures/run-script/package.json @@ -9,7 +9,6 @@ "custom-env-windows": "custom-env.bat", "path-env": "path-env", "path-env-windows": "path-env.bat", - "repeat-args": "node repeat-args.js", "special-env-variables": "special-env-variables", "special-env-variables-windows": "special-env-variables.bat", "pwd": "pwd", From ca4bfa9b1d3cb7a988927987ac565a31e1d141bc Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 26 Jun 2026 20:23:35 +0200 Subject: [PATCH 8/9] fixup! src: fix escaping of single quotes in task runner --- test/parallel/test-node-run.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-node-run.js b/test/parallel/test-node-run.js index 4a6e7db5c57e8e..e24117f6b1658b 100644 --- a/test/parallel/test-node-run.js +++ b/test/parallel/test-node-run.js @@ -147,7 +147,7 @@ describe('node --run [command]', () => { { cwd: fixtures.path('run-script') }, ); if (common.isWindows) { - assert.match(child.stdout, /Arguments: '--help ""hello world test"" A B C "I think therefore I'm"'/); + assert.match(child.stdout, /Arguments: '--help ""hello world test"" A B C I think therefore I'm'/); } else { assert.match(child.stdout, /Arguments: '--help "hello world test" A B C I think therefore I'm'/); } From 8d523b17d66ee0c1cdb53a4575455304c6416152 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 26 Jun 2026 21:39:32 +0200 Subject: [PATCH 9/9] fixup! src: fix escaping of single quotes in task runner