fix(cli): skip merging standalone oxfmt/oxlint config when key already in vite.config.ts#1601
Merged
Conversation
…y in vite.config.ts `mergeAndRemoveJsonConfig` was the only `mergeJsonConfig` call site missing the "skip if key already present" guard the other call sites use. Since the Rust merge step always prepends, templates that ship a populated `vite.config.ts` AND a standalone `.oxfmtrc.jsonc` (e.g. create-fate) ended up with two `fmt:` blocks after `vp create` / `vp migrate`. Now read `vite.config.ts` first; if the key is already declared, unlink the redundant standalone file (matching the existing skip-existing behavior in `applyToolInitConfigToViteConfig`) and log info instead of merging. Adds reproducing unit tests (fmt + lint) and a global snap test mirroring fate's real-world fmt/lint configuration.
✅ Deploy Preview for viteplus-preview canceled.
|
…nfig_key
`mergeAndRemoveJsonConfig`'s regex gate (`\b${configKey}\s*:`) had known
false positives (comments, string literals, nested keys like
`plugins: [{ fmt: ... }]`) and false negatives (e.g. spread). Move the
check into Rust as a precise AST walker that mirrors the six object-literal
shapes the merger already understands (`defineConfig({...})`, arrow
callback, `return {...}` inside callback, plain `export default`,
`satisfies` export, arrow-wrapped defineConfig). Bare identifier keys and
quoted string keys both match; computed keys, comments, strings, and
nested-object keys are ignored.
The `return $VAR` variant cannot be inspected statically — that path
conservatively reports `false`, which is safe because the merger uses
object spread (`{ key: ..., ...$VAR }`) so duplicate keys resolve to the
later spread at runtime.
- crates/vite_migration: new `has_config_key`, 13 unit tests covering the
shapes plus comment/string/nesting traps and the fate template shape.
- packages/cli/binding: NAPI export `hasConfigKey(viteConfigPath, configKey)`.
- packages/cli: `mergeAndRemoveJsonConfig` calls `hasConfigKey` instead of
the regex.
Snap output is byte-identical to the regex version on the regression case;
all 75 migrator unit tests and the migration snap suite still pass.
Address review feedback from /simplify: - Drop the regex-based hasConfigKey twin in init-config.ts; route inspectInitCommand / applyToolInitConfigToViteConfig through the new AST-based NAPI. Same swap for the inline regex in injectConfigDefaults. Removes the bug-prone duplicate that motivated this PR. - Simplify Rust helpers: .map().unwrap_or(false) → .is_some_and(), is_define_config_arrow_body to a filter-chain, drop narrating comments that restate kind names already in the match arms. - Drop the redundant lint unit test in migrator.spec.ts; the Rust suite exhaustively covers AST shapes (13 cases), and the fmt test alone proves the TS wiring through NAPI + mergeAndRemoveJsonConfig. All 180 Rust + 81 TS tests pass; init-config and migration snap tests unchanged.
Member
Author
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e0c99be. Configure here.
cpojer
approved these changes
May 18, 2026
Member
cpojer
left a comment
There was a problem hiding this comment.
Haha, I didn't realize the fate templates ship with duplicate configs. Woops. Thank you for the fix in Vite+!
Member
Author
|
@cpojer fate templates help me realize how fragile the previous temporary solutions were 😢 |
fengmk2
added a commit
that referenced
this pull request
May 19, 2026
Release vite-plus v0.1.22: Security Patch, Parallel Global Install & Scaffold Polish A critical Vitest browser-mode security fix, parallel `vp add -g` installs, a built-in oxlint rule to prefer `vite-plus` imports, and a new `--git` switch for `vp create`. ### Highlights - **Security**: bundled `vitest` bumped to `4.1.6` to address [GHSA-2h32-95rg-cppp](GHSA-2h32-95rg-cppp) (Critical, CVSS 9.6), an XSS to RCE chain via the `otelCarrier` query parameter in Vitest browser mode ([#1633](#1633)) - **Parallel global install**: `vp add/install/update -g` now installs packages concurrently with a progress bar and a `--concurrency` flag (default 5) ([#1597](#1597)) - **Prefer vite-plus imports**: new bundled oxlint rule rewrites `vite`/`vitest` imports to `vite-plus`, enabled by default in generated and migrated `lint` configs ([#1408](#1408)) - **Git init on scaffold**: `vp create` learns `--git`/`--no-git` (interactive prompt; auto-commits "Initial commit from Vite+") ([#1484](#1484)) ### Features - Spawn npm for global installation in parallel with a progress bar and a `--concurrency` option ([#1597](#1597)), by @liangmiQwQ - Add bundled oxlint rule to prefer `vite-plus` imports over `vite`/`vitest` ([#1408](#1408)), by @Han5991 - `vp create`: initialize a git repository and create an initial commit on scaffold ([#1484](#1484)), by @ryohidaka - `vp create`: rename underscore-prefixed files (`_gitignore`, `_npmrc`, `_yarnrc.yml`) to dotfiles for `@org/create` bundled templates ([#1574](#1574)), by @jong-kyung - Add `VP_PR_VERSION` env var to install unreleased PR builds via pkg.pr.new ([#1578](#1578)), by @fengmk2 ### Fixes & Enhancements - Skip merging standalone `.oxfmtrc`/`.oxlintrc` config when the `fmt:`/`lint:` key is already declared in `vite.config.ts` (fixes duplicate-block regression in `vp create fate`) ([#1601](#1601)), by @fengmk2 - Suppress the `VITE+ - The Unified Toolchain for the Web` banner for `vp lint --lsp`, `vp fmt --lsp`, and `vp fmt --stdin-filepath` so stdout stays a pure LSP / formatter stream ([#1619](#1619)), by @fengmk2 - `vp create`: detect output directory when running in the current directory ([#1606](#1606)), by @jong-kyung - `vp update -g`: skip installs when the recorded global package version already matches the npm-resolved version, and tolerate string/array outputs from `npm view ... version --json` ([#1596](#1596)), by @leno23 - `vp create`: preserve single-segment project path in `updateWorkspaceConfig` ([#1582](#1582)), by @jong-kyung - `vp env use`: keep the change session-scoped on Windows ([#1577](#1577)), by @fengmk2 - `vp rebuild`: accept positional package names ([#1564](#1564)), by @fengmk2 - Adopt the new vite-task error formatter; errors now print as `error: <top-level>` plus `* <source>` chain lines, with bold-red highlight on a TTY ([vite-task#390](voidzero-dev/vite-task#390)), by @branchseer - vite-task: forward `LOCALAPPDATA` so Node's compile cache stays outside the workspace on Windows ([vite-task#389](voidzero-dev/vite-task#389)), by @branchseer - Bump vite-task to `c945cc0` ([#1628](#1628)), by @branchseer ### Refactor - Revert `vp pm plugin` command (per discussion in #1038) ([#1623](#1623)), by @jong-kyung ### Docs - Add `vitepress-plugin-llms` to the docs site so the published docs include LLM-friendly outputs (`/llms.txt`) ([#1625](#1625)), by @jong-kyung - Refresh home stats for oxlint, vite, and vitest ([#1512](#1512)), by @nozomee - Mention `vp env doctor` in agent instructions ([#1603](#1603)), by @leno23 ### Chore - Consolidate the upstream build chain into a single `pnpm build` script (justfile recipe now just calls `pnpm build`) ([#1626](#1626)), by @fengmk2 - Fix bootstrap-cli on Windows ([#1583](#1583)), by @fengmk2 - Refresh trusted stack stats ([#1573](#1573), [#1616](#1616)), by @voidzero-guard[bot] - Update GitHub Actions ([#1611](#1611), [#1612](#1612)), by @renovate[bot] - Address zizmor findings in composite actions and the release workflow; drop unused `actions-cool/issues-helper` ([#1630](#1630)), by @Boshen - Switch plain checkouts to `taiki-e/checkout-action` ([#1620](#1620)), by @Boshen - Switch release to a version-bump PR + push trigger flow ([#1575](#1575)), by @Boshen - Gate release publish on environment approval with a Discord notice ([#1571](#1571)), by @Boshen - Enable `cargo clippy` with `-D warnings` ([#1579](#1579)), by @Boshen - Drop unused `setup-node` from the version-check job ([#1600](#1600)), by @fengmk2 - Add Void deploy workflows for the docs site ([#1590](#1590)), by @fengmk2 - Add `--help` case to config snap tests for npm10/yarn1/yarn4 ([#1585](#1585)), by @jong-kyung - Add `--help` case to publish snap tests for npm10/yarn1/yarn4 ([#1584](#1584)), by @jong-kyung - Verify `.gitignore` and `.yarnrc.yml` in the new-vite-monorepo snap ([#1576](#1576)), by @jong-kyung - vite-task: bump pnpm to `11.1.2` ([vite-task#383](voidzero-dev/vite-task#383)), by @branchseer - vite-task: update lint-staged to v17 ([vite-task#385](voidzero-dev/vite-task#385)), by @renovate[bot] ### Bundled Versions | Tool | Version | Source | | --- | --- | --- | | vite | `8.0.11` | [`66f3194`](vitejs/vite@66f3194) | | rolldown | `1.0.0` | [`ac5c710`](rolldown/rolldown@ac5c710) | | tsdown | `0.22.0` | [npm](https://npmx.dev/package/tsdown/v/0.22.0) | | vitest | `4.1.6` | [npm](https://npmx.dev/package/vitest/v/4.1.6) | | oxlint | `1.63.0` | [npm](https://npmx.dev/package/oxlint/v/1.63.0) | | oxlint-tsgolint | `0.22.1` | [npm](https://npmx.dev/package/oxlint-tsgolint/v/0.22.1) | | oxfmt | `0.48.0` | [npm](https://npmx.dev/package/oxfmt/v/0.48.0) | ### New Contributors Welcome to all new contributors! 🎉 @nozomee, @ryohidaka, @leno23 **Full Changelog**: v0.1.21...v0.1.22 --- Merging this PR will trigger the release workflow. --------- Co-authored-by: voidzero-guard[bot] <278573678+voidzero-guard[bot]@users.noreply.github.com> Co-authored-by: MK <fengmk2@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
vp create fateproduced avite.config.tswith twofmt:blocks becausemergeAndRemoveJsonConfig(packages/cli/src/migration/migrator.ts) was the onlymergeJsonConfigcall site missing the "skip if key already present" guard the other call sites use. The Rust merge step always prepends, so when fate's template ships both an inlinefmt:block and a standalone.oxfmtrc.jsonc, both ended up in the output.vite.config.tsfirst; if the key is already declared,fs.unlinkSyncs the redundant standalone file and emits an info log instead of merging. Mirrors the existing skip-existing behavior inapplyToolInitConfigToViteConfig. Also covers the analogouslint:+.oxlintrc.jsoncase.Test plan
packages/cli/src/migration/__tests__/migrator.spec.ts(fmt + lint), confirmed red before the fix and green after —vp test --run packages/cli/src/migration/__tests__/migrator.spec.ts(75/75 passing).packages/cli/snap-tests-global/migration-preserves-existing-fmt-and-lint/modeled on fate's real-world fmt/lint config (experimental options, ignore patterns, overrides); standalone files carry conflicting values so any regression would be obvious in the snapshot.pnpm -F vite-plus snap-test-global migration— all existing migration snaps still pass with no diffs.pnpm -F vite-plus snap-test-global new-create-vite— all create snaps still pass with no diffs.Closes the duplicate-fmt regression reported when running
vp create fate.Note
Medium Risk
Migration logic now conditionally deletes standalone
.oxfmtrc/.oxlintrcfiles based on a new AST-based vite config scan exposed via a native binding; mistakes in the detector could cause config merges to be skipped or applied unexpectedly.Overview
Fixes a migration regression where projects with an existing inline
fmt/lintblock invite.config.*could end up with duplicated blocks after merging standalone.oxfmtrc/.oxlintrcfiles.This adds a Rust AST-based
has_config_keycheck (exported through the NAPI binding ashasConfigKey) and uses it in the CLI migration paths to skip merging when the key already exists, instead deleting the redundant standalone config and optionally logging an info message. New unit and snapshot tests cover the create-fate-style template shape and ensure only a singlefmt/lintblock remains.Reviewed by Cursor Bugbot for commit e0c99be. Configure here.