diff --git a/packages/vitest/src/node/cli/cac.ts b/packages/vitest/src/node/cli/cac.ts index 21afe0c668ec..36f8ca1f50e1 100644 --- a/packages/vitest/src/node/cli/cac.ts +++ b/packages/vitest/src/node/cli/cac.ts @@ -22,6 +22,7 @@ function addCommand(cli: CAC | Command, name: string, option: CLIOption) { `Expected a single value for option "${command}", received [${received}]`, ) } + value = removeQuotes(value) if (option.transform) { return option.transform(value) } @@ -196,11 +197,36 @@ export function createCLI(options: CliParseOptions = {}): CAC { return cli } +function removeQuotes(str: T): T { + if (typeof str !== 'string') { + if (Array.isArray(str)) { + return str.map(removeQuotes) as unknown as T + } + return str + } + if (str.startsWith('"') && str.endsWith('"')) { + return str.slice(1, -1) as unknown as T + } + if (str.startsWith(`'`) && str.endsWith(`'`)) { + return str.slice(1, -1) as unknown as T + } + return str +} + +function splitArgv(argv: string): string[] { + const reg = /(['"])(?:(?!\1).)+\1/g + argv = argv.replace(reg, match => match.replace(/\s/g, '\x00')) + return argv.split(' ').map((arg: string) => { + arg = arg.replace(/\0/g, ' ') + return removeQuotes(arg) + }) +} + export function parseCLI(argv: string | string[], config: CliParseOptions = {}): { filter: string[] options: CliOptions } { - const arrayArgs = typeof argv === 'string' ? argv.split(' ') : argv + const arrayArgs = typeof argv === 'string' ? splitArgv(argv) : argv if (arrayArgs[0] !== 'vitest') { throw new Error(`Expected "vitest" as the first argument, received "${arrayArgs[0]}"`) } diff --git a/test/core/test/cli-test.test.ts b/test/core/test/cli-test.test.ts index f6f1869cc7c4..4638bc734396 100644 --- a/test/core/test/cli-test.test.ts +++ b/test/core/test/cli-test.test.ts @@ -411,6 +411,51 @@ test('public parseCLI works correctly', () => { }, }) + expect(parseCLI('vitest --project="space 1"')).toEqual({ + filter: [], + options: { + 'project': ['space 1'], + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest "--project=space 1"')).toEqual({ + filter: [], + options: { + 'project': ['space 1'], + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest --project "space 1"')).toEqual({ + filter: [], + options: { + 'project': ['space 1'], + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest --project="space 1" --project="space 2"')).toEqual({ + filter: [], + options: { + 'project': ['space 1', 'space 2'], + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest ./test-1.js ./test-2.js --project="space 1" --project="space 2" --project="space 3"')).toEqual({ + filter: ['./test-1.js', './test-2.js'], + options: { + 'project': ['space 1', 'space 2', 'space 3'], + '--': [], + 'color': true, + }, + }) + expect(parseCLI('vitest --exclude=docs --exclude=demo')).toEqual({ filter: [], options: {