From 526bf1b51200374553af543d463efe0c28b5a06e Mon Sep 17 00:00:00 2001 From: "MK (fengmk2)" Date: Mon, 1 Jun 2026 17:12:05 +0800 Subject: [PATCH] add pnpm vite alias oxlint rule --- docs/.vitepress/config.mts | 1 + docs/config/lint-rules.md | 43 +++++ docs/config/lint.md | 2 + .../create-missing-typecheck/snap.txt | 4 +- .../snap.txt | 3 +- .../migration-baseurl-tsconfig/snap.txt | 3 +- .../snap.txt | 2 +- .../migration-env-prefix-lint-staged/snap.txt | 2 +- .../snap.txt | 3 +- .../snap.txt | 3 +- .../migration-eslint-lint-staged/snap.txt | 3 +- .../migration-eslint-lintstagedrc/snap.txt | 3 +- .../migration-eslint-plugins-cleanup/snap.txt | 3 +- .../snap.txt | 3 +- .../migration-eslint-rerun-mjs/snap.txt | 3 +- .../migration-eslint-rerun/snap.txt | 3 +- .../migration-eslint-type-aware/snap.txt | 3 +- .../migration-eslint/snap.txt | 3 +- .../snap.txt | 2 +- .../snap.txt | 2 +- .../snap.txt | 2 +- .../snap.txt | 4 +- .../migration-from-tsdown/snap.txt | 4 +- .../migration-husky-catalog-version/snap.txt | 2 +- .../migration-lint-staged-in-scripts/snap.txt | 2 +- .../migration-lintstagedrc-json/snap.txt | 2 +- .../snap.txt | 2 +- .../migration-merge-vite-config-js/snap.txt | 3 +- .../migration-merge-vite-config-ts/snap.txt | 3 +- .../migration-monorepo-bun/snap.txt | 3 +- .../snap.txt | 2 +- .../migration-monorepo-pnpm/snap.txt | 6 +- .../migration-monorepo-yarn4/snap.txt | 3 +- .../snap.txt | 3 +- .../migration-oxlintrc-jsonc/snap.txt | 3 +- .../migration-prettier-eslint-combo/snap.txt | 3 +- .../migration-prettier-lint-staged/snap.txt | 2 +- .../migration-prettier-pkg-json/snap.txt | 2 +- .../migration-prettier/snap.txt | 2 +- .../migration-subpath/snap.txt | 2 +- .../snap.txt | 2 +- .../snap.txt | 1 + .../new-vite-monorepo/snap.txt | 2 +- .../command-init-inline-config/snap.txt | 2 +- .../create-org-bundled-monorepo/snap.txt | 2 +- .../apps/website-with-vite/package.json | 14 ++ .../apps/website-with-vite/vite.config.ts | 3 + .../apps/website/package.json | 13 ++ .../apps/website/vite.config.ts | 3 + .../lint-pnpm-vite-alias/package.json | 5 + .../packages/utils/package.json | 14 ++ .../packages/utils/vite.config.ts | 3 + .../lint-pnpm-vite-alias/pnpm-workspace.yaml | 8 + .../snap-tests/lint-pnpm-vite-alias/snap.txt | 8 + .../lint-pnpm-vite-alias/steps.json | 7 + .../lint-pnpm-vite-alias/vite.config.ts | 10 ++ .../cli/src/__tests__/oxlint-plugin.spec.ts | 137 ++++++++++++++- packages/cli/src/oxlint-plugin-config.ts | 8 +- packages/cli/src/oxlint-plugin.ts | 151 ++-------------- .../rules/prefer-vite-plus-imports.ts | 131 ++++++++++++++ .../rules/require-pnpm-vite-alias.ts | 166 ++++++++++++++++++ 61 files changed, 659 insertions(+), 180 deletions(-) create mode 100644 docs/config/lint-rules.md create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/package.json create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/vite.config.ts create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/package.json create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/vite.config.ts create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/package.json create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/package.json create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/vite.config.ts create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/pnpm-workspace.yaml create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/snap.txt create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/steps.json create mode 100644 packages/cli/snap-tests/lint-pnpm-vite-alias/vite.config.ts create mode 100644 packages/cli/src/oxlint-plugin/rules/prefer-vite-plus-imports.ts create mode 100644 packages/cli/src/oxlint-plugin/rules/require-pnpm-vite-alias.ts diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index b776d029fb..f3aeb40aec 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -178,6 +178,7 @@ export default extendConfig( { text: 'Run', link: '/config/run' }, { text: 'Format', link: '/config/fmt' }, { text: 'Lint', link: '/config/lint' }, + { text: 'Lint Rules', link: '/config/lint-rules' }, { text: 'Test', link: '/config/test' }, { text: 'Build', link: '/config/build' }, { text: 'Pack', link: '/config/pack' }, diff --git a/docs/config/lint-rules.md b/docs/config/lint-rules.md new file mode 100644 index 0000000000..510d1dfb95 --- /dev/null +++ b/docs/config/lint-rules.md @@ -0,0 +1,43 @@ +# Vite+ Oxlint Rules + +Vite+ ships a small Oxlint JS plugin for rules that protect Vite+ project conventions. These rules are added by `vp lint --init`, `vp create`, and `vp migrate` through the `vite-plus/oxlint-plugin` entry in `lint.jsPlugins`. + +## vite-plus/prefer-vite-plus-imports {#vite-plus-prefer-vite-plus-imports} + +This rule rewrites direct Vite and Vitest imports to the Vite+ package entrypoints. + +Examples: + +```ts +import { defineConfig } from 'vite-plus'; +import { test } from 'vite-plus/test'; +``` + +Use this rule to keep application code on Vite+ entrypoints after migration. + +## vite-plus/require-pnpm-vite-alias {#vite-plus-require-pnpm-vite-alias} + +This rule protects pnpm monorepos where `pnpm-workspace.yaml` redirects `vite` through a catalog entry to `@voidzero-dev/vite-plus-core`. + +In that setup, an application package that runs Vite through `vp dev`, `vp build`, or `vp preview` must keep a direct `vite` dependency, usually: + +```json +{ + "devDependencies": { + "vite": "catalog:", + "vite-plus": "catalog:" + } +} +``` + +Without the direct `vite` dependency, pnpm has no package-level consumer for the workspace override. Commands such as `vp why vite` can then report upstream `vite` from transitive dependencies instead of the Vite+ core alias, making the override look ineffective. + +The rule only reports when all of these are true: + +- Oxlint is checking the package's `vite.config.*` file +- the nearest package depends on `vite-plus` +- the package has an application script using `vp dev`, `vp build`, or `vp preview` +- the package is missing a direct `vite` dependency +- a parent `pnpm-workspace.yaml` redirects `vite` through a catalog entry to `@voidzero-dev/vite-plus-core` + +Library and tooling packages that only use commands such as `vp pack`, `vp test`, or `vp check` are not considered application packages by this rule. diff --git a/docs/config/lint.md b/docs/config/lint.md index 3d097b6603..b3c30f16cb 100644 --- a/docs/config/lint.md +++ b/docs/config/lint.md @@ -2,6 +2,8 @@ `vp lint` and `vp check` read Oxlint settings from the `lint` block in `vite.config.ts`. See [Oxlint's configuration](https://oxc.rs/docs/guide/usage/linter/config.html) for details. +Vite+ also enables a small set of [Vite+ Oxlint rules](/config/lint-rules) for project conventions that are specific to the Vite+ toolchain. + ## Example ```ts [vite.config.ts] diff --git a/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt b/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt index 34d5ab500c..0af07cdbbe 100644 --- a/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt +++ b/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt @@ -9,7 +9,7 @@ export default defineConfig({ fmt: {}, lint: { jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], - rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + rules: { "vite-plus/prefer-vite-plus-imports": "error", "vite-plus/require-pnpm-vite-alias": "error" }, options: { typeAware: true, typeCheck: true }, }, }); @@ -25,7 +25,7 @@ export default defineConfig({ fmt: {}, lint: { jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], - rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + rules: { "vite-plus/prefer-vite-plus-imports": "error", "vite-plus/require-pnpm-vite-alias": "error" }, options: { typeAware: true, typeCheck: true }, }, run: { diff --git a/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt b/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt index 00f949c269..b6487d77cd 100644 --- a/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt @@ -20,7 +20,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt b/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt index 608076c519..51fce02a7a 100644 --- a/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt +++ b/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt @@ -15,7 +15,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt b/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt index bf72628953..626f25cff4 100644 --- a/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt +++ b/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt @@ -39,7 +39,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt index 1aa424701f..bed396fb5e 100644 --- a/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt @@ -39,7 +39,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-eslint-jsplugins-orphan-strip/snap.txt b/packages/cli/snap-tests-global/migration-eslint-jsplugins-orphan-strip/snap.txt index 1895202e86..a1cc36c5ad 100644 --- a/packages/cli/snap-tests-global/migration-eslint-jsplugins-orphan-strip/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-jsplugins-orphan-strip/snap.txt @@ -34,7 +34,8 @@ export default defineConfig({ "builtin": true }, "rules": { - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "overrides": [ { diff --git a/packages/cli/snap-tests-global/migration-eslint-jsplugins-preserve/snap.txt b/packages/cli/snap-tests-global/migration-eslint-jsplugins-preserve/snap.txt index b36a35cd0e..0712c0d33b 100644 --- a/packages/cli/snap-tests-global/migration-eslint-jsplugins-preserve/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-jsplugins-preserve/snap.txt @@ -49,7 +49,8 @@ export default defineConfig({ }, "rules": { "survives/no-fiction": "warn", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt index cb4a72420f..113b8bc288 100644 --- a/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt @@ -54,7 +54,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt b/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt index a62041e3a1..6423129acc 100644 --- a/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt @@ -55,7 +55,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-plugins-cleanup/snap.txt b/packages/cli/snap-tests-global/migration-eslint-plugins-cleanup/snap.txt index 0275e5301a..1394745af9 100644 --- a/packages/cli/snap-tests-global/migration-eslint-plugins-cleanup/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-plugins-cleanup/snap.txt @@ -50,7 +50,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt b/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt index 0464c8ed89..4022977b64 100644 --- a/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt @@ -49,7 +49,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt b/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt index f172f96835..b30b8d7b98 100644 --- a/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt @@ -46,7 +46,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt b/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt index d616fdeeae..051ead7062 100644 --- a/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt @@ -46,7 +46,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint-type-aware/snap.txt b/packages/cli/snap-tests-global/migration-eslint-type-aware/snap.txt index 18c0ded362..c2847ec043 100644 --- a/packages/cli/snap-tests-global/migration-eslint-type-aware/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-type-aware/snap.txt @@ -44,7 +44,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-eslint/snap.txt b/packages/cli/snap-tests-global/migration-eslint/snap.txt index 636589b8c2..37fb93e61b 100644 --- a/packages/cli/snap-tests-global/migration-eslint/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint/snap.txt @@ -61,7 +61,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt index 067d68aba6..80224fa0cc 100644 --- a/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt @@ -39,7 +39,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt b/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt index bc0f54cc72..5b3b2ebc5b 100644 --- a/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt @@ -40,7 +40,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.ts": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt index 96c9e19d94..d2a142922d 100644 --- a/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt @@ -39,7 +39,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt b/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt index cff5f38ba1..446fa20e80 100644 --- a/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt @@ -21,7 +21,7 @@ export default defineConfig({ } }, fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, server: { port: 3000, }, @@ -80,7 +80,7 @@ export default defineConfig({ } }, fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, server: { port: 3000, }, diff --git a/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt b/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt index 2da336e742..0836960ab8 100644 --- a/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt +++ b/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt @@ -26,7 +26,7 @@ export default defineConfig({ }, pack: tsdownConfig, fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > cat package.json # check package.json @@ -86,7 +86,7 @@ export default defineConfig({ }, pack: tsdownConfig, fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > cat package.json # check package.json diff --git a/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt b/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt index 374eca6e6a..130f269c5a 100644 --- a/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt +++ b/packages/cli/snap-tests-global/migration-husky-catalog-version/snap.txt @@ -47,7 +47,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt b/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt index 86da8a68b4..ffa1a02de4 100644 --- a/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt +++ b/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt @@ -40,7 +40,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt index c3d4d437b4..0df1e9749b 100644 --- a/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt +++ b/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt @@ -112,7 +112,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.@(js|ts|tsx|yml|yaml|md|json|html|toml)": [ "vp fmt --staged", diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt index 0bc94af760..0580e80c99 100644 --- a/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt +++ b/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt @@ -43,7 +43,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { '*.js': 'vp check --fix', }, diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt b/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt index 5a6900d06e..97099dd02d 100644 --- a/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt +++ b/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt @@ -14,7 +14,8 @@ export default { lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt index e24fa6682a..55cb09075b 100644 --- a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt +++ b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt @@ -24,7 +24,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt index 144d97ebb0..0264348157 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt @@ -17,7 +17,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt index 5b8caecfe3..fe9a7435b2 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt @@ -12,7 +12,7 @@ export default defineConfig({ "*": "vp check --fix" }, fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, plugins: [react()], }); diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt index f877dfd403..11d2052861 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt @@ -25,7 +25,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, @@ -166,7 +167,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "warn", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt index 0874f30c30..da1b618cef 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt @@ -17,7 +17,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt b/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt index b140b1981d..0e8c6d7661 100644 --- a/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt +++ b/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt @@ -17,7 +17,8 @@ export default defineConfig({ }, "rules": { "no-console": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "globals": {}, "ignorePatterns": [], diff --git a/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt b/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt index 88d3d51bac..a81f650913 100644 --- a/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt +++ b/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt @@ -20,7 +20,8 @@ export default defineConfig({ lint: { "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt b/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt index 58db2eeebd..17fb5bb76f 100644 --- a/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt @@ -65,7 +65,8 @@ export default defineConfig({ }, "rules": { "no-unused-vars": "error", - "vite-plus/prefer-vite-plus-imports": "error" + "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error" }, "options": { "typeAware": true, diff --git a/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt index 4f54aaf230..844b55d490 100644 --- a/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt @@ -40,7 +40,7 @@ peerDependencyRules: import { defineConfig } from "vite-plus"; export default defineConfig({ - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.ts": "vp fmt" }, diff --git a/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt b/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt index f788ee36e4..8d6817bedb 100644 --- a/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt @@ -44,7 +44,7 @@ export default defineConfig({ staged: { "*": "vp check --fix" }, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, fmt: { semi: true, singleQuote: true, diff --git a/packages/cli/snap-tests-global/migration-prettier/snap.txt b/packages/cli/snap-tests-global/migration-prettier/snap.txt index a9064ccaed..4f57dea20a 100644 --- a/packages/cli/snap-tests-global/migration-prettier/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier/snap.txt @@ -47,7 +47,7 @@ export default defineConfig({ staged: { "*": "vp check --fix" }, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, fmt: { semi: true, singleQuote: true, diff --git a/packages/cli/snap-tests-global/migration-subpath/snap.txt b/packages/cli/snap-tests-global/migration-subpath/snap.txt index f68c634e75..959cb2b1a4 100644 --- a/packages/cli/snap-tests-global/migration-subpath/snap.txt +++ b/packages/cli/snap-tests-global/migration-subpath/snap.txt @@ -29,7 +29,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > git config --local core.hooksPath || echo 'core.hooksPath is not set' # should NOT be set diff --git a/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt b/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt index fbdf9b973a..4965a8dcd7 100644 --- a/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt +++ b/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt @@ -23,7 +23,7 @@ export default defineConfig({ "*": "vp check --fix" }, fmt: {}, - lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error","vite-plus/require-pnpm-vite-alias":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > cat package.json # check package.json diff --git a/packages/cli/snap-tests-global/new-create-vite-migrates-eslint-prettier/snap.txt b/packages/cli/snap-tests-global/new-create-vite-migrates-eslint-prettier/snap.txt index ae1bcd139f..85c3cb605f 100644 --- a/packages/cli/snap-tests-global/new-create-vite-migrates-eslint-prettier/snap.txt +++ b/packages/cli/snap-tests-global/new-create-vite-migrates-eslint-prettier/snap.txt @@ -133,6 +133,7 @@ export default defineConfig({ ], rules: { "vite-plus/prefer-vite-plus-imports": "error", + "vite-plus/require-pnpm-vite-alias": "error", }, }, plugins: [react()], diff --git a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt index 5b1a1d3094..5023514c30 100644 --- a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt +++ b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt @@ -39,7 +39,7 @@ export default defineConfig({ fmt: {}, lint: { jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], - rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + rules: { "vite-plus/prefer-vite-plus-imports": "error", "vite-plus/require-pnpm-vite-alias": "error" }, options: { typeAware: true, typeCheck: true }, }, run: { diff --git a/packages/cli/snap-tests/command-init-inline-config/snap.txt b/packages/cli/snap-tests/command-init-inline-config/snap.txt index 0704eaecd8..96578330e1 100644 --- a/packages/cli/snap-tests/command-init-inline-config/snap.txt +++ b/packages/cli/snap-tests/command-init-inline-config/snap.txt @@ -7,7 +7,7 @@ import { defineConfig } from "vite-plus"; export default defineConfig({ lint: { jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], - rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + rules: { "vite-plus/prefer-vite-plus-imports": "error", "vite-plus/require-pnpm-vite-alias": "error" }, options: { typeAware: true, typeCheck: true }, }, }); diff --git a/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt b/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt index 3804d2905f..e867990ce1 100644 --- a/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt +++ b/packages/cli/snap-tests/create-org-bundled-monorepo/snap.txt @@ -14,7 +14,7 @@ export default defineConfig({ fmt: {}, lint: { jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], - rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + rules: { "vite-plus/prefer-vite-plus-imports": "error", "vite-plus/require-pnpm-vite-alias": "error" }, options: { typeAware: true, typeCheck: true }, }, run: { cache: true }, diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/package.json b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/package.json new file mode 100644 index 0000000000..d278464c07 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/package.json @@ -0,0 +1,14 @@ +{ + "name": "website-with-vite", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vp dev", + "build": "vp build", + "preview": "vp preview" + }, + "devDependencies": { + "vite": "catalog:", + "vite-plus": "catalog:" + } +} diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/vite.config.ts b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/vite.config.ts new file mode 100644 index 0000000000..3210accbf9 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website-with-vite/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({}); diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/package.json b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/package.json new file mode 100644 index 0000000000..aef2a9ef29 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/package.json @@ -0,0 +1,13 @@ +{ + "name": "website", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vp dev", + "build": "vp build", + "preview": "vp preview" + }, + "devDependencies": { + "vite-plus": "catalog:" + } +} diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/vite.config.ts b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/vite.config.ts new file mode 100644 index 0000000000..3210accbf9 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/apps/website/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({}); diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/package.json b/packages/cli/snap-tests/lint-pnpm-vite-alias/package.json new file mode 100644 index 0000000000..bb5ae09e97 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/package.json @@ -0,0 +1,5 @@ +{ + "name": "lint-pnpm-vite-alias", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/package.json b/packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/package.json new file mode 100644 index 0000000000..411696cd44 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/package.json @@ -0,0 +1,14 @@ +{ + "name": "utils", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "vp pack", + "dev": "vp pack --watch", + "test": "vp test", + "check": "vp check" + }, + "devDependencies": { + "vite-plus": "catalog:" + } +} diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/vite.config.ts b/packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/vite.config.ts new file mode 100644 index 0000000000..3210accbf9 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/packages/utils/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({}); diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/pnpm-workspace.yaml b/packages/cli/snap-tests/lint-pnpm-vite-alias/pnpm-workspace.yaml new file mode 100644 index 0000000000..f207c1d5dd --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +packages: + - apps/* + +catalog: + vite: npm:@voidzero-dev/vite-plus-core@latest + vite-plus: latest +overrides: + vite: 'catalog:' diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/snap.txt b/packages/cli/snap-tests/lint-pnpm-vite-alias/snap.txt new file mode 100644 index 0000000000..3505932075 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/snap.txt @@ -0,0 +1,8 @@ +[1]> vp lint --fix apps/website/vite.config.ts # should fail when a pnpm Vite+ app is missing the direct vite alias +apps/website/vite.config.ts:1:1: error vite-plus(require-pnpm-vite-alias): pnpm Vite+ application packages must keep a direct 'vite' dependency so the workspace override resolves to @voidzero-dev/vite-plus-core. + +> vp lint --fix apps/website-with-vite/vite.config.ts && echo 'website-with-vite passed' # should pass when the pnpm Vite+ app keeps the direct vite alias +website-with-vite passed + +> vp lint --fix packages/utils/vite.config.ts && echo 'utils passed' # should ignore non-application packages +utils passed diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/steps.json b/packages/cli/snap-tests/lint-pnpm-vite-alias/steps.json new file mode 100644 index 0000000000..836ab95006 --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/steps.json @@ -0,0 +1,7 @@ +{ + "commands": [ + "vp lint --fix apps/website/vite.config.ts # should fail when a pnpm Vite+ app is missing the direct vite alias", + "vp lint --fix apps/website-with-vite/vite.config.ts && echo 'website-with-vite passed' # should pass when the pnpm Vite+ app keeps the direct vite alias", + "vp lint --fix packages/utils/vite.config.ts && echo 'utils passed' # should ignore non-application packages" + ] +} diff --git a/packages/cli/snap-tests/lint-pnpm-vite-alias/vite.config.ts b/packages/cli/snap-tests/lint-pnpm-vite-alias/vite.config.ts new file mode 100644 index 0000000000..3dcb472b8f --- /dev/null +++ b/packages/cli/snap-tests/lint-pnpm-vite-alias/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + lint: { + jsPlugins: [{ name: 'vite-plus', specifier: 'vite-plus/oxlint-plugin' }], + rules: { + 'vite-plus/require-pnpm-vite-alias': 'error', + }, + }, +}); diff --git a/packages/cli/src/__tests__/oxlint-plugin.spec.ts b/packages/cli/src/__tests__/oxlint-plugin.spec.ts index 45a5d1bb44..044964366a 100644 --- a/packages/cli/src/__tests__/oxlint-plugin.spec.ts +++ b/packages/cli/src/__tests__/oxlint-plugin.spec.ts @@ -1,3 +1,7 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + import { RuleTester } from 'oxlint/plugins-dev'; import { describe, expect, it } from 'vitest'; @@ -6,9 +10,16 @@ import { ensureVitePlusImportRuleDefaults, PREFER_VITE_PLUS_IMPORTS_RULE, PREFER_VITE_PLUS_IMPORTS_RULE_NAME, + REQUIRE_PNPM_VITE_ALIAS_RULE, + REQUIRE_PNPM_VITE_ALIAS_RULE_NAME, VITE_PLUS_OXLINT_PLUGIN_SPECIFIER, } from '../oxlint-plugin-config.js'; -import { preferVitePlusImportsRule, rewriteVitePlusImportSpecifier } from '../oxlint-plugin.js'; +import { + pnpmWorkspaceAliasesViteToVitePlusCore, + preferVitePlusImportsRule, + requirePnpmViteAliasRule, + rewriteVitePlusImportSpecifier, +} from '../oxlint-plugin.js'; describe('oxlint plugin config defaults', () => { it('adds vite-plus js plugin and lint rule defaults', () => { @@ -29,6 +40,7 @@ describe('oxlint plugin config defaults', () => { }, rules: { [PREFER_VITE_PLUS_IMPORTS_RULE]: 'error', + [REQUIRE_PNPM_VITE_ALIAS_RULE]: 'error', }, }); }); @@ -39,6 +51,7 @@ describe('oxlint plugin config defaults', () => { jsPlugins: [VITE_PLUS_OXLINT_PLUGIN_SPECIFIER], rules: { [PREFER_VITE_PLUS_IMPORTS_RULE]: 'off', + [REQUIRE_PNPM_VITE_ALIAS_RULE]: 'warn', eqeqeq: 'warn', }, }), @@ -46,12 +59,37 @@ describe('oxlint plugin config defaults', () => { jsPlugins: [VITE_PLUS_OXLINT_PLUGIN_SPECIFIER], rules: { [PREFER_VITE_PLUS_IMPORTS_RULE]: 'off', + [REQUIRE_PNPM_VITE_ALIAS_RULE]: 'warn', eqeqeq: 'warn', }, }); }); }); +describe('pnpmWorkspaceAliasesViteToVitePlusCore', () => { + it('detects pnpm workspace overrides that redirect vite through a catalog alias', () => { + expect( + pnpmWorkspaceAliasesViteToVitePlusCore(` +catalog: + vite: npm:@voidzero-dev/vite-plus-core@latest +overrides: + vite: "catalog:" +`), + ).toBe(true); + }); + + it('ignores workspaces without a Vite+ vite override', () => { + expect( + pnpmWorkspaceAliasesViteToVitePlusCore(` +catalog: + vite: ^7.0.0 +overrides: + react: catalog: +`), + ).toBe(false); + }); +}); + describe('rewriteVitePlusImportSpecifier', () => { it('rewrites supported vite and vitest specifiers', () => { expect(rewriteVitePlusImportSpecifier('vite')).toBe('vite-plus'); @@ -167,3 +205,100 @@ new RuleTester({ }, ], }); + +function createPnpmWorkspacePackage(options: { hasViteDependency: boolean; packageDir?: string }) { + const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vp-oxlint-pnpm-alias-')); + const packageDir = path.join(workspaceDir, options.packageDir ?? 'apps/website'); + const configFile = path.join(packageDir, 'vite.config.ts'); + + fs.mkdirSync(packageDir, { recursive: true }); + fs.writeFileSync( + configFile, + `import { defineConfig } from 'vite-plus';\n\nexport default defineConfig({});\n`, + ); + fs.writeFileSync( + path.join(workspaceDir, 'pnpm-workspace.yaml'), + [ + 'packages:', + ' - apps/*', + 'catalog:', + ' vite: npm:@voidzero-dev/vite-plus-core@latest', + ' vite-plus: latest', + 'overrides:', + ' vite: "catalog:"', + '', + ].join('\n'), + ); + fs.writeFileSync( + path.join(packageDir, 'package.json'), + JSON.stringify( + { + name: 'website', + scripts: { + dev: 'vp dev', + build: 'tsc && vp build', + preview: 'vp preview', + }, + devDependencies: { + ...(options.hasViteDependency ? { vite: 'catalog:' } : {}), + 'vite-plus': 'catalog:', + }, + }, + null, + 2, + ), + ); + return { configFile, workspaceDir }; +} + +const validPnpmApp = createPnpmWorkspacePackage({ hasViteDependency: true }); +const invalidPnpmApp = createPnpmWorkspacePackage({ hasViteDependency: false }); +const validPnpmLibrary = createPnpmWorkspacePackage({ + hasViteDependency: false, + packageDir: 'packages/utils', +}); +fs.writeFileSync( + path.join(validPnpmLibrary.workspaceDir, 'packages/utils/package.json'), + JSON.stringify( + { + name: 'utils', + scripts: { + dev: 'vp pack --watch', + build: 'vp pack', + }, + devDependencies: { + 'vite-plus': 'catalog:', + }, + }, + null, + 2, + ), +); + +new RuleTester({ + languageOptions: { + sourceType: 'module', + }, +}).run(REQUIRE_PNPM_VITE_ALIAS_RULE_NAME, requirePnpmViteAliasRule, { + valid: [ + { + code: `import { defineConfig } from 'vite-plus';\n\nexport default defineConfig({});`, + filename: validPnpmApp.configFile, + }, + { + code: `import { defineConfig } from 'vite-plus';\n\nexport default defineConfig({});`, + filename: validPnpmLibrary.configFile, + }, + { + code: `export const app = true;`, + filename: path.join(path.dirname(invalidPnpmApp.configFile), 'src/main.ts'), + }, + ], + invalid: [ + { + code: `import { defineConfig } from 'vite-plus';\n\nexport default defineConfig({});`, + errors: [{ messageId: 'requirePnpmViteAlias' }], + filename: invalidPnpmApp.configFile, + }, + ], +}); diff --git a/packages/cli/src/oxlint-plugin-config.ts b/packages/cli/src/oxlint-plugin-config.ts index 5d9c21fffa..8f41930cc6 100644 --- a/packages/cli/src/oxlint-plugin-config.ts +++ b/packages/cli/src/oxlint-plugin-config.ts @@ -6,6 +6,8 @@ export const VITE_PLUS_OXLINT_PLUGIN_NAME = VITE_PLUS_NAME; export const VITE_PLUS_OXLINT_PLUGIN_SPECIFIER = `${VITE_PLUS_NAME}/oxlint-plugin`; export const PREFER_VITE_PLUS_IMPORTS_RULE_NAME = 'prefer-vite-plus-imports'; export const PREFER_VITE_PLUS_IMPORTS_RULE = `${VITE_PLUS_OXLINT_PLUGIN_NAME}/${PREFER_VITE_PLUS_IMPORTS_RULE_NAME}`; +export const REQUIRE_PNPM_VITE_ALIAS_RULE_NAME = 'require-pnpm-vite-alias'; +export const REQUIRE_PNPM_VITE_ALIAS_RULE = `${VITE_PLUS_OXLINT_PLUGIN_NAME}/${REQUIRE_PNPM_VITE_ALIAS_RULE_NAME}`; type JsPluginEntry = NonNullable[number]; @@ -35,8 +37,10 @@ export function ensureVitePlusImportRuleDefaults< } const rules = isRuleRecord(config.rules) ? { ...config.rules } : {}; - if (!(PREFER_VITE_PLUS_IMPORTS_RULE in rules)) { - rules[PREFER_VITE_PLUS_IMPORTS_RULE] = 'error'; + for (const rule of [PREFER_VITE_PLUS_IMPORTS_RULE, REQUIRE_PNPM_VITE_ALIAS_RULE]) { + if (!(rule in rules)) { + rules[rule] = 'error'; + } } return { diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts index f71e1f94db..218df4887d 100644 --- a/packages/cli/src/oxlint-plugin.ts +++ b/packages/cli/src/oxlint-plugin.ts @@ -1,139 +1,19 @@ -import { definePlugin, defineRule } from '@oxlint/plugins'; -import type { Context, ESTree } from '@oxlint/plugins'; +import { definePlugin } from '@oxlint/plugins'; import { PREFER_VITE_PLUS_IMPORTS_RULE_NAME, + REQUIRE_PNPM_VITE_ALIAS_RULE_NAME, VITE_PLUS_OXLINT_PLUGIN_NAME, } from './oxlint-plugin-config.ts'; - -function rewriteVitePlusImportSpecifier(specifier: string): string | null { - if (specifier === 'vite') { - return 'vite-plus'; - } - - if (specifier.startsWith('vite/')) { - return `vite-plus/${specifier.slice('vite/'.length)}`; - } - - if (specifier === 'vitest/config') { - return 'vite-plus'; - } - - if (specifier === 'vitest') { - return 'vite-plus/test'; - } - - if (specifier.startsWith('vitest/')) { - return `vite-plus/test/${specifier.slice('vitest/'.length)}`; - } - - if (specifier === '@vitest/browser') { - return 'vite-plus/test/browser'; - } - - const browserSubpathRewrites: Record = { - '@vitest/browser/context': 'vite-plus/test/browser/context', - '@vitest/browser/client': 'vite-plus/test/client', - '@vitest/browser/locators': 'vite-plus/test/locators', - }; - if (specifier in browserSubpathRewrites) { - return browserSubpathRewrites[specifier]; - } - - for (const [prefix, provider] of [ - ['@vitest/browser-playwright', 'playwright'], - ['@vitest/browser-preview', 'preview'], - ['@vitest/browser-webdriverio', 'webdriverio'], - ] as const) { - if (specifier === prefix) { - return `vite-plus/test/${prefix.slice('@vitest/'.length)}`; - } - - if (specifier === `${prefix}/context`) { - return 'vite-plus/test/browser/context'; - } - - if (specifier === `${prefix}/provider`) { - return `vite-plus/test/browser/providers/${provider}`; - } - } - - return null; -} - -function quoteSpecifier(literal: ESTree.StringLiteral, replacement: string): string { - const quote = literal.raw?.startsWith("'") ? "'" : '"'; - return `${quote}${replacement}${quote}`; -} - -function maybeReportLiteral( - context: Context, - literal: ESTree.Expression | ESTree.TSModuleDeclaration['id'] | null | undefined, -) { - if (!literal || literal.type !== 'Literal' || typeof literal.value !== 'string') { - return; - } - - const replacement = rewriteVitePlusImportSpecifier(literal.value); - if (!replacement) { - return; - } - - context.report({ - node: literal, - messageId: 'preferVitePlusImports', - data: { - from: literal.value, - to: replacement, - }, - fix(fixer) { - return fixer.replaceText(literal, quoteSpecifier(literal, replacement)); - }, - }); -} - -export const preferVitePlusImportsRule = defineRule({ - meta: { - type: 'problem', - docs: { - description: 'Prefer vite-plus module specifiers over vite and vitest packages.', - recommended: true, - url: 'https://github.com/voidzero-dev/vite-plus/issues/1301', - }, - fixable: 'code', - messages: { - preferVitePlusImports: "Use '{{to}}' instead of '{{from}}' in Vite+ projects.", - }, - }, - createOnce(context: Context) { - return { - ImportDeclaration(node) { - maybeReportLiteral(context, node.source); - }, - ExportAllDeclaration(node) { - maybeReportLiteral(context, node.source); - }, - ExportNamedDeclaration(node) { - maybeReportLiteral(context, node.source); - }, - ImportExpression(node) { - maybeReportLiteral(context, node.source); - }, - TSImportType(node) { - maybeReportLiteral(context, node.source); - }, - TSExternalModuleReference(node) { - maybeReportLiteral(context, node.expression); - }, - TSModuleDeclaration(node) { - if (node.global) { - return; - } - maybeReportLiteral(context, node.id); - }, - }; - }, -}); +import { + preferVitePlusImportsRule, + rewriteVitePlusImportSpecifier, +} from './oxlint-plugin/rules/prefer-vite-plus-imports.ts'; +import { + pnpmWorkspaceAliasesViteToVitePlusCore, + requirePnpmViteAliasRule, + shouldRequirePnpmViteAlias, +} from './oxlint-plugin/rules/require-pnpm-vite-alias.ts'; const plugin = definePlugin({ meta: { @@ -141,8 +21,15 @@ const plugin = definePlugin({ }, rules: { [PREFER_VITE_PLUS_IMPORTS_RULE_NAME]: preferVitePlusImportsRule, + [REQUIRE_PNPM_VITE_ALIAS_RULE_NAME]: requirePnpmViteAliasRule, }, }); export default plugin; -export { rewriteVitePlusImportSpecifier }; +export { + pnpmWorkspaceAliasesViteToVitePlusCore, + preferVitePlusImportsRule, + requirePnpmViteAliasRule, + rewriteVitePlusImportSpecifier, + shouldRequirePnpmViteAlias, +}; diff --git a/packages/cli/src/oxlint-plugin/rules/prefer-vite-plus-imports.ts b/packages/cli/src/oxlint-plugin/rules/prefer-vite-plus-imports.ts new file mode 100644 index 0000000000..048454760e --- /dev/null +++ b/packages/cli/src/oxlint-plugin/rules/prefer-vite-plus-imports.ts @@ -0,0 +1,131 @@ +import { defineRule } from '@oxlint/plugins'; +import type { Context, ESTree } from '@oxlint/plugins'; + +function quoteSpecifier(literal: ESTree.StringLiteral, replacement: string): string { + const quote = literal.raw?.startsWith("'") ? "'" : '"'; + return `${quote}${replacement}${quote}`; +} + +function maybeReportLiteral( + context: Context, + literal: ESTree.Expression | ESTree.TSModuleDeclaration['id'] | null | undefined, +) { + if (!literal || literal.type !== 'Literal' || typeof literal.value !== 'string') { + return; + } + + const replacement = rewriteVitePlusImportSpecifier(literal.value); + if (!replacement) { + return; + } + + context.report({ + node: literal, + messageId: 'preferVitePlusImports', + data: { + from: literal.value, + to: replacement, + }, + fix(fixer) { + return fixer.replaceText(literal, quoteSpecifier(literal, replacement)); + }, + }); +} + +export function rewriteVitePlusImportSpecifier(specifier: string): string | null { + if (specifier === 'vite') { + return 'vite-plus'; + } + + if (specifier.startsWith('vite/')) { + return `vite-plus/${specifier.slice('vite/'.length)}`; + } + + if (specifier === 'vitest/config') { + return 'vite-plus'; + } + + if (specifier === 'vitest') { + return 'vite-plus/test'; + } + + if (specifier.startsWith('vitest/')) { + return `vite-plus/test/${specifier.slice('vitest/'.length)}`; + } + + if (specifier === '@vitest/browser') { + return 'vite-plus/test/browser'; + } + + const browserSubpathRewrites: Record = { + '@vitest/browser/context': 'vite-plus/test/browser/context', + '@vitest/browser/client': 'vite-plus/test/client', + '@vitest/browser/locators': 'vite-plus/test/locators', + }; + if (specifier in browserSubpathRewrites) { + return browserSubpathRewrites[specifier]; + } + + for (const [prefix, provider] of [ + ['@vitest/browser-playwright', 'playwright'], + ['@vitest/browser-preview', 'preview'], + ['@vitest/browser-webdriverio', 'webdriverio'], + ] as const) { + if (specifier === prefix) { + return `vite-plus/test/${prefix.slice('@vitest/'.length)}`; + } + + if (specifier === `${prefix}/context`) { + return 'vite-plus/test/browser/context'; + } + + if (specifier === `${prefix}/provider`) { + return `vite-plus/test/browser/providers/${provider}`; + } + } + + return null; +} + +export const preferVitePlusImportsRule = defineRule({ + meta: { + type: 'problem', + docs: { + description: 'Prefer vite-plus module specifiers over vite and vitest packages.', + recommended: true, + url: 'https://github.com/voidzero-dev/vite-plus/issues/1301', + }, + fixable: 'code', + messages: { + preferVitePlusImports: "Use '{{to}}' instead of '{{from}}' in Vite+ projects.", + }, + }, + createOnce(context: Context) { + return { + ImportDeclaration(node) { + maybeReportLiteral(context, node.source); + }, + ExportAllDeclaration(node) { + maybeReportLiteral(context, node.source); + }, + ExportNamedDeclaration(node) { + maybeReportLiteral(context, node.source); + }, + ImportExpression(node) { + maybeReportLiteral(context, node.source); + }, + TSImportType(node) { + maybeReportLiteral(context, node.source); + }, + TSExternalModuleReference(node) { + maybeReportLiteral(context, node.expression); + }, + TSModuleDeclaration(node) { + if (node.global) { + return; + } + maybeReportLiteral(context, node.id); + }, + }; + }, +}); diff --git a/packages/cli/src/oxlint-plugin/rules/require-pnpm-vite-alias.ts b/packages/cli/src/oxlint-plugin/rules/require-pnpm-vite-alias.ts new file mode 100644 index 0000000000..329860990a --- /dev/null +++ b/packages/cli/src/oxlint-plugin/rules/require-pnpm-vite-alias.ts @@ -0,0 +1,166 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { defineRule } from '@oxlint/plugins'; +import type { Context } from '@oxlint/plugins'; + +type PackageJson = { + dependencies?: Record; + devDependencies?: Record; + optionalDependencies?: Record; + peerDependencies?: Record; + scripts?: Record; +}; + +const DEPENDENCY_FIELDS = [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', +] as const; + +const VITE_CONFIG_FILE_RE = /^vite\.config\.[cm]?[jt]s$/; + +function readJsonFile(file: string): unknown { + try { + return JSON.parse(fs.readFileSync(file, 'utf8')); + } catch { + return null; + } +} + +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function isStringRecord(value: unknown): value is Record { + return isObject(value) && Object.values(value).every((entry) => typeof entry === 'string'); +} + +function normalizePackageJson(value: unknown): PackageJson | null { + if (!isObject(value)) { + return null; + } + + const pkg: PackageJson = {}; + for (const field of DEPENDENCY_FIELDS) { + if (isStringRecord(value[field])) { + pkg[field] = value[field]; + } + } + if (isStringRecord(value.scripts)) { + pkg.scripts = value.scripts; + } + return pkg; +} + +function findNearestFile(startDir: string, fileName: string): string | null { + let currentDir = path.resolve(startDir); + while (true) { + const candidate = path.join(currentDir, fileName); + if (fs.existsSync(candidate)) { + return candidate; + } + + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) { + return null; + } + currentDir = parentDir; + } +} + +function isViteConfigFile(filename: string): boolean { + return VITE_CONFIG_FILE_RE.test(path.basename(filename)); +} + +function hasDependency(pkg: PackageJson, name: string): boolean { + return DEPENDENCY_FIELDS.some((field) => pkg[field]?.[name] !== undefined); +} + +function hasVitePlusAppScript(pkg: PackageJson): boolean { + return Object.values(pkg.scripts ?? {}).some((script) => + /(?:^|[;&|]\s*)vp\s+(?:dev|build|preview)(?:\s|$)/.test(script), + ); +} + +export function pnpmWorkspaceAliasesViteToVitePlusCore(workspaceConfig: string): boolean { + const lines = workspaceConfig.split(/\r?\n/); + const catalogViteAliasesCore = lines.some((line) => + /^\s*['"]?vite['"]?\s*:\s*['"]?(?:npm:|workspace:)?@voidzero-dev\/vite-plus-core@?/.test(line), + ); + + return lines.some((line, index) => { + if (!/^\s*['"]?vite['"]?\s*:\s*['"]?catalog:['"]?\s*$/.test(line)) { + return false; + } + + const previousText = lines.slice(Math.max(0, index - 12), index).join('\n'); + return /\boverrides\s*:/.test(previousText) && catalogViteAliasesCore; + }); +} + +export function shouldRequirePnpmViteAlias(filename: string): boolean { + if (!isViteConfigFile(filename)) { + return false; + } + + const packageJsonPath = path.join(path.dirname(filename), 'package.json'); + if (!fs.existsSync(packageJsonPath)) { + return false; + } + + const pkg = normalizePackageJson(readJsonFile(packageJsonPath)); + if ( + !pkg || + !hasDependency(pkg, 'vite-plus') || + hasDependency(pkg, 'vite') || + !hasVitePlusAppScript(pkg) + ) { + return false; + } + + const workspaceConfigPath = findNearestFile(path.dirname(packageJsonPath), 'pnpm-workspace.yaml'); + if (!workspaceConfigPath) { + return false; + } + + let workspaceConfig = ''; + try { + workspaceConfig = fs.readFileSync(workspaceConfigPath, 'utf8'); + } catch { + return false; + } + + return pnpmWorkspaceAliasesViteToVitePlusCore(workspaceConfig); +} + +export const requirePnpmViteAliasRule = defineRule({ + meta: { + type: 'problem', + docs: { + description: + 'Require pnpm Vite+ application packages to keep a direct vite alias dependency.', + recommended: true, + url: 'https://viteplus.dev/config/lint-rules#vite-plus-require-pnpm-vite-alias', + }, + messages: { + requirePnpmViteAlias: + "pnpm Vite+ application packages must keep a direct 'vite' dependency so the workspace override resolves to @voidzero-dev/vite-plus-core.", + }, + }, + createOnce(context: Context) { + return { + Program(node) { + if (!shouldRequirePnpmViteAlias(context.physicalFilename)) { + return; + } + + context.report({ + node, + messageId: 'requirePnpmViteAlias', + }); + }, + }; + }, +});