Skip to content

feat: support Vite+ detection and per-tool sources - #259

Open
fengmk2 wants to merge 12 commits into
oxc-project:mainfrom
fengmk2:feat/vite-plus-lsp
Open

fengmk2 wants to merge 12 commits into
oxc-project:mainfrom
fengmk2:feat/vite-plus-lsp

Conversation

@fengmk2

@fengmk2 fengmk2 commented Sep 12, 2026

Copy link
Copy Markdown

The current integration starts the legacy vite-plus/bin/oxlint and vite-plus/bin/oxfmt wrappers. These paths fail when an installation no longer includes the wrappers. The extension now starts vp lint --lsp or vp fmt --lsp from the selected package directory.

By default, detection checks the opened worktree and its ancestors through the first monorepo root. A direct vite-plus dependency takes priority over standalone oxlint or oxfmt. The resolver checks local installations before PATH. If the selected vp executable is missing, startup stops with an installation hint. Standalone selection supports local and hoisted installations, with the extension's managed installation as a fallback.

Users can set binarySource to auto, vite-plus, or oxc under lsp.<tool>.initialization_options.settings. An optional vpPath in the same object selects a specific vp executable. For source selection, lsp.<tool>.settings overrides matching values. binary.path overrides keep priority, with optional binary.arguments. Source changes require a language server restart.

Oxlint startup comparison

This diagram shows default source selection, without binary.path or vpPath overrides. Oxfmt follows the same selection process and uses vp fmt --lsp.

flowchart LR
    subgraph Before["Before"]
        direction TB
        B0["Read the opened worktree's package.json"] --> B1["Choose a declared package:<br/>oxlint before vite-plus"]
        B1 --> B2{"Dependency found?"}
        B2 -->|Yes| B3["Select its bin/oxlint script"]
        B2 -->|No| B4["Install or update<br/>extension-managed Oxlint"]
        B3 --> B5["node selected-script --lsp"]
        B4 --> B5
    end

    subgraph After["After"]
        direction TB
        A0["Inspect the worktree and ancestors<br/>through the monorepo root"] --> A1{"Declares vite-plus?"}
        A1 -->|Yes| A2["Resolve local vp,<br/>otherwise search PATH"]
        A2 --> A3{"vp found?"}
        A3 -->|Yes| A4["vp lint --lsp<br/>from the declaring package"]
        A3 -->|No| A5["Stop with an installation hint"]
        A1 -->|No| A6{"Declared standalone Oxlint<br/>installation found?"}
        A6 -->|Yes| A8["node selected-script --lsp"]
        A6 -->|No| A7["Install or update<br/>extension-managed Oxlint"]
        A7 --> A8
    end
Loading
Zed submission note

Copy the note below into the zed-industries/extensions update PR. Update the source link to the submitted release commit.

This update starts vp lint --lsp and vp fmt --lsp without depending on the legacy tool wrappers. The resolver also supports local, hoisted, and extension-managed installations of standalone oxlint and oxfmt.

The resolver must find the declaring package when a user opens a subpackage of a monorepo. The Worktree API cannot read ancestor manifests or inspect executable metadata. The extension therefore runs the bundled src/binary_resolver/inspect.js through Zed's selected Node runtime.

The script checks manifests and installed package entries in the opened directory and its ancestors. It stops at the first workspace marker or the filesystem root. Workspace markers are pnpm-workspace.yaml, lerna.json, and a package.json with a workspaces field. It also checks explicit vpPath values and vp candidates on PATH, including symlink targets and known executable shims. These reads can extend outside the opened worktree.

The inspection script reads files and returns JSON. It does not write files, execute project code, or make network requests. These properties describe the script; the capability does not enforce them.

The manifest uses command = "*" because node_binary_path() returns an absolute path that varies between installations. The matcher cannot match this path by executable name. The fixed mode arguments describe the intended calls, but the wildcard after -e permits arbitrary script text.

Haxe uses the same Node invocation pattern. Zed accepted its discovery script as an exception in PR #5611. Our script also checks ancestors and executable metadata, so we request a separate decision for this scope.

We request an exception under the publishing prerequisites for this filesystem discovery helper. Removing it would require reduced discovery support or a new resolution API. If an existing supported API can provide this behavior, please direct us to it.

This implements the detection RFC and follows oxc-vscode#384.

Comment thread extension.toml Outdated
Comment on lines +9 to +24
# Zed selects an absolute Node path at runtime. The resolver reads project
# manifests and executable metadata through Node because WASI cannot read them.
[[capabilities]]
kind = "process:exec"
command = "*"
args = ["-e", "*", "--", "ancestors", "*", "*"]

[[capabilities]]
kind = "process:exec"
command = "*"
args = ["-e", "*", "--", "executable", "*", "*"]

[[capabilities]]
kind = "process:exec"
command = "*"
args = ["-e", "*", "--", "global", "*", "vp"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we know if Zed will reject the extension for doing this wildcard? Is there any better option here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a close accepted precedent. Haxe uses command = "*" to run an embedded discovery script through node_binary_path(). Its maintainer initially requested changes, then approved an exception. That decision does not guarantee acceptance of our helper. Implementation, review.

The wildcard handles the absolute Node path that Zed selects at runtime. Zed matches complete command strings or "*", so command = "node" does not match that path. Calling node through PATH would still leave the filesystem workaround. Matcher.

Our script reads ancestor manifests, installed package entries, and executable metadata. These reads extend beyond Haxe's directory scan. Our argument patterns are more specific, but the wildcard script argument does not enforce filesystem limits.

Using only the Worktree API would lose ancestor discovery and executable inspection. I propose documenting the helper in extension.toml and requesting an exception from Zed maintainers for this access scope.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll try reducing the permissions to see how it affects the functionality.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@connorshea Since src/binary_resolver/inspect.js can be reviewed, I suggest keeping this permission request for now and adjusting it based on the feedback during the plugin submission process. If it is indeed not allowed, we can switch to an approach that does not require the permission.

@fengmk2

fengmk2 commented Sep 16, 2026

Copy link
Copy Markdown
Author

For further assessment, here is the expected impact of removing the new process:exec permissions and replacing the inspection helper with Worktree APIs.

This requires a resolver refactor. Removing the declarations alone would make the current inspection calls fail.

Feature Expected behavior without the inspection helper
Lint diagnostics, fixes, and formatting Retain the language-server features.
Managed standalone oxlint and oxfmt Retain installation and updates through Zed's npm APIs. These still require the separate npm:install capability.
Local standalone dependencies Read the opened folder's package.json and infer the conventional local entry path, as before this PR.
Local Vite+ support Detect a direct dependency in the opened folder and start its known node_modules/vite-plus/bin/vp entry with lint --lsp or fmt --lsp. This does not require the legacy tool wrappers.
Per-tool source selection and settings Retain binarySource, configuration forwarding, and Vite+ nested-configuration defaults.
Explicit command overrides Retain binary.path, binary.arguments, and binary.env for manual configuration.
Global vp discovery Use worktree.which("vp") for basic lookup. This uses the worktree's shell PATH and does not reproduce our custom search and shim handling.
Ancestor declarations and hoisted installations Lose automatic discovery above the opened folder. Users must open the appropriate package folder or configure the command explicitly.
Arbitrary vpPath formats Lose automatic classification of Node entries, native executables, symlinks, and npm/pnpm shims. Explicit commands can cover these installations.
Installation checks before startup Reduce package identity, file metadata, and executable checks. A declared dependency would generally select an inferred path.

For example, opening repo/ could detect its local Vite+ dependency. Opening repo/packages/app/ would not automatically find the declaration and installation in repo/.

The launcher cleanup logic is separate from filesystem inspection. Removing these permissions does not require removing that cleanup logic.

Zed exposes worktree reads and executable lookup through its standard API. The capability documentation distinguishes helper execution from npm installation.

This gives us a possible fallback if Zed declines the current filesystem helper. It retains core LSP functionality and conventional local installations, with manual configuration for more complex layouts. This is a design assessment; the alternative is not implemented.

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.

2 participants