fix crash, add cover section. #878
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: Build and publish Docker image (GHCR) | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository }} | |
| ELIXIR_VERSION: "1.20.1" | |
| OTP_VERSION: "29.0.2" | |
| jobs: | |
| build: | |
| name: Build and publish (${{ matrix.adapter }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| include: | |
| - adapter: sqlite | |
| tags_suffix: "" | |
| extra_tag_suffix: "" | |
| - adapter: postgres | |
| tags_suffix: "-postgres" | |
| extra_tag_suffix: "-postgres" | |
| steps: | |
| - name: Checkout repository (full history for commit count) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Compute version | |
| id: version | |
| run: | | |
| git config --global --add safe.directory $GITHUB_WORKSPACE || true | |
| git fetch --prune --unshallow || true | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| VERSION="1.0.${COMMIT_COUNT}" | |
| echo "Generated version=${VERSION}" | |
| echo "APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "GAMEND_CONTENT_APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v2 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v2 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v2 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and (conditionally) push multi-arch image | |
| uses: docker/build-push-action@v4 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| build-args: | | |
| GAMEND_CONTENT_APP_VERSION=${{ env.APP_VERSION }} | |
| GAMEND_DB_ADAPTER=${{ matrix.adapter }} | |
| platforms: linux/amd64,linux/arm64 | |
| # amd64 reuses the smoke build's layers; arm64 keeps its own scope. | |
| # Without this every run rebuilt the Rust toolchain, every dependency | |
| # and every NIF from scratch. | |
| cache-from: type=gha,scope=${{ matrix.adapter }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.adapter }} | |
| push: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} | |
| tags: | | |
| ${{ env.IMAGE }}:latest${{ matrix.tags_suffix }} | |
| ${{ env.IMAGE }}:${{ github.sha }}${{ matrix.extra_tag_suffix }} | |
| smoke: | |
| name: Boot the image (${{ matrix.adapter }}) | |
| runs-on: ubuntu-latest | |
| # Deliberately independent of `build`: this proves the image starts and | |
| # serves traffic, which unit tests cannot. config/runtime.exs is evaluated | |
| # at container start, not at build time, so a green build says nothing | |
| # about whether the app boots. It does not gate publishing. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| adapter: [sqlite, postgres] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-buildx-action@v2 | |
| - name: Build the image (amd64, loaded locally) | |
| uses: docker/build-push-action@v4 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| build-args: | | |
| GAMEND_DB_ADAPTER=${{ matrix.adapter }} | |
| platforms: linux/amd64 | |
| load: true | |
| tags: gamend-smoke:${{ matrix.adapter }} | |
| cache-from: type=gha,scope=${{ matrix.adapter }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.adapter }} | |
| - name: Boot it and check it serves traffic | |
| run: | | |
| set -euo pipefail | |
| docker network create smoke-net | |
| # The postgres container reads its own POSTGRES_* names to | |
| # initialise; the server reads ours. They are deliberately distinct. | |
| { | |
| echo "GAMEND_AUTH_SECRET_KEY_BASE=$(head -c 48 /dev/urandom | base64 | tr -d '\n')" | |
| echo "GAMEND_HTTP_HOST=localhost" | |
| echo "GAMEND_OBSERVABILITY_LOG_LEVEL=info" | |
| } > smoke.env | |
| if [ "${{ matrix.adapter }}" = "postgres" ]; then | |
| docker run -d --name smoke-db --network smoke-net \ | |
| -e POSTGRES_PASSWORD=postgres \ | |
| -e POSTGRES_USER=postgres \ | |
| -e POSTGRES_DB=gamend_prod \ | |
| postgres:15 | |
| for _ in $(seq 1 30); do | |
| docker exec smoke-db pg_isready -U postgres >/dev/null 2>&1 && break | |
| sleep 2 | |
| done | |
| { | |
| echo "GAMEND_DB_POSTGRES_HOST=smoke-db" | |
| echo "GAMEND_DB_POSTGRES_USER=postgres" | |
| echo "GAMEND_DB_POSTGRES_PASSWORD=postgres" | |
| echo "GAMEND_DB_POSTGRES_DB=gamend_prod" | |
| } >> smoke.env | |
| fi | |
| docker run -d --name gamend-smoke --network smoke-net \ | |
| -p 4000:4000 --env-file smoke.env \ | |
| gamend-smoke:${{ matrix.adapter }} | |
| for _ in $(seq 1 60); do | |
| if curl -fsS http://localhost:4000/api/v1/health | grep -q '"status":"ok"'; then | |
| echo "healthy" | |
| exit 0 | |
| fi | |
| if [ -z "$(docker ps -q -f name=gamend-smoke)" ]; then | |
| echo "::error::container exited before serving traffic" | |
| docker logs gamend-smoke | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| echo "::error::timed out waiting for /api/v1/health" | |
| docker logs gamend-smoke | |
| exit 1 | |
| # The build arg has to reach the compiled code, not just the shell: an | |
| # ignored GAMEND_DB_ADAPTER once published a SQLite build as -postgres. | |
| - name: Verify the image compiled against the expected adapter | |
| run: | | |
| set -euo pipefail | |
| adapter=$(docker exec gamend-smoke sh -c \ | |
| 'mix run --no-start -e "IO.puts(inspect(Gamend.Repo.__adapter__()))"' | tail -1) | |
| echo "compiled adapter: $adapter" | |
| case "${{ matrix.adapter }}" in | |
| postgres) echo "$adapter" | grep -q "Ecto.Adapters.Postgres" ;; | |
| sqlite) echo "$adapter" | grep -q "Ecto.Adapters.SQLite3" ;; | |
| esac | |
| - name: Teardown | |
| if: always() | |
| run: | | |
| docker logs gamend-smoke 2>&1 | tail -50 || true | |
| docker rm -f gamend-smoke smoke-db 2>/dev/null || true | |
| docker network rm smoke-net 2>/dev/null || true | |
| lint: | |
| name: Elixir lint | |
| runs-on: ubuntu-latest | |
| container: | |
| image: elixir:1.20.1-otp-29 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install system dependencies | |
| run: | | |
| apt update | |
| apt install -y build-essential erlang-dev curl | |
| - name: Install Rust toolchain (for ex_sctp WebRTC DataChannels) | |
| run: | | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install dependencies | |
| run: MIX_ENV=dev mix deps.get | |
| - name: Install web dependencies | |
| working-directory: apps/gamend_web | |
| run: MIX_ENV=dev mix deps.get | |
| - name: Check code formatting | |
| run: MIX_ENV=dev mix format --check-formatted | |
| - name: Credo (strict) | |
| run: MIX_ENV=dev mix credo --strict | |
| - name: API conventions (docs/specs/api-conventions.md) | |
| run: MIX_ENV=dev mix gamend.api.lint | |
| - name: Credo (strict, web app) | |
| working-directory: apps/gamend_web | |
| run: MIX_ENV=dev mix credo --strict | |
| - name: Settings docs are in sync with the declarations | |
| run: | | |
| MIX_ENV=dev mix gamend.settings.env_example --check | |
| MIX_ENV=dev mix gamend.settings.guide --check | |
| MIX_ENV=dev mix gamend.theme.extract --check | |
| - name: Audit dependencies for known vulnerabilities | |
| run: MIX_ENV=dev mix deps.audit | |
| dialyzer: | |
| name: Dialyzer | |
| runs-on: ubuntu-latest | |
| container: | |
| image: elixir:1.20.1-otp-29 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install system dependencies | |
| run: | | |
| apt update | |
| apt install -y build-essential erlang-dev curl | |
| - name: Install Rust toolchain (for ex_sctp WebRTC DataChannels) | |
| run: | | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| # Each app is analysed as a standalone project below, and a standalone | |
| # project resolves its own deps/ — the root fetch does not reach them. | |
| - name: Install dependencies | |
| run: MIX_ENV=dev mix deps.get | |
| - name: Install dependencies (gamend_core) | |
| working-directory: apps/gamend_core | |
| run: MIX_ENV=dev mix deps.get | |
| - name: Install dependencies (gamend_web) | |
| working-directory: apps/gamend_web | |
| run: MIX_ENV=dev mix deps.get | |
| # The umbrella apps are path deps, so they land in the *deps* PLT. Its | |
| # cache key is mix.lock, which does not change when app source does — so | |
| # the .plt.hash is deliberately not cached: restoring it would make | |
| # dialyxir report "PLT is up to date" and skip revalidation, and every | |
| # module added since the cache was written would look like it does not | |
| # exist. Without the hash it revalidates and updates the PLT in place. | |
| # | |
| # Each standalone app build keeps its own PLT, so all three are cached. | |
| - name: Cache PLTs | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| _build/dev/*.plt | |
| apps/gamend_core/_build/dev/*.plt | |
| apps/gamend_web/_build/dev/*.plt | |
| key: plt-${{ runner.os }}-${{ hashFiles('mix.lock') }} | |
| restore-keys: | | |
| plt-${{ runner.os }}- | |
| # The root project is a thin wrapper: analysing only it covers ~11 | |
| # modules. The domain code lives in the two umbrella apps, so each must | |
| # be analysed in its own right or the job is green by vacuity. | |
| - name: Run dialyzer | |
| run: MIX_ENV=dev mix dialyzer --format github | |
| - name: Run dialyzer (gamend_core) | |
| working-directory: apps/gamend_core | |
| run: MIX_ENV=dev mix dialyzer --format github | |
| - name: Run dialyzer (gamend_web) | |
| working-directory: apps/gamend_web | |
| run: MIX_ENV=dev mix dialyzer --format github | |
| check-outdated: | |
| name: Check for outdated dependencies | |
| runs-on: ubuntu-latest | |
| container: | |
| image: elixir:1.20.1-otp-29 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install dependencies | |
| run: mix deps.get | |
| - name: Check for outdated dependencies | |
| run: | | |
| echo "Checking for outdated dependencies..." | |
| # Capture output to a file to avoid failing the step if `mix hex.outdated` returns non-zero | |
| OUT_FILE=outdated.txt | |
| mix hex.outdated > "$OUT_FILE" 2>&1 || true | |
| cat "$OUT_FILE" | |
| # Fail only if there are actionable updates ("Update possible"). Warn if there are "Update not possible" entries. | |
| if grep -q "Update possible" "$OUT_FILE"; then | |
| echo "❌ FAILURE: Some dependencies have available updates. Please update and test thoroughly before merging." | |
| # Run mix deps.update --all | |
| # mix deps.get | |
| exit 1 | |
| else | |
| if grep -q "Update not possible" "$OUT_FILE"; then | |
| echo "⚠️ Some dependencies have major-version updates that are blocked by constraints (Update not possible). Please plan major upgrades separately." | |
| else | |
| echo "✅ All dependencies are up to date or have compatible updates." | |
| fi | |
| fi | |
| # Add to GitHub step summary for better visibility | |
| echo "## Outdated Dependencies Report" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| cat "$OUT_FILE" >> $GITHUB_STEP_SUMMARY | |
| echo '```' >> $GITHUB_STEP_SUMMARY | |
| test-sqlite: | |
| name: Test with SQLite | |
| runs-on: ubuntu-latest | |
| container: | |
| image: elixir:1.20.1-otp-29 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install system dependencies | |
| run: | | |
| apt update | |
| apt install -y build-essential erlang-dev curl | |
| - name: Install Rust toolchain (for ex_sctp WebRTC DataChannels) | |
| run: | | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install dependencies | |
| run: MIX_ENV=test mix deps.get | |
| - name: Install web test dependencies | |
| working-directory: apps/gamend_web | |
| run: MIX_ENV=test mix deps.get | |
| - name: Compile | |
| run: MIX_ENV=test mix compile --warnings-as-errors | |
| - name: Run tests | |
| run: MIX_ENV=test mix test | |
| test-postgres: | |
| name: Test with PostgreSQL | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:15 | |
| env: | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_USER: postgres | |
| POSTGRES_DB: gamend_test | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| container: | |
| image: elixir:1.20.1-otp-29 | |
| # config/test.exs switches to Postgres on GAMEND_DB_POSTGRES_*; the old | |
| # POSTGRES_* names matched nothing, so this job silently tested SQLite. | |
| env: | |
| GAMEND_DB_POSTGRES_HOST: postgres | |
| GAMEND_DB_POSTGRES_USER: postgres | |
| GAMEND_DB_POSTGRES_PASSWORD: postgres | |
| GAMEND_DB_POSTGRES_DB: gamend_test | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install system dependencies | |
| run: | | |
| apt update | |
| apt install -y build-essential erlang-dev curl | |
| - name: Install Rust toolchain (for ex_sctp WebRTC DataChannels) | |
| run: | | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install dependencies | |
| run: MIX_ENV=test mix deps.get | |
| - name: Install web test dependencies | |
| working-directory: apps/gamend_web | |
| run: MIX_ENV=test mix deps.get | |
| - name: Compile | |
| run: MIX_ENV=test mix compile --warnings-as-errors | |
| - name: Run tests | |
| run: MIX_ENV=test mix test | |
| commands: | |
| name: Mix commands (setup, db.reset) | |
| runs-on: ubuntu-latest | |
| container: | |
| image: elixir:1.20.1-otp-29 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install system dependencies | |
| run: | | |
| apt update | |
| apt install -y build-essential erlang-dev curl | |
| - name: Install Rust toolchain (for ex_sctp WebRTC DataChannels) | |
| run: | | |
| curl https://sh.rustup.rs -sSf | sh -s -- -y | |
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| # Exercise the documented developer commands end-to-end (SQLite dev | |
| # database), so alias/task breakage is caught even though tests bypass | |
| # these aliases. | |
| - name: mix setup | |
| run: mix setup | |
| - name: mix db.reset | |
| run: mix db.reset | |
| publish-js-sdk: | |
| name: Publish JS SDK | |
| # Disable it temporarily | |
| if: false | |
| #if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout repository (full history for commit count) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Compute version | |
| id: version | |
| run: | | |
| git config --global --add safe.directory $GITHUB_WORKSPACE || true | |
| git fetch --prune --unshallow || true | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| # Build a simple semver style version used on CI: 1.0.<commit_count> | |
| VERSION="1.0.${COMMIT_COUNT}" | |
| echo "Generated version=${VERSION}" | |
| # Export for remaining steps in this job | |
| echo "APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "GAMEND_CONTENT_APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Set up Elixir | |
| uses: erlef/setup-beam@v1 | |
| with: | |
| elixir-version: ${{ env.ELIXIR_VERSION }} | |
| otp-version: ${{ env.OTP_VERSION }} | |
| install-hex: false | |
| install-rebar: false | |
| - name: Install Java (for openapi generator) | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '24' | |
| registry-url: 'https://registry.npmjs.org' | |
| cache: 'npm' | |
| # repository uses JS packages under the `clients` subfolder. When caching | |
| # npm dependencies we need to point setup-node at the lockfile location | |
| # so the cache key is computed correctly. | |
| cache-dependency-path: clients/package-lock.json | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install Elixir deps (for generating spec) | |
| run: | | |
| mix deps.get | |
| - name: Prepare database (app boot requires a migrated DB) | |
| run: | | |
| mix ecto.create --quiet -r Gamend.Repo | |
| mix db.migrate | |
| - name: Regenerate OpenAPI JSON | |
| run: | | |
| mix openapi.spec.json --spec GamendWeb.ApiSpec --filename clients/javascript/openapi.json --pretty=true | |
| - name: Generate JS client artifacts | |
| run: | | |
| cd clients | |
| npm ci | |
| npm run generate | |
| - name: Ensure license is included for JS SDK | |
| run: | | |
| cp LICENSE clients/javascript/LICENSE | |
| jq '.license = "MIT"' clients/javascript/package.json > clients/javascript/package.json.tmp && mv clients/javascript/package.json.tmp clients/javascript/package.json | |
| - name: Update package.json with repository field (https://github.com/appsinacup/gamend.git) | |
| run: | | |
| REPO_FIELD='"repository": { "type": "git", "url": "git+https://github.com/appsinacup/gamend.git" },' | |
| jq ". + { $REPO_FIELD }" clients/javascript/package.json > clients/javascript/package.json.tmp && mv clients/javascript/package.json.tmp clients/javascript/package.json | |
| - name: Add phoenix as a dependency | |
| run: | | |
| jq '.dependencies.phoenix = ">=1.7.0"' \ | |
| clients/javascript/package.json > clients/javascript/package.json.tmp \ | |
| && mv clients/javascript/package.json.tmp clients/javascript/package.json | |
| - name: Apply version and publish | |
| run: | | |
| cd clients/javascript | |
| echo "Publishing package with version=${{ steps.version.outputs.version }}" | |
| npm version ${{ steps.version.outputs.version }} --no-git-tag-version | |
| # Trusted Publishing (OIDC) — npm 11+ auto-detects the OIDC | |
| # environment and authenticates without tokens. Provenance | |
| # attestations are generated automatically. | |
| npm publish --access public | |
| build-godot-sdk: | |
| runs-on: ubuntu-latest | |
| name: Generate Godot client and upload addons | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Compute version | |
| id: version | |
| run: | | |
| git config --global --add safe.directory $GITHUB_WORKSPACE || true | |
| git fetch --prune --unshallow || true | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| # Build a simple semver style version used on CI: 1.0.<commit_count> | |
| VERSION="1.0.${COMMIT_COUNT}" | |
| echo "Generated version=${VERSION}" | |
| # Export for remaining steps in this job | |
| echo "APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "GAMEND_CONTENT_APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Set up Elixir (for mix openapi task) | |
| uses: erlef/setup-beam@v1 | |
| with: | |
| elixir-version: ${{ env.ELIXIR_VERSION }} | |
| otp-version: ${{ env.OTP_VERSION }} | |
| install-hex: false | |
| install-rebar: false | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install dependencies (mix deps.get) | |
| working-directory: . | |
| run: | | |
| mix deps.get | |
| - name: Prepare database (app boot requires a migrated DB) | |
| run: | | |
| mix ecto.create --quiet -r Gamend.Repo | |
| mix db.migrate | |
| - name: Run Godot generation script | |
| env: | |
| APP_VERSION: ${{ env.APP_VERSION }} | |
| run: | | |
| echo "Generating Godot client with APP_VERSION=${APP_VERSION:-$(echo $APP_VERSION)}" | |
| cd clients | |
| ./generate_godot.sh | |
| - name: Upload addons zip | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: godot_addons | |
| path: godot_addons | |
| - name: Archive | |
| shell: sh | |
| run: | | |
| zip -r "godot_addons.zip" godot_addons | |
| - name: Release | |
| if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: latest | |
| name: Gamend Godot SDK Nightly | |
| files: | | |
| godot_addons.zip | |
| generate_release_notes: false | |
| make_latest: true | |
| fail_on_unmatched_files: true | |
| publish-hex: | |
| name: Publish Hex packages | |
| if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} | |
| needs: [test-sqlite] | |
| runs-on: ubuntu-latest | |
| env: | |
| HEX_API_KEY: ${{ secrets.HEX_KEY }} | |
| steps: | |
| - name: Checkout repository (full history for commit count) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Compute version | |
| id: version | |
| run: | | |
| git config --global --add safe.directory $GITHUB_WORKSPACE || true | |
| git fetch --prune --unshallow || true | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| VERSION="1.0.${COMMIT_COUNT}" | |
| echo "Generated version=${VERSION}" | |
| echo "APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "GAMEND_CONTENT_APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Set up Elixir | |
| uses: erlef/setup-beam@v1 | |
| with: | |
| otp-version: ${{ env.OTP_VERSION }} | |
| elixir-version: ${{ env.ELIXIR_VERSION }} | |
| install-hex: false | |
| install-rebar: false | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Publish gamend_sdk | |
| working-directory: sdk | |
| run: | | |
| mix deps.get | |
| # Do not swallow the exit code: publishing silently failed for | |
| # months behind an `|| echo`. A version that already exists is | |
| # not an error to hex.publish, so a non-zero exit is real. | |
| mix hex.publish --yes | |
| - name: Publish gamend_plugin_tools | |
| working-directory: sdk_tools | |
| run: | | |
| mix deps.get | |
| # Do not swallow the exit code: publishing silently failed for | |
| # months behind an `|| echo`. A version that already exists is | |
| # not an error to hex.publish, so a non-zero exit is real. | |
| mix hex.publish --yes | |
| # gamend_core and gamend_web are NOT published to Hex. | |
| # | |
| # gamend_core depends on pigeon by git ref: upstream merged the | |
| # kadabra->mint rewrite in May 2025 and bumped mix.exs to 2.1.0 but never | |
| # released it (codedge-llc/pigeon "Publish v2.1.0"), so the newest Hex | |
| # release is still 2.0.1. Hex refuses a package with any non-Hex dep, and | |
| # 2.0.1 would drag back httpoison -> hackney and reintroduce the | |
| # vulnerability chain that issue is about. gamend_web follows: its | |
| # published form points at {:gamend_core, "~> 1.0"}, which would not exist. | |
| # | |
| # Consumers use the GitHub sparse deps instead (see gamend_starter), and | |
| # the API reference is published to docs.gamend.org by the `docs` job — so | |
| # nothing downstream depends on these being on Hex. Re-enable the block | |
| # below if pigeon 2.1.0 ships. | |
| # | |
| # - name: Rewrite in_umbrella deps and stamp version for core/web | |
| # run: | | |
| # # gamend_web depends on gamend_core by path; Hex only accepts Hex | |
| # # deps, so the published package points at the version being cut. | |
| # # This matched `in_umbrella: true` for months, which the file never | |
| # # said — so the substitution silently did nothing and every | |
| # # gamend_web publish failed. | |
| # sed -i "s|{:gamend_core, path: \"../gamend_core\"}|{:gamend_core, \"~> 1.0\"}|g" \ | |
| # apps/gamend_web/mix.exs | |
| # grep -q '{:gamend_core, "~> 1.0"}' apps/gamend_web/mix.exs || | |
| # { echo "::error::gamend_core path dep rewrite failed - gamend_web would publish broken"; exit 1; } | |
| # # Remove heroicons GitHub dep for Hex packaging (Hex only allows Hex deps). | |
| # # Host/consumer apps must provide icon CSS generation for `hero-*` classes. | |
| # sed -i '/{:heroicons/,/depth: 1}/d' apps/gamend_web/mix.exs | |
| # sed -i "s|@version \"[^\"]*\"|@version \"${APP_VERSION}\"|g" \ | |
| # apps/gamend_core/mix.exs \ | |
| # apps/gamend_web/mix.exs | |
| # | |
| # - name: Publish gamend_core | |
| # working-directory: apps/gamend_core | |
| # run: | | |
| # mix deps.get | |
| # # Do not swallow the exit code: publishing silently failed for | |
| # # months behind an `|| echo`. A version that already exists is | |
| # # not an error to hex.publish, so a non-zero exit is real. | |
| # mix hex.publish --yes | |
| # | |
| # - name: Publish gamend_web | |
| # working-directory: apps/gamend_web | |
| # run: | | |
| # mix deps.get | |
| # # Do not swallow the exit code: publishing silently failed for | |
| # # months behind an `|| echo`. A version that already exists is | |
| # # not an error to hex.publish, so a non-zero exit is real. | |
| # mix hex.publish --yes | |
| docs: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Compute version | |
| id: version | |
| run: | | |
| git config --global --add safe.directory $GITHUB_WORKSPACE || true | |
| git fetch --prune --unshallow || true | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| # Build a simple semver style version used on CI: 1.0.<commit_count> | |
| VERSION="1.0.${COMMIT_COUNT}" | |
| echo "Generated version=${VERSION}" | |
| # Export for remaining steps in this job | |
| echo "APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "GAMEND_CONTENT_APP_VERSION=${VERSION}" >> $GITHUB_ENV | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| - name: Setup Elixir | |
| uses: erlef/setup-beam@v1 | |
| with: | |
| elixir-version: ${{ env.ELIXIR_VERSION }} | |
| otp-version: ${{ env.OTP_VERSION }} | |
| install-hex: false | |
| install-rebar: false | |
| - name: Set up Elixir helpers | |
| working-directory: /tmp | |
| run: | | |
| mix local.hex --force | |
| mix local.rebar --force | |
| - name: Install dependencies | |
| working-directory: apps/gamend_core | |
| run: | | |
| mix deps.get | |
| # Docs are generated from gamend_core: it holds the public API | |
| # (Gamend.* domain modules, hooks). The root :gamend_host is a | |
| # thin wrapper, so building docs there yields an almost-empty reference. | |
| # Output to the repo-root ./doc so the publish step below finds it. | |
| - name: Generate docs | |
| working-directory: apps/gamend_core | |
| env: | |
| APP_VERSION: ${{ env.APP_VERSION }} | |
| run: | | |
| echo "Using APP_VERSION=${APP_VERSION:-$(echo $APP_VERSION)}" | |
| MIX_ENV=dev mix docs -o ../../doc | |
| - name: Publish to gh-pages | |
| if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} | |
| uses: peaceiris/actions-gh-pages@v3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_dir: ./doc | |
| # keep history to allow diffs in gh-pages | |
| publish_branch: gh-pages | |
| # GitHub wrote CNAME into gh-pages when the custom domain was set, but | |
| # ExDoc's output does not contain one — publishing without this would | |
| # delete it on the next push and take docs.gamend.org down. | |
| cname: docs.gamend.org |