Commit 13c35f8
authored
feat: unified VS Code extension for the Rstack toolchain (#1)
* feat: unified VS Code extension for the Rstack toolchain
One extension (rstack.rstack) replacing the standalone rstack.rslint and
rstack.rstest extensions: a thin shell (activation, per-folder detection,
status bar, settings migration) hosting the Rslint and Rstest stacks as
near-verbatim upstream copies, plus a detection-only stub for rstack-cli
formatting.
- pnpm monorepo with a single package (packages/vscode)
- rstack-cli powers the repo harness: rs lint --type-check, rs fmt,
rs setup git hooks and rs staged on pre-commit
- E2E suites ported from both upstream extensions, running a real VS Code
(8 shell/detection + 8 rstest + 133 lint tests), plus 107 unit tests
- CI (Linux + Windows) and a 6-target VSIX release workflow
- Contributor scaffolding: AGENTS.md/CLAUDE.md, CONTRIBUTING.md,
issue/PR templates
* fix: spawn pnpm through a shell on Windows in the fixture installer
Node rejects spawning .cmd shims without a shell (CVE-2024-27980
hardening), so the E2E fixture installs failed with EINVAL on the
Windows CI runner.
* fix: Windows and CI-checkout E2E failures
- The detection E2E asserted the extensionless POSIX bin name; on
Windows the probe correctly finds the rs.cmd shim, so compare without
the extension.
- basic/src/gitignored.ts is a test asset deliberately listed in the
lint fixture's own .gitignore, which also hid it from this repo's
checkout — CI never had the file. Force-track it.
* fix: retry sandbox directory removal in the JS-config E2E suite
On the Windows runner the language server / VS Code watcher can still
hold handles inside a nested-config directory, so an immediate recursive
rmSync fails with EPERM. Use Node's built-in maxRetries/retryDelay, as
the suite harness already does for the profile root.
* fix: deflake the JS-config E2E suite on CI runners
- Broken-nested-config cleanup: wait for the restored root config to
take effect before deleting the nested directory — on Windows the
server holds handles inside it until the refresh lands, so the delete
hit EPERM even with short retries. Also widen the retry window.
- Parent-ignore catalog test: VS Code's watcher can miss events for
files created inside a just-created directory, leaving the nested
config undiscovered forever (observed twice on the Linux runner).
Nudge the config file and retry discovery, and compare the
evaluation marker against a baseline instead of a literal count.
* fix: address review findings in the port-owned adaptations
Four fixes to code this port owns (upstream-inherited findings are left
for upstream):
- migration: rstack.rslint.enable is window-scoped in the manifest, so
map the legacy rslint.enable with targetScope 'window' — folder-level
legacy values are now skipped as not-folder-scoped instead of being
migrated into a layer where they have no effect.
- rstack bridge: a native rstest.config.* now suppresses a bridged
rstack.config.* project only at its own directory, per the documented
'native wins at the same root' contract — a native config in a sibling
monorepo package no longer disables every bridge in the folder.
- status: crash / version-mismatch reports are latched so a detection
refresh cannot paint 'running' over a live failure; the latch clears
when a worker actually spawns or the version check passes again.
- rstack bridge: watch rstack.config.* content changes and rebuild the
bridged project, so define.test() edits reach the Test Explorer
without a window reload (path-only detection signatures skip these).
* fix(vscode): lockfile-driven retries and per-root status latches
Two fixes from the second Codex review round:
- A lockfile-only change (e.g. pnpm install) used to produce a detection
pass whose signature was unchanged, so no event fired and stacks never
retried failed resolutions until a window reload. Lockfile-triggered
passes now always notify, and the lint stack explicitly re-attempts
roots whose last start failed (a failed slot is generation-pinned and
reconcile alone never retries it).
- The rstest status failure latches were single-slot: in a multi-root
workspace one root's successful version check or worker spawn cleared
another root's live mismatch/crash. Latches are now keyed by the
resolution root that reported them and only that root's recovery
clears its entry.
* fix(vscode): re-resolve live bridges and drop latches for removed roots
Third Codex review round, both on the previous round's fixes:
- syncBridgeProjects kept a live bridge without re-resolving the shim,
so a dependency upgrade could leave the project pinned to a pruned
pnpm store path. The shim is now re-resolved on every sync; a bridge
is kept (worker stays warm) only while the resolution lands on the
same file, and is rebuilt when it moved or stopped resolving.
- A latched crash/mismatch now dies with its root: Project.dispose
forgets its cwd's entries, and dangling mismatches from failed shim
resolutions (which never create a project) are cleared when the
directory stops being a bridge candidate or the folder is disposed.
* fix(vscode): retry failed native projects, key latches per project
Fourth Codex review round:
- A native project whose one-shot config evaluation rejected (deps not
installed yet) stayed an empty tree forever. Projects now record the
failure and are recreated on the next detection pass (lockfile-driven
installs included). Only detection events trigger the retry, so a
persistently broken config cannot loop.
- Master status reports now latch under the project source URI instead
of cwd: two configs in one directory no longer share a latch key, so
disposing or recovering one cannot clear its sibling's live failure.
This also unshadows bridge resolution latches (keyed by config dir)
from bridge project disposal -- previously a sync that latched a fresh
mismatch and then dropped the dead bridge wiped its own report.
* fix(vscode): gate restart on a live controller, one bridge per directory
Fifth Codex review round:
- The status bar offered Restart for a detected-but-disabled stack
(enable-setting off, Restricted Mode), whose controller never
registered the command; selecting it failed with command-not-found.
Restart now shows only for states a registered controller produces.
- A directory shipping several rstack config names (e.g. .ts next to
.js mid-migration) produced one bridge project per file, but the shim
probes the default names in its cwd itself, so every sibling loaded
the same winning config and duplicated its tests. One bridge per
directory now, keyed to the file rstack's own probe order picks.
* fix(vscode): uncached version reads, restart tracks controller liveness
Sixth Codex review round:
- Version checks read package.json through nodeRequire, whose module
cache pinned the first-seen version for the extension host lifetime;
an in-place upgrade (npm/yarn reuse the path) kept the old verdict.
readPackageVersion() now does a plain filesystem read; used by both
the @rstest/core check (master) and the rstack shim check (bridge).
- Restart availability now tracks controller registration explicitly
(StatusBar.setActive, reported by the shell) instead of being
inferred from the state kind: a crashed state can mean either a live
controller whose worker died (restart valid) or a failed registration
whose command was disposed with the controller (restart dead).
* fix(vscode): bypass require.resolve's path cache for re-resolutions
Seventh Codex review round, one root cause in three places: Node caches
every successful require.resolve() for the process lifetime (request +
paths in Module._pathCache, symlink targets in the CJS realpath cache),
so once a package resolved, re-resolving after pnpm retargets the
node_modules symlink replayed the pre-upgrade store path. Failed
lookups are never cached, which is why resolve-after-install always
worked; this closes the upgrade/downgrade half.
findPackageJsonUncached() walks node_modules up with plain fs calls and
an uncached realpathSync. It now anchors:
- the Rslint core location (locateCore), whose cooperating pieces
already resolve from the returned path (one-resolution-root rule);
- the @rstest/core worker/bin resolution, with the package entry
resolved from the realpath'd package dir so the cache key is
version-pinned on pnpm and the exports map stays honored (a
configured rstestPackagePath keeps its explicit-pin behavior);
- the rstack shim resolution, replacing the previous resolve plus
node_modules containment check (the walk-up only yields node_modules
candidates).
* fix(vscode): reload PnP on retry, report unexpected worker exits
Eighth Codex review round:
- The Yarn PnP fallback required .pnp.cjs through the module cache, so
a dependency-driven retry kept resolving against the map seen by the
first load. The cache entry is evicted before each load -- the PnP
flavor of the resolution staleness findPackageJsonUncached avoids.
- A worker that spawned successfully but exited before being closed
(e.g. an invalid nodeExecArgs option) cleared the crash latch on
spawn and then vanished silently: birpc was unblocked but no state
was reported, leaving the status on running over an empty explorer.
Unexpected exits now report crashed; deliberate teardowns are
recognized either by $close having run first or by an explicit
expected-exit mark set by dispose and the failed-debug-attach path,
so project rebuilds and disposals do not misreport.
* fix(vscode): close birpc for expected worker exits too
Ninth Codex review round, on the previous round's fix: the expected-exit
early return also skipped worker.$close(), so a disposed or rebuilt
project's pending RPC calls stayed alive until timeout and the worker
lingered in the tracking set. Expected exits now skip only the crash
report; birpc is always closed when the process exits before $close.
* fix(vscode): unbounded rstack config discovery
Tenth Codex review round: the 100-file findFiles cap is fine for rows
that are mere detection signals (the stacks rescan their own configs),
but the rstack config list is consumed as-is by the test stack's bridge
sync, so a monorepo with more than 100 rstack configs silently lost the
overflow's bridged projects. The rstack row is now unbounded; the cap
stays for the signal-only rows.1 parent 6fbb53e commit 13c35f8
235 files changed
Lines changed: 29141 additions & 0 deletions
File tree
- .github
- ISSUE_TEMPLATE
- workflows
- .rstack/hooks
- .vscode
- packages/vscode
- scripts
- src
- stacks
- fmt
- lint
- test
- vendored
- worker
- tests
- e2e
- fixtures
- rslint
- src
- rstack
- src
- tests
- rstest
- tests
- lint
- fixtures
- basic
- src
- eslint-plugins
- src
- jsconfig
- src
- monorepo
- packages
- bar/src
- broken
- src
- foo
- src
- src
- multiroot
- parent
- nested
- src
- src
- sentinel
- src
- twins
- left/app
- src
- right/app
- src
- noconfig
- src
- project-service-scope
- src
- template-nested
- test
- type-aware-scope
- packages
- cli/src
- core
- src
- suite-eslint-plugins
- suite-jsconfig
- suite-monorepo
- suite-multiroot
- suite-noconfig
- suite-project-service-scope
- suite-type-aware-scope
- suite
- utils
- rstest
- fixtures
- workspace-1
- config
- src
- test
- workspace-2
- folder/project-2
- test
- project-1
- test
- suite
- smoke
- suite
- unit
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
0 commit comments