diff --git a/docs/guide/cli.md b/docs/guide/cli.md index 5a74291c58c0..825fd6e6e74a 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -106,7 +106,7 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim | `--typecheck [options]` | Custom options for typecheck pool. If passed without options, enables typechecking | | `--typecheck.enabled` | Enable typechecking alongside tests (default: `false`) | | `--typecheck.only` | Run only typecheck tests. This automatically enables typecheck (default: `false`) | -| `--project` | The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: `--project=1 --project=2` | +| `--project` | The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: `--project=1 --project=2`. You can also filter projects using wildcards like `--project=packages*` | | `-h, --help` | Display available CLI options | ::: tip diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts index ce0c7120ef84..28e1b48047e0 100644 --- a/packages/vitest/src/node/cli/cli-config.ts +++ b/packages/vitest/src/node/cli/cli-config.ts @@ -524,7 +524,7 @@ export const cliOptionsConfig: VitestCLIOptions = { }, }, project: { - description: 'The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: --project=1 --project=2', + description: 'The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: --project=1 --project=2. You can also filter projects using wildcards like --project=packages*', argument: '', array: true, }, diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index a4e5ee13aead..73500e99e185 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -11,7 +11,7 @@ import type { CancelReason, File } from '@vitest/runner' import { ViteNodeServer } from 'vite-node/server' import type { defineWorkspace } from 'vitest/config' import type { ArgumentsType, CoverageProvider, OnServerRestartHandler, Reporter, ResolvedConfig, UserConfig, UserWorkspaceConfig, VitestRunMode } from '../types' -import { hasFailed, noop, slash, toArray } from '../utils' +import { hasFailed, noop, slash, toArray, wildcardPatternToRegExp } from '../utils' import { getCoverageProvider } from '../integrations/coverage' import type { BrowserProvider } from '../types/browser' import { CONFIG_NAMES, configFiles, workspacesFiles as workspaceFiles } from '../constants' @@ -161,11 +161,14 @@ export class Vitest { await Promise.all(this._onSetServer.map(fn => fn())) const projects = await this.resolveWorkspace(cliOptions) - this.projects = projects this.resolvedProjects = projects - const filteredProjects = toArray(resolved.project) - if (filteredProjects.length) - this.projects = this.projects.filter(p => filteredProjects.includes(p.getName())) + this.projects = projects + const filters = toArray(resolved.project).map(s => wildcardPatternToRegExp(s)) + if (filters.length > 0) { + this.projects = this.projects.filter(p => + filters.some(pattern => pattern.test(p.getName())), + ) + } if (!this.coreWorkspaceProject) this.coreWorkspaceProject = WorkspaceProject.createBasicProject(this) diff --git a/packages/vitest/src/utils/base.ts b/packages/vitest/src/utils/base.ts index 3897cad739f7..eadf011419a0 100644 --- a/packages/vitest/src/utils/base.ts +++ b/packages/vitest/src/utils/base.ts @@ -160,3 +160,12 @@ export function setProcessTitle(title: string) { } catch {} } + +export function escapeRegExp(s: string) { + // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string +} + +export function wildcardPatternToRegExp(pattern: string): RegExp { + return new RegExp(`^${pattern.split('*').map(escapeRegExp).join('.*')}$`, 'i') +} diff --git a/test/cli/fixtures/project/packages/project_1/base.test.ts b/test/cli/fixtures/project/packages/project_1/base.test.ts new file mode 100644 index 000000000000..8d97874ff383 --- /dev/null +++ b/test/cli/fixtures/project/packages/project_1/base.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from "vitest"; + +test("", () => { + expect(1).toBe(1); +}) diff --git a/test/cli/fixtures/project/packages/project_2/base.test.ts b/test/cli/fixtures/project/packages/project_2/base.test.ts new file mode 100644 index 000000000000..8d97874ff383 --- /dev/null +++ b/test/cli/fixtures/project/packages/project_2/base.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from "vitest"; + +test("", () => { + expect(1).toBe(1); +}) diff --git a/test/cli/fixtures/project/packages/space_1/base.test.ts b/test/cli/fixtures/project/packages/space_1/base.test.ts new file mode 100644 index 000000000000..8d97874ff383 --- /dev/null +++ b/test/cli/fixtures/project/packages/space_1/base.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from "vitest"; + +test("", () => { + expect(1).toBe(1); +}) diff --git a/test/cli/fixtures/project/vitest.config.ts b/test/cli/fixtures/project/vitest.config.ts new file mode 100644 index 000000000000..cc579dcc148b --- /dev/null +++ b/test/cli/fixtures/project/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({}) diff --git a/test/cli/fixtures/project/vitest.workspace.ts b/test/cli/fixtures/project/vitest.workspace.ts new file mode 100644 index 000000000000..ab1d8a7a7bf3 --- /dev/null +++ b/test/cli/fixtures/project/vitest.workspace.ts @@ -0,0 +1,3 @@ +export default [ + 'packages/*' +] diff --git a/test/cli/test/project.test.ts b/test/cli/test/project.test.ts new file mode 100644 index 000000000000..cc84d3399e12 --- /dev/null +++ b/test/cli/test/project.test.ts @@ -0,0 +1,23 @@ +import { expect, test } from 'vitest' +import { runVitestCli } from '../../test-utils' + +test.each([ + { pattern: 'project_1', expected: ['project_1'] }, + { pattern: '*', expected: ['project_1', 'project_2', 'space_1'] }, + { pattern: '*j*', expected: ['project_1', 'project_2'] }, + { pattern: 'project*', expected: ['project_1', 'project_2'] }, + { pattern: 'space*', expected: ['space_1'] }, +])('should match projects correctly: $pattern', async ({ pattern, expected }) => { + const { stdout, stderr } = await runVitestCli( + 'run', + '--root', + 'fixtures/project', + '--project', + pattern, + ) + + expect(stderr).toBeFalsy() + expect(stdout).toBeTruthy() + + expected.forEach(name => expect(stdout).toContain(name)) +})