akyos / ux-native-cli
Console commands to scaffold and build Hotwire Native shells for Symfony apps
Package info
github.com/akyoscommunication/ux-native-cli
Type:symfony-bundle
pkg:composer/akyos/ux-native-cli
Requires
- php: >=8.2
- endroid/qr-code: *
- symfony/config: ^7.0|^8.0
- symfony/console: ^7.0|^8.0
- symfony/dependency-injection: ^7.0|^8.0
- symfony/filesystem: ^7.0|^8.0
- symfony/framework-bundle: ^7.0|^8.0
- symfony/http-kernel: ^7.0|^8.0
- symfony/process: ^7.0|^8.0
- symfony/yaml: ^7.0|^8.0
Requires (Dev)
None
Suggests
- spomky-labs/pwa-bundle: Workbox et service worker pour native:init --offline
- symfony/ux-native: Server-side native detection and JSON path configuration for Hotwire Native
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-30 15:16:59 UTC
README
Transformez votre site Symfony en app iOS et Android (Hotwire Native), sans quitter bin/console.
Le bundle génère des projets natifs prêts à compiler, puis les builde et les installe sur votre téléphone ou votre émulateur. Les pages restent votre site Symfony : les apps natives ne sont qu'une coque autour.
| Commande | À quoi elle sert |
|---|---|
native:init |
Génère les projets native/android et native/ios. |
native:build |
Compile avec Gradle et/ou Xcode, puis installe sur un appareil ou un simulateur. |
native:qr-install |
Sert l'APK (et l'IPA) sur le réseau local, avec un QR code à scanner. |
native:dev |
Rappelle le flux de dev et peut démarrer le serveur Symfony. |
Sommaire
- Démarrage rapide
- Prérequis
- Ce qui est généré
- Onglets et navigation, pilotés par Symfony
native:initet ses optionsnative:buildnative:qr-installetnative:dev- Signature iOS
- Dépannage
- Référence de configuration
Démarrage rapide
1. Installer
composer require akyos/ux-native-cli symfony/ux-native
Si Flex ne le fait pas, activez le bundle :
// config/bundles.php Akyos\UxNativeCliBundle\AkyosUxNativeCliBundle::class => ['all' => true],
2. Configurer l'app dans config/packages/native.yaml :
native: app_name: MonApp url: 'http://192.168.1.249:8003' # exemple : l'IP de votre Mac sur le Wi-Fi application_id: com.akyos.monapp # identifiant Android bundle_id: com.akyos.monapp # identifiant iOS android_home: '%env(ANDROID_HOME)%' java_home: '%env(JAVA_HOME)%'
Pourquoi pas
127.0.0.1? Sur un téléphone ou un émulateur,127.0.0.1désigne le téléphone lui-même, pas votre ordinateur. Mettez l'IP locale de votre machine (Réglages → Wi-Fi → Détails sur macOS), et un téléphone sur le même Wi-Fi.
3. Générer les apps
php bin/console native:init
4. Démarrer Symfony, accessible depuis le réseau :
symfony serve --port=8003 --allow-all-ip
5. Compiler et installer
php bin/console native:build --platform=android # APK installé sur le téléphone ou l'émulateur branché php bin/console native:build --platform=ios # macOS : iPhone USB ou simulateur démarré
L'app s'ouvre sur votre site. C'est tout.
Prérequis
Android (macOS, Linux ou Windows)
- Un JDK 17 ou plus récent, via
JAVA_HOMEounative.java_home. - Le SDK Android, via
ANDROID_HOMEounative.android_home. Si rien n'est défini, le bundle cherche~/Library/Android/sdk(macOS) et~/Android/Sdk(Linux). - Les platform-tools du SDK (
adb), pour l'installation automatique. - Gradle n'est pas à installer : le projet généré contient
gradlew.
iOS (macOS uniquement)
- Xcode, avec la plateforme iOS de votre iPhone (Xcode → Settings → Components).
-
xcrun devicectl(fourni avec Xcode récent) pour installer sur un iPhone. À défaut :brew install ios-deploy. - Pour un vrai iPhone : une Team Apple (voir Signature iOS). Le simulateur n'en a pas besoin.
Optionnel
- L'extension PHP
socketsaidenative:qr-installà détecter votre IP. Sans elle, passez--host=….
Ce qui est généré
native/
├── android/ projet Gradle (Kotlin)
│ ├── app/build.gradle.kts
│ ├── app/google-services.json à ajouter vous-même si --notification
│ └── app/src/main/java/<application_id>/
│ ├── MainActivity.kt onglets et navigation
│ └── NativeApplication.kt bridge components, configuration de chemins
└── ios/
├── NativeApp.xcodeproj projet Xcode avec schéma partagé (requis par xcodebuild)
└── NativeApp/
├── AppDelegate.swift bridge components, configuration de chemins
└── SceneDelegate.swift onglets et navigation
- L'URL, les identifiants et le nom de l'app sont injectés depuis
native.yamlà chaquenative:init. - Le package Kotlin est déplacé automatiquement pour correspondre à
application_id. - Les bridge components de @joemasilotti/bridge-components (
bridge--*) sont préinstallés.
Attention à
--force. Il supprimenative/androidetnative/iosavant de les régénérer, y compris vos modifications manuelles etgoogle-services.json. Sauvegardez ce fichier avant, puis remettez-le.
Onglets et navigation, pilotés par Symfony
Les onglets et les règles de navigation ne sont pas codés dans l'app : l'app les lit sur votre serveur à chaque lancement, dans /config/ios_v1.json et /config/android_v1.json. On les modifie sans rebuild : il suffit de relancer l'app.
Ces fichiers sont produits par Symfony UX Native :
// src/Native/AppNativeConfiguration.php #[AsNativeConfigurationProvider] final class AppNativeConfiguration { #[AsNativeConfiguration('/config/ios_v1.json')] public function ios(): Configuration { return new Configuration( settings: [ 'tabs' => [ ['label' => 'Accueil', 'path' => '/', 'icon' => 'house'], ['label' => 'Push', 'path' => '/demo/page-3', 'icon' => 'bell'], ], ], rules: [new Rule(patterns: ['.*'], properties: ['context' => 'default', 'pull_to_refresh_enabled' => true])], ); } }
iconest un nom de SF Symbol sur iOS et un nom de drawable sur Android.- Sans
tabs, l'app affiche une seule pile de navigation, sans barre d'onglets.
Les onglets ne changent pas ? Supprimez
public/config/*.json. Ces fichiers statiques, créés parux-native:dump, masquent la route dynamique en dev. Vérifiez aussi que l'URLhttp://<votre-ip>/config/ios_v1.jsonrenvoie bien vos onglets.
native:init et ses options
php bin/console native:init [--force] [--offline] [--notification]
| Option | Effet |
|---|---|
--force |
Supprime et régénère native/android et native/ios (voir l'avertissement plus haut). Sans cette option, les dossiers doivent être vides. |
--notification |
Ajoute les notifications push (ci-dessous). Sans cette option, l'app ne contient aucun code de notification. |
--offline |
Mode hors ligne : installe spomky-labs/pwa-bundle, crée config/packages/pwa.yaml et une page /offline, ajoute {{ pwa() }} au layout, et autorise les service workers sur iOS. Pensez ensuite à cache:clear. |
--app-name, --url, --application-id, --bundle-id |
Remplacent la valeur de native.yaml, pour cette exécution uniquement. |
Notifications push (--notification)
Ce qui est ajouté :
| iOS | Android | |
|---|---|---|
| Récupération du token | NotificationTokenComponent.swift (APNs) |
NotificationTokenComponent.kt (FCM) |
| Autorisations | Entitlements aps-environment |
Permission POST_NOTIFICATIONS |
| Affichage app ouverte | Bannière au premier plan | NativeMessagingService.kt |
Toucher → page url |
AppDelegate + SceneDelegate |
MainActivity (singleTask, onNewIntent) |
| Dépendances | — | Firebase Messaging, plugin google-services |
Côté web, le bundle installe @hotwired/hotwire-native-bridge et @joemasilotti/bridge-components dans l'importmap s'ils manquent, et charge les contrôleurs bridge--*.
Récupérer le token dans une page :
<div data-controller="bridge--notification-token" data-action="bridge--notification-token:retrieved->mon-controleur#enregistrer"> <button data-action="bridge--notification-token#get">Activer les notifications</button> <p data-bridge--notification-token-target="token"></p> </div>
L'événement bridge--notification-token:retrieved contient event.detail.token : un token APNs (hexadécimal) sur iOS, un token FCM sur Android.
Envoyer les notifications : akyos/native-push enregistre ce token côté serveur et envoie les notifications (PushMessage, APNs et FCM, ouverture de la page au toucher).
Toucher une notification ouvre l'app sur la page indiquée par la clé url du payload : la clé url à côté de aps sur APNs, data.url sur FCM. Un chemin est résolu sur l'URL de l'app. Sans url, l'app s'ouvre simplement.
À faire de votre côté :
- Android : créez une app Android dans Firebase, avec le même package que
application_id. Déposez songoogle-services.jsondansnative/android/app/. Sans ce fichier, le build passe, mais aucun token n'est renvoyé. - iOS : renseignez
ios_development_teamavec une Team payante (voir Signature iOS) et testez sur un vrai iPhone. Le simulateur ne reçoit pas de token.
native:build
php bin/console native:build # Android (par défaut), puis installation via adb php bin/console native:build -p ios # iOS : iPhone USB ou simulateur démarré php bin/console native:build -p ios --ios-target=simulator # seulement les simulateurs démarrés php bin/console native:build -p all # Android puis iOS php bin/console native:build -p ios -n --ios-destination=device:00008130-000C58C40E21001C # CI, sans question php bin/console native:build --no-install # compile sans installer
| Option | Défaut | Effet |
|---|---|---|
-p, --platform |
android |
android, ios ou all. |
--install / --no-install |
installe | Après un build réussi : adb install -r sur Android, devicectl / ios-deploy ou simctl install sur iOS. |
--ios-target |
— | device (iPhones branchés seulement) ou simulator (simulateurs démarrés seulement). |
--ios-destination |
— | device:UDID ou simulator:UDID : fixe la cible sans poser de question. |
Comment la cible iOS est choisie : seuls les iPhones réellement branchés et les simulateurs réellement démarrés sont proposés. S'il y en a plusieurs, la commande vous demande lequel utiliser. En mode -n, précisez --ios-destination.
Où sont les fichiers produits :
- Android :
native/android/app/build/outputs/apk/debug/app-debug.apk, ou…/release/app-release.apksiandroid_gradle_taskcontientRelease. - iOS :
native/ios/build/DerivedDataCli/Build/Products/Debug-iphoneos/(iPhone) ouDebug-iphonesimulator/.
native:qr-install et native:dev
Installer via un QR code
php bin/console native:qr-install --build # builde, puis affiche un QR à scanner
Le téléphone doit être sur le même Wi-Fi. Avec --platform=all, l'URL unique redirige vers l'APK sur Android et vers l'IPA sur iPhone.
| Option | Défaut | Effet |
|---|---|---|
-p, --platform |
all |
all, android (QR direct vers l'APK) ou ios (QR direct vers l'IPA, macOS). |
-b, --build |
non | Lance native:build sans installation avant de servir. Pour iOS, un iPhone USB est requis. |
--host |
détecté | IP à mettre dans l'URL. |
--port |
9876 |
Port d'écoute (le suivant est essayé s'il est pris). |
--apk |
— | Sert cet APK au lieu de la sortie Gradle. |
Sur iPhone, un IPA ne s'installe pas comme un APK. iOS refuse l'installation depuis un simple lien. Utilisez
native:build -p ios(USB), Xcode → Appareils et simulateurs, ou TestFlight.
La commande tourne jusqu'à ce que vous appuyiez sur Entrée.
Rappel du flux de dev
php bin/console native:dev # affiche l'URL, les étapes et les liens utiles php bin/console native:dev --server # démarre aussi symfony server:start
Signature iOS
Pour installer sur un vrai iPhone, Xcode doit signer l'app avec une Team Apple. Renseignez-la une fois pour toutes : elle est réinjectée à chaque native:init, même avec --force.
native: ios_development_team: 8WS3LS7L99 # exemple
| Type de Team | Installer sur iPhone | Notifications push |
|---|---|---|
| Personal Team (gratuite) | Oui, 7 jours | Non |
| Apple Developer Program (payant) | Oui | Oui |
Trouver le Team ID (10 caractères) :
- sur developer.apple.com/account → Membership details ;
- ou dans le terminal :
defaults read com.apple.dt.Xcode IDEProvisioningTeamByIdentifier | grep -A3 teamID.
La Team doit aussi apparaître dans Xcode → Settings → Accounts, sans croix rouge devant « Certificates, Identifiers & Profiles ». Le simulateur, lui, n'a besoin d'aucune Team.
Dépannage
| Symptôme | Cause | Solution |
|---|---|---|
| L'app affiche une page blanche ou une erreur réseau | native.url pointe sur 127.0.0.1, ou le serveur n'écoute pas sur le réseau. |
Mettez l'IP locale, lancez symfony serve --allow-all-ip, puis native:init --force. |
| Les onglets ne se mettent pas à jour | Un public/config/*.json statique masque la route dynamique. |
Supprimez public/config/*.json et relancez l'app. |
Unable to find a destination matching… |
L'iPhone n'est pas branché, déverrouillé ou approuvé, ou n'est pas signé. | Déverrouillez-le, acceptez « Faire confiance à cet ordinateur », et renseignez ios_development_team. |
| « iOS … is not installed » | Xcode n'a pas la plateforme iOS de l'iPhone. | Xcode → Settings → Components, installez la version demandée. |
No Account for Team "…" |
Le compte de cette Team n'est pas chargé dans Xcode. | Xcode → Settings → Accounts : ajoutez ou reconnectez le compte, et vérifiez qu'il n'y a pas de croix rouge. |
No profiles for '…' were found |
Pas de profil de provisioning pour ce bundle ID. | native:build le crée automatiquement quand la Team est valide. Vérifiez le point précédent. |
SDK location not found |
Gradle ne trouve pas le SDK Android. | Définissez native.android_home, ou ANDROID_HOME. |
Missing package product |
Les dépendances Swift ne sont pas résolues. | Xcode → File → Packages → Reset Package Caches. |
| « No matching client found for package name » | google-services.json a été créé pour un autre application_id. |
Téléchargez le fichier de l'app Firebase qui a le bon package. |
Plus de google-services.json après --force |
--force supprime tout le dossier native/android. |
Remettez votre copie dans native/android/app/. |
Référence de configuration
config/packages/native.yaml, sous la clé native :
| Clé | Défaut | Rôle |
|---|---|---|
app_name |
NativeApp |
Nom affiché sous l'icône. |
url |
http://127.0.0.1:8000 |
URL du site chargé par l'app. Utilisez l'IP locale pour un téléphone. |
application_id |
com.example.nativeapp |
Identifiant Android. Le package Kotlin est déplacé en conséquence. |
bundle_id |
com.example.nativeapp |
Identifiant iOS. |
ios_development_team |
null |
Team ID Apple (10 caractères). Requis pour un iPhone et pour le push. |
android_home |
null |
SDK Android. Sinon ANDROID_HOME, puis les emplacements usuels. |
java_home |
null |
JDK utilisé par Gradle. Sinon JAVA_HOME. |
android_path |
null |
Dossier du projet Android. Par défaut native/android. |
ios_path |
null |
Dossier du projet iOS. Par défaut native/ios. |
android_gradle_task |
assembleDebug |
Tâche Gradle de native:build. Détermine aussi l'APK installé. |
ios_scheme |
NativeApp |
Schéma Xcode. |
ios_project |
NativeApp.xcodeproj |
Projet Xcode, dans ios_path. |
ios_product_name |
null |
Nom du .app. Par défaut, celui de ios_scheme. |
ios_derived_data_path |
null |
Dossier DerivedData. Par défaut native/ios/build/DerivedDataCli. |
Pour passer par les variables d'environnement, définissez ANDROID_HOME et JAVA_HOME dans .env.local et référencez-les avec %env(...)%, comme dans le démarrage rapide. Vous pouvez aussi laisser ces clés à null et exporter les variables dans votre shell.
Ressources
- Hotwire Native
- Symfony UX Native
- Bridge components de Joe Masilotti
akyos/native-push: l'envoi des notifications push côté serveur
Licence
MIT