From 81dd5815f9ae6aa5275ea365e92192d16bb2394f Mon Sep 17 00:00:00 2001 From: Danny M <25923265+dannym-arx@users.noreply.github.com> Date: Sat, 28 Feb 2026 11:07:25 -0300 Subject: [PATCH] make apk for all prs (#401) --- .github/apk-contributors | 4 + .github/workflows/android-apk.yml | 64 ++++++++- .github/workflows/deploy-when-apk.yml | 3 +- scripts/when-apk.sh | 200 ++++++++++++++++++++++++-- 4 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 .github/apk-contributors diff --git a/.github/apk-contributors b/.github/apk-contributors new file mode 100644 index 0000000..8fb552a --- /dev/null +++ b/.github/apk-contributors @@ -0,0 +1,4 @@ +dannym-arx +erskingardner +josefinalliende +untreu2 diff --git a/.github/workflows/android-apk.yml b/.github/workflows/android-apk.yml index 48d62a5..e07b1fb 100644 --- a/.github/workflows/android-apk.yml +++ b/.github/workflows/android-apk.yml @@ -3,12 +3,54 @@ name: Android APK Build on: push: branches: [master] + pull_request: + branches: [master] + types: [opened, synchronize, reopened, labeled] workflow_dispatch: jobs: + check-team: + name: Check Core Contributor + if: >- + github.event_name == 'pull_request' + && contains(github.event.pull_request.labels.*.name, 'apk') + runs-on: ubuntu-latest + outputs: + is-member: ${{ steps.check.outputs.is-member }} + + steps: + - name: Checkout contributors list + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.sha }} + sparse-checkout: .github/apk-contributors + sparse-checkout-cone-mode: false + + - name: Check contributor membership + id: check + env: + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + run: | + if grep -qxF "$PR_AUTHOR" .github/apk-contributors; then + echo "is-member=true" >> "$GITHUB_OUTPUT" + echo "✅ ${PR_AUTHOR} is an APK contributor" + else + echo "is-member=false" >> "$GITHUB_OUTPUT" + echo "⏭️ ${PR_AUTHOR} is not an APK contributor — skipping APK build" + fi + build-apk: name: Build Staging APK runs-on: ubuntu-latest + needs: [check-team] + # Run when: push/dispatch (not a PR, check-team was skipped), or PR where + # the "apk" label is present and actor is a contributor. + if: >- + always() + && ( + (github.event_name != 'pull_request' && needs.check-team.result == 'skipped') + || needs.check-team.outputs.is-member == 'true' + ) steps: - name: Checkout code @@ -73,29 +115,43 @@ jobs: - name: Set artifact name run: | echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV - BRANCH_NAME="${GITHUB_REF_NAME//\//-}" - echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "ARTIFACT_SUFFIX=pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::7}" >> $GITHUB_ENV + else + BRANCH_NAME="${GITHUB_REF_NAME//\//-}" + echo "ARTIFACT_SUFFIX=${BRANCH_NAME}-${GITHUB_SHA::7}" >> $GITHUB_ENV + fi - name: Upload APK artifacts uses: actions/upload-artifact@v4 with: - name: apk-staging-${{ env.BRANCH_NAME }}-${{ env.SHORT_SHA }} + name: apk-staging-${{ env.ARTIFACT_SUFFIX }} path: build/app/outputs/flutter-apk/*-staging-*.apk retention-days: 30 if-no-files-found: error - name: Summary + env: + SAFE_PR_TITLE: ${{ github.event.pull_request.title }} + SAFE_PR_NUMBER: ${{ github.event.pull_request.number }} run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + PR_INFO="**PR:** #${SAFE_PR_NUMBER} — ${SAFE_PR_TITLE}" + else + PR_INFO="" + fi + cat >> "${GITHUB_STEP_SUMMARY}" </\>/g; s/"/\"/g; s/'"'"'/\'/g' } +format_date() { + date -u -d "$1" '+%B %d, %Y at %H:%M UTC' 2>/dev/null \ + || TZ=UTC date -jf '%Y-%m-%dT%H:%M:%SZ' "$1" '+%B %d, %Y at %H:%M UTC' 2>/dev/null \ + || echo "$1" +} + # --------------------------------------------------------------------------- # 1. Find the latest *successful* workflow run on master # --------------------------------------------------------------------------- @@ -94,16 +101,135 @@ SHORT_SHA="${HEAD_SHA:0:7}" echo " Commit: $SHORT_SHA by $COMMIT_AUTHOR — $COMMIT_MSG" # --------------------------------------------------------------------------- -# 4. Generate static HTML — NES retro style, marmot energy +# 4. Fetch recent successful PR builds +# --------------------------------------------------------------------------- +echo "" +echo "Fetching recent PR builds..." + +# Get recent successful runs for the workflow (across all branches, event=pull_request) +PR_RUNS_JSON=$(gh_api "$API/repos/$REPO/actions/workflows/$WORKFLOW_FILE/runs?event=pull_request&status=success&per_page=20") + +# We'll collect up to MAX_PR_BUILDS PR build entries as HTML fragments +PR_BUILDS_HTML="" +PR_COUNT=0 + +# Iterate through runs, extract PR info from each +while IFS= read -r row; do + if [[ "$PR_COUNT" -ge "$MAX_PR_BUILDS" ]]; then + break + fi + + unset pr_artifacts_json + + pr_run_id=$(echo "$row" | jq -r '.id') + pr_head_sha=$(echo "$row" | jq -r '.head_sha') + pr_run_date=$(echo "$row" | jq -r '.updated_at') + + # Get PR number from the run's pull_requests array + pr_number=$(echo "$row" | jq -r '.pull_requests[0].number // empty') + + if [[ -z "$pr_number" ]]; then + # Fallback: check artifact name for pr- pattern + pr_artifacts_json=$(gh_api "$API/repos/$REPO/actions/runs/$pr_run_id/artifacts") + pr_artifact_name=$(echo "$pr_artifacts_json" | jq -r '.artifacts[0].name // empty') + + if [[ "$pr_artifact_name" =~ apk-staging-pr-([0-9]+)- ]]; then + pr_number="${BASH_REMATCH[1]}" + else + echo " Skipping run $pr_run_id — cannot determine PR number" + continue + fi + fi + + # Fetch PR metadata + pr_json=$(gh_api "$API/repos/$REPO/pulls/$pr_number" 2>/dev/null || echo '{}') + pr_title=$(echo "$pr_json" | jq -r '.title // "Unknown"') + pr_author=$(echo "$pr_json" | jq -r '.user.login // "unknown"') + pr_state=$(echo "$pr_json" | jq -r '.state // "unknown"') + merged_at=$(echo "$pr_json" | jq -r '.merged_at // empty') + pr_url="https://github.com/$REPO/pull/$pr_number" + + # Get artifact info for this run + if [[ -z "${pr_artifacts_json:-}" ]]; then + pr_artifacts_json=$(gh_api "$API/repos/$REPO/actions/runs/$pr_run_id/artifacts") + fi + + pr_artifact_id=$(echo "$pr_artifacts_json" | jq -r '.artifacts[0].id // empty') + pr_artifact_size=$(echo "$pr_artifacts_json" | jq -r '.artifacts[0].size_in_bytes // 0') + pr_artifact_expired=$(echo "$pr_artifacts_json" | jq -r '.artifacts[0].expired // false') + + if [[ -z "$pr_artifact_id" || "$pr_artifact_expired" == "true" ]]; then + echo " Skipping PR #$pr_number — artifact missing or expired" + continue + fi + + pr_artifact_size_mb=$(awk "BEGIN {printf \"%.1f\", $pr_artifact_size / 1048576}") + pr_nightly_url="https://nightly.link/$REPO/actions/artifacts/$pr_artifact_id.zip" + pr_short_sha="${pr_head_sha:0:7}" + pr_pretty_date=$(format_date "$pr_run_date") + pr_title_escaped=$(html_escape "$pr_title") + pr_author_escaped=$(html_escape "$pr_author") + pr_commit_url="https://github.com/$REPO/commit/$pr_head_sha" + + # Determine state badge + if [[ "$pr_state" == "open" ]]; then + state_badge="OPEN" + state_color="var(--nes-green)" + elif [[ -n "$merged_at" ]]; then + state_badge="MERGED" + state_color="var(--nes-blue)" + else + state_badge="CLOSED" + state_color="var(--nes-red)" + fi + + echo " PR #$pr_number: $pr_title (by $pr_author, $pr_short_sha)" + + PR_BUILDS_HTML+=" +
+
+
+ PR BUILD + [${state_badge}] +
+ + DOWNLOAD + +
+
+ #${pr_number} + ${pr_title_escaped} +
+ + + + + + + + + + + + + + + + + +
HERO${pr_author_escaped}
COMMIT${pr_short_sha}
BUILT${pr_pretty_date}
SIZE${pr_artifact_size_mb} MB (zip)
+
" + + PR_COUNT=$((PR_COUNT + 1)) +done < <(echo "$PR_RUNS_JSON" | jq -c '.workflow_runs[]') + +echo " Found $PR_COUNT PR builds" + +# --------------------------------------------------------------------------- +# 5. Generate static HTML — NES retro style, marmot energy # --------------------------------------------------------------------------- mkdir -p "$OUT_DIR" -format_date() { - date -u -d "$1" '+%B %d, %Y at %H:%M UTC' 2>/dev/null \ - || TZ=UTC date -jf '%Y-%m-%dT%H:%M:%SZ' "$1" '+%B %d, %Y at %H:%M UTC' 2>/dev/null \ - || echo "$1" -} - PRETTY_BUILD_DATE=$(format_date "$RUN_DATE") PRETTY_COMMIT_DATE=$(format_date "$COMMIT_DATE") @@ -386,6 +512,31 @@ cat > "$OUT_DIR/index.html" <<'HTMLEOF_PART1' 50% { opacity: 1; } } + /* ---- PR builds section ---- */ + .pr-section-header { + font-size: 0.7rem; + color: var(--nes-orange); + margin-bottom: 0.5rem; + text-transform: uppercase; + text-align: center; + } + + .pr-section-sub { + font-size: 0.45rem; + color: var(--muted); + text-align: center; + margin-bottom: 1rem; + line-height: 2; + } + + .pr-empty { + font-size: 0.5rem; + color: var(--muted); + text-align: center; + padding: 1rem; + line-height: 2; + } + /* ---- Responsive ---- */ @media (max-width: 500px) { .title { font-size: 1.2rem; } @@ -425,7 +576,7 @@ cat > "$OUT_DIR/index.html" <<'HTMLEOF_PART1'

▼ GRAB THE LATEST BUILD ▼

HTMLEOF_PART1 -# --- inject dynamic values --- +# --- inject dynamic master build values --- cat >> "$OUT_DIR/index.html" < ★ DOWNLOAD APK ★ @@ -484,6 +635,37 @@ cat >> "$OUT_DIR/index.html" <████████████████████████████████ + +
+

▶ SIDE QUESTS (PR BUILDS) ▶

+

+ Experimental builds from open pull requests.
+ Use at your own risk, adventurer. +

+HTMLEOF_PART2 + +# --- inject PR builds --- +if [[ -n "$PR_BUILDS_HTML" ]]; then + cat >> "$OUT_DIR/index.html" <> "$OUT_DIR/index.html" <<'HTMLEOF_PR_EMPTY' +
+

+ No active PR builds right now.
+ The marmot is resting. 😴 +

+
+HTMLEOF_PR_EMPTY +fi + +# --- close out the page --- +cat >> "$OUT_DIR/index.html" < + +
████████████████████████████████
+