From 14547de6db0b5593a3b28e424f13075063925c33 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 10:33:19 +0800 Subject: [PATCH 01/12] test(snap): add nested-oxc-config-proj-1 to reproduce nested vite.config.ts loading bug Mirrors the layout from oxc-project/oxc#20416: a root `vite.config.ts` plus `packages/proj-1/vite.config.ts`. Running `vp lint` / `vp fmt` from `packages/proj-1` should pick up the nested config, but the committed snap shows both commands exit with [1] applying the root config instead (root `no-debugger: error` vs proj-1 `off`, root `singleQuote: true` vs proj-1 `false`). Refs oxc-project/oxc#20416 --- .../nested-oxc-config-proj-1/package.json | 7 ++++++ .../packages/proj-1/package.json | 5 ++++ .../packages/proj-1/src/index.js | 6 +++++ .../packages/proj-1/vite.config.ts | 13 ++++++++++ .../nested-oxc-config-proj-1/pnpm-lock.yaml | 11 ++++++++ .../pnpm-workspace.yaml | 2 ++ .../nested-oxc-config-proj-1/snap.txt | 25 +++++++++++++++++++ .../nested-oxc-config-proj-1/steps.json | 12 +++++++++ .../nested-oxc-config-proj-1/vite.config.ts | 12 +++++++++ 9 files changed, 93 insertions(+) create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/package.json create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/package.json create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/vite.config.ts create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-workspace.yaml create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json create mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/vite.config.ts diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/package.json b/packages/cli/snap-tests/nested-oxc-config-proj-1/package.json new file mode 100644 index 0000000000..d3aa196bfd --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/package.json @@ -0,0 +1,7 @@ +{ + "name": "nested-oxc-config-proj-1-test", + "version": "0.0.0", + "private": true, + "type": "module", + "packageManager": "pnpm@10.16.1" +} diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/package.json b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/package.json new file mode 100644 index 0000000000..cf0fabf744 --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/package.json @@ -0,0 +1,5 @@ +{ + "name": "proj-1", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js new file mode 100644 index 0000000000..6c9106404b --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js @@ -0,0 +1,6 @@ +function hello() { + debugger; + return 'hello from proj-1'; +} + +export { hello }; diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/vite.config.ts b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/vite.config.ts new file mode 100644 index 0000000000..f1bf2c29de --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/vite.config.ts @@ -0,0 +1,13 @@ +// Nested proj-1 config: relaxes the root's strict rules. +// Running `vp lint` / `vp fmt` from packages/proj-1 should pick this up +// (matching oxc-project/oxc#20416), but currently the root config is used instead. +export default { + lint: { + rules: { + 'no-debugger': 'off', + }, + }, + fmt: { + singleQuote: false, + }, +}; diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml b/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml new file mode 100644 index 0000000000..e5a5eb631a --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml @@ -0,0 +1,11 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + packages/proj-1: {} diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-workspace.yaml b/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-workspace.yaml new file mode 100644 index 0000000000..18ec407efc --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt b/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt new file mode 100644 index 0000000000..21cce1d937 --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt @@ -0,0 +1,25 @@ +> # Reproduces oxc-project/oxc#20416: nested vite.config.ts in packages/proj-1 +> # should override root config when `vp lint` / `vp fmt` run from that cwd. +> # proj-1 disables no-debugger and turns off singleQuote, so both commands +> # should pass. If the root config leaks through, lint reports a debugger +> # violation and fmt reports a quote-style diff. +[1]> cd packages/proj-1 && vp lint # expected: pass (proj-1 disables no-debugger); actual: fails with root rule + + × eslint(no-debugger): `debugger` statement is not allowed + ╭─[src/index.js:2:3] + 1 │ function hello() { + 2 │ debugger; + · ───────── + 3 │ return "hello from proj-1"; + ╰──── + help: Remove the debugger statement + +Found 0 warnings and 1 error. +Finished in ms on 2 files with rules using threads. + +[1]> cd packages/proj-1 && vp fmt --check # expected: pass (proj-1 sets singleQuote:false); actual: fails with root singleQuote:true +Checking formatting... +src/index.js (ms) + +Format issues found in above 1 files. Run without `--check` to fix. +Finished in ms on 3 files using threads. diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json b/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json new file mode 100644 index 0000000000..f4ecaffee1 --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json @@ -0,0 +1,12 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": [ + "# Reproduces oxc-project/oxc#20416: nested vite.config.ts in packages/proj-1", + "# should override root config when `vp lint` / `vp fmt` run from that cwd.", + "# proj-1 disables no-debugger and turns off singleQuote, so both commands", + "# should pass. If the root config leaks through, lint reports a debugger", + "# violation and fmt reports a quote-style diff.", + "cd packages/proj-1 && vp lint # expected: pass (proj-1 disables no-debugger); actual: fails with root rule", + "cd packages/proj-1 && vp fmt --check # expected: pass (proj-1 sets singleQuote:false); actual: fails with root singleQuote:true" + ] +} diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/vite.config.ts b/packages/cli/snap-tests/nested-oxc-config-proj-1/vite.config.ts new file mode 100644 index 0000000000..197e12a53d --- /dev/null +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/vite.config.ts @@ -0,0 +1,12 @@ +// Root config: strict rules that should NOT apply when running from packages/proj-1. +// If proj-1's nested vite.config.ts is loaded correctly, these rules are overridden. +export default { + lint: { + rules: { + 'no-debugger': 'error', + }, + }, + fmt: { + singleQuote: true, + }, +}; From 38869d67157c8fe3cc810df00270a138318276a9 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 10:34:45 +0800 Subject: [PATCH 02/12] test(snap): exclude nested-oxc-config-proj-1 fixture from repo fmt The repo-level `vp check --fix` lint-staged hook was reformatting the snap test fixture to match the repo's own `singleQuote: true`, which erased the double-quoted string the fmt reproduction depends on. Exclude the fixture directory from the repo's `fmt.ignorePatterns` and restore the fixture source so the committed snap.txt captures both the lint and fmt failures. --- .../nested-oxc-config-proj-1/packages/proj-1/src/index.js | 2 +- vite.config.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js index 6c9106404b..6754512952 100644 --- a/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/packages/proj-1/src/index.js @@ -1,6 +1,6 @@ function hello() { debugger; - return 'hello from proj-1'; + return "hello from proj-1"; } export { hello }; diff --git a/vite.config.ts b/vite.config.ts index 093cde7a97..13375711ee 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -69,6 +69,7 @@ export default defineConfig({ '**/tmp/**', 'packages/cli/snap-tests/check-*/**', 'packages/cli/snap-tests/fmt-ignore-patterns/src/ignored', + 'packages/cli/snap-tests/nested-oxc-config-proj-1/**', 'packages/cli/snap-tests-global/migration-lint-staged-ts-config', 'docs/**', 'ecosystem-ci/*/**', From ed793f7af3134e28e24bf39809f65594c1849a93 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 11:57:30 +0800 Subject: [PATCH 03/12] fix(cli): stop injecting -c config for oxlint/oxfmt oxlint 1.60.0 and oxfmt 0.45.0 auto-discover the nearest vite.config.ts from cwd up (gated by VP_VERSION, which vp injects via merge_resolved_envs_with_version and the bin wrappers). Injecting `-c ` broke the nested-workspace case: running `vp lint` / `vp fmt` from a sub-package applied the root config instead of the sub-package's own config. Drop the -c injection from the Lint/Fmt resolver arms and propagate the now-unused resolved_vite_config parameter removal through resolve_and_execute, resolve_and_capture_output, and their callers. resolve_universal_vite_config() stays for check/mod.rs's LintMessageKind derivation. Snapshot updates: - nested-oxc-config-proj-1: both vp lint and vp fmt --check pass when run from packages/proj-1 (regression test for oxc-project/oxc#20416). - workspace-lint-subpackage: sub-package's no-console:off now wins over root's no-console:warn as documented. Closes oxc-project/oxc#20416 --- packages/cli/binding/src/check/mod.rs | 3 -- packages/cli/binding/src/cli/execution.rs | 24 ++++---------- packages/cli/binding/src/cli/handler.rs | 3 +- packages/cli/binding/src/cli/mod.rs | 4 +-- packages/cli/binding/src/cli/resolver.rs | 33 ++----------------- .../nested-oxc-config-proj-1/snap.txt | 33 +++++++------------ .../nested-oxc-config-proj-1/steps.json | 15 +++++---- .../workspace-lint-subpackage/snap.txt | 12 +------ 8 files changed, 30 insertions(+), 97 deletions(-) diff --git a/packages/cli/binding/src/check/mod.rs b/packages/cli/binding/src/check/mod.rs index 62efc8348d..d44de71467 100644 --- a/packages/cli/binding/src/check/mod.rs +++ b/packages/cli/binding/src/check/mod.rs @@ -55,7 +55,6 @@ pub(crate) async fn execute_check( let captured = resolve_and_capture_output( resolver, SynthesizableSubcommand::Fmt { args }, - Some(&resolved_vite_config), envs, cwd, cwd_arc, @@ -137,7 +136,6 @@ pub(crate) async fn execute_check( let captured = resolve_and_capture_output( resolver, SynthesizableSubcommand::Lint { args }, - Some(&resolved_vite_config), envs, cwd, cwd_arc, @@ -204,7 +202,6 @@ pub(crate) async fn execute_check( let captured = resolve_and_capture_output( resolver, SynthesizableSubcommand::Fmt { args }, - Some(&resolved_vite_config), envs, cwd, cwd_arc, diff --git a/packages/cli/binding/src/cli/execution.rs b/packages/cli/binding/src/cli/execution.rs index 887db64efc..055a635a82 100644 --- a/packages/cli/binding/src/cli/execution.rs +++ b/packages/cli/binding/src/cli/execution.rs @@ -7,22 +7,19 @@ use vite_task::ExitStatus; use super::{ resolver::SubcommandResolver, - types::{CapturedCommandOutput, ResolvedUniversalViteConfig, SynthesizableSubcommand}, + types::{CapturedCommandOutput, SynthesizableSubcommand}, }; /// Resolve a subcommand into a prepared `tokio::process::Command`. async fn resolve_and_build_command( resolver: &SubcommandResolver, subcommand: SynthesizableSubcommand, - resolved_vite_config: Option<&ResolvedUniversalViteConfig>, envs: &Arc, Arc>>, cwd: &AbsolutePathBuf, cwd_arc: &Arc, ) -> Result { - let resolved = resolver - .resolve(subcommand, resolved_vite_config, envs, cwd_arc) - .await - .map_err(|e| Error::Anyhow(e))?; + let resolved = + resolver.resolve(subcommand, envs, cwd_arc).await.map_err(|e| Error::Anyhow(e))?; // Resolve the program path using `which` to handle Windows .cmd/.bat files (PATHEXT) let program_path = { @@ -52,14 +49,11 @@ async fn resolve_and_build_command( pub(super) async fn resolve_and_execute( resolver: &SubcommandResolver, subcommand: SynthesizableSubcommand, - resolved_vite_config: Option<&ResolvedUniversalViteConfig>, envs: &Arc, Arc>>, cwd: &AbsolutePathBuf, cwd_arc: &Arc, ) -> Result { - let mut cmd = - resolve_and_build_command(resolver, subcommand, resolved_vite_config, envs, cwd, cwd_arc) - .await?; + let mut cmd = resolve_and_build_command(resolver, subcommand, envs, cwd, cwd_arc).await?; let mut child = cmd.spawn().map_err(|e| Error::Anyhow(e.into()))?; let status = child.wait().await.map_err(|e| Error::Anyhow(e.into()))?; Ok(ExitStatus(status.code().unwrap_or(1) as u8)) @@ -75,16 +69,13 @@ pub(super) enum FilterStream { pub(super) async fn resolve_and_execute_with_filter( resolver: &SubcommandResolver, subcommand: SynthesizableSubcommand, - resolved_vite_config: Option<&ResolvedUniversalViteConfig>, envs: &Arc, Arc>>, cwd: &AbsolutePathBuf, cwd_arc: &Arc, stream: FilterStream, filter: impl Fn(&str) -> Cow<'_, str>, ) -> Result { - let mut cmd = - resolve_and_build_command(resolver, subcommand, resolved_vite_config, envs, cwd, cwd_arc) - .await?; + let mut cmd = resolve_and_build_command(resolver, subcommand, envs, cwd, cwd_arc).await?; match stream { FilterStream::Stdout => cmd.stdout(Stdio::piped()), FilterStream::Stderr => cmd.stderr(Stdio::piped()), @@ -111,15 +102,12 @@ pub(super) async fn resolve_and_execute_with_filter( pub(crate) async fn resolve_and_capture_output( resolver: &SubcommandResolver, subcommand: SynthesizableSubcommand, - resolved_vite_config: Option<&ResolvedUniversalViteConfig>, envs: &Arc, Arc>>, cwd: &AbsolutePathBuf, cwd_arc: &Arc, force_color_if_terminal: bool, ) -> Result { - let mut cmd = - resolve_and_build_command(resolver, subcommand, resolved_vite_config, envs, cwd, cwd_arc) - .await?; + let mut cmd = resolve_and_build_command(resolver, subcommand, envs, cwd, cwd_arc).await?; cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); if force_color_if_terminal && std::io::stdout().is_terminal() { diff --git a/packages/cli/binding/src/cli/handler.rs b/packages/cli/binding/src/cli/handler.rs index 1b7f17ba80..cb953d3b3c 100644 --- a/packages/cli/binding/src/cli/handler.rs +++ b/packages/cli/binding/src/cli/handler.rs @@ -71,8 +71,7 @@ impl CommandHandler for VitePlusCommandHandler { ))) } CLIArgs::Synthesizable(subcmd) => { - let resolved = - self.resolver.resolve(subcmd, None, &command.envs, &command.cwd).await?; + let resolved = self.resolver.resolve(subcmd, &command.envs, &command.cwd).await?; Ok(HandledCommand::Synthesized(resolved.into_synthetic_plan_request())) } CLIArgs::ViteTask(cmd) => Ok(HandledCommand::ViteTaskCommand(cmd)), diff --git a/packages/cli/binding/src/cli/mod.rs b/packages/cli/binding/src/cli/mod.rs index c6f91fa404..9f18a6368f 100644 --- a/packages/cli/binding/src/cli/mod.rs +++ b/packages/cli/binding/src/cli/mod.rs @@ -82,7 +82,6 @@ async fn execute_direct_subcommand( resolve_and_execute_with_filter( &resolver, other, - None, &envs, cwd, &cwd_arc, @@ -94,7 +93,6 @@ async fn execute_direct_subcommand( resolve_and_execute_with_filter( &resolver, other, - None, &envs, cwd, &cwd_arc, @@ -103,7 +101,7 @@ async fn execute_direct_subcommand( ) .await? } else { - resolve_and_execute(&resolver, other, None, &envs, cwd, &cwd_arc).await? + resolve_and_execute(&resolver, other, &envs, cwd, &cwd_arc).await? } } }; diff --git a/packages/cli/binding/src/cli/resolver.rs b/packages/cli/binding/src/cli/resolver.rs index 043f5c8e82..3acd88c50a 100644 --- a/packages/cli/binding/src/cli/resolver.rs +++ b/packages/cli/binding/src/cli/resolver.rs @@ -65,32 +65,17 @@ impl SubcommandResolver { pub(super) async fn resolve( &self, subcommand: SynthesizableSubcommand, - resolved_vite_config: Option<&ResolvedUniversalViteConfig>, envs: &Arc, Arc>>, cwd: &Arc, ) -> anyhow::Result { match subcommand { - SynthesizableSubcommand::Lint { mut args } => { + SynthesizableSubcommand::Lint { args } => { let cli_options = self.cli_options()?; let resolved = (cli_options.lint)().await?; let js_path = resolved.bin_path; let js_path_str = js_path .to_str() .ok_or_else(|| anyhow::anyhow!("lint JS path is not valid UTF-8"))?; - let owned_resolved_vite_config; - let resolved_vite_config = if let Some(config) = resolved_vite_config { - config - } else { - owned_resolved_vite_config = self.resolve_universal_vite_config().await?; - &owned_resolved_vite_config - }; - - if let (Some(_), Some(config_file)) = - (&resolved_vite_config.lint, &resolved_vite_config.config_file) - { - args.insert(0, "-c".to_string()); - args.insert(1, config_file.clone()); - } Ok(ResolvedSubcommand { program: Arc::from(OsStr::new("node")), @@ -106,27 +91,13 @@ impl SubcommandResolver { envs: merge_resolved_envs_with_version(envs, resolved.envs), }) } - SynthesizableSubcommand::Fmt { mut args } => { + SynthesizableSubcommand::Fmt { args } => { let cli_options = self.cli_options()?; let resolved = (cli_options.fmt)().await?; let js_path = resolved.bin_path; let js_path_str = js_path .to_str() .ok_or_else(|| anyhow::anyhow!("fmt JS path is not valid UTF-8"))?; - let owned_resolved_vite_config; - let resolved_vite_config = if let Some(config) = resolved_vite_config { - config - } else { - owned_resolved_vite_config = self.resolve_universal_vite_config().await?; - &owned_resolved_vite_config - }; - - if let (Some(_), Some(config_file)) = - (&resolved_vite_config.fmt, &resolved_vite_config.config_file) - { - args.insert(0, "-c".to_string()); - args.insert(1, config_file.clone()); - } Ok(ResolvedSubcommand { program: Arc::from(OsStr::new("node")), diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt b/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt index 21cce1d937..1e94e4ab67 100644 --- a/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt @@ -1,25 +1,14 @@ -> # Reproduces oxc-project/oxc#20416: nested vite.config.ts in packages/proj-1 -> # should override root config when `vp lint` / `vp fmt` run from that cwd. -> # proj-1 disables no-debugger and turns off singleQuote, so both commands -> # should pass. If the root config leaks through, lint reports a debugger -> # violation and fmt reports a quote-style diff. -[1]> cd packages/proj-1 && vp lint # expected: pass (proj-1 disables no-debugger); actual: fails with root rule - - × eslint(no-debugger): `debugger` statement is not allowed - ╭─[src/index.js:2:3] - 1 │ function hello() { - 2 │ debugger; - · ───────── - 3 │ return "hello from proj-1"; - ╰──── - help: Remove the debugger statement - -Found 0 warnings and 1 error. +> # Regression test for oxc-project/oxc#20416: nested vite.config.ts in +> # packages/proj-1 must override the workspace root config when `vp lint` +> # and `vp fmt` run from that cwd. Root has `no-debugger: error` and +> # `singleQuote: true`, proj-1 overrides both to `off` / `false`. If the +> # root config leaks through, lint reports a debugger violation and fmt +> # reports a quote-style diff. +> cd packages/proj-1 && vp lint +Found 0 warnings and 0 errors. Finished in ms on 2 files with rules using threads. -[1]> cd packages/proj-1 && vp fmt --check # expected: pass (proj-1 sets singleQuote:false); actual: fails with root singleQuote:true +> cd packages/proj-1 && vp fmt --check src Checking formatting... -src/index.js (ms) - -Format issues found in above 1 files. Run without `--check` to fix. -Finished in ms on 3 files using threads. +All matched files use the correct format. +Finished in ms on 1 files using threads. diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json b/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json index f4ecaffee1..442a67b482 100644 --- a/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json @@ -1,12 +1,13 @@ { "ignoredPlatforms": ["win32"], "commands": [ - "# Reproduces oxc-project/oxc#20416: nested vite.config.ts in packages/proj-1", - "# should override root config when `vp lint` / `vp fmt` run from that cwd.", - "# proj-1 disables no-debugger and turns off singleQuote, so both commands", - "# should pass. If the root config leaks through, lint reports a debugger", - "# violation and fmt reports a quote-style diff.", - "cd packages/proj-1 && vp lint # expected: pass (proj-1 disables no-debugger); actual: fails with root rule", - "cd packages/proj-1 && vp fmt --check # expected: pass (proj-1 sets singleQuote:false); actual: fails with root singleQuote:true" + "# Regression test for oxc-project/oxc#20416: nested vite.config.ts in", + "# packages/proj-1 must override the workspace root config when `vp lint`", + "# and `vp fmt` run from that cwd. Root has `no-debugger: error` and", + "# `singleQuote: true`, proj-1 overrides both to `off` / `false`. If the", + "# root config leaks through, lint reports a debugger violation and fmt", + "# reports a quote-style diff.", + "cd packages/proj-1 && vp lint", + "cd packages/proj-1 && vp fmt --check src" ] } diff --git a/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt b/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt index 1f7571585f..08e3c4a21b 100644 --- a/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt +++ b/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt @@ -1,13 +1,3 @@ > cd packages/app-a && vp lint # sub-workspace has no-console:off but root has no-console:warn - - ⚠ eslint(no-console): Unexpected console statement. - ╭─[src/index.js:2:3] - 1 │ function hello() { - 2 │ console.log('hello from app-a'); - · ─────────── - 3 │ return 'hello'; - ╰──── - help: Delete this console statement. - -Found 1 warning and 0 errors. +Found 0 warnings and 0 errors. Finished in ms on 2 files with rules using threads. From 622da1d6adba0a93f850509dd6a3425103a294b6 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 12:01:12 +0800 Subject: [PATCH 04/12] test(snap): cover root-workspace fallback in nested-oxc-config-proj-1 Add root-cwd cases that must fail under the root config's strict rules, complementing the existing proj-1 cwd cases that pass under the nested override. --- .../nested-oxc-config-proj-1/snap.txt | 24 +++++++++++++++++++ .../nested-oxc-config-proj-1/steps.json | 7 +++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt b/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt index 1e94e4ab67..28cbd1e06e 100644 --- a/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/snap.txt @@ -12,3 +12,27 @@ Finished in ms on 2 files with rules using threa Checking formatting... All matched files use the correct format. Finished in ms on 1 files using threads. + +> # Running from the workspace root must use the root config, so both +> # commands should fail: lint flags the `debugger;` and fmt flags the +> # double-quoted string in packages/proj-1/src/index.js. +[1]> vp lint + + × eslint(no-debugger): `debugger` statement is not allowed + ╭─[packages/proj-1/src/index.js:2:3] + 1 │ function hello() { + 2 │ debugger; + · ───────── + 3 │ return "hello from proj-1"; + ╰──── + help: Remove the debugger statement + +Found 0 warnings and 1 error. +Finished in ms on 3 files with rules using threads. + +[1]> vp fmt --check packages/proj-1/src +Checking formatting... +packages/proj-1/src/index.js (ms) + +Format issues found in above 1 files. Run without `--check` to fix. +Finished in ms on 1 files using threads. diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json b/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json index 442a67b482..aa3afeb4ba 100644 --- a/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json +++ b/packages/cli/snap-tests/nested-oxc-config-proj-1/steps.json @@ -8,6 +8,11 @@ "# root config leaks through, lint reports a debugger violation and fmt", "# reports a quote-style diff.", "cd packages/proj-1 && vp lint", - "cd packages/proj-1 && vp fmt --check src" + "cd packages/proj-1 && vp fmt --check src", + "# Running from the workspace root must use the root config, so both", + "# commands should fail: lint flags the `debugger;` and fmt flags the", + "# double-quoted string in packages/proj-1/src/index.js.", + "vp lint", + "vp fmt --check packages/proj-1/src" ] } From 899c1de2231c0b2c3df6eb71ed778fad1a70c735 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 12:01:29 +0800 Subject: [PATCH 05/12] test(snap): drop unused pnpm-lock.yaml from nested-oxc-config-proj-1 --- .../nested-oxc-config-proj-1/pnpm-lock.yaml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml diff --git a/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml b/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml deleted file mode 100644 index e5a5eb631a..0000000000 --- a/packages/cli/snap-tests/nested-oxc-config-proj-1/pnpm-lock.yaml +++ /dev/null @@ -1,11 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: {} - - packages/proj-1: {} From 920464b986884ca2702c1dd0e08f883584757129 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 12:06:16 +0800 Subject: [PATCH 06/12] perf(check): defer resolve_universal_vite_config to the lint branch The resolved config is now only consumed by LintMessageKind, so calling the JS-runtime resolver unconditionally wasted a NAPI round-trip on `vp check --no-lint` and fmt-only paths. --- packages/cli/binding/src/check/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/binding/src/check/mod.rs b/packages/cli/binding/src/check/mod.rs index d44de71467..071d2bccc1 100644 --- a/packages/cli/binding/src/check/mod.rs +++ b/packages/cli/binding/src/check/mod.rs @@ -40,7 +40,6 @@ pub(crate) async fn execute_check( let has_paths = !paths.is_empty(); let mut fmt_fix_started: Option = None; let mut deferred_lint_pass: Option<(String, String)> = None; - let resolved_vite_config = resolver.resolve_universal_vite_config().await?; if !no_fmt { let mut args = if fix { vec![] } else { vec!["--check".to_string()] }; @@ -115,6 +114,7 @@ pub(crate) async fn execute_check( } if !no_lint { + let resolved_vite_config = resolver.resolve_universal_vite_config().await?; let lint_message_kind = LintMessageKind::from_lint_config(resolved_vite_config.lint.as_ref()); let mut args = Vec::new(); From 8c5c8d6236248cc022b2f9405e5f660384949a17 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 13:28:09 +0800 Subject: [PATCH 07/12] test(snap): cover root-cwd lint in workspace-lint-subpackage Root config's `no-console: warn` should flag app-a's console.log when vp lint runs from the workspace root, complementing the existing nested-override case where the sub-workspace's `no-console: off` wins. --- .../snap-tests/workspace-lint-subpackage/snap.txt | 14 ++++++++++++++ .../workspace-lint-subpackage/steps.json | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt b/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt index 08e3c4a21b..7ef2a5f3fb 100644 --- a/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt +++ b/packages/cli/snap-tests/workspace-lint-subpackage/snap.txt @@ -1,3 +1,17 @@ > cd packages/app-a && vp lint # sub-workspace has no-console:off but root has no-console:warn Found 0 warnings and 0 errors. Finished in ms on 2 files with rules using threads. + +> vp lint # at root, no-console:warn should flag app-a's console.log + + ⚠ eslint(no-console): Unexpected console statement. + ╭─[packages/app-a/src/index.js:2:3] + 1 │ function hello() { + 2 │ console.log('hello from app-a'); + · ─────────── + 3 │ return 'hello'; + ╰──── + help: Delete this console statement. + +Found 1 warning and 0 errors. +Finished in ms on 3 files with rules using threads. diff --git a/packages/cli/snap-tests/workspace-lint-subpackage/steps.json b/packages/cli/snap-tests/workspace-lint-subpackage/steps.json index 992fa4aac4..0cc7757e03 100644 --- a/packages/cli/snap-tests/workspace-lint-subpackage/steps.json +++ b/packages/cli/snap-tests/workspace-lint-subpackage/steps.json @@ -1,6 +1,7 @@ { "ignoredPlatforms": ["win32"], "commands": [ - "cd packages/app-a && vp lint # sub-workspace has no-console:off but root has no-console:warn" + "cd packages/app-a && vp lint # sub-workspace has no-console:off but root has no-console:warn", + "vp lint # at root, no-console:warn should flag app-a's console.log" ] } From a15e3dbba99c0b23663586cf7306189811507491 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 15:02:07 +0800 Subject: [PATCH 08/12] test(snap): pin oxlint ignorePatterns + nested-config behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents two behaviors observed while investigating why `vp lint` at the repo root was leaking snap-test fixture diagnostics: 1. Root vite.config.ts `lint.ignorePatterns` (wildcard-prefixed) does filter files out of the lint walk when no nearer oxlint config exists — packages/ignored/src/index.js is skipped despite its console.log. 2. Nested vite.config.ts in a subpackage (packages/included/ vite.config.ts, no-console:off) is NOT consulted when cwd is the workspace root; the root's no-console:error still flags it. --- .../package.json | 9 +++++++++ .../packages/ignored/package.json | 4 ++++ .../packages/ignored/src/index.js | 6 ++++++ .../packages/included/package.json | 4 ++++ .../packages/included/src/index.js | 6 ++++++ .../packages/included/vite.config.ts | 7 +++++++ .../pnpm-workspace.yaml | 2 ++ .../workspace-lint-ignore-patterns/snap.txt | 15 +++++++++++++++ .../workspace-lint-ignore-patterns/steps.json | 8 ++++++++ .../vite.config.ts | 18 ++++++++++++++++++ 10 files changed, 79 insertions(+) create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/package.json create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/package.json create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/src/index.js create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/package.json create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/src/index.js create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/vite.config.ts create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/pnpm-workspace.yaml create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/snap.txt create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/steps.json create mode 100644 packages/cli/snap-tests/workspace-lint-ignore-patterns/vite.config.ts diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/package.json b/packages/cli/snap-tests/workspace-lint-ignore-patterns/package.json new file mode 100644 index 0000000000..bc2d22d2cd --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/package.json @@ -0,0 +1,9 @@ +{ + "name": "workspace-lint-ignore-patterns-test", + "version": "0.0.0", + "workspaces": [ + "packages/*" + ], + "type": "module", + "packageManager": "pnpm@10.16.1" +} diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/package.json b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/package.json new file mode 100644 index 0000000000..57597bc3ca --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/package.json @@ -0,0 +1,4 @@ +{ + "name": "ignored", + "version": "0.0.0" +} diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/src/index.js b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/src/index.js new file mode 100644 index 0000000000..234a2b27cd --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/ignored/src/index.js @@ -0,0 +1,6 @@ +function ignored() { + console.log('this file matches root ignorePatterns'); + return 'ignored'; +} + +export { ignored }; diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/package.json b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/package.json new file mode 100644 index 0000000000..78dce56cb9 --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/package.json @@ -0,0 +1,4 @@ +{ + "name": "included", + "version": "0.0.0" +} diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/src/index.js b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/src/index.js new file mode 100644 index 0000000000..cef2fde5d8 --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/src/index.js @@ -0,0 +1,6 @@ +function included() { + console.log('this file is not in ignorePatterns'); + return 'included'; +} + +export { included }; diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/vite.config.ts b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/vite.config.ts new file mode 100644 index 0000000000..c1a657f0b3 --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/packages/included/vite.config.ts @@ -0,0 +1,7 @@ +export default { + lint: { + rules: { + 'no-console': 'off', + }, + }, +}; diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/pnpm-workspace.yaml b/packages/cli/snap-tests/workspace-lint-ignore-patterns/pnpm-workspace.yaml new file mode 100644 index 0000000000..4de91a383a --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - '.' diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/snap.txt b/packages/cli/snap-tests/workspace-lint-ignore-patterns/snap.txt new file mode 100644 index 0000000000..d8ac056eb4 --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/snap.txt @@ -0,0 +1,15 @@ +> # Tests whether oxlint honors a `**/`-prefixed glob in root lint.ignorePatterns. +> # Mirrors how the vp repo declares ignorePatterns: ['**/snap-tests/**']. +[1]> vp lint + + × eslint(no-console): Unexpected console statement. + ╭─[packages/included/src/index.js:2:3] + 1 │ function included() { + 2 │ console.log('this file is not in ignorePatterns'); + · ─────────── + 3 │ return 'included'; + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in ms on 3 files with rules using threads. diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/steps.json b/packages/cli/snap-tests/workspace-lint-ignore-patterns/steps.json new file mode 100644 index 0000000000..83db74abda --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/steps.json @@ -0,0 +1,8 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": [ + "# Tests whether oxlint honors a `**/`-prefixed glob in root lint.ignorePatterns.", + "# Mirrors how the vp repo declares ignorePatterns: ['**/snap-tests/**'].", + "vp lint" + ] +} diff --git a/packages/cli/snap-tests/workspace-lint-ignore-patterns/vite.config.ts b/packages/cli/snap-tests/workspace-lint-ignore-patterns/vite.config.ts new file mode 100644 index 0000000000..3801117ebb --- /dev/null +++ b/packages/cli/snap-tests/workspace-lint-ignore-patterns/vite.config.ts @@ -0,0 +1,18 @@ +export default { + lint: { + options: { + typeAware: true, + typeCheck: true, + }, + plugins: ['unicorn', 'typescript', 'oxc'], + categories: { + correctness: 'error', + perf: 'error', + suspicious: 'error', + }, + rules: { + 'no-console': 'error', + }, + ignorePatterns: ['**/ignored/**'], + }, +}; From a2115d5c975fea46ea3fe1bb4b21e1c16ce054e0 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 15:39:25 +0800 Subject: [PATCH 09/12] chore: add disable-nested-config --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 98018d0aad..7b990aec4c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "bootstrap-cli:ci": "pnpm install-global-cli", "install-global-cli": "tool install-global-cli", "tsgo": "tsgo -b tsconfig.json", - "lint": "vp lint --type-aware --type-check --threads 4", + "check": "vp check --disable-nested-config", "test": "vp test run && pnpm -r snap-test", "fmt": "vp fmt", "test:unit": "vp test run", @@ -37,7 +37,7 @@ "zod": "catalog:" }, "lint-staged": { - "*.@(js|ts|tsx|md|yaml|yml)": "vp check --fix", + "*.@(js|ts|tsx|md|yaml|yml)": "vp check --fix --disable-nested-config", "*.rs": "cargo fmt --" }, "engines": { From f1547b7661df02354c7cf8a1d2bdf60075141abd Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 16:31:17 +0800 Subject: [PATCH 10/12] docs(guide): document vite.config.ts nested-config resolution Adds a dedicated Nested Configuration guide covering how `vp lint` and `vp fmt` select a `vite.config.ts`, with an explicit note on the asymmetry between the two: `vp lint` is cwd-only, while `vp fmt` walks up until it finds a config. Cross-links it from the Lint and Format pages and the Check sidebar. Adds snap-tests/docs-nested-config to pin each documented claim (cwd walk-up for fmt, cwd-only fallback for lint, root-cwd wins over nested configs, `-c ` bypasses cwd resolution) so the guide stays in sync with actual behavior. --- docs/.vitepress/config.mts | 1 + docs/guide/fmt.md | 2 + docs/guide/lint.md | 2 + docs/guide/nested-config.md | 103 ++++++++++++++++++ .../docs-nested-config/package.json | 7 ++ .../packages/pkg-a/package.json | 5 + .../packages/pkg-a/src/index.js | 6 + .../packages/pkg-a/vite.config.ts | 12 ++ .../packages/pkg-b/package.json | 5 + .../packages/pkg-b/src/index.js | 6 + .../docs-nested-config/pnpm-workspace.yaml | 2 + .../snap-tests/docs-nested-config/snap.txt | 93 ++++++++++++++++ .../snap-tests/docs-nested-config/steps.json | 33 ++++++ .../vite.config.permissive.ts | 12 ++ .../docs-nested-config/vite.config.ts | 11 ++ 15 files changed, 300 insertions(+) create mode 100644 docs/guide/nested-config.md create mode 100644 packages/cli/snap-tests/docs-nested-config/package.json create mode 100644 packages/cli/snap-tests/docs-nested-config/packages/pkg-a/package.json create mode 100644 packages/cli/snap-tests/docs-nested-config/packages/pkg-a/src/index.js create mode 100644 packages/cli/snap-tests/docs-nested-config/packages/pkg-a/vite.config.ts create mode 100644 packages/cli/snap-tests/docs-nested-config/packages/pkg-b/package.json create mode 100644 packages/cli/snap-tests/docs-nested-config/packages/pkg-b/src/index.js create mode 100644 packages/cli/snap-tests/docs-nested-config/pnpm-workspace.yaml create mode 100644 packages/cli/snap-tests/docs-nested-config/snap.txt create mode 100644 packages/cli/snap-tests/docs-nested-config/steps.json create mode 100644 packages/cli/snap-tests/docs-nested-config/vite.config.permissive.ts create mode 100644 packages/cli/snap-tests/docs-nested-config/vite.config.ts diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index aa04051c9c..fa3f476f8b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -42,6 +42,7 @@ const guideSidebar = [ items: [ { text: 'Lint', link: '/guide/lint' }, { text: 'Format', link: '/guide/fmt' }, + { text: 'Nested Configuration', link: '/guide/nested-config' }, ], }, { text: 'Test', link: '/guide/test' }, diff --git a/docs/guide/fmt.md b/docs/guide/fmt.md index bb9a59d02a..eb112e1584 100644 --- a/docs/guide/fmt.md +++ b/docs/guide/fmt.md @@ -30,6 +30,8 @@ For editors, point the formatter config path at `./vite.config.ts` so format-on- For the upstream formatter behavior and configuration reference, see the [Oxfmt docs](https://oxc.rs/docs/guide/usage/formatter.html). +In monorepos, `vp fmt` walks up from the current working directory and uses the first `vite.config.ts` it finds — unlike `vp lint`, which is cwd-only. See [Nested Configuration](/guide/nested-config) for the full resolution rules. + ```ts import { defineConfig } from 'vite-plus'; diff --git a/docs/guide/lint.md b/docs/guide/lint.md index ffd29ecd36..73518217f4 100644 --- a/docs/guide/lint.md +++ b/docs/guide/lint.md @@ -22,6 +22,8 @@ Put lint configuration directly in the `lint` block in `vite.config.ts` so all y For the upstream rule set, options, and compatibility details, see the [Oxlint docs](https://oxc.rs/docs/guide/usage/linter.html). +In monorepos, `vp lint` uses `/vite.config.ts` if it exists, and falls back to Oxlint's built-in defaults otherwise — it does not walk up. See [Nested Configuration](/guide/nested-config) for details. + ```ts import { defineConfig } from 'vite-plus'; diff --git a/docs/guide/nested-config.md b/docs/guide/nested-config.md new file mode 100644 index 0000000000..8fd0ac3479 --- /dev/null +++ b/docs/guide/nested-config.md @@ -0,0 +1,103 @@ +# Nested Configuration + +Vite+ supports multiple `vite.config.ts` files in the same repository, so packages in a monorepo can have their own lint and format settings while sharing a baseline. + +`vp lint` and `vp fmt` resolve configuration from the **current working directory** (cwd), but with one subtle difference: + +- **`vp lint`** — cwd-only. Uses `/vite.config.ts` if it exists. If not, falls back to Oxlint's built-in defaults — it does **not** walk up to find an ancestor config. +- **`vp fmt`** — cwd walk-up. Walks up from cwd and uses the first `vite.config.ts` it finds. If none is found anywhere up to the filesystem root, falls back to Oxfmt defaults. + +In both cases, the selected config applies to every path in the run. + +If you only need to exclude files or folders, use [`lint.ignorePatterns`](/config/lint) or [`fmt.ignorePatterns`](/config/fmt) instead. + +## How it works + +Given the following structure: + +``` +my-project/ +├── vite.config.ts +├── src/ +│ └── index.ts +├── package1/ +│ ├── vite.config.ts +│ └── src/index.ts +└── package2/ + └── src/index.ts +``` + +`vp lint`: + +- From `my-project/` → uses `my-project/vite.config.ts` for every file (including files under `package1/` and `package2/`). +- From `my-project/package1/` → uses `my-project/package1/vite.config.ts`. +- From `my-project/package2/` → no local `vite.config.ts`, so Oxlint's built-in defaults are used. +- From `my-project/package1/src/` → no local `vite.config.ts`, so Oxlint's built-in defaults are used even though `package1/vite.config.ts` exists one level up. + +`vp fmt`: + +- From `my-project/` → uses `my-project/vite.config.ts`. +- From `my-project/package1/` → uses `my-project/package1/vite.config.ts`. +- From `my-project/package2/` → walks up past `package2/` and uses `my-project/vite.config.ts`. +- From `my-project/package1/src/` → walks up past `src/` and uses `my-project/package1/vite.config.ts`. + +If your monorepo needs different settings per package, run `vp lint` / `vp fmt` from each package directory (for example, via a `vp run -r lint` task), or pin a specific config with `-c`. + +## What to expect + +Configuration files are not automatically merged. When a file is selected, it fully replaces any other config — there is no parent/child layering. To share settings, import the parent config and spread it; see the [monorepo pattern](#monorepo-pattern-share-a-base-config) below. + +Command-line options override configuration files. + +Passing an explicit config file location using `-c` or `--config` bypasses cwd-based resolution entirely, and only that single configuration file is used — for both `vp lint` and `vp fmt`. + +For lint, you can also pass `--disable-nested-config` to stop Oxlint from picking up any stray legacy config files that may exist in the tree: + +```bash +vp lint --disable-nested-config +vp check --disable-nested-config +``` + +There is no equivalent flag for `vp fmt`; pass `-c` if you need to pin a single format config. + +`options.typeAware` and `options.typeCheck` are root-config-only. If either is set in a nested `vite.config.ts` that ends up being selected as the lint config, `vp lint` reports an error. + +::: tip Breaking change since the April 2026 release + +Earlier versions of Vite+ always injected the workspace-root `vite.config.ts` into every `vp lint` / `vp fmt` invocation, regardless of cwd. Vite+ now lets cwd-based resolution select the config, so running `vp lint` / `vp fmt` from inside a sub-package picks up that sub-package's own `vite.config.ts`. See [#1378](https://github.com/voidzero-dev/vite-plus/pull/1378) for the migration notes. + +::: + +## Monorepo pattern: share a base config + +In a monorepo, you often want one shared baseline at the root and small package-specific adjustments. Import the root `vite.config.ts` from the nested one and spread it: + +```ts [my-project/vite.config.ts] +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + lint: { + rules: { + 'no-debugger': 'error', + }, + }, +}); +``` + +```ts [my-project/package1/vite.config.ts] +import { defineConfig } from 'vite-plus'; +import baseConfig from '../vite.config.ts'; + +export default defineConfig({ + ...baseConfig, + lint: { + ...baseConfig.lint, + rules: { + ...baseConfig.lint?.rules, + 'no-console': 'off', + }, + }, +}); +``` + +This keeps the shared baseline in one place and makes package configs small and focused. diff --git a/packages/cli/snap-tests/docs-nested-config/package.json b/packages/cli/snap-tests/docs-nested-config/package.json new file mode 100644 index 0000000000..a534beb58c --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/package.json @@ -0,0 +1,7 @@ +{ + "name": "docs-nested-config-test", + "version": "0.0.0", + "private": true, + "type": "module", + "packageManager": "pnpm@10.16.1" +} diff --git a/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/package.json b/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/package.json new file mode 100644 index 0000000000..14d6f2e880 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/package.json @@ -0,0 +1,5 @@ +{ + "name": "pkg-a", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/src/index.js b/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/src/index.js new file mode 100644 index 0000000000..8b63cec403 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/src/index.js @@ -0,0 +1,6 @@ +function pkgA() { + debugger; + return "hello from pkg-a"; +} + +export { pkgA }; diff --git a/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/vite.config.ts b/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/vite.config.ts new file mode 100644 index 0000000000..059c840d83 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/packages/pkg-a/vite.config.ts @@ -0,0 +1,12 @@ +// Nested config in pkg-a: relaxes the root's strict rules. +// cwd walk-up from pkg-a/ should pick this file, not the root. +export default { + lint: { + rules: { + 'no-debugger': 'off', + }, + }, + fmt: { + singleQuote: false, + }, +}; diff --git a/packages/cli/snap-tests/docs-nested-config/packages/pkg-b/package.json b/packages/cli/snap-tests/docs-nested-config/packages/pkg-b/package.json new file mode 100644 index 0000000000..b225a0d50c --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/packages/pkg-b/package.json @@ -0,0 +1,5 @@ +{ + "name": "pkg-b", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/docs-nested-config/packages/pkg-b/src/index.js b/packages/cli/snap-tests/docs-nested-config/packages/pkg-b/src/index.js new file mode 100644 index 0000000000..55c4412ff8 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/packages/pkg-b/src/index.js @@ -0,0 +1,6 @@ +function pkgB() { + debugger; + return "hello from pkg-b"; +} + +export { pkgB }; diff --git a/packages/cli/snap-tests/docs-nested-config/pnpm-workspace.yaml b/packages/cli/snap-tests/docs-nested-config/pnpm-workspace.yaml new file mode 100644 index 0000000000..18ec407efc --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/packages/cli/snap-tests/docs-nested-config/snap.txt b/packages/cli/snap-tests/docs-nested-config/snap.txt new file mode 100644 index 0000000000..3196e32868 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/snap.txt @@ -0,0 +1,93 @@ +> # Pins each claim in docs/guide/nested-config.md. Fixture layout: +> # root vite.config.ts -> no-debugger:error, singleQuote:true +> # root vite.config.permissive.ts -> no-debugger:off, singleQuote:false +> # packages/pkg-a/vite.config.ts -> no-debugger:off, singleQuote:false +> # packages/pkg-b/ -> no local vite.config.ts +> # Both pkg-a/src/index.js and pkg-b/src/index.js contain `debugger;` and double-quoted strings. +> # Claim 1: from a cwd that contains a vite.config.ts, both lint and fmt use it. +> # pkg-a's own config wins: no-debugger:off and singleQuote:false -> lint and fmt pass. +> cd packages/pkg-a && vp lint +Found 0 warnings and 0 errors. +Finished in ms on 2 files with rules using threads. + +> cd packages/pkg-a && vp fmt --check src +Checking formatting... +All matched files use the correct format. +Finished in ms on 1 files using threads. + +> # Claim 2a: `vp lint` does NOT walk up. pkg-b has no local vite.config.ts, +> # so Oxlint's built-in defaults apply -> no-debugger fires as a warning (exit 0), +> # not as the root's `error`. Root's vite.config.ts is NOT consulted. +> cd packages/pkg-b && vp lint + + ⚠ eslint(no-debugger): `debugger` statement is not allowed + ╭─[src/index.js:2:3] + 1 │ function pkgB() { + 2 │ debugger; + · ───────── + 3 │ return "hello from pkg-b"; + ╰──── + help: Remove the debugger statement + +Found 1 warning and 0 errors. +Finished in ms on 1 file with rules using threads. + +> # Claim 2b: `vp fmt` DOES walk up. From pkg-b (no local config) it reaches the root, +> # which sets singleQuote:true -> double-quoted strings fail (exit 1). +[1]> cd packages/pkg-b && vp fmt --check src +Checking formatting... +src/index.js (ms) + +Format issues found in above 1 files. Run without `--check` to fix. +Finished in ms on 1 files using threads. + +> # Claim 3: from the workspace root the root config applies to every file, +> # including files in pkg-a that have their own vite.config.ts (not merged, +> # and not resolved per-file). Running against each package separately keeps +> # the snap output stable regardless of oxlint's parallel scheduling. +[1]> vp lint packages/pkg-a/src + + × eslint(no-debugger): `debugger` statement is not allowed + ╭─[packages/pkg-a/src/index.js:2:3] + 1 │ function pkgA() { + 2 │ debugger; + · ───────── + 3 │ return "hello from pkg-a"; + ╰──── + help: Remove the debugger statement + +Found 0 warnings and 1 error. +Finished in ms on 1 file with rules using threads. + +[1]> vp lint packages/pkg-b/src + + × eslint(no-debugger): `debugger` statement is not allowed + ╭─[packages/pkg-b/src/index.js:2:3] + 1 │ function pkgB() { + 2 │ debugger; + · ───────── + 3 │ return "hello from pkg-b"; + ╰──── + help: Remove the debugger statement + +Found 0 warnings and 1 error. +Finished in ms on 1 file with rules using threads. + +[1]> vp fmt --check packages +Checking formatting... +packages/pkg-a/src/index.js (ms) +packages/pkg-b/src/index.js (ms) + +Format issues found in above 2 files. Run without `--check` to fix. +Finished in ms on 5 files using threads. + +> # Claim 4: `-c ` bypasses cwd-based resolution. The permissive config applies +> # to every targeted file, so both debugger statements and double-quoted strings pass. +> vp lint -c vite.config.permissive.ts +Found 0 warnings and 0 errors. +Finished in ms on 5 files with rules using threads. + +> vp fmt -c vite.config.permissive.ts --check packages/pkg-a/src packages/pkg-b/src +Checking formatting... +All matched files use the correct format. +Finished in ms on 2 files using threads. diff --git a/packages/cli/snap-tests/docs-nested-config/steps.json b/packages/cli/snap-tests/docs-nested-config/steps.json new file mode 100644 index 0000000000..34ed436e94 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/steps.json @@ -0,0 +1,33 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": [ + "# Pins each claim in docs/guide/nested-config.md. Fixture layout:", + "# root vite.config.ts -> no-debugger:error, singleQuote:true", + "# root vite.config.permissive.ts -> no-debugger:off, singleQuote:false", + "# packages/pkg-a/vite.config.ts -> no-debugger:off, singleQuote:false", + "# packages/pkg-b/ -> no local vite.config.ts", + "# Both pkg-a/src/index.js and pkg-b/src/index.js contain `debugger;` and double-quoted strings.", + "# Claim 1: from a cwd that contains a vite.config.ts, both lint and fmt use it.", + "# pkg-a's own config wins: no-debugger:off and singleQuote:false -> lint and fmt pass.", + "cd packages/pkg-a && vp lint", + "cd packages/pkg-a && vp fmt --check src", + "# Claim 2a: `vp lint` does NOT walk up. pkg-b has no local vite.config.ts,", + "# so Oxlint's built-in defaults apply -> no-debugger fires as a warning (exit 0),", + "# not as the root's `error`. Root's vite.config.ts is NOT consulted.", + "cd packages/pkg-b && vp lint", + "# Claim 2b: `vp fmt` DOES walk up. From pkg-b (no local config) it reaches the root,", + "# which sets singleQuote:true -> double-quoted strings fail (exit 1).", + "cd packages/pkg-b && vp fmt --check src", + "# Claim 3: from the workspace root the root config applies to every file,", + "# including files in pkg-a that have their own vite.config.ts (not merged,", + "# and not resolved per-file). Running against each package separately keeps", + "# the snap output stable regardless of oxlint's parallel scheduling.", + "vp lint packages/pkg-a/src", + "vp lint packages/pkg-b/src", + "vp fmt --check packages", + "# Claim 4: `-c ` bypasses cwd-based resolution. The permissive config applies", + "# to every targeted file, so both debugger statements and double-quoted strings pass.", + "vp lint -c vite.config.permissive.ts", + "vp fmt -c vite.config.permissive.ts --check packages/pkg-a/src packages/pkg-b/src" + ] +} diff --git a/packages/cli/snap-tests/docs-nested-config/vite.config.permissive.ts b/packages/cli/snap-tests/docs-nested-config/vite.config.permissive.ts new file mode 100644 index 0000000000..b0518c13c2 --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/vite.config.permissive.ts @@ -0,0 +1,12 @@ +// Pinned-via-`-c` config: relaxes the root's strict rules. +// Used to prove that `-c ` disables the cwd walk-up. +export default { + lint: { + rules: { + 'no-debugger': 'off', + }, + }, + fmt: { + singleQuote: false, + }, +}; diff --git a/packages/cli/snap-tests/docs-nested-config/vite.config.ts b/packages/cli/snap-tests/docs-nested-config/vite.config.ts new file mode 100644 index 0000000000..07a67246ed --- /dev/null +++ b/packages/cli/snap-tests/docs-nested-config/vite.config.ts @@ -0,0 +1,11 @@ +// Root config: strict rules and single quotes. +export default { + lint: { + rules: { + 'no-debugger': 'error', + }, + }, + fmt: { + singleQuote: true, + }, +}; From 046831cd24235f906dc7d95eaccfb3f15b16f585 Mon Sep 17 00:00:00 2001 From: MK Date: Tue, 14 Apr 2026 16:46:36 +0800 Subject: [PATCH 11/12] test(snap): cover bare `vp lint` / `vp fmt --check` at the workspace root Adds claim 2c/2d to probe deeper cwds where lint and fmt diverge (lint stays on oxlint defaults from pkg-a/src, fmt walks up to pkg-a's vite.config.ts), and replaces the targeted per-package lint commands in claim 3 with the bare `vp lint --threads=1` / `vp fmt --check` invocations a user would actually run. `--threads=1` keeps the multi-error output order stable across snapshot runs. --- .../snap-tests/docs-nested-config/snap.txt | 46 +++++++++++++------ .../snap-tests/docs-nested-config/steps.json | 21 ++++++--- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/packages/cli/snap-tests/docs-nested-config/snap.txt b/packages/cli/snap-tests/docs-nested-config/snap.txt index 3196e32868..5f985344c5 100644 --- a/packages/cli/snap-tests/docs-nested-config/snap.txt +++ b/packages/cli/snap-tests/docs-nested-config/snap.txt @@ -41,14 +41,13 @@ src/index.js (ms) Format issues found in above 1 files. Run without `--check` to fix. Finished in ms on 1 files using threads. -> # Claim 3: from the workspace root the root config applies to every file, -> # including files in pkg-a that have their own vite.config.ts (not merged, -> # and not resolved per-file). Running against each package separately keeps -> # the snap output stable regardless of oxlint's parallel scheduling. -[1]> vp lint packages/pkg-a/src +> # Claim 2c: `vp lint` does not walk up even when an ancestor config exists. +> # From pkg-a/src (pkg-a has a vite.config.ts one level up) oxlint defaults still apply, +> # proving lint resolution does not traverse parent directories. +> cd packages/pkg-a/src && vp lint - × eslint(no-debugger): `debugger` statement is not allowed - ╭─[packages/pkg-a/src/index.js:2:3] + ⚠ eslint(no-debugger): `debugger` statement is not allowed + ╭─[index.js:2:3] 1 │ function pkgA() { 2 │ debugger; · ───────── @@ -56,10 +55,22 @@ Finished in ms on 1 files using threads. ╰──── help: Remove the debugger statement -Found 0 warnings and 1 error. +Found 1 warning and 0 errors. Finished in ms on 1 file with rules using threads. -[1]> vp lint packages/pkg-b/src +> # Claim 2d: `vp fmt` walks up several levels when needed. +> # From pkg-a/src (no config) fmt finds pkg-a/vite.config.ts -> singleQuote:false -> passes. +> cd packages/pkg-a/src && vp fmt --check . +Checking formatting... +All matched files use the correct format. +Finished in ms on 1 files using threads. + +> # Claim 3: running `vp lint` / `vp fmt --check` directly at the workspace root +> # applies the root config to every file, including files in pkg-a that have +> # their own vite.config.ts (configs are not merged and not resolved per-file). +> # `--threads=1` is only used to keep oxlint's multi-error output order stable +> # across snapshot runs; it does not affect resolution. +[1]> vp lint --threads=1 × eslint(no-debugger): `debugger` statement is not allowed ╭─[packages/pkg-b/src/index.js:2:3] @@ -70,16 +81,25 @@ Finished in ms on 1 file with rules using thread ╰──── help: Remove the debugger statement -Found 0 warnings and 1 error. -Finished in ms on 1 file with rules using threads. + × eslint(no-debugger): `debugger` statement is not allowed + ╭─[packages/pkg-a/src/index.js:2:3] + 1 │ function pkgA() { + 2 │ debugger; + · ───────── + 3 │ return "hello from pkg-a"; + ╰──── + help: Remove the debugger statement + +Found 0 warnings and 2 errors. +Finished in ms on 5 files with rules using threads. -[1]> vp fmt --check packages +[1]> vp fmt --check Checking formatting... packages/pkg-a/src/index.js (ms) packages/pkg-b/src/index.js (ms) Format issues found in above 2 files. Run without `--check` to fix. -Finished in ms on 5 files using threads. +Finished in ms on 10 files using threads. > # Claim 4: `-c ` bypasses cwd-based resolution. The permissive config applies > # to every targeted file, so both debugger statements and double-quoted strings pass. diff --git a/packages/cli/snap-tests/docs-nested-config/steps.json b/packages/cli/snap-tests/docs-nested-config/steps.json index 34ed436e94..87b5d0aeb2 100644 --- a/packages/cli/snap-tests/docs-nested-config/steps.json +++ b/packages/cli/snap-tests/docs-nested-config/steps.json @@ -18,13 +18,20 @@ "# Claim 2b: `vp fmt` DOES walk up. From pkg-b (no local config) it reaches the root,", "# which sets singleQuote:true -> double-quoted strings fail (exit 1).", "cd packages/pkg-b && vp fmt --check src", - "# Claim 3: from the workspace root the root config applies to every file,", - "# including files in pkg-a that have their own vite.config.ts (not merged,", - "# and not resolved per-file). Running against each package separately keeps", - "# the snap output stable regardless of oxlint's parallel scheduling.", - "vp lint packages/pkg-a/src", - "vp lint packages/pkg-b/src", - "vp fmt --check packages", + "# Claim 2c: `vp lint` does not walk up even when an ancestor config exists.", + "# From pkg-a/src (pkg-a has a vite.config.ts one level up) oxlint defaults still apply,", + "# proving lint resolution does not traverse parent directories.", + "cd packages/pkg-a/src && vp lint", + "# Claim 2d: `vp fmt` walks up several levels when needed.", + "# From pkg-a/src (no config) fmt finds pkg-a/vite.config.ts -> singleQuote:false -> passes.", + "cd packages/pkg-a/src && vp fmt --check .", + "# Claim 3: running `vp lint` / `vp fmt --check` directly at the workspace root", + "# applies the root config to every file, including files in pkg-a that have", + "# their own vite.config.ts (configs are not merged and not resolved per-file).", + "# `--threads=1` is only used to keep oxlint's multi-error output order stable", + "# across snapshot runs; it does not affect resolution.", + "vp lint --threads=1", + "vp fmt --check", "# Claim 4: `-c ` bypasses cwd-based resolution. The permissive config applies", "# to every targeted file, so both debugger statements and double-quoted strings pass.", "vp lint -c vite.config.permissive.ts", From 4102ae27ef56d91df5856221cbbcfd8ab71356f9 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 15 Apr 2026 11:02:49 +0800 Subject: [PATCH 12/12] docs(guide): restructure nested-config resolution rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop oxlint/oxfmt internals from the user-facing guide, lead with the three rules (default cwd resolution, `-c` pins, `--disable-nested-config`), and replace the per-cwd bullet lists with tables. Also drop the unverified `typeAware`/`typeCheck` root-only caveat — current code does not enforce it. --- docs/guide/nested-config.md | 73 +++++++++++++++++++++---------------- vite.config.ts | 1 + 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/docs/guide/nested-config.md b/docs/guide/nested-config.md index 8fd0ac3479..8b42ae499a 100644 --- a/docs/guide/nested-config.md +++ b/docs/guide/nested-config.md @@ -1,19 +1,29 @@ # Nested Configuration -Vite+ supports multiple `vite.config.ts` files in the same repository, so packages in a monorepo can have their own lint and format settings while sharing a baseline. +Vite+ supports multiple `vite.config.ts` files in a repository, so packages in a monorepo can have their own lint and format settings while sharing a baseline. -`vp lint` and `vp fmt` resolve configuration from the **current working directory** (cwd), but with one subtle difference: +## How `vp lint` and `vp fmt` pick a config -- **`vp lint`** — cwd-only. Uses `/vite.config.ts` if it exists. If not, falls back to Oxlint's built-in defaults — it does **not** walk up to find an ancestor config. -- **`vp fmt`** — cwd walk-up. Walks up from cwd and uses the first `vite.config.ts` it finds. If none is found anywhere up to the filesystem root, falls back to Oxfmt defaults. +Config resolution is driven by the current working directory (cwd): -In both cases, the selected config applies to every path in the run. +- **`vp lint`** uses `/vite.config.ts`. If that file is missing, built-in defaults apply — `vp lint` does **not** walk up the directory tree looking for a parent `vite.config.ts`. +- **`vp fmt`** walks up from cwd and uses the first `vite.config.ts` it finds. If none is found, built-in defaults apply. -If you only need to exclude files or folders, use [`lint.ignorePatterns`](/config/lint) or [`fmt.ignorePatterns`](/config/fmt) instead. +In both cases, the selected config applies to every path in the run — there is no per-file resolution, and configs are never merged. -## How it works +If your monorepo needs different settings per package, run `vp lint` / `vp fmt` from each package directory (for example, via `vp run -r lint`), or pin a specific config with `-c`. -Given the following structure: +If you only want to exclude files or folders from an otherwise-shared config, use [`lint.ignorePatterns`](/config/lint) or [`fmt.ignorePatterns`](/config/fmt) instead. + +::: tip Breaking change since the April 2026 release + +Earlier versions of Vite+ pinned every `vp lint` / `vp fmt` invocation to the workspace-root `vite.config.ts`, regardless of cwd. Vite+ now lets cwd-based resolution select the config, so running from a sub-package picks up that sub-package's own `vite.config.ts`. See [#1378](https://github.com/voidzero-dev/vite-plus/pull/1378) for the migration notes. + +::: + +## Example + +Given this layout: ``` my-project/ @@ -29,48 +39,47 @@ my-project/ `vp lint`: -- From `my-project/` → uses `my-project/vite.config.ts` for every file (including files under `package1/` and `package2/`). -- From `my-project/package1/` → uses `my-project/package1/vite.config.ts`. -- From `my-project/package2/` → no local `vite.config.ts`, so Oxlint's built-in defaults are used. -- From `my-project/package1/src/` → no local `vite.config.ts`, so Oxlint's built-in defaults are used even though `package1/vite.config.ts` exists one level up. +| cwd | config used | +| ---------------------------- | ----------------------------------- | +| `my-project/` | `my-project/vite.config.ts` | +| `my-project/package1/` | `my-project/package1/vite.config.ts`| +| `my-project/package1/src/` | built-in defaults (no walk-up) | +| `my-project/package2/` | built-in defaults (no walk-up) | `vp fmt`: -- From `my-project/` → uses `my-project/vite.config.ts`. -- From `my-project/package1/` → uses `my-project/package1/vite.config.ts`. -- From `my-project/package2/` → walks up past `package2/` and uses `my-project/vite.config.ts`. -- From `my-project/package1/src/` → walks up past `src/` and uses `my-project/package1/vite.config.ts`. +| cwd | config used | +| ---------------------------- | ------------------------------------ | +| `my-project/` | `my-project/vite.config.ts` | +| `my-project/package1/` | `my-project/package1/vite.config.ts` | +| `my-project/package1/src/` | `my-project/package1/vite.config.ts` | +| `my-project/package2/` | `my-project/vite.config.ts` | -If your monorepo needs different settings per package, run `vp lint` / `vp fmt` from each package directory (for example, via a `vp run -r lint` task), or pin a specific config with `-c`. +## Pinning a config with `-c` -## What to expect +`-c` / `--config` bypasses cwd-based resolution. The specified file is used for every path in the run: -Configuration files are not automatically merged. When a file is selected, it fully replaces any other config — there is no parent/child layering. To share settings, import the parent config and spread it; see the [monorepo pattern](#monorepo-pattern-share-a-base-config) below. +```bash +vp lint -c vite.config.ts +vp fmt --check -c vite.config.ts +``` -Command-line options override configuration files. +This also works when you need a one-off config, for example a permissive CI variant. -Passing an explicit config file location using `-c` or `--config` bypasses cwd-based resolution entirely, and only that single configuration file is used — for both `vp lint` and `vp fmt`. +## `--disable-nested-config` (lint only) -For lint, you can also pass `--disable-nested-config` to stop Oxlint from picking up any stray legacy config files that may exist in the tree: +`vp lint` accepts `--disable-nested-config` to stop any auto-loading of nested lint configuration files that may exist in the tree: ```bash vp lint --disable-nested-config vp check --disable-nested-config ``` -There is no equivalent flag for `vp fmt`; pass `-c` if you need to pin a single format config. - -`options.typeAware` and `options.typeCheck` are root-config-only. If either is set in a nested `vite.config.ts` that ends up being selected as the lint config, `vp lint` reports an error. - -::: tip Breaking change since the April 2026 release - -Earlier versions of Vite+ always injected the workspace-root `vite.config.ts` into every `vp lint` / `vp fmt` invocation, regardless of cwd. Vite+ now lets cwd-based resolution select the config, so running `vp lint` / `vp fmt` from inside a sub-package picks up that sub-package's own `vite.config.ts`. See [#1378](https://github.com/voidzero-dev/vite-plus/pull/1378) for the migration notes. - -::: +This flag has no effect on `vite.config.ts` resolution, which is already cwd-only for `vp lint`. `vp fmt` has no equivalent flag; use `-c` to pin a single format config. ## Monorepo pattern: share a base config -In a monorepo, you often want one shared baseline at the root and small package-specific adjustments. Import the root `vite.config.ts` from the nested one and spread it: +Configs are never merged automatically — the selected config fully replaces any other. To share a baseline, import the parent config and spread it: ```ts [my-project/vite.config.ts] import { defineConfig } from 'vite-plus'; diff --git a/vite.config.ts b/vite.config.ts index 13375711ee..4bd73d01f4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -71,6 +71,7 @@ export default defineConfig({ 'packages/cli/snap-tests/fmt-ignore-patterns/src/ignored', 'packages/cli/snap-tests/nested-oxc-config-proj-1/**', 'packages/cli/snap-tests-global/migration-lint-staged-ts-config', + 'packages/cli/snap-tests/docs-nested-config', 'docs/**', 'ecosystem-ci/*/**', 'packages/test/**.cjs',