From 36b5aceb9e146f790a60fc36d74359fdd209fcf9 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 13 Sep 2024 10:17:08 +0200 Subject: [PATCH] fix(workspace): resolve glob pattern once to avoid name collision (#6489) --- docs/guide/workspace.md | 7 +-- .../src/node/workspace/resolveWorkspace.ts | 61 +++++++++++-------- .../several-folders/apps/b/package.json | 3 + .../several-folders/apps/b/test.test.ts | 3 + .../several-folders/projects/a/package.json | 3 + .../several-folders/projects/a/test.test.ts | 3 + .../several-folders/vitest.workspace.ts | 4 ++ test/config/test/workspace.test.ts | 18 +++++- 8 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 test/config/fixtures/workspace/several-folders/apps/b/package.json create mode 100644 test/config/fixtures/workspace/several-folders/apps/b/test.test.ts create mode 100644 test/config/fixtures/workspace/several-folders/projects/a/package.json create mode 100644 test/config/fixtures/workspace/several-folders/projects/a/test.test.ts create mode 100644 test/config/fixtures/workspace/several-folders/vitest.workspace.ts diff --git a/docs/guide/workspace.md b/docs/guide/workspace.md index 8f55882b2c64..3952faca72ce 100644 --- a/docs/guide/workspace.md +++ b/docs/guide/workspace.md @@ -26,7 +26,7 @@ export default [ ``` ::: -Vitest will consider every folder in `packages` as a separate project even if it doesn't have a config file inside. +Vitest will consider every folder in `packages` as a separate project even if it doesn't have a config file inside. Since Vitest 2.1, if this glob pattern matches any file it will be considered a Vitest config even if it doesn't have a `vitest` in its name. ::: warning Vitest will not consider the root config as a workspace project (so it will not run tests specified in `include`) unless it is specified in this config. @@ -44,10 +44,6 @@ export default [ This pattern will only include projects with `vitest.config` file that includes `e2e` and `unit` before the extension. -::: warning -If you are referencing filenames with glob pattern, make sure your config file starts with `vite.config` or `vitest.config`. Otherwise Vitest will skip it. -::: - You can also define projects with inline config. Workspace file supports using both syntaxes at the same time. :::code-group @@ -56,6 +52,7 @@ import { defineWorkspace } from 'vitest/config' // defineWorkspace provides a nice type hinting DX export default defineWorkspace([ + // matches every folder and file inside the `packages` folder 'packages/*', { // add "extends" to merge two configs together diff --git a/packages/vitest/src/node/workspace/resolveWorkspace.ts b/packages/vitest/src/node/workspace/resolveWorkspace.ts index a9e4cc630fac..75c0785b3995 100644 --- a/packages/vitest/src/node/workspace/resolveWorkspace.ts +++ b/packages/vitest/src/node/workspace/resolveWorkspace.ts @@ -51,10 +51,11 @@ export async function resolveWorkspace( const cwd = process.cwd() const projects: WorkspaceProject[] = [] + const fileProjects = [...configFiles, ...nonConfigDirectories] try { // we have to resolve them one by one because CWD should depend on the project - for (const filepath of [...configFiles, ...nonConfigDirectories]) { + for (const filepath of fileProjects) { // if file leads to the root config, then we can just reuse it because we already initialized it if (vitest.server.config.configFile === filepath) { const project = await vitest._createCoreProject() @@ -111,12 +112,20 @@ export async function resolveWorkspace( const name = project.getName() if (names.has(name)) { const duplicate = resolvedProjects.find(p => p.getName() === name && p !== project)! + const filesError = fileProjects.length + ? [ + '\n\nYour config matched these files:\n', + fileProjects.map(p => ` - ${relative(vitest.config.root, p)}`).join('\n'), + '\n\n', + ].join('') + : [' '] throw new Error([ `Project name "${name}"`, project.server.config.configFile ? ` from "${relative(vitest.config.root, project.server.config.configFile)}"` : '', ' is not unique.', duplicate?.server.config.configFile ? ` The project is already defined by "${relative(vitest.config.root, duplicate.server.config.configFile)}".` : '', - ' All projects in a workspace should have unique names. Make sure your configuration is correct.', + filesError, + 'All projects in a workspace should have unique names. Make sure your configuration is correct.', ].join('')) } names.add(name) @@ -196,36 +205,36 @@ async function resolveWorkspaceProjectConfigs( else { projectsOptions.push(await definition) } + } - if (workspaceGlobMatches.length) { - const globOptions: GlobOptions = { - absolute: true, - dot: true, - onlyFiles: false, - cwd: vitest.config.root, - expandDirectories: false, - ignore: ['**/node_modules/**', '**/*.timestamp-*'], - } + if (workspaceGlobMatches.length) { + const globOptions: GlobOptions = { + absolute: true, + dot: true, + onlyFiles: false, + cwd: vitest.config.root, + expandDirectories: false, + ignore: ['**/node_modules/**', '**/*.timestamp-*'], + } - const workspacesFs = await glob(workspaceGlobMatches, globOptions) + const workspacesFs = await glob(workspaceGlobMatches, globOptions) - await Promise.all(workspacesFs.map(async (filepath) => { - // directories are allowed with a glob like `packages/*` - // in this case every directory is treated as a project - if (filepath.endsWith('/')) { - const configFile = await resolveDirectoryConfig(filepath) - if (configFile) { - workspaceConfigFiles.push(configFile) - } - else { - nonConfigProjectDirectories.push(filepath) - } + await Promise.all(workspacesFs.map(async (filepath) => { + // directories are allowed with a glob like `packages/*` + // in this case every directory is treated as a project + if (filepath.endsWith('/')) { + const configFile = await resolveDirectoryConfig(filepath) + if (configFile) { + workspaceConfigFiles.push(configFile) } else { - workspaceConfigFiles.push(filepath) + nonConfigProjectDirectories.push(filepath) } - })) - } + } + else { + workspaceConfigFiles.push(filepath) + } + })) } const projectConfigFiles = Array.from(new Set(workspaceConfigFiles)) diff --git a/test/config/fixtures/workspace/several-folders/apps/b/package.json b/test/config/fixtures/workspace/several-folders/apps/b/package.json new file mode 100644 index 000000000000..1c67e0d00b47 --- /dev/null +++ b/test/config/fixtures/workspace/several-folders/apps/b/package.json @@ -0,0 +1,3 @@ +{ + "name": "b" +} \ No newline at end of file diff --git a/test/config/fixtures/workspace/several-folders/apps/b/test.test.ts b/test/config/fixtures/workspace/several-folders/apps/b/test.test.ts new file mode 100644 index 000000000000..54a1e87c174d --- /dev/null +++ b/test/config/fixtures/workspace/several-folders/apps/b/test.test.ts @@ -0,0 +1,3 @@ +import { test } from 'vitest'; + +test('test - b') \ No newline at end of file diff --git a/test/config/fixtures/workspace/several-folders/projects/a/package.json b/test/config/fixtures/workspace/several-folders/projects/a/package.json new file mode 100644 index 000000000000..1aeaf2c81a7a --- /dev/null +++ b/test/config/fixtures/workspace/several-folders/projects/a/package.json @@ -0,0 +1,3 @@ +{ + "name": "a" +} \ No newline at end of file diff --git a/test/config/fixtures/workspace/several-folders/projects/a/test.test.ts b/test/config/fixtures/workspace/several-folders/projects/a/test.test.ts new file mode 100644 index 000000000000..125cc26d33ab --- /dev/null +++ b/test/config/fixtures/workspace/several-folders/projects/a/test.test.ts @@ -0,0 +1,3 @@ +import { test } from 'vitest'; + +test('test - a') \ No newline at end of file diff --git a/test/config/fixtures/workspace/several-folders/vitest.workspace.ts b/test/config/fixtures/workspace/several-folders/vitest.workspace.ts new file mode 100644 index 000000000000..ab04316f2736 --- /dev/null +++ b/test/config/fixtures/workspace/several-folders/vitest.workspace.ts @@ -0,0 +1,4 @@ +export default [ + 'projects/*', + 'apps/*' +] \ No newline at end of file diff --git a/test/config/test/workspace.test.ts b/test/config/test/workspace.test.ts index 7640843f1ed6..497454e3040f 100644 --- a/test/config/test/workspace.test.ts +++ b/test/config/test/workspace.test.ts @@ -25,13 +25,29 @@ it('runs the workspace if there are several vitest config files', async () => { expect(stdout).toContain('2 + 2 = 4') }) +it('correctly resolves workspace projects with a several folder globs', async () => { + const { stderr, stdout } = await runVitest({ + root: 'fixtures/workspace/several-folders', + workspace: './fixtures/workspace/several-folders/vitest.workspace.ts', + }) + expect(stderr).toBe('') + expect(stdout).toContain('test - a') + expect(stdout).toContain('test - b') +}) + it('fails if project names are identical with a nice error message', async () => { const { stderr } = await runVitest({ root: 'fixtures/workspace/invalid-duplicate-configs', workspace: './fixtures/workspace/invalid-duplicate-configs/vitest.workspace.ts', }, [], 'test', {}, { fails: true }) expect(stderr).toContain( - 'Project name "test" from "vitest2.config.js" is not unique. The project is already defined by "vitest1.config.js". All projects in a workspace should have unique names. Make sure your configuration is correct.', + `Project name "test" from "vitest2.config.js" is not unique. The project is already defined by "vitest1.config.js". + +Your config matched these files: + - vitest1.config.js + - vitest2.config.js + +All projects in a workspace should have unique names. Make sure your configuration is correct.`, ) })