name: Node.js Quality Assurance
on:
workflow_call:
inputs:
working-directory:
description: 'Directory containing Node.js/TypeScript code'
required: false
default: '.'
type: string
coverage-threshold:
description: 'Minimum code coverage percentage'
required: false
default: '80'
type: string
node-version:
description: 'Node.js version to use'
required: false
default: '20'
type: string
outputs:
quality-score:
description: 'Overall quality score'
value: ${{ jobs.aggregate.outputs.quality-score }}
coverage:
description: 'Code coverage percentage'
value: ${{ jobs.test.outputs.coverage }}
push:
paths:
- '**.ts'
- '**.tsx'
- '**.js'
- '**.jsx'
- '**/package.json'
- '**/package-lock.json'
- '**/tsconfig.json'
pull_request:
paths:
- '**.ts'
- '**.tsx'
- '**.js'
- '**.jsx'
- '**/package.json'
- '**/package-lock.json'
- '**/tsconfig.json'
jobs:
lint:
name: Lint & Format
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory || '.' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version || '20' }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Check formatting
run: npm run format:check
typecheck:
name: Type Check
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory || '.' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version || '20' }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run TypeScript compiler
run: npm run typecheck
test:
name: Test (Node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ["18", "20", "22"]
outputs:
coverage: ${{ steps.coverage.outputs.coverage }}
defaults:
run:
working-directory: ${{ inputs.working-directory || '.' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Extract coverage
id: coverage
run: |
if [ -f coverage/coverage-summary.json ]; then
COVERAGE=$(cat coverage/coverage-summary.json | jq -r '.total.lines.pct')
else
COVERAGE="0"
fi
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: ./coverage/lcov.info
fail_ci_if_error: false
e2e:
name: E2E Tests
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory || '.' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version || '20' }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
security:
name: Security Audit
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory || '.' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version || '20' }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: Run better-npm-audit
run: npx better-npm-audit audit
build:
name: Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory || '.' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version || '20' }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build
path: dist/
aggregate:
name: Aggregate Results
needs: [lint, typecheck, test, e2e, security, build]
runs-on: ubuntu-latest
outputs:
quality-score: ${{ steps.score.outputs.score }}
steps:
- name: Calculate Quality Score
id: score
run: |
SCORE=100
echo "score=$SCORE" >> $GITHUB_OUTPUT
echo "Node.js Quality Score: $SCORE/100"