name: CI on: push: branches: [main, dev, ci/merge-workflows] pull_request: branches: [main, dev, ci/merge-workflows] release: types: [published] delete: env: SQLX_OFFLINE: true jobs: # --------------------------------------------------------------------------- # Path detection — determines which job groups should run. # Required because workflow-level `paths` filters can't vary per job. # --------------------------------------------------------------------------- changes: if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: depot-ubuntu-24.04 outputs: server: ${{ steps.filter.outputs.server }} sdk: ${{ steps.filter.outputs.sdk }} postgres: ${{ steps.filter.outputs.postgres }} steps: - name: Checkout repository uses: actions/checkout@v6 - name: Detect changed paths id: filter uses: dorny/paths-filter@v3 with: filters: | server: - '**' - '!packages/**' - '!.github/workflows/merge-main-to-dev.yml' sdk: - 'packages/oauth-client/**' - 'packages/oauth-client-browser/**' - 'packages/oauth-client-node/**' - 'packages/lex-agent/**' postgres: - 'docker/Dockerfile.postgres' - 'docker/init-databases.sh' # --------------------------------------------------------------------------- # Server — unit tests, e2e tests, frontend build, lint # --------------------------------------------------------------------------- unit-tests: needs: changes if: needs.changes.outputs.server == 'true' runs-on: depot-ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Cache cargo uses: actions/cache@v5 with: path: | ~/.cargo/registry ~/.cargo/git target key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} restore-keys: | cargo-${{ runner.os }}- - name: Run unit tests run: cargo test --lib e2e-tests: needs: changes if: needs.changes.outputs.server == 'true' runs-on: depot-ubuntu-24.04 services: postgres: image: postgres:16-alpine env: POSTGRES_USER: happyview POSTGRES_PASSWORD: happyview POSTGRES_DB: happyview_test ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Cache cargo uses: actions/cache@v5 with: path: | ~/.cargo/registry ~/.cargo/git target key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} restore-keys: | cargo-${{ runner.os }}- - name: Add WASM target run: rustup target add wasm32-unknown-unknown - name: Build test plugin run: cargo build --manifest-path tests/fixtures/test_plugin/Cargo.toml --target wasm32-unknown-unknown --release - name: Run e2e tests env: TEST_DATABASE_URL: postgres://happyview:happyview@localhost:5432/happyview_test run: cargo test --tests frontend: needs: changes if: needs.changes.outputs.server == 'true' runs-on: depot-ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 - name: Build frontend working-directory: web run: npm ci && npm run build lint: needs: changes if: needs.changes.outputs.server == 'true' runs-on: depot-ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Cache cargo dependencies uses: actions/cache@v5 with: path: | ~/.cargo/registry ~/.cargo/git key: cargo-deps-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} restore-keys: | cargo-deps-${{ runner.os }}- - name: Check formatting run: cargo fmt -- --check - name: Clippy run: cargo clippy --all-targets -- -D warnings # --------------------------------------------------------------------------- # Server release # --------------------------------------------------------------------------- release: if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.server == 'true' needs: [changes, unit-tests, e2e-tests, frontend, lint] runs-on: depot-ubuntu-24.04 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 22 - name: Release env: GITHUB_TOKEN: ${{ secrets.DISPATCH_GH_TOKEN }} run: npx semantic-release # --------------------------------------------------------------------------- # SDK — tests + per-package releases # --------------------------------------------------------------------------- sdk-tests: needs: changes if: needs.changes.outputs.sdk == 'true' runs-on: depot-ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install - name: Build SDK packages run: bun run --filter '@happyview/*' build - name: Typecheck SDK packages run: bun run --filter '@happyview/*' typecheck - name: Test SDK packages run: bun run --filter '@happyview/*' test release-oauth-client: if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.sdk == 'true' needs: [changes, sdk-tests] runs-on: depot-ubuntu-24.04 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 - name: Install dependencies run: bun install - name: Build oauth-client run: bun run --filter '@happyview/oauth-client' build - name: Configure npm auth for bun publish run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc - name: Release oauth-client env: GITHUB_TOKEN: ${{ secrets.DISPATCH_GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} working-directory: packages/oauth-client run: npx semantic-release release-oauth-client-browser: if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.sdk == 'true' needs: [changes, release-oauth-client] runs-on: depot-ubuntu-24.04 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 - name: Install dependencies run: bun install - name: Build SDK packages run: bun run --filter '@happyview/*' build - name: Resolve workspace dependencies to published versions run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then DIST_TAG="latest" else DIST_TAG="dev" fi CORE_VERSION=$(npm view @happyview/oauth-client@${DIST_TAG} version) node -e " const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('packages/oauth-client-browser/package.json', 'utf8')); if (pkg.dependencies['@happyview/oauth-client']?.startsWith('workspace:')) { pkg.dependencies['@happyview/oauth-client'] = '^${CORE_VERSION}'; fs.writeFileSync('packages/oauth-client-browser/package.json', JSON.stringify(pkg, null, 2) + '\n'); } " - name: Configure npm auth for bun publish run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc - name: Release oauth-client-browser env: GITHUB_TOKEN: ${{ secrets.DISPATCH_GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} working-directory: packages/oauth-client-browser run: npx semantic-release release-oauth-client-node: if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.sdk == 'true' needs: [changes, release-oauth-client] runs-on: depot-ubuntu-24.04 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 - name: Install dependencies run: bun install - name: Build SDK packages run: bun run --filter '@happyview/*' build - name: Resolve workspace dependencies to published versions run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then DIST_TAG="latest" else DIST_TAG="dev" fi CORE_VERSION=$(npm view @happyview/oauth-client@${DIST_TAG} version) node -e " const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('packages/oauth-client-node/package.json', 'utf8')); if (pkg.dependencies['@happyview/oauth-client']?.startsWith('workspace:')) { pkg.dependencies['@happyview/oauth-client'] = '^${CORE_VERSION}'; fs.writeFileSync('packages/oauth-client-node/package.json', JSON.stringify(pkg, null, 2) + '\n'); } " - name: Configure npm auth for bun publish run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc - name: Release oauth-client-node env: GITHUB_TOKEN: ${{ secrets.DISPATCH_GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} working-directory: packages/oauth-client-node run: npx semantic-release release-lex-agent: if: >- github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.sdk == 'true' needs: [changes, release-oauth-client] runs-on: depot-ubuntu-24.04 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 - name: Install dependencies run: bun install - name: Build SDK packages run: bun run --filter '@happyview/*' build - name: Resolve workspace dependencies to published versions run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then DIST_TAG="latest" else DIST_TAG="dev" fi CORE_VERSION=$(npm view @happyview/oauth-client@${DIST_TAG} version) node -e " const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('packages/lex-agent/package.json', 'utf8')); if (pkg.dependencies['@happyview/oauth-client']?.startsWith('workspace:')) { pkg.dependencies['@happyview/oauth-client'] = '^${CORE_VERSION}'; fs.writeFileSync('packages/lex-agent/package.json', JSON.stringify(pkg, null, 2) + '\n'); } " - name: Configure npm auth for bun publish run: echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc - name: Release lex-agent env: GITHUB_TOKEN: ${{ secrets.DISPATCH_GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} working-directory: packages/lex-agent run: npx semantic-release # --------------------------------------------------------------------------- # Docker — main app image (triggered by GitHub release) # --------------------------------------------------------------------------- docker: if: github.event_name == 'release' && startsWith(github.event.release.tag_name, 'v') runs-on: ${{ matrix.runs-on }} permissions: contents: read packages: write id-token: write strategy: fail-fast: false matrix: include: - platform: linux/amd64 runs-on: depot-ubuntu-24.04 - platform: linux/arm64 runs-on: depot-ubuntu-24.04-arm env: GHCR_IMAGE: ghcr.io/${{ github.repository }} ATCR_IMAGE: atcr.io/${{ secrets.ATCR_NAMESPACE }}/happyview steps: - name: Checkout repository uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # TODO: Re-enable once ATCR rate limit issue is resolved # - name: Log in to ATCR # uses: docker/login-action@v3 # with: # registry: atcr.io # username: ${{ secrets.ATCR_USERNAME }} # password: ${{ secrets.ATCR_PASSWORD }} - name: Extract GHCR metadata id: meta-ghcr uses: docker/metadata-action@v5 with: images: ${{ env.GHCR_IMAGE }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} type=sha # TODO: Re-enable once ATCR rate limit issue is resolved # - name: Extract ATCR metadata # id: meta-atcr # uses: docker/metadata-action@v5 # with: # images: ${{ env.ATCR_IMAGE }} # tags: | # type=semver,pattern={{version}} # type=semver,pattern={{major}}.{{minor}} # type=semver,pattern={{major}} # type=sha - name: Build and push to GHCR by digest id: build uses: docker/build-push-action@v5 with: context: . platforms: ${{ matrix.platform }} labels: ${{ steps.meta-ghcr.outputs.labels }} build-args: | HAPPYVIEW_VERSION=${{ github.event.release.tag_name }} cache-from: type=gha,scope=build-${{ matrix.platform }} cache-to: type=gha,scope=build-${{ matrix.platform }},mode=max,ignore-error=true outputs: type=image,"name=${{ env.GHCR_IMAGE }}",push-by-digest=true,name-canonical=true,push=true # TODO: Re-enable once ATCR rate limit issue is resolved # - name: Push to ATCR by digest # id: build-atcr # continue-on-error: true # uses: docker/build-push-action@v5 # with: # context: . # platforms: ${{ matrix.platform }} # labels: ${{ steps.meta-atcr.outputs.labels }} # build-args: | # HAPPYVIEW_VERSION=${{ github.event.release.tag_name }} # cache-from: type=gha,scope=build-${{ matrix.platform }} # outputs: type=image,"name=${{ env.ATCR_IMAGE }}",push-by-digest=true,name-canonical=true,push=true - name: Create GHCR manifest run: | GHCR_TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta-ghcr.outputs.json }}') docker buildx imagetools create --append $GHCR_TAGS \ ${{ env.GHCR_IMAGE }}@${{ steps.build.outputs.digest }} 2>/dev/null || \ docker buildx imagetools create $GHCR_TAGS \ ${{ env.GHCR_IMAGE }}@${{ steps.build.outputs.digest }} # TODO: Re-enable once ATCR rate limit issue is resolved # - name: Create ATCR manifest # if: steps.build-atcr.outcome == 'success' # continue-on-error: true # run: | # ATCR_TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta-atcr.outputs.json }}') # if [ -n "$ATCR_TAGS" ]; then # docker buildx imagetools create --append $ATCR_TAGS \ # ${{ env.ATCR_IMAGE }}@${{ steps.build-atcr.outputs.digest }} 2>/dev/null || \ # docker buildx imagetools create $ATCR_TAGS \ # ${{ env.ATCR_IMAGE }}@${{ steps.build-atcr.outputs.digest }} # fi # --------------------------------------------------------------------------- # Docker — Postgres image (only when Postgres Docker files change) # --------------------------------------------------------------------------- docker-postgres: needs: changes if: >- github.event_name == 'push' && needs.changes.outputs.postgres == 'true' runs-on: depot-ubuntu-24.04 permissions: contents: read packages: write env: GHCR_POSTGRES_IMAGE: ghcr.io/${{ github.repository }}-postgres steps: - name: Checkout repository uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: context: docker file: docker/Dockerfile.postgres push: true tags: ${{ env.GHCR_POSTGRES_IMAGE }}:latest cache-from: type=gha,scope=postgres cache-to: type=gha,scope=postgres,mode=max,ignore-error=true # --------------------------------------------------------------------------- # Mirror to Tangled # --------------------------------------------------------------------------- mirror: if: github.event_name == 'push' || github.event_name == 'delete' runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: yesolutions/mirror-action@master with: REMOTE: "git@tangled.org:gamesgamesgamesgames.games/happyview" GIT_SSH_PRIVATE_KEY: ${{ secrets.TANGLED_SSH_PRIVATE_KEY }} GIT_SSH_NO_VERIFY_HOST: "true"