CI #38
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, master, dev] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main, master] | |
| env: | |
| NODE_VERSION_PRIMARY: '22' | |
| jobs: | |
| # ── Lint ────────────────────────────────────────────────────────────────── | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| - run: npm ci | |
| - run: npm run lint | |
| # ── Typecheck (daemon + server) ─────────────────────────────────────────── | |
| typecheck: | |
| name: Typecheck | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| - run: npm ci | |
| - name: Install server deps | |
| run: npm ci | |
| working-directory: server | |
| - name: Daemon | |
| run: npx tsc --noEmit | |
| - name: Server | |
| run: npx tsc --noEmit | |
| working-directory: server | |
| # ── Secret scanning ──────────────────────────────────────────────────────── | |
| secret-scan: | |
| name: Secret Scan (gitleaks) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: gitleaks/gitleaks-action@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ── Daemon unit tests ───────────────────────────────────────────────────── | |
| unit-tests: | |
| name: Unit Tests (Node ${{ matrix.node }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| node: ['20', '22'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| cache: 'npm' | |
| - run: npm ci | |
| - run: npm run test:unit | |
| # ── Web frontend tests ──────────────────────────────────────────────────── | |
| web-tests: | |
| name: Web Tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| - run: npm ci | |
| - name: Install web deps (needed for tsx component tests) | |
| run: npm ci | |
| working-directory: web | |
| - run: npm run test:web | |
| # ── Server unit tests ───────────────────────────────────────────────────── | |
| server-tests: | |
| name: Server Unit Tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - run: npm ci | |
| - run: npm run test:server | |
| - run: npm ci | |
| working-directory: server | |
| - name: Run server-native tests (auth-flow, proxy-addr — require server/node_modules) | |
| run: npm test | |
| working-directory: server | |
| # ── Server DB integration tests (testcontainers + real PostgreSQL) ───────── | |
| server-db-tests: | |
| name: Server DB Integration Tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| cache-dependency-path: server/package-lock.json | |
| - run: npm ci | |
| working-directory: server | |
| - name: Run DB integration tests | |
| run: npm run test:integration | |
| working-directory: server | |
| env: | |
| TESTCONTAINERS_RYUK_DISABLED: 'true' | |
| # ── E2E tests (tmux installed — tests run for real) ────────────────────── | |
| e2e-tests: | |
| name: E2E Tests | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| - name: Install tmux | |
| run: sudo apt-get install -y tmux | |
| - name: Prime tmux server (ensures socket dir exists) | |
| run: tmux new-session -d -s init && tmux kill-session -t init | |
| - run: npm ci | |
| - name: Run pipe-pane e2e tests | |
| run: npx vitest run test/e2e/pipe-pane-stream.test.ts | |
| - name: Run other e2e tests | |
| run: npm run test:e2e | |
| # ── Coverage ────────────────────────────────────────────────────────────── | |
| coverage: | |
| name: Coverage Report | |
| runs-on: ubuntu-latest | |
| needs: [unit-tests, web-tests] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| - name: Install tmux | |
| run: sudo apt-get install -y tmux | |
| - name: Prime tmux server | |
| run: tmux new-session -d -s init && tmux kill-session -t init | |
| - run: npm ci | |
| - name: Install web deps (needed for tsx component tests) | |
| run: npm ci | |
| working-directory: web | |
| - run: npm run test:coverage | |
| - name: Upload to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| - name: Comment PR with coverage diff | |
| if: github.event_name == 'pull_request' | |
| uses: davelosert/vitest-coverage-report-action@v2 | |
| # ── Publish to npm ──────────────────────────────────────────────────────── | |
| publish: | |
| name: Publish to npm | |
| runs-on: ubuntu-latest | |
| needs: [docker] | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION_PRIMARY }} | |
| cache: 'npm' | |
| registry-url: 'https://registry.npmjs.org' | |
| - run: npm install -g npm@latest | |
| - run: npm ci | |
| - name: Install web deps | |
| run: npm ci | |
| working-directory: web | |
| - run: npm run build | |
| - name: Set version | |
| run: npm version ${{ needs.docker.outputs.npm_version }} --no-git-tag-version | |
| - run: npm publish --provenance --access public | |
| # ── Build & push Docker image ───────────────────────────────────────────── | |
| docker: | |
| name: Docker Build & Push | |
| runs-on: ubuntu-latest | |
| needs: [lint, typecheck, secret-scan, unit-tests, web-tests, server-tests, server-db-tests, e2e-tests] | |
| if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' | |
| outputs: | |
| npm_version: ${{ steps.git_tag.outputs.npm_version }} | |
| permissions: | |
| contents: write | |
| packages: write | |
| env: | |
| IMAGE: ghcr.io/im4codes/imcodes | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # full history needed for git rev-list --count | |
| - name: Get build timestamp | |
| id: ts | |
| run: echo "value=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Resolve tags | |
| id: tags | |
| run: | | |
| set -euo pipefail | |
| DATE_TAG="v$(date -u +%Y.%-m.%-d)" | |
| SHA_SHORT="${GITHUB_SHA::7}" | |
| TAGS="${IMAGE}:latest" | |
| TAGS="${TAGS},${IMAGE}:${DATE_TAG}" | |
| TAGS="${TAGS},${IMAGE}:${DATE_TAG}-${SHA_SHORT}" | |
| TAGS="${TAGS},${IMAGE}:sha-${SHA_SHORT}" | |
| echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" | |
| echo "date_tag=${DATE_TAG}" >> "$GITHUB_OUTPUT" | |
| - name: Get OTA version (commit count) | |
| id: ota | |
| run: echo "version=$(git rev-list --count HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: server/Dockerfile | |
| platforms: linux/amd64 | |
| tags: ${{ steps.tags.outputs.tags }} | |
| build-args: | | |
| BUILD_TIME=${{ steps.ts.outputs.value }} | |
| OTA_VERSION=${{ steps.ota.outputs.version }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| push: true | |
| - name: Create git tag | |
| id: git_tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git fetch --tags | |
| # Use YYYY.M as major.minor and total commit count as patch — always valid semver | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| NPM_VERSION="$(date -u +%Y.%-m).${COMMIT_COUNT}" | |
| GIT_TAG="v${NPM_VERSION}" | |
| # Skip if this exact tag already exists (idempotent) | |
| if ! git rev-parse "$GIT_TAG" >/dev/null 2>&1; then | |
| git tag "$GIT_TAG" | |
| git push origin "$GIT_TAG" | |
| fi | |
| echo "npm_version=${NPM_VERSION}" >> "$GITHUB_OUTPUT" |