feat(lint): serve the Oxlint plugin API from vite-plus - #2328
Conversation
✅ Deploy Preview for viteplus-preview canceled.
|
|
✅ Staging deployment successful! Preview: https://viteplus-staging.void.app/ |
Native binary sizes (
|
| 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.68 MiB | 33.69 MiB | +12.00 KiB (+0.03%) |
| NAPI (Linux x64) | gzip -9 | 13.08 MiB | 13.08 MiB | +2.09 KiB (+0.02%) |
vp (macOS ARM64) |
Binary | 7.87 MiB | 7.87 MiB | 0 B (0.00%) |
vp (macOS ARM64) |
gzip -9 | 3.97 MiB | 3.97 MiB | 0 B (0.00%) |
| NAPI (macOS ARM64) | Binary | 41.00 MiB | 41.02 MiB | +16.13 KiB (+0.04%) |
| NAPI (macOS ARM64) | gzip -9 | 17.31 MiB | 17.31 MiB | +2.12 KiB (+0.01%) |
vp (Windows x64) |
Binary | 8.41 MiB | 8.41 MiB | 0 B (0.00%) |
vp (Windows x64) |
gzip -9 | 3.66 MiB | 3.66 MiB | -1 B (-0.00%) |
| NAPI (Windows x64) | Binary | 27.77 MiB | 27.78 MiB | +9.50 KiB (+0.03%) |
| NAPI (Windows x64) | gzip -9 | 10.89 MiB | 10.90 MiB | +2.27 KiB (+0.02%) |
| Trampoline (Windows x64) | Binary | 205.00 KiB | 205.00 KiB | 0 B (0.00%) |
| Trampoline (Windows x64) | gzip -9 | 99.00 KiB | 99.00 KiB | -1 B (-0.00%) |
| Installer (Windows x64) | Binary | 4.45 MiB | 4.45 MiB | 0 B (0.00%) |
| Installer (Windows x64) | gzip -9 | 2.09 MiB | 2.09 MiB | -1 B (-0.00%) |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
5a10b7a to
ee5e4e8
Compare
ee5e4e8 to
c748fa1
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c748fa165d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Seven fixes from review on #2328. Correctness of the rewrite: - A statement that mixes the two `oxlint` surfaces, such as `import { defineConfig, defineRule } from 'oxlint'`, is now left alone. The rewrite replaces the whole specifier, so moving it stripped `defineConfig` of its module. Splitting is the user's call. - `require('@oxlint/plugins')` and `require('oxlint/plugins-dev')` no longer rewrite. The `vite-plus/lint/*` exports are ESM-only, so a rewritten `require()` failed with ERR_PACKAGE_PATH_NOT_EXPORTED. Static import, export, and dynamic `import()` still rewrite, because those resolve through the `import` condition. The published-plugin exemption, which did not work end to end: - `rewritePackageJson` strips `oxlint` before the import rewriter reads the manifests, so `SkipPackages::skip_oxlint` never saw the signal in a real migration. `collectOxlintOwnerDirs` now captures it before the edit and passes the directories through to the rewriter. Note that `skip_tsdown` has the same latent flaw, since `tsdown` is also in `REMOVE_PACKAGES`; this change does not touch it. - `vp lint --fix` rewrote a published plugin's source unconditionally, undoing the exemption the migration had just honored. The rule now checks the nearest manifest, reusing the mtime-keyed cache shape already used for `@nuxt/test-utils`. - The `oxlint` peer entry is no longer stripped from a package that owns the plugin API. A peer is a consumer contract, not a tool the package runs, and removing it left the source importing a package the manifest no longer declared. - `declare module '@oxlint/plugins'` and the `oxlint` forms are preserved, the same way the rule already preserves Vitest-family augmentations. The re-exported types keep their upstream module identity, so a retargeted augmentation stopped merging. Cleanup: - The migration now drops a dead `@oxlint/plugins` devDependency, since nothing imports it after the rewrite. Only from devDependencies: a `dependencies` or `peerDependencies` edge marks a published plugin. Tests: 3 Rust cases for the mixed, `require`, and dynamic-import rules; 8 lint-rule cases for the published-plugin and augmentation guards; and a new `migration_oxlint_published_plugin` snapshot fixture covering the skip.
c748fa1 to
43a9b87
Compare
Seven fixes from review on #2328. Correctness of the rewrite: - A statement that mixes the two `oxlint` surfaces, such as `import { defineConfig, defineRule } from 'oxlint'`, is now left alone. The rewrite replaces the whole specifier, so moving it stripped `defineConfig` of its module. Splitting is the user's call. - `require('@oxlint/plugins')` and `require('oxlint/plugins-dev')` no longer rewrite. The `vite-plus/lint/*` exports are ESM-only, so a rewritten `require()` failed with ERR_PACKAGE_PATH_NOT_EXPORTED. Static import, export, and dynamic `import()` still rewrite, because those resolve through the `import` condition. The published-plugin exemption, which did not work end to end: - `rewritePackageJson` strips `oxlint` before the import rewriter reads the manifests, so `SkipPackages::skip_oxlint` never saw the signal in a real migration. `collectOxlintOwnerDirs` now captures it before the edit and passes the directories through to the rewriter. Note that `skip_tsdown` has the same latent flaw, since `tsdown` is also in `REMOVE_PACKAGES`; this change does not touch it. - `vp lint --fix` rewrote a published plugin's source unconditionally, undoing the exemption the migration had just honored. The rule now checks the nearest manifest, reusing the mtime-keyed cache shape already used for `@nuxt/test-utils`. - The `oxlint` peer entry is no longer stripped from a package that owns the plugin API. A peer is a consumer contract, not a tool the package runs, and removing it left the source importing a package the manifest no longer declared. - `declare module '@oxlint/plugins'` and the `oxlint` forms are preserved, the same way the rule already preserves Vitest-family augmentations. The re-exported types keep their upstream module identity, so a retargeted augmentation stopped merging. Cleanup: - The migration now drops a dead `@oxlint/plugins` devDependency, since nothing imports it after the rewrite. Only from devDependencies: a `dependencies` or `peerDependencies` edge marks a published plugin. Tests: 3 Rust cases for the mixed, `require`, and dynamic-import rules; 8 lint-rule cases for the published-plugin and augmentation guards; and a new `migration_oxlint_published_plugin` snapshot fixture covering the skip.
43a9b87 to
a7180fa
Compare
Registry bridge build (
|
| Package | Version |
|---|---|
vite-plus |
0.0.0-commit.9aa3b7d400901cdee9e1dd21f50a4640788d23f2 |
@voidzero-dev/vite-plus-core |
0.0.0-commit.9aa3b7d400901cdee9e1dd21f50a4640788d23f2 |
Install the Vite+ CLI built from this commit, then migrate a project:
# macOS / Linux
curl -fsSL https://vite.plus | VP_PR_VERSION=2328 bash# Windows (PowerShell)
$env:VP_PR_VERSION="2328"; irm https://vite.plus/ps1 | iexAfter installing, upgrade the current project's vite-plus to this test build with:
vp migrateOr point your package manager at the bridge registry https://registry-bridge.viteplus.dev/:
| Package manager | Registry config |
|---|---|
| npm / pnpm / Bun | .npmrc: registry=https://registry-bridge.viteplus.dev/ |
| Yarn (v2+) | .yarnrc.yml: npmRegistryServer: "https://registry-bridge.viteplus.dev/" |
Then pin the build (vite aliases to vite-plus-core; pnpm can use a catalog, npm an overrides entry):
{
"devDependencies": {
"vite-plus": "0.0.0-commit.9aa3b7d400901cdee9e1dd21f50a4640788d23f2",
"vite": "npm:@voidzero-dev/vite-plus-core@0.0.0-commit.9aa3b7d400901cdee9e1dd21f50a4640788d23f2"
}
}The Docker preview job now fails outright: #14 exporting to GitHub Actions Cache #14 ERROR: error writing layer blob: failed to reserve cache #13 exporting to image ... CANCELED ERROR: failed to build: failed to solve: error writing layer blob The cache export is fatal to the build, so it cancelled the image push. My optimization broke the job it was meant to speed up, and the npm preview for PR #2328 published fine while its Docker image did not. Reverting rather than fixing it. Making it work would need `actions: write` on the one job that installs and executes the preview package, which is the job SR-5 says to keep as unprivileged as possible, and this was the only `type=gha` usage in the repo so there was no working precedent to copy. The benefit was 60-90s of apt on a path that already waits on a human approval measured in minutes to days, so it was buying almost nothing. `ignore-error=true` would keep the build green but the export would keep failing, leaving a dead directive and a stack trace in every log.
Two fixes to `main`, both found by the first real runs of the publishing workflow. It could not be exercised before merge, because `workflow_run` only fires for workflow files already on the default branch. **First, the good news: the design works.** PR #2328 published end to end through the new path with an OIDC token, no admin token involved. `authorize`, `Pkg Preview`, and the sticky comment all succeeded, and `commit.a7180fa85c06fad48` is on the bridge: ``` commit.a7180fa85c06fad48 | pr: .../pull/2328 | at: 2026-08-10T07:35:31.617Z ``` ## 1. Fork PRs could not be resolved at all #2391 (from `liangmiQwQ`) failed in `authorize` with `no open PR of voidzero-dev/vite-plus has head c7e51be…` while that PR was open with exactly that head. `listPullRequestsAssociatedWithCommit` returns **empty** for a fork PR's head commit. Confirmed against the live API: | commit | result | | --- | --- | | #2387 head (same-repo) | returns `#2387` | | #2391 head (fork) | **empty** | So it worked for every case reachable before merge and failed for the only case this feature exists for. `workflow_run.pull_requests` is empty for forks too, which is what sent me to the commit endpoint originally — I swapped one fork-blind source for another. Now resolves via `pulls?state=open&head=<head_owner>:<head_branch>`, both GitHub-signed payload fields. The head-sha match is a separate step so the message distinguishes "no such PR" from "the PR moved on": ``` fork PR 2391 (real failure) -> OK: #2391 labeled=true fork=true stale head -> FAIL: PR #2391 now at c7e51be, built 0000000 no such branch -> FAIL: no open PR from liangmiQwQ:does-not-exist ``` I re-checked the rest of the publishing workflow for the same blind spot. Everything else keys off the PR number or the run id, which are base-repo objects and fork-safe: the post-approval `pulls.get` re-check returns correct state, head and labels for #2391, and the artifact download and the `listWorkflowRunArtifacts` precondition both see that run's 148MB `bridge-packages`. ## 2. The Docker gha cache broke the image push ``` #14 exporting to GitHub Actions Cache #14 ERROR: error writing layer blob: failed to reserve cache #13 exporting to image ... CANCELED ``` The cache export is fatal to the build, so it cancelled the push. I added this in the cleanup pass; it broke the job it was meant to speed up, and #2328's npm preview published while its Docker image did not. Reverted rather than repaired. Making it work needs `actions: write` on the one job that installs and executes the preview package, which is the job SR-5 says to keep unprivileged, and this was the only `type=gha` usage in the repo so there was no working precedent. It was saving 60-90s of apt on a path that already waits on a human approval measured in minutes to days. ## 3. Terminology "Trusted leg" and "build leg" were my own coinage and meant nothing to a reader who was not in the design conversation. The two workflows are now described as **the build workflow** and **the publishing workflow**, and where trust was the point the property is stated rather than encoded in a name. This also surfaced something worth fixing later: `publish-preview.yml` is named "Publish preview build" and no longer publishes anything. Renaming it is the real fix, but the publishing workflow matches it by `name:`, so that has to be a coordinated change. The header says so outright for now. The same terminology fix for the RFC and bridge docs is voidzero-dev/pkg-pr-registry-bridge#93, which also corrects SR-1 for the fork-blind endpoint above. ## After merging Re-label #2391 to get the first genuine fork preview.
Vite+ bundles Oxlint, and `vp migrate` strips a standalone `oxlint` dependency. A project's own Oxlint JS plugins import the authoring API by name. That import then stops resolving, and `vp lint` fails to load the plugin. Adding `@oxlint/plugins` back as a direct dependency fixes it, but pins a second copy against whatever Oxlint the bundled linter runs. It also does not resolve from a plugin file under pnpm's strict layout, unless every package that holds a plugin 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. The API therefore always matches the bundled linter. - `vite-plus/lint/plugins-dev` re-exports `RuleTester`, which lives in `oxlint/plugins-dev` and breaks the same way. The subpath mirrors upstream's, so the mapping stays mechanical and later additions to that entry still fit the name. - `vp migrate` rewrites `@oxlint/plugins` and `oxlint/plugins-dev` in every statement form. It rewrites bare `oxlint` only when the import names a binding outside Oxlint's config surface. `defineConfig`, `OxlintConfig`, and `OxlintOverride` imports therefore stay as they are. Default, namespace, and side-effect imports name no binding, so the migration skips them. - The migration skips a package that declares `oxlint` or `@oxlint/plugins` in `dependencies` or `peerDependencies`. That shape marks a published Oxlint plugin, and its consumers may not run Vite+. - `prefer-vite-plus-imports` repeats the mapping, so the codemod has a standing backstop.
Two PTY fixtures for the `vite-plus/lint/plugins` work: - `lint_oxlint_plugin_api`: a local JS plugin authored against `vite-plus/lint/plugins`. It declares no `@oxlint/plugins` dependency of its own. A reported diagnostic therefore proves the export resolves and loads under `vp lint`, which is the premise of the change. The same case covers the `prefer-vite-plus-imports` autofix for all three legacy authoring specifiers, and shows that the config surface stays clean. - `migration_oxlint_js_plugin_imports`: `vp migrate` rewrites a plugin, a rule module, and a rule test. It leaves `defineConfig` and `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. A relative-path JS plugin has no package name, so the `.oxlintrc.json` merge drops its rules. The snapshot records this with a comment instead of hiding it, so a fix shows up as a snapshot diff.
Seven fixes from review on #2328. Correctness of the rewrite: - A statement that mixes the two `oxlint` surfaces, such as `import { defineConfig, defineRule } from 'oxlint'`, is now left alone. The rewrite replaces the whole specifier, so moving it stripped `defineConfig` of its module. Splitting is the user's call. - `require('@oxlint/plugins')` and `require('oxlint/plugins-dev')` no longer rewrite. The `vite-plus/lint/*` exports are ESM-only, so a rewritten `require()` failed with ERR_PACKAGE_PATH_NOT_EXPORTED. Static import, export, and dynamic `import()` still rewrite, because those resolve through the `import` condition. The published-plugin exemption, which did not work end to end: - `rewritePackageJson` strips `oxlint` before the import rewriter reads the manifests, so `SkipPackages::skip_oxlint` never saw the signal in a real migration. `collectOxlintOwnerDirs` now captures it before the edit and passes the directories through to the rewriter. Note that `skip_tsdown` has the same latent flaw, since `tsdown` is also in `REMOVE_PACKAGES`; this change does not touch it. - `vp lint --fix` rewrote a published plugin's source unconditionally, undoing the exemption the migration had just honored. The rule now checks the nearest manifest, reusing the mtime-keyed cache shape already used for `@nuxt/test-utils`. - The `oxlint` peer entry is no longer stripped from a package that owns the plugin API. A peer is a consumer contract, not a tool the package runs, and removing it left the source importing a package the manifest no longer declared. - `declare module '@oxlint/plugins'` and the `oxlint` forms are preserved, the same way the rule already preserves Vitest-family augmentations. The re-exported types keep their upstream module identity, so a retargeted augmentation stopped merging. Cleanup: - The migration now drops a dead `@oxlint/plugins` devDependency, since nothing imports it after the rewrite. Only from devDependencies: a `dependencies` or `peerDependencies` edge marks a published plugin. Tests: 3 Rust cases for the mixed, `require`, and dynamic-import rules; 8 lint-rule cases for the published-plugin and augmentation guards; and a new `migration_oxlint_published_plugin` snapshot fixture covering the skip.
a7180fa to
9aa3b7d
Compare
🐳 Docker preview imageBuilt from this PR's registry bridge build:
# remove any stale local copy from a previous run, then pull fresh
docker rmi ghcr.io/voidzero-dev/vite-plus:pr-2328 2>/dev/null; docker pull ghcr.io/voidzero-dev/vite-plus:pr-2328Quick check: docker run --rm ghcr.io/voidzero-dev/vite-plus:pr-2328 vp --versionSee docs/guide/docker.md for usage. |
|
@codex review |
`vp check` failed with `unicorn(no-empty-file)`: Oxlint counts a file whose only content is a comment as empty. The file was never needed. `nearestPackageOwnsOxlintApi` walks up from the filename to find the nearest package.json and never reads the file itself, so the RuleTester cases only need a path, not a file on disk. This matches the existing `fixtures/nuxt-test-utils/` fixture, which is a package.json alone while the spec filenames it is referenced by do not exist.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9aa3b7d400
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Five findings, two of them interactions between fixes from the first round.
- Deleting the dead `@oxlint/plugins` devDependency broke a preserved
`require('@oxlint/plugins')`. Round one stopped rewriting `require()`
because the target is ESM-only, which left the direct dependency as the
only resolvable copy under pnpm's strict layout. The deletion is now
gated on a source scan for those CommonJS forms.
- `collectOxlintOwnerDirs` treats `dependencies` or `peerDependencies` as
ownership, but the manifest-preservation check only looked at peers. A
published plugin declaring `oxlint` under `dependencies` kept its source
but lost the edge that provided it. Both checks now match.
- A preserved `catalog:` peer would dangle, because the catalog rewrite
still drops every `REMOVE_PACKAGES` entry. Such a reference is now
resolved to the concrete range before the entry goes away.
- `import plugins = require('@oxlint/plugins')` was still autofixed. It has
require semantics, so it is skipped like plain `require()`.
- `import oxlint, { defineRule } from 'oxlint'` was rewritten, but
`vite-plus/lint/plugins` has no default export. A default or namespace
binding now disqualifies the statement, in both implementations.
Tests: one Rust case for the default-binding guard, three lint-rule cases
for the import-equals and default-binding guards.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f1f9a7578
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Three of five findings were real.
- `export { defineRule } from 'oxlint'` was not rewritten. A named
re-export identifies the surface exactly as an import does, so it now
follows the same rules in both implementations. `export * from 'oxlint'`
names nothing and stays put, as do config-surface re-exports.
- The CommonJS scan that guards the `@oxlint/plugins` deletion matched four
fixed substrings, so `require( '@oxlint/plugins' )` and a line break
before the argument slipped through. It is now a tolerant regex that also
catches `createRequire(...)(...)`, and it errs toward keeping the
dependency: a false positive leaves one unused entry, a false negative
breaks a plugin at load time.
- The migrate fixture records that `oxlint` itself is still deleted while a
config-surface import survives. That gap predates this PR, since those
imports were never rewritten and `oxlint` was always in REMOVE_PACKAGES,
so it is captured with a comment rather than fixed here.
The other two did not reproduce, and both are now pinned by tests:
- `import plugins = require('@oxlint/plugins')` is NOT rewritten by the
Rust rule. tree-sitter does not surface it as a plain `import_statement`
string, so the ESM rules never match it.
- `type C = import('@oxlint/plugins').Context` IS rewritten, and correctly:
the shim re-exports the types, so the type resolves through it.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c73a93c7f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Fourth review round. One real finding, one trivial, one that did not reproduce. `vite-plus/lint/plugins` now ships a CJS build alongside the ESM one, and its export gained a `require` condition. A `.cts` plugin, or a `.ts` one compiled with `module: commonjs`, emits its import as `require()`, which the ESM-only export rejected at runtime even though typechecking passed. `@oxlint/plugins` ships CJS, so the shim can mirror it. `lint/plugins-dev` deliberately stays ESM-only: `oxlint/plugins-dev` is ESM-only upstream, so the shim mirrors exactly what upstream can offer rather than inventing a capability. The CommonJS scan that guards the dependency deletion now also tolerates block comments inside the call, and separately catches the `createRequire` indirection. Its comment previously claimed the latter without doing it. `export const pluginApi = '@oxlint/plugins'` is NOT rewritten, contrary to the report: `inside:` matches the immediate parent, and that string sits under a lexical_declaration rather than directly under the export statement. Pinned by a test so a rule loosened to `stopBy: end` cannot start corrupting data.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 729e7b3a71
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Fifth review round found two more spellings the pre-rewrite scan missed:
a template-literal `require(`@oxlint/plugins`)` and a JSDoc
`@typedef {import('@oxlint/plugins').Context}`, which is a comment and so
invisible to every ast-grep string rule.
Both are symptoms of the same wrong shape. The scan ran BEFORE the import
rewrite, so it had to predict which specifiers the rewriter would leave
behind, and every missed spelling silently deleted a dependency that was
still load-bearing. Three rounds of regex patching did not converge.
The deletion now runs AFTER the rewrite, in `dropDeadOxlintPluginsDependency`.
By then every form the rewriter handles has already become a
`vite-plus/lint/*` specifier, so a plain substring scan for the package name
answers the only question that matters: does anything still need it? That
covers `require()`, import-equals, JSDoc, template literals, and plain
strings without enumerating any of them, and the tolerant-regex machinery
is gone.
Snapshots are unchanged, which is the point: same outcome for the cases that
were already right, and no longer wrong for the ones that were not.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f160c0255a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Sixth review round, three findings, all real.
`export default '@oxlint/plugins'` had its exported DATA VALUE rewritten.
That literal is a direct child of the export statement, so an unconstrained
`inside: export_statement` matched it. The export rules now match the
`source` field, so only a genuine re-export qualifies.
My earlier probe of this claim tested `export const x = '...'` and a literal
nested in an object, both of which sit below a declaration and are safe. It
missed the bare direct-child form, so the disproof was wrong. Pinned now.
Moving the dependency deletion after the rewrite dropped the `needVitePlus`
signal it used to set. A monorepo leaf whose only migration signal was an
`@oxlint/plugins` devDependency would get its imports repointed at
`vite-plus/lint/plugins` without gaining a direct `vite-plus` edge. The edge
decision moves back to `rewritePackageJson`, where the pre-rewrite manifest
is still intact; only the deletion happens later.
The retention scan stopped at nested package.json boundaries, so a
non-workspace example or fixture directory holding a preserved
`require('@oxlint/plugins')` went unscanned while the root dependency it
resolved was deleted. That scan now crosses package boundaries, which is
correct for this question specifically: the nested file resolves the root's
dependency by walking up.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dbde05c23c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Seventh review round, one finding. `@oxlint/plugins` declared only in `optionalDependencies` still has its imports repointed at `vite-plus/lint/plugins`, but it did not set `needVitePlus`, so a standalone project or an isolated leaf could finish with rewritten imports and no declared `vite-plus`. An optional install edge provisions the API the same way a dev one does, so both now feed the same signal, and the post-rewrite cleanup clears whichever of the two declared it.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93c0f22ce2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Vite+ bundles Oxlint, and
vp migratestrips a standaloneoxlintdependency. Your own Oxlint JS plugins import the authoring API by name, so that import stops resolving andvp lintfails to load the plugin:Adding
@oxlint/pluginsback as a direct dependency fixes that, but it pins a second copy against whatever Oxlint the bundled linter runs. cloudflare/kumo#658 pinned=1.73.0while the bundled Oxlint moved on. That copy also does not resolve from a plugin file under pnpm's strict layout unless every package holding a plugin declares it.So this serves the API from vite-plus instead, the same shape as
vitesttovite-plus/test.What it adds
vite-plus/lint/pluginsre-exportsdefineRule,definePlugin, andeslintCompatPluginfrom the@oxlint/pluginscopy vite-plus already depends on. The API always matches the bundled linter, and you pin nothing.vite-plus/lint/plugins-devre-exportsRuleTester. The subpath mirrors upstream's, so the mapping stays mechanical.vp migraterepoints existing imports, andprefer-vite-plus-importskeeps them there.What the rewrite deliberately does not touch
oxlintsurfaces, such asimport { defineConfig, defineRule } from 'oxlint'. The rewrite replaces the whole specifier, so moving it would leave the file invalid.require(). Thevite-plus/lint/*exports are ESM-only. Static import, export, and dynamicimport()all rewrite.defineConfig,OxlintConfig,OxlintOverride), and default, namespace, and side-effect imports, which name no binding.oxlintor@oxlint/pluginsindependenciesorpeerDependencies. That shape marks a published Oxlint plugin whose consumers may run plain Oxlint. Its peer entry is preserved too.Tests
Three PTY snapshot fixtures alongside the Rust and vitest units.
lint_oxlint_plugin_apiproves the export resolves and loads under realvp lint, using a plugin with no@oxlint/pluginsdependency of its own.migration_oxlint_js_plugin_importsandmigration_oxlint_published_plugincover the rewrite and the exemption.324 Rust tests, 988 vitest tests, 8 snapshot cases. Clippy clean.
Two things for a follow-up
skip_tsdownhas the same latent flaw this PR fixes forskip_oxlint:rewritePackageJsonstripstsdownbefore the import rewriter reads the manifests, so the signal is already gone. Left alone here to avoid changing unrelated behaviour.sanitizeMigratedOxlintConfigderives a plugin's rule namespace from its package name. A relative-path JS plugin has none, so the.oxlintrc.jsonmerge drops its rules while keeping thejsPluginsentry: the plugin loads but no rule runs. This hits exactly the repos this PR targets, since in-repo plugins are registered by path. The migrate snapshot records it with a comment so a fix shows up as a diff.