name: CI on: push: branches: [main, dev] pull_request: branches: [main, dev] 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 == 'pull_request' || github.event_name == 'push' runs-on: depot-ubuntu-24.04 outputs: server: ${{ steps.filter.outputs.server }} oauth-client: ${{ steps.filter.outputs.oauth-client }} oauth-client-browser: ${{ steps.filter.outputs.oauth-client-browser }} oauth-client-node: ${{ steps.filter.outputs.oauth-client-node }} lex-agent: ${{ steps.filter.outputs.lex-agent }} 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' oauth-client: - 'packages/oauth-client/**' oauth-client-browser: - 'packages/oauth-client/**' - 'packages/oauth-client-browser/**' oauth-client-node: - 'packages/oauth-client/**' - 'packages/oauth-client-node/**' lex-agent: - 'packages/oauth-client/**' - 'packages/lex-agent/**' # --------------------------------------------------------------------------- # 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.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 outputs: version: ${{ steps.semantic.outputs.version }} 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 id: semantic env: GITHUB_TOKEN: ${{ secrets.DISPATCH_GH_TOKEN }} run: npx -p semantic-release -p semantic-release-gha-output semantic-release # --------------------------------------------------------------------------- # SDK — per-package tests + releases # --------------------------------------------------------------------------- test-oauth-client: needs: changes if: needs.changes.outputs.oauth-client == '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 run: bun run --filter '@happyview/oauth-client' build - name: Typecheck run: bun run --filter '@happyview/oauth-client' typecheck - name: Test run: bun run --filter '@happyview/oauth-client' test test-oauth-client-browser: needs: changes if: needs.changes.outputs.oauth-client-browser == '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 oauth-client run: bun run --filter '@happyview/oauth-client' build - name: Build oauth-client-browser run: bun run --filter '@happyview/oauth-client-browser' build - name: Typecheck run: bun run --filter '@happyview/oauth-client-browser' typecheck - name: Test run: bun run --filter '@happyview/oauth-client-browser' test test-oauth-client-node: needs: changes if: needs.changes.outputs.oauth-client-node == '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 oauth-client run: bun run --filter '@happyview/oauth-client' build - name: Build oauth-client-node run: bun run --filter '@happyview/oauth-client-node' build - name: Typecheck run: bun run --filter '@happyview/oauth-client-node' typecheck - name: Test run: bun run --filter '@happyview/oauth-client-node' test test-lex-agent: needs: changes if: needs.changes.outputs.lex-agent == '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 oauth-client run: bun run --filter '@happyview/oauth-client' build - name: Build lex-agent run: bun run --filter '@happyview/lex-agent' build - name: Typecheck run: bun run --filter '@happyview/lex-agent' typecheck - name: Test run: bun run --filter '@happyview/lex-agent' test release-oauth-client: if: >- (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.oauth-client == 'true' needs: [changes, test-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 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: >- always() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.oauth-client-browser == 'true' && needs.test-oauth-client-browser.result == 'success' && (needs.release-oauth-client.result == 'success' || needs.changes.outputs.oauth-client != 'true') needs: [changes, test-oauth-client-browser, 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: >- always() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.oauth-client-node == 'true' && needs.test-oauth-client-node.result == 'success' && (needs.release-oauth-client.result == 'success' || needs.changes.outputs.oauth-client != 'true') needs: [changes, test-oauth-client-node, 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: >- always() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && needs.changes.outputs.lex-agent == 'true' && needs.test-lex-agent.result == 'success' && (needs.release-oauth-client.result == 'success' || needs.changes.outputs.oauth-client != 'true') needs: [changes, test-lex-agent, 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: needs: release if: needs.release.outputs.version != '' 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: Parse version id: version run: | VERSION="${{ needs.release.outputs.version }}" MAJOR="${VERSION%%.*}" MINOR="${VERSION#*.}" MINOR="${MINOR%%.*}" echo "full=${VERSION}" >> "$GITHUB_OUTPUT" echo "major=${MAJOR}" >> "$GITHUB_OUTPUT" echo "major_minor=${MAJOR}.${MINOR}" >> "$GITHUB_OUTPUT" - name: Extract GHCR metadata id: meta-ghcr uses: docker/metadata-action@v5 with: images: ${{ env.GHCR_IMAGE }} tags: | type=raw,value=${{ steps.version.outputs.full }} type=raw,value=${{ steps.version.outputs.major_minor }} type=raw,value=${{ steps.version.outputs.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=v${{ needs.release.outputs.version }} 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 # --------------------------------------------------------------------------- # Mirror to Tangled # --------------------------------------------------------------------------- mirror: 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"