From f0cd1b650307de571bdc24f7698c824306359ba6 Mon Sep 17 00:00:00 2001 From: fi3ework Date: Thu, 8 Oct 2026 18:07:54 +0800 Subject: [PATCH 1/3] feat(vscode): publish alpine-x64 and alpine-arm64 VSIX targets VS Code installs a platform-specific extension only on an exact target match, so VS Code Server on Alpine (musl) hosts could not install rstack.rstack at all. Two more release legs ship the musl yuku-parser binding, which rslib.config.mts now selects for alpine-* targets. --- .github/workflows/release.yml | 27 ++++++++++++--- packages/vscode/AGENTS.md | 2 +- packages/vscode/rslib.config.mts | 40 ++++++++++++++++++---- packages/vscode/scripts/packageTargets.mjs | 13 ++++++- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7dcb353..b870290 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,6 +46,14 @@ jobs: - runner: ubuntu-22.04 vsce-target: linux-arm64 arch: arm64 + - runner: ubuntu-latest + vsce-target: alpine-x64 + arch: x64 + libc: musl + - runner: ubuntu-22.04 + vsce-target: alpine-arm64 + arch: arm64 + libc: musl - runner: macos-15 vsce-target: darwin-x64 arch: x64 @@ -63,7 +71,8 @@ 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 (it maps `linux-*` to the `-gnu` binding and + # `alpine-*` to the `linux-*-musl` one). VSCE_TARGET: ${{ matrix.vsce-target }} steps: - name: Checkout @@ -82,12 +91,22 @@ 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 + # 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). + # linux-* VSIX targets need; the alpine-* legs set `matrix.libc: musl` to + # also fetch the `-musl` bindings, while the other legs keep libc unset. - name: Install Dependencies + env: + ARCH: ${{ matrix.arch }} + LIBC: ${{ matrix.libc }} run: | - pnpm config set --location=project --json supportedArchitectures '{"cpu":["current","${{ matrix.arch }}"]}' + if [ -n "$LIBC" ]; then + supported="{\"cpu\":[\"current\",\"$ARCH\"],\"libc\":[\"current\",\"$LIBC\"]}" + else + supported="{\"cpu\":[\"current\",\"$ARCH\"]}" + fi + echo "supportedArchitectures: $supported" + pnpm config set --location=project --json supportedArchitectures "$supported" pnpm install - name: Build diff --git a/packages/vscode/AGENTS.md b/packages/vscode/AGENTS.md index 02cc2b0..51353b2 100644 --- a/packages/vscode/AGENTS.md +++ b/packages/vscode/AGENTS.md @@ -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 matrix has eight legs, including `alpine-x64` / `alpine-arm64`: VS Code installs a platform-specific extension only on an exact target match, so without them VS Code Server on Alpine (musl) could not install the extension at all. `rslib.config.mts` maps each target to its Yuku binding — `linux-*` → `linux-*-gnu`, `alpine-*` → `linux-*-musl` — and the Alpine release legs add `libc: musl` to pnpm's `supportedArchitectures` so that binding is installed. A local build without `VSCE_TARGET` on musl Linux detects it from the missing `glibcVersionRuntime` in Node's diagnostic report. - `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. diff --git a/packages/vscode/rslib.config.mts b/packages/vscode/rslib.config.mts index 03dcb28..c67c9d9 100644 --- a/packages/vscode/rslib.config.mts +++ b/packages/vscode/rslib.config.mts @@ -12,18 +12,46 @@ 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). -const vsceTarget = - process.env.VSCE_TARGET ?? `${process.platform}-${process.arch}`; -// The Linux VSIX targets use glibc, whose Yuku bindings have a `-gnu` suffix. -const yukuBindingSuffix = vsceTarget.startsWith('linux-') - ? `${vsceTarget}-gnu` - : vsceTarget; +const vsceTarget = process.env.VSCE_TARGET ?? hostVsceTarget(); +const yukuBindingSuffix = yukuBindingSuffixFor(vsceTarget); const yukuRequire = createRequire(require.resolve('yuku-parser')); // Yuku computes this package name at runtime, so Rspack cannot discover it. const yukuBindingPath = yukuRequire.resolve( `@yuku-parser/binding-${yukuBindingSuffix}`, ); +/** + * The VSIX target of the machine running the build, used when `VSCE_TARGET` + * is unset (local builds and E2E). On musl `process.platform` is still + * `linux`; Node's diagnostic report carries `header.glibcVersionRuntime` only + * when the running libc exports `gnu_get_libc_version`, so its absence on + * Linux means musl (Alpine). A glibc compatibility layer can defeat this — + * set `VSCE_TARGET` explicitly there. + */ +function hostVsceTarget(): string { + const { platform, arch } = process; + if (platform !== 'linux') return `${platform}-${arch}`; + const report = process.report.getReport() as { + header?: { glibcVersionRuntime?: string }; + }; + return report.header?.glibcVersionRuntime + ? `linux-${arch}` + : `alpine-${arch}`; +} + +/** + * Maps a VSIX target to the suffix of its `@yuku-parser/binding-*` package, + * which is also the directory Yuku's loader looks up at runtime + * (`binding-${platform}-${arch}${libc}`): `linux-*` targets are glibc + * (`-gnu`), `alpine-*` targets are musl Linux (`linux-*-musl`). + */ +function yukuBindingSuffixFor(target: string): string { + const [platform, arch] = target.split('-'); + if (platform === 'linux') return `linux-${arch}-gnu`; + if (platform === 'alpine') return `linux-${arch}-musl`; + return target; +} + /** * Packages that are always resolved from the user's project at runtime, never * bundled: the extension ships types only. diff --git a/packages/vscode/scripts/packageTargets.mjs b/packages/vscode/scripts/packageTargets.mjs index 39512a7..10ea64e 100644 --- a/packages/vscode/scripts/packageTargets.mjs +++ b/packages/vscode/scripts/packageTargets.mjs @@ -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', @@ -26,7 +31,13 @@ const TARGETS = [ const args = process.argv.slice(2); const requested = args.filter((arg) => !arg.startsWith('-')); const all = args.includes('--all'); -const hostTarget = `${process.platform}-${process.arch}`; +// Same host detection as `hostVsceTarget()` in rslib.config.mts: musl Linux +// reports no `glibcVersionRuntime` and maps to the alpine-* target. +const hostTarget = + process.platform === 'linux' && + !process.report.getReport().header?.glibcVersionRuntime + ? `alpine-${process.arch}` + : `${process.platform}-${process.arch}`; const targets = all ? TARGETS : requested.length > 0 ? requested : [hostTarget]; From e2a94d4012d0ccd2eb2fcefd7ee2ea7f3394f20d Mon Sep 17 00:00:00 2001 From: fi3ework Date: Thu, 8 Oct 2026 18:28:37 +0800 Subject: [PATCH 2/3] refactor(vscode): drop local musl detection and inline the alpine target mapping The review found no caller for host musl detection: the release matrix always sets VSCE_TARGET and CI builds run on glibc runners. The binding-suffix mapping and the release install step are now one expression each. --- .github/workflows/release.yml | 24 +++--------- packages/vscode/AGENTS.md | 2 +- packages/vscode/rslib.config.mts | 45 ++++++---------------- packages/vscode/scripts/packageTargets.mjs | 8 +--- 4 files changed, 19 insertions(+), 60 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b870290..b18e402 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,11 +49,9 @@ jobs: - runner: ubuntu-latest vsce-target: alpine-x64 arch: x64 - libc: musl - runner: ubuntu-22.04 vsce-target: alpine-arm64 arch: arm64 - libc: musl - runner: macos-15 vsce-target: darwin-x64 arch: x64 @@ -71,8 +69,7 @@ jobs: shell: bash env: # rslib.config.mts keys the staged `@yuku-parser/binding-*` napi payload - # off this variable (it maps `linux-*` to the `-gnu` binding and - # `alpine-*` to the `linux-*-musl` one). + # off this variable (`linux-*` → `-gnu`, `alpine-*` → `linux-*-musl`). VSCE_TARGET: ${{ matrix.vsce-target }} steps: - name: Checkout @@ -91,22 +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; the alpine-* legs set `matrix.libc: musl` to - # also fetch the `-musl` bindings, while the other legs keep libc unset. + # 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 - env: - ARCH: ${{ matrix.arch }} - LIBC: ${{ matrix.libc }} run: | - if [ -n "$LIBC" ]; then - supported="{\"cpu\":[\"current\",\"$ARCH\"],\"libc\":[\"current\",\"$LIBC\"]}" - else - supported="{\"cpu\":[\"current\",\"$ARCH\"]}" - fi - echo "supportedArchitectures: $supported" - pnpm config set --location=project --json supportedArchitectures "$supported" + pnpm config set --location=project --json supportedArchitectures '{"cpu":["current","${{ matrix.arch }}"],"libc":["current","musl"]}' pnpm install - name: Build diff --git a/packages/vscode/AGENTS.md b/packages/vscode/AGENTS.md index 51353b2..f394e08 100644 --- a/packages/vscode/AGENTS.md +++ b/packages/vscode/AGENTS.md @@ -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 matrix has eight legs, including `alpine-x64` / `alpine-arm64`: VS Code installs a platform-specific extension only on an exact target match, so without them VS Code Server on Alpine (musl) could not install the extension at all. `rslib.config.mts` maps each target to its Yuku binding — `linux-*` → `linux-*-gnu`, `alpine-*` → `linux-*-musl` — and the Alpine release legs add `libc: musl` to pnpm's `supportedArchitectures` so that binding is installed. A local build without `VSCE_TARGET` on musl Linux detects it from the missing `glibcVersionRuntime` in Node's diagnostic report. +- 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. diff --git a/packages/vscode/rslib.config.mts b/packages/vscode/rslib.config.mts index c67c9d9..7c861aa 100644 --- a/packages/vscode/rslib.config.mts +++ b/packages/vscode/rslib.config.mts @@ -12,46 +12,23 @@ 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). -const vsceTarget = process.env.VSCE_TARGET ?? hostVsceTarget(); -const yukuBindingSuffix = yukuBindingSuffixFor(vsceTarget); +// On musl Linux (Alpine), set `VSCE_TARGET=alpine-` explicitly. +const vsceTarget = + process.env.VSCE_TARGET ?? `${process.platform}-${process.arch}`; +// Must equal the directory Yuku's loader resolves at runtime +// (`binding-${platform}-${arch}${libc}`): linux-* targets are glibc (-gnu), +// alpine-* targets are musl Linux (linux-*-musl). +const yukuBindingSuffix = vsceTarget.startsWith('linux-') + ? `${vsceTarget}-gnu` + : vsceTarget.startsWith('alpine-') + ? `${vsceTarget.replace('alpine-', 'linux-')}-musl` + : vsceTarget; const yukuRequire = createRequire(require.resolve('yuku-parser')); // Yuku computes this package name at runtime, so Rspack cannot discover it. const yukuBindingPath = yukuRequire.resolve( `@yuku-parser/binding-${yukuBindingSuffix}`, ); -/** - * The VSIX target of the machine running the build, used when `VSCE_TARGET` - * is unset (local builds and E2E). On musl `process.platform` is still - * `linux`; Node's diagnostic report carries `header.glibcVersionRuntime` only - * when the running libc exports `gnu_get_libc_version`, so its absence on - * Linux means musl (Alpine). A glibc compatibility layer can defeat this — - * set `VSCE_TARGET` explicitly there. - */ -function hostVsceTarget(): string { - const { platform, arch } = process; - if (platform !== 'linux') return `${platform}-${arch}`; - const report = process.report.getReport() as { - header?: { glibcVersionRuntime?: string }; - }; - return report.header?.glibcVersionRuntime - ? `linux-${arch}` - : `alpine-${arch}`; -} - -/** - * Maps a VSIX target to the suffix of its `@yuku-parser/binding-*` package, - * which is also the directory Yuku's loader looks up at runtime - * (`binding-${platform}-${arch}${libc}`): `linux-*` targets are glibc - * (`-gnu`), `alpine-*` targets are musl Linux (`linux-*-musl`). - */ -function yukuBindingSuffixFor(target: string): string { - const [platform, arch] = target.split('-'); - if (platform === 'linux') return `linux-${arch}-gnu`; - if (platform === 'alpine') return `linux-${arch}-musl`; - return target; -} - /** * Packages that are always resolved from the user's project at runtime, never * bundled: the extension ships types only. diff --git a/packages/vscode/scripts/packageTargets.mjs b/packages/vscode/scripts/packageTargets.mjs index 10ea64e..7f43f9b 100644 --- a/packages/vscode/scripts/packageTargets.mjs +++ b/packages/vscode/scripts/packageTargets.mjs @@ -31,13 +31,7 @@ const TARGETS = [ const args = process.argv.slice(2); const requested = args.filter((arg) => !arg.startsWith('-')); const all = args.includes('--all'); -// Same host detection as `hostVsceTarget()` in rslib.config.mts: musl Linux -// reports no `glibcVersionRuntime` and maps to the alpine-* target. -const hostTarget = - process.platform === 'linux' && - !process.report.getReport().header?.glibcVersionRuntime - ? `alpine-${process.arch}` - : `${process.platform}-${process.arch}`; +const hostTarget = `${process.platform}-${process.arch}`; const targets = all ? TARGETS : requested.length > 0 ? requested : [hostTarget]; From fca16a807ed4835ac9b77443791b0bb701836875 Mon Sep 17 00:00:00 2001 From: fi3ework Date: Thu, 8 Oct 2026 18:37:37 +0800 Subject: [PATCH 3/3] refactor(vscode): map VSIX targets to Yuku bindings through a libc table The Linux VSIX targets differ only by libc, so a lookup table states the mapping as data instead of a nested ternary. Behavior is unchanged. --- packages/vscode/rslib.config.mts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/vscode/rslib.config.mts b/packages/vscode/rslib.config.mts index 7c861aa..4686dc5 100644 --- a/packages/vscode/rslib.config.mts +++ b/packages/vscode/rslib.config.mts @@ -15,14 +15,12 @@ const rootDir = path.dirname(fileURLToPath(import.meta.url)); // On musl Linux (Alpine), set `VSCE_TARGET=alpine-` explicitly. const vsceTarget = process.env.VSCE_TARGET ?? `${process.platform}-${process.arch}`; -// Must equal the directory Yuku's loader resolves at runtime -// (`binding-${platform}-${arch}${libc}`): linux-* targets are glibc (-gnu), -// alpine-* targets are musl Linux (linux-*-musl). -const yukuBindingSuffix = vsceTarget.startsWith('linux-') - ? `${vsceTarget}-gnu` - : vsceTarget.startsWith('alpine-') - ? `${vsceTarget.replace('alpine-', 'linux-')}-musl` - : 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 = { 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(