perf(bridge): skip JS-side unresolved-import walk for tsgo-backed pro… #278
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ci | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| env: | |
| VOLAR_BRANCH: master | |
| VOLAR_SHA: fc9b2d67cfb569d267d81061b643ee55d4b4c38e # vuejs/language-tools@master | |
| BASELINE: test/baselines/nav-results-8c26673-t1.json # sim-nav baseline gate (same as nightly) | |
| jobs: | |
| prepare: | |
| runs-on: macos-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| matrix: ${{ steps.groups.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| # tools/ci-witness-groups.mjs is the single source of truth for the | |
| # witness matrix — it validates the table (dupes, missing scripts, | |
| # count) on every run, so a bad edit fails here before six jobs spawn. | |
| - id: groups | |
| run: echo "matrix=$(node tools/ci-witness-groups.mjs matrix)" >> "$GITHUB_OUTPUT" | |
| build: | |
| runs-on: macos-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Submodule SHAs | |
| id: sub-sha | |
| # The nested typescript-go/_submodules/TypeScript pin is a function of | |
| # the typescript-go gitlink, so two SHAs key all three checkouts. | |
| run: echo "sha=$(git ls-tree HEAD typescript typescript-go | awk '{print $3}' | paste -sd- -)" >> "$GITHUB_OUTPUT" | |
| - name: Restore submodules (cache) | |
| id: sub-cache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| typescript | |
| typescript-go | |
| .git/modules | |
| key: submodules-v1-${{ steps.sub-sha.outputs.sha }} | |
| - name: Fetch submodules | |
| if: steps.sub-cache.outputs.cache-hit != 'true' | |
| run: git submodule update --init --force --recursive --depth 1 | |
| # The save runs before patch/npm-ci/build steps touch the worktrees, so | |
| # the cached entry is always the pristine pinned tree. Keep this block | |
| # in sync with the vet job's (GitHub Actions has no step reuse). | |
| - name: Save submodules (cache) | |
| if: steps.sub-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: | | |
| typescript | |
| typescript-go | |
| .git/modules | |
| key: submodules-v1-${{ steps.sub-sha.outputs.sha }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: | | |
| package-lock.json | |
| typescript/package-lock.json | |
| typescript-go/package-lock.json | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.26" # typescript-go/go.mod | |
| cache-dependency-path: typescript-go/go.sum | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install typescript submodule dependencies | |
| run: npm ci | |
| working-directory: typescript | |
| - name: Install typescript-go submodule dependencies | |
| run: npm ci | |
| working-directory: typescript-go | |
| - name: Build js (tsgo native-preview -> vendor) | |
| run: npm run build:js | |
| - name: Build lib (patched TypeScript fork) | |
| run: npm run build:lib | |
| - name: Build bridge (cgo dylib) | |
| run: npm run build:bridge | |
| - name: Gates | |
| run: | | |
| npm run check:lib | |
| npm run check:enums | |
| # V8-sandbox failure class: the bridge's binary path must work under | |
| # Electron (VS Code's tsserver host) — invisible to every Node-only gate | |
| # (2026-07-20 incident). The probe spawns itself under Electron and | |
| # round-trips callBinary payloads. | |
| - name: Electron sandbox ABI probe | |
| run: | | |
| npm i --no-save electron@42 | |
| node tools/triage-electron-abi.mjs | |
| # This job is the only place the volar cache is populated: downstream | |
| # jobs (volar-test, witnesses, sim-nav-shard) restore it with | |
| # fail-on-cache-miss, and their needs: build guarantees this job's | |
| # post-job save has landed. The tree is BUILT (install + pnpm run build, | |
| # same as nightly) — the sim-nav shards need the compiled plugin; v4 | |
| # splits off stale install-only v2 entries so a shard never restores an | |
| # unbuilt tree. | |
| - name: Clone volar (cache) | |
| id: volar-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ${{ runner.temp }}/volar | |
| ${{ runner.temp }}/volar-stock | |
| key: volar-${{ env.VOLAR_SHA }}-v4 | |
| - name: Clone volar | |
| if: steps.volar-cache.outputs.cache-hit != 'true' | |
| run: | | |
| git clone --depth 50 --branch "$VOLAR_BRANCH" https://github.com/vuejs/language-tools.git "$RUNNER_TEMP/volar" | |
| git -C "$RUNNER_TEMP/volar" checkout "$VOLAR_SHA" 2>/dev/null || { | |
| git -C "$RUNNER_TEMP/volar" fetch --deepen 50 origin "$VOLAR_BRANCH" | |
| git -C "$RUNNER_TEMP/volar" checkout "$VOLAR_SHA" | |
| } | |
| # Point volar's typescript override at the CI-built TNB — volar | |
| # master's override is `npm:typescript-native-bridge@<release>`, which | |
| # would fetch the released package instead of the commit under test. | |
| cd "$RUNNER_TEMP/volar" | |
| node -e "const fs=require('fs');const p='pnpm-workspace.yaml';let s=fs.readFileSync(p,'utf8');s=s.replace(/typescript: npm:typescript-native-bridge@.*/,'typescript: link:'+process.env.GITHUB_WORKSPACE);fs.writeFileSync(p,s);console.log(s.match(/overrides:[\s\S]*/)[0])" | |
| - name: Install + build volar | |
| if: steps.volar-cache.outputs.cache-hit != 'true' | |
| run: | | |
| cd "$RUNNER_TEMP/volar" | |
| corepack enable | |
| pnpm install --no-frozen-lockfile | |
| pnpm run build | |
| - name: Pack stock typescript | |
| if: steps.volar-cache.outputs.cache-hit != 'true' | |
| run: | | |
| mkdir "$RUNNER_TEMP/volar-stock" | |
| cd "$RUNNER_TEMP/volar-stock" | |
| npm pack typescript@6.0.3 --silent | |
| tar -xzf typescript-6.0.3.tgz | |
| - name: sourcefile-guard | |
| env: | |
| VOLAR_ROOT: ${{ runner.temp }}/volar | |
| run: npm run check:sourcefile-guard | |
| - name: Upload built outputs | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: built-outputs | |
| path: | | |
| lib/ | |
| native/ | |
| vendor/ | |
| # lib/.gitattributes is hidden; v4.4+ skips hidden files by default. | |
| include-hidden-files: true | |
| retention-days: 1 | |
| - name: Cleanup harness processes | |
| if: always() | |
| run: node tools/kill-leaked-harness.mjs || true | |
| vet: | |
| runs-on: macos-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: Submodule SHAs | |
| id: sub-sha | |
| # The nested typescript-go/_submodules/TypeScript pin is a function of | |
| # the typescript-go gitlink, so two SHAs key all three checkouts. | |
| run: echo "sha=$(git ls-tree HEAD typescript typescript-go | awk '{print $3}' | paste -sd- -)" >> "$GITHUB_OUTPUT" | |
| - name: Restore submodules (cache) | |
| id: sub-cache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| typescript | |
| typescript-go | |
| .git/modules | |
| key: submodules-v1-${{ steps.sub-sha.outputs.sha }} | |
| - name: Fetch submodules | |
| if: steps.sub-cache.outputs.cache-hit != 'true' | |
| run: git submodule update --init --force --recursive --depth 1 | |
| # The save runs before patch steps touch the worktree, so the cached | |
| # entry is always the pristine pinned tree. Keep this block in sync | |
| # with the build job's (GitHub Actions has no step reuse). | |
| - name: Save submodules (cache) | |
| if: steps.sub-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: | | |
| typescript | |
| typescript-go | |
| .git/modules | |
| key: submodules-v1-${{ steps.sub-sha.outputs.sha }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.26" # typescript-go/go.mod | |
| cache-dependency-path: typescript-go/go.sum | |
| - name: Patch tsgo | |
| run: npm run patch:tsgo | |
| - name: go vet | |
| # Scoped to packages TNB patches touch — vetting all of typescript-go | |
| # (plus every test file) costs ~6min/run; upstream code is not our | |
| # surface. Keep in sync with patches/typescript-go/. | |
| run: go vet ./internal/api/... ./internal/ls/... ./internal/checker/... ./internal/compiler/... ./internal/bundled/... ./internal/project/... ./internal/module/... | |
| working-directory: typescript-go | |
| volar-test: | |
| needs: build | |
| runs-on: macos-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Download built outputs | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: built-outputs | |
| path: . | |
| - name: Restore volar (cache) | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| ${{ runner.temp }}/volar | |
| ${{ runner.temp }}/volar-stock | |
| key: volar-${{ env.VOLAR_SHA }}-v4 | |
| fail-on-cache-miss: true | |
| # Runs on every CI run: the cached node_modules links | |
| # typescript -> $GITHUB_WORKSPACE, i.e. the commit under test. | |
| - name: Test volar | |
| run: | | |
| # Volar's tsserver harness resolves typescript/lib/tsserver via a | |
| # fresh module lookup from dirname(realpath(typescript/package.json)). | |
| # With the link: override that realpath is this repo, so `typescript` | |
| # must resolve from here too — self-link it (TNB has no typescript | |
| # dependency of its own; node_modules is CI-local state). | |
| # node_modules may not exist at all: the root package has no | |
| # dependencies, and unlike the old single job this job never installs | |
| # Electron. Create it before self-linking. | |
| mkdir -p "$GITHUB_WORKSPACE/node_modules" | |
| ln -sfn "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/node_modules/typescript" | |
| cd "$RUNNER_TEMP/volar" | |
| npm test | |
| - name: Cleanup harness processes | |
| if: always() | |
| run: node tools/kill-leaked-harness.mjs || true | |
| witnesses: | |
| needs: [prepare, build] | |
| runs-on: macos-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| # One group's failure must not cancel the others — every commit keeps | |
| # full witness coverage in a single run. | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} | |
| name: witnesses (wg${{ matrix.index }}) | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Download built outputs | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: built-outputs | |
| path: . | |
| - name: Restore volar (cache) | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| ${{ runner.temp }}/volar | |
| ${{ runner.temp }}/volar-stock | |
| key: volar-${{ env.VOLAR_SHA }}-v4 | |
| fail-on-cache-miss: true | |
| - name: sweep-ls-throws | |
| if: matrix.sweep | |
| env: | |
| VOLAR_ROOT: ${{ runner.temp }}/volar | |
| run: | | |
| # Runs before this group's witnesses: it regenerates | |
| # /tmp/tnb-sweep-fixtures that triage-quickinfo-emptyparity and | |
| # triage-refs-exportspec read. /tmp is per-runner, so the three must | |
| # share one job — ci-witness-groups.mjs flags this group via sweep. | |
| set -o pipefail | |
| TNB_TRACE_THROW=1 TNB_TRACE_THROW_FILE=/tmp/tnb-throw-ci.jsonl \ | |
| node tools/sweep-ls-throws.mjs 2>&1 | tee /tmp/tnb-sweep-ci.log | |
| grep -q "correlated throw hits: 0" /tmp/tnb-sweep-ci.log \ | |
| || { echo "sweep correlated hits != 0"; exit 1; } | |
| - name: Regression witnesses | |
| env: | |
| VOLAR_ROOT: ${{ runner.temp }}/volar | |
| STOCK_TSSERVER_PATH: ${{ runner.temp }}/volar-stock/package/lib/tsserver.js | |
| WITNESSES: ${{ matrix.witnesses }} | |
| run: | | |
| # Isolated tools copy: the harness lock lives at | |
| # <toolsDir>/../.tnb-harness.lock, and this group's witnesses run | |
| # sequentially under one lock. vendor/ lib/ native/ are symlinked | |
| # into the copy for tools that boot the bridge in-process (they | |
| # resolve the repo from import.meta.dirname/..); README.md + | |
| # patches/ too — the ledger gate reads them for the patch<->README | |
| # reconciliation; bin/ too — triage-nuxtui-exportstar shells out to | |
| # repoRoot/bin/tsc. pipefail is set explicitly so the tee below does | |
| # not mask a group failure (the step shell's -eo pipefail default | |
| # must not be load-bearing). | |
| i=${{ matrix.index }} | |
| set -o pipefail | |
| rm -rf "/tmp/tnb-wg$i" | |
| mkdir -p "/tmp/tnb-wg$i" | |
| cp -R tools "/tmp/tnb-wg$i/tools" | |
| ln -sfn "$GITHUB_WORKSPACE/vendor" "/tmp/tnb-wg$i/vendor" | |
| ln -sfn "$GITHUB_WORKSPACE/lib" "/tmp/tnb-wg$i/lib" | |
| ln -sfn "$GITHUB_WORKSPACE/native" "/tmp/tnb-wg$i/native" | |
| ln -sfn "$GITHUB_WORKSPACE/README.md" "/tmp/tnb-wg$i/README.md" | |
| ln -sfn "$GITHUB_WORKSPACE/patches" "/tmp/tnb-wg$i/patches" | |
| ln -sfn "$GITHUB_WORKSPACE/bin" "/tmp/tnb-wg$i/bin" | |
| rc=0 | |
| { | |
| failed=() | |
| for w in $WITNESSES; do | |
| echo "=== [wg$i] $w ===" | |
| (cd "/tmp/tnb-wg$i/tools" && node "$w.mjs") || failed+=("$w") | |
| done | |
| if ((${#failed[@]})); then | |
| echo "WG$i FAILURES (${#failed[@]}): ${failed[*]}" | |
| exit 1 | |
| fi | |
| } 2>&1 | tee "/tmp/tnb-wg$i.log" || rc=$? | |
| if ((rc)); then | |
| # On failure, surface diff lines with their stock/tnb context — | |
| # the streamed log can be thousands of lines, and a bare tail can | |
| # land on a KNOWN-exemption pair and hide the real failure keys | |
| # (run 30062511367 misread this way). | |
| echo "=== witness group $i (failure lines) ===" | |
| grep -E -A2 "^DIFF |^FAIL |VERDICT: FAIL|FAILURES \(|verdict=DIFF" "/tmp/tnb-wg$i.log" || true | |
| fi | |
| exit $rc | |
| - name: Upload witness log on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: witness-log-wg${{ matrix.index }} | |
| path: | | |
| /tmp/tnb-wg${{ matrix.index }}.log | |
| /tmp/tnb-sweep-ci.log | |
| retention-days: 30 | |
| - name: Cleanup harness processes | |
| if: always() | |
| run: node tools/kill-leaked-harness.mjs || true | |
| # sim-nav mirrors nightly's shard mechanism (nightly.yml sim-nav-shard + | |
| # merge): same 4-shard matrix, merge, and --baseline gate, so PR CI, nightly | |
| # and local `npm run check:sim-nav` all run the same full pipeline. No | |
| # SIM_NAV_SKIP_CLASSIFY here — the classify replay runs, isomorphic with the | |
| # local full gate. | |
| sim-nav-shard: | |
| needs: build | |
| runs-on: macos-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| # One shard's failure must not cancel the others. | |
| fail-fast: false | |
| matrix: | |
| index: [0, 1, 2, 3] | |
| name: sim-nav (shard ${{ matrix.index }}) | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Download built outputs | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: built-outputs | |
| path: . | |
| - name: Restore volar (cache) | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: | | |
| ${{ runner.temp }}/volar | |
| ${{ runner.temp }}/volar-stock | |
| key: volar-${{ env.VOLAR_SHA }}-v4 | |
| fail-on-cache-miss: true | |
| - name: Run sim-nav shard | |
| env: | |
| VOLAR_ROOT: ${{ runner.temp }}/volar | |
| STOCK_TSSERVER_PATH: ${{ runner.temp }}/volar-stock/package/lib/tsserver.js | |
| SIM_NAV_BASELINE: ${{ github.workspace }}/${{ env.BASELINE }} | |
| run: | | |
| set -u | |
| # Isolated tools copy, same shape as the old single-job shards: the | |
| # harness lock lives at <toolsDir>/../.tnb-harness.lock — one shard | |
| # per runner now, but the shard script still expects to run from a | |
| # tools copy it owns. | |
| i=${{ matrix.index }} | |
| rm -rf "/tmp/tnb-shard$i" | |
| mkdir -p "/tmp/tnb-shard$i" | |
| cp -R tools "/tmp/tnb-shard$i/tools" | |
| rc=0 | |
| (cd "/tmp/tnb-shard$i/tools" && \ | |
| SIM_NAV_SHARD_INDEX=$i SIM_NAV_SHARD_COUNT=4 \ | |
| SIM_NAV_OUT_JSON="/tmp/tnb-sim-nav-shard$i.json" \ | |
| SIM_NAV_THROW_FILE="/tmp/tnb-sim-nav-shard$i.throws.jsonl" \ | |
| node triage-sim-nav-shard.mjs > "/tmp/tnb-sim-nav-shard$i.log" 2>&1) || rc=$? | |
| echo "=== shard $i (tail) ===" | |
| tail -5 "/tmp/tnb-sim-nav-shard$i.log" || true | |
| exit $rc | |
| - name: Upload shard result | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sim-nav-shard-json-${{ matrix.index }} | |
| path: /tmp/tnb-sim-nav-shard${{ matrix.index }}.json | |
| retention-days: 1 | |
| - name: Upload shard logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sim-nav-shard-logs-${{ matrix.index }} | |
| path: | | |
| /tmp/tnb-sim-nav-shard${{ matrix.index }}.log | |
| /tmp/tnb-sim-nav-shard${{ matrix.index }}.throws.jsonl | |
| retention-days: 30 | |
| - name: Cleanup harness processes | |
| if: always() | |
| run: node tools/kill-leaked-harness.mjs || true | |
| merge: | |
| needs: sim-nav-shard | |
| runs-on: macos-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Download shard results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: sim-nav-shard-json-* | |
| path: /tmp/tnb-shards | |
| merge-multiple: true | |
| - name: Merge shard results | |
| run: node tools/sim-nav-merge.mjs --out /tmp/tnb-sim-nav-merged.json /tmp/tnb-shards/tnb-sim-nav-shard0.json /tmp/tnb-shards/tnb-sim-nav-shard1.json /tmp/tnb-shards/tnb-sim-nav-shard2.json /tmp/tnb-shards/tnb-sim-nav-shard3.json | |
| - name: Check against baseline | |
| run: node tools/sim-nav-merge.mjs --baseline "$BASELINE" /tmp/tnb-sim-nav-merged.json | |
| - name: Upload sim-nav results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sim-nav-results | |
| path: /tmp/tnb-sim-nav-merged.json | |
| retention-days: 30 |