Allez directement au contenu principal
CD/CI

Automatic Capacitor IOS build with GitHub actions using match

Comment configurer une chaîne d'intégration/continu (CI/CD) pour votre application IOS Ionic en utilisant Fastlane et des GitHub Actions en 5 minutes (2022)

Crédits de l'article

Martin Donadieu

Auteur

Valeria

Relecteur

Jordan

Éditeur

Automatic Capacitor IOS build with GitHub actions using match

Automatic iOS Builds avec des GitHub Actions à l'aide de Match

Configurer la CI/CD pour les applications Capacitor peut être complexe et fastidieux. Voici ce dont vous avez besoin de savoir :

Nous recommandons maintenant d'utiliser la construction Capgo avec le Capgo CLI pour les constructions natives Capacitor. Cette guide Fastlane Match est conservée pour les équipes qui maintiennent les pipelines d'actions GitHub existants, mais les nouvelles constructions IOS devraient utiliser le Capgo CLI afin que vous ne deviez pas maintenir les dépôts Fastlane, Match, les exécuteurs Xcode, les certificats et les scripts d'envoi.

la construction Capgo pour la CI/CD par Capgo

Évitez la gestion de Match, du runner Xcode, du certificat, du profil de provisionnement et de l'upload-script. Capgo Build exécute des builds natifs iOS signés à partir de votre pipeline CI/CD existant :

  • Fonctionne avec votre pipeline : Déclenchez Capgo Build à partir d'GitHub Actions, GitLab CI, Jenkins ou scripts locaux après votre build web et npx cap sync.
  • Signature à partir de secrets CI : Gardez les clés App Store Connect, les certificats, les profils de provisioning, les mots de passe et les identifiants d'équipe dans vos propres secrets CI.
  • Aucune maintenance de l'exécuteur natif : Capgo Build fournit des environnements de build Apple maintenus, vous n'avez donc pas à gérer les exécutants macOS, les images Xcode, Fastlane ou les référentiels Match.
  • Artéfacts et soumission : Téléchargez les artefacts signés pour les tests ou soumettez les builds de mise en production à travers le Capgo CLI.

Tarifs

  • : Les plans Capgo commencent à 12 $/mois
  • Include les mises à jour OTA et environ 15 builds natifs par mois
  • Les minutes de build supplémentaires sont facturées par minute en crédits.

Configurez Capgo Build dans CI/CD

Guide de configuration manuelle

Voici ce que vous devez faire :

Livraison Continue pour iOS en utilisant Fastlane et GitHub Actions en utilisant match

Prerequisites

Avant de continuer avec le tutoriel…

  • Vérifiez que vous avez Fastlane installé sur votre machine de développement.
  • adhésion au programme de développement iOS.
  • Envie de lire 😆…
  • Une équipe de nombreux devs, nous recommandons donc d'utiliser fastlane cert pour des workflows plus simples.

Important sur le prix

Prix GitHub Action

https://github.com/features/actions

Le service est ‘gratuit’ jusqu'à la limite, en fonction de la machine choisie.
Nous allons utiliser un machine macOS Vous pouvez voir dans l'écran d'aperçu son prix et ses limites (tarifs valables à la création de ce tutoriel, ils pourraient évoluer dans le futur)

🔴 Une fois averti des exigences et des prix, si vous le souhaitez, on continue…

📣 In ce post, nous supposons que nous avons créé l'application dans iTunes Connect, nous avons les certificats de l'écosystème Apple, tout sera copié par Fastlane !

Plongons-y !

Étapes à suivre dans ce post

  1. Utilisation d'App Store Connect API avec Fastlane Match
  2. Exigences
  3. Création d'une clé App Store Connect API
  4. Utilisation d'une clé App Store Connect API
  5. Copie des fichiers Fastlane
  6. Configuration de Fastlane match

1. Utilisation d'App Store Connect API avec Fastlane Match

À partir de février 2021, la deuxième factor d'authentification ou la vérification à deux étapes est requise pour tous les utilisateurs pour se connecter à App Store Connect. Cette couche supplémentaire de sécurité pour votre ID Apple vous aide à vous assurer que vous êtes la seule personne qui peut accéder à votre compte.
Depuis Support Apple

Pour commencer avec match, vous devez révoquer vos certificats existants. Mais n'ayez crainte, vous obtiendrez le nouveau directement.

Exigences

Pour utiliser App Store Connect API, Fastlane a besoin de trois things.

  1. ID d'émetteur.
  2. Clé ID.
  3. Création d'une clé App Store Connect __CAPGO_KEEP_0__

Créer une clé App Store Connect API

To generate keys, you must have Admin permission in App Store Connect. If you don’t have that permission, you can direct the relevant person to this article and follow the following instructions.

1 — Se connecter à App Store Connect.

2 — Sélectionner Utilisateurs et accès.

L'accès à App Store Connect

3 — Sélectionnez l'onglet Intégration.

App Store Connect API Intégration

4 — Cliquez sur Générer API clé ou le bouton Ajouter (+).

App Store Connect API clés créent

5 — Entrez un nom pour la clé. Le nom est uniquement à des fins de référence et n'est pas partie de la clé elle-même.

App Store Connect API clés créent un nom

6 — Dans Accès, sélectionnez le rôle pour la clé. Les rôles qui s'appliquent aux clés sont les mêmes rôles qui s'appliquent aux utilisateurs de votre équipe. Voir les permissions de rôle. Nous vous recommandons de sélectionner App manager.

7 — Cliquez sur Générer.

L'accès d'une clé API ne peut pas être limité à des applications spécifiques.

Le nom de la clé, son ID, un lien de téléchargement et d'autres informations s'affichent sur la page.

Téléchargement de clés App Store Connect

Vous pouvez récupérer les trois informations nécessaires ici.
<1> ID de l'incident.
<2> ID de la clé.
<3> Cliquez sur « Télécharger la clé API » pour télécharger votre clé privée API. Le lien de téléchargement n'apparaît que si la clé privée n'a pas encore été téléchargée. Apple ne garde pas de copie de la clé privée. Vous pouvez donc la télécharger qu'une seule fois.

🔴 Stockez votre clé privée dans un endroit sûr. Vous ne devez jamais partager vos clés, stocker les clés dans un dépôt code ou inclure les clés dans le code côté client code.

Utilisation d'une clé API App Store Connect

The API Key file (p8 file that you download), the key ID, and the issuer ID are needed to create the JWT token for authorization. There are multiple ways that these pieces of information can be input into Fastlane using Fastlane’s new action, app_store_connect_api_keyVous pouvez apprendre d'autres façons dans la documentation de Fastlane. Je montre cette méthode parce que je pense qu'il s'agit de la façon la plus facile de travailler avec la plupart des CI en ligne, où vous pouvez définir des variables d'environnement.

Maintenant, nous pouvons gérer Fastlane avec la clé API de l'App Store Connect, c'est génial !

2. Copiez les fichiers Fastlane

Fastlane est une bibliothèque Ruby créée pour automatiser les tâches de développement mobile courantes. En utilisant Fastlane, vous pouvez configurer des « voies » personnalisées qui regroupent une série d'« actions » qui exécutent des tâches que vous effectueriez normalement à l'aide d'Android Studio.

Créez un dossier Fastlane à la racine de votre projet et copiez les fichiers suivants : Fastfile

default_platform(:ios)

DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]

def delete_temp_keychain(name)
  delete_keychain(
    name: name
  ) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end

def create_temp_keychain(name, password)
  create_keychain(
    name: name,
    password: password,
    unlock: false,
    timeout: 0
  )
end

def ensure_temp_keychain(name, password)
  delete_temp_keychain(name)
  create_temp_keychain(name, password)
end

platform :ios do
  lane :build do
    build_app(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )
  end
  lane :refresh_profiles do
    match(
      type: "development",
      force: true)
    match(
      type: "adhoc",
      force: true)
  end
  desc "Register new device"
  lane :register_new_device do  |options|
      device_name = prompt(text: "Enter the device name: ")
      device_udid = prompt(text: "Enter the device UDID: ")
      device_hash = {}
      device_hash[device_name] = device_udid
      register_devices(
                       devices: device_hash
                       )
    refresh_profiles
  end
  lane :closed_beta do
    keychain_name = TEMP_KEYCHAIN_USER
    keychain_password = TEMP_KEYCHAIN_PASSWORD
    ensure_temp_keychain(keychain_name, keychain_password)

    api_key = app_store_connect_api_key(
      key_id: APPLE_KEY_ID,
      issuer_id: APPLE_ISSUER_ID,
      key_content: APPLE_KEY_CONTENT,            
      duration: 1200,            
      in_house: false
    )

    match(
      type: 'appstore',
      git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
      readonly: true,
      keychain_name: keychain_name,
      keychain_password: keychain_password,
      api_key: api_key
    )

    gym(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )

    pilot(
      apple_id: "#{DEVELOPER_APP_ID}",
      app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )

    delete_temp_keychain(keychain_name)
  end
  lane :submit_review do
    version = ''
    Dir.chdir("..") do
      file = File.read("package.json")
      data = JSON.parse(file)
      version = data["version"]
    end
    deliver(
      app_version: version,
      submit_for_review: true,
      automatic_release: true,
      force: true, # Skip HTMl report verification
      skip_metadata: false,
      skip_screenshots: false,
      skip_binary_upload: true
    )
  end
end

Appfile

app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])

Configurez Fastlane match

Fastlane match is une nouvelle approche de la signature d'code iOS. Fastlane match facilite la gestion des certificats et des profils de provisionnement requis pour vos applications iOS.

Créez un nouveau dépôt privé nommé certificates, par exemple sur votre compte ou organisation GitHub personnel.

Initialisez Fastlane match pour votre application iOS.

fastlane match init

Choisissez ensuite l'option #1 (Stockage Git).

[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?

Attribuez l'URL du nouveau dépôt créé.

[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>

Vous avez maintenant à l'intérieur du dossier Fastlane un fichier nommé Matchfile et _git_url_context : Page/zone : Site web de marketing Capgo. Rôle : Étiquette de navigation ou élément de l'interface utilisateur court. Vu dans : page trust.astro. Clé de message `and` (Et).

# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")

Ensuite, nous allons générer les certificats et entrer vos informations de connexion lorsque vous y êtes invité avec Fastlane Match.

You will be prompted to enter a passphrase. Remember it correctly because it will be used later by GitHub Actions to decrypt your certificates repository.

fastlane match appstore

Si tout s'est bien passé, vous devriez voir quelque chose comme cela :

[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌

Si vous avez rencontré un problème avec GitHub et les permissions nécessaires, peut-être que cet article article Générera des jetons d'authentification pour Git.

Les certificats et les profils de provisionnement générés sont chargés dans les ressources du dépôt des certificats.

Certificats App Store Connect

Certificats App Store Connect project Dans Xcode, mettez à jour le profil de provisioning pour la configuration de sortie de votre application.

certificats XCode

Quelques choses à noter

MATCH

For the CI/CD to import the certificates and provisioning profiles, it needs to have access to the certificates repository. You can do this by generating a personal access token (should be used before) that has the scope to access or read private repositories.

In GitHub, allez à Paramètres → Paramètres du développeur → Clés d'accès personnelles → cliquez Generate New Token → cochez la repo scope → puis cliquez Generate token.

Créer une clé d'accès personnelle

Obtenez une copie de la clé d'accès personnelle générée. Vous l'utiliserez plus tard pour la variable d'environnement GIT_TOKEN.

Ensuite, remplacez votre fichier match généré dans le dossier Fastlane par Matchfile

CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]

git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)

Cela sera utilisé par les Actions GitHub pour importer les certificats et les profils de provisioning. Et var sera défini dans les Secrets GitHub, au lieu de les coderdur dans le fichier.

Traitement de la construction

Dans les GitHub Actions, Vous êtes facturés en fonction des minutes que vous avez utilisées pour exécuter votre flux de CI/CD. D'expérience, il faut environ 10-15 minutes avant que votre build ne soit traité dans App Store Connect.

Pour les projets privés, le coût estimé par build peut aller jusqu'à $0,08/min x 15 min = $1,2, ou plus, en fonction de la configuration ou des dépendances de votre projet.

Si vous partagez les mêmes préoccupations pour les tarifs que moi pour les projets privés, vous pouvez garder le skip_waiting_for_build_processing à true.

What’s the catch? You have to manually update the compliance of your app in App Store Connect after the build has been processed, for you to distribute the build to your users.

Page/area: Page de produit Live Update. Role: Étiquette de navigation ou élément de menu. Message clé `live_update_dynamic_label_to` (Live Update Dynamic Label To). pricing.

3. Configuration des GitHub Actions

Configure les secrets GitHub

Vous vous demandez peut-être où les valeurs de les ENV sont venues ? Eh bien, ce n'est plus un secret – elles proviennent de votre secret de projet. 🤦

Configurez GitHub secrets

1. APP_STORE_CONNECT_TEAM_ID - l'ID de votre équipe d'App Store Connect si vous en avez plusieurs.

2. DEVELOPER_APP_ID - dans App Store Connect, allez dans l'application → Information sur l'application → Déroulez vers le bas jusqu'à la General Information section de votre application et cherchez Apple ID.

3. DEVELOPER_APP_IDENTIFIER - l'ID de votre identifiant de bundle d'application.

4. DEVELOPER_PORTAL_TEAM_ID - l'ID de votre équipe du Portail des développeurs si vous en avez plusieurs.

5. FASTLANE_APPLE_ID - the Apple ID or developer email you use to manage the app.

6. GIT_USERNAME &amp; GIT_TOKEN - Votre identifiant Git et votre jeton d'accès personnel.

7. MATCH_PASSWORD La passphrase que vous avez assignée lors de l'initialisation de match, sera utilisée pour déchiffrer les certificats et les profils de provisionnement.

8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>, eg. match AppStore com.domain.blabla.demo.

9. TEMP_KEYCHAIN_USER &amp; TEMP_KEYCHAIN_PASSWORD Attribuez un utilisateur et un mot de passe temporaire à votre flux de travail.

10. APPLE_KEY_ID — App Store Connect API Clé 🔺ID de clé.

11. APPLE_ISSUER_ID — App Store Connect API Clé 🔺ID émetteur.

12. APPLE_KEY_CONTENT — App Store Connect API Clé 🔺Fichier de clé ou contenu de clé de .p8, vérifiez-le

13. CERTIFICATE_STORE_URL — L'URL du dépôt de vos clés Match (ex: https://github.com/***/fastlane-match.git)

4. Configure le fichier de workflow GitHub

Créez un répertoire de workflow GitHub.

cd .github/workflows

Dans le répertoire workflow créez un fichier nommé build-upload-ios.ymlet ajoutez le contenu suivant.

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '24'
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7.2
      - uses: maierj/fastlane-action@v2.3.0
        env:
          DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
          PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
          TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
          GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
          GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
        with:
          lane: closed_beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v2
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 60

Ce workflow doit être déclenché après chaque tag GitHub tagAutomatic build and release avec les actions __CAPGO_KEEP_0__ Construction automatique et publication avec des actions GitHub first.

Ensuite, ce workflow récupérera vos dépendances NodeJS, les installera et construira votre application JavaScript.

Chaque fois que vous envoyez un nouveau commit, une mise à jour sera créée dans TestFlight.

Votre application n'a pas besoin d'utiliser Ionic, seule la base Capacitor est obligatoire, elle peut avoir un module Cordova ancien, mais le plugin Capacitor JS devrait être préféré.

5. Déclencher le workflow

Créer un Commit

Faites un commitVous devriez voir le workflow actif dans le dépôt.

Déclenchez le workflow

Poussez les nouveaux commits vers la branche main ou development Pour déclencher le workflow.

Débuté avec commit

After quelques minutes, la mise à niveau devrait être disponible dans votre tableau de bord App Store Connect.

Tableau de bord Testflight

Peut-on déployer depuis la machine locale ?

Oui, vous pouvez, et cela est sans effort.

Imaginez que vous avez un dépôt privé, et que vous avez utilisé toutes les minutes du plan gratuit et que vous ne voulez pas payer pour de nouvelles mises à jour, ou peut-être préférez-vous soumettre l'application manuellement.

Alors, allons-y !

Ok, nous devons d'abord créer dans chemin_d'accès_à_mon_projet/fastlane un fichier appelé .env, juste dans le même chemin que Fastfile, pour être capable de créer la même chose secret les propriétés trouvées dans notre _GitHub, ci-dessous :

.env file for deploy from local machine

Maintenant, vous pouvez vous rendre au terminal et lancer le Fastlane de votre machine :

fastlane closed_beta

❌ Informations essentielles sur le fichier .env puisqu'on préfère ne pas exposer ces données, il faut les ajouter dans notre .gitignore, quelque chose comme cela : ❌

fastlane/*.env

Il devrait fonctionner de la même manière que si cela se produisait à partir d'actions sur le GitHub machine distante mais sur notre machine locale.

Exécution locale de Fastlane

Exécution terminale : $ Fastlane closed_beta

Si vous avez réussi à ce point, félicitations, vous avez maintenant un processus entièrement automatisé pour vos applications iOS avec Fastlane et GitHub Actions.

Chaque fois que vous envoyez un nouveau commit, une mise à jour sera construite dans le console Google Play, canal bêta. Je vais améliorer ce blog avec vos retours, si vous avez une question ou une suggestion, veuillez me le faire savoir par email martin@capgo.app

Construire sur votre appareil

Si vous avez besoin de construire sur votre appareil, vous devez les ajouter manuellement à la provisioning. Connectez votre appareil à votre Mac et ouvrez le menu appareil Trouvez le menu iOS de l'appareil Ensuite, copiez votre identifiant Trouvez l'identifiant iOS Et commencez ensuite la commande : fastlane register_new_device Elle vous demandera de définir un nom de appareil et l'identifiant : Définir l'identifiant iOS

Si vous rencontrez des problèmes

If you have issue with dev device not able to test etc that usually fix it.

Il existe une commande magique qui peut vous aider :

fastlane match nuke development
fastlane match development

Nettoyez ensuite le projet en appuyant sur Shift(⇧)+Command(⌘)+K ou en sélectionnant Produit > Nettoyer (ce dernier peut être étiqueté « Nettoyer le dossier de build »).

Essayez ensuite de lancer l'application sur votre appareil.

Merci

Cet article est basé sur les articles suivants :

Continuez à utiliser Automatic Capacitor IOS build avec GitHub actions en utilisant match

Si vous utilisez Automatic Capacitor IOS build avec GitHub actions en utilisant match pour planifier l'automatisation CI/CD, connectez-l’à Capgo CI/CD pour le flux de travail du produit dans Capgo CI/CD, Capgo Builds natifs pour le flux de travail du produit dans Capgo Builds natifs, Capgo Intégrations pour le flux de travail du produit dans Capgo Intégrations Intégration CI/CD détails d'implémentation dans l'intégration CI/CD, et GitHub Intégration d'actions détails d'implémentation dans GitHub Intégration d'actions.

Mises à jour instantanées pour les applications Capacitor

Lorsqu'un bug de la couche web est actif, expédiez la correction par le biais de Capgo plutôt que d'attendre des jours pour l'approbation de l'app store. Les utilisateurs reçoivent la mise à jour en arrière-plan tandis que les modifications natives suivent la voie de revue normale.

Soutien humain de Martin

Commencez Maintenant

Dernières actualités

Capgo vous offre les meilleures informations nécessaires pour créer une application mobile véritablement professionnelle.