1
1
import { existsSync , promises as fs } from 'node:fs'
2
2
import type { Writable } from 'node:stream'
3
- import { isMainThread } from 'node:worker_threads'
4
3
import 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'
8
5
import mm from 'micromatch'
9
6
import { ViteNodeRunner } from 'vite-node/client'
10
7
import { SnapshotManager } from '@vitest/snapshot/manager'
@@ -14,7 +11,7 @@ import type { defineWorkspace } from 'vitest/config'
14
11
import { version } from '../../package.json' with { type : 'json' }
15
12
import { getTasks , hasFailed , noop , slash , toArray , wildcardPatternToRegExp } from '../utils'
16
13
import { getCoverageProvider } from '../integrations/coverage'
17
- import { CONFIG_NAMES , configFiles , workspacesFiles as workspaceFiles } from '../constants'
14
+ import { workspacesFiles as workspaceFiles } from '../constants'
18
15
import { rootDir } from '../paths'
19
16
import { WebSocketReporter } from '../api/setup'
20
17
import type { SerializedCoverageConfig } from '../runtime/config'
@@ -27,13 +24,14 @@ import { StateManager } from './state'
27
24
import { resolveConfig } from './config/resolveConfig'
28
25
import { Logger } from './logger'
29
26
import { VitestCache } from './cache'
30
- import { WorkspaceProject , initializeProject } from './workspace'
27
+ import { WorkspaceProject } from './workspace'
31
28
import { VitestPackageInstaller } from './packageInstaller'
32
29
import { BlobReporter , readBlobs } from './reporters/blob'
33
30
import { FilesNotFoundError , GitNotFoundError } from './errors'
34
- import type { ResolvedConfig , UserConfig , UserWorkspaceConfig , VitestRunMode } from './types/config'
31
+ import type { ResolvedConfig , UserConfig , VitestRunMode } from './types/config'
35
32
import type { Reporter } from './types/reporter'
36
33
import type { CoverageProvider } from './types/coverage'
34
+ import { resolveWorkspace } from './workspace/resolveWorkspace'
37
35
38
36
const WATCHER_DEBOUNCE = 100
39
37
@@ -192,7 +190,10 @@ export class Vitest {
192
190
this . getCoreWorkspaceProject ( ) . provide ( key , value )
193
191
}
194
192
195
- private async createCoreProject ( ) {
193
+ /**
194
+ * @internal
195
+ */
196
+ async _createCoreProject ( ) {
196
197
this . coreWorkspaceProject = await WorkspaceProject . createCoreProject ( this )
197
198
return this . coreWorkspaceProject
198
199
}
@@ -241,160 +242,23 @@ export class Vitest {
241
242
const workspaceConfigPath = await this . getWorkspaceConfigPath ( )
242
243
243
244
if ( ! workspaceConfigPath ) {
244
- return [ await this . createCoreProject ( ) ]
245
+ return [ await this . _createCoreProject ( ) ]
245
246
}
246
247
247
248
const workspaceModule = await this . runner . executeFile ( workspaceConfigPath ) as {
248
249
default : ReturnType < typeof defineWorkspace >
249
250
}
250
251
251
252
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.` )
370
254
}
371
255
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
+ )
398
262
}
399
263
400
264
private async initCoverageProvider ( ) {
0 commit comments