11import { spawn } from "node:child_process" ;
2+ import fs from "node:fs" ;
23import { createRequire } from "node:module" ;
34import path from "node:path" ;
45import { isUiTestTarget , isUnitUiTestTarget } from "../test/vitest/vitest.ui-paths.mjs" ;
@@ -16,6 +17,60 @@ const ANSI_CSI_SUFFIX_RE = /^[0-?]*[ -/]*[@-~]/u;
1617const SUPPRESSED_VITEST_STDERR_PATTERNS = [ "[PLUGIN_TIMINGS]" ] ;
1718const UI_VITEST_CONFIG = "test/vitest/vitest.ui.config.ts" ;
1819const UNIT_UI_VITEST_CONFIG = "test/vitest/vitest.unit-ui.config.ts" ;
20+ const EXPLICIT_TEST_FILE_RE = / \. (?: t e s t | e 2 e | l i v e ) \. (?: [ c m ] ? [ j t ] s x ? ) $ / u;
21+ const GLOB_PATTERN_CHARS_RE = / [ * ? [ \] { } ] / u;
22+ const VITEST_OPTIONS_WITH_VALUE = new Set ( [
23+ "--attachmentsDir" ,
24+ "--bail" ,
25+ "--browser" ,
26+ "--config" ,
27+ "--configLoader" ,
28+ "-c" ,
29+ "--changed" ,
30+ "--dir" ,
31+ "--environment" ,
32+ "--exclude" ,
33+ "--execArgv" ,
34+ "--hookTimeout" ,
35+ "--inspect" ,
36+ "--inspect-brk" ,
37+ "--listTags" ,
38+ "--maxConcurrency" ,
39+ "--maxWorkers" ,
40+ "--mergeReports" ,
41+ "--mode" ,
42+ "--outputFile" ,
43+ "--pool" ,
44+ "--project" ,
45+ "--reporter" ,
46+ "--reporters" ,
47+ "--retry" ,
48+ "--root" ,
49+ "-r" ,
50+ "--sequence.shuffle.seed" ,
51+ "--shard" ,
52+ "--silent" ,
53+ "--slowTestThreshold" ,
54+ "--tagsFilter" ,
55+ "--teardownTimeout" ,
56+ "--testNamePattern" ,
57+ "-t" ,
58+ "--testTimeout" ,
59+ "--update" ,
60+ "-u" ,
61+ "--vmMemoryLimit" ,
62+ ] ) ;
63+ const VITEST_DOTTED_OPTIONS_WITH_VALUE_PREFIXES = [
64+ "--browser." ,
65+ "--coverage." ,
66+ "--diff." ,
67+ "--expect." ,
68+ "--experimental." ,
69+ "--outputFile." ,
70+ "--retry." ,
71+ "--sequence." ,
72+ "--typecheck." ,
73+ ] ;
1974const require = createRequire ( import . meta. url ) ;
2075
2176function isTruthyEnvValue ( value ) {
@@ -99,6 +154,69 @@ function hasExplicitVitestConfigArg(argv) {
99154 return argv . some ( ( arg ) => arg === "--config" || arg === "-c" || arg . startsWith ( "--config=" ) ) ;
100155}
101156
157+ function optionConsumesNextArg ( arg ) {
158+ if ( arg . includes ( "=" ) ) {
159+ return false ;
160+ }
161+ return (
162+ VITEST_OPTIONS_WITH_VALUE . has ( arg ) ||
163+ VITEST_DOTTED_OPTIONS_WITH_VALUE_PREFIXES . some ( ( prefix ) => arg . startsWith ( prefix ) )
164+ ) ;
165+ }
166+
167+ function isExplicitTestFileArg ( arg ) {
168+ if ( ! EXPLICIT_TEST_FILE_RE . test ( arg ) || GLOB_PATTERN_CHARS_RE . test ( arg ) ) {
169+ return false ;
170+ }
171+ return (
172+ path . isAbsolute ( arg ) || arg . startsWith ( "./" ) || arg . startsWith ( "../" ) || / [ / \\ ] / u. test ( arg )
173+ ) ;
174+ }
175+
176+ function collectExplicitTestFileArgs ( argv ) {
177+ const files = [ ] ;
178+ for ( let index = 0 ; index < argv . length ; index += 1 ) {
179+ const arg = argv [ index ] ;
180+ if ( arg === "--" ) {
181+ break ;
182+ }
183+ if ( optionConsumesNextArg ( arg ) ) {
184+ index += 1 ;
185+ continue ;
186+ }
187+ if ( arg . startsWith ( "-" ) ) {
188+ continue ;
189+ }
190+ if ( isExplicitTestFileArg ( arg ) ) {
191+ files . push ( arg ) ;
192+ }
193+ }
194+ return files ;
195+ }
196+
197+ function hasAlternateVitestRootArg ( argv ) {
198+ return argv . some (
199+ ( arg ) =>
200+ arg === "--root" ||
201+ arg === "-r" ||
202+ arg === "--dir" ||
203+ arg . startsWith ( "--root=" ) ||
204+ arg . startsWith ( "--dir=" ) ,
205+ ) ;
206+ }
207+
208+ export function resolveMissingExplicitTestFiles ( argv , cwd = process . cwd ( ) , fsImpl = fs ) {
209+ if ( hasExplicitVitestConfigArg ( argv ) || hasAlternateVitestRootArg ( argv ) ) {
210+ return [ ] ;
211+ }
212+ return collectExplicitTestFileArgs ( argv )
213+ . filter ( ( arg ) => {
214+ const filePath = path . isAbsolute ( arg ) ? arg : path . resolve ( cwd , arg ) ;
215+ return ! fsImpl . existsSync ( filePath ) ;
216+ } )
217+ . map ( ( arg ) => toRepoRelativeArg ( arg , cwd ) ) ;
218+ }
219+
102220function toRepoRelativeArg ( arg , cwd ) {
103221 const normalized = path . isAbsolute ( arg ) ? path . relative ( cwd , arg ) : arg ;
104222 return normalized . replaceAll ( path . sep , "/" ) . replace ( / ^ \. \/ / u, "" ) ;
@@ -309,6 +427,17 @@ function main(argv = process.argv.slice(2), env = process.env) {
309427 process . exit ( 1 ) ;
310428 }
311429
430+ const missingTestFiles = resolveMissingExplicitTestFiles ( argv ) ;
431+ if ( missingTestFiles . length > 0 ) {
432+ console . error (
433+ [
434+ "[vitest] explicit test file(s) not found:" ,
435+ ...missingTestFiles . map ( ( file ) => ` - ${ file } ` ) ,
436+ ] . join ( "\n" ) ,
437+ ) ;
438+ process . exit ( 1 ) ;
439+ }
440+
312441 const vitestArgs = resolveImplicitVitestArgs ( argv ) ;
313442 const { child, teardown } = spawnWatchedVitestProcess ( {
314443 pnpmArgs : [
0 commit comments