mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
8e81ebbd77
element-x-ios had no release machinery at all: no fastlane, no Gemfile, and none of its workflows built or uploaded anything. Upstream ships via Xcode Cloud, which needs Element's private Enterprise submodule and so is unavailable here, while the legacy element-ios fork already proves a fastlane pipeline against the same Apple team, certificate and App Store Connect key. Tagging healthchat-v<version> now builds a signed App Store IPA on the org's Mac Studio and uploads it to TestFlight. Release notes come from a new HEALTHCHAT_CHANGES.md, because upstream's own tooling owns CHANGES.md. The lane deliberately does not pin Xcode. The runner carries a single globally selected version, and naming an absent one does not fail fast — it sends xcodes into a multi-GB authenticated download that no CI secret can satisfy. Each run logs xcodebuild -version instead, which is the only record of what compiled a build. Everything here is fork-owned, since upstream has no fastlane directory, so none of it can conflict on rebase.
134 lines
5.3 KiB
YAML
134 lines
5.3 KiB
YAML
name: Release HealthChat
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'healthchat-v*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'Marketing version (e.g. 26.7.4). Must be 1-3 dot-separated integers — Apple rejects anything else.'
|
|
required: true
|
|
type: string
|
|
release_notes_override:
|
|
description: 'Optional release notes. If empty, falls back to the HEALTHCHAT_CHANGES.md section for the given version.'
|
|
required: false
|
|
type: string
|
|
|
|
permissions:
|
|
# Minimum needed for actions/checkout to clone an internal repo. The workflow does not push,
|
|
# comment, or create releases — everything else authenticates with the ASC key and .p12.
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: release-healthchat-${{ github.event.inputs.version || github.ref_name }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release:
|
|
name: Build & upload to TestFlight
|
|
runs-on: [self-hosted, macOS]
|
|
timeout-minutes: 90
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# This repo's only submodule is element-hq's private Enterprise repo, which Novaloop
|
|
# cannot clone. Checking out submodules fails the job on its very first step.
|
|
submodules: false
|
|
persist-credentials: false
|
|
|
|
- name: Record toolchain
|
|
# HealthChat: nothing pins Xcode (the runner carries a single globally-selected one), so this
|
|
# log line is the only record of which compiler produced a given TestFlight build.
|
|
run: xcodebuild -version
|
|
|
|
- name: Resolve marketing version
|
|
id: version
|
|
env:
|
|
# Read via env, never `${{ }}` inside the script: the input is attacker-controlled text
|
|
# that GitHub would splice into the shell before the regex below could reject it.
|
|
INPUT_VERSION: ${{ github.event.inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
VERSION="$INPUT_VERSION"
|
|
else
|
|
VERSION="${GITHUB_REF_NAME#healthchat-v}"
|
|
fi
|
|
# Apple caps CFBundleShortVersionString at three integers. The Fastfile guards this too,
|
|
# but failing here costs seconds instead of a signing run.
|
|
if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]]; then
|
|
echo "::error::Invalid marketing version '$VERSION' — expected 1-3 dot-separated integers, e.g. 26.7.4"
|
|
exit 1
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "Resolved marketing version: $VERSION"
|
|
|
|
- name: Extract release notes
|
|
id: notes
|
|
env:
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
OVERRIDE: ${{ github.event.inputs.release_notes_override }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -n "$OVERRIDE" ]]; then
|
|
NOTES="$OVERRIDE"
|
|
echo "Using release notes from the workflow input"
|
|
elif [[ ! -f HEALTHCHAT_CHANGES.md ]]; then
|
|
echo "::error::HEALTHCHAT_CHANGES.md is missing — it is the release-notes source"
|
|
exit 1
|
|
else
|
|
NOTES=$(awk -v ver="$VERSION" '
|
|
/^## Changes in / {
|
|
if (found) { exit }
|
|
if ($0 ~ "## Changes in " ver "( |$|\\()") { found = 1; next }
|
|
}
|
|
found { print }
|
|
' HEALTHCHAT_CHANGES.md | sed '/^[[:space:]]*$/{N;/^\n$/D;}')
|
|
if [[ -z "$NOTES" ]]; then
|
|
NOTES="HealthChat ${VERSION} — automated build from ${GITHUB_REF_NAME}."
|
|
echo "::warning::No HEALTHCHAT_CHANGES.md section found for $VERSION — using fallback"
|
|
fi
|
|
fi
|
|
{
|
|
echo 'notes<<NOTES_EOF'
|
|
echo "$NOTES"
|
|
echo 'NOTES_EOF'
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Bundle install
|
|
run: |
|
|
bundle config path vendor/bundle
|
|
bundle install --jobs 4 --retry 3
|
|
|
|
- name: Import signing certificate
|
|
uses: apple-actions/import-codesign-certs@b610f78488812c1e56b20e6df63ec42d833f2d14 # v6.0.0
|
|
with:
|
|
p12-file-base64: ${{ secrets.HEALTHCHAT_CERTIFICATES_P12 }}
|
|
p12-password: ${{ secrets.HEALTHCHAT_CERTIFICATES_P12_PASSWORD }}
|
|
|
|
- name: Build & upload to TestFlight
|
|
run: bundle exec fastlane release_testflight
|
|
env:
|
|
# Passed via env rather than lane options so multi-line notes containing quotes,
|
|
# $, or backticks survive without shell escaping.
|
|
HEALTHCHAT_MARKETING_VERSION: ${{ steps.version.outputs.version }}
|
|
HEALTHCHAT_RELEASE_NOTES: ${{ steps.notes.outputs.notes }}
|
|
APPSTORECONNECT_KEY_ID: ${{ secrets.HEALTHCHAT_ASC_KEY_ID }}
|
|
APPSTORECONNECT_KEY_ISSUER_ID: ${{ secrets.HEALTHCHAT_ASC_KEY_ISSUER_ID }}
|
|
APPSTORECONNECT_KEY_CONTENT: ${{ secrets.HEALTHCHAT_ASC_KEY_CONTENT }}
|
|
TEAM_ID: '6G37K72W82'
|
|
|
|
- name: Upload artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: healthchat-${{ steps.version.outputs.version }}
|
|
path: |
|
|
out/ElementX.ipa
|
|
out/ElementX.app.dSYM.zip
|
|
build/Logs/Gym/*.log
|
|
retention-days: 90
|
|
if-no-files-found: warn
|