Saltare al contenuto principale

CI/CD per Capacitor: Difficoltà comuni e soluzioni

Il problema CI/CD che interrompe Capacitor build: firma iOS, esecuzione macOS, Xcode 26, sincronizzazione cap datata, codici versione, rifiuti di archiviazione e soluzioni per ogni problema.

Crediti dell'articolo

Martino Donadieu

Autore

Valeria

Recensore

Jordan

Editore

CI/CD per Capacitor: Difficoltà comuni e soluzioni

La maggior parte delle fallite CI/CD per Capacitor derivano da una lista corta di problemi: la firma iOS code non funziona, mancano o sono obsolete le attrezzature macOS, un build web che non è mai stato inserito nel progetto nativo, numeri di build ripetuti, e requisiti di archiviazione che falliscono solo al momento dell'invio. Ogni problema ha una causa nota e una soluzione che puoi applicare una volta. Questa guida raggruppa i problemi per fase del pipeline, con l'errore che vedrai, perché succede e cosa cambiare.

Se stai configurando un pipeline da zero, leggi Configurazione della CI/CD per le app Capacitor first, then use this list to harden it.

Fase 1: Costruzione della layer web

Difficoltà: l'app nativa invia un vecchio build web

Sintomo: CI ha successo, l'app viene installata, ma mostra l'interfaccia utente di ieri.

La causa: cap sync copia tutto ciò che c'è webDir In quel momento. Se il flusso di lavoro viene eseguito cap sync prima della costruzione web, o la costruzione web scrive in una cartella diversa da webDir in capacitor.config.tssi trova il progetto nativo, i file diventano obsoleti.

Risoluzione: assicurati di eseguire sempre i passaggi in questo ordine e falli se la cartella di output è vuota.

bun install --frozen-lockfile
bun run build
test -f dist/index.html || { echo "web build missing"; exit 1; }
bunx cap sync

assicurati che webDir corrisponda all'output del tuo bundler (dist per Vite, www per Angular con Ionic, build per alcune configurazioni React).

Pitfall: dev server URL left in the config

Sintomo: la versione di rilascio mostra uno schermo vuoto o cerca di caricare http://192.168.x.x:5173.

Causa: server.url in capacitor.config.ts was set for live reload and committed.

Soluzione: non commettere mai server.url. Leggilo da una variabile di ambiente che il CI non imposta:

import type { CapacitorConfig } from '@capacitor/cli'

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'Example',
  webDir: 'dist',
  ...(process.env.LIVE_RELOAD_URL && {
    server: { url: process.env.LIVE_RELOAD_URL, cleartext: true },
  }),
}

export default config

Trappola: le variabili di ambiente incorporate al momento sbagliato

Simpatia: l'applicazione di produzione parla con lo staging API.

Causa: Vite, webpack e Angular impostano le variabili di ambiente inline al momento della compilazione. Il valore presente quando bun run build è il relativo nella binario, e in qualsiasi live update costruito dalla stessa job.

Fix: impostare le variabili specifiche dell'ambiente prima della costruzione web, per ogni job, e costruire bundle separati per staging e produzione. Non cercare di scambiarli dopo. cap sync.

Pitfall: deriva del file di lock

Simpatia: Una versione del plugin in CI differisce da quella sul tuo computer, e la compilazione nativa fallisce per mancanza di simboli.

Fix: Pitfall: deriva del file di lock bun install --frozen-lockfile (o npm ci). Pin @capacitor/core, @capacitor/ios, @capacitor/android, e @capacitor/cli alla stessa versione. Le versioni non corrispondenti sono una fonte frequente di errori native; vedi Fix Capacitor version mismatch errors.

Stadio 2: La catena di strumenti nativi

Trappola: versione Node, JDK o Xcode sbagliata

Sintomo: Unsupported class file major version, The engine "node" is incompatible, o errori Xcode su SDK funzionalità.

Causa: le immagini dei runner ospitati cambiano, e Capacitor 8 ha requisiti minimi fermi.

Strumento Capacitor 8 richiesta
Node.js 22 o più recente
JDK 21
Xcode 26 o più recente
Target di distribuzione iOS 15.0
Android minSdkVersion / targetSdkVersion 24 / 36

Rimedio: pinare ogni versione esplicitamente nella pipeline anziché fidarsi latest:

- uses: actions/setup-node@v6
  with:
    node-version-file: .nvmrc
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '21'
- uses: maxim-lobanov/setup-xcode@v1
  with:
    xcode-version: '26'

Pitfall: costruire contro un Xcode Apple che non accetta più

Sintomo: l'upload fallisce con un messaggio che l'app è stata costruita con un SDK non supportato.

La causa: dal 28 aprile 2026, App Store Connect richiede Xcode 26 e l'SDK iOS 26. Le Mac auto-hosted e le immagini di esecuzione più vecchie ancora utilizzano Xcode 16.

Fix: seleziona un macos-26 immagine o installa Xcode 26 sui tuoi esecutori. Dettagli in Requisito di Xcode 26 di Apple per le Capacitor app.

Pitfall: confusione tra CocoaPods e SPM

Sintomo: xcodebuild: error: 'App.xcworkspace' does not exist, o i pods non trovati.

La causa: i nuovi Capacitor 8 progetti utilizzano il gestore di pacchetti Swift e costruiscono ios/App/App.xcodeproj. Progetti più vecchi utilizzano CocoaPods e costruiscono ios/App/App.xcworkspace after pod installPipelines copiate da tutorial più vecchi presuppongono lo spazio di lavoro.

Fix: controlla quale utilizza il tuo progetto e costruisci il file giusto. Se stai migrando, consulta Come migrare il tuo app Capacitor con SPM.

Pitfall: i plugin Android si interrompono dopo un aggiornamento di Gradle

Symptom: Namespace not specified, package attribute is deprecated dopo l'aggiornamento di Android Studio. build.gradle blocca il plugin Gradle Android in

Fix: pin the Android Gradle Plugin in android/build.gradleAggiornamento su una branch, e aggiorna i plugin per primo. Specifici errori sono trattati in Risolvi gli errori di build del plugin Capacitor con AGP 9..

Fase 3: firma di Code

Trappola: la firma iOS funziona solo sul tuo Mac

Sintomo: No signing certificate "iOS Distribution" found, No profiles for 'com.example.app' were foundo errSecInternalComponent.

Causa: la chiave del tuo Mac tiene il certificato e Xcode scarica i profili per te. Un esecutore CI non ne ha, e la sua chiave è bloccata in una sessione non interattiva.

Risolvi: importa il certificato in una chiave temporanea sbloccata e installa il profilo dove Xcode 16 e successive versioni lo cercano:

security create-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security set-keychain-settings -lut 21600 ci.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" ci.keychain
security import dist.p12 -k ci.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" ci.keychain
security list-keychains -d user -s ci.keychain login.keychain

PROFILES="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
mkdir -p "$PROFILES"
cp app.mobileprovision "$PROFILES/"

Usa il nome dell'identità Apple Distribution, non il legato iOS Distribution. fastlane’s setup_ci plus match automates this. Capgo Build takes the certificate and profile as environment variables and does the keychain work on its own machines, so the Linux job never touches security.

Trappola: certificati scaduti o revocati

Symptom: un flusso che funzionava da un anno fallisce di notte.

Simulazione: Apple distribution certificates and provisioning profiles expire after a year. A teammate creating a new certificate in Xcode can also invalidate the profile your CI uses.

Causa: Inserisci le date di scadenza nel calendario della tua squadra, utilizza un certificato di distribuzione condiviso per la CI e controlla prima di costruire. bunx @capgo/cli@latest build prescan --platform ios controlla la scadenza del certificato, la password e l'associazione del profilo prima di caricare qualsiasi cosa.

Pitfall: Apple ID logins and two-factor prompts

Sintomo: fastlane si blocca in attesa di un codice code a 6 cifre.

Soluzione: utilizza una chiave App Store Connect API (.p8, ID chiave, ID emittente) al posto di un ID Apple e una password. Non richiede l'autenticazione a due fattori e può essere limitato all'App Manager. Se l'autenticazione fallisce ancora con una chiave corretta, controlla l'orologio del runner: il token è firmato con l'ora locale e Apple rifiuta i token con un timestamp distorto.

Trappola: segreti base64 che non si decodificano

Sintomo: MAC verification failed, invalid keystore format, o base64: invalid input.

Causa: linee di fine riga aggiunte quando si copia il valore base64, o un .p12 creato con OpenSSL 3 impostazioni che macOS non può leggere.

Soluzione: codifica su una sola riga, e utilizza -legacy quando si crea un .p12 con OpenSSL 3:

base64 -i dist.p12 | tr -d '\n' > dist.p12.b64
openssl pkcs12 -export -legacy -inkey key.pem -in cert.pem -out dist.p12

Pitfall: un keystore Android perso

Sintomo: non puoi firmare un aggiornamento perché nessuno ha il keystore.

Soluzione: con Play App Signing, il keystore è la chiave di upload, e il supporto di Play Console può registrare uno nuovo. Memorizza il keystore nelle tue segrete di CI e in un backup offline. Non lasciarlo vivere solo su un laptop. Il generatore di keystore Android crea uno nuovo se inizi da capo.

Fase 4: progettazione della pipeline

Pitfall: costruzioni native su ogni richiesta di pull

Sintomo: controlli PR lenti e un grande conto per i minuti di macOS.

Causa: Costruisce iOS su runner macOS ospitati è il minuto più costoso in quasi tutti i piani CI, e la maggior parte dei commit tocca solo JavaScript.

Soluzione: eseguire lint, test e il build web su ogni PR. Esegui i build nativi su tag di rilascio o merge per mainPer le anteprime dei PR, invia il bundle web a un canale Capgo invece di costruire un binario, come descritto in Confrontando piattaforme CI/CD per applicazioni Capacitor.

Sfida: costruire iOS e Android in sequenza

Soluzione: Usa una matrice per costruire entrambe le piattaforme in parallelo, e impostare fail-fast: false così che un problema di firma iOS non annulli un buon build Android.

strategy:
  fail-fast: false
  matrix:
    platform: [ios, android]

Pitfall: nessun caching, o il cache sbagliato

Symptom: ogni build scarica le dipendenze Gradle e CocoaPods da zero, o un build di staging invia la configurazione di produzione da un cache obsoleto.

Fix: cache ~/.gradle/caches, ~/.gradle/wrapper, e ios/App/Pods Riflettete sui lockfile. Partitionate le cache per ambiente. Con Capgo Build, la cache di build per-app può essere suddivisa con --cache-key prod e --cache-key staging, o saltare con --no-cache per un build pulito.

Pitfall: percorsi di repository unico

Symptom: could not find capacitor.config o plugin mancanti dal progetto nativo.

Risoluzione: Eseguire Capacitor comandi dall'applicazione, e puntare gli strumenti verso le librerie elevate. node_modulesil comando Capgo CLI accetta --path e --node-modules per questo.

Fase 5: Invio di archiviazione

Fallimento: numeri di build ripetuti

Sintomo: “La versione del bundle deve essere superiore alla versione precedentemente caricata” su iOS, o “Versione code è già stata utilizzata” su Google Play.

Risoluzione: generare il numero in CI. Con fastlane, leggere l'ultima build di TestFlight e aggiungere uno. Con Capgo Build, questo è il default: recupera il numero di build più recente da App Store Connect o il più alto versionCode da Google Play e lo incrementa.

Pitfall: costruzioni bloccate in TestFlight

Sintomo: l'upload ha successo ma i tester non vedono mai la costruzione.

Causa: risposta di esportazione mancante.

Soluzione: declaratelo una volta in ios/App/App/Info.plist se utilizzate solo l'encryption standard:

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

Pitfall: rifiuti del manifesto di privacy

Sintomo: e-mail da Apple su ragioni richieste mancanti API (ITMS-91053).

Risoluzione: aggiungi un PrivacyInfo.xcprivacy a target dell'app e aggiorna i plugin che forniscono il proprio. guida alla privacy per le app Capacitor.

Fallimento: tipo di artefatto sbagliato

Risoluzione: Google Play richiede un AAB (bundleRelease), non un APK. bundleRelease riuscisce ancora senza un release configura la firma e produce un AAB non firmato che Play rifiuta, quindi configura signingConfigs.release in android/app/build.gradle Prima. L'exportazione per iOS richiede un IPA per la Store App, non un IPA di sviluppo o ad hoc. Controlla il metodo di exportazione nel tuo passo di build.

Stage 6: Aggiornamenti in tempo reale

Pitfall: invio di un live update che richiede una nuova compilazione nativa

Simulacro: dopo un aggiornamento over-the-air, l'app si blocca chiamando un metodo di plugin che non esiste nella versione installata.

Causa: la bundle web dipende da una versione di plugin più recente di quella compilata nell'app sulle dispositivi degli utenti.

Soluzione: lascia che il pipeline decida. build needed esce 0 quando le dipendenze native corrispondono a quelle presenti sul canale e 1 quando è necessaria una nuova versione binaria:

if bunx @capgo/cli@latest build needed com.example.app --channel production; then
  bunx @capgo/cli@latest bundle upload com.example.app --channel production
else
  bunx cap sync
  bunx @capgo/cli@latest build request com.example.app --platform ios
  bunx @capgo/cli@latest build request com.example.app --platform android
fi

Anche forza il percorso nativo quando i file sotto ios/, android/o capacitor.config.* sono cambiati. Il pattern completo è in Auto scegli live update o costruzione nativa, e le regole di compatibilità sono in compatibilità nativa.

La soluzione che elimina più trappole

Se cambi solo una cosa, sposta la compilazione e la firma di iOS fuori dai tuoi esecutori CI. La configurazione della chiave di sicurezza, gli aggiornamenti di Xcode, i costi di macOS e l'installazione del profilo scompaiono dalla tua pipeline quando un lavoro Linux passa il progetto preparato a Capgo Build:

bun install --frozen-lockfile && bun run build
bunx cap sync ios
bunx @capgo/cli@latest build request com.example.app --platform ios --build-mode release

The pitfalls in stages 1, 5, and 6 still apply, because they are about your project, not the runner. For more on debugging failing jobs, see Risolvere gli errori di compilazione nei flussi CI/CD di Capacitor.

Riferimento rapido

Error Errore Section
Interfaccia vecchia in una nuova build cap sync prima della build web Fase 1
No profiles for ... were found Profilo non installato o non corrispondente Fase 3
errSecInternalComponent Chiavechain bloccata Fase 3
MAC verification failed Password sbagliata o OpenSSL 3 .p12 Fase 3
Unsupported class file major version JDK sbagliato Fase 2
SDK troppo vecchio all'upload Xcode più vecchio di 26 Stadio 2
La versione del pacchetto deve essere più alta Numero di build riutilizzato Stadio 5
La versione code è già stata utilizzata Riutilizzato versionCode Stadio 5
Crash dopo live update Modifica nativa inviata via aria Stadio 6
Aggiornamenti in tempo reale per le app Capacitor

Quando un bug nel layer web è attivo, invia la correzione attraverso Capgo invece di attendere giorni per l'approvazione della store. Gli utenti ricevono l'aggiornamento in background mentre le modifiche native rimangono nel normale percorso di revisione.

Sostegno umano da Martin

Inizia subito

Dai ultimi nostri articoli

Capgo vi dà le migliori informazioni che avete bisogno per creare un'app mobile davvero professionale.