diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index ba764c9e2f..200c0f922a 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -169,6 +169,7 @@ export default extendConfig( { text: 'Run', link: '/config/run' }, { text: 'Format', link: '/config/fmt' }, { text: 'Lint', link: '/config/lint' }, + { text: 'Check', link: '/config/check' }, { text: 'Test', link: '/config/test' }, { text: 'Build', link: '/config/build' }, { text: 'Pack', link: '/config/pack' }, diff --git a/docs/config/check.md b/docs/config/check.md new file mode 100644 index 0000000000..81bfd6520d --- /dev/null +++ b/docs/config/check.md @@ -0,0 +1,35 @@ +# Check Config + +`vp check` runs format, lint, and type checks together. The `check` block in `vite.config.ts` sets defaults for the composite command, mirroring the `--no-fmt` and `--no-lint` CLI flags. + +This is useful when a project wants to keep most of the toolchain but skip one step by default. For example, a team that lints but does not format can disable `check.fmt` so a plain `vp check` (the command agents and contributors run most) only lints, without anyone needing to remember `--no-fmt`. + +## Example + +```ts [vite.config.ts] +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + check: { + // Skip the format step in `vp check`. Defaults to true. + fmt: false, + // Skip lint rules in `vp check`. Type-check still runs when both + // `lint.options.typeAware` and `lint.options.typeCheck` are enabled. + // Defaults to true. + lint: true, + }, +}); +``` + +When a step is disabled here, `vp check` prints a short `note:` line so it is clear why the step did not run. With the `check.fmt: false` config above: + +```bash +$ vp check +note: Format skipped (check.fmt: false in vite.config.ts) +pass: Found no warnings or lint errors in 1 file (12ms, 8 threads) +``` + +## Scope and precedence + +- These options only affect the composite `vp check`. Standalone [`vp fmt`](/config/fmt) and [`vp lint`](/config/lint) are unaffected, so you can still run a disabled tool directly when you need it once. Note that any `vp check` invocation honors these defaults, including one run from a pre-commit hook: if your [`staged`](/config/staged) tasks call `vp check`, that step is skipped there too. +- A step is skipped if the config disables it **or** the matching CLI flag is passed. There is no flag to re-enable a step disabled in config; run `vp fmt` or `vp lint` directly instead. diff --git a/docs/config/index.md b/docs/config/index.md index df20c6a683..d7f1b25543 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -14,6 +14,7 @@ export default defineConfig({ run: {}, fmt: {}, lint: {}, + check: {}, test: {}, pack: {}, staged: {}, @@ -28,6 +29,7 @@ Vite+ extends the basic Vite configuration with these additions: - [`run`](/config/run) for Vite Task - [`fmt`](/config/fmt) for Oxfmt - [`lint`](/config/lint) for Oxlint +- [`check`](/config/check) for `vp check` defaults - [`test`](/config/test) for Vitest - [`pack`](/config/pack) for tsdown - [`staged`](/config/staged) for staged-file checks diff --git a/docs/guide/check.md b/docs/guide/check.md index 92241a7cd9..0d04c3e58e 100644 --- a/docs/guide/check.md +++ b/docs/guide/check.md @@ -42,3 +42,19 @@ export default defineConfig({ }, }); ``` + +### Disabling a step by default + +To make `vp check` skip formatting or linting without passing a flag every time, set the [`check`](/config/check) block in `vite.config.ts`. This is handy when a project wants the rest of the toolchain but not, say, formatting: + +```ts [vite.config.ts] +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + check: { + fmt: false, // `vp check` lints (and type-checks) but does not format + }, +}); +``` + +These options only affect `vp check`; standalone `vp fmt` and `vp lint` still run normally. A step is skipped if it is disabled in config or the matching `--no-fmt` / `--no-lint` flag is passed. Because the defaults apply to every `vp check` run, a pre-commit hook that calls `vp check` will skip the disabled step too. See [Check config](/config/check) for the full reference. diff --git a/packages/cli/binding/src/check/analysis.rs b/packages/cli/binding/src/check/analysis.rs index 6068b1a866..e02d26fb54 100644 --- a/packages/cli/binding/src/check/analysis.rs +++ b/packages/cli/binding/src/check/analysis.rs @@ -83,15 +83,13 @@ impl LintMessageKind { /// analysis must be on for TypeScript diagnostics to surface. pub(super) fn lint_config_type_check_enabled(lint_config: Option<&serde_json::Value>) -> bool { let options = lint_config.and_then(|config| config.get("options")); - let type_aware = options - .and_then(|options| options.get("typeAware")) - .and_then(serde_json::Value::as_bool) - .unwrap_or(false); - let type_check = options - .and_then(|options| options.get("typeCheck")) - .and_then(serde_json::Value::as_bool) - .unwrap_or(false); - type_aware && type_check + json_bool(options, "typeAware", false) && json_bool(options, "typeCheck", false) +} + +/// Read a boolean `key` from a JSON object, falling back to `default` when the +/// object is absent, the key is missing, or the value is not a boolean. +pub(super) fn json_bool(value: Option<&serde_json::Value>, key: &str, default: bool) -> bool { + value.and_then(|value| value.get(key)).and_then(serde_json::Value::as_bool).unwrap_or(default) } fn parse_check_summary(line: &str) -> Option { @@ -247,7 +245,7 @@ pub(super) fn analyze_lint_output(output: &str) -> Option, envs: &Arc, Arc>>, @@ -38,6 +38,21 @@ pub(crate) async fn execute_check( let mut deferred_lint_pass: Option<(String, String)> = None; let resolved_vite_config = resolver.resolve_universal_vite_config().await?; + // A step is skipped when either the CLI flag is passed OR `check.fmt`/ + // `check.lint` is disabled in vite.config.ts. The skip note is printed only + // when CONFIG (not the CLI flag) turned a step off, so existing `--no-fmt` / + // `--no-lint` output stays byte-identical. + let config_fmt_off = !json_bool(resolved_vite_config.check.as_ref(), "fmt", true); + let config_lint_off = !json_bool(resolved_vite_config.check.as_ref(), "lint", true); + if config_fmt_off && !no_fmt_flag { + output::note("Format skipped (check.fmt: false in vite.config.ts)"); + } + if config_lint_off && !no_lint_flag { + output::note("Lint skipped (check.lint: false in vite.config.ts)"); + } + let no_fmt = no_fmt_flag || config_fmt_off; + let no_lint = no_lint_flag || config_lint_off; + let type_check_enabled = lint_config_type_check_enabled(resolved_vite_config.lint.as_ref()); let lint_enabled = !no_lint; let run_lint_phase = lint_enabled || type_check_enabled; @@ -45,7 +60,7 @@ pub(crate) async fn execute_check( if no_fmt && !run_lint_phase { output::error("No checks enabled"); print_summary_line( - "Enable `lint.options.typeCheck` in vite.config.ts to use `vp check --no-fmt --no-lint` for type-check only, or drop a flag to re-enable fmt/lint.", + "Enable `lint.options.typeCheck` in vite.config.ts for type-check only, drop a `--no-fmt`/`--no-lint` flag, or re-enable `check.fmt`/`check.lint` in vite.config.ts.", ); return Ok(ExitStatus(1)); } diff --git a/packages/cli/binding/src/cli/types.rs b/packages/cli/binding/src/cli/types.rs index b0b935af02..d857ed7ee1 100644 --- a/packages/cli/binding/src/cli/types.rs +++ b/packages/cli/binding/src/cli/types.rs @@ -15,6 +15,7 @@ pub(crate) struct ResolvedUniversalViteConfig { pub(crate) config_file: Option, pub(crate) lint: Option, pub(crate) fmt: Option, + pub(crate) check: Option, pub(crate) run: Option, } diff --git a/packages/cli/snap-tests/check-all-skipped/snap.txt b/packages/cli/snap-tests/check-all-skipped/snap.txt index c462f68156..5d67c4b8ed 100644 --- a/packages/cli/snap-tests/check-all-skipped/snap.txt +++ b/packages/cli/snap-tests/check-all-skipped/snap.txt @@ -1,4 +1,4 @@ [1]> vp check --no-fmt --no-lint error: No checks enabled -Enable `lint.options.typeCheck` in vite.config.ts to use `vp check --no-fmt --no-lint` for type-check only, or drop a flag to re-enable fmt/lint. +Enable `lint.options.typeCheck` in vite.config.ts for type-check only, drop a `--no-fmt`/`--no-lint` flag, or re-enable `check.fmt`/`check.lint` in vite.config.ts. diff --git a/packages/cli/snap-tests/check-config-flag-precedence/package.json b/packages/cli/snap-tests/check-config-flag-precedence/package.json new file mode 100644 index 0000000000..7b073ab646 --- /dev/null +++ b/packages/cli/snap-tests/check-config-flag-precedence/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-config-flag-precedence", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-config-flag-precedence/snap.txt b/packages/cli/snap-tests/check-config-flag-precedence/snap.txt new file mode 100644 index 0000000000..81f5967558 --- /dev/null +++ b/packages/cli/snap-tests/check-config-flag-precedence/snap.txt @@ -0,0 +1,2 @@ +> vp check --no-fmt +pass: Found no warnings or lint errors in 2 files (ms, threads) diff --git a/packages/cli/snap-tests/check-config-flag-precedence/src/index.js b/packages/cli/snap-tests/check-config-flag-precedence/src/index.js new file mode 100644 index 0000000000..13305bd3e9 --- /dev/null +++ b/packages/cli/snap-tests/check-config-flag-precedence/src/index.js @@ -0,0 +1,5 @@ +function hello() { + return "hello"; +} + +export { hello }; diff --git a/packages/cli/snap-tests/check-config-flag-precedence/steps.json b/packages/cli/snap-tests/check-config-flag-precedence/steps.json new file mode 100644 index 0000000000..aba7793b58 --- /dev/null +++ b/packages/cli/snap-tests/check-config-flag-precedence/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check --no-fmt"] +} diff --git a/packages/cli/snap-tests/check-config-flag-precedence/vite.config.ts b/packages/cli/snap-tests/check-config-flag-precedence/vite.config.ts new file mode 100644 index 0000000000..f3f231d736 --- /dev/null +++ b/packages/cli/snap-tests/check-config-flag-precedence/vite.config.ts @@ -0,0 +1,5 @@ +export default { + check: { + fmt: true, + }, +}; diff --git a/packages/cli/snap-tests/check-config-no-fmt/package.json b/packages/cli/snap-tests/check-config-no-fmt/package.json new file mode 100644 index 0000000000..b9c2ead40e --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-fmt/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-config-no-fmt", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-config-no-fmt/snap.txt b/packages/cli/snap-tests/check-config-no-fmt/snap.txt new file mode 100644 index 0000000000..6f48cac073 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-fmt/snap.txt @@ -0,0 +1,3 @@ +> vp check +note: Format skipped (check.fmt: false in vite.config.ts) +pass: Found no warnings or lint errors in 2 files (ms, threads) diff --git a/packages/cli/snap-tests/check-config-no-fmt/src/index.js b/packages/cli/snap-tests/check-config-no-fmt/src/index.js new file mode 100644 index 0000000000..13305bd3e9 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-fmt/src/index.js @@ -0,0 +1,5 @@ +function hello() { + return "hello"; +} + +export { hello }; diff --git a/packages/cli/snap-tests/check-config-no-fmt/steps.json b/packages/cli/snap-tests/check-config-no-fmt/steps.json new file mode 100644 index 0000000000..d9c26d5a29 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-fmt/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check"] +} diff --git a/packages/cli/snap-tests/check-config-no-fmt/vite.config.ts b/packages/cli/snap-tests/check-config-no-fmt/vite.config.ts new file mode 100644 index 0000000000..88226e94aa --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-fmt/vite.config.ts @@ -0,0 +1,5 @@ +export default { + check: { + fmt: false, + }, +}; diff --git a/packages/cli/snap-tests/check-config-no-lint/package.json b/packages/cli/snap-tests/check-config-no-lint/package.json new file mode 100644 index 0000000000..1af4f130a2 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-lint/package.json @@ -0,0 +1,5 @@ +{ + "name": "check-config-no-lint", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/check-config-no-lint/snap.txt b/packages/cli/snap-tests/check-config-no-lint/snap.txt new file mode 100644 index 0000000000..c5d4e8f5fe --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-lint/snap.txt @@ -0,0 +1,3 @@ +> vp check +note: Lint skipped (check.lint: false in vite.config.ts) +pass: All 4 files are correctly formatted (ms, threads) diff --git a/packages/cli/snap-tests/check-config-no-lint/src/index.js b/packages/cli/snap-tests/check-config-no-lint/src/index.js new file mode 100644 index 0000000000..13305bd3e9 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-lint/src/index.js @@ -0,0 +1,5 @@ +function hello() { + return "hello"; +} + +export { hello }; diff --git a/packages/cli/snap-tests/check-config-no-lint/steps.json b/packages/cli/snap-tests/check-config-no-lint/steps.json new file mode 100644 index 0000000000..d9c26d5a29 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-lint/steps.json @@ -0,0 +1,6 @@ +{ + "env": { + "VITE_DISABLE_AUTO_INSTALL": "1" + }, + "commands": ["vp check"] +} diff --git a/packages/cli/snap-tests/check-config-no-lint/vite.config.ts b/packages/cli/snap-tests/check-config-no-lint/vite.config.ts new file mode 100644 index 0000000000..6ba8f85d81 --- /dev/null +++ b/packages/cli/snap-tests/check-config-no-lint/vite.config.ts @@ -0,0 +1,5 @@ +export default { + check: { + lint: false, + }, +}; diff --git a/packages/cli/src/define-config.ts b/packages/cli/src/define-config.ts index eefe03e824..4dcd5ec5e8 100644 --- a/packages/cli/src/define-config.ts +++ b/packages/cli/src/define-config.ts @@ -31,6 +31,26 @@ declare module '@voidzero-dev/vite-plus-core' { fmt?: OxfmtConfig; + /** + * Defaults for the `vp check` composite command. Each flag mirrors the + * matching CLI option (`--no-fmt` / `--no-lint`) and only affects + * `vp check`; standalone `vp fmt` / `vp lint` are unaffected. + */ + check?: { + /** + * Run the format step in `vp check`. + * @default true + */ + fmt?: boolean; + + /** + * Run the lint step in `vp check`. Type-check still runs when both + * `lint.options.typeAware` and `lint.options.typeCheck` are enabled. + * @default true + */ + lint?: boolean; + }; + pack?: PackUserConfig | PackUserConfig[]; run?: RunConfig; diff --git a/packages/cli/src/resolve-vite-config.ts b/packages/cli/src/resolve-vite-config.ts index 8cafa5bb6a..52d24619ce 100644 --- a/packages/cli/src/resolve-vite-config.ts +++ b/packages/cli/src/resolve-vite-config.ts @@ -126,6 +126,7 @@ export async function resolveUniversalViteConfig(err: null | Error, viteConfigCw configFile: config.configFile, lint: config.lint, fmt: config.fmt, + check: config.check, run: config.run, staged: config.staged, });