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
2 changes: 1 addition & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Glossary of terms used across rstack-editor. Code, docs, commit messages and rev
## Core

- **Stack** — one tool integration (lint, test, fmt) hosted by the extension shell. A stack registers against the shell and reports status through it; stacks never own UI chrome.
- **Shell** — the always-activating extension core: detection, status bar, output channels, settings migration, stack lifecycle.
- **Shell** — the always-activating extension core: detection, status bar, output channels, stack lifecycle.
- **Detection** — the per-workspace-folder scan deciding which stacks a folder lights up. Detection signals are config files and installed tool binaries, never user settings.
- **Gate** — the per-stack activation condition: detected, workspace trusted, and the enable settings on.

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0001-node-runtime-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ This decision was written for one path, the rstest worker, and named two others

## Consequences

- An explicit `rstack.nodeExecutable` (shared with the fmt server since ADR 0002; the standalone Rstest extension's `rstest.nodeExecutable` migrates to it) is always honoured, but it is probed too: falling short of the floor produces a status, not a refusal. The escape hatch stays an escape hatch; it stops being silent.
- An explicit `rstack.nodeExecutable` (shared with the fmt server since ADR 0002; the standalone Rstest extension's `rstest.nodeExecutable` had this role — it is not migrated, #15) is always honoured, but it is probed too: falling short of the floor produces a status, not a refusal. The escape hatch stays an escape hatch; it stops being silent.
- A below-floor configured executable is reported through the same status as "no runtime found at all", so the two messages must state their _consequence_ explicitly — one says tests will not run, the other says the extension is running with it anyway.
- The interactive-shell probe is the recovery path and does not exist on Windows (no `-i -c` equivalent reliably evaluates a user's profile across cmd and PowerShell). A Windows user whose PATH `node` is below the floor gets the failure status with no second candidate.
- `NODE_OPTIONS` can carry `--no-strip-types`, which defeats the floor on any version. Deliberately not detected: the same setting breaks `rs test` in the terminal, so the editor failing identically is correct, and special-casing one flag would be permanent trivia bought for one diagnostic.
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0002-fmt-lsp-on-user-node-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ One rule across both stacks now: the workspace folder root is the config root. A

## Why the User Node runtime

The server loads the project's `rstack.config.*` through `@rstackjs/load-config` with `loader: 'native'` — the exact path ADR 0001 analysed to set the worker floor, with no jiti fallback and no `process.features.typescript` consultation. So fmt is not a new case: it is the second caller of the same decision, and it takes the floor, the candidate order (PATH `node`, then the user's interactive shell) and the failure reporting out of the one shared module, `shared/nodeResolution.ts`. The escape hatch is shared too — `rstack.nodeExecutable`, resource-scoped, honoured whenever it is set and probed anyway, advisory-only. A user pinning a Node for one tool means it for the toolchain, so there is one setting rather than one per stack (the standalone Rstest extension's `rstest.nodeExecutable` migrates to it).
The server loads the project's `rstack.config.*` through `@rstackjs/load-config` with `loader: 'native'` — the exact path ADR 0001 analysed to set the worker floor, with no jiti fallback and no `process.features.typescript` consultation. So fmt is not a new case: it is the second caller of the same decision, and it takes the floor, the candidate order (PATH `node`, then the user's interactive shell) and the failure reporting out of the one shared module, `shared/nodeResolution.ts`. The escape hatch is shared too — `rstack.nodeExecutable`, resource-scoped, honoured whenever it is set and probed anyway, advisory-only. A user pinning a Node for one tool means it for the toolchain, so there is one setting rather than one per stack (the standalone Rstest extension's `rstest.nodeExecutable` had this role — it is not migrated, #15).

Falling back to the VS Code Node runtime stays rejected — ADR 0001's load-bearing "no", unchanged. It is worth naming that the old fmt path did exactly that: `process.execPath` with `ELECTRON_RUN_AS_NODE=1`, loading the user's config on Electron's Node, with no floor and no preflight. Moving the server onto a User Node runtime is what takes fmt off that ADR's debt list.

Expand Down
6 changes: 3 additions & 3 deletions packages/vscode/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md — `rstack.rstack` VS Code extension

One extension replacing the standalone `rstack.rslint` and `rstack.rstest` extensions: a thin shell (activation, detection, status bar, settings migration) hosting one stack per tool under `src/stacks/`.
One extension replacing the standalone `rstack.rslint` and `rstack.rstest` extensions: a thin shell (activation, detection, status bar) hosting one stack per tool under `src/stacks/`.

## The copies are intentional

Expand All @@ -10,7 +10,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten
## The seven adaptations

1. **Shell activation** — stacks never self-activate; `register()` returns fast and never blocks on starting a server/worker.
2. **Namespace** — everything user-visible is `rstack.*`. Legacy `rslint.*` / `rstest.*` names appear only in the migration mapping. Command IDs were renamed without aliases (breaking old keybindings was an accepted cost).
2. **Namespace** — everything user-visible is `rstack.*`. Legacy `rslint.*` / `rstest.*` settings and command ids are not read, aliased or migrated (breaking old settings and keybindings was an accepted cost).
3. **Resolve-from-project** — no tool binaries or tool packages in the VSIX; everything resolves from the user's project so the editor runs the CLI's exact versions. Version floors surface as a status, never a crash. All cooperating lint pieces (binary, config loader, plugin host) must come from one resolution root. Enforced by lint: `@typescript-eslint/no-restricted-imports` in the root `rstack.config.ts` rejects any non-type import of `@rslint/core`, `@rstest/core`, `rstack` or `jiti` under `src/` — types only at compile time, runtime modules through explicit project paths.
4. **Status aggregation** — stacks own no UI chrome; they report to the shell's single status bar item, which always exists. In CI the test stack's `MasterLogger` also mirrors every entry to stderr (`RSTACK_E2E_MIRROR_LOGS=1`, set by `e2e/rstest/runTest.ts`) — the output channel is unreadable there; rationale in `stacks/test/logger.ts`.
5. **Worker-cwd decoupling** (test) — a project's cwd is explicit, not derived from the config file path; for native configs behavior stays byte-identical to upstream.
Expand All @@ -19,7 +19,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten

## Rules

- **Pre-1.0.0 the extension breaks freely.** No compatibility is owed with earlier unpublished states of this extension — settings, command ids and behavior may change without deprecation paths, and dead compat code for them is removed, not kept. Only the **latest released** `rstack`, `@rstest/core` and `@rslint/core` need support: whenever a change touches a floor in `SUPPORT_MATRIX`, set it to the latest release at that time — do not reason about which older release would still work — and raise it without a transition story (the floor status names the required version). The settings migration exists for users of the two retired standalone extensions, never for earlier states of this one.
- **Pre-1.0.0 the extension breaks freely.** No compatibility is owed with earlier unpublished states of this extension — settings, command ids and behavior may change without deprecation paths, and dead compat code for them is removed, not kept. Only the **latest released** `rstack`, `@rstest/core` and `@rslint/core` need support: whenever a change touches a floor in `SUPPORT_MATRIX`, set it to the latest release at that time — do not reason about which older release would still work — and raise it without a transition story (the floor status names the required version). No settings migration exists either — not for earlier states of this extension, and not for the two retired standalone extensions (removed in #15; users re-enter their settings under `rstack.*`).
- **The three tools are treated uniformly by default.** Detection, dependency-change retry, restart semantics, version gating and status reporting follow one shared pattern across the lint/test/fmt stacks; a stack diverges only when its tool forces it, and the divergence is recorded here as a gotcha. When adding behavior to one stack, first ask whether it belongs to all three. This is about behavior, not code — the upstream copies still must not be deduplicated.
- One stack failing to register or crashing must never take another stack (or the shell) down.
- The shell always activates; per-folder config detection decides which stacks start, and re-runs on config/lockfile changes without a window reload. Enable-settings are coarse kill switches only.
Expand Down
9 changes: 2 additions & 7 deletions packages/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,9 @@ To use `rs fmt` as the formatter for supported documents, opt in through your VS

Formatting runs one `rs fmt` language server per workspace folder, which loads `define.fmt()` from the `rstack.config.*` at the **folder root** — the same config `rs fmt` in a terminal there would use, so a config in a subdirectory is not picked up (open that subdirectory as its own workspace folder if it needs different settings). Editing the config restarts the server for you. Your editor's own formatting options (tab size, spaces) are not consulted: the project config decides, exactly as on the command line.

## Migrating from the standalone extensions
## Coming from the standalone extensions

Run **Rstack: Migrate Rslint/Rstest Settings** from the Command Palette (it is also offered once, dismissibly, when legacy keys are found).

- Settings are migrated per layer (User, Workspace, Workspace Folder), and the legacy keys are removed after they are copied. Workspace and folder layers touch files inside your repository, so nothing is written before you confirm the previewed key mapping.
- Legacy `rslint.binPath` / `rslint.customBinPath` values are left untouched: a standalone binary path cannot be translated safely into the `@rslint/core` directory the worker requires.
- **Keybindings are not migrated.** Command ids were renamed to `rstack.*` with no aliases, and VS Code has no keybindings API, so any keybinding bound to an old `rslint.*` / `rstest.*` command id has to be re-bound by hand.
- Projects with only `rslint.json` / `rslint.jsonc` are reported as `not detected`; run `rslint --init` to migrate to a JS/TS config.
Settings and keybindings are not carried over from the retired `rstack.rslint` / `rstack.rstest` extensions: re-enter your settings under the `rstack.*` keys listed above and re-bind any keybinding to the new `rstack.*` command ids. Legacy `rslint.binPath` / `rslint.customBinPath` have no equivalent — use `rstack.rslint.corePath` to point at an `@rslint/core` package directory if you still need an override.

## Community

Expand Down
1 change: 0 additions & 1 deletion packages/vscode/e2e/suite/shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ suite('shell', () => {
for (const command of [
'rstack.showOutput',
'rstack.restart',
'rstack.migrateSettings',
'rstack.rslint.output.focus',
'rstack.rslint.restart',
'rstack.rstest.output.focus',
Expand Down
5 changes: 0 additions & 5 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@
"category": "Rstack",
"icon": "$(debug-restart)"
},
{
"command": "rstack.migrateSettings",
"title": "Migrate Rslint/Rstest Settings",
"category": "Rstack"
},
{
"command": "rstack.rslint.output.focus",
"title": "Show Rslint Log",
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const CHANNEL_NAMES: Readonly<Record<StackId | 'shell', string>> = {

/**
* The extension's four output channels — a deliberate cap: one per stack plus
* one for the shell (detection results, state transitions, migration logs).
* one for the shell (detection results, state transitions).
*
* Stacks never create their own channel — a copied stack that used to create
* one per workspace folder has to log into the shared channel instead.
Expand Down
6 changes: 0 additions & 6 deletions packages/vscode/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import vscode from 'vscode';
import { Channels } from './channels';
import { DetectionService } from './detection';
import { maybePromptForMigration, runSettingsMigration } from './migration';
import { resetUserNodeCaches } from './shared/nodeResolution';
import { StatusBar } from './statusBar';
import {
Expand Down Expand Up @@ -132,8 +131,6 @@ class ExtensionShell {
// is still initialising — and running beside it would let that restart
// retire a controller whose `register()` has not returned yet.
await this.reconcile();

void maybePromptForMigration(this.context, this.#channels.shell);
}

private registerCommands(): void {
Expand All @@ -145,9 +142,6 @@ class ExtensionShell {

register('rstack.showOutput', () => this.#channels.shell.show());
register('rstack.restart', () => this.restart());
register('rstack.migrateSettings', () =>
runSettingsMigration(this.#channels.shell),
);
for (const stack of STACK_IDS) {
register(stackCommand(stack, 'output.focus'), () =>
this.#channels.forStack(stack).show(),
Expand Down
Loading