From 7aba8cb28c6acaef6807502ee86b7fba1eaaa819 Mon Sep 17 00:00:00 2001 From: "MK (fengmk2)" Date: Mon, 14 Sep 2026 15:09:36 +0800 Subject: [PATCH 1/6] fix(migrate): preserve referenced Oxc configs --- .../snapshots.toml | 45 +++++- .../migration_existing_oxc_configs_extends.md | 55 +++++++ ...migration_existing_oxc_configs_imported.md | 95 +++++++++++ ...gration_existing_oxc_configs_inline_fmt.md | 6 +- .../migration_existing_oxc_configs_script.md | 46 ++++++ .../snapshots.toml | 4 +- ...gration_preserves_existing_fmt_and_lint.md | 12 +- .../src/migration/__tests__/migrator.spec.ts | 147 +++++++++++++++++- .../cli/src/migration/migrator/vite-config.ts | 60 +++++++ 9 files changed, 451 insertions(+), 19 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_extends.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_imported.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_script.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots.toml index eb4e7224f9..80b2568fee 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots.toml @@ -22,11 +22,52 @@ steps = [ { argv = ["vp", "fmt", "src/index.ts"], comment = "the existing inline fmt config takes precedence over the standalone config" }, ["vpt", "print-file", "src/index.ts"], { argv = ["vpt", "write-file", "src/index.ts", "export const message = \"preserved\";\n"], snapshot = false }, - { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "remove the leftover standalone config without changing the existing fmt config" }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "keep the standalone config when fmt is already configured" }, ["vpt", "print-file", "vite.config.ts"], - ["vpt", "stat-file", ".oxfmtrc.json", "--assert-not", "file"], + ["vpt", "stat-file", ".oxfmtrc.json", "--assert", "file"], { argv = ["vp", "fmt", "src/index.ts"], comment = "formatting must still use the existing inline options after migration" }, ["vpt", "print-file", "src/index.ts"], { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "retrying the completed migration should be a no-op" }, ["vpt", "print-file", "vite.config.ts"], ] + +[[case]] +name = "migration_existing_oxc_configs_imported" +vp = "global" +steps = [ + { argv = ["vpt", "write-file", "vite.config.ts", "import fmt from './.oxfmtrc.json' with { type: 'json' };\nimport { readFileSync } from 'node:fs';\nconst lint = JSON.parse(readFileSync(new URL('./.oxlintrc.json', import.meta.url), 'utf8'));\nexport default { fmt, lint };\n"], snapshot = false }, + { argv = ["vpt", "write-file", ".oxlintrc.json", "{\"rules\":{\"no-debugger\":\"error\"}}\n"], snapshot = false }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "preserve JSON configs loaded with imports and readFileSync" }, + ["vpt", "print-file", ".oxfmtrc.json"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vp", "fmt", "src/index.ts"], + ["vpt", "print-file", "src/index.ts"], + ["vp", "lint", "src/index.ts"], + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "retry preserves both imported configs" }, + ["vp", "fmt", "--check", "src/index.ts"], + ["vp", "lint", "src/index.ts"], +] + +[[case]] +name = "migration_existing_oxc_configs_script" +vp = "global" +steps = [ + { argv = ["vpt", "json-edit", "package.json", "scripts.fmt", "vp fmt -c .oxfmtrc.json src/index.ts"], snapshot = false }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "preserve the config passed explicitly by a package script" }, + ["vpt", "stat-file", ".oxfmtrc.json", "--assert", "file"], + ["vpt", "print-file", "vite.config.ts"], + ["vp", "run", "fmt"], + ["vpt", "print-file", "src/index.ts"], +] + +[[case]] +name = "migration_existing_oxc_configs_extends" +vp = "global" +steps = [ + { argv = ["vpt", "write-file", ".oxlintrc.json", "{\"extends\":[\"./lint-base.json\"]}\n"], snapshot = false }, + { argv = ["vpt", "write-file", "lint-base.json", "{\"rules\":{\"no-console\":\"error\"}}\n"], snapshot = false }, + { argv = ["vpt", "write-file", "src/index.ts", "console.log('hello');\n"], snapshot = false }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "preserve JSON inheritance instead of copying file paths into inline lint.extends" }, + ["vpt", "print-file", ".oxlintrc.json"], + { argv = ["vp", "lint", "-c", ".oxlintrc.json", "src/index.ts"], comment = "the retained JSON config must still load the inherited no-console rule", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_extends.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_extends.md new file mode 100644 index 0000000000..8af8994634 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_extends.md @@ -0,0 +1,55 @@ +# migration_existing_oxc_configs_extends + +## `vpt write-file .oxlintrc.json '{"extends":["./lint-base.json"]} +'` + + +## `vpt write-file lint-base.json '{"rules":{"no-console":"error"}} +'` + + +## `vpt write-file src/index.ts 'console.log('\''hello'\''); +'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +preserve JSON inheritance instead of copying file paths into inline lint.extends + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• 1 config update applied +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{"extends":["./lint-base.json"]} +``` + +## `vp lint -c .oxlintrc.json src/index.ts` + +the retained JSON config must still load the inherited no-console rule + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello'); + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_imported.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_imported.md new file mode 100644 index 0000000000..80af79fd40 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_imported.md @@ -0,0 +1,95 @@ +# migration_existing_oxc_configs_imported + +## `vpt write-file vite.config.ts 'import fmt from '\''./.oxfmtrc.json'\'' with { type: '\''json'\'' }; +import { readFileSync } from '\''node:fs'\''; +const lint = JSON.parse(readFileSync(new URL('\''./.oxlintrc.json'\'', import.meta.url), '\''utf8'\'')); +export default { fmt, lint }; +'` + + +## `vpt write-file .oxlintrc.json '{"rules":{"no-debugger":"error"}} +'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +preserve JSON configs loaded with imports and readFileSync + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vpt print-file .oxlintrc.json` + +``` +{"rules":{"no-debugger":"error"}} +``` + +## `vp fmt src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Finished in on 1 files using threads. +``` + +## `vpt print-file src/index.ts` + +``` +export const message = 'preserved' +``` + +## `vp lint src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +retry preserves both imported configs + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp fmt --check src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp lint src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_inline_fmt.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_inline_fmt.md index 5e5db5b33d..88b5781e23 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_inline_fmt.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_inline_fmt.md @@ -26,7 +26,7 @@ export const message = "preserved" ## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` -remove the leftover standalone config without changing the existing fmt config +keep the standalone config when fmt is already configured ``` VITE+ - The Unified Toolchain for the Web @@ -45,10 +45,10 @@ VITE+ - The Unified Toolchain for the Web export default { fmt: { singleQuote: false, semi: false } }; ``` -## `vpt stat-file .oxfmtrc.json --assert-not file` +## `vpt stat-file .oxfmtrc.json --assert file` ``` -.oxfmtrc.json: missing +.oxfmtrc.json: file ``` ## `vp fmt src/index.ts` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_script.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_script.md new file mode 100644 index 0000000000..716dae4e09 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_existing_oxc_configs/snapshots/migration_existing_oxc_configs_script.md @@ -0,0 +1,46 @@ +# migration_existing_oxc_configs_script + +## `vpt json-edit package.json scripts.fmt 'vp fmt -c .oxfmtrc.json src/index.ts'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +preserve the config passed explicitly by a package script + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt stat-file .oxfmtrc.json --assert file` + +``` +.oxfmtrc.json: file +``` + +## `vpt print-file vite.config.ts` + +``` +export default {}; +``` + +## `vp run fmt` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt -c .oxfmtrc.json src/index.ts ⊘ cache disabled +Finished in on 1 files using threads. +``` + +## `vpt print-file src/index.ts` + +``` +export const message = 'preserved' +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots.toml index 6a6506ca09..0e82facdb9 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots.toml @@ -4,6 +4,6 @@ vp = "global" steps = [ { argv = ["vp", "migrate", "--no-interactive"], comment = "should NOT duplicate fmt/lint blocks already in vite.config.ts (regression for vp create fate)", continue-on-failure = true }, { argv = ["vpt", "print-file", "vite.config.ts"], comment = "exactly one fmt: and one lint: block, preserving template values", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".oxfmtrc.jsonc", "--assert-not", "file"], comment = "redundant standalone file removed", continue-on-failure = true }, - { argv = ["vpt", "stat-file", ".oxlintrc.json", "--assert-not", "file"], comment = "redundant standalone file removed", continue-on-failure = true }, + { argv = ["vpt", "stat-file", ".oxfmtrc.jsonc", "--assert", "file"], comment = "standalone config preserved", continue-on-failure = true }, + { argv = ["vpt", "stat-file", ".oxlintrc.json", "--assert", "file"], comment = "standalone config preserved", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots/migration_preserves_existing_fmt_and_lint.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots/migration_preserves_existing_fmt_and_lint.md index 2061ff6df2..3d358547c4 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots/migration_preserves_existing_fmt_and_lint.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_preserves_existing_fmt_and_lint/snapshots/migration_preserves_existing_fmt_and_lint.md @@ -70,18 +70,18 @@ export default defineConfig({ }); ``` -## `vpt stat-file .oxfmtrc.jsonc --assert-not file` +## `vpt stat-file .oxfmtrc.jsonc --assert file` -redundant standalone file removed +standalone config preserved ``` -.oxfmtrc.jsonc: missing +.oxfmtrc.jsonc: file ``` -## `vpt stat-file .oxlintrc.json --assert-not file` +## `vpt stat-file .oxlintrc.json --assert file` -redundant standalone file removed +standalone config preserved ``` -.oxlintrc.json: missing +.oxlintrc.json: file ``` diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index 026874eb0a..123b2eb0d5 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -8880,7 +8880,7 @@ describe('existing Vite+ core migration finalization', () => { expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); }); - it('preserves existing inline config when removing a redundant standalone config', () => { + it('preserves standalone configs when the tool already has an inline config', () => { fs.writeFileSync( path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'test', devDependencies: { 'vite-plus': 'latest' } }), @@ -8890,12 +8890,99 @@ describe('existing Vite+ core migration finalization', () => { fs.writeFileSync(path.join(tmpDir, '.oxfmtrc.json'), '{"singleQuote":true}\n'); const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.npm); - expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(true); + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toBe(config); - expect(fs.existsSync(path.join(tmpDir, '.oxfmtrc.json'))).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, '.oxfmtrc.json'), 'utf8')).toBe( + '{"singleQuote":true}\n', + ); expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); }); + it.each([ + `import fmt from './.oxfmtrc.json' with { type: 'json' }; +import lint from './.oxlintrc.json' with { type: 'json' }; +export default { fmt, lint }; +`, + `import { readFileSync } from 'node:fs'; +const fmt = JSON.parse(readFileSync(new URL('./.oxfmtrc.json', import.meta.url), 'utf8')); +const lint = JSON.parse(readFileSync(new URL('./.oxlintrc.json', import.meta.url), 'utf8')); +export default { fmt, lint }; +`, + `import config from './.oxfmtrc.json' with { type: 'json' }; +export default { define: { config: JSON.stringify(config) } }; +`, + ])('preserves config files loaded by vite.config.ts (%#)', (config) => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'test', devDependencies: { 'vite-plus': 'latest' } }), + ); + fs.writeFileSync(path.join(tmpDir, 'vite.config.ts'), config); + fs.writeFileSync(path.join(tmpDir, '.oxfmtrc.json'), '{"singleQuote":true}\n'); + if (config.includes('.oxlintrc.json')) { + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), '{"rules":{"no-console":"error"}}\n'); + } + const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.npm); + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toBe(config); + expect(fs.readFileSync(path.join(tmpDir, '.oxfmtrc.json'), 'utf8')).toBe( + '{"singleQuote":true}\n', + ); + if (config.includes('.oxlintrc.json')) { + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe( + '{"rules":{"no-console":"error"}}\n', + ); + } + } + }); + + it.each(['.', 'packages/app'])( + 'preserves configs used by scripts in %s without an inline tool config', + (packagePath) => { + const appDir = path.join(tmpDir, packagePath); + fs.mkdirSync(appDir, { recursive: true }); + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'root', devDependencies: { 'vite-plus': 'latest' } }), + ); + const prefix = packagePath === '.' ? './' : '../../'; + fs.writeFileSync( + path.join(appDir, 'package.json'), + JSON.stringify({ + name: 'app', + devDependencies: { 'vite-plus': 'latest' }, + scripts: { + fmt: `vp fmt -c ${prefix}.oxfmtrc.json src --write`, + lint: `vp lint --config=${prefix}.oxlintrc.json src`, + }, + }), + ); + fs.writeFileSync(path.join(tmpDir, 'vite.config.ts'), 'export default {};\n'); + fs.writeFileSync(path.join(tmpDir, '.oxfmtrc.json'), '{"singleQuote":true}\n'); + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), '{"rules":{"no-console":"error"}}\n'); + const workspaceInfo = { + ...makeWorkspaceInfo(tmpDir, PackageManager.pnpm), + packages: packagePath === '.' ? [] : [{ name: 'app', path: packagePath }], + }; + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe( + false, + ); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toBe( + 'export default {};\n', + ); + expect(fs.readFileSync(path.join(tmpDir, '.oxfmtrc.json'), 'utf8')).toBe( + '{"singleQuote":true}\n', + ); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe( + '{"rules":{"no-console":"error"}}\n', + ); + } + }, + ); + it('keeps an unmergeable config and reports the incomplete migration', () => { fs.writeFileSync( path.join(tmpDir, 'package.json'), @@ -8918,6 +9005,31 @@ describe('existing Vite+ core migration finalization', () => { expect(report.warnings.some((warning) => warning.includes('Failed to merge'))).toBe(true); }); + it('preserves JSON lint inheritance across workspace packages', () => { + const appDir = path.join(tmpDir, 'packages/app'); + fs.mkdirSync(appDir, { recursive: true }); + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'root', devDependencies: { 'vite-plus': 'latest' } }), + ); + fs.writeFileSync(path.join(appDir, 'package.json'), JSON.stringify({ name: 'app' })); + const rootConfig = '{"rules":{"no-console":"error"}}\n'; + const appConfig = '{"extends":["../../.oxlintrc.json"],"rules":{"no-debugger":"error"}}\n'; + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), rootConfig); + fs.writeFileSync(path.join(appDir, '.oxlintrc.json'), appConfig); + const workspaceInfo = { + ...makeWorkspaceInfo(tmpDir, PackageManager.pnpm), + packages: [{ name: 'app', path: 'packages/app' }], + }; + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe(rootConfig); + expect(fs.readFileSync(path.join(appDir, '.oxlintrc.json'), 'utf8')).toBe(appConfig); + expect(fs.existsSync(path.join(appDir, 'vite.config.ts'))).toBe(false); + } + }); + it('detects package-level legacy signals in workspaces', () => { const appDir = path.join(tmpDir, 'packages', 'app'); fs.mkdirSync(appDir, { recursive: true }); @@ -9099,11 +9211,34 @@ export default defineConfig({ const viteConfig = fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8'); expect(viteConfig.match(/\bfmt\s*:/g)?.length).toBe(1); - // Template-authored value wins (singleQuote: true) — standalone config dropped. + // Template-authored value wins (singleQuote: true). expect(viteConfig).toContain('singleQuote: true'); expect(viteConfig).not.toContain('singleQuote: false'); - // Redundant standalone file removed. - expect(fs.existsSync(path.join(tmpDir, '.oxfmtrc.jsonc'))).toBe(false); + // The standalone file can still be used outside this config. + expect(fs.existsSync(path.join(tmpDir, '.oxfmtrc.jsonc'))).toBe(true); + }); + + it('does not inject defaults over configs retained for explicit script arguments', () => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ + name: 'test', + devDependencies: { vite: '^7.0.0' }, + scripts: { fmt: 'oxfmt -c .oxfmtrc.json src', lint: 'oxlint -c .oxlintrc.json src' }, + }), + ); + fs.writeFileSync(path.join(tmpDir, '.oxfmtrc.json'), '{"singleQuote":true}\n'); + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), '{"rules":{"no-console":"error"}}\n'); + + rewriteStandaloneProject(tmpDir, makeWorkspaceInfo(tmpDir, PackageManager.pnpm), true, true); + + expect(fs.existsSync(path.join(tmpDir, 'vite.config.ts'))).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, '.oxfmtrc.json'), 'utf8')).toBe( + '{"singleQuote":true}\n', + ); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe( + '{"rules":{"no-console":"error"}}\n', + ); }); }); diff --git a/packages/cli/src/migration/migrator/vite-config.ts b/packages/cli/src/migration/migrator/vite-config.ts index 63253688cb..05525fabae 100644 --- a/packages/cli/src/migration/migrator/vite-config.ts +++ b/packages/cli/src/migration/migrator/vite-config.ts @@ -254,6 +254,59 @@ export function mergeViteConfigFiles( if (!configs.oxfmtConfig && !configs.oxlintConfig) { return; } + const fullViteConfigPath = configs.viteConfig && path.join(projectPath, configs.viteConfig); + const viteConfigContent = fullViteConfigPath ? fs.readFileSync(fullViteConfigPath, 'utf8') : ''; + const rootDir = workspaceRoot ?? projectPath; + const projectPaths = new Set([ + rootDir, + projectPath, + ...(packages ?? []).map((pkg) => path.join(rootDir, pkg.path)), + ]); + const scripts: string[] = []; + const preservedLintConfigs = new Set(); + for (const projectDir of projectPaths) { + const packageJsonPath = path.join(projectDir, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + const pkg = readJsonFile(packageJsonPath) as { scripts?: Record }; + scripts.push(...Object.values(pkg.scripts ?? {})); + } + const lintConfig = detectConfigs(projectDir).oxlintConfig; + if (lintConfig) { + const lintConfigPath = path.join(projectDir, lintConfig); + const json = readJsonFile(lintConfigPath, true) as { extends?: unknown }; + if (Array.isArray(json.extends) && json.extends.length > 0) { + // JSON extends uses file paths; inline lint.extends requires config objects. + // Keep both the extending config and any workspace configs it loads. + preservedLintConfigs.add(lintConfigPath); + for (const extendedConfig of json.extends) { + if (typeof extendedConfig === 'string') { + preservedLintConfigs.add(path.resolve(projectDir, extendedConfig)); + } + } + } + } + } + const canMergeConfig = (filename: string, configKey: string): boolean => { + // An existing tool config can load the JSON file indirectly. Direct imports, + // readFileSync calls, and package scripts can also use it without a tool key. + // Keep these files intact, including their lint options. A filename match is + // deliberately conservative because script paths can depend on shell state. + return ( + (!fullViteConfigPath || !hasConfigKey(fullViteConfigPath, configKey)) && + !viteConfigContent.includes(filename) && + !preservedLintConfigs.has(path.join(projectPath, filename)) && + !scripts.some((script) => script.includes(filename)) + ); + }; + if (configs.oxlintConfig && !canMergeConfig(configs.oxlintConfig, 'lint')) { + configs.oxlintConfig = undefined; + } + if (configs.oxfmtConfig && !canMergeConfig(configs.oxfmtConfig, 'fmt')) { + configs.oxfmtConfig = undefined; + } + if (!configs.oxlintConfig && !configs.oxfmtConfig) { + return; + } const viteConfig = ensureViteConfig(projectPath, configs, silent, report); if (configs.oxlintConfig) { // Inject options.typeAware and options.typeCheck defaults before merging @@ -378,6 +431,13 @@ function injectConfigDefaults( report?: MigrationReport, ): void { const configs = detectConfigs(projectPath); + // A config retained for imports, scripts, or extends must keep taking effect. + if ( + (configKey === 'lint' && configs.oxlintConfig) || + (configKey === 'fmt' && configs.oxfmtConfig) + ) { + return; + } if (configs.viteConfig && hasConfigKey(path.join(projectPath, configs.viteConfig), configKey)) { return; } From 632ee21c3e2ac88ed9c14a400009086cd9f412e3 Mon Sep 17 00:00:00 2001 From: "MK (fengmk2)" Date: Mon, 14 Sep 2026 15:18:49 +0800 Subject: [PATCH 2/6] test(create): preserve referenced template configs --- .../snapshots.toml | 4 ++-- .../create_monorepo_local_template_shorthand.md | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots.toml index defdfb38b6..12703b557e 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots.toml @@ -4,6 +4,6 @@ vp = "local" steps = [ { argv = ["vp", "create", "starter", "--no-interactive", "--no-agent", "--", "--directory", "my-app"], comment = "run the local create.templates entry; generated pkg declares fmt/lint via shorthand", continue-on-failure = true }, { argv = ["vpt", "print-file", "packages/my-app/vite.config.ts"], comment = "fmt/lint stay shorthand only, no injected duplicate inline fmt:/lint: blocks (#1836)", continue-on-failure = true }, - { argv = ["vpt", "stat-file", "packages/my-app/.oxlintrc.json", "--assert-not", "file"], comment = "standalone lint config merge-skipped and removed", continue-on-failure = true }, - { argv = ["vpt", "stat-file", "packages/my-app/.oxfmtrc.json", "--assert-not", "file"], comment = "standalone fmt config merge-skipped and removed", continue-on-failure = true }, + { argv = ["vpt", "stat-file", "packages/my-app/.oxlintrc.json", "--assert", "file"], comment = "standalone lint config preserved", continue-on-failure = true }, + { argv = ["vpt", "stat-file", "packages/my-app/.oxfmtrc.json", "--assert", "file"], comment = "standalone fmt config preserved", continue-on-failure = true }, ] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots/create_monorepo_local_template_shorthand.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots/create_monorepo_local_template_shorthand.md index ba84ceaa1b..88e813c99e 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots/create_monorepo_local_template_shorthand.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/create_monorepo_local_template_shorthand/snapshots/create_monorepo_local_template_shorthand.md @@ -13,10 +13,6 @@ cloned starter-template to my-app Monorepo integration... -lint config already present in packages/my-app/vite.config.ts — removed redundant packages/my-app/.oxlintrc.json - -fmt config already present in packages/my-app/vite.config.ts — removed redundant packages/my-app/.oxfmtrc.json - Formatting code... Code formatted @@ -44,18 +40,18 @@ export default defineConfig(({ mode }) => { }); ``` -## `vpt stat-file packages/my-app/.oxlintrc.json --assert-not file` +## `vpt stat-file packages/my-app/.oxlintrc.json --assert file` -standalone lint config merge-skipped and removed +standalone lint config preserved ``` -packages/my-app/.oxlintrc.json: missing +packages/my-app/.oxlintrc.json: file ``` -## `vpt stat-file packages/my-app/.oxfmtrc.json --assert-not file` +## `vpt stat-file packages/my-app/.oxfmtrc.json --assert file` -standalone fmt config merge-skipped and removed +standalone fmt config preserved ``` -packages/my-app/.oxfmtrc.json: missing +packages/my-app/.oxfmtrc.json: file ``` From 72e02ab57139c165959a9c0412b016afbe6584db Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 14 Sep 2026 15:56:29 +0800 Subject: [PATCH 3/6] fix(migrate): preserve workspace config reference chains --- .../.oxfmtrc.json | 3 + .../.oxlintrc.json | 5 + .../package.json | 9 ++ .../packages/app/package.json | 5 + .../packages/app/src/index.ts | 1 + .../pnpm-workspace.yaml | 2 + .../snapshots.toml | 30 ++++++ ...eferenced_workspace_oxc_configs_extends.md | 83 ++++++++++++++++ ...ferenced_workspace_oxc_configs_imported.md | 94 +++++++++++++++++++ .../src/migration/__tests__/migrator.spec.ts | 78 +++++++++++++++ .../cli/src/migration/migrator/vite-config.ts | 49 ++++++---- 11 files changed, 341 insertions(+), 18 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxfmtrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/pnpm-workspace.yaml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_extends.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_imported.md diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxfmtrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxfmtrc.json new file mode 100644 index 0000000000..544138be45 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxfmtrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxlintrc.json new file mode 100644 index 0000000000..2ff50f91ec --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": "error" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/package.json new file mode 100644 index 0000000000..6f52fa50d1 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/package.json @@ -0,0 +1,9 @@ +{ + "name": "migration-referenced-workspace-oxc-configs", + "private": true, + "type": "module", + "packageManager": "pnpm@12.3.4", + "devDependencies": { + "vite-plus": "latest" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/package.json new file mode 100644 index 0000000000..33f2660adc --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/package.json @@ -0,0 +1,5 @@ +{ + "name": "app", + "private": true, + "type": "module" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/src/index.ts new file mode 100644 index 0000000000..38b0fa8a2f --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/packages/app/src/index.ts @@ -0,0 +1 @@ +export const message = "preserved"; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/pnpm-workspace.yaml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/pnpm-workspace.yaml new file mode 100644 index 0000000000..924b55f42e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots.toml new file mode 100644 index 0000000000..17edaf868d --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots.toml @@ -0,0 +1,30 @@ +[[case]] +name = "migration_referenced_workspace_oxc_configs_imported" +vp = "global" +steps = [ + { argv = ["vpt", "write-file", "packages/app/vite.config.ts", "import fmt from '../../.oxfmtrc.json' with { type: 'json' };\nimport { readFileSync } from 'node:fs';\nconst lint = JSON.parse(readFileSync(new URL('../../.oxlintrc.json', import.meta.url), 'utf8'));\nexport default { fmt, lint };\n"], snapshot = false }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "preserve root configs loaded by a workspace Vite config" }, + ["vpt", "print-file", ".oxfmtrc.json"], + ["vpt", "print-file", ".oxlintrc.json"], + { argv = ["vp", "fmt", "src/index.ts"], cwd = "packages/app" }, + ["vpt", "print-file", "packages/app/src/index.ts"], + { argv = ["vp", "lint", "src/index.ts"], cwd = "packages/app" }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "repeated migration keeps the workspace imports usable" }, + { argv = ["vp", "fmt", "--check", "src/index.ts"], cwd = "packages/app" }, + { argv = ["vp", "lint", "src/index.ts"], cwd = "packages/app" }, +] + +[[case]] +name = "migration_referenced_workspace_oxc_configs_extends" +vp = "global" +steps = [ + { argv = ["vpt", "write-file", "packages/app/.oxlintrc.json", "{\"extends\":[\"./config/lint-base.jsonc\"]}\n"], snapshot = false }, + { argv = ["vpt", "write-file", "packages/app/config/lint-base.jsonc", "// Shared lint rules\n{\"extends\":[\"../../../.oxlintrc.json\"]}\n"], snapshot = false }, + { argv = ["vpt", "write-file", "packages/app/src/index.ts", "console.log('hello');\n"], snapshot = false }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "preserve every config in a transitive JSON inheritance chain" }, + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", "packages/app/.oxlintrc.json"], + ["vpt", "print-file", "packages/app/config/lint-base.jsonc"], + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "repeated migration keeps inherited rules usable" }, + { argv = ["vp", "lint", "-c", ".oxlintrc.json", "src/index.ts"], cwd = "packages/app", comment = "the inherited no-console rule must still report the console call", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_extends.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_extends.md new file mode 100644 index 0000000000..736142b276 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_extends.md @@ -0,0 +1,83 @@ +# migration_referenced_workspace_oxc_configs_extends + +## `vpt write-file packages/app/.oxlintrc.json '{"extends":["./config/lint-base.jsonc"]} +'` + + +## `vpt write-file packages/app/config/lint-base.jsonc '// Shared lint rules +{"extends":["../../../.oxlintrc.json"]} +'` + + +## `vpt write-file packages/app/src/index.ts 'console.log('\''hello'\''); +'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +preserve every config in a transitive JSON inheritance chain + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• 2 config updates applied +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + } +} +``` + +## `vpt print-file packages/app/.oxlintrc.json` + +``` +{"extends":["./config/lint-base.jsonc"]} +``` + +## `vpt print-file packages/app/config/lint-base.jsonc` + +``` +// Shared lint rules +{"extends":["../../../.oxlintrc.json"]} +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +repeated migration keeps inherited rules usable + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `cd packages/app && vp lint -c .oxlintrc.json src/index.ts` + +the inherited no-console rule must still report the console call + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello'); + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_imported.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_imported.md new file mode 100644 index 0000000000..f0c634544e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_referenced_workspace_oxc_configs/snapshots/migration_referenced_workspace_oxc_configs_imported.md @@ -0,0 +1,94 @@ +# migration_referenced_workspace_oxc_configs_imported + +## `vpt write-file packages/app/vite.config.ts 'import fmt from '\''../../.oxfmtrc.json'\'' with { type: '\''json'\'' }; +import { readFileSync } from '\''node:fs'\''; +const lint = JSON.parse(readFileSync(new URL('\''../../.oxlintrc.json'\'', import.meta.url), '\''utf8'\'')); +export default { fmt, lint }; +'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +preserve root configs loaded by a workspace Vite config + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true +} +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + } +} +``` + +## `cd packages/app && vp fmt src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Finished in on 1 files using threads. +``` + +## `vpt print-file packages/app/src/index.ts` + +``` +export const message = 'preserved'; +``` + +## `cd packages/app && vp lint src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +repeated migration keeps the workspace imports usable + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `cd packages/app && vp fmt --check src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `cd packages/app && vp lint src/index.ts` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index 123b2eb0d5..3bd7cad13a 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -8937,6 +8937,47 @@ export default { define: { config: JSON.stringify(config) } }; } }); + it.each([ + ['.', 'packages/app'], + ['packages/shared', 'packages/app'], + ['packages/app', '.'], + ])('preserves configs in %s loaded by the Vite config in %s', (configDir, consumerDir) => { + const workspaceInfo = { + ...makeWorkspaceInfo(tmpDir, PackageManager.pnpm), + packages: [ + { name: 'app', path: 'packages/app' }, + { name: 'shared', path: 'packages/shared' }, + ], + }; + for (const dir of ['.', ...workspaceInfo.packages.map((pkg) => pkg.path)]) { + fs.mkdirSync(path.join(tmpDir, dir), { recursive: true }); + fs.writeFileSync(path.join(tmpDir, dir, 'package.json'), JSON.stringify({ name: dir })); + } + const configPath = path.join(tmpDir, configDir); + const consumerPath = path.join(tmpDir, consumerDir); + const relativePath = path.relative(consumerPath, configPath).replaceAll('\\', '/'); + const config = `import fmt from './${relativePath}/.oxfmtrc.json' with { type: 'json' }; +import { readFileSync } from 'node:fs'; +const lint = JSON.parse(readFileSync(new URL('./${relativePath}/.oxlintrc.json', import.meta.url), 'utf8')); +export default { fmt, lint }; +`; + fs.writeFileSync(path.join(consumerPath, 'vite.config.mts'), config); + fs.writeFileSync(path.join(configPath, '.oxfmtrc.json'), '{"singleQuote":true}\n'); + fs.writeFileSync(path.join(configPath, '.oxlintrc.json'), '{"rules":{"no-console":"error"}}\n'); + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); + expect(fs.readFileSync(path.join(consumerPath, 'vite.config.mts'), 'utf8')).toBe(config); + expect(fs.readFileSync(path.join(configPath, '.oxfmtrc.json'), 'utf8')).toBe( + '{"singleQuote":true}\n', + ); + expect(fs.readFileSync(path.join(configPath, '.oxlintrc.json'), 'utf8')).toBe( + '{"rules":{"no-console":"error"}}\n', + ); + expect(fs.existsSync(path.join(configPath, 'vite.config.ts'))).toBe(false); + } + }); + it.each(['.', 'packages/app'])( 'preserves configs used by scripts in %s without an inline tool config', (packagePath) => { @@ -9030,6 +9071,43 @@ export default { define: { config: JSON.stringify(config) } }; } }); + it.each(['leaf', 'cycle', 'missing'])( + 'preserves transitive lint inheritance through custom JSON files with a %s target', + (target) => { + const appDir = path.join(tmpDir, 'packages/app'); + fs.mkdirSync(path.join(appDir, 'config'), { recursive: true }); + fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'root' })); + fs.writeFileSync(path.join(appDir, 'package.json'), JSON.stringify({ name: 'app' })); + const rootConfig = '{"rules":{"no-console":"error"}}\n'; + const appConfig = '{"extends":["./config/lint-base.jsonc"]}\n'; + const targets = ['../../../.oxlintrc.json']; + if (target !== 'leaf') { + targets.push(target === 'cycle' ? '../.oxlintrc.json' : './missing.json'); + } + const baseConfig = `// Shared lint rules\n${JSON.stringify({ extends: targets })}\n`; + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), rootConfig); + fs.writeFileSync(path.join(appDir, '.oxlintrc.json'), appConfig); + fs.writeFileSync(path.join(appDir, 'config/lint-base.jsonc'), baseConfig); + const workspaceInfo = { + ...makeWorkspaceInfo(tmpDir, PackageManager.pnpm), + packages: [{ name: 'app', path: 'packages/app' }], + }; + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe( + false, + ); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe(rootConfig); + expect(fs.readFileSync(path.join(appDir, '.oxlintrc.json'), 'utf8')).toBe(appConfig); + expect(fs.readFileSync(path.join(appDir, 'config/lint-base.jsonc'), 'utf8')).toBe( + baseConfig, + ); + expect(fs.existsSync(path.join(tmpDir, 'vite.config.ts'))).toBe(false); + expect(fs.existsSync(path.join(appDir, 'vite.config.ts'))).toBe(false); + } + }, + ); + it('detects package-level legacy signals in workspaces', () => { const appDir = path.join(tmpDir, 'packages', 'app'); fs.mkdirSync(appDir, { recursive: true }); diff --git a/packages/cli/src/migration/migrator/vite-config.ts b/packages/cli/src/migration/migrator/vite-config.ts index 05525fabae..cf8f4b92b7 100644 --- a/packages/cli/src/migration/migrator/vite-config.ts +++ b/packages/cli/src/migration/migrator/vite-config.ts @@ -255,33 +255,47 @@ export function mergeViteConfigFiles( return; } const fullViteConfigPath = configs.viteConfig && path.join(projectPath, configs.viteConfig); - const viteConfigContent = fullViteConfigPath ? fs.readFileSync(fullViteConfigPath, 'utf8') : ''; const rootDir = workspaceRoot ?? projectPath; const projectPaths = new Set([ rootDir, projectPath, ...(packages ?? []).map((pkg) => path.join(rootDir, pkg.path)), ]); - const scripts: string[] = []; + const configReferences: string[] = []; + const lintConfigPaths = new Set(); const preservedLintConfigs = new Set(); for (const projectDir of projectPaths) { const packageJsonPath = path.join(projectDir, 'package.json'); if (fs.existsSync(packageJsonPath)) { const pkg = readJsonFile(packageJsonPath) as { scripts?: Record }; - scripts.push(...Object.values(pkg.scripts ?? {})); + configReferences.push(...Object.values(pkg.scripts ?? {})); } - const lintConfig = detectConfigs(projectDir).oxlintConfig; - if (lintConfig) { - const lintConfigPath = path.join(projectDir, lintConfig); - const json = readJsonFile(lintConfigPath, true) as { extends?: unknown }; - if (Array.isArray(json.extends) && json.extends.length > 0) { - // JSON extends uses file paths; inline lint.extends requires config objects. - // Keep both the extending config and any workspace configs it loads. - preservedLintConfigs.add(lintConfigPath); - for (const extendedConfig of json.extends) { - if (typeof extendedConfig === 'string') { - preservedLintConfigs.add(path.resolve(projectDir, extendedConfig)); - } + const projectConfigs = detectConfigs(projectDir); + if (projectConfigs.viteConfig) { + configReferences.push( + fs.readFileSync(path.join(projectDir, projectConfigs.viteConfig), 'utf8'), + ); + } + if (projectConfigs.oxlintConfig) { + lintConfigPaths.add(path.resolve(projectDir, projectConfigs.oxlintConfig)); + } + } + // Set iteration also visits newly discovered targets, once each, including + // custom filenames. This follows transitive extends without looping on cycles. + for (const lintConfigPath of lintConfigPaths) { + if (!fs.existsSync(lintConfigPath)) { + continue; + } + const json = readJsonFile(lintConfigPath, true) as { extends?: unknown } | null | undefined; + if (Array.isArray(json?.extends) && json.extends.length > 0) { + // JSON extends uses file paths; inline lint.extends requires config objects. + // Keep both the extending config and every config in its inheritance chain. + preservedLintConfigs.add(lintConfigPath); + for (const extendedConfig of json.extends) { + if (typeof extendedConfig === 'string') { + const extendedConfigPath = path.resolve(path.dirname(lintConfigPath), extendedConfig); + preservedLintConfigs.add(extendedConfigPath); + lintConfigPaths.add(extendedConfigPath); } } } @@ -293,9 +307,8 @@ export function mergeViteConfigFiles( // deliberately conservative because script paths can depend on shell state. return ( (!fullViteConfigPath || !hasConfigKey(fullViteConfigPath, configKey)) && - !viteConfigContent.includes(filename) && - !preservedLintConfigs.has(path.join(projectPath, filename)) && - !scripts.some((script) => script.includes(filename)) + !preservedLintConfigs.has(path.resolve(projectPath, filename)) && + !configReferences.some((content) => content.includes(filename)) ); }; if (configs.oxlintConfig && !canMergeConfig(configs.oxlintConfig, 'lint')) { From 5fdab2fc8f62efd2ac0dd9f4d2afa8d8d4da733a Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 14 Sep 2026 17:27:24 +0800 Subject: [PATCH 4/6] fix(cli): honor explicit lint and format configs --- .../.oxfmtrc.json | 4 + .../.oxlintrc.json | 9 + .../package.json | 12 ++ .../packages/app/package.json | 5 + .../packages/app/src/index.ts | 1 + .../pnpm-workspace.yaml | 2 + .../snapshots.toml | 16 ++ .../migration_oxc_ecosystem_delta_comic.md | 115 ++++++++++ .../vite.config.ts | 11 + .../.oxfmtrc.json | 4 + .../.oxlintrc.json | 9 + .../package.json | 14 ++ .../snapshots.toml | 17 ++ .../migration_oxc_ecosystem_freecut.md | 122 +++++++++++ .../src/index.ts | 1 + .../vite.config.ts | 16 ++ .../.oxfmtrc.json | 4 + .../.oxlintrc.json | 9 + .../package.json | 18 ++ .../packages/common/package.json | 9 + .../packages/common/src/index.ts | 1 + .../playground/.oxlintrc.json | 11 + .../playground/package.json | 5 + .../playground/src/index.ts | 1 + .../snapshots.toml | 45 ++++ .../migration_oxc_ecosystem_pdfme.md | 202 ++++++++++++++++++ ...xc_ecosystem_pdfme_inline_config.global.md | 158 ++++++++++++++ ...oxc_ecosystem_pdfme_inline_config.local.md | 144 +++++++++++++ .../vite.config.mts | 7 + .../.oxlintrc.json | 5 + .../apps/custom-domain/.oxlintrc.json | 6 + .../apps/custom-domain/package.json | 5 + .../apps/custom-domain/src/index.ts | 1 + .../apps/sigle/.oxlintrc.json | 6 + .../apps/sigle/package.json | 5 + .../apps/sigle/src/index.ts | 1 + .../apps/sigle/vite.config.ts | 7 + .../package.json | 9 + .../pnpm-workspace.yaml | 2 + .../snapshots.toml | 21 ++ .../migration_oxc_ecosystem_sigle.md | 162 ++++++++++++++ .../vite.config.ts | 12 ++ .../oxc_explicit_config/custom-fmt.json | 3 + .../oxc_explicit_config/custom-lint.json | 5 + .../fixtures/oxc_explicit_config/index.js | 1 + .../fixtures/oxc_explicit_config/package.json | 9 + .../oxc_explicit_config/snapshots.toml | 31 +++ .../snapshots/fmt_explicit_config.global.md | 97 +++++++++ .../snapshots/fmt_explicit_config.local.md | 83 +++++++ .../snapshots/lint_explicit_config.global.md | 93 ++++++++ .../snapshots/lint_explicit_config.local.md | 78 +++++++ .../oxc_explicit_config/vite.config.ts | 10 + packages/cli/binding/src/cli/resolver.rs | 164 +++++++++++--- 53 files changed, 1757 insertions(+), 31 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxfmtrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/pnpm-workspace.yaml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots/migration_oxc_ecosystem_delta_comic.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/vite.config.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxfmtrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots/migration_oxc_ecosystem_freecut.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/vite.config.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxfmtrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.global.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.local.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/vite.config.mts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/src/index.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/vite.config.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/pnpm-workspace.yaml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots/migration_oxc_ecosystem_sigle.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/vite.config.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-fmt.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-lint.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/index.js create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.global.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.local.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.global.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.local.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/vite.config.ts diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxfmtrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxfmtrc.json new file mode 100644 index 0000000000..fd496a820e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxlintrc.json new file mode 100644 index 0000000000..3a438765d5 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/package.json new file mode 100644 index 0000000000..75aeb4229e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/package.json @@ -0,0 +1,12 @@ +{ + "name": "migration-oxc-ecosystem-delta-comic", + "private": true, + "type": "module", + "packageManager": "pnpm@12.3.4", + "devDependencies": { + "vite-plus": "latest" + }, + "scripts": { + "check": "vp check packages/app/src" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/package.json new file mode 100644 index 0000000000..33f2660adc --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/package.json @@ -0,0 +1,5 @@ +{ + "name": "app", + "private": true, + "type": "module" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/src/index.ts new file mode 100644 index 0000000000..38b0fa8a2f --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/packages/app/src/index.ts @@ -0,0 +1 @@ +export const message = "preserved"; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/pnpm-workspace.yaml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/pnpm-workspace.yaml new file mode 100644 index 0000000000..924b55f42e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots.toml new file mode 100644 index 0000000000..051946a49b --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots.toml @@ -0,0 +1,16 @@ +[[case]] +name = "migration_oxc_ecosystem_delta_comic" +vp = "global" +comment = "Reproduces the typed JSON imports in https://github.com/vite-plus-ecosystem-ci/delta-comic/pull/9." +steps = [ + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", ".oxfmtrc.json"], + ["vp", "fmt", "packages/app/src"], + ["vpt", "print-file", "packages/app/src/index.ts"], + ["vp", "run", "check"], + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "repeat migration without invalidating either JSON import" }, + ["vp", "run", "check"], + { argv = ["vpt", "write-file", "packages/app/src/index.ts", "console.log('hello')\n"], snapshot = false }, + { argv = ["vp", "run", "check"], comment = "vp check must still apply the imported lint rule", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots/migration_oxc_ecosystem_delta_comic.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots/migration_oxc_ecosystem_delta_comic.md new file mode 100644 index 0000000000..9cf7709794 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/snapshots/migration_oxc_ecosystem_delta_comic.md @@ -0,0 +1,115 @@ +# migration_oxc_ecosystem_delta_comic + +Reproduces the typed JSON imports in https://github.com/vite-plus-ecosystem-ci/delta-comic/pull/9. + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vp fmt packages/app/src` + +``` +VITE+ - The Unified Toolchain for the Web + +Finished in on 1 files using threads. +``` + +## `vpt print-file packages/app/src/index.ts` + +``` +export const message = 'preserved' +``` + +## `vp run check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp check packages/app/src ⊘ cache disabled +VITE+ - The Unified Toolchain for the Web + +pass: All 1 file are correctly formatted (, threads) +pass: Found no warnings or lint errors in 1 file (, threads) +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +repeat migration without invalidating either JSON import + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp run check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp check packages/app/src ⊘ cache disabled +VITE+ - The Unified Toolchain for the Web + +pass: All 1 file are correctly formatted (, threads) +pass: Found no warnings or lint errors in 1 file (, threads) +``` + +## `vpt write-file packages/app/src/index.ts 'console.log('\''hello'\'') +'` + + +## `vp run check` + +vp check must still apply the imported lint rule + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp check packages/app/src ⊘ cache disabled +VITE+ - The Unified Toolchain for the Web + +pass: All 1 file are correctly formatted (, threads) +error: Lint issues found +× eslint(no-console): Unexpected console statement. + ╭─[packages/app/src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 1 error and 0 warnings in 1 file (, threads) +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/vite.config.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/vite.config.ts new file mode 100644 index 0000000000..c2e9c9eec2 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_delta_comic/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite-plus'; +import type { OxfmtConfig } from 'vite-plus/fmt'; +import type { OxlintConfig } from 'vite-plus/lint'; + +import fmt from './.oxfmtrc.json' with { type: 'json' }; +import lint from './.oxlintrc.json' with { type: 'json' }; + +export default defineConfig({ + fmt: fmt as OxfmtConfig, + lint: lint as OxlintConfig, +}); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxfmtrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxfmtrc.json new file mode 100644 index 0000000000..fd496a820e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxlintrc.json new file mode 100644 index 0000000000..3a438765d5 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/package.json new file mode 100644 index 0000000000..b8383e58a3 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/package.json @@ -0,0 +1,14 @@ +{ + "name": "migration-oxc-ecosystem-freecut", + "private": true, + "type": "module", + "packageManager": "npm@11.16.0", + "devDependencies": { + "vite-plus": "latest" + }, + "scripts": { + "lint": "vp lint src", + "format": "vp fmt src .oxlintrc.json .oxfmtrc.json", + "format:check": "vp fmt src .oxlintrc.json .oxfmtrc.json --check" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots.toml new file mode 100644 index 0000000000..b4611bbb48 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots.toml @@ -0,0 +1,17 @@ +[[case]] +name = "migration_oxc_ecosystem_freecut" +vp = "global" +comment = "Reproduces the config file reads and script inputs in https://github.com/vite-plus-ecosystem-ci/freecut/pull/9." +steps = [ + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", ".oxfmtrc.json"], + ["vp", "run", "format"], + ["vpt", "print-file", "src/index.ts"], + ["vp", "run", "lint"], + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "repeat migration without deleting either file read by vite.config.ts" }, + ["vp", "run", "format:check"], + ["vp", "run", "lint"], + { argv = ["vpt", "write-file", "src/index.ts", "console.log('hello')\n"], snapshot = false }, + { argv = ["vp", "run", "lint"], comment = "the rules read from the retained JSON must still apply", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots/migration_oxc_ecosystem_freecut.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots/migration_oxc_ecosystem_freecut.md new file mode 100644 index 0000000000..77b1cbed9f --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/snapshots/migration_oxc_ecosystem_freecut.md @@ -0,0 +1,122 @@ +# migration_oxc_ecosystem_freecut + +Reproduces the config file reads and script inputs in https://github.com/vite-plus-ecosystem-ci/freecut/pull/9. + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node npm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vp run format` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt src .oxlintrc.json .oxfmtrc.json ⊘ cache disabled +Finished in on 3 files using threads. +``` + +## `vpt print-file src/index.ts` + +``` +export const message = 'preserved' +``` + +## `vp run lint` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +repeat migration without deleting either file read by vite.config.ts + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp run format:check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt src .oxlintrc.json .oxfmtrc.json --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 3 files using threads. +``` + +## `vp run lint` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vpt write-file src/index.ts 'console.log('\''hello'\'') +'` + + +## `vp run lint` + +the rules read from the retained JSON must still apply + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/src/index.ts new file mode 100644 index 0000000000..38b0fa8a2f --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/src/index.ts @@ -0,0 +1 @@ +export const message = "preserved"; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/vite.config.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/vite.config.ts new file mode 100644 index 0000000000..4473993950 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_freecut/vite.config.ts @@ -0,0 +1,16 @@ +import { readFileSync } from 'node:fs'; +import { defineConfig } from 'vite-plus'; + +const oxlintConfig = JSON.parse(readFileSync(new URL('./.oxlintrc.json', import.meta.url), 'utf8')); +const oxfmtConfig = JSON.parse(readFileSync(new URL('./.oxfmtrc.json', import.meta.url), 'utf8')); + +export default defineConfig({ + lint: { + ...oxlintConfig, + ignorePatterns: ['dist/**'], + }, + fmt: { + ...oxfmtConfig, + ignorePatterns: ['dist/**'], + }, +}); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxfmtrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxfmtrc.json new file mode 100644 index 0000000000..fd496a820e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxlintrc.json new file mode 100644 index 0000000000..3a438765d5 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/package.json new file mode 100644 index 0000000000..be73e816c3 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/package.json @@ -0,0 +1,18 @@ +{ + "name": "migration-oxc-ecosystem-pdfme", + "private": true, + "type": "module", + "packageManager": "npm@11.16.0", + "workspaces": [ + "packages/*" + ], + "devDependencies": { + "vite-plus": "latest" + }, + "scripts": { + "lint": "vp run --filter '@pdfme/*' lint", + "lint:root": "vp lint --config .oxlintrc.json packages/common/src", + "fmt": "vp run --filter '@pdfme/*' fmt", + "fmt:check": "vp fmt -c .oxfmtrc.json packages/common/src --check" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/package.json new file mode 100644 index 0000000000..ccc47b9cb6 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/package.json @@ -0,0 +1,9 @@ +{ + "name": "@pdfme/common", + "private": true, + "type": "module", + "scripts": { + "lint": "vp lint -c ../../.oxlintrc.json src", + "fmt": "vp fmt -c ../../.oxfmtrc.json src --write" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/src/index.ts new file mode 100644 index 0000000000..38b0fa8a2f --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/packages/common/src/index.ts @@ -0,0 +1 @@ +export const message = "preserved"; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/.oxlintrc.json new file mode 100644 index 0000000000..4c4b01a512 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/.oxlintrc.json @@ -0,0 +1,11 @@ +{ + "extends": ["../.oxlintrc.json"], + "overrides": [ + { + "files": ["e2e/**/*.ts"], + "rules": { + "no-console": "off" + } + } + ] +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/package.json new file mode 100644 index 0000000000..b25713e9bc --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/package.json @@ -0,0 +1,5 @@ +{ + "name": "playground", + "private": true, + "type": "module" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/src/index.ts new file mode 100644 index 0000000000..d6e8d0722e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/playground/src/index.ts @@ -0,0 +1 @@ +export const message = 'playground'; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots.toml new file mode 100644 index 0000000000..efa53dbe35 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots.toml @@ -0,0 +1,45 @@ +[[case]] +name = "migration_oxc_ecosystem_pdfme" +vp = "global" +comment = "Reproduces the root and workspace config flags and playground inheritance in https://github.com/vite-plus-ecosystem-ci/pdfme/pull/7." +steps = [ + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", ".oxfmtrc.json"], + ["vpt", "print-file", "playground/.oxlintrc.json"], + ["vp", "run", "fmt"], + ["vpt", "print-file", "packages/common/src/index.ts"], + ["vp", "run", "fmt:check"], + ["vp", "run", "lint"], + ["vp", "run", "lint:root"], + { argv = ["vp", "lint", "-c", ".oxlintrc.json", "src"], cwd = "playground" }, + { argv = ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], comment = "repeat migration and rerun scripts from both the root and a workspace" }, + ["vp", "run", "fmt:check"], + { argv = ["vp", "run", "fmt"], cwd = "packages/common" }, + { argv = ["vp", "run", "lint"], cwd = "packages/common" }, + { argv = ["vpt", "write-file", "packages/common/src/index.ts", "console.log('hello')\n"], snapshot = false }, + { argv = ["vpt", "write-file", "playground/src/index.ts", "console.log('hello')\n"], snapshot = false }, + { argv = ["vp", "run", "lint"], comment = "the workspace script must still use the root JSON rules", continue-on-failure = true }, + { argv = ["vp", "lint", "-c", ".oxlintrc.json", "src"], cwd = "playground", comment = "the playground must still inherit the root JSON rules", continue-on-failure = true }, +] + +[[case]] +name = "migration_oxc_ecosystem_pdfme_inline_config" +vp = ["local", "global"] +comment = "Explicit config paths in pdfme-style scripts must also work when inline lint and fmt options exist." +steps = [ + { argv = ["vpt", "write-file", "vite.config.mts", "export default { lint: { rules: { 'no-console': 'off' } }, fmt: { singleQuote: false, semi: true } };\n"], snapshot = false }, + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", ".oxfmtrc.json"], + ["vp", "run", "fmt"], + ["vpt", "print-file", "packages/common/src/index.ts"], + ["vp", "run", "fmt:check"], + ["vp", "run", "lint"], + ["vp", "run", "lint:root"], + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vp", "run", "fmt:check"], + { argv = ["vpt", "write-file", "packages/common/src/index.ts", "console.log('hello')\n"], snapshot = false }, + { argv = ["vp", "run", "lint"], comment = "the explicit JSON rule must override the inline no-console setting", continue-on-failure = true }, + { argv = ["vp", "run", "lint:root"], comment = "the root --config script must also use the explicit JSON rule", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme.md new file mode 100644 index 0000000000..af300fde79 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme.md @@ -0,0 +1,202 @@ +# migration_oxc_ecosystem_pdfme + +Reproduces the root and workspace config flags and playground inheritance in https://github.com/vite-plus-ecosystem-ci/pdfme/pull/7. + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node npm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vpt print-file playground/.oxlintrc.json` + +``` +{ + "extends": ["../.oxlintrc.json"], + "overrides": [ + { + "files": ["e2e/**/*.ts"], + "rules": { + "no-console": "off" + } + } + ] +} +``` + +## `vp run fmt` + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp fmt -c ../../.oxfmtrc.json src --write ⊘ cache disabled +Finished in on 1 files using threads. +``` + +## `vpt print-file packages/common/src/index.ts` + +``` +export const message = 'preserved' +``` + +## `vp run fmt:check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt -c .oxfmtrc.json packages/common/src --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp run lint` + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:root` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config .oxlintrc.json packages/common/src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `cd playground && vp lint -c .oxlintrc.json src` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +repeat migration and rerun scripts from both the root and a workspace + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp run fmt:check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt -c .oxfmtrc.json packages/common/src --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `cd packages/common && vp run fmt` + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp fmt -c ../../.oxfmtrc.json src --write ⊘ cache disabled +Finished in on 1 files using threads. +``` + +## `cd packages/common && vp run lint` + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vpt write-file packages/common/src/index.ts 'console.log('\''hello'\'') +'` + + +## `vpt write-file playground/src/index.ts 'console.log('\''hello'\'') +'` + + +## `vp run lint` + +the workspace script must still use the root JSON rules + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` + +## `cd playground && vp lint -c .oxlintrc.json src` + +the playground must still inherit the root JSON rules + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.global.md new file mode 100644 index 0000000000..7766dd70b6 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.global.md @@ -0,0 +1,158 @@ +# migration_oxc_ecosystem_pdfme_inline_config + +Explicit config paths in pdfme-style scripts must also work when inline lint and fmt options exist. + +## `vpt write-file vite.config.mts 'export default { lint: { rules: { '\''no-console'\'': '\''off'\'' } }, fmt: { singleQuote: false, semi: true } }; +'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node npm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vp run fmt` + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp fmt -c ../../.oxfmtrc.json src --write ⊘ cache disabled +Finished in on 1 files using threads. +``` + +## `vpt print-file packages/common/src/index.ts` + +``` +export const message = 'preserved' +``` + +## `vp run fmt:check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt -c .oxfmtrc.json packages/common/src --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp run lint` + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:root` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config .oxlintrc.json packages/common/src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp run fmt:check` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp fmt -c .oxfmtrc.json packages/common/src --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vpt write-file packages/common/src/index.ts 'console.log('\''hello'\'') +'` + + +## `vp run lint` + +the explicit JSON rule must override the inline no-console setting + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:root` + +the root --config script must also use the explicit JSON rule + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config .oxlintrc.json packages/common/src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[packages/common/src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.local.md new file mode 100644 index 0000000000..e34ebfe7ef --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/snapshots/migration_oxc_ecosystem_pdfme_inline_config.local.md @@ -0,0 +1,144 @@ +# migration_oxc_ecosystem_pdfme_inline_config + +Explicit config paths in pdfme-style scripts must also work when inline lint and fmt options exist. + +## `vpt write-file vite.config.mts 'export default { lint: { rules: { '\''no-console'\'': '\''off'\'' } }, fmt: { singleQuote: false, semi: true } }; +'` + + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node npm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vp run fmt` + +``` +~/packages/common$ vp fmt -c ../../.oxfmtrc.json src --write ⊘ cache disabled +Finished in on 1 files using threads. +``` + +## `vpt print-file packages/common/src/index.ts` + +``` +export const message = 'preserved' +``` + +## `vp run fmt:check` + +``` +$ vp fmt -c .oxfmtrc.json packages/common/src --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp run lint` + +``` +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:root` + +``` +$ vp lint --config .oxlintrc.json packages/common/src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp run fmt:check` + +``` +$ vp fmt -c .oxfmtrc.json packages/common/src --check ⊘ cache disabled +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vpt write-file packages/common/src/index.ts 'console.log('\''hello'\'') +'` + + +## `vp run lint` + +the explicit JSON rule must override the inline no-console setting + +**Exit code:** 1 + +``` +~/packages/common$ vp lint -c ../../.oxlintrc.json src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:root` + +the root --config script must also use the explicit JSON rule + +**Exit code:** 1 + +``` +$ vp lint --config .oxlintrc.json packages/common/src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[packages/common/src/index.ts:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/vite.config.mts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/vite.config.mts new file mode 100644 index 0000000000..c62e7a59fe --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_pdfme/vite.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + run: { + enablePrePostScripts: true, + }, +}); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/.oxlintrc.json new file mode 100644 index 0000000000..2ff50f91ec --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/.oxlintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": "error" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/.oxlintrc.json new file mode 100644 index 0000000000..8e2ab53210 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../.oxlintrc.json"], + "rules": { + "no-alert": "off" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/package.json new file mode 100644 index 0000000000..701939fcf2 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/package.json @@ -0,0 +1,5 @@ +{ + "name": "custom-domain", + "private": true, + "type": "module" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/src/index.ts new file mode 100644 index 0000000000..42838525f1 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/custom-domain/src/index.ts @@ -0,0 +1 @@ +export const message = 'custom domain'; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/.oxlintrc.json new file mode 100644 index 0000000000..8e2ab53210 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../.oxlintrc.json"], + "rules": { + "no-alert": "off" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/package.json new file mode 100644 index 0000000000..7e69add5a4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/package.json @@ -0,0 +1,5 @@ +{ + "name": "sigle", + "private": true, + "type": "module" +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/src/index.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/src/index.ts new file mode 100644 index 0000000000..1e96b75ca7 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/src/index.ts @@ -0,0 +1 @@ +export const message = 'sigle'; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/vite.config.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/vite.config.ts new file mode 100644 index 0000000000..8abe24a2a4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/apps/sigle/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + resolve: { + tsconfigPaths: true, + }, +}); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/package.json new file mode 100644 index 0000000000..9d4e608c4c --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/package.json @@ -0,0 +1,9 @@ +{ + "name": "migration-oxc-ecosystem-sigle", + "private": true, + "type": "module", + "packageManager": "pnpm@12.3.4", + "devDependencies": { + "vite-plus": "latest" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/pnpm-workspace.yaml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/pnpm-workspace.yaml new file mode 100644 index 0000000000..852bf6b8f9 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - apps/* diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots.toml new file mode 100644 index 0000000000..c847c1496a --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots.toml @@ -0,0 +1,21 @@ +[[case]] +name = "migration_oxc_ecosystem_sigle" +vp = "global" +comment = "Reproduces the two app-level JSON extends configs in https://github.com/vite-plus-ecosystem-ci/sigle/pull/8, with a shared base to verify inherited rules." +steps = [ + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", "apps/sigle/.oxlintrc.json"], + ["vpt", "print-file", "apps/custom-domain/.oxlintrc.json"], + { argv = ["vpt", "print-file", "apps/sigle/vite.config.ts"], comment = "JSON extends paths must not be copied into the existing Vite config" }, + { argv = ["vpt", "stat-file", "apps/custom-domain/vite.config.ts", "--assert-not", "file"], comment = "an app without a Vite config must keep its JSON config" }, + { argv = ["vp", "lint", "-c", ".oxlintrc.json", "src"], cwd = "apps/sigle" }, + { argv = ["vp", "lint", "--config", ".oxlintrc.json", "src"], cwd = "apps/custom-domain" }, + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", "apps/sigle/vite.config.ts"], + ["vpt", "stat-file", "apps/custom-domain/vite.config.ts", "--assert-not", "file"], + { argv = ["vpt", "write-file", "apps/sigle/src/index.ts", "console.log('hello');\n"], snapshot = false }, + { argv = ["vpt", "write-file", "apps/custom-domain/src/index.ts", "console.log('hello');\n"], snapshot = false }, + { argv = ["vp", "lint", "-c", ".oxlintrc.json", "src"], cwd = "apps/sigle", comment = "the app must still load its inherited JSON rule", continue-on-failure = true }, + { argv = ["vp", "lint", "--config", ".oxlintrc.json", "src"], cwd = "apps/custom-domain", comment = "the app without vite.config.ts must also load its inherited JSON rule", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots/migration_oxc_ecosystem_sigle.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots/migration_oxc_ecosystem_sigle.md new file mode 100644 index 0000000000..747b843986 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/snapshots/migration_oxc_ecosystem_sigle.md @@ -0,0 +1,162 @@ +# migration_oxc_ecosystem_sigle + +Reproduces the two app-level JSON extends configs in https://github.com/vite-plus-ecosystem-ci/sigle/pull/8, with a shared base to verify inherited rules. + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + } +} +``` + +## `vpt print-file apps/sigle/.oxlintrc.json` + +``` +{ + "extends": ["../../.oxlintrc.json"], + "rules": { + "no-alert": "off" + } +} +``` + +## `vpt print-file apps/custom-domain/.oxlintrc.json` + +``` +{ + "extends": ["../../.oxlintrc.json"], + "rules": { + "no-alert": "off" + } +} +``` + +## `vpt print-file apps/sigle/vite.config.ts` + +JSON extends paths must not be copied into the existing Vite config + +``` +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + resolve: { + tsconfigPaths: true, + }, +}); +``` + +## `vpt stat-file apps/custom-domain/vite.config.ts --assert-not file` + +an app without a Vite config must keep its JSON config + +``` +apps/custom-domain/vite.config.ts: missing +``` + +## `cd apps/sigle && vp lint -c .oxlintrc.json src` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `cd apps/custom-domain && vp lint --config .oxlintrc.json src` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vpt print-file apps/sigle/vite.config.ts` + +``` +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + resolve: { + tsconfigPaths: true, + }, +}); +``` + +## `vpt stat-file apps/custom-domain/vite.config.ts --assert-not file` + +``` +apps/custom-domain/vite.config.ts: missing +``` + +## `vpt write-file apps/sigle/src/index.ts 'console.log('\''hello'\''); +'` + + +## `vpt write-file apps/custom-domain/src/index.ts 'console.log('\''hello'\''); +'` + + +## `cd apps/sigle && vp lint -c .oxlintrc.json src` + +the app must still load its inherited JSON rule + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello'); + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` + +## `cd apps/custom-domain && vp lint --config .oxlintrc.json src` + +the app without vite.config.ts must also load its inherited JSON rule + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.ts:1:1] + 1 │ console.log('hello'); + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/vite.config.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/vite.config.ts new file mode 100644 index 0000000000..a21ea9b063 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxc_ecosystem_sigle/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + lint: { + rules: { + 'no-console': 'off', + }, + }, + fmt: { + singleQuote: true, + }, +}); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-fmt.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-fmt.json new file mode 100644 index 0000000000..544138be45 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-fmt.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-lint.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-lint.json new file mode 100644 index 0000000000..d5ba8f9d9c --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/custom-lint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": "off" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/index.js b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/index.js new file mode 100644 index 0000000000..702f4280ce --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/index.js @@ -0,0 +1 @@ +console.log("hello"); diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/package.json new file mode 100644 index 0000000000..bb883e47c4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/oxc-explicit-config", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "lint:custom": "vp lint --config custom-lint.json index.js" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots.toml new file mode 100644 index 0000000000..3f192e38e4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots.toml @@ -0,0 +1,31 @@ +[[case]] +name = "lint_explicit_config" +vp = ["local", "global"] +steps = [ + { argv = ["vp", "lint", "index.js"], comment = "use inline lint rules when no config flag is supplied", continue-on-failure = true }, + { argv = ["vp", "lint", "--config", "custom-lint.json", "index.js"], comment = "an explicit config overrides the inline lint rules", continue-on-failure = true }, + ["vp", "lint", "-c", "custom-lint.json", "index.js"], + ["vp", "lint", "--config=custom-lint.json", "index.js"], + ["vp", "lint", "-c=custom-lint.json", "index.js"], + ["vp", "lint", "-ccustom-lint.json", "index.js"], + ["vp", "run", "lint:custom"], + { argv = ["vpt", "write-file", "vite.config.ts", "throw new Error('Vite config must not be loaded');\n"], snapshot = false }, + { argv = ["vp", "lint", "--config", "custom-lint.json", "index.js"], comment = "explicit lint configs do not require a working Vite config" }, +] + +[[case]] +name = "fmt_explicit_config" +vp = ["local", "global"] +steps = [ + { argv = ["vp", "fmt", "--config", "custom-fmt.json", "index.js"], comment = "an explicit config overrides the inline format options" }, + ["vpt", "print-file", "index.js"], + ["vp", "fmt", "--check", "-c", "custom-fmt.json", "index.js"], + ["vp", "fmt", "--check", "--config=custom-fmt.json", "index.js"], + ["vp", "fmt", "--check", "-c=custom-fmt.json", "index.js"], + ["vp", "fmt", "--check", "-ccustom-fmt.json", "index.js"], + { argv = ["vp", "fmt", "index.js"], comment = "use inline format options when no config flag is supplied" }, + ["vpt", "print-file", "index.js"], + { argv = ["vpt", "write-file", "vite.config.ts", "throw new Error('Vite config must not be loaded');\n"], snapshot = false }, + { argv = ["vp", "fmt", "--config", "custom-fmt.json", "index.js"], comment = "explicit format configs do not require a working Vite config" }, + ["vpt", "print-file", "index.js"], +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.global.md new file mode 100644 index 0000000000..81f07874e4 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.global.md @@ -0,0 +1,97 @@ +# fmt_explicit_config + +## `vp fmt --config custom-fmt.json index.js` + +an explicit config overrides the inline format options + +``` +VITE+ - The Unified Toolchain for the Web + +Finished in on 1 files using threads. +``` + +## `vpt print-file index.js` + +``` +console.log('hello'); +``` + +## `vp fmt --check -c custom-fmt.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt --check --config=custom-fmt.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt --check -c=custom-fmt.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt --check -ccustom-fmt.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt index.js` + +use inline format options when no config flag is supplied + +``` +VITE+ - The Unified Toolchain for the Web + +Finished in on 1 files using threads. +``` + +## `vpt print-file index.js` + +``` +console.log("hello"); +``` + +## `vpt write-file vite.config.ts 'throw new Error('\''Vite config must not be loaded'\''); +'` + + +## `vp fmt --config custom-fmt.json index.js` + +explicit format configs do not require a working Vite config + +``` +VITE+ - The Unified Toolchain for the Web + +Finished in on 1 files using threads. +``` + +## `vpt print-file index.js` + +``` +console.log('hello'); +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.local.md new file mode 100644 index 0000000000..8803e19a95 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/fmt_explicit_config.local.md @@ -0,0 +1,83 @@ +# fmt_explicit_config + +## `vp fmt --config custom-fmt.json index.js` + +an explicit config overrides the inline format options + +``` +Finished in on 1 files using threads. +``` + +## `vpt print-file index.js` + +``` +console.log('hello'); +``` + +## `vp fmt --check -c custom-fmt.json index.js` + +``` +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt --check --config=custom-fmt.json index.js` + +``` +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt --check -c=custom-fmt.json index.js` + +``` +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt --check -ccustom-fmt.json index.js` + +``` +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp fmt index.js` + +use inline format options when no config flag is supplied + +``` +Finished in on 1 files using threads. +``` + +## `vpt print-file index.js` + +``` +console.log("hello"); +``` + +## `vpt write-file vite.config.ts 'throw new Error('\''Vite config must not be loaded'\''); +'` + + +## `vp fmt --config custom-fmt.json index.js` + +explicit format configs do not require a working Vite config + +``` +Finished in on 1 files using threads. +``` + +## `vpt print-file index.js` + +``` +console.log('hello'); +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.global.md new file mode 100644 index 0000000000..25bf0a48e6 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.global.md @@ -0,0 +1,93 @@ +# lint_explicit_config + +## `vp lint index.js` + +use inline lint rules when no config flag is supplied + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[index.js:1:1] + 1 │ console.log("hello"); + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` + +## `vp lint --config custom-lint.json index.js` + +an explicit config overrides the inline lint rules + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint -c custom-lint.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint --config=custom-lint.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint -c=custom-lint.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint -ccustom-lint.json index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:custom` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config custom-lint.json index.js ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vpt write-file vite.config.ts 'throw new Error('\''Vite config must not be loaded'\''); +'` + + +## `vp lint --config custom-lint.json index.js` + +explicit lint configs do not require a working Vite config + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.local.md new file mode 100644 index 0000000000..3e8924c393 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/snapshots/lint_explicit_config.local.md @@ -0,0 +1,78 @@ +# lint_explicit_config + +## `vp lint index.js` + +use inline lint rules when no config flag is supplied + +**Exit code:** 1 + +``` + + × eslint(no-console): Unexpected console statement. + ╭─[index.js:1:1] + 1 │ console.log("hello"); + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` + +## `vp lint --config custom-lint.json index.js` + +an explicit config overrides the inline lint rules + +``` +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint -c custom-lint.json index.js` + +``` +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint --config=custom-lint.json index.js` + +``` +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint -c=custom-lint.json index.js` + +``` +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp lint -ccustom-lint.json index.js` + +``` +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp run lint:custom` + +``` +$ vp lint --config custom-lint.json index.js ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vpt write-file vite.config.ts 'throw new Error('\''Vite config must not be loaded'\''); +'` + + +## `vp lint --config custom-lint.json index.js` + +explicit lint configs do not require a working Vite config + +``` +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/vite.config.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/vite.config.ts new file mode 100644 index 0000000000..39d832bfa9 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/oxc_explicit_config/vite.config.ts @@ -0,0 +1,10 @@ +export default { + lint: { + rules: { + 'no-console': 'error', + }, + }, + fmt: { + singleQuote: false, + }, +}; diff --git a/packages/cli/binding/src/cli/resolver.rs b/packages/cli/binding/src/cli/resolver.rs index 7d29aa4910..286a8b00fe 100644 --- a/packages/cli/binding/src/cli/resolver.rs +++ b/packages/cli/binding/src/cli/resolver.rs @@ -77,19 +77,21 @@ impl SubcommandResolver { 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 !has_explicit_config(&args) { + 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()); + 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 { @@ -114,19 +116,21 @@ impl SubcommandResolver { 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 !has_explicit_config(&args) { + 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()); + 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 { @@ -321,6 +325,14 @@ pub(super) fn check_cache_inputs() -> Vec { ] } +// Oxlint and Oxfmt accept both separate and attached config values. Leave +// validation to the tool, and do not treat paths after `--` as options. +fn has_explicit_config(args: &[String]) -> bool { + args.iter() + .take_while(|arg| *arg != "--") + .any(|arg| arg == "--config" || arg.starts_with("--config=") || arg.starts_with("-c")) +} + fn merge_resolved_envs( envs: &Arc, Arc>>, resolved_envs: Vec<(String, String)>, @@ -362,13 +374,9 @@ mod tests { }) } - #[tokio::test] - async fn builtins_reuse_the_calling_node_runtime() { - let temp = tempfile::tempdir().unwrap(); - let cwd = AbsolutePathBuf::new(temp.path().to_path_buf()).unwrap(); - let runtime: Arc = Arc::from(cwd.join("custom runtime").as_path().as_os_str()); - let resolver = SubcommandResolver::new(cwd.clone().into()).with_cli_options(CliOptions { - node_exec_path: Arc::clone(&runtime), + fn cli_options(runtime: Arc) -> CliOptions { + CliOptions { + node_exec_path: runtime, lint: tool_resolver(), fmt: tool_resolver(), vite: tool_resolver(), @@ -378,7 +386,101 @@ mod tests { toolchain_manifest_path: String::new(), vite_plus_package_path: String::new(), resolve_universal_vite_config: Arc::new(|_| Box::pin(async { Ok("{}".to_string()) })), + } + } + + #[tokio::test] + async fn explicit_tool_configs_bypass_vite_config() { + let temp = tempfile::tempdir().unwrap(); + let cwd = AbsolutePathBuf::new(temp.path().to_path_buf()).unwrap(); + let mut options = cli_options(Arc::from(OsStr::new("node"))); + options.resolve_universal_vite_config = Arc::new(|_| { + Box::pin(async { anyhow::bail!("explicit configs must not load vite.config") }) + }); + let resolver = SubcommandResolver::new(cwd.clone().into()).with_cli_options(options); + let config = serde_json::from_value(serde_json::json!({ + "configFile": "vite.config.ts", "lint": {}, "fmt": {} + })) + .unwrap(); + let envs = Arc::new(FxHashMap::default()); + + for args in [ + vec!["--config", "custom config.json", "index.js"], + vec!["-c", "custom.json", "index.js"], + vec!["--config=custom.json", "index.js"], + vec!["-c=custom.json", "index.js"], + vec!["-ccustom.json", "index.js"], + vec!["--config"], + vec!["--config="], + ] { + for resolved_config in [None, Some(&config)] { + for command in [ + SynthesizableSubcommand::Lint { + args: args.iter().map(ToString::to_string).collect(), + }, + SynthesizableSubcommand::Fmt { + args: args.iter().map(ToString::to_string).collect(), + }, + ] { + let resolved = + resolver.resolve(command, resolved_config, &envs, &cwd).await.unwrap(); + let tool_args: Vec<_> = resolved + .args + .iter() + .skip_while(|arg| arg.as_str() != "tool.js") + .skip(1) + .map(Str::as_str) + .collect(); + assert_eq!(tool_args, args); + } + } + } + } + + #[tokio::test] + async fn tool_configs_default_to_vite_config_before_option_terminator() { + let temp = tempfile::tempdir().unwrap(); + let cwd = AbsolutePathBuf::new(temp.path().to_path_buf()).unwrap(); + let mut options = cli_options(Arc::from(OsStr::new("node"))); + options.resolve_universal_vite_config = Arc::new(|_| { + Box::pin(async { + Ok(r#"{"configFile":"vite.config.ts","lint":{},"fmt":{}}"#.to_string()) + }) }); + let resolver = SubcommandResolver::new(cwd.clone().into()).with_cli_options(options); + let envs = Arc::new(FxHashMap::default()); + + for args in [vec!["index.js"], vec!["--", "--config", "-custom.js"]] { + for command in [ + SynthesizableSubcommand::Lint { + args: args.iter().map(ToString::to_string).collect(), + }, + SynthesizableSubcommand::Fmt { + args: args.iter().map(ToString::to_string).collect(), + }, + ] { + let resolved = resolver.resolve(command, None, &envs, &cwd).await.unwrap(); + let tool_args: Vec<_> = resolved + .args + .iter() + .skip_while(|arg| arg.as_str() != "tool.js") + .skip(1) + .map(Str::as_str) + .collect(); + let expected: Vec<_> = + ["-c", "vite.config.ts"].into_iter().chain(args.clone()).collect(); + assert_eq!(tool_args, expected); + } + } + } + + #[tokio::test] + async fn builtins_reuse_the_calling_node_runtime() { + let temp = tempfile::tempdir().unwrap(); + let cwd = AbsolutePathBuf::new(temp.path().to_path_buf()).unwrap(); + let runtime: Arc = Arc::from(cwd.join("custom runtime").as_path().as_os_str()); + let resolver = SubcommandResolver::new(cwd.clone().into()) + .with_cli_options(cli_options(Arc::clone(&runtime))); let envs = Arc::new(FxHashMap::default()); for command in [ SynthesizableSubcommand::Lint { args: vec![] }, From 34e5e931bb3d24a88fa1473c3119827ea6345bfd Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 14 Sep 2026 18:01:09 +0800 Subject: [PATCH 5/6] fix(ci): allow intentional config test identifiers --- .typos.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.typos.toml b/.typos.toml index 0c6c885186..6c9c1ba380 100644 --- a/.typos.toml +++ b/.typos.toml @@ -4,12 +4,15 @@ PUNICODE = "PUNICODE" Jod = "Jod" # Node.js v22 LTS codename flate = "flate" # flate2 crate name (gzip/deflate compression) fpr = "fpr" # GPG colon-format record name for a key fingerprint +sigle = "sigle" # Ecosystem repository name used in migration regression fixtures [default.extend-identifiers] # Yarn Plug'n'Play — tokenizer splits `PnP` into the word `Pn`, which typos # otherwise wants to autocorrect to `On`. Whitelist the full identifier so # the parts aren't tokenized. PnP = "PnP" +# Tests pass an attached config path: `-c` followed by `custom.json`. +ccustom = "ccustom" [files] extend-exclude = [ From a8d39f28b910b88975fa2f5d828a81008a963df2 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 14 Sep 2026 22:41:27 +0800 Subject: [PATCH 6/6] fix(migrate): preserve indirect Oxc config references --- .../.oxfmtrc.json | 4 + .../.oxlintrc.json | 9 ++ .../config/lint.json | 3 + .../config/shared.ts | 4 + .../package.json | 9 ++ .../snapshots.toml | 36 +++++ .../migration_custom_lint_config_root.md | 122 ++++++++++++++++ .../migration_shared_vite_config_module.md | 138 ++++++++++++++++++ .../src/index.js | 1 + .../vite.config.ts | 1 + .../src/migration/__tests__/migrator.spec.ts | 128 ++++++++++++++++ .../cli/src/migration/migrator/vite-config.ts | 110 ++++++++++++-- 12 files changed, 555 insertions(+), 10 deletions(-) create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxfmtrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxlintrc.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/lint.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/shared.ts create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/package.json create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots.toml create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_custom_lint_config_root.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_shared_vite_config_module.md create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/src/index.js create mode 100644 crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/vite.config.ts diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxfmtrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxfmtrc.json new file mode 100644 index 0000000000..fd496a820e --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxlintrc.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxlintrc.json new file mode 100644 index 0000000000..3a438765d5 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/lint.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/lint.json new file mode 100644 index 0000000000..d4ac61eb93 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/lint.json @@ -0,0 +1,3 @@ +{ + "extends": ["../.oxlintrc.json"] +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/shared.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/shared.ts new file mode 100644 index 0000000000..33d9f7c0fd --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/config/shared.ts @@ -0,0 +1,4 @@ +import fmt from '../.oxfmtrc.json' with { type: 'json' }; +import lint from '../.oxlintrc.json' with { type: 'json' }; + +export default { fmt, lint }; diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/package.json b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/package.json new file mode 100644 index 0000000000..fd4957a38c --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/package.json @@ -0,0 +1,9 @@ +{ + "name": "migration-indirect-oxc-configs", + "devDependencies": { + "vite-plus": "latest" + }, + "scripts": { + "lint:custom": "vp lint --config config/lint.json src" + } +} diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots.toml new file mode 100644 index 0000000000..c643784b76 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots.toml @@ -0,0 +1,36 @@ +[[case]] +name = "migration_custom_lint_config_root" +vp = "global" +steps = [ + ["vp", "run", "lint:custom"], + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", "config/lint.json"], + { argv = ["vpt", "stat-file", ".oxfmtrc.json", "--assert-not", "file"], comment = "the unrelated formatter config still merges" }, + { argv = ["vp", "run", "lint:custom"], comment = "the custom entrypoint must still load its inherited JSON config" }, + ["vp", "fmt", "--check", "src/index.js"], + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vp", "run", "lint:custom"], + { argv = ["vpt", "write-file", "src/index.js", "console.log('hello')\n"], snapshot = false }, + { argv = ["vp", "run", "lint:custom"], comment = "the inherited no-console rule remains active after two migrations", continue-on-failure = true }, +] + +[[case]] +name = "migration_shared_vite_config_module" +vp = "global" +steps = [ + { argv = ["vpt", "write-file", "package.json", "{\"name\":\"migration-shared-vite-config\",\"devDependencies\":{\"vite-plus\":\"latest\"}}\n"], snapshot = false }, + { argv = ["vpt", "write-file", "vite.config.ts", "import shared from './config/shared.js';\nexport default { ...shared };\n"], snapshot = false }, + ["vp", "fmt", "--check", "src/index.js"], + ["vp", "lint", "src/index.js"], + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vpt", "print-file", ".oxfmtrc.json"], + ["vpt", "print-file", ".oxlintrc.json"], + ["vpt", "print-file", "vite.config.ts"], + { argv = ["vp", "fmt", "--check", "src/index.js"], comment = "the shared TypeScript module must still import its JSON configs" }, + ["vp", "lint", "src/index.js"], + ["vp", "migrate", "--no-interactive", "--no-hooks", "--no-agent", "--no-editor"], + ["vp", "fmt", "--check", "src/index.js"], + { argv = ["vpt", "write-file", "src/index.js", "console.log('hello')\n"], snapshot = false }, + { argv = ["vp", "lint", "src/index.js"], comment = "the indirectly imported lint rule remains active after two migrations", continue-on-failure = true }, +] diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_custom_lint_config_root.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_custom_lint_config_root.md new file mode 100644 index 0000000000..10d686446b --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_custom_lint_config_root.md @@ -0,0 +1,122 @@ +# migration_custom_lint_config_root + +## `vp run lint:custom` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config config/lint.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• 1 config update applied +• Package manager settings configured +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file config/lint.json` + +``` +{ + "extends": ["../.oxlintrc.json"] +} +``` + +## `vpt stat-file .oxfmtrc.json --assert-not file` + +the unrelated formatter config still merges + +``` +.oxfmtrc.json: missing +``` + +## `vp run lint:custom` + +the custom entrypoint must still load its inherited JSON config + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config config/lint.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp fmt --check src/index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp run lint:custom` + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config config/lint.json src ⊘ cache disabled +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vpt write-file src/index.js 'console.log('\''hello'\'') +'` + + +## `vp run lint:custom` + +the inherited no-console rule remains active after two migrations + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + +$ vp lint --config config/lint.json src ⊘ cache disabled + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.js:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_shared_vite_config_module.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_shared_vite_config_module.md new file mode 100644 index 0000000000..7182e99207 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/snapshots/migration_shared_vite_config_module.md @@ -0,0 +1,138 @@ +# migration_shared_vite_config_module + +## `vpt write-file package.json '{"name":"migration-shared-vite-config","devDependencies":{"vite-plus":"latest"}} +'` + + +## `vpt write-file vite.config.ts 'import shared from '\''./config/shared.js'\''; +export default { ...shared }; +'` + + +## `vp fmt --check src/index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp lint src/index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +◇ Updated . to Vite+ +• Node pnpm +• Dependencies: + vite-plus latest → + vite → +• Package manager settings configured +``` + +## `vpt print-file .oxfmtrc.json` + +``` +{ + "singleQuote": true, + "semi": false +} +``` + +## `vpt print-file .oxlintrc.json` + +``` +{ + "rules": { + "no-console": "error" + }, + "options": { + "typeAware": false, + "typeCheck": false + } +} +``` + +## `vpt print-file vite.config.ts` + +``` +import shared from './config/shared.js'; +export default { ...shared }; +``` + +## `vp fmt --check src/index.js` + +the shared TypeScript module must still import its JSON configs + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vp lint src/index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Found 0 warnings and 0 errors. +Finished in on 1 file with rules using threads. +``` + +## `vp migrate --no-interactive --no-hooks --no-agent --no-editor` + +``` +VITE+ - The Unified Toolchain for the Web + +This project is already using Vite+! Happy coding! +``` + +## `vp fmt --check src/index.js` + +``` +VITE+ - The Unified Toolchain for the Web + +Checking formatting... + +All matched files use the correct format. +Finished in on 1 files using threads. +``` + +## `vpt write-file src/index.js 'console.log('\''hello'\'') +'` + + +## `vp lint src/index.js` + +the indirectly imported lint rule remains active after two migrations + +**Exit code:** 1 + +``` +VITE+ - The Unified Toolchain for the Web + + × eslint(no-console): Unexpected console statement. + ╭─[src/index.js:1:1] + 1 │ console.log('hello') + · ─────────── + ╰──── + help: Delete this console statement. + +Found 0 warnings and 1 error. +Finished in on 1 file with rules using threads. +``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/src/index.js b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/src/index.js new file mode 100644 index 0000000000..854ef01aa3 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/src/index.js @@ -0,0 +1 @@ +export const message = 'preserved' diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/vite.config.ts b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/vite.config.ts new file mode 100644 index 0000000000..ff8b4c5632 --- /dev/null +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_indirect_oxc_configs/vite.config.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index 3bd7cad13a..3ce655ef6d 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -9024,6 +9024,134 @@ export default { fmt, lint }; }, ); + it.each([ + '--config "config files/lint.jsonc"', + "-c 'config files/lint.jsonc'", + '--config="config files/lint.jsonc"', + '-c="config files/lint.jsonc"', + '-c"config files/lint.jsonc"', + '"--config" "config files/lint.jsonc"', + '"--config=config files/lint.jsonc"', + ])('preserves inheritance rooted at a custom script config: %s', (args) => { + const appDir = path.join(tmpDir, 'packages/app'); + fs.mkdirSync(path.join(appDir, 'config files'), { recursive: true }); + fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'root' })); + fs.writeFileSync( + path.join(appDir, 'package.json'), + JSON.stringify({ name: 'app', scripts: { lint: `vp lint ${args} src` } }), + ); + const rootConfig = '{"rules":{"no-console":"error"}}\n'; + const customConfig = '// Shared lint rules\n{"extends":["../../../.oxlintrc.json"]}\n'; + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), rootConfig); + fs.writeFileSync(path.join(appDir, 'config files/lint.jsonc'), customConfig); + fs.writeFileSync(path.join(tmpDir, 'vite.config.ts'), 'export default {};\n'); + const workspaceInfo = { + ...makeWorkspaceInfo(tmpDir, PackageManager.pnpm), + packages: [{ name: 'app', path: 'packages/app' }], + }; + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe(rootConfig); + expect(fs.readFileSync(path.join(appDir, 'config files/lint.jsonc'), 'utf8')).toBe( + customConfig, + ); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toBe( + 'export default {};\n', + ); + } + }); + + it.each(['vp lint --config "$LINT_CONFIG"', 'cd config && vp lint -c lint.json'])( + 'preserves configs when a script config path is uncertain: %s', + (lint) => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'root', scripts: { lint } }), + ); + const config = '{"rules":{"no-console":"error"}}\n'; + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), config); + expect( + finalizeCoreMigrationForExistingVitePlus( + makeWorkspaceInfo(tmpDir, PackageManager.npm), + true, + ).oxcConfigs, + ).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe(config); + expect(fs.existsSync(path.join(tmpDir, 'vite.config.ts'))).toBe(false); + }, + ); + + it.each([ + ['./config/shared.ts', 'shared.ts'], + ['./config/shared', 'shared.ts'], + ['./config/shared.base', 'shared.base.ts'], + ['./config/shared.js', 'shared.ts'], + ['./config/shared.mjs', 'shared.mts'], + ['./config/shared.cjs', 'shared.cts'], + ['./config', 'index.ts'], + ])('preserves configs imported through the shared Vite module %s', (specifier, filename) => { + fs.mkdirSync(path.join(tmpDir, 'config')); + fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'root' })); + const config = `import shared from '${specifier}';\nexport default { ...shared };\n`; + fs.writeFileSync(path.join(tmpDir, 'vite.config.ts'), config); + // Follow a re-export, a readFileSync path, and a cycle without evaluating code. + fs.writeFileSync( + path.join(tmpDir, 'config', filename), + "export { default } from './tools.ts';\nthrow new Error('must not execute shared config');\n", + ); + // A competing source must not hide references under another config loader. + if (specifier.endsWith('.js')) { + fs.writeFileSync(path.join(tmpDir, specifier), 'export default {};\n'); + } + fs.writeFileSync( + path.join(tmpDir, 'config/tools.ts'), + `import './${filename}'; +import { readFileSync } from 'node:fs'; +import lint from '../.oxlintrc.json' with { type: 'json' }; +const fmt = JSON.parse(readFileSync(new URL('../.oxfmtrc.json', import.meta.url), 'utf8')); +export default { fmt, lint }; +`, + ); + const lint = '{"rules":{"no-console":"error"}}\n'; + const fmt = '{"singleQuote":true}\n'; + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), lint); + fs.writeFileSync(path.join(tmpDir, '.oxfmtrc.json'), fmt); + const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.npm); + + for (let run = 0; run < 2; run++) { + expect(finalizeCoreMigrationForExistingVitePlus(workspaceInfo, true).oxcConfigs).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, '.oxlintrc.json'), 'utf8')).toBe(lint); + expect(fs.readFileSync(path.join(tmpDir, '.oxfmtrc.json'), 'utf8')).toBe(fmt); + expect(fs.readFileSync(path.join(tmpDir, 'vite.config.ts'), 'utf8')).toBe(config); + } + }); + + it('still merges unreferenced configs alongside custom config arguments and local imports', () => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'root', scripts: { lint: 'vp lint --config custom.json src' } }), + ); + fs.writeFileSync(path.join(tmpDir, 'custom.json'), '{"rules":{"no-debugger":"error"}}\n'); + fs.writeFileSync(path.join(tmpDir, 'shared.ts'), 'export default { define: {} };\n'); + fs.writeFileSync( + path.join(tmpDir, 'vite.config.ts'), + "import shared from './shared';\nexport default { ...shared };\n", + ); + fs.writeFileSync(path.join(tmpDir, '.oxfmtrc.json'), '{"singleQuote":true}\n'); + fs.writeFileSync(path.join(tmpDir, '.oxlintrc.json'), '{"rules":{"no-console":"error"}}\n'); + + expect( + finalizeCoreMigrationForExistingVitePlus(makeWorkspaceInfo(tmpDir, PackageManager.npm), true) + .oxcConfigs, + ).toBe(true); + expect(fs.existsSync(path.join(tmpDir, '.oxfmtrc.json'))).toBe(false); + expect(fs.existsSync(path.join(tmpDir, '.oxlintrc.json'))).toBe(false); + expect(fs.readFileSync(path.join(tmpDir, 'custom.json'), 'utf8')).toBe( + '{"rules":{"no-debugger":"error"}}\n', + ); + }); + it('keeps an unmergeable config and reports the incomplete migration', () => { fs.writeFileSync( path.join(tmpDir, 'package.json'), diff --git a/packages/cli/src/migration/migrator/vite-config.ts b/packages/cli/src/migration/migrator/vite-config.ts index cf8f4b92b7..4e00a79125 100644 --- a/packages/cli/src/migration/migrator/vite-config.ts +++ b/packages/cli/src/migration/migrator/vite-config.ts @@ -236,6 +236,40 @@ export function mergeTsdownConfigFile( return createdViteConfig || result.updated; } +const CONFIG_MODULE_EXTENSIONS = ['.ts', '.mts', '.cts', '.tsx', '.js', '.mjs', '.cjs', '.jsx']; +const CONFIG_SOURCE_EXTENSIONS: Record = { + '.js': ['.ts', '.tsx'], + '.mjs': ['.mts'], + '.cjs': ['.cts'], + '.jsx': ['.tsx'], +}; + +function resolveConfigReferences(directory: string, reference: string): string[] { + if (!reference) { + return []; + } + const filename = path.resolve(directory, reference); + const extension = path.extname(filename); + const candidates = [filename]; + // Vite resolves .js imports to TypeScript sources, as well as extensionless + // imports and directory indexes. Inspect those sources before deleting JSON. + for (const suffix of CONFIG_SOURCE_EXTENSIONS[extension] ?? []) { + candidates.push(filename.slice(0, -extension.length) + suffix); + } + for (const suffix of CONFIG_MODULE_EXTENSIONS) { + candidates.push(filename + suffix, path.join(filename, `index${suffix}`)); + } + // Preserve references from all matching sources instead of relying on one + // config loader's extension precedence when multiple candidates exist. + const resolved = new Set(); + for (const candidate of candidates) { + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) { + resolved.add(fs.realpathSync(candidate)); + } + } + return [...resolved]; +} + /** * Merge oxlint and oxfmt config into vite.config.ts */ @@ -263,21 +297,73 @@ export function mergeViteConfigFiles( ]); const configReferences: string[] = []; const lintConfigPaths = new Set(); - const preservedLintConfigs = new Set(); + const preservedConfigs = new Set(); + const modulePaths = new Set(); + let unresolvedScriptConfig = false; for (const projectDir of projectPaths) { const packageJsonPath = path.join(projectDir, 'package.json'); if (fs.existsSync(packageJsonPath)) { const pkg = readJsonFile(packageJsonPath) as { scripts?: Record }; - configReferences.push(...Object.values(pkg.scripts ?? {})); + for (const script of Object.values(pkg.scripts ?? {})) { + configReferences.push(script); + // Inspect config arguments without executing shell commands. An unknown + // path (for example an environment variable or a preceding `cd`) means + // we cannot safely decide that a standalone config is unused. + const words = (script.match(/(?:[^\s"';&|]+|"[^"]*"|'[^']*')+/g) ?? []).map((word) => + word.replace(/(["'])(.*?)\1/g, '$2'), + ); + for (let index = 0; index < words.length; index++) { + const word = words[index]; + let reference: string; + if (word === '--config' || word === '-c') { + reference = words[++index] ?? ''; + } else if (word.startsWith('--config=')) { + reference = word.slice('--config='.length); + } else if (word.startsWith('-c')) { + reference = word.slice(2).replace(/^=/, ''); + } else { + continue; + } + const filenames = resolveConfigReferences(projectDir, reference); + if (filenames.length === 0 || words.includes('cd')) { + unresolvedScriptConfig = true; + } + for (const filename of filenames) { + if (/\.jsonc?$/.test(filename)) { + preservedConfigs.add(filename); + lintConfigPaths.add(filename); + } else if (CONFIG_MODULE_EXTENSIONS.includes(path.extname(filename))) { + modulePaths.add(filename); + } else { + unresolvedScriptConfig = true; + } + } + } + } } const projectConfigs = detectConfigs(projectDir); if (projectConfigs.viteConfig) { - configReferences.push( - fs.readFileSync(path.join(projectDir, projectConfigs.viteConfig), 'utf8'), - ); + modulePaths.add(fs.realpathSync(path.join(projectDir, projectConfigs.viteConfig))); } if (projectConfigs.oxlintConfig) { - lintConfigPaths.add(path.resolve(projectDir, projectConfigs.oxlintConfig)); + lintConfigPaths.add(fs.realpathSync(path.join(projectDir, projectConfigs.oxlintConfig))); + } + } + for (const modulePath of modulePaths) { + const content = fs.readFileSync(modulePath, 'utf8'); + configReferences.push(content); + // Quoted local paths cover imports, re-exports, require(), dynamic import(), + // and readFileSync/new URL calls. Extra matches only preserve more configs. + // Canonical paths keep cycles, including symlink cycles, finite. + for (const match of content.matchAll(/(["'`])((?:\.{1,2}\/|\/)[^"'`\r\n]+)\1/g)) { + for (const filename of resolveConfigReferences(path.dirname(modulePath), match[2])) { + if (/\.jsonc?$/.test(filename)) { + preservedConfigs.add(filename); + lintConfigPaths.add(filename); + } else if (CONFIG_MODULE_EXTENSIONS.includes(path.extname(filename))) { + modulePaths.add(filename); + } + } } } // Set iteration also visits newly discovered targets, once each, including @@ -290,11 +376,14 @@ export function mergeViteConfigFiles( if (Array.isArray(json?.extends) && json.extends.length > 0) { // JSON extends uses file paths; inline lint.extends requires config objects. // Keep both the extending config and every config in its inheritance chain. - preservedLintConfigs.add(lintConfigPath); + preservedConfigs.add(lintConfigPath); for (const extendedConfig of json.extends) { if (typeof extendedConfig === 'string') { - const extendedConfigPath = path.resolve(path.dirname(lintConfigPath), extendedConfig); - preservedLintConfigs.add(extendedConfigPath); + const resolvedPath = path.resolve(path.dirname(lintConfigPath), extendedConfig); + const extendedConfigPath = fs.existsSync(resolvedPath) + ? fs.realpathSync(resolvedPath) + : resolvedPath; + preservedConfigs.add(extendedConfigPath); lintConfigPaths.add(extendedConfigPath); } } @@ -306,8 +395,9 @@ export function mergeViteConfigFiles( // Keep these files intact, including their lint options. A filename match is // deliberately conservative because script paths can depend on shell state. return ( + !unresolvedScriptConfig && (!fullViteConfigPath || !hasConfigKey(fullViteConfigPath, configKey)) && - !preservedLintConfigs.has(path.resolve(projectPath, filename)) && + !preservedConfigs.has(fs.realpathSync(path.join(projectPath, filename))) && !configReferences.some((content) => content.includes(filename)) ); };