Miru è la mia app per tenere traccia degli anime. È scritta in React e TypeScript, impacchettata con Capacitor per iOS e Android, e ha Supabase come backend. Il vero costo di un’app mobile, per chi lavora da solo, non è scriverla: è pubblicarla ogni volta. Questo è il modo in cui ho tolto la pubblicazione dalla lista delle cose da fare a mano.
L’idea
A ogni push sul ramo main, GitHub Actions compila l’app iOS, la firma e la carica su TestFlight. Niente Xcode aperto, niente archivio manuale. Per Android succede quasi lo stesso, con una differenza che racconto in fondo.
La pipeline iOS, passo per passo
Il job gira su un runner macOS, perché per compilare e firmare un’app iOS serve Xcode:
- Checkout e Xcode. Seleziono l’ultima versione stabile di Xcode.
- Build della parte web.
npm cie poinpm run build, con le variabili d’ambiente passate come secret. npx cap sync ios. Copia la build web nel progetto nativo. Capacitor 8 usa Swift Package Manager e non CocoaPods, quindi non devo versionare a mano ilPackage.swiftgenerato: lo produce la CI.- Dipendenze Swift. Risolvo i pacchetti con
xcodebuild -resolvePackageDependenciesprima dell’archivio, così gli errori di rete escono presto. - Firma. Creo un portachiavi temporaneo sul runner, ci importo il certificato (da un secret in base64) e installo i provisioning profile: uno per l’app e uno per il widget.
- Archivio ed export.
xcodebuild archivein configurazione Release, poi-exportArchivecon un fileExportOptions.plistche sta nel repository. - Upload.
xcrun altool --upload-appcon una chiave API di App Store Connect, anch’essa in secret.
Ecco la parte di firma, in forma semplificata:
- name: Setup signing certificate
run: |
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
echo "$CERTIFICATE_BASE64" | base64 --decode > certificate.p12
security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
Android
Per Android il job gira su Ubuntu, con JDK 21: costruisco la web app, sincronizzo Capacitor, decodifico il keystore da un secret e lancio ./gradlew bundleRelease. Il risultato è un AAB firmato, che salvo come artefatto del job.
Qui c’è la differenza: il caricamento su Play Console resta manuale. È un passaggio che per ora ho lasciato fuori dall’automazione.
Cosa ho imparato
- I secret sono il vero progetto. Il YAML è banale; la parte lunga è ottenere certificati, profili e chiavi giusti e tenerli fuori dal repository.
- Il widget raddoppia i profili. Se l’app ha un’estensione, serve un provisioning profile anche per quella.
- Un timeout generoso. Il job iOS ha 60 minuti di margine: le build su macOS sono lente e un timeout troppo stretto fallisce proprio quando serve.
- Meglio far fallire presto. Risolvere le dipendenze in un passo separato rende gli errori più leggibili.
