11import { existsSync , promises as fs } from 'node:fs'
22import type { Writable } from 'node:stream'
3- import { isMainThread } from 'node:worker_threads'
43import type { ViteDevServer } from 'vite'
5- import { mergeConfig } from 'vite'
6- import { basename , dirname , join , normalize , relative , resolve } from 'pathe'
7- import fg from 'fast-glob'
4+ import { dirname , join , normalize , relative , resolve } from 'pathe'
85import mm from 'micromatch'
96import { ViteNodeRunner } from 'vite-node/client'
107import { SnapshotManager } from '@vitest/snapshot/manager'
@@ -14,7 +11,7 @@ import type { defineWorkspace } from 'vitest/config'
1411import { version } from '../../package.json' with { type : 'json' }
1512import { getTasks , hasFailed , noop , slash , toArray , wildcardPatternToRegExp } from '../utils'
1613import { getCoverageProvider } from '../integrations/coverage'
17- import { CONFIG_NAMES , configFiles , workspacesFiles as workspaceFiles } from '../constants'
14+ import { workspacesFiles as workspaceFiles } from '../constants'
1815import { rootDir } from '../paths'
1916import { WebSocketReporter } from '../api/setup'
2017import type { SerializedCoverageConfig } from '../runtime/config'
@@ -27,13 +24,14 @@ import { StateManager } from './state'
2724import { resolveConfig } from './config/resolveConfig'
2825import { Logger } from './logger'
2926import { VitestCache } from './cache'
30- import { WorkspaceProject , initializeProject } from './workspace'
27+ import { WorkspaceProject } from './workspace'
3128import { VitestPackageInstaller } from './packageInstaller'
3229import { BlobReporter , readBlobs } from './reporters/blob'
3330import { FilesNotFoundError , GitNotFoundError } from './errors'
34- import type { ResolvedConfig , UserConfig , UserWorkspaceConfig , VitestRunMode } from './types/config'
31+ import type { ResolvedConfig , UserConfig , VitestRunMode } from './types/config'
3532import type { Reporter } from './types/reporter'
3633import type { CoverageProvider } from './types/coverage'
34+ import { resolveWorkspace } from './workspace/resolveWorkspace'
3735
3836const WATCHER_DEBOUNCE = 100
3937
@@ -192,7 +190,10 @@ export class Vitest {
192190 this . getCoreWorkspaceProject ( ) . provide ( key , value )
193191 }
194192
195- private async createCoreProject ( ) {
193+ /**
194+ * @internal
195+ */
196+ async _createCoreProject ( ) {
196197 this . coreWorkspaceProject = await WorkspaceProject . createCoreProject ( this )
197198 return this . coreWorkspaceProject
198199 }
@@ -241,160 +242,23 @@ export class Vitest {
241242 const workspaceConfigPath = await this . getWorkspaceConfigPath ( )
242243
243244 if ( ! workspaceConfigPath ) {
244- return [ await this . createCoreProject ( ) ]
245+ return [ await this . _createCoreProject ( ) ]
245246 }
246247
247248 const workspaceModule = await this . runner . executeFile ( workspaceConfigPath ) as {
248249 default : ReturnType < typeof defineWorkspace >
249250 }
250251
251252 if ( ! workspaceModule . default || ! Array . isArray ( workspaceModule . default ) ) {
252- throw new Error ( `Workspace config file ${ workspaceConfigPath } must export a default array of project paths.` )
253- }
254-
255- const workspaceGlobMatches : string [ ] = [ ]
256- const projectsOptions : UserWorkspaceConfig [ ] = [ ]
257-
258- for ( const project of workspaceModule . default ) {
259- if ( typeof project === 'string' ) {
260- workspaceGlobMatches . push ( project . replace ( '<rootDir>' , this . config . root ) )
261- }
262- else if ( typeof project === 'function' ) {
263- projectsOptions . push ( await project ( {
264- command : this . server . config . command ,
265- mode : this . server . config . mode ,
266- isPreview : false ,
267- isSsrBuild : false ,
268- } ) )
269- }
270- else {
271- projectsOptions . push ( await project )
272- }
273- }
274-
275- const globOptions : fg . Options = {
276- absolute : true ,
277- dot : true ,
278- onlyFiles : false ,
279- markDirectories : true ,
280- cwd : this . config . root ,
281- ignore : [ '**/node_modules/**' , '**/*.timestamp-*' ] ,
282- }
283-
284- const workspacesFs = await fg ( workspaceGlobMatches , globOptions )
285- const resolvedWorkspacesPaths = await Promise . all ( workspacesFs . filter ( ( file ) => {
286- if ( file . endsWith ( '/' ) ) {
287- // if it's a directory, check that we don't already have a workspace with a config inside
288- const hasWorkspaceWithConfig = workspacesFs . some ( ( file2 ) => {
289- return file2 !== file && `${ dirname ( file2 ) } /` === file
290- } )
291- return ! hasWorkspaceWithConfig
292- }
293- const filename = basename ( file )
294- return CONFIG_NAMES . some ( configName => filename . startsWith ( configName ) )
295- } ) . map ( async ( filepath ) => {
296- if ( filepath . endsWith ( '/' ) ) {
297- const filesInside = await fs . readdir ( filepath )
298- const configFile = configFiles . find ( config => filesInside . includes ( config ) )
299- return configFile ? join ( filepath , configFile ) : filepath
300- }
301- return filepath
302- } ) )
303-
304- const workspacesByFolder = resolvedWorkspacesPaths
305- . reduce ( ( configByFolder , filepath ) => {
306- const dir = filepath . endsWith ( '/' ) ? filepath . slice ( 0 , - 1 ) : dirname ( filepath )
307- configByFolder [ dir ] ??= [ ]
308- configByFolder [ dir ] . push ( filepath )
309- return configByFolder
310- } , { } as Record < string , string [ ] > )
311-
312- const filteredWorkspaces = Object . values ( workspacesByFolder ) . map ( ( configFiles ) => {
313- if ( configFiles . length === 1 ) {
314- return configFiles [ 0 ]
315- }
316- const vitestConfig = configFiles . find ( configFile => basename ( configFile ) . startsWith ( 'vitest.config' ) )
317- return vitestConfig || configFiles [ 0 ]
318- } )
319-
320- const overridesOptions = [
321- 'logHeapUsage' ,
322- 'allowOnly' ,
323- 'sequence' ,
324- 'testTimeout' ,
325- 'pool' ,
326- 'update' ,
327- 'globals' ,
328- 'expandSnapshotDiff' ,
329- 'disableConsoleIntercept' ,
330- 'retry' ,
331- 'testNamePattern' ,
332- 'passWithNoTests' ,
333- 'bail' ,
334- 'isolate' ,
335- 'printConsoleTrace' ,
336- ] as const
337-
338- const cliOverrides = overridesOptions . reduce ( ( acc , name ) => {
339- if ( name in cliOptions ) {
340- acc [ name ] = cliOptions [ name ] as any
341- }
342- return acc
343- } , { } as UserConfig )
344-
345- const cwd = process . cwd ( )
346-
347- const projects : WorkspaceProject [ ] = [ ]
348-
349- try {
350- // we have to resolve them one by one because CWD should depend on the project
351- for ( const filepath of filteredWorkspaces ) {
352- if ( this . server . config . configFile === filepath ) {
353- const project = await this . createCoreProject ( )
354- projects . push ( project )
355- continue
356- }
357- const dir = filepath . endsWith ( '/' ) ? filepath . slice ( 0 , - 1 ) : dirname ( filepath )
358- if ( isMainThread ) {
359- process . chdir ( dir )
360- }
361- projects . push (
362- await initializeProject ( filepath , this , { workspaceConfigPath, test : cliOverrides } ) ,
363- )
364- }
365- }
366- finally {
367- if ( isMainThread ) {
368- process . chdir ( cwd )
369- }
253+ throw new TypeError ( `Workspace config file "${ workspaceConfigPath } " must export a default array of project paths.` )
370254 }
371255
372- const projectPromises : Promise < WorkspaceProject > [ ] = [ ]
373-
374- projectsOptions . forEach ( ( options , index ) => {
375- // we can resolve these in parallel because process.cwd() is not changed
376- projectPromises . push ( initializeProject ( index , this , mergeConfig ( options , { workspaceConfigPath, test : cliOverrides } ) as any ) )
377- } )
378-
379- if ( ! projects . length && ! projectPromises . length ) {
380- return [ await this . createCoreProject ( ) ]
381- }
382-
383- const resolvedProjects = await Promise . all ( [
384- ...projects ,
385- ...await Promise . all ( projectPromises ) ,
386- ] )
387- const names = new Set < string > ( )
388-
389- for ( const project of resolvedProjects ) {
390- const name = project . getName ( )
391- if ( names . has ( name ) ) {
392- throw new Error ( `Project name "${ name } " is not unique. All projects in a workspace should have unique names.` )
393- }
394- names . add ( name )
395- }
396-
397- return resolvedProjects
256+ return resolveWorkspace (
257+ this ,
258+ cliOptions ,
259+ workspaceConfigPath ,
260+ workspaceModule . default ,
261+ )
398262 }
399263
400264 private async initCoverageProvider ( ) {
0 commit comments