vibetunnel/.github/workflows/node.yml
Peter Steinberger bc9f505026
Fix Ghostty terminal spawn issues with dynamic delays (#408)
* Fix Ghostty terminal spawn issues with dynamic delays

- Add isTerminalRunning() helper to check if terminal app is running
- Implement dynamic delays for Ghostty based on running state
  - 0.5s delay for warm start (already running)
  - 2.0s delay for cold start (needs to launch)
- Add window count checking to ensure UI is ready
- Fix issue where commands weren't executed when Ghostty had no windows

Fixes #371

* Fix CI: Skip Node.js check when using pre-built web artifacts

- Add SKIP_NODE_CHECK=true environment variable to Mac CI build step
- Prevents install-node.sh from failing when pnpm is not available
- CI downloads pre-built web artifacts, so Node.js/pnpm are not needed

* Fix CI: Properly handle pre-built web artifacts in Mac build

- Add early exit in build-web-frontend.sh when CI has pre-built artifacts
- Set CI=true environment variable in all Xcode build steps
- Update node-path-setup.sh to skip Node.js check in CI
- Copy pre-built artifacts directly without attempting rebuild
- This prevents pnpm dependency errors in CI environment

* Fix SwiftFormat modifier order issue

- Change 'static weak' to 'weak static' in AppDelegate
- SwiftFormat requires consistent modifier ordering

* Fix CI: Include native binaries in web artifacts

- Add web/native/ directory to uploaded artifacts
- Add web/bin/vt script to uploaded artifacts
- This ensures Mac tests can find the vibetunnel executable
- Fixes test failures due to missing server binary

* Fix CI: Copy native binaries from web artifacts in Mac CI

- Update artifact extraction to copy web/native/ directory
- Also copy web/bin/ directory for vt script
- Add debugging output to show native contents
- This ensures tests can find the vibetunnel executable
2025-07-18 17:24:30 +02:00

489 lines
No EOL
17 KiB
YAML

name: Node.js CI
on:
workflow_call:
permissions:
contents: read
pull-requests: write
issues: write
# All jobs run in parallel for faster CI execution
# Using pnpm install --frozen-lockfile for reproducible installs
# Build already uses esbuild for fast TypeScript compilation
jobs:
lint:
name: Lint TypeScript/JavaScript Code
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
env:
GITHUB_REPO_NAME: ${{ github.repository }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
run_install: false
# Skip pnpm cache - testing if fresh installs are faster
# The cache was extremely large and might be slower than fresh install
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev
- name: Install dependencies
working-directory: web
run: |
pnpm config set network-concurrency 4
pnpm config set child-concurrency 2
pnpm install --frozen-lockfile --prefer-offline
- name: Check formatting with Biome
id: biome-format
working-directory: web
continue-on-error: true
run: |
pnpm run format:check 2>&1 | tee biome-format-output.txt
echo "result=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
- name: Run Biome linting
id: biome-lint
working-directory: web
continue-on-error: true
run: |
pnpm run lint:biome 2>&1 | tee biome-lint-output.txt
echo "result=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
- name: Read Biome Format Output
if: always()
id: biome-format-output
working-directory: web
run: |
if [ -f biome-format-output.txt ]; then
echo 'content<<EOF' >> $GITHUB_OUTPUT
cat biome-format-output.txt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
else
echo "content=No output" >> $GITHUB_OUTPUT
fi
- name: Read Biome Lint Output
if: always()
id: biome-lint-output
working-directory: web
run: |
if [ -f biome-lint-output.txt ]; then
echo 'content<<EOF' >> $GITHUB_OUTPUT
cat biome-lint-output.txt >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
else
echo "content=No output" >> $GITHUB_OUTPUT
fi
- name: Report Biome Format Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Biome Formatting'
lint-result: ${{ steps.biome-format.outputs.result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.biome-format-output.outputs.content }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Report Biome Lint Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Biome Linting'
lint-result: ${{ steps.biome-lint.outputs.result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.biome-lint-output.outputs.content }}
github-token: ${{ secrets.GITHUB_TOKEN }}
build-and-test:
name: Build and Test
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
env:
GITHUB_REPO_NAME: ${{ github.repository }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
run_install: false
# Skip pnpm cache - testing if fresh installs are faster
# The cache was extremely large and might be slower than fresh install
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev
- name: Cache TypeScript build info
uses: useblacksmith/cache@v5
continue-on-error: true
with:
path: |
web/dist/tsconfig.server.tsbuildinfo
web/public/tsconfig.client.tsbuildinfo
web/public/tsconfig.sw.tsbuildinfo
key: ${{ runner.os }}-tsbuild-${{ hashFiles('web/src/**/*.ts', 'web/tsconfig*.json') }}
restore-keys: |
${{ runner.os }}-tsbuild-
- name: Install dependencies
working-directory: web
run: |
pnpm config set network-concurrency 4
pnpm config set child-concurrency 2
pnpm install --frozen-lockfile --prefer-offline
- name: Build node-pty
working-directory: web
run: |
cd node-pty && npm install && npm run build
- name: Build frontend and backend
working-directory: web
run: |
# Use all available cores for esbuild
export ESBUILD_MAX_WORKERS=$(nproc)
pnpm run build:ci
- name: Run client tests with coverage
id: test-client-coverage
working-directory: web
run: |
pnpm run test:client:coverage 2>&1 | tee test-client-output.txt
echo "result=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
- name: Run server tests with coverage
id: test-server-coverage
working-directory: web
run: |
pnpm run test:server:coverage 2>&1 | tee test-server-output.txt
echo "result=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
env:
CI: true
- name: Check test results
if: always()
working-directory: web
run: |
if [ "${{ steps.test-client-coverage.outputs.result }}" != "0" ] || [ "${{ steps.test-server-coverage.outputs.result }}" != "0" ]; then
echo "::error::Tests failed"
exit 1
fi
- name: Generate coverage summaries
if: always()
working-directory: web
run: |
# Process client coverage
if [ -f coverage/client/coverage-summary.json ]; then
node -e "
const coverage = require('./coverage/client/coverage-summary.json');
const total = coverage.total;
const summary = {
type: 'client',
lines: { pct: total.lines.pct, covered: total.lines.covered, total: total.lines.total },
statements: { pct: total.statements.pct, covered: total.statements.covered, total: total.statements.total },
functions: { pct: total.functions.pct, covered: total.functions.covered, total: total.functions.total },
branches: { pct: total.branches.pct, covered: total.branches.covered, total: total.branches.total }
};
console.log(JSON.stringify(summary, null, 2));
" > coverage-client-summary.json
if [ -f test-client-output.txt ]; then
tail -n 50 test-client-output.txt > coverage-client-output.txt
fi
else
echo '{"error": "No client coverage data found"}' > coverage-client-summary.json
fi
# Process server coverage
if [ -f coverage/server/coverage-summary.json ]; then
node -e "
const coverage = require('./coverage/server/coverage-summary.json');
const total = coverage.total;
const summary = {
type: 'server',
lines: { pct: total.lines.pct, covered: total.lines.covered, total: total.lines.total },
statements: { pct: total.statements.pct, covered: total.statements.covered, total: total.statements.total },
functions: { pct: total.functions.pct, covered: total.functions.covered, total: total.functions.total },
branches: { pct: total.branches.pct, covered: total.branches.covered, total: total.branches.total }
};
console.log(JSON.stringify(summary, null, 2));
" > coverage-server-summary.json
if [ -f test-server-output.txt ]; then
tail -n 50 test-server-output.txt > coverage-server-output.txt
fi
else
echo '{"error": "No server coverage data found"}' > coverage-server-summary.json
fi
# Create combined summary for backward compatibility
node -e "
const clientCov = require('./coverage-client-summary.json');
const serverCov = require('./coverage-server-summary.json');
const combined = {
client: clientCov,
server: serverCov
};
console.log(JSON.stringify(combined, null, 2));
" > coverage-summary-formatted.json || echo '{"error": "Failed to combine coverage data"}' > coverage-summary-formatted.json
- name: Upload coverage artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: node-coverage
path: |
web/coverage-summary-formatted.json
web/coverage-client-summary.json
web/coverage-server-summary.json
web/coverage-client-output.txt
web/coverage-server-output.txt
web/coverage/client/lcov.info
web/coverage/server/lcov.info
- name: List build artifacts before upload
working-directory: web
run: |
echo "=== Contents of dist directory ==="
find dist -type f | head -20 || echo "No files in dist"
echo "=== Contents of public/bundle directory ==="
find public/bundle -type f | head -20 || echo "No files in public/bundle"
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: web-build
path: |
web/dist/
web/public/bundle/
web/native/
web/bin/vt
retention-days: 1
if-no-files-found: error
type-check:
name: TypeScript Type Checking
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
env:
GITHUB_REPO_NAME: ${{ github.repository }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
run_install: false
# Skip pnpm cache - testing if fresh installs are faster
# The cache was extremely large and might be slower than fresh install
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev
- name: Install dependencies
working-directory: web
run: |
pnpm config set network-concurrency 4
pnpm config set child-concurrency 2
pnpm install --frozen-lockfile --prefer-offline
- name: Build node-pty for TypeScript
working-directory: web
run: |
cd node-pty && npm install && npm run build
- name: Check TypeScript types
working-directory: web
run: pnpm run typecheck
audit:
name: Security Audit
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
run_install: false
- name: Run pnpm audit
working-directory: web
run: pnpm audit --audit-level=moderate || true
# || true to not fail the build on vulnerabilities, but still report them
report-coverage:
name: Report Coverage Results
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
needs: [build-and-test]
# Keep Node.js coverage reporting for PRs since it's fast
if: always() && github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
name: node-coverage
path: web/coverage-artifacts
- name: Read coverage summaries
id: coverage
working-directory: web
run: |
# Initialize result variables
CLIENT_RESULT="failure"
SERVER_RESULT="failure"
# Process client coverage
if [ -f coverage-artifacts/coverage-client-summary.json ]; then
CLIENT_JSON=$(cat coverage-artifacts/coverage-client-summary.json)
CLIENT_LINES=$(echo "$CLIENT_JSON" | jq -r '.lines.pct // 0')
CLIENT_FUNCTIONS=$(echo "$CLIENT_JSON" | jq -r '.functions.pct // 0')
CLIENT_BRANCHES=$(echo "$CLIENT_JSON" | jq -r '.branches.pct // 0')
CLIENT_STATEMENTS=$(echo "$CLIENT_JSON" | jq -r '.statements.pct // 0')
# Always report as success - we're just reporting coverage
CLIENT_RESULT="success"
echo "client_lines=$CLIENT_LINES" >> $GITHUB_OUTPUT
echo "client_functions=$CLIENT_FUNCTIONS" >> $GITHUB_OUTPUT
echo "client_branches=$CLIENT_BRANCHES" >> $GITHUB_OUTPUT
echo "client_statements=$CLIENT_STATEMENTS" >> $GITHUB_OUTPUT
fi
# Process server coverage
if [ -f coverage-artifacts/coverage-server-summary.json ]; then
SERVER_JSON=$(cat coverage-artifacts/coverage-server-summary.json)
SERVER_LINES=$(echo "$SERVER_JSON" | jq -r '.lines.pct // 0')
SERVER_FUNCTIONS=$(echo "$SERVER_JSON" | jq -r '.functions.pct // 0')
SERVER_BRANCHES=$(echo "$SERVER_JSON" | jq -r '.branches.pct // 0')
SERVER_STATEMENTS=$(echo "$SERVER_JSON" | jq -r '.statements.pct // 0')
# Always report as success - we're just reporting coverage
SERVER_RESULT="success"
echo "server_lines=$SERVER_LINES" >> $GITHUB_OUTPUT
echo "server_functions=$SERVER_FUNCTIONS" >> $GITHUB_OUTPUT
echo "server_branches=$SERVER_BRANCHES" >> $GITHUB_OUTPUT
echo "server_statements=$SERVER_STATEMENTS" >> $GITHUB_OUTPUT
fi
# Always report as success - we're just reporting coverage
echo "result=success" >> $GITHUB_OUTPUT
echo "client_result=$CLIENT_RESULT" >> $GITHUB_OUTPUT
echo "server_result=$SERVER_RESULT" >> $GITHUB_OUTPUT
# Format output
CLIENT_OUTPUT=""
SERVER_OUTPUT=""
if [ -f coverage-artifacts/coverage-client-output.txt ]; then
CLIENT_OUTPUT=$(tail -n 20 coverage-artifacts/coverage-client-output.txt | grep -v "^\[" | head -10)
fi
if [ -f coverage-artifacts/coverage-server-output.txt ]; then
SERVER_OUTPUT=$(tail -n 20 coverage-artifacts/coverage-server-output.txt | grep -v "^\[" | head -10)
fi
echo "client_output<<EOF" >> $GITHUB_OUTPUT
echo "$CLIENT_OUTPUT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "server_output<<EOF" >> $GITHUB_OUTPUT
echo "$SERVER_OUTPUT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Format coverage output
id: format-coverage
if: always()
run: |
# Format client coverage
CLIENT_OUTPUT="**Client Coverage:**\n"
if [ "${{ steps.coverage.outputs.client_lines }}" != "" ]; then
CLIENT_LINES="${{ steps.coverage.outputs.client_lines }}"
CLIENT_FUNCTIONS="${{ steps.coverage.outputs.client_functions }}"
CLIENT_BRANCHES="${{ steps.coverage.outputs.client_branches }}"
CLIENT_STATEMENTS="${{ steps.coverage.outputs.client_statements }}"
CLIENT_OUTPUT="${CLIENT_OUTPUT}• Lines: ${CLIENT_LINES}%\n"
CLIENT_OUTPUT="${CLIENT_OUTPUT}• Functions: ${CLIENT_FUNCTIONS}%\n"
CLIENT_OUTPUT="${CLIENT_OUTPUT}• Branches: ${CLIENT_BRANCHES}%\n"
CLIENT_OUTPUT="${CLIENT_OUTPUT}• Statements: ${CLIENT_STATEMENTS}%\n"
else
CLIENT_OUTPUT="${CLIENT_OUTPUT}No client coverage data found\n"
fi
# Format server coverage
SERVER_OUTPUT="\n**Server Coverage:**\n"
if [ "${{ steps.coverage.outputs.server_lines }}" != "" ]; then
SERVER_LINES="${{ steps.coverage.outputs.server_lines }}"
SERVER_FUNCTIONS="${{ steps.coverage.outputs.server_functions }}"
SERVER_BRANCHES="${{ steps.coverage.outputs.server_branches }}"
SERVER_STATEMENTS="${{ steps.coverage.outputs.server_statements }}"
SERVER_OUTPUT="${SERVER_OUTPUT}• Lines: ${SERVER_LINES}%\n"
SERVER_OUTPUT="${SERVER_OUTPUT}• Functions: ${SERVER_FUNCTIONS}%\n"
SERVER_OUTPUT="${SERVER_OUTPUT}• Branches: ${SERVER_BRANCHES}%\n"
SERVER_OUTPUT="${SERVER_OUTPUT}• Statements: ${SERVER_STATEMENTS}%"
else
SERVER_OUTPUT="${SERVER_OUTPUT}No server coverage data found"
fi
echo "output<<EOF" >> $GITHUB_OUTPUT
echo -e "${CLIENT_OUTPUT}${SERVER_OUTPUT}" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Report Coverage Results
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Test Coverage'
lint-result: ${{ steps.coverage.outputs.result }}
lint-output: ${{ steps.format-coverage.outputs.output }}
github-token: ${{ secrets.GITHUB_TOKEN }}