Skip to content

Publish addons

Publish addons #10

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"