Reusable composite GitHub Actions and workflows for mieweb CI/CD pipelines.
📄 Docs site: https://mieweb.github.io/actions
Any developer in the mieweb org can call these from their caller workflow —
use secrets: inherit and pass the required inputs.
Shipping an iOS app from CI is deceptively hard. The build is the easy part — the pain is everything around it. If you've ever set up iOS CI from scratch, you've lost days to some of these:
- Code signing is a black hole. Certificates, provisioning profiles,
keychains, the Apple Developer portal,
.p12exports,.mobileprovisionfiles, App Store Connect API keys — get one wrong and you get a crypticNo signing certificate "iOS Distribution" foundafter a 20-minute build. - Secrets are everywhere and copied everywhere. Every repo re-pastes the same base64 cert, team ID, and API key into its own GitHub secrets. Rotate a cert and you're editing ten repos by hand.
- Every project reinvents the same 150 lines of YAML. Xcode selection,
Node + Meteor/Expo setup,
pod install, Fastlane, archive, export, TestFlight upload — copy-pasted between repos, then drifting out of sync the moment one of them gets a fix the others never receive. - It "works on my machine." Local builds sign fine; CI fails because the runner has no keychain, no certs, and a different Xcode. Debugging means pushing commit after commit and waiting on a macOS runner each time.
- Fastlane, Ruby, and CocoaPods are a setup tax. Pinning Ruby versions, bundler caching, gem installs, Cordova pod quirks — all incidental work that has nothing to do with your app.
These actions absorb all of that. Signing is centralized (use match to share
one identity across repos, or secrets for a one-off), secrets live once at the
org level and are inherited, and the whole setup → build → sign → upload
pipeline is a single uses: line that every repo gets fixes for at once.
Before — every repo owns ~150 lines of fragile, drifting YAML and its own copy of the signing secrets.
After:
jobs:
ios:
uses: mieweb/actions/.github/workflows/ios-meteor.yml@v1
secrets: inherit
with:
app_identifier: org.mieweb.os.dev
meteor_server: https://app.example.comThat's the whole thing. Push, and TestFlight gets a build.
Store these at the GitHub org level so every repo inherits them via
secrets: inherit (see Org-level secrets).
The four App Store Connect secrets are always required; the signing secrets
depend on which signing_mode you use.
| Secret | Required | Used by | Description |
|---|---|---|---|
APPLE_TEAM_ID |
always | all | Apple Developer Team ID |
APPLE_API_KEY_ID |
always | all | App Store Connect API Key ID |
APPLE_API_ISSUER_ID |
always | all | App Store Connect Issuer ID |
APPLE_API_KEY_P8_BASE64 |
always | all | Base64-encoded App Store Connect API key (.p8) |
MATCH_GIT_BASIC_AUTHORIZATION |
match mode |
signing | Base64 user:token for the match signing repo |
MATCH_PASSWORD |
match mode |
signing | Encryption passphrase for match-stored certs/profiles |
IOS_DIST_CERT_P12_BASE64 |
secrets mode |
signing | Base64-encoded distribution certificate (.p12) |
IOS_DIST_CERT_PASSWORD |
secrets mode |
signing | Password for the .p12 certificate |
IOS_PROVISIONING_PROFILE_BASE64 |
secrets mode |
signing | Base64-encoded provisioning profile (.mobileprovision) |
| Topic | Type | Why use it |
|---|---|---|
| Required secrets | Guidance | The secrets every pipeline needs, and which signing mode requires which. |
setup-meteor |
Composite action | Prepare a Meteor/Cordova build environment (Xcode, Node, Meteor, npm install) before building. |
setup-expo |
Composite action | Prepare an Expo build environment (Xcode, Node, JS deps) before expo prebuild. |
ios |
Composite action | Sign, archive, and optionally upload an iOS app to TestFlight. Use directly for bare React Native or custom pipelines. |
ios-meteor.yml |
Reusable workflow | One-call end-to-end pipeline for Meteor/Cordova iOS apps. |
ios-expo.yml |
Reusable workflow | One-call end-to-end pipeline for Expo iOS apps. |
| Org-level secrets | Guidance | Where to store shared signing secrets so every repo inherits them. |
| Important notes | Guidance | Gotchas to know before calling the ios action directly. |
Sets up the Meteor/Cordova build environment: checks out the repo, selects
Xcode, installs Node.js and Meteor, and runs meteor npm install.
- uses: mieweb/actions/setup-meteor@v1
with:
xcode_path: /Applications/Xcode_26.app # optional, default
node_version: "20" # optional, defaultSets up the Expo build environment: checks out the repo, selects Xcode,
installs Node.js, and installs the project's JS dependencies (npm, yarn, or
pnpm). Does not run expo prebuild — the workflow does that after the
optional pre-build hook.
- uses: mieweb/actions/setup-expo@v1
with:
xcode_path: /Applications/Xcode_26.app # optional, default
node_version: "20" # optional, default
package_manager: npm # optional: npm | yarn | pnpm
working_directory: . # optional: path to package.jsonSigns, archives, and optionally uploads an iOS app to TestFlight via Fastlane. Supports two code-signing strategies.
| Mode | How it works | When to use |
|---|---|---|
match |
Fetches encrypted cert + profile from a shared git repo, decrypts with match_password |
Multiple repos share one signing identity |
secrets |
Decodes a raw .p12 cert + .mobileprovision from GitHub secrets, imports into a temporary keychain |
Single repo, or you want to avoid a signing repo dependency |
- uses: mieweb/actions/ios@v1
with:
signing_mode: match
app_identifier: org.mieweb.os.dev
apple_team_id: ${{ secrets.APPLE_TEAM_ID }}
match_git_basic_authorization: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}
match_password: ${{ secrets.MATCH_PASSWORD }}
apple_api_key_id: ${{ secrets.APPLE_API_KEY_ID }}
apple_api_issuer_id: ${{ secrets.APPLE_API_ISSUER_ID }}
apple_api_key_p8_base64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }}| Input | Required | Default | Description |
|---|---|---|---|
signing_mode |
match |
match or secrets |
|
app_identifier |
yes | — | Bundle ID |
apple_team_id |
yes | — | Apple Developer Team ID |
match_git_url |
if match | https://github.com/mieweb/mobile-signing |
Match signing repo URL |
match_git_basic_authorization |
if match | — | Base64 user:token for the signing repo |
match_password |
if match | — | Encryption passphrase for match |
match_type |
appstore |
Match profile type | |
match_readonly |
true |
Never create/renew certs in CI | |
ios_cert_p12_base64 |
if secrets | — | Base64 distribution cert (.p12) |
ios_cert_password |
if secrets | — | Password for the .p12 |
ios_prov_profile_base64 |
if secrets | — | Base64 provisioning profile |
apple_api_key_id |
yes | — | App Store Connect API Key ID |
apple_api_issuer_id |
yes | — | App Store Connect Issuer ID |
apple_api_key_p8_base64 |
yes | — | Base64 API key (.p8) |
workspace_path |
auto-discovered | Path to .xcworkspace |
|
xcode_scheme |
auto-discovered | Xcode scheme name | |
run_pod_install |
false |
Run pod install before Fastlane (set true for Cordova) |
|
upload_to_testflight |
true |
Upload IPA to TestFlight. Set false to stop after producing a signed IPA (no upload). |
|
ruby_version |
3.4 |
Ruby version for Fastlane |
Full pipeline for Meteor/Cordova iOS apps: setup → optional pre-build hook → Meteor build → CocoaPods → Fastlane sign/archive → TestFlight upload.
jobs:
ios:
uses: mieweb/actions/.github/workflows/ios-meteor.yml@v1
secrets: inherit
with:
app_identifier: org.mieweb.os.dev
meteor_server: https://app.example.comjobs:
ios:
uses: mieweb/actions/.github/workflows/ios-meteor.yml@v1
secrets: inherit
with:
app_identifier: org.mieweb.os.dev
meteor_server: https://app.example.com
pre_build_script: |
bash scripts/setup-firebase.shThe pre_build_script is inline bash that runs after the environment is set
up but before meteor build. Use it for any project-specific setup
(Firebase configs, environment files, asset generation, etc.).
| Input | Required | Default | Description |
|---|---|---|---|
app_identifier |
yes | — | Bundle ID (e.g. org.mieweb.os.dev) |
meteor_server |
yes | — | Meteor DDP server URL |
xcode_path |
/Applications/Xcode_26.app |
Absolute path to Xcode.app | |
node_version |
20 |
Node.js version | |
pre_build_script |
— | Inline bash to run before meteor build |
|
signing_mode |
match |
match or secrets |
|
upload_to_testflight |
true |
Upload IPA to TestFlight |
See Required secrets. Match mode (the default) needs the
four always-required secrets plus MATCH_GIT_BASIC_AUTHORIZATION and
MATCH_PASSWORD.
Full pipeline for Expo iOS apps: setup → optional pre-build hook → expo prebuild → CocoaPods → Fastlane sign/archive → TestFlight upload.
For Expo apps only — the native ios/ dir is regenerated each run from
app.json / app.config.js. The caller's project must have expo in its
dependencies. For bare React Native without expo, call the ios action
directly.
jobs:
ios:
uses: mieweb/actions/.github/workflows/ios-expo.yml@v1
secrets: inherit
with:
app_identifier: com.example.appjobs:
ios:
uses: mieweb/actions/.github/workflows/ios-expo.yml@v1
secrets: inherit
with:
app_identifier: com.example.app
package_manager: pnpm
working_directory: apps/mobilejobs:
ios:
uses: mieweb/actions/.github/workflows/ios-expo.yml@v1
secrets: inherit
with:
app_identifier: com.example.app
pre_build_script: |
bash scripts/setup-firebase.shThe pre_build_script is inline bash that runs after JS deps are installed
but before expo prebuild, so the script can place files (e.g.
GoogleService-Info.plist) that the prebuild step picks up. The script's
working directory is working_directory (defaults to repo root).
| Input | Required | Default | Description |
|---|---|---|---|
app_identifier |
yes | — | Bundle ID (must match app.json / Info.plist) |
xcode_path |
/Applications/Xcode_26.app |
Absolute path to Xcode.app | |
node_version |
20 |
Node.js version | |
package_manager |
npm |
npm | yarn | pnpm |
|
working_directory |
. |
Path to the Expo project (where package.json lives) |
|
pre_build_script |
— | Inline bash to run before expo prebuild, executed from working_directory |
|
signing_mode |
match |
match or secrets |
|
upload_to_testflight |
true |
Upload IPA to TestFlight |
See Required secrets. The four always-required App Store
Connect secrets, plus either the match-mode pair or the secrets-mode trio
depending on your signing_mode.
Set shared signing secrets at the GitHub org level so every repo inherits them automatically. Repos can override with repo-level secrets when they need a different cert or profile.
match_readonlyshould always betruein CI. Only set tofalsefor one-time local seeding of the match signing repo.- Xcode must be selected before calling the
iosaction directly. Theios-meteor.yml/ios-expo.ymlworkflows and thesetup-meteor/setup-expoactions handle this automatically. - The
.xcworkspacemust already exist. Build your native project (e.g.meteor build,expo prebuild) before invoking theiosaction directly.