Skip to content

nightly

nightly #57

Workflow file for this run

name: nightly
on:
schedule:
- cron: "23 3 * * *" # UTC, off-peak
workflow_dispatch:
permissions:
contents: read
env:
VOLAR_BRANCH: master
VOLAR_SHA: fc9b2d67cfb569d267d81061b643ee55d4b4c38e # vuejs/language-tools@master
BASELINE: test/baselines/nav-results-8c26673-t1.json
jobs:
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 other submodule jobs (GitHub Actions has no step
# reuse): pristine-attribution + build-win32 here, build + vet in ci.yml.
- 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
# This job is the only place the volar cache is populated: shard jobs
# restore it with fail-on-cache-miss, and their needs: build guarantees
# this job's post-job save has landed.
- name: Clone volar (cache)
id: volar-cache
# Both workflows need a BUILT volar tree (install + pnpm run build —
# the sim-nav shards load the compiled plugin). Keys stay per-workflow
# (nightly-v2 vs ci.yml's v4): first-writer-wins on a shared key would
# let one workflow's write timing define the other's content.
uses: actions/cache@v4
with:
path: |
${{ runner.temp }}/volar
${{ runner.temp }}/volar-stock
key: volar-${{ env.VOLAR_SHA }}-nightly-v2
- 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"
# 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)"
- 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: 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
pristine-attribution:
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 other submodule jobs (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
# v5 attribution audit: every stopgap/KNOWN the tree carries must
# reproduce on pristine upstream tsgo (never the patched submodule).
# Fails loud when upstream fixes one, forcing stopgap removal.
# Standalone job: the gate tests the patched submodule plus a pristine
# clone — it has no dependency on the build job's outputs, so it must
# not sit on the shards' critical path (it costs ~4min alone).
- name: pristine-tsgo attribution gate
run: node tools/check-pristine-attribution.mjs
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 }}-nightly-v2
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 }}
# SIM_NAV_SKIP_CLASSIFY was removed: nightly now runs the full
# classify replay, isomorphic with local `npm run check:sim-nav`
# (green since the stalls). If a replay stall/SDK-disconnect recurs
# in CI, re-add SIM_NAV_SKIP_CLASSIFY=1 to the shard invocation
# below and file the issue.
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
build-win32:
runs-on: ubuntu-latest
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 other submodule jobs (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
- 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: Install llvm-mingw
run: |
curl -sSL https://github.com/mstorsjo/llvm-mingw/releases/download/20250417/llvm-mingw-20250417-ucrt-ubuntu-22.04-x86_64.tar.xz | sudo tar -xJ -C /opt
echo "/opt/llvm-mingw-20250417-ucrt-ubuntu-22.04-x86_64/bin" >> "$GITHUB_PATH"
- name: Download node.lib
run: curl -sSL "https://nodejs.org/dist/v24.15.0/win-x64/node.lib" -o /tmp/node.lib
- name: Build bridge (win32-x64, stripped)
run: node tools/build-bridge.js
env:
GOOS: windows
GOARCH: amd64
CC: x86_64-w64-mingw32-gcc
TNB_NODE_LIB: /tmp/node.lib
CGO_ENABLED: 1
TNB_STRIP: 1
- name: Upload lib payload
uses: actions/upload-artifact@v4
with:
name: lib-dist-win32-nightly
path: |
package.json
README.md
LICENSE
NOTICE
bin/
lib/
vendor/
retention-days: 1
- name: Upload win32 bridge
uses: actions/upload-artifact@v4
with:
name: bridge-win32-x64-nightly
path: native/
retention-days: 1
test-bridge-win32:
needs: build-win32
runs-on: windows-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: 24
# Checkout must precede the artifact downloads: its default clean wipes
# untracked paths, which include the downloaded staging/ and native/.
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: bridge-win32-x64-nightly
path: native
- uses: actions/download-artifact@v4
with:
name: lib-dist-win32-nightly
path: staging
- name: Typecheck a tsconfig project (full tsc path)
# require() alone misses adapter-level Windows bugs: tsc hands the
# bridge backslash paths, and only the full lib + project open
# exercises that (the win32 "project not found" class).
run: |
mkdir staging/native
cp native/bridge.* staging/native/
mkdir proj/src
'{ "compilerOptions": { "strict": true }, "include": ["src/**/*.ts"] }' | Set-Content proj/tsconfig.json
'export const x: number = 1;' | Set-Content proj/src/index.ts
node staging/lib/_tsc.js --noEmit -p proj/tsconfig.json
echo "TSC-P-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Incremental single-project build (tsc --noEmit --incremental)
run: |
mkdir inc/src
'{ "compilerOptions": { "composite": true, "tsBuildInfoFile": "./.tsbuildinfo", "strict": true, "noEmit": true }, "include": ["src/**/*.ts"] }' | Set-Content inc/tsconfig.json
'export const x: number = 1;' | Set-Content inc/src/index.ts
'import { x } from "./index"; export const y: number = x;' | Set-Content inc/src/dep.ts
node staging/lib/_tsc.js --noEmit -p inc/tsconfig.json
echo "TSC-INC-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node staging/lib/_tsc.js --noEmit -p inc/tsconfig.json
echo "TSC-INC-RERUN-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Solution-build a composite project graph (tsc -b)
run: |
mkdir sol/a/src, sol/b/src
'{ "files": [], "references": [{ "path": "./a" }, { "path": "./b" }] }' | Set-Content sol/tsconfig.json
'{ "compilerOptions": { "composite": true, "strict": true, "outDir": "dist", "declaration": true }, "include": ["src/**/*.ts"] }' | Set-Content sol/a/tsconfig.json
'{ "compilerOptions": { "composite": true, "strict": true, "outDir": "dist", "declaration": true }, "references": [{ "path": "../a" }], "include": ["src/**/*.ts"] }' | Set-Content sol/b/tsconfig.json
'export const a: number = 1;' | Set-Content sol/a/src/index.ts
'import { a } from "../../a/src/index"; export const b: number = a;' | Set-Content sol/b/src/index.ts
node staging/lib/_tsc.js -b sol/tsconfig.json
echo "TSC-B-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node staging/lib/_tsc.js -b sol/tsconfig.json
echo "TSC-B-RERUN-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Checker type query keeps proto methods (component-meta class)
env:
GODEBUG: asyncpreemptoff=1
run: |
@'
const ts = require('./staging/lib/typescript.js');
const fs = require('fs');
fs.mkdirSync('cq/src', { recursive: true });
fs.writeFileSync('cq/tsconfig.json', JSON.stringify({ compilerOptions: { strict: true, noEmit: true }, include: ['src/**/*.ts'] }));
fs.writeFileSync('cq/src/index.ts', 'export function Foo(x: string): number { return 1; }\n');
const host = {
getScriptFileNames: () => [process.cwd() + '/cq/src/index.ts'],
getScriptVersion: () => '1',
getScriptSnapshot: f => fs.existsSync(f) ? ts.ScriptSnapshot.fromString(fs.readFileSync(f, 'utf8')) : undefined,
getCurrentDirectory: () => process.cwd() + '/cq',
getCompilationSettings: () => ({ strict: true, noEmit: true, configFilePath: process.cwd() + '/cq/tsconfig.json' }),
getDefaultLibFileName: o => ts.getDefaultLibFilePath(o),
fileExists: ts.sys.fileExists, readFile: ts.sys.readFile, readDirectory: ts.sys.readDirectory,
directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories,
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
getNewLine: () => '\n',
};
const ls = ts.createLanguageService(host);
const program = ls.getProgram();
const checker = program.getTypeChecker();
const sf = program.getSourceFile(process.cwd() + '/cq/src/index.ts');
if (!sf || !sf.statements || !sf.statements.length) { console.error('FAIL: no source file statements'); process.exit(1); }
const t = checker.getTypeAtLocation(sf.statements[0].name);
if (typeof t.getConstructSignatures !== 'function' || typeof t.getCallSignatures !== 'function') {
console.error('FAIL: type lacks proto methods (fallback not fixed up)'); process.exit(1);
}
const callSigs = t.getCallSignatures();
if (callSigs.length !== 1) { console.error('FAIL: node mapping broken — expected 1 call signature, got ' + callSigs.length); process.exit(1); }
const bsSf = program.getSourceFile(require('path').join(process.cwd(), 'cq', 'src', 'index.ts'));
if (!bsSf) { console.error('FAIL: getSourceFile(backslash path) returned undefined'); process.exit(1); }
console.log('CHECKER-QUERY-OK');
'@ | Set-Content -Encoding ascii checker-query.js
node checker-query.js
echo "CHECKER-QUERY-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Host-overlay module resolves (Volar .vue shape)
env:
GODEBUG: asyncpreemptoff=1
run: |
@'
const ts = require('./staging/lib/typescript.js');
const fs = require('fs');
fs.mkdirSync('ov', { recursive: true });
fs.writeFileSync('ov/tsconfig.json', JSON.stringify({ compilerOptions: { strict: true, noEmit: true, allowArbitraryExtensions: true }, include: ['**/*'] }));
fs.writeFileSync('ov/main.ts', 'import C from "./component.vue";\nexport const n: number = C;\n');
const vueTs = 'const C: number = 1;\nexport default C;\n';
const host = {
getScriptFileNames: () => [process.cwd() + '/ov/main.ts', process.cwd() + '/ov/component.vue'],
getScriptVersion: () => '1',
getScriptSnapshot: f => f.endsWith('component.vue') ? ts.ScriptSnapshot.fromString(vueTs) : (fs.existsSync(f) ? ts.ScriptSnapshot.fromString(fs.readFileSync(f, 'utf8')) : undefined),
getCurrentDirectory: () => process.cwd() + '/ov',
getCompilationSettings: () => ({ strict: true, noEmit: true, allowArbitraryExtensions: true, configFilePath: process.cwd() + '/ov/tsconfig.json' }),
getDefaultLibFileName: o => ts.getDefaultLibFilePath(o),
fileExists: f => f.endsWith('component.vue') || ts.sys.fileExists(f),
readFile: f => f.endsWith('component.vue') ? vueTs : ts.sys.readFile(f),
readDirectory: ts.sys.readDirectory, directoryExists: ts.sys.directoryExists, getDirectories: ts.sys.getDirectories,
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
getNewLine: () => '\n',
};
const ls = ts.createLanguageService(host);
const program = ls.getProgram();
const sf = program.getSourceFile(process.cwd() + '/ov/main.ts');
const diags = program.getSemanticDiagnostics(sf);
if (diags.some(d => d.code === 2306)) { console.error('FAIL: .vue overlay not applied (TS2306 not a module)'); process.exit(1); }
if (diags.length) { console.error('FAIL: unexpected diagnostics ' + JSON.stringify(diags.map(d => d.code))); process.exit(1); }
console.log('OVERLAY-MODULE-OK');
'@ | Set-Content -Encoding ascii overlay-module.js
node overlay-module.js
echo "OVERLAY-MODULE-EXIT=$LASTEXITCODE"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Electron-host load probe (issue #44 class)
# VS Code runs tsserver as Code.exe (ELECTRON_RUN_AS_NODE); a
# node.exe import only binds when the host image is literally named
# node.exe, so every step above (plain node) is blind to it. The
# probe loads bridge.node under Electron-as-node and round-trips a
# session — the exact #44 failure mode.
run: |
Copy-Item -Recurse staging/lib lib
npm i --no-save electron@42
node tools/triage-electron-abi.mjs