From bccb2c5018d4c906b0f44f5b861f66e8cadca43c Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Wed, 5 Jun 2024 17:02:48 -0400 Subject: [PATCH] fix(core): add quotes around string to command (#23056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Current Behavior ## Expected Behavior - nx run will not have quotes, but underlying command will have Screenshot 2024-05-23 at 2 07 20 PM Screenshot 2024-05-23 at 2 07 03 PM ## Related Issue(s) Fixes # --- e2e/nx/src/run.test.ts | 26 ++++++++++ packages/nx/bin/init-local.ts | 4 -- .../run-commands/run-commands.impl.spec.ts | 50 +++++++++++++++++++ .../run-commands/run-commands.impl.ts | 23 ++++++++- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/e2e/nx/src/run.test.ts b/e2e/nx/src/run.test.ts index dd8ca216eb..0518850a1b 100644 --- a/e2e/nx/src/run.test.ts +++ b/e2e/nx/src/run.test.ts @@ -60,6 +60,32 @@ describe('Nx Running Tests', () => { const output = runCLI(`echo ${proj} ${args}`); expect(output).toContain(`ECHO: ${args.replace(/^.*-- /, '')}`); }); + + it.each([ + { + args: '--test="hello world" "abc def"', + result: '--test="hello world" "abc def"', + }, + { + args: `--test="hello world" 'abc def'`, + result: '--test="hello world" "abc def"', + }, + { + args: `--test="hello world" 'abcdef'`, + result: '--test="hello world" abcdef', + }, + { + args: `--test='hello world' 'abcdef'`, + result: '--test="hello world" abcdef', + }, + { + args: `"--test='hello world' 'abcdef'"`, + result: `--test='hello world' 'abcdef'`, + }, + ])('should forward %args properly with quotes', ({ args, result }) => { + const output = runCLI(`echo ${proj} ${args}`); + expect(output).toContain(`ECHO: ${result}`); + }); }); it('should execute long running tasks', () => { diff --git a/packages/nx/bin/init-local.ts b/packages/nx/bin/init-local.ts index 41a8f02b59..9ec83bca08 100644 --- a/packages/nx/bin/init-local.ts +++ b/packages/nx/bin/init-local.ts @@ -81,10 +81,6 @@ export function rewriteTargetsAndProjects(args: string[]) { return newArgs; } -function wrapIntoQuotesIfNeeded(arg: string) { - return arg.indexOf(':') > -1 ? `"${arg}"` : arg; -} - function isKnownCommand(command: string) { const commands = [ ...Object.keys( diff --git a/packages/nx/src/executors/run-commands/run-commands.impl.spec.ts b/packages/nx/src/executors/run-commands/run-commands.impl.spec.ts index 65b79e9f10..aa2192f087 100644 --- a/packages/nx/src/executors/run-commands/run-commands.impl.spec.ts +++ b/packages/nx/src/executors/run-commands/run-commands.impl.spec.ts @@ -323,6 +323,56 @@ describe('Run Commands', () => { ) ).toEqual('echo "hello world"'); }); + + it('should interpolate provided values with spaces', () => { + expect( + interpolateArgsIntoCommand( + 'echo', + { + unknownOptions: { hello: 'test 123' }, + parsedArgs: { hello: 'test 123' }, + } as any, + true + ) + ).toEqual('echo --hello="test 123"'); // should wrap in quotes + + expect( + interpolateArgsIntoCommand( + 'echo', + { + unknownOptions: { hello: '"test 123"' }, + parsedArgs: { hello: '"test 123"' }, + } as any, + true + ) + ).toEqual('echo --hello="test 123"'); // should leave double quotes + + expect( + interpolateArgsIntoCommand( + 'echo', + { + unknownOptions: { hello: "'test 123'" }, + parsedArgs: { hello: "'test 123'" }, + } as any, + true + ) + ).toEqual("echo --hello='test 123'"); // should leave single quote + + expect( + interpolateArgsIntoCommand( + 'echo', + { + __unparsed__: [ + '--hello=test 123', + 'hello world', + '"random config"', + '456', + ], + } as any, + true + ) + ).toEqual(`echo --hello="test 123" "hello world" "random config" 456`); // should wrap aroound __unparsed__ args with key value + }); }); describe('--color', () => { diff --git a/packages/nx/src/executors/run-commands/run-commands.impl.ts b/packages/nx/src/executors/run-commands/run-commands.impl.ts index 4675de6944..af8c4c8abc 100644 --- a/packages/nx/src/executors/run-commands/run-commands.impl.ts +++ b/packages/nx/src/executors/run-commands/run-commands.impl.ts @@ -494,6 +494,7 @@ export function interpolateArgsIntoCommand( opts.parsedArgs[k] === opts.unknownOptions[k] ) .map((k) => `--${k}=${opts.unknownOptions[k]}`) + .map(wrapArgIntoQuotesIfNeeded) .join(' '); } if (opts.args) { @@ -505,7 +506,9 @@ export function interpolateArgsIntoCommand( opts.unparsedCommandArgs ); if (filterdParsedOptions.length > 0) { - args += ` ${filterdParsedOptions.join(' ')}`; + args += ` ${filterdParsedOptions + .map(wrapArgIntoQuotesIfNeeded) + .join(' ')}`; } } return `${command}${args}`; @@ -627,3 +630,21 @@ function registerProcessListener() { // will store results to the cache and will terminate this process }); } + +function wrapArgIntoQuotesIfNeeded(arg: string): string { + if (arg.includes('=')) { + const [key, value] = arg.split('='); + if ( + key.startsWith('--') && + value.includes(' ') && + !(value[0] === "'" || value[0] === '"') + ) { + return `${key}="${value}"`; + } + return arg; + } else if (arg.includes(' ') && !(arg[0] === "'" || arg[0] === '"')) { + return `"${arg}"`; + } else { + return arg; + } +}