diff --git a/samples/monorepo-vitest-workspace/package.json b/samples/monorepo-vitest-workspace/package.json index 24117aa3..890e2f87 100644 --- a/samples/monorepo-vitest-workspace/package.json +++ b/samples/monorepo-vitest-workspace/package.json @@ -11,6 +11,6 @@ "devDependencies": { "@vitest/coverage-v8": "^2.1.4", "happy-dom": "^15.7.4", - "vitest": "^2.1.4" + "vitest": "^3.0.5" } } diff --git a/samples/monorepo-vitest-workspace/pnpm-lock.yaml b/samples/monorepo-vitest-workspace/pnpm-lock.yaml index 9a7a514e..286de513 100644 --- a/samples/monorepo-vitest-workspace/pnpm-lock.yaml +++ b/samples/monorepo-vitest-workspace/pnpm-lock.yaml @@ -10,13 +10,13 @@ importers: devDependencies: '@vitest/coverage-v8': specifier: ^2.1.4 - version: 2.1.9(vitest@2.1.9(happy-dom@15.11.7)(jsdom@26.0.0)) + version: 2.1.9(vitest@3.0.5(happy-dom@15.11.7)(jsdom@26.0.0)) happy-dom: specifier: ^15.7.4 version: 15.11.7 vitest: - specifier: ^2.1.4 - version: 2.1.9(happy-dom@15.11.7)(jsdom@26.0.0) + specifier: ^3.0.5 + version: 3.0.5(happy-dom@15.11.7)(jsdom@26.0.0) packages/react: dependencies: @@ -522,34 +522,34 @@ packages: '@vitest/browser': optional: true - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.0.5': + resolution: {integrity: sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==} - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + '@vitest/mocker@3.0.5': + resolution: {integrity: sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.0.5': + resolution: {integrity: sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==} - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.0.5': + resolution: {integrity: sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==} - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.0.5': + resolution: {integrity: sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==} - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.0.5': + resolution: {integrity: sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==} - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.0.5': + resolution: {integrity: sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==} agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} @@ -986,8 +986,8 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} pathval@2.0.0: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} @@ -1180,6 +1180,10 @@ packages: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyspy@3.0.2: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} @@ -1214,9 +1218,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} + vite-node@3.0.5: + resolution: {integrity: sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite@5.4.14: @@ -1250,20 +1254,23 @@ packages: terser: optional: true - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@3.0.5: + resolution: {integrity: sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.5 + '@vitest/ui': 3.0.5 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/debug': + optional: true '@types/node': optional: true '@vitest/browser': @@ -1731,7 +1738,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@2.1.9(vitest@2.1.9(happy-dom@15.11.7)(jsdom@26.0.0))': + '@vitest/coverage-v8@2.1.9(vitest@3.0.5(happy-dom@15.11.7)(jsdom@26.0.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -1745,49 +1752,49 @@ snapshots: std-env: 3.8.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.9(happy-dom@15.11.7)(jsdom@26.0.0) + vitest: 3.0.5(happy-dom@15.11.7)(jsdom@26.0.0) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.9': + '@vitest/expect@3.0.5': dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + '@vitest/spy': 3.0.5 + '@vitest/utils': 3.0.5 chai: 5.1.2 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/mocker@2.1.9(vite@5.4.14)': + '@vitest/mocker@3.0.5(vite@5.4.14)': dependencies: - '@vitest/spy': 2.1.9 + '@vitest/spy': 3.0.5 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: vite: 5.4.14 - '@vitest/pretty-format@2.1.9': + '@vitest/pretty-format@3.0.5': dependencies: - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/runner@2.1.9': + '@vitest/runner@3.0.5': dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 + '@vitest/utils': 3.0.5 + pathe: 2.0.3 - '@vitest/snapshot@2.1.9': + '@vitest/snapshot@3.0.5': dependencies: - '@vitest/pretty-format': 2.1.9 + '@vitest/pretty-format': 3.0.5 magic-string: 0.30.17 - pathe: 1.1.2 + pathe: 2.0.3 - '@vitest/spy@2.1.9': + '@vitest/spy@3.0.5': dependencies: tinyspy: 3.0.2 - '@vitest/utils@2.1.9': + '@vitest/utils@3.0.5': dependencies: - '@vitest/pretty-format': 2.1.9 + '@vitest/pretty-format': 3.0.5 loupe: 3.1.3 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 agent-base@7.1.3: {} @@ -2230,7 +2237,7 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - pathe@1.1.2: {} + pathe@2.0.3: {} pathval@2.0.0: {} @@ -2455,6 +2462,8 @@ snapshots: tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyspy@3.0.2: {} tldts-core@6.1.77: {} @@ -2483,12 +2492,12 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@2.1.9: + vite-node@3.0.5: dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.6.0 - pathe: 1.1.2 + pathe: 2.0.3 vite: 5.4.14 transitivePeerDependencies: - '@types/node' @@ -2509,27 +2518,27 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - vitest@2.1.9(happy-dom@15.11.7)(jsdom@26.0.0): + vitest@3.0.5(happy-dom@15.11.7)(jsdom@26.0.0): dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.14) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + '@vitest/expect': 3.0.5 + '@vitest/mocker': 3.0.5(vite@5.4.14) + '@vitest/pretty-format': 3.0.5 + '@vitest/runner': 3.0.5 + '@vitest/snapshot': 3.0.5 + '@vitest/spy': 3.0.5 + '@vitest/utils': 3.0.5 chai: 5.1.2 debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.17 - pathe: 1.1.2 + pathe: 2.0.3 std-env: 3.8.0 tinybench: 2.9.0 tinyexec: 0.3.2 tinypool: 1.0.2 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 vite: 5.4.14 - vite-node: 2.1.9 + vite-node: 3.0.5 why-is-node-running: 2.3.0 optionalDependencies: happy-dom: 15.11.7 diff --git a/samples/monorepo-vitest-workspace/test/vitest.config.ts b/samples/monorepo-vitest-workspace/test/vitest.config.ts new file mode 100644 index 00000000..9e5305ed --- /dev/null +++ b/samples/monorepo-vitest-workspace/test/vitest.config.ts @@ -0,0 +1 @@ +throw new Error('should not be called') \ No newline at end of file diff --git a/samples/monorepo-vitest-workspace/vitest.config.ts b/samples/monorepo-vitest-workspace/vitest.config.ts index ff8b4c56..e1b448a2 100644 --- a/samples/monorepo-vitest-workspace/vitest.config.ts +++ b/samples/monorepo-vitest-workspace/vitest.config.ts @@ -1 +1,14 @@ -export default {}; +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + workspace: [ + 'packages/*', + { + test: { + environment: 'happy-dom', + }, + }, + ] + } +}) diff --git a/samples/monorepo-vitest-workspace/vitest.workspace.ts b/samples/monorepo-vitest-workspace/vitest.workspace.ts deleted file mode 100644 index e3554a17..00000000 --- a/samples/monorepo-vitest-workspace/vitest.workspace.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineWorkspace } from 'vitest/config' - -export default defineWorkspace([ - 'packages/*', - { - test: { - environment: 'happy-dom', - }, - }, -]) diff --git a/src/api.ts b/src/api.ts index 3aa32fd8..ca3f9985 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,3 +1,4 @@ +import { dirname, isAbsolute } from 'node:path' import { normalize, relative } from 'pathe' import * as vscode from 'vscode' import { log } from './log' @@ -101,6 +102,10 @@ export class VitestFolderAPI { return this.meta.configs } + get workspaceSource() { + return this.meta.workspaceSource + } + get version() { return this.pkg.version } @@ -264,20 +269,46 @@ function createQueuedHandler(resolver: (value: T[]) => Promise) { export async function resolveVitestAPI(workspaceConfigs: VitestPackage[], configs: VitestPackage[]) { const usedConfigs = new Set() const workspacePromises = workspaceConfigs.map(pkg => createVitestFolderAPI(usedConfigs, pkg)) + + if (workspacePromises.length) { + log.info('[API]', `Resolving workspace configs: ${workspaceConfigs.map(p => relative(p.folder.uri.fsPath, p.id)).join(', ')}`) + } + const apis = await Promise.all(workspacePromises) const configsToResolve = configs.filter((pkg) => { return !pkg.configFile || pkg.workspaceFile || !usedConfigs.has(pkg.configFile) + }).sort((a, b) => { + const depthA = a.id.split('/').length + const depthB = b.id.split('/').length + return depthA - depthB }) const maximumConfigs = getConfig().maximumConfigs ?? 3 + const workspaceRoots: string[] = apis + .map(api => api.workspaceSource ? dirname(api.workspaceSource) : null) + .filter(api => api != null) + let configsResolved = 0 + if (configsToResolve.length) { + log.info('[API]', `Resolving configs: ${configsToResolve.map(p => relative(p.folder.uri.fsPath, p.configFile!)).join(', ')}`) + } + // one by one because it's possible some of them have "workspace:" -- the configs are already sorted by priority for (const pkg of configsToResolve) { + // if the config is used by the workspace, ignore the config if (pkg.configFile && usedConfigs.has(pkg.configFile)) { + log.info('[API]', `Ignoring config ${relative(pkg.folder.uri.fsPath, pkg.configFile)} because it's already used by the workspace`) continue } + + // if the config is defined in the directory that is covered by the workspace, ignore the config + if (pkg.configFile && isCoveredByWorkspace(workspaceRoots, pkg.configFile)) { + log.info('[API]', `Ignoring config ${relative(pkg.folder.uri.fsPath, pkg.configFile)} because there is a workspace config in the parent folder`) + continue + } + configsResolved++ if (configsResolved > maximumConfigs) { @@ -287,11 +318,21 @@ export async function resolveVitestAPI(workspaceConfigs: VitestPackage[], config const api = await createVitestFolderAPI(usedConfigs, pkg) apis.push(api) + if (api.workspaceSource) { + workspaceRoots.push(dirname(api.workspaceSource)) + } } return new VitestAPI(apis) } +function isCoveredByWorkspace(workspacesRoots: string[], currentConfig: string): boolean { + return workspacesRoots.some((root) => { + const relative_ = relative(root, currentConfig) + return !relative_.startsWith('..') && !isAbsolute(relative_) + }) +} + function warnPerformanceConfigLimit(configsToResolve: VitestPackage[]) { const maximumConfigs = getConfig().maximumConfigs ?? 3 const warningMessage = [ @@ -346,6 +387,7 @@ async function createVitestFolderAPI(usedConfigs: Set, pkg: VitestPackag export interface ResolvedMeta { rpc: VitestRPC process: VitestProcess + workspaceSource: string | false pkg: VitestPackage configs: string[] handlers: { diff --git a/src/api/terminal.ts b/src/api/terminal.ts index 7b753462..83277fa9 100644 --- a/src/api/terminal.ts +++ b/src/api/terminal.ts @@ -50,6 +50,7 @@ export async function createVitestTerminalProcess(pkg: VitestPackage): Promise void): void abstract off(event: string, listener: (...args: any[]) => void): void - ready(configs: string[]) { - this.sendWorkerEvent({ type: 'ready', configs }) + ready(configs: string[], workspaceSource: string | false) { + this.sendWorkerEvent({ type: 'ready', configs, workspaceSource }) } error(err: any) { diff --git a/src/worker/init.ts b/src/worker/init.ts index df7a5bd2..94686e27 100644 --- a/src/worker/init.ts +++ b/src/worker/init.ts @@ -92,9 +92,15 @@ export async function initVitest(meta: WorkerMeta, options?: UserConfig) { 'getRootProject' in vitest ? vitest.getRootProject() : vitest.getCoreWorkspaceProject(), ...vitest.projects, ] as WorkspaceProject[]).map(p => p.server.config.configFile).filter(c => c != null) + const workspaceSource: string | false = meta.workspaceFile + ? meta.workspaceFile + : vitest.config.workspace != null + ? vitest.server.config.configFile || false + : false return { vitest, reporter, + workspaceSource, configs: Array.from(new Set(configs)), meta, } diff --git a/src/worker/types.ts b/src/worker/types.ts index 9fb5b90a..c401cee3 100644 --- a/src/worker/types.ts +++ b/src/worker/types.ts @@ -21,6 +21,7 @@ export interface WorkerRunnerOptions { export interface EventReady { type: 'ready' configs: string[] + workspaceSource: string | false } export interface EventDebug { diff --git a/src/worker/worker.ts b/src/worker/worker.ts index 1899a9f5..dfce193a 100644 --- a/src/worker/worker.ts +++ b/src/worker/worker.ts @@ -20,7 +20,7 @@ emitter.on('message', async function onMessage(message: any) { const data = message as WorkerRunnerOptions try { - const { reporter, vitest, configs } = await initVitest( + const { reporter, vitest, configs, workspaceSource } = await initVitest( data.meta, data.debug ? { @@ -43,7 +43,7 @@ emitter.on('message', async function onMessage(message: any) { deserialize: v => v8.deserialize(Buffer.from(v) as any), }) reporter.initRpc(rpc) - emitter.ready(configs) + emitter.ready(configs, workspaceSource) } catch (err: any) { emitter.error(err)