Skip to content

CI

CI #38

Workflow file for this run

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"