selfware 0.2.2

Your personal AI workshop — software you own, software that lasts
Documentation
# .github/workflows/nodejs-qa.yml
# Node.js/TypeScript Quality Assurance workflow for Selfware

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"