Repository navigation
Publish addons #10
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: Publish addons | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| addon: | |
| description: One addon directory name, or "all" | |
| default: all | |
| staging: | |
| description: Publish under staging/ instead of the live keys | |
| type: boolean | |
| default: true | |
| # This job holds the R2 write credentials and runs Gradle builds for every | |
| # addon directory, so its token gets the least it can do its work with. | |
| permissions: | |
| contents: read | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - uses: gradle/actions/setup-gradle@v4 | |
| - uses: astral-sh/setup-uv@v5 | |
| - name: Verify the R2 configuration | |
| # The build below takes a long time. Without this, a renamed variable | |
| # is only discovered after all of it, inside boto3. | |
| env: | |
| R2_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| R2_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_ACCESS_KEY_ID }} | |
| R2_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }} | |
| R2_BUCKET: ${{ vars.CLOUDFLARE_R2_BUCKET_NAME }} | |
| run: | | |
| for v in R2_ACCOUNT_ID R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY R2_BUCKET; do | |
| if [ -z "${!v}" ]; then | |
| echo "$v is empty. Check the repository secrets and variables." >&2 | |
| exit 1 | |
| fi | |
| done | |
| echo "Publishing to bucket '$R2_BUCKET'." | |
| - name: Check names and metadata | |
| run: uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" check | |
| - name: Resolve the libs revision | |
| id: libs | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # This workflow builds the addons itself instead of going through | |
| # scripts/update-libs.sh, so nothing here knows which CodeOnTheGo commit | |
| # produced libs/. "Update libs from CodeOnTheGo" records it in the subject | |
| # of the commit it pushes, so the newest libs/ commit that names one wins. | |
| # Ordinary commits touch libs/ too (a missing jar, a pipeline change), and | |
| # keying on the single most recent one would let any of them block the whole | |
| # release path until someone dispatched Update libs, which also bumps the | |
| # toolchain as a side effect. Only "none of the recent ones names a sha" | |
| # fails the run: these are the artifacts the gallery serves, and an empty | |
| # export makes the builder omit the key and verify-provenance.sh skip its | |
| # check, so the pairing would be lost on a green run. The subject is | |
| # hand-writable, so the sha is validated rather than taken on trust, and | |
| # capped at the 12 characters update-libs.sh records, so the two forms of | |
| # one commit compare by prefix (subjects written before the --short=12 | |
| # change carry 9). | |
| subjects="$(gh api "repos/${GITHUB_REPOSITORY}/commits?path=libs/&sha=${GITHUB_SHA}&per_page=50" --jq '.[].commit.message | split("\n")[0]')" | |
| revision="" | |
| while IFS= read -r subject; do | |
| candidate="${subject##*CodeOnTheGo@}" | |
| if [[ "$subject" == *CodeOnTheGo@* && "$candidate" =~ ^[0-9a-f]{7,40}$ ]]; then | |
| revision="${candidate:0:12}" | |
| break | |
| fi | |
| done <<< "$subjects" | |
| if [ -z "$revision" ]; then | |
| echo "No recent commit touching libs/ names a CodeOnTheGo revision." >&2 | |
| echo "One of them must carry 'CodeOnTheGo@<sha>' in its subject." >&2 | |
| exit 1 | |
| fi | |
| echo "revision=$revision" >> "$GITHUB_OUTPUT" | |
| - name: Build, package, and stage | |
| id: stage | |
| env: | |
| ADDON: ${{ inputs.addon }} | |
| PLUGIN_LIBS_REVISION: ${{ steps.libs.outputs.revision }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| all="$(uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" discover)" | |
| if [ "$ADDON" = "all" ]; then | |
| names="$all" | |
| else | |
| # resolve through discover so an unknown or hostile value cannot | |
| # reach the shell as a path | |
| # match the directory name or the lowercase slug the rest of the | |
| # system uses (dl/<slug>.cgp, the catalog's slug field) | |
| names="$(printf '%s\n' "$all" | awk -v want="$ADDON" ' | |
| { n = $0; sub(/.*\//, "", n); l = tolower(n) | |
| if (n == want || $0 == want || l == tolower(want)) print }')" | |
| if [ -z "$names" ]; then | |
| echo "Unknown addon: $ADDON" >&2; exit 1 | |
| fi | |
| fi | |
| printf 'only<<EOF\n%s\nEOF\n' "$names" >> "$GITHUB_OUTPUT" | |
| for dir in $names; do | |
| echo "==> $dir" | |
| ( cd "$dir" | |
| gradlew="$GITHUB_WORKSPACE/gradlew" | |
| if [ -x ./gradlew ]; then gradlew=./gradlew; fi | |
| if grep -q downloadAssets build.gradle.kts; then | |
| "$gradlew" --console=plain downloadAssets | |
| fi | |
| "$gradlew" --console=plain assemblePlugin ) | |
| "$GITHUB_WORKSPACE/scripts/verify-provenance.sh" "$dir" | |
| slug="$(basename "$dir" | tr '[:upper:]' '[:lower:]')" | |
| src="$(ls "$dir"/build/plugin/*.cgp | grep -v -- '-debug\.cgp$' | head -n1)" | |
| cp "$src" "dist/${slug}.cgp" | |
| done | |
| - name: Build the source tarballs | |
| env: | |
| ONLY: ${{ steps.stage.outputs.only }} | |
| run: | | |
| # shellcheck disable=SC2086 | |
| uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" \ | |
| tarball --out "$GITHUB_WORKSPACE/dist" --only $ONLY | |
| - name: Work out where this is being published | |
| id: dest | |
| env: | |
| STAGING: ${{ inputs.staging }} | |
| run: | | |
| prefix="" | |
| if [ "$STAGING" = "true" ]; then | |
| prefix="staging/${{ github.run_id }}/" | |
| fi | |
| echo "prefix=${prefix}" >> "$GITHUB_OUTPUT" | |
| base="https://addons.appdevforall.org${prefix:+/${prefix%/}}" | |
| echo "base=$base" >> "$GITHUB_OUTPUT" | |
| - name: Generate the catalog | |
| env: | |
| BASE: ${{ steps.dest.outputs.base }} | |
| ONLY: ${{ steps.stage.outputs.only }} | |
| ADDONS_SOURCE_REF: ${{ github.ref_name }} | |
| run: | | |
| # shellcheck disable=SC2086 | |
| uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" catalog \ | |
| --dist "$GITHUB_WORKSPACE/dist" \ | |
| --out "$GITHUB_WORKSPACE/dist/catalog.json" \ | |
| --base "$BASE" --only $ONLY | |
| - name: Publish to Cloudflare R2 | |
| env: | |
| R2_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} | |
| R2_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_ACCESS_KEY_ID }} | |
| R2_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_SECRET_ACCESS_KEY }} | |
| R2_BUCKET: ${{ vars.CLOUDFLARE_R2_BUCKET_NAME }} | |
| PREFIX: ${{ steps.dest.outputs.prefix }} | |
| BASE: ${{ steps.dest.outputs.base }} | |
| ONLY: ${{ steps.stage.outputs.only }} | |
| run: | | |
| # shellcheck disable=SC2086 | |
| uv run --directory tools/addons addons --root "$GITHUB_WORKSPACE" publish \ | |
| --dist "$GITHUB_WORKSPACE/dist" --prefix "$PREFIX" --only $ONLY | |
| { | |
| echo "### Published" | |
| echo "$BASE/index.html" | |
| } >> "$GITHUB_STEP_SUMMARY" |