Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ jobs:
- runner: ubuntu-22.04
vsce-target: linux-arm64
arch: arm64
- runner: ubuntu-latest
vsce-target: alpine-x64
arch: x64
- runner: ubuntu-22.04
vsce-target: alpine-arm64
arch: arm64
- runner: macos-15
vsce-target: darwin-x64
arch: x64
Expand All @@ -63,7 +69,7 @@ jobs:
shell: bash
env:
# rslib.config.mts keys the staged `@yuku-parser/binding-*` napi payload
# off this variable (Linux targets get a `-gnu` suffix appended there).
# off this variable (`linux-*` → `-gnu`, `alpine-*` → `linux-*-musl`).
VSCE_TARGET: ${{ matrix.vsce-target }}
steps:
- name: Checkout
Expand All @@ -82,12 +88,13 @@ jobs:
cache-dependency-path: pnpm-lock.yaml

# `supportedArchitectures` must be set BEFORE `pnpm install` so pnpm also
# fetches the cross-target optional `@yuku-parser/binding-*` package (the
# Linux runners' current libc is glibc, matching the `-gnu` bindings the
# linux-* VSIX targets need).
# fetches the cross-target optional `@yuku-parser/binding-*` package.
# musl is always listed so the alpine-* legs get Yuku's `-musl` binding;
# on the glibc Linux legs that is a few extra optional downloads, and the
# macOS/Windows legs are unaffected because pnpm still filters by os.
- name: Install Dependencies
run: |
pnpm config set --location=project --json supportedArchitectures '{"cpu":["current","${{ matrix.arch }}"]}'
pnpm config set --location=project --json supportedArchitectures '{"cpu":["current","${{ matrix.arch }}"],"libc":["current","musl"]}'
pnpm install

- name: Build
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten
- Rstest's upstream VS Code extension deep-imports `quoteFilter` from core to mark exact file filters. The published package does not export that helper, so our copy lives in `stacks/test/vendored/coreInternals.ts` beside the other core internals; keep it byte-identical when syncing filter behavior.
- The fmt stack is an LSP client: one `rs fmt --lsp` server per detected workspace folder, spawned at the **folder root** even when a deeper `rstack.config.*` exists. Deepest-config-wins was removed deliberately — `rs fmt` loads one config from its cwd with no upward walk, so anchoring deeper made the editor disagree with `rs fmt` in a terminal; a subproject that needs its own fmt config becomes its own workspace folder. The stack registers **no** `DocumentFormattingEditProvider`: the client registers the provider from the server's `documentFormattingProvider` capability, and adding one by hand would double-register. A config create/change/delete **restarts** the owning folder's server (the server caches its config for its process lifetime and has no config-change message), which is also why the stack watches `RSTACK_CONFIG_GLOB` itself instead of relying on detection — a detection signature records which config files exist, not their contents. A detection pass keeps healthy servers and restarts failed ones in place (`isFailedFmtState`) — lockfile events notify even when the folder set is unchanged, precisely so a completed install or upgrade is retried without a manual restart. There is no stdin fallback below `SUPPORT_MATRIX.rstack`; that is a version gate, not an omission. **Nested workspace folders are a documented limitation, by decision**: when a folder and its subdirectory are both workspace folders and both detect fmt, the parent's per-folder selector also matches the nested folder's files, and which server VS Code hands the request to is not defined — the supported shape is subprojects as _sibling_ workspace folders (or only the subproject opened), not parent-plus-child. Routing (lint's `WorkspaceDocumentRouter` shape) was considered and deferred. Why all of it: `docs/adr/0002-fmt-lsp-on-user-node-runtime.md`.
- fmt importing `stacks/lint/LanguageServerProcessOwner.ts` is not a refactor across the copies: that file has no lint imports and no lint behaviour, it only owns the native children of one language client — including the ones vscode-languageclient's automatic restart creates, which is exactly the leak an ad-hoc copy would reintroduce. Lint's `ManagedLanguageClient` is _restated_ in `stacks/fmt/index.ts` instead, because importing it from `Rslint.ts` would couple fmt to the lint stack's runtime graph. Keep that line where it is: shared process ownership yes, shared stack runtime no.
- The VSIX is platform-targeted for exactly one reason: the test stack's AST collection loads a native parser binding. Do not add another native dependency — it multiplies the release matrix.
- The VSIX is platform-targeted for exactly one reason: the test stack's AST collection loads a native parser binding. Do not add another native dependency — it multiplies the release matrix. The `alpine-*` legs exist because VS Code installs a platform-specific extension only on an exact target match, so without them VS Code Server on Alpine (musl) cannot install the extension at all; they need Yuku's `-musl` binding, which pnpm installs only when `supportedArchitectures` lists `libc: musl`.
- `shared/nodeResolution.ts` takes its shell, its cwd and its notify callback as options instead of importing `vscode` and a stack's `logger` singleton, unlike its neighbours. That is not stylistic: it keeps `resolveUserNode` a pure decision table over its inputs, which is what makes the case-by-case unit tests possible without a `vscode` stub. It sat in `stacks/test/` until fmt became the second stack running project code on a User Node runtime — the exact condition its old note named — and moved on that trigger, not before. Its host-scoped preflight memo is reset by the shell's restart pass only when **no** consumer stack (`USER_NODE_STACKS`) survives the pass — a single-stack `rstack.fmt.restart` beside a live Rstest controller deliberately keeps the memo, since the survivor's existing workers were built on that decision; the full `rstack.restart` always clears it. `stacks/fmt/binEntry.ts` and `stacks/fmt/status.ts` are separate pure modules for the same testability reason — `stacks/fmt/index.ts` evaluates `vscode`, and `status.ts` pins the fold's invariant (a healthy sibling folder never masks another folder's failure) in a unit test the single-folder E2E fixtures cannot.
- The uniform Node floor deliberately exceeds `@rstest/core`'s own `engines` (`^20.19.0 || >=22.12.0`) and also mirrors the supported rstack line's `engines.node` (`rstack >= 0.7.6`). Do not specialise the floor per project — that was considered and rejected. Why, and what else was rejected: `docs/adr/0001-node-runtime-selection.md`.
- Bun is not a supported worker runtime (it segfaults running `@rstest/core`). If that is ever revisited, gate it on an explicit setting — never on `bun.lock`, since bun-as-package-manager still runs the `rs` bin through its `#!/usr/bin/env node` shebang.
Expand Down
11 changes: 7 additions & 4 deletions packages/vscode/rslib.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ const rootDir = path.dirname(fileURLToPath(import.meta.url));
// collection loads the `yuku-parser` napi binding. Everything else in this
// extension is platform neutral (the Rslint Go binary and its napi parser are
// resolved from the project at runtime and never ship in the VSIX).
// On musl Linux (Alpine), set `VSCE_TARGET=alpine-<arch>` explicitly.
const vsceTarget =
process.env.VSCE_TARGET ?? `${process.platform}-${process.arch}`;
Comment thread
fi3ework marked this conversation as resolved.
// The Linux VSIX targets use glibc, whose Yuku bindings have a `-gnu` suffix.
const yukuBindingSuffix = vsceTarget.startsWith('linux-')
? `${vsceTarget}-gnu`
: vsceTarget;
// Yuku's loader resolves `binding-${platform}-${arch}${libc}`; the Linux VSIX
// targets differ only by libc: `linux-*` is glibc, `alpine-*` is musl.
const LINUX_LIBC: Record<string, string> = { linux: 'gnu', alpine: 'musl' };
const [targetOs, targetArch] = vsceTarget.split('-');
const libc = LINUX_LIBC[targetOs];
const yukuBindingSuffix = libc ? `linux-${targetArch}-${libc}` : vsceTarget;
const yukuRequire = createRequire(require.resolve('yuku-parser'));
// Yuku computes this package name at runtime, so Rspack cannot discover it.
const yukuBindingPath = yukuRequire.resolve(
Expand Down
5 changes: 5 additions & 0 deletions packages/vscode/scripts/packageTargets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
// pnpm config set --location=project --json supportedArchitectures \
// '{"cpu":["current","arm64"]}' && pnpm install
//
// The alpine-* targets need the musl bindings, so also add
// `"libc":["current","musl"]` (and, off Linux, `"os":["current","linux"]`).
//
// CI does exactly that, one runner per target.
import { spawnSync } from 'node:child_process';
import process from 'node:process';

const TARGETS = [
'alpine-arm64',
'alpine-x64',
'darwin-arm64',
'darwin-x64',
'linux-arm64',
Expand Down
Loading