From 75c1eee2ec4fc6689aae05825d47f238b107109e Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:39:31 +0100 Subject: [PATCH 1/8] feat(vitest): add --stale CLI option types and config Add stale option definition to CLI config, type definition to UserConfig interface, and mutual exclusion check with --changed in resolveConfig. Co-authored-by: Sisyphus --- packages/vitest/src/node/cli/cli-config.ts | 4 ++++ packages/vitest/src/node/config/resolveConfig.ts | 8 ++++++++ packages/vitest/src/node/types/config.ts | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts index ddf7641096b2..ffcc4faabfac 100644 --- a/packages/vitest/src/node/cli/cli-config.ts +++ b/packages/vitest/src/node/cli/cli-config.ts @@ -474,6 +474,10 @@ export const cliOptionsConfig: VitestCLIOptions = { 'Run tests that are affected by the changed files (default: `false`)', argument: '[since]', }, + stale: { + description: + 'Run only tests that are stale. A test is stale when it or any of its dependencies have changed since the last run with --stale (default: `false`)', + }, sequence: { description: 'Options for how tests should be sorted', argument: '', diff --git a/packages/vitest/src/node/config/resolveConfig.ts b/packages/vitest/src/node/config/resolveConfig.ts index 33cb2b54531d..1d072e8ad933 100644 --- a/packages/vitest/src/node/config/resolveConfig.ts +++ b/packages/vitest/src/node/config/resolveConfig.ts @@ -741,6 +741,14 @@ export function resolveConfig( resolved.passWithNoTests ??= true } + if (resolved.stale) { + resolved.passWithNoTests ??= true + } + + if (resolved.stale && resolved.changed) { + throw new Error('Cannot use both --stale and --changed options at the same time') + } + resolved.css ??= {} if (typeof resolved.css === 'object') { resolved.css.modules ??= {} diff --git a/packages/vitest/src/node/types/config.ts b/packages/vitest/src/node/types/config.ts index e530e30e3ad3..7d3d4c757680 100644 --- a/packages/vitest/src/node/types/config.ts +++ b/packages/vitest/src/node/types/config.ts @@ -1042,6 +1042,14 @@ export interface UserConfig extends InlineConfig { */ changed?: boolean | string + /** + * Run only tests that are stale. A test is stale when it or any of its dependencies have changed since the last run with --stale. + * Uses filesystem mtime-based manifest stored in the cache directory. + * Mutually exclusive with --changed option. + * @default false + */ + stale?: boolean + /** * Test suite shard to execute in a format of /. * Will divide tests into a `count` numbers, and run only the `indexed` part. From 6839041cfa83daa8f9a9799fa71a146ea256cf11 Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:39:38 +0100 Subject: [PATCH 2/8] feat(vitest): add StaleManifest cache class for --stale option Create StaleManifest class for persistent mtime tracking, wire into VitestCache, and initialize in core.ts during setup. Co-authored-by: Sisyphus --- packages/vitest/src/node/cache/index.ts | 3 + packages/vitest/src/node/cache/stale.ts | 118 ++++++++++++++++++++++++ packages/vitest/src/node/core.ts | 11 ++- 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/vitest/src/node/cache/stale.ts diff --git a/packages/vitest/src/node/cache/index.ts b/packages/vitest/src/node/cache/index.ts index 296a0d0d1853..28fe0932d3d0 100644 --- a/packages/vitest/src/node/cache/index.ts +++ b/packages/vitest/src/node/cache/index.ts @@ -5,13 +5,16 @@ import { resolve } from 'pathe' import { hash } from '../hash' import { FilesStatsCache } from './files' import { ResultsCache } from './results' +import { StaleManifest } from './stale' export class VitestCache { results: ResultsCache stats: FilesStatsCache = new FilesStatsCache() + stale: StaleManifest constructor(logger: Logger) { this.results = new ResultsCache(logger) + this.stale = new StaleManifest(logger) } getFileTestResults(key: string): SuiteResultCache | undefined { diff --git a/packages/vitest/src/node/cache/stale.ts b/packages/vitest/src/node/cache/stale.ts new file mode 100644 index 000000000000..968d9ff57bb3 --- /dev/null +++ b/packages/vitest/src/node/cache/stale.ts @@ -0,0 +1,118 @@ +import type { Logger } from '../logger' +import type { ResolvedConfig } from '../types/config' +import fs, { existsSync } from 'node:fs' +import { rm } from 'node:fs/promises' +import { dirname, relative, resolve } from 'pathe' +import { Vitest } from '../core' + +export interface StaleManifestData { + version: string + timestamp: number + files: Record +} + +export class StaleManifest { + private manifest: StaleManifestData | null = null + private cachePath: string | null = null + private version: string + private root = '/' + + constructor(private logger: Logger) { + this.version = Vitest.version + } + + public getCachePath(): string | null { + return this.cachePath + } + + setConfig(root: string, config: ResolvedConfig['cache']): void { + this.root = root + if (config) { + this.cachePath = resolve(config.dir, 'stale.json') + } + } + + async clearCache(): Promise { + if (this.cachePath && existsSync(this.cachePath)) { + await rm(this.cachePath, { force: true, recursive: true }) + this.logger.log('[cache] cleared stale manifest at', this.cachePath) + } + this.manifest = null + } + + async readFromCache(): Promise { + if (!this.cachePath) { + return + } + + if (!fs.existsSync(this.cachePath)) { + return + } + + const staleData = await fs.promises.readFile(this.cachePath, 'utf8') + const parsed = JSON.parse(staleData || '{}') as StaleManifestData + const [major, minor] = parsed.version?.split('.') || ['0', '0'] + + // handling changed in 0.30.0 + if (Number(major) > 0 || Number(minor) >= 30) { + this.manifest = parsed + this.version = parsed.version + } + } + + async writeToCache(): Promise { + if (!this.cachePath || !this.manifest) { + return + } + + const cacheDirname = dirname(this.cachePath) + + if (!fs.existsSync(cacheDirname)) { + await fs.promises.mkdir(cacheDirname, { recursive: true }) + } + + const cache = JSON.stringify({ + version: this.version, + timestamp: this.manifest.timestamp, + files: this.manifest.files, + }) + + await fs.promises.writeFile(this.cachePath, cache) + } + + getFileMtime(relativePath: string): number | undefined { + if (!this.manifest) { + return undefined + } + return this.manifest.files[relativePath]?.mtimeMs + } + + async updateFiles(root: string, filePaths: string[]): Promise { + if (!this.manifest) { + this.manifest = { + version: this.version, + timestamp: Date.now(), + files: {}, + } + } + + for (const filePath of filePaths) { + try { + const stats = await fs.promises.stat(filePath) + const relativePath = relative(root, filePath) + this.manifest.files[relativePath] = { + mtimeMs: stats.mtimeMs, + } + } + catch { + // file may have been deleted, skip + } + } + + this.manifest.timestamp = Date.now() + } + + hasManifest(): boolean { + return this.manifest !== null + } +} diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index c753835c9e0a..b4f5e54a4695 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -294,6 +294,12 @@ export class Vitest { } catch { } + this.cache.stale.setConfig(resolved.root, resolved.cache) + try { + await this.cache.stale.readFromCache() + } + catch { } + const projects = await this.resolveProjects(this._cliOptions) this.projects = projects @@ -769,7 +775,7 @@ export class Vitest { await this.reportCoverage(coverage, true) }) - if (!this.config.watch || !(this.config.changed || this.config.related?.length)) { + if (!this.config.watch || !(this.config.changed || this.config.related?.length || this.config.stale)) { throw new FilesNotFoundError(this.mode) } } @@ -931,6 +937,7 @@ export class Vitest { this.cache.results.updateResults(files) try { await this.cache.results.writeToCache() + await this.cache.stale.writeToCache() } catch {} @@ -954,6 +961,7 @@ export class Vitest { // all subsequent runs will treat this as a fresh run this.config.changed = false + this.config.stale = false this.config.related = undefined }) @@ -1082,6 +1090,7 @@ export class Vitest { // all subsequent runs will treat this as a fresh run this.config.changed = false + this.config.stale = false this.config.related = undefined }) From 76c2823e7fdb8f719db03838f4472f8c4685fde6 Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:39:41 +0100 Subject: [PATCH 3/8] feat(vitest): implement stale test filtering in specifications Add filtering logic to detect stale tests based on mtime comparison, handle first-run behavior, and update manifest with scanned files. Co-authored-by: Sisyphus --- packages/vitest/src/node/specifications.ts | 68 +++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/node/specifications.ts b/packages/vitest/src/node/specifications.ts index 54fe67e7544b..bdba9f15b3c4 100644 --- a/packages/vitest/src/node/specifications.ts +++ b/packages/vitest/src/node/specifications.ts @@ -1,7 +1,7 @@ import type { Vitest } from './core' import type { TestProject } from './project' import type { TestSpecification } from './test-specification' -import { existsSync } from 'node:fs' +import { existsSync, statSync } from 'node:fs' import { join, relative, resolve } from 'pathe' import pm from 'picomatch' import { isWindows } from '../utils/env' @@ -120,6 +120,72 @@ export class VitestSpecifications { } private async filterTestsBySource(specs: TestSpecification[]): Promise { + if (this.vitest.config.stale) { + const root = this.vitest.config.root + const testGraphs = await Promise.all( + specs.map(async (spec) => { + const deps = await this.getTestDependencies(spec) + return [spec, deps] as const + }), + ) + + const allScannedFiles = new Set() + const staleSpecs: TestSpecification[] = [] + const staleManifest = this.vitest.cache.stale + const shouldRunAllByManifest = !staleManifest.hasManifest() + + const forceRerunTriggers = this.vitest.config.forceRerunTriggers + const matcher = forceRerunTriggers.length ? pm(forceRerunTriggers) : undefined + let shouldRunAllByTrigger = false + + const isFileStale = (filePath: string) => { + const relativePath = relative(root, filePath) + const manifestMtime = staleManifest.getFileMtime(relativePath) + + if (manifestMtime === undefined) { + return true + } + + if (!existsSync(filePath)) { + return true + } + + const currentMtime = statSync(filePath).mtimeMs + return currentMtime > manifestMtime + } + + for (const [spec, deps] of testGraphs) { + const files = [spec.moduleId, ...deps] + let isSpecStale = false + + for (const filePath of files) { + allScannedFiles.add(filePath) + + const fileChanged = isFileStale(filePath) + + if (!shouldRunAllByTrigger && matcher && fileChanged && matcher(filePath)) { + shouldRunAllByTrigger = true + } + + if (!isSpecStale && fileChanged) { + isSpecStale = true + } + } + + if (isSpecStale) { + staleSpecs.push(spec) + } + } + + await staleManifest.updateFiles(root, Array.from(allScannedFiles)) + + if (shouldRunAllByManifest || shouldRunAllByTrigger) { + return specs + } + + return staleSpecs + } + if (this.vitest.config.changed && !this.vitest.config.related) { const { VitestGit } = await import('./git') const vitestGit = new VitestGit(this.vitest.config.root) From f64ae0729944534070b88722d052535bb2e29bd6 Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:39:45 +0100 Subject: [PATCH 4/8] feat(vitest): wire --stale integration points in core and logger Add stale config checks to FilesNotFoundError guard, manifest persistence, watch mode reset blocks, and user messaging. Co-authored-by: Sisyphus --- packages/vitest/src/node/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index c96cb73dc921..d81890c630bc 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -165,7 +165,7 @@ export class Logger { printNoTestFound(filters?: string[]): void { const config = this.ctx.config - if (config.watch && (config.changed || config.related?.length)) { + if (config.watch && (config.changed || config.related?.length || config.stale)) { this.log(`No affected ${config.mode} files found\n`) } else if (config.watch) { From 86e0305e97239e7e287fc1d73f2a6b89f5ee2650 Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:39:50 +0100 Subject: [PATCH 5/8] test(vitest): add test suite for --stale option Add 6 comprehensive test cases covering first-run, subsequent-run, dependency changes, mutual exclusion, and test file changes. Includes fixture directory with test and source files. Co-authored-by: Sisyphus --- test/cli/fixtures/stale/dep-of-a.ts | 1 + test/cli/fixtures/stale/source-a.ts | 3 + test/cli/fixtures/stale/source-b.ts | 1 + test/cli/fixtures/stale/test-a.test.ts | 6 + test/cli/fixtures/stale/test-b.test.ts | 6 + .../fixtures/stale/test-standalone.test.ts | 5 + test/cli/fixtures/stale/vitest.config.js | 5 + test/cli/test/stale.test.ts | 139 ++++++++++++++++++ test/test-utils/index.ts | 2 + 9 files changed, 168 insertions(+) create mode 100644 test/cli/fixtures/stale/dep-of-a.ts create mode 100644 test/cli/fixtures/stale/source-a.ts create mode 100644 test/cli/fixtures/stale/source-b.ts create mode 100644 test/cli/fixtures/stale/test-a.test.ts create mode 100644 test/cli/fixtures/stale/test-b.test.ts create mode 100644 test/cli/fixtures/stale/test-standalone.test.ts create mode 100644 test/cli/fixtures/stale/vitest.config.js create mode 100644 test/cli/test/stale.test.ts diff --git a/test/cli/fixtures/stale/dep-of-a.ts b/test/cli/fixtures/stale/dep-of-a.ts new file mode 100644 index 000000000000..983a4cd23cbf --- /dev/null +++ b/test/cli/fixtures/stale/dep-of-a.ts @@ -0,0 +1 @@ +export const dep = 42 diff --git a/test/cli/fixtures/stale/source-a.ts b/test/cli/fixtures/stale/source-a.ts new file mode 100644 index 000000000000..b1613d1170e5 --- /dev/null +++ b/test/cli/fixtures/stale/source-a.ts @@ -0,0 +1,3 @@ +import { dep } from './dep-of-a' + +export const a = 'hello' + dep diff --git a/test/cli/fixtures/stale/source-b.ts b/test/cli/fixtures/stale/source-b.ts new file mode 100644 index 000000000000..919a1bb9eeeb --- /dev/null +++ b/test/cli/fixtures/stale/source-b.ts @@ -0,0 +1 @@ +export const b = 'world' diff --git a/test/cli/fixtures/stale/test-a.test.ts b/test/cli/fixtures/stale/test-a.test.ts new file mode 100644 index 000000000000..e2b601051174 --- /dev/null +++ b/test/cli/fixtures/stale/test-a.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from 'vitest' +import { a } from './source-a' + +test('a', () => { + expect(a).toBeDefined() +}) diff --git a/test/cli/fixtures/stale/test-b.test.ts b/test/cli/fixtures/stale/test-b.test.ts new file mode 100644 index 000000000000..540ad2a14b0c --- /dev/null +++ b/test/cli/fixtures/stale/test-b.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from 'vitest' +import { b } from './source-b' + +test('b', () => { + expect(b).toBeDefined() +}) diff --git a/test/cli/fixtures/stale/test-standalone.test.ts b/test/cli/fixtures/stale/test-standalone.test.ts new file mode 100644 index 000000000000..568f82ab24ba --- /dev/null +++ b/test/cli/fixtures/stale/test-standalone.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from 'vitest' + +test('standalone', () => { + expect(true).toBe(true) +}) diff --git a/test/cli/fixtures/stale/vitest.config.js b/test/cli/fixtures/stale/vitest.config.js new file mode 100644 index 000000000000..57927f3a4d6b --- /dev/null +++ b/test/cli/fixtures/stale/vitest.config.js @@ -0,0 +1,5 @@ +export default { + test: { + include: ['*.test.ts'], + }, +} diff --git a/test/cli/test/stale.test.ts b/test/cli/test/stale.test.ts new file mode 100644 index 000000000000..bc654915ddc6 --- /dev/null +++ b/test/cli/test/stale.test.ts @@ -0,0 +1,139 @@ +import { existsSync, rmSync } from 'node:fs' +import { resolve } from 'node:path' +import { beforeEach, describe, expect, it } from 'vitest' +import { editFile, resolvePath, runVitest } from '../../test-utils' + +const fixtureRoot = resolvePath(import.meta.url, '../fixtures/stale') + +function clearStaleCache() { + const cacheDir = resolve(fixtureRoot, 'node_modules') + if (existsSync(cacheDir)) { + rmSync(cacheDir, { recursive: true, force: true }) + } +} + +function normalizeOutput(stdout: string) { + const rows = stdout.replace(/\d?\.?\d+m?s/g, '[...]ms').split('\n').map((row) => { + if (row.includes('RUN v')) { + return `${row.split('RUN v')[0]}RUN v[...]` + } + + if (row.includes('Start at')) { + return row.replace(/\d+:\d+:\d+/, '[...]') + } + return row + }) + + return rows.join('\n').trim() +} + +async function runStale() { + return runVitest({ + root: './fixtures/stale', + stale: true, + cache: undefined, + }) +} + +describe.skipIf(process.env.ECOSYSTEM_CI)('--stale', () => { + beforeEach(() => { + clearStaleCache() + }) + + it('runs all tests on first run when no manifest exists', async () => { + const { stdout, stderr } = await runStale() + expect(stderr).toBe('') + expect(normalizeOutput(stdout)).toMatchInlineSnapshot(` + "RUN v[...] + + ✓ test-a.test.ts > a [...]ms + ✓ test-b.test.ts > b [...]ms + ✓ test-standalone.test.ts > standalone [...]ms + + Test Files 3 passed (3) + Tests 3 passed (3) + Start at [...] + Duration [...]ms (transform [...]ms, setup [...]ms, import [...]ms, tests [...]ms, environment [...]ms)" + `) + }) + + it('runs no tests on second run with no changes', async () => { + await runStale() + const { stdout } = await runStale() + expect(normalizeOutput(stdout)).toMatchInlineSnapshot(` + "RUN v[...] + + No test files found, exiting with code 0" + `) + }) + + it('runs only affected test when source dependency changes', async () => { + await runStale() + editFile( + resolvePath(import.meta.url, '../fixtures/stale/source-a.ts'), + content => `${content}\n`, + ) + const { stdout, stderr } = await runStale() + expect(stderr).toBe('') + expect(normalizeOutput(stdout)).toMatchInlineSnapshot(` + "RUN v[...] + + ✓ test-a.test.ts > a [...]ms + + Test Files 1 passed (1) + Tests 1 passed (1) + Start at [...] + Duration [...]ms (transform [...]ms, setup [...]ms, import [...]ms, tests [...]ms, environment [...]ms)" + `) + }) + + it('runs only affected test when transitive dependency changes', async () => { + await runStale() + editFile( + resolvePath(import.meta.url, '../fixtures/stale/dep-of-a.ts'), + content => `${content}\n`, + ) + const { stdout, stderr } = await runStale() + expect(stderr).toBe('') + expect(normalizeOutput(stdout)).toMatchInlineSnapshot(` + "RUN v[...] + + ✓ test-a.test.ts > a [...]ms + + Test Files 1 passed (1) + Tests 1 passed (1) + Start at [...] + Duration [...]ms (transform [...]ms, setup [...]ms, import [...]ms, tests [...]ms, environment [...]ms)" + `) + }) + + it('errors when both --stale and --changed are used', async () => { + const { stderr } = await runVitest({ + root: './fixtures/stale', + stale: true, + changed: true, + cache: undefined, + }) + expect(stderr.split('\n')[0]).toMatchInlineSnapshot(`"Error: Cannot use both --stale and --changed options at the same time"`) + }) + + it('runs only changed test when test file itself changes', async () => { + await runStale() + editFile( + resolvePath(import.meta.url, '../fixtures/stale/test-standalone.test.ts'), + content => `${content}\n`, + ) + const { stdout, stderr } = await runStale() + expect(stderr).toBe('') + expect(normalizeOutput(stdout)).toMatchInlineSnapshot(` + "RUN v[...] + + ✓ test-standalone.test.ts > standalone [...]ms + + Test Files 1 passed (1) + Tests 1 passed (1) + Start at [...] + Duration [...]ms (transform [...]ms, setup [...]ms, import [...]ms, tests [...]ms, environment [...]ms)" + `) + }) +}) diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts index 967c7bafc10a..b0f554c69c35 100644 --- a/test/test-utils/index.ts +++ b/test/test-utils/index.ts @@ -125,6 +125,7 @@ export async function runVitest( related, mode, changed, + stale, shard, project, cliExclude, @@ -154,6 +155,7 @@ export async function runVitest( related, mode, changed, + stale, shard, project, cliExclude, From 2118b562840430efbce865672bb60fa01dee83a8 Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:39:56 +0100 Subject: [PATCH 6/8] docs(vitest): document --stale CLI option Add manual prose section to cli.md explaining first-run behavior, mutual exclusion with --changed, and forceRerunTriggers interaction. Auto-generated cli-generated.md entry. Co-authored-by: Sisyphus --- docs/guide/cli-generated.md | 7 +++++++ docs/guide/cli.md | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/guide/cli-generated.md b/docs/guide/cli-generated.md index 6b82b3926181..29b79b8c87d6 100644 --- a/docs/guide/cli-generated.md +++ b/docs/guide/cli-generated.md @@ -492,6 +492,13 @@ Allow tests and suites that are marked as only (default: `!process.env.CI`) Ignore any unhandled errors that occur +### stale + +- **CLI:** `--stale` +- **Config:** [stale](/config/stale) + +Run only tests that are stale. A test is stale when it or any of its dependencies have changed since the last run with --stale (default: `false`) + ### sequence.shuffle.files - **CLI:** `--sequence.shuffle.files` diff --git a/docs/guide/cli.md b/docs/guide/cli.md index e1a16c45193b..7670dc297576 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -202,6 +202,21 @@ When used with code coverage the report will contain only the files that were re If paired with the [`forceRerunTriggers`](/config/forcereruntriggers) config option it will run the whole test suite if at least one of the files listed in the `forceRerunTriggers` list changes. By default, changes to the Vitest config file and `package.json` will always rerun the whole suite. +### stale + +- **Type**: `boolean` +- **Default**: false + +Run only tests that are stale. A test is considered stale when it or any of its dependencies (recursively) have been modified since the last time tests were run with `--stale`. + +The first time tests are run with `--stale`, all tests are executed and a manifest is generated. On subsequent runs, only stale tests are executed. If no tests are stale, Vitest exits with code 0. + +This option is useful for fast iteration during development, allowing you to run only the tests affected by your recent changes without relying on git. + +Cannot be used together with [`--changed`](#changed). + +If paired with the [`forceRerunTriggers`](/config/forcereruntriggers) config option, changes to matched files will cause the entire test suite to run. + ### shard - **Type**: `string` From 421f68f018f32bf71db6c22edec2c556d58a5c8d Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 17:40:02 +0100 Subject: [PATCH 7/8] chore(eslint): exclude .sisyphus directory from linting Add .sisyphus directory to eslint ignores to prevent parsing errors on markdown documentation files. Co-authored-by: Sisyphus --- eslint.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.js b/eslint.config.js index 1eeed690841d..e7b1a6f177ca 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -33,6 +33,7 @@ export default antfu( // uses invalid js example 'docs/api/advanced/import-example.md', 'docs/guide/examples/*.md', + '.sisyphus/**', ], }, { From f43c300135de62709844b6a8abf71ceb3d387325 Mon Sep 17 00:00:00 2001 From: gojimotoro Date: Thu, 19 Mar 2026 18:02:42 +0100 Subject: [PATCH 8/8] docs(vitest): add config page for stale option to fix dead link --- docs/config/stale.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/config/stale.md diff --git a/docs/config/stale.md b/docs/config/stale.md new file mode 100644 index 000000000000..6f2156fbb51a --- /dev/null +++ b/docs/config/stale.md @@ -0,0 +1,22 @@ +--- +title: stale | Config +outline: deep +--- + +# stale + +- **Type**: `boolean` +- **Default**: `false` +- **CLI:** `--stale` + +Run only tests that are stale. A test is considered stale when it or any of its dependencies (recursively) have been modified since the last time tests were run with `--stale`. + +The first time tests are run with `--stale`, all tests are executed and a manifest is generated. On subsequent runs, only stale tests are executed. If no tests are stale, Vitest exits with code 0. + +This option is useful for fast iteration during development, particularly for agentic coding systems that run tests continuously during their development loops. + +Cannot be used together with [`changed`](/guide/cli#changed). + +::: tip +When paired with [`forceRerunTriggers`](/config/forcereruntriggers), changes to matched files will cause the entire test suite to run. +:::