Skip to content

feat(lint): serve the Oxlint plugin API from vite-plus - #2328

Draft
fengmk2 wants to merge 2 commits into
mainfrom
feat/lint-plugins-export
Draft

feat(lint): serve the Oxlint plugin API from vite-plus#2328
fengmk2 wants to merge 2 commits into
mainfrom
feat/lint-plugins-export

Conversation

@fengmk2

@fengmk2 fengmk2 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Vite+ bundles Oxlint, and vp migrate strips a standalone oxlint dependency. A project's own Oxlint JS plugins import the authoring API by name, so that import stops resolving after migration and vp lint fails to load the plugin:

Failed to parse oxlint configuration file.
  x Failed to load JS plugin: ./lint/my-plugin.js
  |   Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'oxlint' imported from .../lint/my-plugin.js

Adding @oxlint/plugins back as a direct dependency fixes it, but pins a second copy against whatever Oxlint the bundled linter runs (cloudflare/kumo#658 ended up pinning =1.73.0 while the bundled Oxlint moved on), and it is not resolvable from a plugin file under pnpm's strict layout unless every package hosting one declares it. So this exposes the API from vite-plus instead, the same shape as vitest to vite-plus/test.

  • vite-plus/lint/plugins re-exports defineRule, definePlugin, and eslintCompatPlugin from the @oxlint/plugins copy vite-plus already carries as a real dependency, so the API always matches the bundled linter and there is nothing for the user to keep in sync.
  • vite-plus/lint/rule-tester re-exports RuleTester, which moved to oxlint/plugins-dev and breaks the same way. Kept separate so the authoring API does not pull in a test-only utility.
  • vp migrate rewrites @oxlint/plugins and oxlint/plugins-dev unconditionally, and bare oxlint only when the import names a binding outside Oxlint's config surface, so defineConfig / OxlintConfig / OxlintOverride imports are left alone. Default, namespace, and side-effect imports carry no discriminating name and are skipped rather than guessed at. Discriminating on the 11 config-surface names rather than an allowlist of ~60 plugin type names keeps the list small and stable, and an unrecognized name errs toward fixing the breakage.
  • Packages declaring oxlint or @oxlint/plugins in dependencies or peerDependencies are skipped entirely (new SkipPackages::skip_oxlint): that shape marks a published Oxlint plugin whose consumers may not be running Vite+. A devDependency is not a signal, since that is just how a project's own in-repo plugin gets its types.
  • prefer-vite-plus-imports mirrors the mapping so the codemod has a standing backstop instead of being one-shot.

Tests

Two PTY snapshot fixtures alongside the Rust and vitest units:

  • lint_oxlint_plugin_api: a local JS plugin authored against vite-plus/lint/plugins with no @oxlint/plugins dependency of its own. A reported diagnostic proves the export resolves and loads under real vp lint, which is the premise of the whole change. The same case covers the autofix on all three legacy specifiers and that the config surface stays clean.
  • migration_oxlint_js_plugin_imports: vp migrate rewriting a plugin, a rule module, and a rule test, while leaving defineConfig / OxlintOverride alone.

321 Rust tests pass including 8 new ones, clippy clean; 946 vitest tests pass. Two unrelated failures reproduce identically on the base commit in the same worktree: packages/core/__tests__/binding-resolution-layout.spec.ts (local install layout) and a batch of migration snapshot cases that time out when many run concurrently, each doing a real install.

A pre-existing gap the migrate snapshot records

sanitizeMigratedOxlintConfig derives a plugin's rule namespace from its package name, so a relative-path JS plugin has none and every rule under its namespace is dropped during the .oxlintrc.json merge. The jsPlugins entry itself survives, so the plugin loads but none of its rules run. This is untouched by this PR and worth its own fix: it bites exactly the repos this PR is aimed at, since in-repo plugins are registered by path. Captured in the snapshot with a comment rather than papered over, so a fix shows up as a diff.

One deliberate omission: @oxlint/plugins is not added to REMOVE_PACKAGES. Removal there is unconditional across all dependency groups, so it would strip the peer dep from published Oxlint plugins. A leftover unused devDependency after the rewrite is inert.

Vite+ bundles Oxlint, and `vp migrate` strips a standalone `oxlint`
dependency. A project's own Oxlint JS plugins import the authoring API by
name, so that import stops resolving and `vp lint` fails to load the
plugin. Adding `@oxlint/plugins` back as a direct dependency works, but
pins a second copy against whatever Oxlint the bundled linter runs, and
it is not resolvable from a plugin file under pnpm's strict layout unless
every package hosting one declares it.

Expose the API from vite-plus instead, and repoint the imports at it:

- `vite-plus/lint/plugins` re-exports `defineRule`, `definePlugin`, and
  `eslintCompatPlugin` from the `@oxlint/plugins` copy vite-plus already
  depends on, so the API always matches the bundled linter.
- `vite-plus/lint/rule-tester` re-exports `RuleTester`, which moved to
  `oxlint/plugins-dev` and breaks the same way.
- `vp migrate` rewrites `@oxlint/plugins` and `oxlint/plugins-dev`
  unconditionally, and bare `oxlint` only when the import names a binding
  outside Oxlint's config surface, so `defineConfig` / `OxlintConfig` /
  `OxlintOverride` imports are left alone. Default, namespace, and
  side-effect imports carry no discriminating name and are skipped.
- Packages declaring `oxlint` or `@oxlint/plugins` in `dependencies` or
  `peerDependencies` are skipped entirely: that shape marks a published
  Oxlint plugin whose consumers may not be running Vite+.
- `prefer-vite-plus-imports` mirrors the mapping so the codemod has a
  standing backstop.
@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for viteplus-preview canceled.

Name Link
🔨 Latest commit 5a10b7a
🔍 Latest deploy log https://app.netlify.com/projects/viteplus-preview/deploys/6a7202476f19e90008c695e6

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ Staging deployment successful!

Preview: https://viteplus-staging.void.app/
Commit: 5a10b7a

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Native binary sizes (5a10b7a)

Final release artifacts built by the canonical build-upstream and build-windows-cli actions.

Artifact Format Base PR Change
vp (Linux x64) Binary 10.52 MiB 10.52 MiB 0 B (0.00%)
vp (Linux x64) gzip -9 4.55 MiB 4.55 MiB 0 B (0.00%)
NAPI (Linux x64) Binary 33.66 MiB 33.66 MiB +4.00 KiB (+0.01%)
NAPI (Linux x64) gzip -9 13.05 MiB 13.05 MiB +844 B (+0.01%)
vp (macOS ARM64) Binary 7.84 MiB 7.84 MiB 0 B (0.00%)
vp (macOS ARM64) gzip -9 3.96 MiB 3.96 MiB 0 B (0.00%)
NAPI (macOS ARM64) Binary 40.95 MiB 40.95 MiB 0 B (0.00%)
NAPI (macOS ARM64) gzip -9 17.26 MiB 17.26 MiB +273 B (+0.00%)
vp (Windows x64) Binary 8.43 MiB 8.43 MiB 0 B (0.00%)
vp (Windows x64) gzip -9 3.67 MiB 3.67 MiB -2 B (-0.00%)
NAPI (Windows x64) Binary 27.81 MiB 27.81 MiB +4.00 KiB (+0.01%)
NAPI (Windows x64) gzip -9 10.88 MiB 10.88 MiB +323 B (+0.00%)
Trampoline (Windows x64) Binary 203.00 KiB 203.00 KiB 0 B (0.00%)
Trampoline (Windows x64) gzip -9 97.91 KiB 97.91 KiB +1 B (+0.00%)
Installer (Windows x64) Binary 4.46 MiB 4.46 MiB 0 B (0.00%)
Installer (Windows x64) gzip -9 2.09 MiB 2.09 MiB +1 B (+0.00%)

Two PTY fixtures for the `vite-plus/lint/plugins` work:

- `lint_oxlint_plugin_api`: a local JS plugin authored against
  `vite-plus/lint/plugins` with no `@oxlint/plugins` dependency of its own.
  A reported diagnostic proves the export resolves and loads under
  `vp lint`, which is the whole premise of the change. The same case
  covers the `prefer-vite-plus-imports` autofix for all three legacy
  authoring specifiers, and that the config surface stays clean.
- `migration_oxlint_js_plugin_imports`: `vp migrate` rewriting a plugin,
  a rule module, and a rule test, while leaving `defineConfig` /
  `OxlintOverride` imports alone.

The migrate snapshot also records a pre-existing gap unrelated to this
change: `sanitizeMigratedOxlintConfig` derives a plugin's rule namespace
from its package name, so rules belonging to a relative-path JS plugin are
dropped during the `.oxlintrc.json` merge. Captured with a comment rather
than papered over, so a fix shows up as a snapshot diff.
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​oxlint/​plugins@​1.77.0781009996100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant