mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-25 09:25:50 +00:00
345 lines
No EOL
13 KiB
YAML
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 |