From c793a13684b5c73fb2fa4992056dcf17a0d55b71 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 20 Feb 2024 14:28:00 +0100 Subject: [PATCH] feat(vitest): expose parseCLI method (#5248) --- docs/advanced/api.md | 10 ++++ packages/vitest/src/node/cli/cac.ts | 45 +++++++++++++++--- packages/vitest/src/node/index.ts | 1 + test/core/test/cli-test.test.ts | 71 ++++++++++++++++++++++++++++- 4 files changed, 119 insertions(+), 8 deletions(-) diff --git a/docs/advanced/api.md b/docs/advanced/api.md index 312c59d6ec87..d523123efdfd 100644 --- a/docs/advanced/api.md +++ b/docs/advanced/api.md @@ -46,6 +46,16 @@ const vitest = await createVitest('test', { }) ``` +## parseCLI + +You can use this method to parse CLI arguments. It accepts a string (where arguments are split by a single space) or a strings array of CLI arguments in the same format that Vitest CLI uses. It returns a filter and `options` that you can later pass down to `createVitest` or `startVitest` methods. + +```ts +import { parseCLI } from 'vitest/node' + +parseCLI('vitest ./files.ts --coverage --browser=chrome') +``` + ## Vitest Vitest instance requires the current test mode. It can be either: diff --git a/packages/vitest/src/node/cli/cac.ts b/packages/vitest/src/node/cli/cac.ts index 903b748445ad..8fd4587380aa 100644 --- a/packages/vitest/src/node/cli/cac.ts +++ b/packages/vitest/src/node/cli/cac.ts @@ -54,7 +54,11 @@ function addCommand(cli: CAC, name: string, option: CLIOption) { } } -export function createCLI() { +interface CLIOptions { + allowUnknownOptions?: boolean +} + +export function createCLI(options: CLIOptions = {}) { const cli = cac('vitest') cli @@ -141,23 +145,23 @@ export function createCLI() { }) cli - .command('run [...filters]') + .command('run [...filters]', undefined, options) .action(run) cli - .command('related [...filters]') + .command('related [...filters]', undefined, options) .action(runRelated) cli - .command('watch [...filters]') + .command('watch [...filters]', undefined, options) .action(watch) cli - .command('dev [...filters]') + .command('dev [...filters]', undefined, options) .action(watch) cli - .command('bench [...filters]') + .command('bench [...filters]', undefined, options) .action(benchmark) // TODO: remove in Vitest 2.0 @@ -168,12 +172,39 @@ export function createCLI() { }) cli - .command('[...filters]') + .command('[...filters]', undefined, options) .action((filters, options) => start('test', filters, options)) return cli } +export function parseCLI(argv: string | string[], config: CLIOptions = {}): { + filter: string[] + options: CliOptions +} { + const arrayArgs = typeof argv === 'string' ? argv.split(' ') : argv + if (arrayArgs[0] !== 'vitest') + throw new Error(`Expected "vitest" as the first argument, received "${arrayArgs[0]}"`) + arrayArgs[0] = '/index.js' + arrayArgs.unshift('node') + let { args, options } = createCLI(config).parse(arrayArgs, { + run: false, + }) + if (arrayArgs[2] === 'watch' || arrayArgs[2] === 'dev') + options.watch = true + if (arrayArgs[2] === 'run') + options.run = true + if (arrayArgs[2] === 'related') { + options.related = args + options.passWithNoTests ??= true + args = [] + } + return { + filter: args as string[], + options, + } +} + async function runRelated(relatedFiles: string[] | string, argv: CliOptions): Promise { argv.related = relatedFiles argv.passWithNoTests ??= true diff --git a/packages/vitest/src/node/index.ts b/packages/vitest/src/node/index.ts index d6b635fd29e8..ef5b65737633 100644 --- a/packages/vitest/src/node/index.ts +++ b/packages/vitest/src/node/index.ts @@ -3,6 +3,7 @@ export type { WorkspaceProject } from './workspace' export { createVitest } from './create' export { VitestPlugin } from './plugins' export { startVitest } from './cli/cli-api' +export { parseCLI } from './cli/cac' export { registerConsoleShortcuts } from './stdin' export type { GlobalSetupContext } from './globalSetup' export type { WorkspaceSpec, ProcessPool } from './pool' diff --git a/test/core/test/cli-test.test.ts b/test/core/test/cli-test.test.ts index 4bc5b3b7a69a..a3addf3749e2 100644 --- a/test/core/test/cli-test.test.ts +++ b/test/core/test/cli-test.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest' -import { createCLI } from '../../../packages/vitest/src/node/cli/cac.js' +import { createCLI, parseCLI } from '../../../packages/vitest/src/node/cli/cac.js' const vitestCli = createCLI() @@ -253,3 +253,72 @@ test('browser by name', () => { expect(args).toEqual([]) expect(options).toEqual({ browser: { enabled: true, name: 'firefox' } }) }) + +test('public parseCLI works correctly', () => { + expect(parseCLI('vitest dev')).toEqual({ + filter: [], + options: { + 'watch': true, + '--': [], + 'color': true, + }, + }) + expect(parseCLI('vitest watch')).toEqual({ + filter: [], + options: { + 'watch': true, + '--': [], + 'color': true, + }, + }) + expect(parseCLI('vitest run')).toEqual({ + filter: [], + options: { + 'run': true, + '--': [], + 'color': true, + }, + }) + expect(parseCLI('vitest related ./some-files.js')).toEqual({ + filter: [], + options: { + 'passWithNoTests': true, + 'related': ['./some-files.js'], + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest --coverage --browser=chrome')).toEqual({ + filter: [], + options: { + 'coverage': { enabled: true }, + 'browser': { enabled: true, name: 'chrome' }, + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest ./tests.js --coverage')).toEqual({ + filter: ['./tests.js'], + options: { + 'coverage': { enabled: true }, + '--': [], + 'color': true, + }, + }) + + expect(parseCLI('vitest ./tests.js --coverage --custom-options', { allowUnknownOptions: true })).toEqual({ + filter: ['./tests.js'], + options: { + 'coverage': { enabled: true }, + 'customOptions': true, + '--': [], + 'color': true, + }, + }) + + expect(() => { + parseCLI('node --test --coverage --browser --typecheck') + }).toThrowError(`Expected "vitest" as the first argument, received "node"`) +})