vibetunnel/.github/workflows/node.yml

345 lines
No EOL
13 KiB
YAML

name: Node.js CI
on:
workflow_call:
permissions:
contents: read
pull-requests: write
issues: write
jobs:
node-ci:
name: Node.js CI - Lint, Build, Test, Type Check
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@v4
with:
version: 10
run_install: false
- 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
# Run all checks - build first, then tests
- name: Run all checks
working-directory: web
run: |
# Create a temporary directory for outputs
mkdir -p ci-outputs
# Run format, lint, typecheck, and audit in parallel (these don't depend on build)
(
echo "Starting format check..."
pnpm run format:check > ci-outputs/format-output.txt 2>&1
echo $? > ci-outputs/format-exit-code.txt
echo "Format check completed"
) &
(
echo "Starting lint..."
pnpm run lint:biome > ci-outputs/lint-output.txt 2>&1
echo $? > ci-outputs/lint-exit-code.txt
echo "Lint completed"
) &
(
echo "Starting type check..."
pnpm run typecheck > ci-outputs/typecheck-output.txt 2>&1
echo $? > ci-outputs/typecheck-exit-code.txt
echo "Type check completed"
) &
(
echo "Starting security audit..."
pnpm audit --audit-level=moderate > ci-outputs/audit-output.txt 2>&1 || true
echo 0 > ci-outputs/audit-exit-code.txt # Don't fail on audit
echo "Audit completed"
) &
# Wait for parallel checks
wait
# Run build (must complete before tests)
echo "Starting build..."
export ESBUILD_MAX_WORKERS=$(nproc)
pnpm run build:ci > ci-outputs/build-output.txt 2>&1
echo $? > ci-outputs/build-exit-code.txt
echo "Build completed"
# Run tests after build completes
echo "Starting tests..."
# Run client and server tests sequentially to avoid conflicts
pnpm run test:client:coverage > ci-outputs/test-client-output.txt 2>&1
CLIENT_EXIT=$?
pnpm run test:server:coverage > ci-outputs/test-server-output.txt 2>&1
SERVER_EXIT=$?
# Return non-zero if either test failed
if [ $CLIENT_EXIT -ne 0 ] || [ $SERVER_EXIT -ne 0 ]; then
echo 1 > ci-outputs/test-exit-code.txt
else
echo 0 > ci-outputs/test-exit-code.txt
fi
echo "Tests completed"
echo "All checks completed"
# Process results
- name: Process check results
if: always()
id: results
working-directory: web
run: |
# Read exit codes
FORMAT_EXIT=$(cat ci-outputs/format-exit-code.txt || echo 1)
LINT_EXIT=$(cat ci-outputs/lint-exit-code.txt || echo 1)
TYPECHECK_EXIT=$(cat ci-outputs/typecheck-exit-code.txt || echo 1)
BUILD_EXIT=$(cat ci-outputs/build-exit-code.txt || echo 1)
TEST_EXIT=$(cat ci-outputs/test-exit-code.txt || echo 1)
# Set outputs
echo "format_result=$FORMAT_EXIT" >> $GITHUB_OUTPUT
echo "lint_result=$LINT_EXIT" >> $GITHUB_OUTPUT
echo "typecheck_result=$TYPECHECK_EXIT" >> $GITHUB_OUTPUT
echo "build_result=$BUILD_EXIT" >> $GITHUB_OUTPUT
echo "test_result=$TEST_EXIT" >> $GITHUB_OUTPUT
# Read outputs for reporting
echo 'format_output<<EOF' >> $GITHUB_OUTPUT
cat ci-outputs/format-output.txt 2>/dev/null || echo "No output" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
echo 'lint_output<<EOF' >> $GITHUB_OUTPUT
cat ci-outputs/lint-output.txt 2>/dev/null || echo "No output" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
echo 'typecheck_output<<EOF' >> $GITHUB_OUTPUT
cat ci-outputs/typecheck-output.txt 2>/dev/null || echo "No output" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
echo 'build_output<<EOF' >> $GITHUB_OUTPUT
tail -n 50 ci-outputs/build-output.txt 2>/dev/null || echo "No output" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
echo 'test_output<<EOF' >> $GITHUB_OUTPUT
tail -n 100 ci-outputs/test-client-output.txt 2>/dev/null || echo "No client test output" >> $GITHUB_OUTPUT
echo "---" >> $GITHUB_OUTPUT
tail -n 100 ci-outputs/test-server-output.txt 2>/dev/null || echo "No server test output" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
echo 'audit_output<<EOF' >> $GITHUB_OUTPUT
cat ci-outputs/audit-output.txt 2>/dev/null || echo "No output" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
# Determine overall result
if [ $FORMAT_EXIT -ne 0 ] || [ $LINT_EXIT -ne 0 ] || [ $TYPECHECK_EXIT -ne 0 ] || [ $BUILD_EXIT -ne 0 ] || [ $TEST_EXIT -ne 0 ]; then
echo "overall_result=failure" >> $GITHUB_OUTPUT
else
echo "overall_result=success" >> $GITHUB_OUTPUT
fi
# Generate coverage summary
- 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
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
else
echo '{"error": "No server coverage data found"}' > coverage-server-summary.json
fi
# Report results
- name: Report Format Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Biome Formatting'
lint-result: ${{ steps.results.outputs.format_result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.results.outputs.format_output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Report Lint Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Biome Linting'
lint-result: ${{ steps.results.outputs.lint_result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.results.outputs.lint_output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Report TypeCheck Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js TypeScript Type Checking'
lint-result: ${{ steps.results.outputs.typecheck_result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.results.outputs.typecheck_output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Report Build Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Build'
lint-result: ${{ steps.results.outputs.build_result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.results.outputs.build_output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Report Test Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Tests'
lint-result: ${{ steps.results.outputs.test_result == '0' && 'success' || 'failure' }}
lint-output: ${{ steps.results.outputs.test_output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Report Audit Results
if: always()
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Security Audit'
lint-result: 'success'
lint-output: ${{ steps.results.outputs.audit_output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
# Format and report coverage
- name: Format coverage output
id: format-coverage
if: always() && github.event_name == 'pull_request'
working-directory: web
run: |
# Format client coverage
CLIENT_OUTPUT="**Client Coverage:**\n"
if [ -f coverage-client-summary.json ] && [ "$(jq -r '.error // empty' coverage-client-summary.json)" = "" ]; then
CLIENT_LINES=$(jq -r '.lines.pct' coverage-client-summary.json)
CLIENT_FUNCTIONS=$(jq -r '.functions.pct' coverage-client-summary.json)
CLIENT_BRANCHES=$(jq -r '.branches.pct' coverage-client-summary.json)
CLIENT_STATEMENTS=$(jq -r '.statements.pct' coverage-client-summary.json)
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 [ -f coverage-server-summary.json ] && [ "$(jq -r '.error // empty' coverage-server-summary.json)" = "" ]; then
SERVER_LINES=$(jq -r '.lines.pct' coverage-server-summary.json)
SERVER_FUNCTIONS=$(jq -r '.functions.pct' coverage-server-summary.json)
SERVER_BRANCHES=$(jq -r '.branches.pct' coverage-server-summary.json)
SERVER_STATEMENTS=$(jq -r '.statements.pct' coverage-server-summary.json)
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
if: always() && github.event_name == 'pull_request'
uses: ./.github/actions/lint-reporter
with:
title: 'Node.js Test Coverage'
lint-result: 'success'
lint-output: ${{ steps.format-coverage.outputs.output }}
github-token: ${{ secrets.GITHUB_TOKEN }}
# Upload artifacts
- name: Upload coverage artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: node-coverage
path: |
web/coverage-client-summary.json
web/coverage-server-summary.json
web/coverage/client/lcov.info
web/coverage/server/lcov.info
# Check overall result
- name: Check overall result
if: always()
run: |
if [ "${{ steps.results.outputs.overall_result }}" = "failure" ]; then
echo "::error::One or more checks failed"
exit 1
fi