Skip to content

Commit 945d909

Browse files
hi-ogawacodex
andauthored
fix!: don't lookup config file from ancestor directories (#10428)
Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
1 parent 7cb346c commit 945d909

12 files changed

Lines changed: 113 additions & 39 deletions

File tree

docs/config/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ If you are using Vite and have a `vite.config` file, Vitest will read it to matc
1010
- Pass `--config` option to CLI, e.g. `vitest --config ./path/to/vitest.config.ts`
1111
- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests
1212

13+
When an explicit `--config` option is not provided, Vitest looks for `vitest.config.{ts,mts,cts,js,mjs,cjs}` first and `vite.config.{ts,mts,cts,js,mjs,cjs}` second in the project [`root`](/config/root). If no config file is found, Vitest will run without one.
14+
1315
To configure `vitest` itself, add `test` property in your Vite config. You'll also need to add a reference to Vitest types using a [triple slash command](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of your config file, if you are importing `defineConfig` from `vite` itself.
1416

1517
If you are not using `vite`, add `defineConfig` imported from `vitest/config` to your config file:

docs/guide/migration.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ await expect.element(banner).toMatchTextContent(/error/i) // [!code ++]
125125
await expect.element(banner).toHaveTextContent('Error!')
126126
```
127127

128+
### Config Files Are Not Looked Up From Parent Directories
129+
130+
Vitest no longer searches parent directories for config files. If you previously relied on running `vitest` from a subdirectory while using a config file from a parent directory, pass the config explicitly and scope test discovery with `--dir`. For example,
131+
132+
```bash
133+
$ cd subdir && vitest # [!code --]
134+
$ cd subdir && vitest --config ../vitest.config.ts # [!code ++]
135+
```
136+
128137
## Migrating to Vitest 4.0 {#vitest-4}
129138

130139
::: warning Prerequisites

packages/vitest/LICENSE.md

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -316,23 +316,6 @@ Repository: egoist/cac
316316
317317
---------------------------------------
318318

319-
## empathic
320-
License: MIT
321-
By: Luke Edwards
322-
Repository: lukeed/empathic
323-
324-
> MIT License
325-
>
326-
> Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com)
327-
>
328-
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
329-
>
330-
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
331-
>
332-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
333-
334-
---------------------------------------
335-
336319
## flatted
337320
License: ISC
338321
By: Andrea Giammarchi

packages/vitest/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@
206206
"acorn-walk": "catalog:",
207207
"birpc": "catalog:",
208208
"cac": "catalog:",
209-
"empathic": "^2.0.0",
210209
"flatted": "catalog:",
211210
"happy-dom": "^20.8.3",
212211
"jsdom": "^27.4.0",

packages/vitest/src/create/browser/creator.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import { existsSync, readFileSync } from 'node:fs'
44
import { writeFile } from 'node:fs/promises'
55
import { dirname, relative, resolve } from 'node:path'
66
import { detectPackageManager, installPackage } from '@antfu/install-pkg'
7-
import * as find from 'empathic/find'
87
import prompt from 'prompts'
98
import { x } from 'tinyexec'
109
import c from 'tinyrainbow'
11-
import { configFiles } from '../../constants'
10+
import { findConfigFile } from '../../node/config/resolveConfig'
1211
import { generateExampleFiles } from './examples'
1312

1413
// eslint-disable-next-line no-console
@@ -424,9 +423,7 @@ export async function create(): Promise<void> {
424423
dependenciesToInstall.filter(pkg => !dependencies[pkg]),
425424
)
426425

427-
const rootConfig = find.any(configFiles, {
428-
cwd: process.cwd(),
429-
})
426+
const rootConfig = findConfigFile(process.cwd())
430427

431428
let scriptCommand = 'vitest'
432429

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
} from '../types/config'
1010
import type { CoverageOptions, CoverageReporterWithOptions } from '../types/coverage'
1111
import crypto from 'node:crypto'
12+
import { existsSync, statSync } from 'node:fs'
1213
import { pathToFileURL } from 'node:url'
1314
import { slash, toArray } from '@vitest/utils/helpers'
1415
import { resolveModule } from 'local-pkg'
@@ -45,6 +46,15 @@ function resolvePath(path: string, root: string) {
4546
)
4647
}
4748

49+
export function findConfigFile(root: string): string | undefined {
50+
for (const configFile of configFiles) {
51+
const configPath = resolve(root, configFile)
52+
if (existsSync(configPath)) {
53+
return configPath
54+
}
55+
}
56+
}
57+
4858
function parseInspector(inspect: string | undefined | boolean | number) {
4959
if (typeof inspect === 'boolean' || inspect === undefined) {
5060
return {}
@@ -178,6 +188,11 @@ export function resolveConfig(
178188
root: viteConfig.root,
179189
} as any as ResolvedConfig
180190

191+
const rootStats = statSync(resolved.root, { throwIfNoEntry: false })
192+
if (!rootStats?.isDirectory()) {
193+
throw new Error(`Root path does not exist or is not a directory: ${resolved.root}`)
194+
}
195+
181196
resolved.mode ??= viteConfig.mode ?? 'test'
182197

183198
if (resolved.retry && typeof resolved.retry === 'object' && typeof resolved.retry.condition === 'function') {

packages/vitest/src/node/create.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import type { VitestOptions } from './core'
77
import type { VitestRunMode } from './types/config'
88
import { resolve } from 'node:path'
99
import { deepClone, slash } from '@vitest/utils/helpers'
10-
import * as find from 'empathic/find'
1110
import { resolveModule } from 'local-pkg'
1211
import { mergeConfig } from 'vite'
13-
import { configFiles } from '../constants'
12+
import { findConfigFile } from './config/resolveConfig'
1413
import { Vitest } from './core'
1514
import { VitestPlugin } from './plugins'
1615
import { createViteServer } from './vite'
@@ -56,7 +55,7 @@ export async function createVitest(
5655
? false
5756
: options.config
5857
? (resolveModule(options.config, { paths: [root] }) ?? resolve(root, options.config))
59-
: find.any(configFiles, { cwd: root })
58+
: findConfigFile(root)
6059

6160
options.config = configPath
6261

packages/vitest/src/node/plugins/publicConfig.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import type {
44
} from 'vite'
55
import type { ResolvedConfig, UserConfig } from '../types/config'
66
import { deepClone, slash } from '@vitest/utils/helpers'
7-
import * as find from 'empathic/find'
87
import { resolve } from 'pathe'
98
import { mergeConfig, resolveConfig as resolveViteConfig } from 'vite'
10-
import { configFiles } from '../../constants'
11-
import { resolveConfig as resolveVitestConfig } from '../config/resolveConfig'
9+
import { findConfigFile, resolveConfig as resolveVitestConfig } from '../config/resolveConfig'
1210
import { Vitest } from '../core'
1311
import { VitestPlugin } from './index'
1412

@@ -24,7 +22,7 @@ export async function resolveConfig(
2422
? false
2523
: options.config
2624
? resolve(root, options.config)
27-
: find.any(configFiles, { cwd: root })
25+
: findConfigFile(root)
2826
options.config = configPath
2927

3028
const vitest = new Vitest(deepClone(options))

pnpm-lock.yaml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/coverage-test/test/include-exclude.unit.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { BaseCoverageProvider, CoverageOptions } from 'vitest/node'
2+
import { mkdirSync, rmSync } from 'node:fs'
23
import { join, resolve, sep } from 'node:path'
34
import { Writable } from 'node:stream'
45
import { expect, onTestFinished, test } from 'vitest'
@@ -145,15 +146,22 @@ test('files outside project when allowExternal: true', async () => {
145146
})
146147

147148
test('files with almost matching name, outside project when allowExternal: false', async () => {
149+
const parent = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
150+
const root = resolve(parent, 'something')
151+
mkdirSync(root, { recursive: true })
152+
onTestFinished(() => {
153+
rmSync(parent, { recursive: true, force: true })
154+
})
155+
148156
const isIncluded = await init({
149157
include: ['**/*.ts'],
150-
root: './something/',
158+
root,
151159
allowExternal: false,
152160
})
153161

154-
expect(isIncluded(resolve(process.cwd(), './something/src/one.ts'))).toBe(true)
155-
expect(isIncluded(resolve(process.cwd(), './not-something/src/two.ts'))).toBe(false)
156-
expect(isIncluded(resolve(process.cwd(), './something-else/src/three.ts'))).toBe(false)
162+
expect(isIncluded(resolve(parent, './something/src/one.ts'))).toBe(true)
163+
expect(isIncluded(resolve(parent, './not-something/src/two.ts'))).toBe(false)
164+
expect(isIncluded(resolve(parent, './something-else/src/three.ts'))).toBe(false)
157165
})
158166

159167
async function init(options: Partial<CoverageOptions> & { testInclude?: string[]; root?: string }) {

0 commit comments

Comments
 (0)