forgedb 0.2.1

ForgeDB — an application database generator. Compiles a declarative .forge schema into tailored Rust database code, a TypeScript SDK, and a REST API.
Documentation
# Docker image for the `forgedb` CLI — epic #181 / Docker channel.
#
# Triggered by the "Release" workflow (cargo-dist / release.yml) COMPLETING, via
# `workflow_run`. That ordering is load-bearing: the Dockerfile's `fetch` stage
# does an UNAUTHENTICATED `curl` of `releases/download/v<version>/forgedb-<triple>
# .tar.xz`, so the image can only build once release.yml's `host` job has created
# the (published, non-draft) GitHub Release and uploaded every per-platform
# tarball. `workflow_run: [completed]` fires only after the whole Release run
# (build → host → announce) finishes, so the assets are guaranteed present.
#
# Why not `on: release: [published]`? cargo-dist creates the Release with the
# built-in GITHUB_TOKEN, and GitHub suppresses `release` events for
# GITHUB_TOKEN-authored releases — that trigger would never fire. `workflow_run`
# keys off the *workflow completing* (the Release run was triggered by a human tag
# push), so it is not subject to that suppression.
#
# Why not a cargo-dist custom publish job (like publish-npm.yml)? dist hardcodes
# custom-publish call-site permissions to {id-token, packages} with no way to add
# `contents: read`, and this job needs checkout (for the Dockerfile / build
# context). A reusable workflow requesting a scope the caller didn't grant fails
# the caller at startup, so Docker must stay a standalone workflow_run job with
# its own permissions.
#
# workflow_run runs the copy of this file on the DEFAULT branch (main). Builds a
# multi-arch image (linux/amd64 + linux/arm64), pushes to GHCR always and to
# Docker Hub when its credentials are configured.
#
# Registries / go-live gating:
#   - GHCR (ghcr.io/hoodiecollin/forgedb): auth via the built-in GITHUB_TOKEN — no
#     secret to provision (this workflow grants itself `packages: write`).
#   - Docker Hub (docker.io/hoodiecollin/forgedb): needs `DOCKERHUB_USERNAME` +
#     `DOCKERHUB_TOKEN` secrets. Absent → the image still ships to GHCR; Docker Hub
#     is skipped (not failed).
#
# `workflow_dispatch` stays for manual rebuilds of an already-published tag.
name: Docker

on:
  workflow_run:
    workflows: [Release]
    types: [completed]
  workflow_dispatch:
    inputs:
      tag:
        description: Release tag to build (e.g. v0.2.0) — must already be published
        required: true

permissions:
  contents: read
  packages: write # push to GHCR

jobs:
  image:
    name: build & push (multi-arch)
    runs-on: ubuntu-22.04
    # Manual dispatch always runs. For a workflow_run trigger, require the upstream
    # Release to have SUCCEEDED and the tag to be a stable `v*` (skip prereleases —
    # a `-rc`/`-beta` tag gets no image or `latest`). head_branch is the tag name
    # for a tag-triggered upstream run.
    if: >-
      ${{ github.event_name == 'workflow_dispatch' ||
          (github.event.workflow_run.conclusion == 'success' &&
           startsWith(github.event.workflow_run.head_branch, 'v') &&
           !contains(github.event.workflow_run.head_branch, '-')) }}
    env:
      TAG_INPUT: ${{ github.event.inputs.tag }}
      RUN_TAG: ${{ github.event.workflow_run.head_branch }}
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      # Resolve the tag from the upstream Release run's head_branch (workflow_run)
      # or the manual dispatch input, then the bare version the Dockerfile fetches by.
      - name: Resolve version
        id: v
        shell: bash
        run: |
          tag="${TAG_INPUT:-$RUN_TAG}"
          echo "tag=$tag" >> "$GITHUB_OUTPUT"
          echo "version=${tag#v}" >> "$GITHUB_OUTPUT"

      # Only push to Docker Hub when creds exist (secrets can't be used in `if:`
      # directly, so surface presence as a step output first).
      - name: Detect Docker Hub credentials
        id: hub
        shell: bash
        run: |
          if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then
            echo "enabled=true" >> "$GITHUB_OUTPUT"
          else
            echo "enabled=false" >> "$GITHUB_OUTPUT"
            echo "::notice::Docker Hub secrets not set — pushing to GHCR only."
          fi

      - uses: docker/setup-qemu-action@v3
      - uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Log in to Docker Hub
        if: ${{ steps.hub.outputs.enabled == 'true' }}
        uses: docker/login-action@v3
        with:
          username: ${{ env.DOCKERHUB_USERNAME }}
          password: ${{ env.DOCKERHUB_TOKEN }}

      - name: Compute tags
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ghcr.io/hoodiecollin/forgedb
            name=docker.io/hoodiecollin/forgedb,enable=${{ steps.hub.outputs.enabled }}
          tags: |
            type=semver,pattern={{version}},value=${{ steps.v.outputs.tag }}
            type=semver,pattern={{major}}.{{minor}},value=${{ steps.v.outputs.tag }}
            type=raw,value=latest

      - name: Build & push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          build-args: |
            VERSION=${{ steps.v.outputs.version }}
            REPO=${{ github.repository }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}