Skip to content
127 changes: 118 additions & 9 deletions .github/workflows/testflight.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
name: Validate trigger
if: >
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
github.event_name == 'issue_comment'
Expand Down Expand Up @@ -52,7 +53,7 @@ jobs:
if (netbirdMatch) core.setOutput('netbird-ref', netbirdMatch[1]);
}

if (context.eventName === 'push') {
if (context.eventName === 'workflow_dispatch' || context.eventName === 'push') {
core.setOutput('ref', context.sha);
core.setOutput('should-build', 'true');
core.setOutput('upload', 'true');
Expand Down Expand Up @@ -149,19 +150,87 @@ jobs:
echo "version=$NEXT" >> "$GITHUB_OUTPUT"
echo "Tag: $LATEST_TAG → next: $NEXT"

- name: Fetch latest build number from App Store Connect
if: steps.pre.outputs.should-build == 'true'
id: asc-build
env:
ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY }}
APP_ID: ${{ secrets.APP_STORE_APP_ID_IOS }}
run: |
VERSION="${{ steps.pre.outputs.version-override || steps.derive.outputs.version }}"

pip install cryptography --quiet

echo "$PRIVATE_KEY_BASE64" | base64 --decode > /tmp/AuthKey.p8
trap 'rm -f /tmp/AuthKey.p8' EXIT

JWT=$(python3 -c "import base64,json,time,os;from cryptography.hazmat.primitives import hashes,serialization;from cryptography.hazmat.primitives.asymmetric import ec;from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature;key=serialization.load_pem_private_key(open('/tmp/AuthKey.p8','rb').read(),password=None);b64url=lambda d:base64.urlsafe_b64encode(d if isinstance(d,bytes) else d.encode()).rstrip(b'=').decode();now=int(time.time());h=b64url(json.dumps({'alg':'ES256','kid':os.environ['KEY_ID'],'typ':'JWT'},separators=(',',':')));p=b64url(json.dumps({'iss':os.environ['ISSUER_ID'],'exp':now+1200,'aud':'appstoreconnect-v1'},separators=(',',':')));msg=f'{h}.{p}'.encode();sig_der=key.sign(msg,ec.ECDSA(hashes.SHA256()));r,s=decode_dss_signature(sig_der);sig=b64url(r.to_bytes(32,'big')+s.to_bytes(32,'big'));print(f'{h}.{p}.{sig}')")
Comment thread
evgeniyChepelev marked this conversation as resolved.

HTTP_STATUS=$(curl -sg \
-o /tmp/asc_response.json \
-w "%{http_code}" \
"https://api.appstoreconnect.apple.com/v1/builds?filter[app]=$APP_ID&filter[preReleaseVersion.version]=$VERSION&sort=-uploadedDate&limit=1" \
-H "Authorization: Bearer $JWT")

RESPONSE=$(cat /tmp/asc_response.json)
echo "HTTP status: $HTTP_STATUS"

if [ "$HTTP_STATUS" != "200" ]; then
echo "API error response:"
echo "$RESPONSE" | jq . || echo "$RESPONSE"
exit 1
fi

LATEST_BUILD=$(echo "$RESPONSE" | jq -r 'if (.data | length) > 0 and (.data[0].attributes.version != null) then .data[0].attributes.version else "none" end')

echo "latest-build=$LATEST_BUILD" >> "$GITHUB_OUTPUT"

echo "========================================="
echo " App Store Connect — latest build info"
echo " Version: $VERSION"
echo " Latest uploaded build: $LATEST_BUILD"
echo "========================================="


- name: Finalize outputs
id: finalize
uses: actions/github-script@v7
env:
PRE_REF: ${{ steps.pre.outputs.ref }}
PRE_SHOULD_BUILD: ${{ steps.pre.outputs.should-build }}
PRE_UPLOAD: ${{ steps.pre.outputs.upload }}
PRE_NETBIRD_REF: ${{ steps.pre.outputs.netbird-ref }}
PRE_BUILD_NUMBER: ${{ steps.pre.outputs.build-number }}
PRE_VERSION_OVERRIDE: ${{ steps.pre.outputs.version-override }}
DERIVE_VERSION: ${{ steps.derive.outputs.version }}
ASC_LATEST_BUILD: ${{ steps.asc-build.outputs.latest-build }}
GH_RUN_NUMBER: ${{ github.run_number }}
with:
script: |
core.setOutput('ref', '${{ steps.pre.outputs.ref }}');
core.setOutput('should-build', '${{ steps.pre.outputs.should-build }}');
core.setOutput('upload', '${{ steps.pre.outputs.upload }}');
core.setOutput('build-number', '${{ steps.pre.outputs.build-number }}');
core.setOutput('netbird-ref', '${{ steps.pre.outputs.netbird-ref }}');

const override = '${{ steps.pre.outputs.version-override }}';
const derived = '${{ steps.derive.outputs.version }}';
core.setOutput('ref', process.env.PRE_REF);
core.setOutput('should-build', process.env.PRE_SHOULD_BUILD);
core.setOutput('upload', process.env.PRE_UPLOAD);
core.setOutput('netbird-ref', process.env.PRE_NETBIRD_REF);

const overrideBuild = process.env.PRE_BUILD_NUMBER || '';
const latestBuild = process.env.ASC_LATEST_BUILD || '';
const runNumber = process.env.GH_RUN_NUMBER || '';
let buildNumber;
if (overrideBuild && overrideBuild !== runNumber) {
buildNumber = overrideBuild;
} else if (latestBuild && latestBuild !== 'none') {
const parsed = parseInt(latestBuild, 10);
buildNumber = !isNaN(parsed) ? String(parsed + 1) : '1';
} else {
buildNumber = '1';
}
core.info(`build-number: ${buildNumber} (latest=${latestBuild}, override=${overrideBuild})`);
core.setOutput('build-number', buildNumber);

const override = process.env.PRE_VERSION_OVERRIDE || '';
const derived = process.env.DERIVE_VERSION || '';
core.setOutput('version', override || derived);
core.info(`version: ${override || derived} (override=${override || 'none'}, derived=${derived})`);

Expand Down Expand Up @@ -237,3 +306,43 @@ jobs:
issue_number: prNumber,
body,
});

notify-merge:
name: Comment on merge commit
needs: [gate, build, build-tvos]
if: >
always() &&
github.event_name == 'push' &&
needs.gate.outputs.should-build == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Comment on commit
uses: actions/github-script@v7
env:
VERSION: ${{ needs.gate.outputs.version }}
BUILD_NUMBER: ${{ needs.gate.outputs.build-number }}
IOS_RESULT: ${{ needs.build.result }}
TVOS_RESULT: ${{ needs.build-tvos.result }}
with:
script: |
const version = process.env.VERSION;
const buildNumber = process.env.BUILD_NUMBER;
const iosResult = process.env.IOS_RESULT;
const tvosResult = process.env.TVOS_RESULT;
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;

const iosOk = iosResult === 'success';
const tvosOk = tvosResult === 'success';
const iosBadge = iosOk ? '✅' : '❌';
const tvosBadge = tvosOk ? '✅' : '❌';

const body = `**TestFlight** \`${version} (${buildNumber})\` — iOS ${iosBadge} tvOS ${tvosBadge}\n\n[View workflow run](${runUrl})`;

await github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
body,
});
Loading