fix: report a usable error when @angular/language-server is missing - #100
fix: report a usable error when @angular/language-server is missing#100nntndfrk wants to merge 2 commits into
Conversation
When `@angular/language-server` is absent, node fails before the server starts and Zed shows a bare `MODULE_NOT_FOUND` stack trace. The trace names neither this extension nor the package the user has to install, so the reader has no way to know what to do next. See nathansbradshaw#97. Route the invocation through a small `node --eval` preamble. The preamble keeps `process.argv` identical to a plain `node <entry>` call, so the server parses its flags unchanged, and it adds two behaviours: - If the literal path does not resolve, fall back to node's own resolver from the worktree root. Node walks up the directory tree, so a workspace that hoists the package to a parent `node_modules` now works without `angular_language_server_path`. - If both attempts fail, print the locations that were tried, the install command, and the version rule, then exit. Resolution stays in node. There is no filesystem check in the extension, so the false negatives from nathansbradshaw#95 do not come back.
|
seems ok at a glance. Though I don't know if @nathansbradshaw may have opinions around the use of --eval here |
|
I do worry that the use of --eval here could unintentionally compromise someone at some point. |
Addresses the review concern that building a `--eval` script could compromise someone by accident later on. The worktree root now reaches the preamble through the ZED_ANGULAR_WORKTREE_ROOT environment variable instead of being inlined, so LOADER_SCRIPT is a compile-time constant. Nothing is templated, formatted, or concatenated into the string node evaluates, which removes the escaping step and the placeholder that a later change could have interpolated an unescaped value into. Any inherited value of the variable is dropped so the shell cannot redirect the lookup. Also load the entry point with `Module._load(entry, null, true)` rather than `require(entry)`. Under `--eval` a plain require leaves `require.main` undefined, so an entry point guarded by `require.main === module` would load and silently do nothing. The current @angular/language-server has no such guard, but that guard is the usual shape for a CLI entry point and the failure would look like a Zed bug.
|
@nathansbradshaw Fair concern. I've changed it so there's nothing to escape. The worktree root is no longer inlined into the script. It's passed as an environment variable instead, so the script is now a static constant: fixed at compile time, never built up from anything at runtime. The placeholder and the escaping step are both gone, so there's no place where a future change could slip an unescaped value in. I also fixed something I noticed while checking. Under Retested every scenario from the PR description plus the new ones against Worth noting: |
|
Can we please cut out the AI slop. Thanks. |
|
I will go ahead and close this because of two thoughts:
So to conclude, if the README isn't doing it's job, let's improve the README. |
Fixes #99.
Problem
When
@angular/language-serveris not in the project, node stops before the server starts. Zed shows aMODULE_NOT_FOUNDstack trace. The trace does not name this extension and does not name the package to install, so the reader cannot act on it. #97 is the same report, answered by hand in the comments.A filesystem check in the extension is not an option. #95 showed that the Zed worktree snapshot excludes
node_modules, so the check gives false negatives. The current code documents this and is right to do so.Approach
Route the invocation through a small
node --evalpreamble, then pass the entry point and the existing flags after--.The preamble leaves
process.argvbyte for byte identical to a plainnode <entry>call, so the server parses its own flags unchanged.It adds two behaviours:
require.resolve('@angular/language-server', { paths: [root] }). Node walks up the directory tree, so a workspace that hoists the package to a parentnode_modulesnow works with noangular_language_server_pathsetting.All resolution stays inside node. The extension performs no filesystem access, so #95 cannot regress.
Before
After
Testing
Built with
cargo build --release --target wasm32-wasip1.cargo fmt --checkis clean.The shipped preamble was extracted from the built source and driven with a real LSP
initializerequest against@angular/language-server21.1.5 on node v24.16.0:initializeresponds withcapabilitiesinitializeresponds withcapabilitiesScenario 1 confirms no behaviour change on the existing happy path. Scenario 2 is new.
parseCommandLinein@angular/language-serverscans argv withindexOf, and the preamble preserves argv positions anyway, so flag parsing is unaffected.Notes
extension.tomlorCargo.tomlversions, since releases look like separate commits in this repo. Tell me if you want the bump included here.