chore: bump version to 0.1.12 #4
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
| # Build and push staging images from the `staging` branch. | |
| # Tags: X.Y.Z-staging.N and floating :staging. Version Git ref reserved via the Git API. | |
| name: Publish (staging) | |
| on: | |
| push: | |
| branches: | |
| - staging | |
| workflow_dispatch: | |
| inputs: | |
| version_override: | |
| description: "Optional version override (default: atomic Git tag reservation for | |
| staging line)" | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| packages: read | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: npm ci --include=optional | |
| - name: Security audit | |
| run: npm audit --omit=dev --audit-level=moderate | |
| - name: Build all packages | |
| run: npm run build:packages | |
| - name: Lint | |
| run: npm run lint | |
| - name: Type check | |
| run: npm run type-check | |
| - name: Setup web env for build | |
| run: test -f apps/web/.env.local || cp apps/web/.env.example apps/web/.env.local | |
| - name: Build all apps | |
| run: npm run build:apps | |
| reserve-version: | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.reserve.outputs.version }} | |
| float_tag: ${{ steps.reserve.outputs.float_tag }} | |
| is_prod: ${{ steps.reserve.outputs.is_prod }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Reserve next version | |
| id: reserve | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| SHA: ${{ github.sha }} | |
| OVERRIDE: ${{ inputs.version_override }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| BASE=$(node -p "require('./package.json').version" | sed 's/-.*//') | |
| SUFFIX="staging" | |
| FLOAT="staging" | |
| IS_PROD="false" | |
| # This workflow is only for the `staging` branch; ref name is not used in logic below. | |
| create_tag() { | |
| local tag="$1" | |
| local sha="$2" | |
| local body | |
| body=$(mktemp) | |
| LAST_CREATE_CODE=$(curl -sS -o "$body" -w "%{http_code}" \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -X POST "https://api.github.com/repos/${REPO}/git/refs" \ | |
| -d "{\"ref\":\"refs/tags/${tag}\",\"sha\":\"${sha}\"}") | |
| LAST_CREATE_BODY=$(cat "$body") | |
| rm -f "$body" | |
| echo "create-ref attempt: tag=${tag} status=${LAST_CREATE_CODE}" | |
| if [ "$LAST_CREATE_CODE" != "201" ] && [ "$LAST_CREATE_CODE" != "422" ]; then | |
| echo "GitHub API error ${LAST_CREATE_CODE} while creating tag ${tag}:" >&2 | |
| printf '%s\n' "$LAST_CREATE_BODY" >&2 | |
| return 1 | |
| fi | |
| } | |
| if [ -n "$OVERRIDE" ]; then | |
| VERSION="$OVERRIDE" | |
| create_tag "$VERSION" "$SHA" | |
| if [ "$LAST_CREATE_CODE" != "201" ] && [ "$LAST_CREATE_CODE" != "422" ]; then | |
| exit 1 | |
| fi | |
| if [ "$LAST_CREATE_CODE" = "422" ]; then | |
| echo "::error::Override tag $VERSION already exists" >&2 | |
| exit 1 | |
| fi | |
| else | |
| START=0 | |
| if TAG_LINES=$(git ls-remote --tags origin "refs/tags/${BASE}-${SUFFIX}.*" 2>/dev/null); then | |
| MAX=$(printf '%s\n' "$TAG_LINES" | awk -v prefix="refs/tags/${BASE}-${SUFFIX}." ' | |
| { | |
| ref=$2 | |
| sub(/\^\{\}$/, "", ref) | |
| if (index(ref, prefix) == 1) { | |
| n=substr(ref, length(prefix) + 1) | |
| if (n ~ /^[0-9]+$/) { | |
| if (max == "" || (n + 0) > (max + 0)) { max=n + 0 } | |
| } | |
| } | |
| } | |
| END { if (max != "") { print max } }') | |
| if [ -n "$MAX" ]; then | |
| START=$((MAX + 1)) | |
| fi | |
| fi | |
| N=$START | |
| while :; do | |
| VERSION="${BASE}-${SUFFIX}.${N}" | |
| create_tag "$VERSION" "$SHA" | |
| if [ "$LAST_CREATE_CODE" = "201" ]; then | |
| break | |
| fi | |
| if [ "$LAST_CREATE_CODE" = "422" ]; then | |
| N=$((N + 1)) | |
| continue | |
| fi | |
| exit 1 | |
| done | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "float_tag=$FLOAT" >> "$GITHUB_OUTPUT" | |
| echo "is_prod=$IS_PROD" >> "$GITHUB_OUTPUT" | |
| echo "Reserved version: $VERSION" | |
| publish-docker: | |
| needs: [validate, reserve-version] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| matrix: | |
| include: | |
| - app: api | |
| dockerfile: infra/docker/local/api/Dockerfile | |
| - app: management-api | |
| dockerfile: infra/docker/local/management-api/Dockerfile | |
| - app: web | |
| dockerfile: infra/docker/local/web/Dockerfile | |
| - app: web-sidecar | |
| dockerfile: infra/docker/local/web-sidecar/Dockerfile | |
| - app: management-web | |
| dockerfile: infra/docker/local/management-web/Dockerfile | |
| - app: management-web-sidecar | |
| dockerfile: infra/docker/local/management-web-sidecar/Dockerfile | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: ${{ matrix.dockerfile }} | |
| push: true | |
| tags: | | |
| ghcr.io/${{ github.repository }}/${{ matrix.app }}:${{ needs.reserve-version.outputs.version }} | |
| ghcr.io/${{ github.repository }}/${{ matrix.app }}:${{ needs.reserve-version.outputs.float_tag }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| verify-published-tags: | |
| needs: [reserve-version, publish-docker] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| steps: | |
| - name: Verify expected tags in GHCR | |
| env: | |
| VERSION: ${{ needs.reserve-version.outputs.version }} | |
| FLOAT_TAG: ${{ needs.reserve-version.outputs.float_tag }} | |
| GHCR_TOKEN_PRIMARY: ${{ secrets.GHCR_REGISTRY_TOKEN }} | |
| GHCR_TOKEN_FALLBACK: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| GHCR_TOKEN="$GHCR_TOKEN_PRIMARY" | |
| if [ -z "$GHCR_TOKEN" ]; then | |
| GHCR_TOKEN="$GHCR_TOKEN_FALLBACK" | |
| echo "GHCR_REGISTRY_TOKEN not set; falling back to GITHUB_TOKEN for verification." | |
| fi | |
| APPS="api management-api web web-sidecar management-web management-web-sidecar" | |
| for APP in $APPS; do | |
| IMAGE_PATH="${{ github.repository }}/${APP}" | |
| echo "Checking tags for ${IMAGE_PATH}" | |
| TAGS_RESPONSE=$(curl -s -w "\n%{http_code}" \ | |
| -H "Authorization: Bearer $GHCR_TOKEN" \ | |
| "https://ghcr.io/v2/${IMAGE_PATH}/tags/list") | |
| TAGS_JSON=$(echo "$TAGS_RESPONSE" | sed '$d') | |
| TAGS_STATUS=$(echo "$TAGS_RESPONSE" | tail -n1) | |
| if [ "$TAGS_STATUS" != "200" ]; then | |
| echo "Tag verification failed for ${IMAGE_PATH} (HTTP ${TAGS_STATUS})." | |
| exit 1 | |
| fi | |
| if ! echo "$TAGS_JSON" | jq -e --arg VERSION "$VERSION" '.tags | index($VERSION)' >/dev/null; then | |
| echo "Missing version tag ${VERSION} for ${IMAGE_PATH}." | |
| exit 1 | |
| fi | |
| if ! echo "$TAGS_JSON" | jq -e --arg T "$FLOAT_TAG" '.tags | index($T)' >/dev/null; then | |
| echo "Missing floating tag ${FLOAT_TAG} for ${IMAGE_PATH}." | |
| exit 1 | |
| fi | |
| done | |
| workflow-summary: | |
| needs: [reserve-version, verify-published-tags] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Published image version (workflow summary) | |
| env: | |
| VERSION: ${{ needs.reserve-version.outputs.version }} | |
| REPO: ${{ github.repository }} | |
| FT: ${{ needs.reserve-version.outputs.float_tag }} | |
| run: | | |
| { | |
| echo "## Published Docker images (staging branch)" | |
| echo "" | |
| echo "**Image version (semver tag):** \`$VERSION\`" | |
| echo "" | |
| echo "Each image was also tagged **\`$FT\`** (floating)." | |
| echo "" | |
| echo "| Image | Tags |" | |
| echo "|-------|------|" | |
| for app in api management-api web web-sidecar management-web management-web-sidecar; do | |
| echo "| \`ghcr.io/${REPO}/${app}\` | \`${VERSION}\`, \`${FT}\` |" | |
| done | |
| echo "" | |
| echo "Pin GitOps to the version tag or the floating tag. Git tag matches the semver when created." | |
| echo "" | |
| echo "## All published image references" | |
| for app in api management-api web web-sidecar management-web management-web-sidecar; do | |
| echo "- \`ghcr.io/${REPO}/${app}:${VERSION}\` (version tag)" | |
| echo "- \`ghcr.io/${REPO}/${app}:${FT}\` (floating tag)" | |
| done | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| github-prerelease-create: | |
| needs: [reserve-version, verify-published-tags] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Create GitHub prerelease | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const version = "${{ needs.reserve-version.outputs.version }}"; | |
| const baseVersion = version.replace(/-(staging|alpha|beta)\.\d+$/, ""); | |
| const path = "docs/development/CHANGELOGS/" + baseVersion + ".md"; | |
| const fs = require("fs"); | |
| let body; | |
| if (fs.existsSync(path)) { | |
| body = fs.readFileSync(path, "utf8"); | |
| } else { | |
| body = "Prerelease build; no " + path + " at this commit. Bump version on develop at the start of work and write notes continuously in that file."; | |
| } | |
| const tag = version; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const { data: list } = await github.rest.repos.listReleases({ owner, repo, per_page: 100 }); | |
| if (list.some((r) => r.tag_name === tag)) { | |
| core.info("Release for tag " + tag + " already exists; skip."); | |
| return; | |
| } | |
| const rel = await github.rest.repos.createRelease({ | |
| owner, repo, tag_name: tag, name: tag, body, prerelease: true, | |
| }); | |
| core.info("Created release: " + rel.data.html_url); |