Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4215e62
chore: bump VERSION to 0.0.6 [skip ci]
github-actions[bot] Feb 14, 2026
c154e84
feat: rename project to charflow (#18)
bahadirgezer Feb 14, 2026
6013ea7
Merge branch 'master' into develop
bahadirgezer Feb 14, 2026
a9fcee0
chore: bump VERSION to 0.1.2 [skip ci]
github-actions[bot] Feb 14, 2026
d8968b2
fix: double release bug (#20)
bahadirgezer Feb 14, 2026
5317846
chore: bump VERSION to 0.1.3 [skip ci]
github-actions[bot] Feb 14, 2026
c01c493
fix: xcode cloud build (#21)
bahadirgezer Feb 14, 2026
a603894
chore: bump VERSION to 0.1.4 [skip ci]
github-actions[bot] Feb 14, 2026
3616b64
feat: add manual xcode upload wf (#24)
bahadirgezer Feb 22, 2026
5b8cd43
chore: bump VERSION to 0.1.5 [skip ci]
github-actions[bot] Feb 22, 2026
38be2ca
build(deps): bump actions/upload-artifact from 4 to 6 (#26)
dependabot[bot] Feb 22, 2026
3a94b52
chore: bump VERSION to 0.1.6 [skip ci]
github-actions[bot] Feb 22, 2026
002b573
fix: update deprecated app store connect CLI (#29)
bahadirgezer Feb 22, 2026
c09af1d
chore: bump VERSION to 0.1.7 [skip ci]
github-actions[bot] Feb 22, 2026
823d80f
build(deps): bump apple-actions/import-codesign-certs from 3 to 6 (#27)
dependabot[bot] Feb 22, 2026
dce7148
chore: bump VERSION to 0.1.8 [skip ci]
github-actions[bot] Feb 22, 2026
6d872c9
build(deps): bump apple-actions/download-provisioning-profiles from 1…
dependabot[bot] Feb 22, 2026
3ccc507
chore: bump VERSION to 0.1.9 [skip ci]
github-actions[bot] Feb 22, 2026
3e456b8
fix: bump apple actions profile (#30)
bahadirgezer Feb 22, 2026
8e38404
chore: bump VERSION to 0.1.10 [skip ci]
github-actions[bot] Feb 22, 2026
39bdc47
fix: deterministic singing profiles (#31)
bahadirgezer Feb 22, 2026
34429b0
fix: profile certificate mismatch (#32)
bahadirgezer Feb 22, 2026
1faca08
fix: remove wrong post cleanup (#33)
bahadirgezer Feb 22, 2026
31d9d88
chore: bump VERSION to 0.1.11 [skip ci]
github-actions[bot] Feb 23, 2026
48cb5b2
build(deps): bump actions/upload-artifact from 6 to 7 (#34)
dependabot[bot] Mar 1, 2026
aeda464
chore: bump VERSION to 0.1.12 [skip ci]
github-actions[bot] Mar 1, 2026
887f7e1
feat: app store submit wf (#25)
bahadirgezer Mar 21, 2026
b34ddd8
update release
bahadirgezer Mar 21, 2026
7cfb8f2
fix: update release (#35)
bahadirgezer Mar 22, 2026
d169095
chore: bump VERSION to 0.1.14 [skip ci]
github-actions[bot] Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
292 changes: 292 additions & 0 deletions .github/workflows/appstore-submit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
name: App Store Submit

on:
repository_dispatch:
types: [appstore-submit-ready]
workflow_dispatch:
inputs:
tag:
description: "Release tag (optional, e.g. v1.2.3)"
required: false
type: string
version:
description: "Marketing version override (x.y.z)"
required: false
type: string
build_number:
description: "Build number override (CFBundleVersion)"
required: false
type: string
build_id:
description: "ASC build ID override (optional)"
required: false
type: string
sync_assets:
description: "Sync appstore-assets screenshots/previews before submission"
required: false
type: boolean
default: false

permissions:
contents: read

defaults:
run:
shell: bash

concurrency:
group: appstore-submit-${{ github.event_name == 'repository_dispatch' && github.event.client_payload.tag || github.ref }}
cancel-in-progress: false

jobs:
preflight:
name: Preflight Checks
if: github.event_name == 'workflow_dispatch' || github.event.client_payload.prerelease != true
runs-on: [self-hosted, macOS, ARM64, bgpro-charstack]
timeout-minutes: 45

outputs:
tag: ${{ steps.ctx.outputs.tag }}
version: ${{ steps.ctx.outputs.version }}
build_number: ${{ steps.build.outputs.build_number }}
build_id: ${{ steps.build.outputs.build_id }}
app_store_version_id: ${{ steps.appstore.outputs.app_store_version_id }}
sync_assets: ${{ steps.ctx.outputs.sync_assets }}

env:
ASC_APP_ID: ${{ vars.ASC_APP_ID }}
ASC_PRIMARY_LOCALE: ${{ vars.ASC_PRIMARY_LOCALE || 'en-US' }}
ASC_SUPPORT_URL: ${{ vars.ASC_SUPPORT_URL }}
ASC_PRIVACY_POLICY_URL: ${{ vars.ASC_PRIVACY_POLICY_URL }}
APPSTORE_API_PRIVATE_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }}

steps:
- name: Checkout dispatch tag
if: github.event_name == 'repository_dispatch' && github.event.client_payload.tag != ''
uses: actions/checkout@v6
with:
ref: ${{ github.event.client_payload.tag }}

- name: Checkout workflow_dispatch tag
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
uses: actions/checkout@v6
with:
ref: ${{ inputs.tag }}

- name: Checkout default branch
if: (github.event_name == 'workflow_dispatch' && inputs.tag == '') || (github.event_name == 'repository_dispatch' && github.event.client_payload.tag == '')
uses: actions/checkout@v6

- name: Validate required configuration
run: |
set -euo pipefail
required=(
ASC_APP_ID
APPSTORE_API_PRIVATE_KEY
APPSTORE_API_KEY_ID
APPSTORE_API_ISSUER_ID
)
for name in "${required[@]}"; do
if [[ -z "${!name:-}" ]]; then
echo "error: missing required secret/var: $name" >&2
exit 1
fi
done

- name: Build submission context
id: ctx
env:
DISPATCH_TAG: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.tag || '' }}
DISPATCH_VERSION: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version || '' }}
DISPATCH_BUILD_NUMBER: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.build_number || '' }}
DISPATCH_BUILD_ID: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.build_id || '' }}
DISPATCH_SYNC_ASSETS: false
INPUT_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || '' }}
INPUT_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || '' }}
INPUT_BUILD_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.build_number || '' }}
INPUT_BUILD_ID: ${{ github.event_name == 'workflow_dispatch' && inputs.build_id || '' }}
INPUT_SYNC_ASSETS: ${{ github.event_name == 'workflow_dispatch' && inputs.sync_assets || false }}
run: |
set -euo pipefail

tag="${DISPATCH_TAG:-${INPUT_TAG:-}}"
version="${DISPATCH_VERSION:-${INPUT_VERSION:-}}"
requested_build_number="${DISPATCH_BUILD_NUMBER:-${INPUT_BUILD_NUMBER:-}}"
requested_build_id="${DISPATCH_BUILD_ID:-${INPUT_BUILD_ID:-}}"

if [[ -z "$version" ]]; then
if [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
version="${BASH_REMATCH[1]}"
else
version="$(tr -d '[:space:]' < VERSION)"
fi
fi

if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "error: invalid marketing version '$version'" >&2
exit 1
fi

if [[ -n "$requested_build_number" ]]; then
if ! [[ "$requested_build_number" =~ ^[0-9]+$ ]]; then
echo "error: build_number must be numeric" >&2
exit 1
fi
fi

sync_assets="false"
if [[ "${INPUT_SYNC_ASSETS}" == "true" ]]; then
sync_assets="true"
fi

{
echo "tag=$tag"
echo "version=$version"
echo "requested_build_number=$requested_build_number"
echo "requested_build_id=$requested_build_id"
echo "sync_assets=$sync_assets"
} >> "$GITHUB_OUTPUT"

- name: Validate submission checklist
run: |
set -euo pipefail
python3 scripts/appstore/validate_submission_checklist.py
env:
VERSION: ${{ steps.ctx.outputs.version }}

- name: Resolve App Store version
id: appstore
run: |
set -euo pipefail

lookup="$(scripts/appstore/asc_api.sh GET "/v1/appStoreVersions?filter[app]=${ASC_APP_ID}&filter[versionString]=${{ steps.ctx.outputs.version }}&filter[platform]=IOS&limit=1")"
app_store_version_id="$(printf '%s' "$lookup" | python3 -c 'import json,sys; items=(json.load(sys.stdin).get("data") or []); print(items[0].get("id", "") if items else "")')"

if [[ -z "$app_store_version_id" ]]; then
VERSION_STRING="${{ steps.ctx.outputs.version }}" APP_ID="${ASC_APP_ID}" python3 -c 'import json,os; print(json.dumps({"data":{"type":"appStoreVersions","attributes":{"platform":"IOS","versionString":os.environ["VERSION_STRING"]},"relationships":{"app":{"data":{"type":"apps","id":os.environ["APP_ID"]}}}}}))' > build/create-app-store-version.json
created="$(scripts/appstore/asc_api.sh POST "/v1/appStoreVersions" build/create-app-store-version.json)"
app_store_version_id="$(printf '%s' "$created" | python3 -c 'import json,sys; payload=json.load(sys.stdin); print((payload.get("data") or {}).get("id", ""))')"
fi

if [[ -z "$app_store_version_id" ]]; then
echo "error: failed to resolve appStoreVersion ID" >&2
exit 1
fi

echo "app_store_version_id=$app_store_version_id" >> "$GITHUB_OUTPUT"

- name: Resolve VALID build
id: build
run: |
set -euo pipefail

requested_build_id="${{ steps.ctx.outputs.requested_build_id }}"
requested_build_number="${{ steps.ctx.outputs.requested_build_number }}"

if [[ -n "$requested_build_id" ]]; then
response="$(scripts/appstore/asc_api.sh GET "/v1/builds/${requested_build_id}")"
parsed="$(printf '%s' "$response" | python3 -c 'import json,sys; payload=json.load(sys.stdin); data=(payload.get("data") or {}); attrs=(data.get("attributes") or {}); print("{}|{}|{}".format(data.get("id", ""), attrs.get("processingState", ""), attrs.get("version", "")))')"
build_id="${parsed%%|*}"
rest="${parsed#*|}"
state="${rest%%|*}"
build_number="${rest#*|}"
else
query="/v1/builds?filter[app]=${ASC_APP_ID}&include=preReleaseVersion&sort=-uploadedDate&limit=50"
response="$(scripts/appstore/asc_api.sh GET "$query")"
selector_args=(
scripts/appstore/select_build.py
--marketing-version "${{ steps.ctx.outputs.version }}"
)
if [[ -n "$requested_build_number" ]]; then
selector_args+=( --build-number "$requested_build_number" )
fi
parsed="$(printf '%s' "$response" | python3 "${selector_args[@]}")"
build_id="${parsed%%|*}"
rest="${parsed#*|}"
state="${rest%%|*}"
build_number="${rest#*|}"
fi

if [[ -z "$build_id" ]]; then
echo "error: no matching build found for app=${ASC_APP_ID} version=${{ steps.ctx.outputs.version }} build_number=${requested_build_number:-latest}" >&2
exit 1
fi

if [[ "$state" != "VALID" ]]; then
echo "error: selected build is not VALID (state=$state, id=$build_id)" >&2
exit 1
fi

echo "build_id=$build_id" >> "$GITHUB_OUTPUT"
echo "build_number=$build_number" >> "$GITHUB_OUTPUT"

- name: Optional asset sync
if: steps.ctx.outputs.sync_assets == 'true'
run: |
set -euo pipefail
scripts/appstore/sync_assets.sh \
--app-store-version-id "${{ steps.appstore.outputs.app_store_version_id }}" \
--locale "${ASC_PRIMARY_LOCALE}" \
--root "appstore-assets"

- name: Upload preflight artifact
uses: actions/upload-artifact@v4
with:
name: appstore-submit-preflight
path: build/submission-preflight.json

submit:
name: Submit for App Review
needs: preflight
if: needs.preflight.result == 'success'
runs-on: [self-hosted, macOS, ARM64, bgpro-charstack]
timeout-minutes: 30
environment: appstore-production

env:
APPSTORE_API_PRIVATE_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }}

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Prepare workspace
run: mkdir -p build

- name: Attach build to App Store version
run: |
set -euo pipefail

python3 -c 'import json; print(json.dumps({"data":{"type":"builds","id":"${{ needs.preflight.outputs.build_id }}"}}))' > build/attach-build.json
scripts/appstore/asc_api.sh PATCH "/v1/appStoreVersions/${{ needs.preflight.outputs.app_store_version_id }}/relationships/build" build/attach-build.json >/dev/null

- name: Set manual release control
run: |
set -euo pipefail

python3 -c 'import json; print(json.dumps({"data":{"type":"appStoreVersions","id":"${{ needs.preflight.outputs.app_store_version_id }}","attributes":{"releaseType":"MANUAL"}}}))' > build/set-release-type.json
scripts/appstore/asc_api.sh PATCH "/v1/appStoreVersions/${{ needs.preflight.outputs.app_store_version_id }}" build/set-release-type.json >/dev/null

- name: Submit App Store version for review
run: |
set -euo pipefail

existing="$(scripts/appstore/asc_api.sh GET "/v1/appStoreVersionSubmissions?filter[appStoreVersion]=${{ needs.preflight.outputs.app_store_version_id }}&limit=1")"
existing_id="$(printf '%s' "$existing" | python3 -c 'import json,sys; items=(json.load(sys.stdin).get("data") or []); print(items[0].get("id", "") if items else "")')"

if [[ -n "$existing_id" ]]; then
echo "Submission already exists: $existing_id"
exit 0
fi

python3 -c 'import json; print(json.dumps({"data":{"type":"appStoreVersionSubmissions","relationships":{"appStoreVersion":{"data":{"type":"appStoreVersions","id":"${{ needs.preflight.outputs.app_store_version_id }}"}}}}}))' > build/create-submission.json
scripts/appstore/asc_api.sh POST "/v1/appStoreVersionSubmissions" build/create-submission.json >/dev/null

- name: Log submission summary
run: |
set -euo pipefail
echo "Submitted version=${{ needs.preflight.outputs.version }} build_number=${{ needs.preflight.outputs.build_number }} build_id=${{ needs.preflight.outputs.build_id }}"
2 changes: 1 addition & 1 deletion .github/workflows/pr-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
update-pr-docs:
name: Update PR docs
runs-on: [self-hosted, macOS, ARM64, bgpro-charstack]
if: github.event.pull_request.head.repo.full_name == github.repository
if: github.event.pull_request.head.repo.full_name == github.repository && !startsWith(github.event.pull_request.head.ref, 'dependabot/')
env:
PR_DOCS_DIR: /tmp/pr-docs-${{ github.run_id }}-${{ github.run_attempt }}
PR_NUMBER: ${{ github.event.pull_request.number }}
Expand Down
Loading
Loading