mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-10-03 00:54:34 -04:00
ci(release): upload to Play after every leg builds and automate the promotion tail (#7390)
This commit is contained in:
1 parent
b1e696b841
commit
4561b100fb
19 files changed
+916
-573
No files matched your search
@@ -0,0 +1,78 @@
|
||||
name: Bot PR
|
||||
description: Open or update a PR from the working tree's changes and hand it to the merge queue.
|
||||
# The token must be a PAT: GITHUB_TOKEN may not enable auto-merge on protected main, and
|
||||
# PRs it opens start no workflows, so the required checks would never report.
|
||||
inputs:
|
||||
token:
|
||||
description: 'PAT that pushes the branch, opens the PR and requests the merge'
|
||||
required: true
|
||||
branch:
|
||||
description: 'PR head branch'
|
||||
required: true
|
||||
base:
|
||||
description: 'PR base branch; must be the branch checked out in the workspace'
|
||||
default: 'main'
|
||||
title:
|
||||
description: 'PR title'
|
||||
required: true
|
||||
commit-message:
|
||||
description: 'Commit message; defaults to the title'
|
||||
default: ''
|
||||
body:
|
||||
description: 'PR body'
|
||||
default: ''
|
||||
add-paths:
|
||||
description: 'Newline-separated pathspecs to commit; all changes when empty'
|
||||
default: ''
|
||||
labels:
|
||||
description: 'Newline-separated labels'
|
||||
default: ''
|
||||
outputs:
|
||||
number:
|
||||
description: 'PR number'
|
||||
value: ${{ steps.pr.outputs.pull-request-number }}
|
||||
url:
|
||||
description: 'PR URL'
|
||||
value: ${{ steps.pr.outputs.pull-request-url }}
|
||||
operation:
|
||||
description: 'created, updated, closed or none'
|
||||
value: ${{ steps.pr.outputs.pull-request-operation }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Open or update the PR
|
||||
id: pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
branch: ${{ inputs.branch }}
|
||||
base: ${{ inputs.base }}
|
||||
title: ${{ inputs.title }}
|
||||
commit-message: ${{ inputs.commit-message || inputs.title }}
|
||||
body: ${{ inputs.body }}
|
||||
add-paths: ${{ inputs.add-paths }}
|
||||
labels: ${{ inputs.labels }}
|
||||
delete-branch: true
|
||||
|
||||
# No merge method is passed; the queue sets it. Re-requested on every update, since a
|
||||
# push can clear the request. GitHub refuses it on a PR that is already mergeable, so
|
||||
# that case merges directly.
|
||||
- name: Enable auto-merge
|
||||
if: ${{ steps.pr.outputs.pull-request-operation == 'created' || steps.pr.outputs.pull-request-operation == 'updated' }}
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pull-request-number }}
|
||||
run: |
|
||||
PR_ID=$(gh pr view "$PR_NUMBER" --json id --jq .id)
|
||||
QUERY=$(cat <<'GQL'
|
||||
mutation($id: ID!) {
|
||||
enablePullRequestAutoMerge(input: { pullRequestId: $id }) {
|
||||
pullRequest { number autoMergeRequest { enabledAt } }
|
||||
}
|
||||
}
|
||||
GQL
|
||||
)
|
||||
gh api graphql -f query="$QUERY" -F id="$PR_ID" \
|
||||
|| gh pr merge "$PR_NUMBER"
|
||||
@@ -28,8 +28,6 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
@@ -41,13 +39,14 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
determine-tags:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
tag_to_process: ${{ steps.calculate_tags.outputs.tag_to_process }}
|
||||
release_name: ${{ steps.calculate_tags.outputs.release_name }}
|
||||
final_tag: ${{ steps.calculate_tags.outputs.final_tag }}
|
||||
from_channel: ${{ steps.calculate_tags.outputs.from_channel }}
|
||||
version_name: ${{ steps.version.outputs.version_name }}
|
||||
version_code: ${{ steps.version.outputs.version_code }}
|
||||
steps:
|
||||
# Internal releases are exempt: Play internal testing skips full review,
|
||||
# so only promotions (closed/open/production) can clobber an in-flight
|
||||
@@ -66,9 +65,14 @@ jobs:
|
||||
|
||||
- name: Calculate tags
|
||||
id: calculate_tags
|
||||
env:
|
||||
BASE_VERSION: ${{ inputs.base_version }}
|
||||
CHANNEL: ${{ inputs.channel }}
|
||||
run: |
|
||||
BASE_VERSION="${{ inputs.base_version }}"
|
||||
CHANNEL="${{ inputs.channel }}"
|
||||
if [[ ! "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::base_version '$BASE_VERSION' is not X.Y.Z (e.g., 2.8.3)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$CHANNEL" == "internal" ]]; then
|
||||
# This is a new build, create a new internal tag
|
||||
@@ -84,7 +88,6 @@ jobs:
|
||||
echo "Calculated new tag: $NEW_TAG"
|
||||
{
|
||||
echo "tag_to_process=$NEW_TAG"
|
||||
echo "release_name=$NEW_TAG"
|
||||
echo "final_tag=$NEW_TAG"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
@@ -121,17 +124,49 @@ jobs:
|
||||
NEW_TAG="v${BASE_VERSION}"
|
||||
fi
|
||||
|
||||
echo "New release name will be: $NEW_TAG"
|
||||
echo "Final tag will be: $NEW_TAG"
|
||||
{
|
||||
echo "from_channel=${FROM_CHANNEL}"
|
||||
echo "tag_to_process=${LATEST_TAG_TO_PROMOTE}"
|
||||
echo "release_name=${NEW_TAG}"
|
||||
echo "final_tag=${NEW_TAG}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
# Name from the tag, code from the commit count plus VERSION_CODE_OFFSET, both at the
|
||||
# commit being released: for a promotion the promoted tag's, which main has moved past.
|
||||
- name: Calculate version
|
||||
id: version
|
||||
env:
|
||||
BASE_VERSION: ${{ inputs.base_version }}
|
||||
CHANNEL: ${{ inputs.channel }}
|
||||
TAG: ${{ steps.calculate_tags.outputs.tag_to_process }}
|
||||
run: |
|
||||
if [[ "$CHANNEL" == "internal" ]]; then REF=HEAD; else REF="$TAG"; fi
|
||||
git show "${REF}:config.properties" > "$RUNNER_TEMP/config.properties"
|
||||
|
||||
# The release highlights and the Play what's-new are looked up by VERSION_NAME_BASE.
|
||||
CONFIG_BASE=$(sed -n 's/^VERSION_NAME_BASE=//p' "$RUNNER_TEMP/config.properties")
|
||||
if [[ "$CONFIG_BASE" != "$BASE_VERSION" ]]; then
|
||||
echo "::error::base_version is $BASE_VERSION but config.properties at $REF has VERSION_NAME_BASE=${CONFIG_BASE:-<unset>}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION_NAME=$(echo "$TAG" | sed 's/-.*//' | sed 's/v//')
|
||||
VERSION_CODE_OFFSET=$(grep '^VERSION_CODE_OFFSET=' "$RUNNER_TEMP/config.properties" | cut -d'=' -f2 || true)
|
||||
if ! [[ "$VERSION_CODE_OFFSET" =~ ^[0-9]+$ ]]; then
|
||||
echo "::error::VERSION_CODE_OFFSET from config.properties is not numeric: '$VERSION_CODE_OFFSET'"
|
||||
exit 1
|
||||
fi
|
||||
VERSION_CODE=$(( $(git rev-list --count "$REF") + VERSION_CODE_OFFSET ))
|
||||
|
||||
echo "Version: $VERSION_NAME ($VERSION_CODE) from $REF"
|
||||
{
|
||||
echo "version_name=$VERSION_NAME"
|
||||
echo "version_code=$VERSION_CODE"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
- name: Create and Push Release Tag
|
||||
if: ${{ !inputs.dry_run && inputs.channel == 'internal' }}
|
||||
env:
|
||||
@@ -148,10 +183,8 @@ jobs:
|
||||
uses: ./.github/workflows/release.yml
|
||||
with:
|
||||
tag_name: ${{ needs.determine-tags.outputs.final_tag }}
|
||||
channel: ${{ inputs.channel }}
|
||||
base_version: ${{ inputs.base_version }}
|
||||
build_desktop: true
|
||||
build_flatpak_src: true
|
||||
version_name: ${{ needs.determine-tags.outputs.version_name }}
|
||||
version_code: ${{ needs.determine-tags.outputs.version_code }}
|
||||
secrets: inherit
|
||||
|
||||
call-promote-workflow:
|
||||
@@ -164,38 +197,65 @@ jobs:
|
||||
# so call-release-workflow doesn't carry it.
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
actions: write
|
||||
uses: ./.github/workflows/promote.yml
|
||||
with:
|
||||
tag_name: ${{ needs.determine-tags.outputs.tag_to_process }}
|
||||
release_name: ${{ needs.determine-tags.outputs.release_name }}
|
||||
final_tag: ${{ needs.determine-tags.outputs.final_tag }}
|
||||
channel: ${{ inputs.channel }}
|
||||
base_version: ${{ inputs.base_version }}
|
||||
from_channel: ${{ needs.determine-tags.outputs.from_channel }}
|
||||
version_code: ${{ needs.determine-tags.outputs.version_code }}
|
||||
secrets: inherit
|
||||
|
||||
# A production promotion stamps CHANGELOG.md itself; every other cut refreshes [Unreleased].
|
||||
update-changelog:
|
||||
needs: [call-release-workflow, call-promote-workflow]
|
||||
if: >-
|
||||
${{ !cancelled() && !inputs.dry_run && inputs.channel != 'production'
|
||||
&& (needs.call-release-workflow.result == 'success' || needs.call-promote-workflow.result == 'success') }}
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Dispatch Update Changelog
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: gh workflow run update-changelog.yml --ref main
|
||||
|
||||
cleanup-on-failure:
|
||||
needs: [determine-tags, call-release-workflow]
|
||||
if: ${{ (failure() || cancelled()) && !inputs.dry_run && inputs.channel == 'internal' }}
|
||||
runs-on: ubuntu-26.04-arm
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Delete Failed or Cancelled Tag
|
||||
env:
|
||||
FINAL_TAG: ${{ needs.determine-tags.outputs.final_tag }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
if [ -n "$FINAL_TAG" ]; then
|
||||
echo "Release workflow failed or was cancelled. Deleting tag $FINAL_TAG to allow a clean retry..."
|
||||
git push origin :refs/tags/"$FINAL_TAG" || echo "Tag was not pushed or already deleted."
|
||||
else
|
||||
if [ -z "$FINAL_TAG" ]; then
|
||||
echo "No tag was created to delete."
|
||||
exit 0
|
||||
fi
|
||||
# Play keeps an uploaded versionCode, so once publish-play succeeded the tag stays with it.
|
||||
PLAY=$(gh api "repos/$REPO/actions/runs/$RUN_ID/jobs?per_page=100" \
|
||||
--jq '[.jobs[] | select(.name | endswith("publish-play")) | .conclusion][0] // ""') || {
|
||||
echo "::warning::Could not read this run's jobs, so $FINAL_TAG stays. Delete it by hand if Play has no build from it."
|
||||
exit 0
|
||||
}
|
||||
if [ "$PLAY" = "success" ]; then
|
||||
echo "::warning::Play already has the build from $FINAL_TAG, so the tag stays. Re-run the failed jobs to finish the release."
|
||||
exit 0
|
||||
fi
|
||||
echo "Release workflow failed or was cancelled. Deleting tag $FINAL_TAG to allow a clean retry..."
|
||||
git push origin :refs/tags/"$FINAL_TAG" || echo "Tag was not pushed or already deleted."
|
||||
@@ -46,11 +46,12 @@ jobs:
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
with:
|
||||
ruby-version: '4.0.7'
|
||||
bundler-cache: true
|
||||
|
||||
- name: Decode Play Store credentials
|
||||
run: echo '${{ secrets.GOOGLE_PLAY_JSON_KEY }}' > fastlane/play-store-credentials.json
|
||||
env:
|
||||
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
|
||||
run: printf '%s\n' "$GOOGLE_PLAY_JSON_KEY" > fastlane/play-store-credentials.json
|
||||
|
||||
- name: Upload listing
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
name: Play Rollout
|
||||
|
||||
# Moves the staged rollout already on a Play track: the one inProgress release a promotion
|
||||
# leaves there (production at 10%, beta at 50%). rollout widens it to `fraction`, complete
|
||||
# ships it to every user, halt stops it where it is. supply acts only on inProgress releases,
|
||||
# so a halted release is resumed or completed in the Play Console.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
track:
|
||||
description: 'Play track holding the staged release'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- beta
|
||||
action:
|
||||
description: 'rollout widens it to fraction, complete ships it to everyone, halt stops it'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- rollout
|
||||
- complete
|
||||
- halt
|
||||
fraction:
|
||||
description: 'rollout only: the new user fraction, above the current one and below 1 (e.g., 0.5)'
|
||||
required: false
|
||||
type: string
|
||||
no_review_in_flight:
|
||||
description: 'I checked Publishing overview > Submission activity and no submission is In review. A committed rollout change CANCELS and RESTARTS any review in flight.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Not the promotion's group: a group keeps one pending run, so this would cancel a pending promotion.
|
||||
concurrency:
|
||||
group: play-rollout
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
rollout:
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
TRACK: ${{ inputs.track }}
|
||||
ACTION: ${{ inputs.action }}
|
||||
steps:
|
||||
- name: Require review-in-flight confirmation
|
||||
if: ${{ !inputs.no_review_in_flight }}
|
||||
run: |
|
||||
echo "::error::Rollout change blocked: confirm no Play review is in flight. Check Play Console > Publishing overview > Submission activity; if a submission shows 'In review', wait. If clear, re-dispatch with 'no_review_in_flight' checked."
|
||||
exit 1
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Decode Play Store credentials
|
||||
env:
|
||||
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
|
||||
run: printf '%s\n' "$GOOGLE_PLAY_JSON_KEY" > fastlane/play-store-credentials.json
|
||||
|
||||
# Exactly one inProgress release, or nothing is changed: that release's version code
|
||||
# pins supply's selection, and its fraction is what a halt keeps.
|
||||
- name: Find the staged release
|
||||
id: staged
|
||||
env:
|
||||
FRACTION: ${{ inputs.fraction }}
|
||||
run: |
|
||||
bundle exec fastlane play_track_releases track:"$TRACK" out:"$RUNNER_TEMP/before.json"
|
||||
STAGED=$(jq -c '[.[] | select(.status == "inProgress")]' "$RUNNER_TEMP/before.json")
|
||||
if [[ $(jq length <<< "$STAGED") -ne 1 ]]; then
|
||||
echo "::error::Track '$TRACK' needs exactly one inProgress release; it holds: $(jq -c . "$RUNNER_TEMP/before.json")"
|
||||
exit 1
|
||||
fi
|
||||
VERSION_CODE=$(jq -r '.[0].version_codes | max' <<< "$STAGED")
|
||||
CURRENT=$(jq -r '.[0].user_fraction // empty' <<< "$STAGED")
|
||||
if [[ ! "$VERSION_CODE" =~ ^[0-9]+$ || ! "$CURRENT" =~ ^0?\.[0-9]+$ ]]; then
|
||||
echo "::error::The inProgress release on '$TRACK' has no usable version code or fraction: $STAGED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATUS=""
|
||||
case "$ACTION" in
|
||||
rollout)
|
||||
if [[ ! "$FRACTION" =~ ^0?\.[0-9]+$ ]] || ! awk -v c="$CURRENT" -v n="$FRACTION" 'BEGIN { exit !(n > c && n < 1) }'; then
|
||||
echo "::error::fraction '$FRACTION' must be above the current $CURRENT and below 1; use complete to ship to everyone."
|
||||
exit 1
|
||||
fi
|
||||
ROLLOUT="$FRACTION"
|
||||
;;
|
||||
complete) ROLLOUT=1 ;;
|
||||
halt)
|
||||
ROLLOUT="$CURRENT"
|
||||
STATUS=halted
|
||||
;;
|
||||
esac
|
||||
echo "versionCode $VERSION_CODE on '$TRACK' at $CURRENT: $ACTION to $ROLLOUT${STATUS:+ ($STATUS)}"
|
||||
{
|
||||
echo "version_code=$VERSION_CODE"
|
||||
echo "current=$CURRENT"
|
||||
echo "rollout=$ROLLOUT"
|
||||
echo "status=$STATUS"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Every upload is skipped, so the edit changes only this release's status and fraction.
|
||||
- name: Change the rollout
|
||||
env:
|
||||
VERSION_CODE: ${{ steps.staged.outputs.version_code }}
|
||||
ROLLOUT: ${{ steps.staged.outputs.rollout }}
|
||||
STATUS: ${{ steps.staged.outputs.status }}
|
||||
run: |
|
||||
bundle exec fastlane supply \
|
||||
--track "$TRACK" \
|
||||
--version_code "$VERSION_CODE" \
|
||||
--rollout "$ROLLOUT" \
|
||||
${STATUS:+--release_status "$STATUS"} \
|
||||
--skip_upload_apk \
|
||||
--skip_upload_aab \
|
||||
--skip_upload_metadata \
|
||||
--skip_upload_changelogs \
|
||||
--skip_upload_images \
|
||||
--skip_upload_screenshots
|
||||
|
||||
# supply exits 0 even when its edit changed nothing. The script's verify counts only
|
||||
# completed and inProgress releases, so a halt is confirmed from a fresh read instead.
|
||||
- name: Verify the release on the track
|
||||
env:
|
||||
VERSION_CODE: ${{ steps.staged.outputs.version_code }}
|
||||
CURRENT: ${{ steps.staged.outputs.current }}
|
||||
ROLLOUT: ${{ steps.staged.outputs.rollout }}
|
||||
run: |
|
||||
PKG=$(grep '^APPLICATION_ID=' config.properties | cut -d'=' -f2)
|
||||
if [[ "$ACTION" != "halt" ]]; then
|
||||
bash scripts/play-track-preflight.sh \
|
||||
fastlane/play-store-credentials.json "$PKG" "$TRACK" "$VERSION_CODE" verify
|
||||
fi
|
||||
case "$ACTION" in
|
||||
rollout) WANT=inProgress ;;
|
||||
complete) WANT=completed ;;
|
||||
halt) WANT=halted ;;
|
||||
esac
|
||||
bundle exec fastlane play_track_releases track:"$TRACK" out:"$RUNNER_TEMP/after.json"
|
||||
AFTER=$(jq -c --argjson vc "$VERSION_CODE" '[.[] | select(.version_codes | index($vc))] | first // empty' "$RUNNER_TEMP/after.json")
|
||||
if [[ "$(jq -r '.status' <<< "${AFTER:-null}")" != "$WANT" ]]; then
|
||||
echo "::error::versionCode $VERSION_CODE on '$TRACK' is not $WANT after the change: ${AFTER:-absent}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$ACTION" == "rollout" ]] && ! awk -v a="$(jq -r '.user_fraction' <<< "$AFTER")" -v w="$ROLLOUT" 'BEGIN { exit !(a == w) }'; then
|
||||
echo "::error::versionCode $VERSION_CODE on '$TRACK' is not at $ROLLOUT after the change: $AFTER"
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "## Play rollout: ${TRACK}"
|
||||
echo
|
||||
echo "versionCode ${VERSION_CODE}: ${ACTION}, from ${CURRENT} to $(jq -r '.user_fraction // "all users"' <<< "$AFTER") (${WANT})."
|
||||
echo
|
||||
echo "A change Play could not send for review on its own waits under Publishing overview in the Play Console."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Clean up credentials
|
||||
if: always()
|
||||
run: rm -f fastlane/play-store-credentials.json
|
||||
+225
-183
@@ -11,18 +11,10 @@ on:
|
||||
description: 'The tag that triggered the release'
|
||||
required: true
|
||||
type: string
|
||||
release_name:
|
||||
description: 'The desired name for the GitHub release'
|
||||
required: true
|
||||
type: string
|
||||
final_tag:
|
||||
description: 'The final tag for the release'
|
||||
required: true
|
||||
type: string
|
||||
commit_sha:
|
||||
description: 'The commit SHA to tag'
|
||||
required: false
|
||||
type: string
|
||||
channel:
|
||||
description: 'The channel to promote to'
|
||||
required: true
|
||||
@@ -31,29 +23,26 @@ on:
|
||||
description: 'The channel to promote from'
|
||||
required: true
|
||||
type: string
|
||||
version_code:
|
||||
description: 'The version code of the build being promoted'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GSERVICES:
|
||||
required: true
|
||||
KEYSTORE:
|
||||
required: true
|
||||
KEYSTORE_FILENAME:
|
||||
required: true
|
||||
KEYSTORE_PROPERTIES:
|
||||
required: true
|
||||
DATADOG_APPLICATION_ID:
|
||||
required: true
|
||||
DATADOG_CLIENT_TOKEN:
|
||||
required: true
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
required: true
|
||||
GOOGLE_PLAY_JSON_KEY:
|
||||
required: true
|
||||
GRADLE_ENCRYPTION_KEY:
|
||||
required: true
|
||||
DISCORD_WEBHOOK_ANDROID:
|
||||
required: false
|
||||
HOMEBREW_TAP_TOKEN:
|
||||
required: false
|
||||
CROWDIN_GITHUB_TOKEN:
|
||||
required: false
|
||||
FLATHUB_TOKEN:
|
||||
required: false
|
||||
# Read only for presence, so the checklist says whether each store workflow will skip.
|
||||
WINGET_TOKEN:
|
||||
required: false
|
||||
MSSTORE_PRODUCT_ID:
|
||||
required: false
|
||||
|
||||
# Never cancel a promotion mid-flight: being killed between the Play edit
|
||||
# commit and the GitHub release/tag update leaves the two disagreeing. The
|
||||
@@ -65,59 +54,11 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
prepare-build-info:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
APP_VERSION_NAME: ${{ steps.prep_version.outputs.APP_VERSION_NAME }}
|
||||
APP_VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Prep APP_VERSION_NAME
|
||||
id: prep_version
|
||||
env:
|
||||
INPUT_TAG_NAME: ${{ inputs.tag_name }}
|
||||
run: |
|
||||
VERSION_NAME=$(echo "$INPUT_TAG_NAME" | sed 's/-.*//' | sed 's/v//')
|
||||
echo "APP_VERSION_NAME=$VERSION_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "Parsed Version: $VERSION_NAME"
|
||||
|
||||
- name: Extract VERSION_CODE_OFFSET from config.properties
|
||||
id: get_version_code_offset
|
||||
run: |
|
||||
OFFSET=$(grep '^VERSION_CODE_OFFSET=' config.properties | cut -d'=' -f2)
|
||||
echo "VERSION_CODE_OFFSET=$OFFSET" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Calculate Version Code from Git Commit Count
|
||||
id: calculate_version_code
|
||||
env:
|
||||
VERSION_CODE_OFFSET: ${{ steps.get_version_code_offset.outputs.VERSION_CODE_OFFSET }}
|
||||
run: |
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
if ! [[ "$VERSION_CODE_OFFSET" =~ ^[0-9]+$ ]]; then
|
||||
echo "::error::VERSION_CODE_OFFSET from config.properties is not numeric: '$VERSION_CODE_OFFSET'"
|
||||
exit 1
|
||||
fi
|
||||
VERSION_CODE=$((COMMIT_COUNT + VERSION_CODE_OFFSET))
|
||||
echo "versionCode=$VERSION_CODE" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
promote-release:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 30
|
||||
needs: [ prepare-build-info ]
|
||||
outputs:
|
||||
already_on_track: ${{ steps.preflight.outputs.already_on_track }}
|
||||
screenshots: ${{ steps.screenshots.outputs.found }}
|
||||
@@ -130,16 +71,17 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || inputs.tag_name }}
|
||||
ref: ${{ inputs.tag_name }}
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
with:
|
||||
ruby-version: '4.0.7'
|
||||
bundler-cache: true
|
||||
|
||||
- name: Decode Play Store credentials
|
||||
run: echo '${{ secrets.GOOGLE_PLAY_JSON_KEY }}' > fastlane/play-store-credentials.json
|
||||
env:
|
||||
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
|
||||
run: printf '%s\n' "$GOOGLE_PLAY_JSON_KEY" > fastlane/play-store-credentials.json
|
||||
|
||||
# A re-dispatched promotion whose versionCode is already live on the
|
||||
# target track must no-op: every redundant `supply` commit creates a new
|
||||
@@ -169,7 +111,7 @@ jobs:
|
||||
- name: Preflight — is this versionCode already on the target track?
|
||||
id: preflight
|
||||
env:
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
run: |
|
||||
PKG=$(grep '^APPLICATION_ID=' config.properties | cut -d'=' -f2)
|
||||
bash .workflow-ref/scripts/play-track-preflight.sh \
|
||||
@@ -185,7 +127,7 @@ jobs:
|
||||
- name: Promote to next channel
|
||||
if: ${{ steps.preflight.outputs.already_on_track != 'true' }}
|
||||
env:
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
run: |
|
||||
bundle exec fastlane supply \
|
||||
--track "$FROM_TRACK" \
|
||||
@@ -202,7 +144,7 @@ jobs:
|
||||
- name: Verify versionCode landed on the target track
|
||||
if: ${{ steps.preflight.outputs.already_on_track != 'true' }}
|
||||
env:
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
run: |
|
||||
PKG=$(grep '^APPLICATION_ID=' config.properties | cut -d'=' -f2)
|
||||
bash .workflow-ref/scripts/play-track-preflight.sh \
|
||||
@@ -250,51 +192,50 @@ jobs:
|
||||
run: rm -f fastlane/play-store-credentials.json
|
||||
|
||||
update-github-release:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 10
|
||||
needs: [ prepare-build-info, promote-release ]
|
||||
needs: [ promote-release ]
|
||||
# actions: write is scoped here — only this job's publish-workflow
|
||||
# dispatch needs it, and the other jobs must not get it. Job-level
|
||||
# permissions replace the workflow-level block, so the full set this
|
||||
# job uses is listed.
|
||||
# job uses is listed. Its PRs are opened with CROWDIN_GITHUB_TOKEN.
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
actions: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || inputs.tag_name }}
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
# Same reasoning as promote-release: scripts come from the caller's commit,
|
||||
# not the tag, so a tag cut before a script landed still gets it.
|
||||
# Same reasoning as promote-release: scripts and the bot-pr action come from the
|
||||
# caller's commit, not the tag, so a tag cut before either landed still gets it.
|
||||
- name: Checkout release scripts from caller commit
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
path: .workflow-ref
|
||||
sparse-checkout: scripts
|
||||
|
||||
- name: Push Git Tag on Success
|
||||
if: ${{ inputs.commit_sha != '' }}
|
||||
run: |
|
||||
git tag ${{ inputs.final_tag }} ${{ inputs.commit_sha }}
|
||||
git push origin ${{ inputs.final_tag }}
|
||||
sparse-checkout: |
|
||||
scripts
|
||||
.github/actions/bot-pr
|
||||
|
||||
# A rerun finds the release already moved to the final tag, so that is looked up first.
|
||||
- name: Update GitHub Release with gh CLI
|
||||
id: release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ inputs.tag_name }}
|
||||
FINAL_TAG: ${{ inputs.final_tag }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
PRERELEASE: ${{ inputs.channel != 'production' }}
|
||||
run: |
|
||||
gh release edit ${{ inputs.tag_name }} \
|
||||
--tag ${{ inputs.final_tag }} \
|
||||
--title "${{ inputs.release_name }} (${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }})" \
|
||||
CURRENT=$(gh release view "$FINAL_TAG" --json tagName --jq .tagName 2>/dev/null || echo "$TAG")
|
||||
gh release edit "$CURRENT" \
|
||||
--tag "$FINAL_TAG" \
|
||||
--title "$FINAL_TAG ($VERSION_CODE)" \
|
||||
--draft=false \
|
||||
--prerelease=${{ inputs.channel != 'production' }}
|
||||
--prerelease="$PRERELEASE"
|
||||
|
||||
# The draft's notes were generated at the internal cut and cover only the
|
||||
# PRs since the previous published pre-release. A production release is
|
||||
@@ -386,18 +327,28 @@ jobs:
|
||||
done
|
||||
gh workflow run docs-release.yml --ref "$TAG"
|
||||
|
||||
# The Obtainium table in README.md follows the channel releases; this refreshes it
|
||||
# now rather than at the next hourly run.
|
||||
- name: Dispatch scheduled updates
|
||||
id: sched
|
||||
if: ${{ inputs.channel != 'internal' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh workflow run scheduled-updates.yml --ref main
|
||||
|
||||
# bot-pr resets the checked-out branch to its origin copy before branching the PR,
|
||||
# so the edits below are made on a local main, never on a branch origin lacks.
|
||||
- name: Stamp CHANGELOG.md for release
|
||||
id: stamp
|
||||
if: ${{ inputs.channel == 'production' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ inputs.base_version }}
|
||||
run: |
|
||||
DATE=$(date -u +%Y-%m-%d)
|
||||
|
||||
# Checkout CHANGELOG.md from main (we're on a tag checkout)
|
||||
git fetch origin main
|
||||
git checkout -b "changelog/v${VERSION}" origin/main
|
||||
git checkout -B main origin/main
|
||||
|
||||
# The full-range notes from the step above, minus the boilerplate.
|
||||
# The file is the record whether or not the release edit went
|
||||
@@ -444,98 +395,66 @@ jobs:
|
||||
f.write(new_content)
|
||||
" "$VERSION" "$DATE" "$FLAT_NOTES"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add CHANGELOG.md
|
||||
git diff --cached --quiet || {
|
||||
BRANCH="automation/changelog-v${VERSION}"
|
||||
git checkout -B "$BRANCH"
|
||||
git commit -m "docs: release CHANGELOG.md for v${VERSION}"
|
||||
git push origin "$BRANCH" --force
|
||||
|
||||
PR_URL=$(gh pr create \
|
||||
--title "docs: release CHANGELOG.md for v${VERSION}" \
|
||||
--body "Automated changelog stamp for production release v${VERSION}." \
|
||||
--head "$BRANCH" \
|
||||
--base main \
|
||||
--label "automation" \
|
||||
--label "skip-changelog")
|
||||
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Post required commit status so the PR isn't blocked
|
||||
COMMIT_SHA=$(git rev-parse HEAD)
|
||||
gh api "repos/${{ github.repository }}/statuses/${COMMIT_SHA}" \
|
||||
-f state="success" \
|
||||
-f context="Check Workflow Status" \
|
||||
-f description="Skipped — changelog-only PR"
|
||||
}
|
||||
- name: Open the CHANGELOG.md PR
|
||||
id: stamp_pr
|
||||
if: ${{ inputs.channel == 'production' }}
|
||||
uses: ./.workflow-ref/.github/actions/bot-pr
|
||||
with:
|
||||
token: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
branch: automation/changelog-v${{ inputs.base_version }}
|
||||
title: 'docs: release CHANGELOG.md for v${{ inputs.base_version }}'
|
||||
body: 'Automated changelog stamp for production release v${{ inputs.base_version }}.'
|
||||
add-paths: CHANGELOG.md
|
||||
labels: |
|
||||
automation
|
||||
skip-changelog
|
||||
|
||||
# F-Droid and IzzyOnDroid read fastlane/ straight from git, so the committed set
|
||||
# follows what production shipped: the fdroid-flavor captures come back from the
|
||||
# release into a bot PR that merges itself, with the desktop set beside them. The
|
||||
# Flathub metainfo reads the release assets directly and needs nothing here.
|
||||
# Nothing to open when the files already match.
|
||||
- name: Refresh the committed store screenshots
|
||||
id: shots_pr
|
||||
# Nothing is opened when the files already match.
|
||||
- name: Stage the store screenshots from the release
|
||||
id: shots
|
||||
if: ${{ inputs.channel == 'production' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ inputs.final_tag }}
|
||||
VERSION: ${{ inputs.base_version }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
ASSET=$(gh release view "$TAG" --json assets --jq '.assets[].name | select(startswith("store-listing-screenshots-fdroid-"))' | head -1)
|
||||
if [[ -z "$ASSET" ]]; then
|
||||
echo "::warning::No rendered screenshots attached to $TAG; the committed set stays."
|
||||
echo "refreshed=none" >> "$GITHUB_OUTPUT"
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
gh release download "$TAG" --pattern "$ASSET" --pattern 'meshtastic-desktop-*.png' --dir "$RUNNER_TEMP/shots"
|
||||
git fetch origin main
|
||||
BRANCH="automation/store-screenshots-v${VERSION}"
|
||||
git checkout -B "$BRANCH" origin/main
|
||||
git checkout -B main origin/main
|
||||
unzip -qo "$RUNNER_TEMP/shots/$ASSET" -d fastlane/metadata/android/en-US/images
|
||||
cp "$RUNNER_TEMP"/shots/meshtastic-desktop-*.png desktopApp/packaging/linux/screenshots/
|
||||
git add fastlane/metadata/android/en-US/images desktopApp/packaging/linux/screenshots
|
||||
if git diff --cached --quiet; then
|
||||
echo "Committed screenshots already match v${VERSION}."
|
||||
echo "refreshed=match" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "chore(store): refresh the committed screenshots from v${VERSION}"
|
||||
git push origin "$BRANCH" --force
|
||||
PR_URL=$(gh pr create \
|
||||
--title "chore(store): refresh the committed screenshots from v${VERSION}" \
|
||||
--body "The screenshots rendered for the v${VERSION} release, so the fastlane tree F-Droid reads matches what shipped." \
|
||||
--head "$BRANCH" \
|
||||
--base main \
|
||||
--label "automation" \
|
||||
--label "skip-changelog")
|
||||
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
|
||||
# GITHUB_TOKEN pushes start no workflows: post the status the queue
|
||||
# requires, as the changelog stamp does, then let the queue take it.
|
||||
gh api "repos/${REPO}/statuses/$(git rev-parse HEAD)" \
|
||||
-f state="success" \
|
||||
-f context="Check Workflow Status" \
|
||||
-f description="Skipped — screenshot-only PR"
|
||||
PR_ID=$(gh pr view "$PR_URL" --json id --jq .id)
|
||||
QUERY=$(cat <<'GQL'
|
||||
mutation($id: ID!) {
|
||||
enablePullRequestAutoMerge(input: { pullRequestId: $id }) {
|
||||
pullRequest { number autoMergeRequest { enabledAt } }
|
||||
}
|
||||
}
|
||||
GQL
|
||||
)
|
||||
gh api graphql -f query="$QUERY" -F id="$PR_ID" || gh pr merge "$PR_URL"
|
||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Refresh the committed store screenshots
|
||||
id: shots_pr
|
||||
if: ${{ steps.shots.outputs.found == 'true' }}
|
||||
continue-on-error: true
|
||||
uses: ./.workflow-ref/.github/actions/bot-pr
|
||||
with:
|
||||
token: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
branch: automation/store-screenshots-v${{ inputs.base_version }}
|
||||
title: 'chore(store): refresh the committed screenshots from v${{ inputs.base_version }}'
|
||||
body: 'The screenshots rendered for the v${{ inputs.base_version }} release, so the fastlane tree F-Droid reads matches what shipped.'
|
||||
add-paths: |
|
||||
fastlane/metadata/android/en-US/images
|
||||
desktopApp/packaging/linux/screenshots
|
||||
labels: |
|
||||
automation
|
||||
skip-changelog
|
||||
|
||||
# One place to read what this promotion did, what it dispatched, and what
|
||||
# is still done by hand. The store dispatches report the dispatch only;
|
||||
# each store workflow skips inside itself until its secrets exist. The
|
||||
# hand-done list stays out of the Discord post, which is an announcement.
|
||||
# is still done by hand. The hand-done list stays out of the Discord post,
|
||||
# which is an announcement.
|
||||
- name: Release checklist
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
@@ -545,19 +464,37 @@ jobs:
|
||||
ALREADY_ON_TRACK: ${{ needs.promote-release.outputs.already_on_track }}
|
||||
SCREENSHOTS: ${{ needs.promote-release.outputs.screenshots }}
|
||||
LISTING: ${{ needs.promote-release.outputs.listing }}
|
||||
SHOTS_PR: ${{ steps.shots_pr.outputs.pr_url }}
|
||||
SHOTS: ${{ steps.shots.outcome }}
|
||||
SHOTS_FOUND: ${{ steps.shots.outputs.found }}
|
||||
SHOTS_PR: ${{ steps.shots_pr.outputs.url }}
|
||||
SHOTS_PR_OUTCOME: ${{ steps.shots_pr.outcome }}
|
||||
SHOTS_REFRESHED: ${{ steps.shots_pr.outputs.refreshed }}
|
||||
RELEASE: ${{ steps.release.outcome }}
|
||||
NOTES: ${{ steps.notes.outcome }}
|
||||
DOCS: ${{ steps.docs.outcome }}
|
||||
SCHED: ${{ steps.sched.outcome }}
|
||||
WINGET: ${{ steps.winget.outcome }}
|
||||
MSSTORE: ${{ steps.msstore.outcome }}
|
||||
BUMP: ${{ steps.bump.outcome }}
|
||||
STAMP: ${{ steps.stamp.outcome }}
|
||||
STAMP_PR: ${{ steps.stamp.outputs.pr_url }}
|
||||
STAMP_PR: ${{ steps.stamp_pr.outputs.url }}
|
||||
STAMP_PR_OUTCOME: ${{ steps.stamp_pr.outcome }}
|
||||
HAS_FLATHUB_TOKEN: ${{ secrets.FLATHUB_TOKEN != '' }}
|
||||
HAS_WINGET_TOKEN: ${{ secrets.WINGET_TOKEN != '' }}
|
||||
HAS_MSSTORE_PRODUCT_ID: ${{ secrets.MSSTORE_PRODUCT_ID != '' }}
|
||||
HAS_HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }}
|
||||
run: |
|
||||
row() { printf '| %s | %s |\n' "$1" "$2"; }
|
||||
# A store workflow skips inside itself when its secret is unset, so a successful
|
||||
# dispatch alone does not mean anything was submitted.
|
||||
store_row() {
|
||||
if [[ "$2" != "success" ]]; then
|
||||
row "$1" "$2"
|
||||
elif [[ "$3" != "true" ]]; then
|
||||
row "$1" "will skip: $4 unset"
|
||||
else
|
||||
row "$1" "dispatched"
|
||||
fi
|
||||
}
|
||||
{
|
||||
echo "## Release checklist: ${TAG} (${CHANNEL})"
|
||||
echo
|
||||
@@ -580,30 +517,56 @@ jobs:
|
||||
row "Play listing validated (dry run)" "$LISTING"
|
||||
fi
|
||||
row "Docs Release dispatched" "$DOCS"
|
||||
row "Scheduled Updates dispatched (Obtainium table)" "$SCHED"
|
||||
if [[ "$CHANNEL" == "production" ]]; then
|
||||
if [[ -n "$SHOTS_PR" ]]; then
|
||||
row "Committed screenshots refresh PR" "$SHOTS_PR"
|
||||
elif [[ "$SHOTS" != "success" ]]; then
|
||||
row "Committed screenshots refresh PR" "$SHOTS"
|
||||
elif [[ "$SHOTS_FOUND" != "true" ]]; then
|
||||
row "Committed screenshots refresh PR" "none needed (no rendered set attached)"
|
||||
elif [[ "$SHOTS_PR_OUTCOME" == "success" ]]; then
|
||||
row "Committed screenshots refresh PR" "none needed (${SHOTS_REFRESHED:-no change})"
|
||||
row "Committed screenshots refresh PR" "none needed (committed set matches)"
|
||||
else
|
||||
row "Committed screenshots refresh PR" "$SHOTS_PR_OUTCOME"
|
||||
fi
|
||||
row "Release notes rewritten (full range)" "$NOTES"
|
||||
row "CHANGELOG.md stamp" "$STAMP ${STAMP_PR:+- $STAMP_PR}"
|
||||
row "winget dispatched" "$WINGET"
|
||||
row "Microsoft Store dispatched" "$MSSTORE"
|
||||
if [[ -n "$STAMP_PR" ]]; then
|
||||
row "CHANGELOG.md stamp PR" "$STAMP_PR"
|
||||
elif [[ "$STAMP" == "success" && "$STAMP_PR_OUTCOME" == "success" ]]; then
|
||||
row "CHANGELOG.md stamp PR" "none needed (already stamped)"
|
||||
else
|
||||
row "CHANGELOG.md stamp PR" "stamp $STAMP, PR ${STAMP_PR_OUTCOME:-skipped}"
|
||||
fi
|
||||
store_row "winget" "$WINGET" "$HAS_WINGET_TOKEN" WINGET_TOKEN
|
||||
store_row "Microsoft Store" "$MSSTORE" "$HAS_MSSTORE_PRODUCT_ID" MSSTORE_PRODUCT_ID
|
||||
row "Version bump dispatched" "$BUMP"
|
||||
row "Post-Release Cleanup" "dispatched by Docs Release once /${TAG}/ is published"
|
||||
if [[ "$HAS_HOMEBREW_TAP_TOKEN" == "true" ]]; then
|
||||
row "Homebrew cask PR" "opened by the update-homebrew-cask job after this one"
|
||||
else
|
||||
row "Homebrew cask PR" "will skip: HOMEBREW_TAP_TOKEN unset"
|
||||
fi
|
||||
if [[ "$HAS_FLATHUB_TOKEN" == "true" ]]; then
|
||||
row "Flathub update PR" "opened by the update-flathub job after this one"
|
||||
else
|
||||
row "Flathub update PR" "will skip: FLATHUB_TOKEN unset"
|
||||
fi
|
||||
echo
|
||||
echo "Still by hand:"
|
||||
echo "- Play Console: the production rollout is staged; complete it there."
|
||||
echo "- Flathub: bump flathub/org.meshtastic.MeshtasticDesktop (tag, commit, gradle zip and sha256, flatpak-sources.json)."
|
||||
echo "- Play: the production rollout is staged; widen, complete or halt it with the Play Rollout workflow."
|
||||
if [[ "$HAS_FLATHUB_TOKEN" == "true" ]]; then
|
||||
echo "- Flathub: merge the update-flathub PR once its test build passes."
|
||||
else
|
||||
echo "- Flathub: bump flathub/org.meshtastic.MeshtasticDesktop (tag, commit, gradle zip and sha256, flatpak-sources.json)."
|
||||
fi
|
||||
echo "- Release notes: replace the placeholder <description> the version bump PR wrote for the next line."
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# Announces once the release is published, whatever happened to the steps after it.
|
||||
- name: Notify Discord
|
||||
if: ${{ inputs.channel != 'internal' }}
|
||||
if: ${{ always() && steps.release.outcome == 'success' && inputs.channel != 'internal' }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_ANDROID }}
|
||||
VERSION: ${{ inputs.final_tag }}
|
||||
@@ -654,14 +617,14 @@ jobs:
|
||||
# promoted to homebrew/cask, replace the tap PR with `brew bump-cask-pr`.
|
||||
update-homebrew-cask:
|
||||
if: ${{ inputs.channel == 'production' }}
|
||||
runs-on: ubuntu-26.04-arm
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 15
|
||||
needs: [ update-github-release ]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || inputs.tag_name }}
|
||||
ref: ${{ inputs.tag_name }}
|
||||
|
||||
- name: Render cask and open PR against meshtastic/homebrew-tap
|
||||
env:
|
||||
@@ -713,3 +676,82 @@ jobs:
|
||||
--body "Automated cask bump for the ${TAG} production release of [Meshtastic-Android](https://github.com/${{ github.repository }}/releases/tag/${TAG})." \
|
||||
|| echo "PR already exists for $BRANCH; branch updated.")
|
||||
echo "- Homebrew cask: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# The same for flathub/org.meshtastic.MeshtasticDesktop: the tag and its commit, the Gradle
|
||||
# zip that tag's wrapper pins, and the release's flatpak-sources.json. The JBR, runtime and
|
||||
# patches stay as they are; Flathub's test build on the PR checks them against the tag.
|
||||
update-flathub:
|
||||
if: ${{ inputs.channel == 'production' }}
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 15
|
||||
needs: [ update-github-release ]
|
||||
steps:
|
||||
# From the caller's commit, like the scripts in the jobs above.
|
||||
- name: Checkout the manifest bump script from caller commit
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
sparse-checkout: scripts/verify-flatpak
|
||||
|
||||
- name: Update the manifest and open PR against flathub/org.meshtastic.MeshtasticDesktop
|
||||
env:
|
||||
FLATHUB_TOKEN: ${{ secrets.FLATHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ inputs.final_tag }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
if [[ -z "$FLATHUB_TOKEN" ]]; then
|
||||
echo "::notice::FLATHUB_TOKEN not set; skipping the Flathub update."
|
||||
echo "- Flathub: skipped, FLATHUB_TOKEN not set" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
VERSION=${TAG#v}
|
||||
|
||||
# The tag appears a moment after the undraft.
|
||||
TYPE="" COMMIT=""
|
||||
for _ in 1 2 3 4 5 6; do
|
||||
if OBJ=$(gh api "repos/${REPO}/git/ref/tags/${TAG}" --jq '.object.type + " " + .object.sha' 2>/dev/null); then
|
||||
read -r TYPE COMMIT <<< "$OBJ"
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
if [[ -z "$COMMIT" ]]; then
|
||||
echo "::error::Tag $TAG not found."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$TYPE" == "tag" ]]; then
|
||||
COMMIT=$(gh api "repos/${REPO}/git/tags/${COMMIT}" --jq .object.sha)
|
||||
fi
|
||||
|
||||
gh api "repos/${REPO}/contents/gradle/wrapper/gradle-wrapper.properties?ref=${COMMIT}" \
|
||||
-H 'Accept: application/vnd.github.raw' > "$RUNNER_TEMP/gradle-wrapper.properties"
|
||||
gh release download "$TAG" --repo "$REPO" --pattern flatpak-sources.json --dir "$RUNNER_TEMP"
|
||||
jq empty "$RUNNER_TEMP/flatpak-sources.json"
|
||||
|
||||
FLATHUB="$RUNNER_TEMP/flathub"
|
||||
git clone "https://x-access-token:${FLATHUB_TOKEN}@github.com/flathub/org.meshtastic.MeshtasticDesktop.git" "$FLATHUB"
|
||||
BASE=$(git -C "$FLATHUB" rev-parse --abbrev-ref HEAD)
|
||||
BRANCH="update-${VERSION}"
|
||||
git -C "$FLATHUB" checkout -b "$BRANCH"
|
||||
python3 scripts/verify-flatpak/bump-flathub-manifest.py \
|
||||
"$FLATHUB/org.meshtastic.MeshtasticDesktop.yaml" "$TAG" "$COMMIT" "$RUNNER_TEMP/gradle-wrapper.properties"
|
||||
cp "$RUNNER_TEMP/flatpak-sources.json" "$FLATHUB/flatpak-sources.json"
|
||||
|
||||
cd "$FLATHUB"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add org.meshtastic.MeshtasticDesktop.yaml flatpak-sources.json
|
||||
if git diff --cached --quiet; then
|
||||
echo "Flathub manifest already at ${TAG}."
|
||||
echo "- Flathub: already at ${TAG}" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "Update to ${TAG}"
|
||||
git push -fu origin "$BRANCH"
|
||||
PR_URL=$(GH_TOKEN="$FLATHUB_TOKEN" gh pr create --repo flathub/org.meshtastic.MeshtasticDesktop \
|
||||
--head "$BRANCH" --base "$BASE" \
|
||||
--title "Update to ${TAG}" \
|
||||
--body "$(printf 'Update Meshtastic Desktop to version %s\nhttps://github.com/%s/releases/tag/%s\n' "$TAG" "$REPO" "$TAG")" \
|
||||
|| echo "PR already exists for $BRANCH; branch updated.")
|
||||
echo "- Flathub: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
|
||||
+125
-175
@@ -3,32 +3,18 @@ name: Make Release
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
base_version:
|
||||
description: 'The base version for the release (e.g., 2.3.0)'
|
||||
required: true
|
||||
type: string
|
||||
tag_name:
|
||||
description: 'The tag that triggered the release'
|
||||
required: true
|
||||
type: string
|
||||
commit_sha:
|
||||
description: 'The commit SHA to build and tag'
|
||||
required: false
|
||||
type: string
|
||||
channel:
|
||||
description: 'The channel to create a release for or promote to'
|
||||
version_name:
|
||||
description: 'The version name the build carries (e.g., 2.3.0)'
|
||||
required: true
|
||||
type: string
|
||||
version_code:
|
||||
description: 'The version code the build carries'
|
||||
required: true
|
||||
type: string
|
||||
build_desktop:
|
||||
description: 'Whether to build the desktop distribution'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
build_flatpak_src:
|
||||
description: 'Whether to build the Flatpak sources'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
GSERVICES:
|
||||
required: true
|
||||
@@ -79,64 +65,19 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
prepare-build-info:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
APP_VERSION_NAME: ${{ steps.prep_version.outputs.APP_VERSION_NAME }}
|
||||
APP_VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Prep APP_VERSION_NAME
|
||||
id: prep_version
|
||||
env:
|
||||
INPUT_TAG_NAME: ${{ inputs.tag_name }}
|
||||
run: |
|
||||
VERSION_NAME=$(echo "$INPUT_TAG_NAME" | sed 's/-.*//' | sed 's/v//')
|
||||
echo "APP_VERSION_NAME=$VERSION_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "Parsed Version: $VERSION_NAME"
|
||||
|
||||
- name: Extract VERSION_CODE_OFFSET from config.properties
|
||||
id: get_version_code_offset
|
||||
run: |
|
||||
OFFSET=$(grep '^VERSION_CODE_OFFSET=' config.properties | cut -d'=' -f2)
|
||||
echo "VERSION_CODE_OFFSET=$OFFSET" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Calculate Version Code from Git Commit Count
|
||||
id: calculate_version_code
|
||||
env:
|
||||
VERSION_CODE_OFFSET: ${{ steps.get_version_code_offset.outputs.VERSION_CODE_OFFSET }}
|
||||
run: |
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
if ! [[ "$VERSION_CODE_OFFSET" =~ ^[0-9]+$ ]]; then
|
||||
echo "::error::VERSION_CODE_OFFSET from config.properties is not numeric: '$VERSION_CODE_OFFSET'"
|
||||
exit 1
|
||||
fi
|
||||
VERSION_CODE=$((COMMIT_COUNT + VERSION_CODE_OFFSET))
|
||||
echo "versionCode=$VERSION_CODE" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
release-google:
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 90
|
||||
needs: [prepare-build-info]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
@@ -154,7 +95,6 @@ jobs:
|
||||
DATADOG_APPLICATION_ID: ${{ secrets.DATADOG_APPLICATION_ID }}
|
||||
DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }}
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
|
||||
run: |
|
||||
rm -f ./androidApp/google-services.json
|
||||
echo "$GSERVICES" > ./androidApp/google-services.json
|
||||
@@ -165,19 +105,18 @@ jobs:
|
||||
echo "datadogClientToken=$DATADOG_CLIENT_TOKEN"
|
||||
echo "MAPS_API_KEY=$GOOGLE_MAPS_API_KEY"
|
||||
} >> ./secrets.properties
|
||||
echo "$GOOGLE_PLAY_JSON_KEY" > ./fastlane/play-store-credentials.json
|
||||
|
||||
- name: Setup Fastlane
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
with:
|
||||
ruby-version: '4.0.7'
|
||||
bundler-cache: true
|
||||
|
||||
- name: Build and Deploy Google Play to Internal Track with Fastlane
|
||||
# Build only: publish-play uploads the bundle once every leg has built.
|
||||
- name: Build the Google release
|
||||
env:
|
||||
VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
run: bundle exec fastlane internal
|
||||
VERSION_NAME: ${{ inputs.version_name }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
run: >
|
||||
./gradlew :androidApp:bundleGoogleRelease :androidApp:assembleGoogleRelease
|
||||
-Pandroid.injected.version.name="$VERSION_NAME"
|
||||
-Pandroid.injected.version.code="$VERSION_CODE"
|
||||
-PaboutLibraries.release=true
|
||||
-Pmeshtastic.disableAbiSplits=true
|
||||
|
||||
- name: List outputs
|
||||
run: ls -R androidApp/build/outputs/
|
||||
@@ -212,15 +151,13 @@ jobs:
|
||||
|
||||
release-fdroid:
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 90
|
||||
needs: [prepare-build-info]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
@@ -238,17 +175,15 @@ jobs:
|
||||
echo "$KEYSTORE" | base64 -di > "./androidApp/$KEYSTORE_FILENAME"
|
||||
echo "$KEYSTORE_PROPERTIES" > ./keystore.properties
|
||||
|
||||
- name: Setup Fastlane
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
with:
|
||||
ruby-version: '4.0.7'
|
||||
bundler-cache: true
|
||||
|
||||
- name: Build F-Droid with Fastlane
|
||||
# No aboutLibraries.release: offlineMode keeps the output matching F-Droid's reproducible rebuild.
|
||||
- name: Build the F-Droid release
|
||||
env:
|
||||
VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
run: bundle exec fastlane fdroid_build
|
||||
VERSION_NAME: ${{ inputs.version_name }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
run: >
|
||||
./gradlew :androidApp:assembleFdroidRelease
|
||||
-Pandroid.injected.version.name="$VERSION_NAME"
|
||||
-Pandroid.injected.version.code="$VERSION_CODE"
|
||||
|
||||
- name: List outputs
|
||||
run: ls -R androidApp/build/outputs/
|
||||
@@ -268,10 +203,8 @@ jobs:
|
||||
subject-path: androidApp/build/outputs/apk/fdroid/release/*.apk
|
||||
|
||||
release-desktop:
|
||||
if: ${{ inputs.build_desktop }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
needs: [prepare-build-info]
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -285,7 +218,6 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
@@ -301,8 +233,8 @@ jobs:
|
||||
|
||||
- name: Package Native Distributions
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_appVersionName: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
ORG_GRADLE_PROJECT_appVersionName: ${{ inputs.version_name }}
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
APPIMAGE_EXTRACT_AND_RUN: 1
|
||||
SIGN_MACOS: ${{ runner.os == 'macOS' && secrets.APPLE_SIGNING_IDENTITY != '' && 'true' || 'false' }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
@@ -366,7 +298,7 @@ jobs:
|
||||
- name: Build AppImage from jpackage app-image
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
APP_VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
|
||||
APP_VERSION_NAME: ${{ inputs.version_name }}
|
||||
run: scripts/build-appimage.sh
|
||||
|
||||
- name: List Desktop Binaries
|
||||
@@ -403,10 +335,8 @@ jobs:
|
||||
desktopApp/build/compose/jars/*-release.jar
|
||||
|
||||
create-flatpak-src:
|
||||
if: ${{ inputs.build_flatpak_src }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
needs: [prepare-build-info]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -419,7 +349,6 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
@@ -457,9 +386,8 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
release-flatpak-src:
|
||||
if: ${{ inputs.build_flatpak_src }}
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 10
|
||||
needs: [create-flatpak-src]
|
||||
steps:
|
||||
- name: Download Flatpak source artifacts
|
||||
@@ -501,96 +429,55 @@ jobs:
|
||||
# (the caller deletes the tag on a failed run): the capture runs soft, github-release
|
||||
# does not gate on it, and packaging falls back to the committed sets.
|
||||
store-screenshots:
|
||||
needs: [prepare-build-info]
|
||||
uses: ./.github/workflows/store-screenshots.yml
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
soft: true
|
||||
secrets: inherit
|
||||
|
||||
# One zip per flavor laid out as fastlane's images/ folder (google feeds the Play
|
||||
# listing, fdroid the committed tree F-Droid reads) and the five desktop PNGs loose,
|
||||
# so metainfo.xml can point at them by name. A flavor or the desktop set with a shot
|
||||
# missing is replaced whole by the committed one, never mixed.
|
||||
store-screenshots-assets:
|
||||
if: ${{ !cancelled() }}
|
||||
# Play receives the bundle only after every leg has built: a failed leg makes the caller
|
||||
# delete the tag, and Play keeps any versionCode it has been sent.
|
||||
publish-play:
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
needs: [prepare-build-info, store-screenshots]
|
||||
timeout-minutes: 15
|
||||
needs: [release-google, release-fdroid, release-desktop, release-flatpak-src]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
|
||||
- name: Download the captures
|
||||
continue-on-error: true
|
||||
- name: Download the Google AAB
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: store-screenshots-*
|
||||
path: shots
|
||||
name: google-aab
|
||||
path: ${{ runner.temp }}/google-aab
|
||||
|
||||
- name: Package the screenshots
|
||||
env:
|
||||
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
|
||||
run: |
|
||||
mkdir -p out
|
||||
echo "### Store screenshots" >> "$GITHUB_STEP_SUMMARY"
|
||||
# Three form factors times five shots.
|
||||
for flavor in google fdroid; do
|
||||
src="shots/store-screenshots-$flavor"
|
||||
count=$(find "$src" -name '*.png' 2>/dev/null | wc -l)
|
||||
if [ "$count" -ne 15 ]; then
|
||||
echo "::warning::$flavor captured $count of 15 shots; the committed set is attached instead."
|
||||
echo "- $flavor: $count of 15 captured, committed set attached" >> "$GITHUB_STEP_SUMMARY"
|
||||
src=fastlane/metadata/android/en-US/images
|
||||
else
|
||||
echo "- $flavor: captured from the tag" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
(cd "$src" && zip -qr "$GITHUB_WORKSPACE/out/store-listing-screenshots-$flavor-${VERSION_CODE}.zip" .)
|
||||
done
|
||||
src=shots/store-screenshots-desktop
|
||||
count=$(find "$src" -name 'meshtastic-desktop-*.png' 2>/dev/null | wc -l)
|
||||
if [ "$count" -ne 5 ]; then
|
||||
echo "::warning::desktop captured $count of 5 shots; the committed set is attached instead."
|
||||
echo "- desktop: $count of 5 captured, committed set attached" >> "$GITHUB_STEP_SUMMARY"
|
||||
src=desktopApp/packaging/linux/screenshots
|
||||
else
|
||||
echo "- desktop: captured from the tag" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
cp "$src"/meshtastic-desktop-*.png out/
|
||||
|
||||
- name: Upload store screenshots artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
|
||||
with:
|
||||
name: store-screenshots
|
||||
path: out/*
|
||||
retention-days: 1
|
||||
bundler-cache: true
|
||||
|
||||
- name: Decode Play Store credentials
|
||||
env:
|
||||
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
|
||||
run: printf '%s\n' "$GOOGLE_PLAY_JSON_KEY" > fastlane/play-store-credentials.json
|
||||
|
||||
- name: Upload to the internal track
|
||||
env:
|
||||
AAB: ${{ runner.temp }}/google-aab/androidApp-google-release.aab
|
||||
run: bundle exec fastlane upload_internal aab:"$AAB"
|
||||
|
||||
- name: Clean up credentials
|
||||
if: always()
|
||||
run: rm -f fastlane/play-store-credentials.json
|
||||
|
||||
# Gates on the build jobs by name: store-screenshots-assets is in needs only so its
|
||||
# artifact exists before the download below, and its result is not consulted.
|
||||
github-release:
|
||||
if: >-
|
||||
${{ !cancelled()
|
||||
&& needs.prepare-build-info.result == 'success'
|
||||
&& needs.release-google.result == 'success'
|
||||
&& needs.release-fdroid.result == 'success'
|
||||
&& (needs.release-desktop.result == 'success' || !inputs.build_desktop)
|
||||
&& (needs.release-flatpak-src.result == 'success' || !inputs.build_flatpak_src) }}
|
||||
runs-on: ubuntu-26.04-arm
|
||||
timeout-minutes: 15
|
||||
needs:
|
||||
- prepare-build-info
|
||||
- release-google
|
||||
- release-fdroid
|
||||
- release-desktop
|
||||
- release-flatpak-src
|
||||
- store-screenshots-assets
|
||||
needs: [publish-play]
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -602,7 +489,7 @@ jobs:
|
||||
with:
|
||||
path: ./artifacts
|
||||
|
||||
# The raw captures are packaged by store-screenshots-assets; only its artifact ships.
|
||||
# Raw captures already uploaded stay out: store-screenshots-assets packages and attaches them.
|
||||
- name: Exclude intermediate artifacts from release
|
||||
run: rm -rf ./artifacts/flatpak-multisrc-* ./artifacts/store-screenshots-*
|
||||
|
||||
@@ -617,7 +504,7 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG: ${{ inputs.tag_name }}
|
||||
TARGET: ${{ inputs.commit_sha || github.sha }}
|
||||
TARGET: ${{ github.sha }}
|
||||
run: |
|
||||
# Fail the step if the listing itself fails — an empty PREV must only ever
|
||||
# mean "no published v* release exists yet", never a swallowed API error,
|
||||
@@ -651,9 +538,72 @@ jobs:
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3
|
||||
with:
|
||||
tag_name: ${{ inputs.tag_name }}
|
||||
target_commitish: ${{ inputs.commit_sha || github.sha }}
|
||||
name: ${{ inputs.tag_name }} (${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }})
|
||||
target_commitish: ${{ github.sha }}
|
||||
name: ${{ inputs.tag_name }} (${{ inputs.version_code }})
|
||||
body_path: release-notes.md
|
||||
files: ./artifacts/**/*
|
||||
draft: true
|
||||
prerelease: true
|
||||
|
||||
# One zip per flavor laid out as fastlane's images/ folder (google feeds the Play
|
||||
# listing, fdroid the committed tree F-Droid reads) and the five desktop PNGs loose,
|
||||
# so metainfo.xml can point at them by name. A flavor or the desktop set with a shot
|
||||
# missing is replaced whole by the committed one, never mixed. They are attached to
|
||||
# the draft github-release created, so the draft never waits on the emulator.
|
||||
store-screenshots-assets:
|
||||
if: ${{ !cancelled() && needs.github-release.result == 'success' }}
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
needs: [store-screenshots, github-release]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
|
||||
- name: Download the captures
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: store-screenshots-*
|
||||
path: shots
|
||||
|
||||
- name: Package the screenshots
|
||||
env:
|
||||
VERSION_CODE: ${{ inputs.version_code }}
|
||||
run: |
|
||||
mkdir -p out
|
||||
echo "### Store screenshots" >> "$GITHUB_STEP_SUMMARY"
|
||||
# Three form factors times five shots.
|
||||
for flavor in google fdroid; do
|
||||
src="shots/store-screenshots-$flavor"
|
||||
count=$(find "$src" -name '*.png' 2>/dev/null | wc -l)
|
||||
if [ "$count" -ne 15 ]; then
|
||||
echo "::warning::$flavor captured $count of 15 shots; the committed set is attached instead."
|
||||
echo "- $flavor: $count of 15 captured, committed set attached" >> "$GITHUB_STEP_SUMMARY"
|
||||
src=fastlane/metadata/android/en-US/images
|
||||
else
|
||||
echo "- $flavor: captured from the tag" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
(cd "$src" && zip -qr "$GITHUB_WORKSPACE/out/store-listing-screenshots-$flavor-${VERSION_CODE}.zip" .)
|
||||
done
|
||||
src=shots/store-screenshots-desktop
|
||||
count=$(find "$src" -name 'meshtastic-desktop-*.png' 2>/dev/null | wc -l)
|
||||
if [ "$count" -ne 5 ]; then
|
||||
echo "::warning::desktop captured $count of 5 shots; the committed set is attached instead."
|
||||
echo "- desktop: $count of 5 captured, committed set attached" >> "$GITHUB_STEP_SUMMARY"
|
||||
src=desktopApp/packaging/linux/screenshots
|
||||
else
|
||||
echo "- desktop: captured from the tag" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
cp "$src"/meshtastic-desktop-*.png out/
|
||||
|
||||
- name: Attach the screenshots to the draft release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG: ${{ inputs.tag_name }}
|
||||
run: gh release upload "$TAG" out/* --clobber --repo "$REPO"
|
||||
@@ -1,10 +1,10 @@
|
||||
name: Store Screenshots
|
||||
|
||||
# Captures the store-listing screenshots from the real debug apps, connected to Demo
|
||||
# Mode's hidden showcase mesh. Android runs `:store-screenshots` on an emulator: the
|
||||
# google flavor feeds the Play listing, the fdroid flavor the fastlane tree F-Droid and
|
||||
# IzzyOnDroid read, each uploaded as `store-screenshots-<flavor>` laid out as
|
||||
# `images/<type>Screenshots/<n>_<name>.png`. Desktop runs the real app on a virtual
|
||||
# Mode's hidden showcase mesh. Android runs `:store-screenshots` for both flavors on one
|
||||
# emulator: the google flavor feeds the Play listing, the fdroid flavor the fastlane
|
||||
# tree F-Droid and IzzyOnDroid read, each uploaded as `store-screenshots-<flavor>` laid
|
||||
# out as `images/<type>Screenshots/<n>_<name>.png`. Desktop runs the real app on a virtual
|
||||
# display (`store-screenshots/capture-desktop.sh`) and uploads the five Flathub PNGs as
|
||||
# `store-screenshots-desktop`.
|
||||
#
|
||||
@@ -45,26 +45,17 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
android:
|
||||
name: Android (${{ matrix.flavor }})
|
||||
name: Android
|
||||
# Hosted x64 runners expose KVM; the emulator needs it.
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 35
|
||||
continue-on-error: ${{ inputs.soft == true }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- flavor: google
|
||||
task: Google
|
||||
- flavor: fdroid
|
||||
task: Fdroid
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
@@ -76,7 +67,6 @@ jobs:
|
||||
# The google flavor draws Google Maps, which needs the debug Maps key; the fdroid
|
||||
# flavor draws MapLibre and needs nothing.
|
||||
- name: Provide the debug Maps key
|
||||
if: ${{ matrix.flavor == 'google' }}
|
||||
env:
|
||||
GOOGLE_MAPS_API_KEY_DEBUG: ${{ secrets.GOOGLE_MAPS_API_KEY_DEBUG }}
|
||||
run: |
|
||||
@@ -88,7 +78,9 @@ jobs:
|
||||
|
||||
# Built before the emulator boots, so the emulator step only installs and runs.
|
||||
- name: Build the app and the capture module
|
||||
run: ./gradlew :androidApp:assemble${{ matrix.task }}Debug :store-screenshots:assemble${{ matrix.task }}Debug
|
||||
run: >
|
||||
./gradlew :androidApp:assembleGoogleDebug :store-screenshots:assembleGoogleDebug
|
||||
:androidApp:assembleFdroidDebug :store-screenshots:assembleFdroidDebug
|
||||
|
||||
- name: Enable KVM (for the emulator)
|
||||
run: |
|
||||
@@ -97,10 +89,12 @@ jobs:
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
# The runner's `script:` runs each line in its own shell. The capture copies each
|
||||
# PNG to /data/local/tmp, which outlives the test app's uninstall at the end of the
|
||||
# connected run. --no-configuration-cache: connected tasks in a com.android.test
|
||||
# module are not configuration-cache serializable (see scheduled-baseline.yml).
|
||||
# The runner's `script:` runs each line in its own shell and stops at the first that
|
||||
# fails, so a failure is recorded in out/failed and both flavors are still pulled.
|
||||
# Each flavor leaves its PNGs in /data/local/tmp/store-screenshots/<flavor>, which
|
||||
# outlives the test app's uninstall at the end of the connected run.
|
||||
# --no-configuration-cache: connected tasks in a com.android.test module are not
|
||||
# configuration-cache serializable (see scheduled-baseline.yml).
|
||||
- name: Capture on the emulator
|
||||
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2
|
||||
with:
|
||||
@@ -108,32 +102,45 @@ jobs:
|
||||
target: google_apis
|
||||
arch: x86_64
|
||||
profile: pixel_6
|
||||
cores: 4
|
||||
disable-animations: true
|
||||
emulator-options: -no-window -gpu swiftshader -noaudio -no-boot-anim -camera-back none
|
||||
script: |
|
||||
./gradlew :store-screenshots:connected${{ matrix.task }}DebugAndroidTest -Dorg.gradle.isolated-projects=false --no-configuration-cache
|
||||
mkdir -p out/images && adb pull /data/local/tmp/store-screenshots/. out/images/
|
||||
mkdir -p out/google out/fdroid
|
||||
./gradlew --continue :store-screenshots:connectedGoogleDebugAndroidTest :store-screenshots:connectedFdroidDebugAndroidTest -Dorg.gradle.isolated-projects=false --no-configuration-cache || touch out/failed
|
||||
adb pull /data/local/tmp/store-screenshots/google/. out/google/ || touch out/failed
|
||||
adb pull /data/local/tmp/store-screenshots/fdroid/. out/fdroid/ || touch out/failed
|
||||
test ! -e out/failed
|
||||
|
||||
- name: Summarize the captures
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
FLAVOR: ${{ matrix.flavor }}
|
||||
run: |
|
||||
{
|
||||
echo "### Store screenshots (${FLAVOR})"
|
||||
if [ -d out/images ]; then
|
||||
find out/images -name '*.png' | sort | sed 's|^out/images/|- |'
|
||||
else
|
||||
echo "- none captured"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
for flavor in google fdroid; do
|
||||
{
|
||||
echo "### Store screenshots (${flavor})"
|
||||
if [ -n "$(find "out/$flavor" -name '*.png' 2>/dev/null)" ]; then
|
||||
find "out/$flavor" -name '*.png' | sort | sed "s|^out/$flavor/|- |"
|
||||
else
|
||||
echo "- none captured"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
done
|
||||
|
||||
- name: Upload the captures
|
||||
- name: Upload the google captures
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: store-screenshots-${{ matrix.flavor }}
|
||||
path: out/images
|
||||
name: store-screenshots-google
|
||||
path: out/google
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload the fdroid captures
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: store-screenshots-fdroid
|
||||
path: out/fdroid
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
@@ -141,7 +148,7 @@ jobs:
|
||||
desktop:
|
||||
name: Desktop
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 20
|
||||
continue-on-error: ${{ inputs.soft == true }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -149,7 +156,6 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
name: Update Changelog
|
||||
|
||||
# Manual only: promote.yml stamps the released section at release time on its
|
||||
# own. Dispatch this to refresh the [Unreleased] section between releases.
|
||||
# Create or Promote Release dispatches this after every internal, closed and open run;
|
||||
# a production promotion stamps the released section itself. Dispatch it by hand to
|
||||
# refresh the [Unreleased] section at any other time.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
# The PR is opened and queued with CROWDIN_GITHUB_TOKEN through .github/actions/bot-pr.
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
concurrency:
|
||||
group: changelog-${{ github.ref }}
|
||||
@@ -266,41 +266,13 @@ jobs:
|
||||
|
||||
- name: Create or update changelog PR
|
||||
if: steps.tags.outputs.prod != '' && steps.update.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
BRANCH="automation/update-changelog"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Force-update the automation branch
|
||||
git checkout -B "$BRANCH"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "docs: update CHANGELOG.md"
|
||||
git push origin "$BRANCH" --force
|
||||
|
||||
# Create or update the PR
|
||||
EXISTING_PR=$(gh pr list --head "$BRANCH" --state open --json number -q '.[0].number')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
echo "Updated existing PR #$EXISTING_PR"
|
||||
else
|
||||
gh pr create \
|
||||
--title "docs: update CHANGELOG.md" \
|
||||
--body "Automated changelog refresh, dispatched from main." \
|
||||
--head "$BRANCH" \
|
||||
--base main \
|
||||
--label "automation" \
|
||||
--label "skip-changelog"
|
||||
echo "Created new changelog PR"
|
||||
fi
|
||||
|
||||
# Post the required "Check Workflow Status" commit status so the PR
|
||||
# isn't blocked. PRs from GITHUB_TOKEN don't trigger pull_request
|
||||
# workflows, so the normal CI never runs. CHANGELOG-only PRs don't
|
||||
# need CI checks.
|
||||
COMMIT_SHA=$(git rev-parse HEAD)
|
||||
gh api "repos/${{ github.repository }}/statuses/${COMMIT_SHA}" \
|
||||
-f state="success" \
|
||||
-f context="Check Workflow Status" \
|
||||
-f description="Skipped — changelog-only PR"
|
||||
uses: ./.github/actions/bot-pr
|
||||
with:
|
||||
token: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
branch: automation/update-changelog
|
||||
title: 'docs: update CHANGELOG.md'
|
||||
body: 'Automated changelog refresh, dispatched from main.'
|
||||
add-paths: CHANGELOG.md
|
||||
labels: |
|
||||
automation
|
||||
skip-changelog
|
||||
@@ -8,29 +8,17 @@ on:
|
||||
paths:
|
||||
- 'scripts/verify-flatpak/**'
|
||||
- '.github/workflows/verify-flatpak.yml'
|
||||
# The dependency surface the manifest captures is verified post-merge. This is not a
|
||||
# required check and never ran in the merge queue, so per-PR it cost ~2 runner slots
|
||||
# for a signal that blocks nothing; post-merge still catches a break within one merge,
|
||||
# and `main` itself was previously never verified at all.
|
||||
# Post-merge only for what the check itself depends on. The offline manifest pins the
|
||||
# Gradle distribution apart from the wrapper, so a wrapper bump is checked on merge.
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'scripts/verify-flatpak/**'
|
||||
- '.github/workflows/verify-flatpak.yml'
|
||||
- 'build.gradle.kts'
|
||||
- 'settings.gradle.kts'
|
||||
# The desktop module's build config shapes the uber jar the flatpak wraps.
|
||||
- 'desktopApp/**'
|
||||
# The offline manifest pins the Gradle distribution independently of the wrapper —
|
||||
# a wrapper bump without a manifest update breaks the offline build silently.
|
||||
- 'gradle/wrapper/**'
|
||||
# build.gradle.kts reads compose-multiplatform from the catalog (#6911), so a
|
||||
# catalog-only bump changes the manifest's platform URLs. Deliberately the whole
|
||||
# file and not a key filter: a filter naming today's keys goes stale silently the
|
||||
# moment the manifest reads another one — the failure #6911 existed to remove.
|
||||
- 'gradle/libs.versions.toml'
|
||||
# Drift no path filter can see: a Flathub runtime bump, or an upstream artifact that
|
||||
# moved or vanished. Nothing in this repo changes, so no other trigger would fire.
|
||||
# The dependency surface the manifest captures (desktopApp/**, the version catalog, the
|
||||
# root build scripts) is verified nightly, as is drift no path filter can see: a
|
||||
# Flathub runtime bump, or an upstream artifact that moved or vanished.
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
workflow_dispatch:
|
||||
@@ -60,8 +48,6 @@ jobs:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Renovate mirrors wrapper bumps into the manifest's distribution URL but cannot
|
||||
# rewrite its sha256, so on a wrapper bump this drift is expected: fail here in
|
||||
|
||||
@@ -17,8 +17,7 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
# The PR is opened and queued with CROWDIN_GITHUB_TOKEN, as scheduled-updates.yml does:
|
||||
# GITHUB_TOKEN may not enable auto-merge on protected main, and its PRs run no checks.
|
||||
# The PR is opened and queued with CROWDIN_GITHUB_TOKEN through .github/actions/bot-pr.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
@@ -31,7 +30,7 @@ concurrency:
|
||||
jobs:
|
||||
bump:
|
||||
if: ${{ github.repository == 'meshtastic/Meshtastic-Android' && !github.event.release.prerelease && !github.event.release.draft }}
|
||||
runs-on: ubuntu-26.04-arm
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
TAG: ${{ inputs.tag || github.event.release.tag_name }}
|
||||
@@ -87,20 +86,16 @@ jobs:
|
||||
# Creates the PR, or updates it on a retry. Pushed with the PAT, so pull-request.yml's
|
||||
# AppStream and what's-new gates run on it like any other bump.
|
||||
- name: Open the bump PR
|
||||
id: pr
|
||||
if: ${{ steps.next.outputs.skip == 'false' }}
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
|
||||
uses: ./.github/actions/bot-pr
|
||||
with:
|
||||
token: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
commit-message: 'chore: bump VERSION_NAME_BASE to ${{ steps.next.outputs.next }}'
|
||||
title: 'chore: bump VERSION_NAME_BASE to ${{ steps.next.outputs.next }}'
|
||||
body: |
|
||||
Opens the ${{ steps.next.outputs.next }} line now that v${{ steps.next.outputs.shipped }} has shipped.
|
||||
|
||||
The metainfo `<description>` for ${{ steps.next.outputs.next }} is a placeholder. Rewrite it and re-run `python3 scripts/sync-play-changelog.py` before the ${{ steps.next.outputs.next }} internal cut, or it ships as the release Highlights and the Play what's-new.
|
||||
branch: ${{ steps.next.outputs.branch }}
|
||||
base: main
|
||||
delete-branch: true
|
||||
add-paths: |
|
||||
config.properties
|
||||
desktopApp/packaging/linux/org.meshtastic.MeshtasticDesktop.metainfo.xml
|
||||
@@ -108,23 +103,3 @@ jobs:
|
||||
labels: |
|
||||
automation
|
||||
skip-changelog
|
||||
|
||||
# Same request as scheduled-updates.yml: the queue sets the merge method, and a PR
|
||||
# that is already mergeable is refused the request and merges directly.
|
||||
- name: Enable auto-merge
|
||||
if: ${{ steps.pr.outputs.pull-request-operation == 'created' || steps.pr.outputs.pull-request-operation == 'updated' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pull-request-number }}
|
||||
run: |
|
||||
PR_ID=$(gh pr view "$PR_NUMBER" --json id --jq .id)
|
||||
QUERY=$(cat <<'GQL'
|
||||
mutation($id: ID!) {
|
||||
enablePullRequestAutoMerge(input: { pullRequestId: $id }) {
|
||||
pullRequest { number autoMergeRequest { enabledAt } }
|
||||
}
|
||||
}
|
||||
GQL
|
||||
)
|
||||
gh api graphql -f query="$QUERY" -F id="$PR_ID" \
|
||||
|| gh pr merge "$PR_NUMBER"
|
||||
Reference in new issue
Block a user