kasy-cli 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/bin/kasy.js +529 -0
- package/docs/cli-reference.md +251 -0
- package/lib/commands/add.js +777 -0
- package/lib/commands/check.js +240 -0
- package/lib/commands/codemagic.js +161 -0
- package/lib/commands/deploy.js +257 -0
- package/lib/commands/docs.js +47 -0
- package/lib/commands/doctor.js +227 -0
- package/lib/commands/features.js +36 -0
- package/lib/commands/ios.js +206 -0
- package/lib/commands/new.js +1870 -0
- package/lib/commands/notifications.js +207 -0
- package/lib/commands/remove.js +466 -0
- package/lib/commands/run.js +75 -0
- package/lib/commands/update.js +382 -0
- package/lib/commands/validate.js +131 -0
- package/lib/scaffold/CHANGELOG.json +6 -0
- package/lib/scaffold/backends/api/.flutter-plugins-dependencies +1 -0
- package/lib/scaffold/backends/api/analysis_options.yaml +5 -0
- package/lib/scaffold/backends/api/generator.js +76 -0
- package/lib/scaffold/backends/api/patch/README.md +85 -0
- package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +87 -0
- package/lib/scaffold/backends/api/patch/ios/Runner/AppDelegate.swift +61 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +80 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +117 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +148 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/json_converters.dart +35 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +48 -0
- package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +211 -0
- package/lib/scaffold/backends/api/patch/lib/environnements.dart +151 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +303 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +80 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +406 -0
- package/lib/scaffold/backends/api/patch/lib/features/camera/xfile_extension.dart +6 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/entities/feature_request_entity.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/entities/feature_vote_entity.dart +21 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +52 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +58 -0
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_message_entity.dart +29 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +398 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/device_entity.dart +39 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +23 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/notifications_api.dart +333 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +185 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +26 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/user_infos_api.dart +107 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +94 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +34 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/widgets/avatar_utils.dart +33 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +34 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/subscription_api.dart +49 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +88 -0
- package/lib/scaffold/backends/api/patch/lib/main.dart +256 -0
- package/lib/scaffold/backends/api/patch/lib/router.dart +133 -0
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +108 -0
- package/lib/scaffold/backends/firebase/deploy.js +1006 -0
- package/lib/scaffold/backends/firebase/generator.js +24 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +1008 -0
- package/lib/scaffold/backends/firebase/tokens.js +113 -0
- package/lib/scaffold/backends/supabase/.flutter-plugins-dependencies +1 -0
- package/lib/scaffold/backends/supabase/analysis_options.yaml +5 -0
- package/lib/scaffold/backends/supabase/config.toml +34 -0
- package/lib/scaffold/backends/supabase/deploy.js +485 -0
- package/lib/scaffold/backends/supabase/edge-functions/delete-user-account/README.md +15 -0
- package/lib/scaffold/backends/supabase/edge-functions/delete-user-account/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/llm-chat/index.ts +156 -0
- package/lib/scaffold/backends/supabase/edge-functions/meta-track-event/index.ts +292 -0
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/README.md +30 -0
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +488 -0
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/README.md +35 -0
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +314 -0
- package/lib/scaffold/backends/supabase/generator.js +108 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000001_initial_schema.sql +149 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000002_storage_bucket.sql +40 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000005_vote_triggers.sql +34 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000006_notification_webhook.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +64 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000008_user_feature_requests.sql +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +22 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000010_notification_image.sql +2 -0
- package/lib/scaffold/backends/supabase/patch/README.md +121 -0
- package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +87 -0
- package/lib/scaffold/backends/supabase/patch/ios/Runner/AppDelegate.swift +61 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/meta_ads_api.dart +75 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +83 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +143 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/json_converters.dart +35 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +48 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +211 -0
- package/lib/scaffold/backends/supabase/patch/lib/environnements.dart +149 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +546 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +410 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/camera/xfile_extension.dart +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/entities/feature_request_entity.dart +25 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/entities/feature_vote_entity.dart +21 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +59 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_vote_api.dart +85 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +199 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +47 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_message_entity.dart +30 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +266 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +410 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/entities/device_entity.dart +51 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/entities/notifications_entity.dart +26 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/notifications_api.dart +317 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +174 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +26 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/user_infos_api.dart +90 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +94 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +34 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/widgets/avatar_utils.dart +35 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +35 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/subscription_api.dart +44 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/shared/maybeshow_premium.dart +85 -0
- package/lib/scaffold/backends/supabase/patch/lib/google_auth_options.dart +23 -0
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +288 -0
- package/lib/scaffold/backends/supabase/patch/lib/router.dart +133 -0
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +110 -0
- package/lib/scaffold/backends/supabase/tokens.js +9 -0
- package/lib/scaffold/catalog.js +177 -0
- package/lib/scaffold/engine.js +230 -0
- package/lib/scaffold/features/README.md +152 -0
- package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +124 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +35 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +35 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +35 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +12 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +12 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +12 -0
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +17 -0
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +17 -0
- package/lib/scaffold/features/ci/.github/dependabot.yml +16 -0
- package/lib/scaffold/features/ci/.github/workflows/app.yml +20 -0
- package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +14 -0
- package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +19 -0
- package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +163 -0
- package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +28 -0
- package/lib/scaffold/features/ci/.gitlab-ci.yml +37 -0
- package/lib/scaffold/features/ci/codemagic.yaml +157 -0
- package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +111 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +27 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +27 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +50 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +79 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +48 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +42 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +147 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +95 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +199 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +175 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +76 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +279 -0
- package/lib/scaffold/features/ios-release/.kasy/apple.env.example +8 -0
- package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +7 -0
- package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +50 -0
- package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +50 -0
- package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +50 -0
- package/lib/scaffold/features/ios-release/docs/ios-release.en.md +41 -0
- package/lib/scaffold/features/ios-release/docs/ios-release.es.md +41 -0
- package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +41 -0
- package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +38 -0
- package/lib/scaffold/features/ios-release/scripts/release-ios.sh +137 -0
- package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +301 -0
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +81 -0
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +76 -0
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +282 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +24 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +71 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +92 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +15 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +78 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +30 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +72 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +92 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +89 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +94 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +80 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +139 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +110 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +84 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +45 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +77 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +392 -0
- package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +116 -0
- package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +322 -0
- package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +41 -0
- package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +305 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_background_task.dart +41 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart +57 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_service.dart +54 -0
- package/lib/scaffold/features/widget/lib/features/settings/ui/components/admin/admin_home_widgets.dart +32 -0
- package/lib/scaffold/generate.js +370 -0
- package/lib/scaffold/patch.js +395 -0
- package/lib/scaffold/shared/backend-config.js +74 -0
- package/lib/scaffold/shared/fcm-service-account.js +126 -0
- package/lib/scaffold/shared/generator-utils.js +1664 -0
- package/lib/scaffold/shared/post-build.js +809 -0
- package/lib/scaffold/shared/template-strings.js +112 -0
- package/lib/utils/apple-release.js +273 -0
- package/lib/utils/checks.js +319 -0
- package/lib/utils/codemagic-release.js +127 -0
- package/lib/utils/fs.js +122 -0
- package/lib/utils/i18n.js +2123 -0
- package/lib/utils/license-gate.js +72 -0
- package/lib/utils/license.js +64 -0
- package/lib/utils/prompts.js +213 -0
- package/lib/utils/updates.js +97 -0
- package/package.json +55 -0
- package/templates/firebase/.env.example +24 -0
- package/templates/firebase/.firebaserc +5 -0
- package/templates/firebase/.kasy/apple.env.example +8 -0
- package/templates/firebase/.kasy/codemagic.env.example +7 -0
- package/templates/firebase/.metadata +30 -0
- package/templates/firebase/.windsurfrules +95 -0
- package/templates/firebase/Makefile +30 -0
- package/templates/firebase/README.en.md +180 -0
- package/templates/firebase/README.es.md +180 -0
- package/templates/firebase/README.md +180 -0
- package/templates/firebase/analysis_options.yaml +37 -0
- package/templates/firebase/android/app/build.gradle.kts +80 -0
- package/templates/firebase/android/app/src/debug/AndroidManifest.xml +7 -0
- package/templates/firebase/android/app/src/main/AndroidManifest.xml +93 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +36 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +50 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +7 -0
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/values/strings.xml +6 -0
- package/templates/firebase/android/app/src/main/res/values/styles.xml +22 -0
- package/templates/firebase/android/app/src/main/res/values-night/styles.xml +22 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +20 -0
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +20 -0
- package/templates/firebase/android/app/src/main/res/xml/locales_config.xml +7 -0
- package/templates/firebase/android/app/src/main/res/xml/mywidget_info.xml +11 -0
- package/templates/firebase/android/app/src/profile/AndroidManifest.xml +7 -0
- package/templates/firebase/android/build.gradle.kts +28 -0
- package/templates/firebase/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/templates/firebase/android/gradle.properties +2 -0
- package/templates/firebase/android/settings.gradle.kts +29 -0
- package/templates/firebase/assets/icons/apple.png +0 -0
- package/templates/firebase/assets/icons/facebook.png +0 -0
- package/templates/firebase/assets/icons/google.png +0 -0
- package/templates/firebase/assets/icons/google_play_games.png +0 -0
- package/templates/firebase/assets/images/app_icon.png +0 -0
- package/templates/firebase/assets/images/empty_notifications.jpg +0 -0
- package/templates/firebase/assets/images/icon.png +0 -0
- package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
- package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
- package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
- package/templates/firebase/assets/images/premium-bg.jpg +0 -0
- package/templates/firebase/assets/images/premium-switch-header.png +0 -0
- package/templates/firebase/assets/images/review.png +0 -0
- package/templates/firebase/assets/images/splashscreen.png +0 -0
- package/templates/firebase/assets/images/update.png +0 -0
- package/templates/firebase/build.yaml +13 -0
- package/templates/firebase/devtools_options.yaml +3 -0
- package/templates/firebase/docs/auth-setup.en.md +145 -0
- package/templates/firebase/docs/auth-setup.es.md +145 -0
- package/templates/firebase/docs/auth-setup.pt.md +145 -0
- package/templates/firebase/docs/codemagic-release.en.md +50 -0
- package/templates/firebase/docs/codemagic-release.es.md +50 -0
- package/templates/firebase/docs/codemagic-release.pt.md +50 -0
- package/templates/firebase/docs/ios-release.en.md +41 -0
- package/templates/firebase/docs/ios-release.es.md +41 -0
- package/templates/firebase/docs/ios-release.pt.md +54 -0
- package/templates/firebase/docs/navigation-transitions.md +80 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +341 -0
- package/templates/firebase/docs/revenuecat-setup.pt.md +341 -0
- package/templates/firebase/firebase.json +1 -0
- package/templates/firebase/firestore.indexes.json +25 -0
- package/templates/firebase/firestore.rules +51 -0
- package/templates/firebase/functions/.eslintrc.js +45 -0
- package/templates/firebase/functions/jest.config.js +16 -0
- package/templates/firebase/functions/package-lock.json +6876 -0
- package/templates/firebase/functions/package.json +33 -0
- package/templates/firebase/functions/src/authentication/functions.ts +30 -0
- package/templates/firebase/functions/src/authentication/triggers.ts +103 -0
- package/templates/firebase/functions/src/core/api/meta_ads_api.ts +390 -0
- package/templates/firebase/functions/src/core/data/entities/entity_utils.ts +87 -0
- package/templates/firebase/functions/src/core/data/entities/notification_entity.ts +74 -0
- package/templates/firebase/functions/src/core/data/entities/page.ts +4 -0
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +76 -0
- package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +64 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +43 -0
- package/templates/firebase/functions/src/core/data/repositories/repositories.ts +16 -0
- package/templates/firebase/functions/src/core/data/repositories/subscription_repository.ts +42 -0
- package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +31 -0
- package/templates/firebase/functions/src/core/data/repositories/user_notifications_repository.ts +58 -0
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +84 -0
- package/templates/firebase/functions/src/core/logger/logger.ts +13 -0
- package/templates/firebase/functions/src/core/storage/storage_manager.ts +52 -0
- package/templates/firebase/functions/src/core/utils/async_utils.ts +5 -0
- package/templates/firebase/functions/src/feature_requests/triggers.ts +3 -0
- package/templates/firebase/functions/src/index.ts +24 -0
- package/templates/firebase/functions/src/llm_chat/index.ts +181 -0
- package/templates/firebase/functions/src/notifications/admin_functions.ts +91 -0
- package/templates/firebase/functions/src/notifications/models/notification.ts +61 -0
- package/templates/firebase/functions/src/notifications/notifications_api.ts +147 -0
- package/templates/firebase/functions/src/notifications/triggers.ts +102 -0
- package/templates/firebase/functions/src/subscriptions/models/revenuecat_events.ts +162 -0
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +18 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +173 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +135 -0
- package/templates/firebase/functions/src/subscriptions/triggers.ts +35 -0
- package/templates/firebase/functions/src/test/core/data/entities/entity_utils.spec.ts +44 -0
- package/templates/firebase/functions/tsconfig.dev.json +5 -0
- package/templates/firebase/functions/tsconfig.json +18 -0
- package/templates/firebase/home_widget_config.json +30 -0
- package/templates/firebase/ios/Flutter/AppFrameworkInfo.plist +24 -0
- package/templates/firebase/ios/Flutter/Debug.xcconfig +2 -0
- package/templates/firebase/ios/Flutter/Profile.xcconfig +2 -0
- package/templates/firebase/ios/Flutter/Release.xcconfig +2 -0
- package/templates/firebase/ios/HomeWidgetExtension/HomeWidgetBundle.swift +13 -0
- package/templates/firebase/ios/HomeWidgetExtension/Info.plist +29 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +86 -0
- package/templates/firebase/ios/HomeWidgetExtension.entitlements +10 -0
- package/templates/firebase/ios/NotificationService/Info.plist +31 -0
- package/templates/firebase/ios/NotificationService/NotificationService.swift +76 -0
- package/templates/firebase/ios/Podfile +111 -0
- package/templates/firebase/ios/Runner/AppDelegate.swift +75 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +1 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +21 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +44 -0
- package/templates/firebase/ios/Runner/Base.lproj/Main.storyboard +26 -0
- package/templates/firebase/ios/Runner/Info.plist +132 -0
- package/templates/firebase/ios/Runner/Runner-Bridging-Header.h +1 -0
- package/templates/firebase/ios/Runner/Runner.entitlements +14 -0
- package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +10 -0
- package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +10 -0
- package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +10 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.pbxproj +1108 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/firebase/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme +58 -0
- package/templates/firebase/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- package/templates/firebase/ios/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/templates/firebase/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/firebase/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/firebase/ios/RunnerTests/RunnerTests.swift +12 -0
- package/templates/firebase/lib/components/components.dart +27 -0
- package/templates/firebase/lib/components/kasy_accordion.dart +384 -0
- package/templates/firebase/lib/components/kasy_alert.dart +266 -0
- package/templates/firebase/lib/components/kasy_app_bar.dart +536 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +549 -0
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +104 -0
- package/templates/firebase/lib/components/kasy_badge.dart +278 -0
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +338 -0
- package/templates/firebase/lib/components/kasy_button.dart +926 -0
- package/templates/firebase/lib/components/kasy_card.dart +128 -0
- package/templates/firebase/lib/components/kasy_checkbox.dart +429 -0
- package/templates/firebase/lib/components/kasy_chip.dart +83 -0
- package/templates/firebase/lib/components/kasy_dialog.dart +789 -0
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +233 -0
- package/templates/firebase/lib/components/kasy_skeleton.dart +225 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +392 -0
- package/templates/firebase/lib/components/kasy_text_field.dart +591 -0
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +532 -0
- package/templates/firebase/lib/components/kasy_toast.dart +803 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +136 -0
- package/templates/firebase/lib/core/ads/ads_state.dart +15 -0
- package/templates/firebase/lib/core/animations/bottomfade_anim.dart +37 -0
- package/templates/firebase/lib/core/animations/movefade_anim.dart +34 -0
- package/templates/firebase/lib/core/animations/slideright_anim.dart +38 -0
- package/templates/firebase/lib/core/bottom_menu/bart_inner_paths.dart +5 -0
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +127 -0
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +281 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +22 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +127 -0
- package/templates/firebase/lib/core/bottom_menu/notification_bottom_item.dart +48 -0
- package/templates/firebase/lib/core/config/app_env.dart +100 -0
- package/templates/firebase/lib/core/config/features.dart +15 -0
- package/templates/firebase/lib/core/data/api/analytics_api.dart +124 -0
- package/templates/firebase/lib/core/data/api/base_api_exceptions.dart +28 -0
- package/templates/firebase/lib/core/data/api/firestore_retry.dart +29 -0
- package/templates/firebase/lib/core/data/api/http_client.dart +38 -0
- package/templates/firebase/lib/core/data/api/image.dart +52 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +170 -0
- package/templates/firebase/lib/core/data/api/storage_api.dart +115 -0
- package/templates/firebase/lib/core/data/api/tracking_api.dart +119 -0
- package/templates/firebase/lib/core/data/api/user_api.dart +115 -0
- package/templates/firebase/lib/core/data/entities/json_converters.dart +39 -0
- package/templates/firebase/lib/core/data/entities/upload_result.dart +48 -0
- package/templates/firebase/lib/core/data/entities/user_entity.dart +54 -0
- package/templates/firebase/lib/core/data/models/pageable.dart +12 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +322 -0
- package/templates/firebase/lib/core/data/models/user.dart +107 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +130 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +464 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_highlight.dart +88 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_info.dart +70 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_panel.dart +204 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +321 -0
- package/templates/firebase/lib/core/extensions/date.ext.dart +32 -0
- package/templates/firebase/lib/core/guards/authenticated_guard.dart +37 -0
- package/templates/firebase/lib/core/guards/guard.dart +68 -0
- package/templates/firebase/lib/core/guards/user_info_guard.dart +55 -0
- package/templates/firebase/lib/core/haptics/haptic_feedback_notifier.dart +19 -0
- package/templates/firebase/lib/core/haptics/kasy_haptics.dart +27 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +41 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +57 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +54 -0
- package/templates/firebase/lib/core/icons/kasy_icons.dart +86 -0
- package/templates/firebase/lib/core/initializer/models/run_state.dart +14 -0
- package/templates/firebase/lib/core/initializer/onstart_service.dart +38 -0
- package/templates/firebase/lib/core/initializer/onstart_widget.dart +83 -0
- package/templates/firebase/lib/core/initializer/pending_notification_handler.dart +48 -0
- package/templates/firebase/lib/core/keyboard_fix/keyboard_flicker_fix.dart +164 -0
- package/templates/firebase/lib/core/navigation/kasy_fade_page_transitions_builder.dart +34 -0
- package/templates/firebase/lib/core/navigation/kasy_material_page_route.dart +41 -0
- package/templates/firebase/lib/core/navigation/kasy_navigation_config.dart +29 -0
- package/templates/firebase/lib/core/navigation/kasy_page_transition.dart +40 -0
- package/templates/firebase/lib/core/navigation/kasy_route_transition.dart +68 -0
- package/templates/firebase/lib/core/navigation/kasy_transition_kind.dart +17 -0
- package/templates/firebase/lib/core/navigation/navigation.dart +6 -0
- package/templates/firebase/lib/core/rating/api/rating_api.dart +99 -0
- package/templates/firebase/lib/core/rating/models/rating.dart +52 -0
- package/templates/firebase/lib/core/rating/models/review.dart +70 -0
- package/templates/firebase/lib/core/rating/providers/rating_repository.dart +75 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +190 -0
- package/templates/firebase/lib/core/rating/widgets/rate_popup.dart +54 -0
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +175 -0
- package/templates/firebase/lib/core/security/biometric_copy_slot.dart +20 -0
- package/templates/firebase/lib/core/security/biometric_guard.dart +114 -0
- package/templates/firebase/lib/core/security/biometric_preference_notifier.dart +17 -0
- package/templates/firebase/lib/core/security/biometric_service.dart +79 -0
- package/templates/firebase/lib/core/security/biometric_ui_bundle.dart +136 -0
- package/templates/firebase/lib/core/security/secured_storage.dart +46 -0
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +70 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +2021 -0
- package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +88 -0
- package/templates/firebase/lib/core/states/components/maybe_ask_rating.dart +49 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_bottom_sheet.dart +86 -0
- package/templates/firebase/lib/core/states/components/maybeshow_component.dart +110 -0
- package/templates/firebase/lib/core/states/events_dispatcher.dart +103 -0
- package/templates/firebase/lib/core/states/models/event_model.dart +118 -0
- package/templates/firebase/lib/core/states/models/user_state.dart +22 -0
- package/templates/firebase/lib/core/states/notifications_dispatcher.dart +81 -0
- package/templates/firebase/lib/core/states/translations.dart +11 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +321 -0
- package/templates/firebase/lib/core/theme/colors.dart +262 -0
- package/templates/firebase/lib/core/theme/extensions/theme_extension.dart +48 -0
- package/templates/firebase/lib/core/theme/providers/kasy_theme.dart +76 -0
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +212 -0
- package/templates/firebase/lib/core/theme/radius.dart +33 -0
- package/templates/firebase/lib/core/theme/shadows.dart +64 -0
- package/templates/firebase/lib/core/theme/spacing.dart +44 -0
- package/templates/firebase/lib/core/theme/texts.dart +176 -0
- package/templates/firebase/lib/core/theme/theme.dart +24 -0
- package/templates/firebase/lib/core/theme/theme_data/theme_data.dart +32 -0
- package/templates/firebase/lib/core/theme/theme_data/theme_data_factory.dart +15 -0
- package/templates/firebase/lib/core/theme/universal_theme.dart +210 -0
- package/templates/firebase/lib/core/toast/toast_service.dart +55 -0
- package/templates/firebase/lib/core/ui/app_dialog.dart +26 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +1126 -0
- package/templates/firebase/lib/core/widgets/debouncer.dart +31 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +166 -0
- package/templates/firebase/lib/core/widgets/kasy_pressable.dart +2 -0
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +161 -0
- package/templates/firebase/lib/core/widgets/kasy_scroll_behavior.dart +17 -0
- package/templates/firebase/lib/core/widgets/keyboard_visibility.dart +75 -0
- package/templates/firebase/lib/core/widgets/page_background.dart +71 -0
- package/templates/firebase/lib/core/widgets/page_not_found.dart +15 -0
- package/templates/firebase/lib/core/widgets/responsive_layout.dart +215 -0
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +192 -0
- package/templates/firebase/lib/environnements.dart +160 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +528 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +84 -0
- package/templates/firebase/lib/features/authentication/providers/models/email.dart +36 -0
- package/templates/firebase/lib/features/authentication/providers/models/password.dart +37 -0
- package/templates/firebase/lib/features/authentication/providers/models/phone_signin_state.dart +21 -0
- package/templates/firebase/lib/features/authentication/providers/models/recover_state.dart +21 -0
- package/templates/firebase/lib/features/authentication/providers/models/signin_state.dart +20 -0
- package/templates/firebase/lib/features/authentication/providers/models/signup_state.dart +20 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +132 -0
- package/templates/firebase/lib/features/authentication/providers/recover_provider.dart +51 -0
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +166 -0
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +67 -0
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +399 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +85 -0
- package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +19 -0
- package/templates/firebase/lib/features/authentication/ui/components/facebook_signin.dart +19 -0
- package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +32 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +100 -0
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +109 -0
- package/templates/firebase/lib/features/authentication/ui/phone_auth_page.dart +60 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +101 -0
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +321 -0
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +310 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_page_back_button.dart +149 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/otp_input.dart +194 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +62 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +73 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/social_separator.dart +26 -0
- package/templates/firebase/lib/features/dev/keyboard_test_page.dart +93 -0
- package/templates/firebase/lib/features/feedbacks/api/entities/feature_request_entity.dart +27 -0
- package/templates/firebase/lib/features/feedbacks/api/entities/feature_vote_entity.dart +27 -0
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +51 -0
- package/templates/firebase/lib/features/feedbacks/api/feature_vote_api.dart +79 -0
- package/templates/firebase/lib/features/feedbacks/models/feature_requests.dart +48 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +43 -0
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +147 -0
- package/templates/firebase/lib/features/feedbacks/repositories/feature_request_repository.dart +92 -0
- package/templates/firebase/lib/features/feedbacks/ui/component/add_feature_form.dart +112 -0
- package/templates/firebase/lib/features/feedbacks/ui/feedback_page.dart +184 -0
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +66 -0
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +266 -0
- package/templates/firebase/lib/features/home/design_system_page.dart +552 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +314 -0
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +791 -0
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +7712 -0
- package/templates/firebase/lib/features/home/home_features_page.dart +207 -0
- package/templates/firebase/lib/features/home/home_page.dart +348 -0
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +50 -0
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_message_entity.dart +35 -0
- package/templates/firebase/lib/features/llm_chat/llm_chat_page.dart +352 -0
- package/templates/firebase/lib/features/llm_chat/providers/llm_chat_notifier.dart +315 -0
- package/templates/firebase/lib/features/llm_chat/ui/widgets/llm_chat_avatars.dart +148 -0
- package/templates/firebase/lib/features/llm_chat/ui/widgets/llm_chat_composer.dart +124 -0
- package/templates/firebase/lib/features/local_reminder/providers/reminder_notifier.dart +81 -0
- package/templates/firebase/lib/features/local_reminder/repositories/reminder_preferences.dart +76 -0
- package/templates/firebase/lib/features/local_reminder/ui/reminder_page.dart +288 -0
- package/templates/firebase/lib/features/notifications/api/device_api.dart +433 -0
- package/templates/firebase/lib/features/notifications/api/entities/device_entity.dart +62 -0
- package/templates/firebase/lib/features/notifications/api/entities/notifications_entity.dart +35 -0
- package/templates/firebase/lib/features/notifications/api/local_notifier.dart +409 -0
- package/templates/firebase/lib/features/notifications/api/notifications_api.dart +319 -0
- package/templates/firebase/lib/features/notifications/providers/models/device.dart +27 -0
- package/templates/firebase/lib/features/notifications/providers/models/notification.dart +196 -0
- package/templates/firebase/lib/features/notifications/providers/models/notification_list.dart +38 -0
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +120 -0
- package/templates/firebase/lib/features/notifications/repositories/background_notification_handler.dart +13 -0
- package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +137 -0
- package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +214 -0
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +75 -0
- package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +144 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +169 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +46 -0
- package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +32 -0
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +106 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +202 -0
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +84 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +73 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +362 -0
- package/templates/firebase/lib/features/onboarding/api/entities/user_info_entity.dart +24 -0
- package/templates/firebase/lib/features/onboarding/api/user_infos_api.dart +71 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +92 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +15 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +78 -0
- package/templates/firebase/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/templates/firebase/lib/features/onboarding/ui/animations/page_transitions.dart +33 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +76 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +92 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +76 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +89 -0
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +108 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +80 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +145 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +125 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +84 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +44 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +77 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +392 -0
- package/templates/firebase/lib/features/settings/settings_page.dart +460 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +301 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +38 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +53 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +22 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +366 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notifier.dart +40 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +430 -0
- package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +28 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +163 -0
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +59 -0
- package/templates/firebase/lib/features/settings/ui/widgets/avatar_utils.dart +51 -0
- package/templates/firebase/lib/features/settings/ui/widgets/round_progress.dart +151 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +52 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +163 -0
- package/templates/firebase/lib/features/subscription/api/entities/subscription_entity.dart +40 -0
- package/templates/firebase/lib/features/subscription/api/inapp_subscription_api.dart +389 -0
- package/templates/firebase/lib/features/subscription/api/subscription_api.dart +39 -0
- package/templates/firebase/lib/features/subscription/providers/models/premium_state.dart +35 -0
- package/templates/firebase/lib/features/subscription/providers/premium_page_provider.dart +259 -0
- package/templates/firebase/lib/features/subscription/repositories/subscription_repository.dart +177 -0
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +85 -0
- package/templates/firebase/lib/features/subscription/ui/component/active_premium_content.dart +171 -0
- package/templates/firebase/lib/features/subscription/ui/component/paywall_minimal.dart +146 -0
- package/templates/firebase/lib/features/subscription/ui/component/paywall_row.dart +332 -0
- package/templates/firebase/lib/features/subscription/ui/component/paywall_with_switch.dart +207 -0
- package/templates/firebase/lib/features/subscription/ui/component/premium_content.dart +157 -0
- package/templates/firebase/lib/features/subscription/ui/component/premium_page_factory.dart +161 -0
- package/templates/firebase/lib/features/subscription/ui/premium_page.dart +139 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/comparison_table.dart +346 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/feature_line.dart +131 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/paywall_empty_state.dart +68 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_background.dart +63 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_background_gradient.dart +53 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_banner.dart +80 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_bottom_menu.dart +102 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_card.dart +26 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_close_button.dart +61 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_feature.dart +39 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/selectable_col.dart +377 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/selectable_row.dart +436 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/trial_switcher.dart +85 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/unsubscribe_feedback_popup.dart +123 -0
- package/templates/firebase/lib/firebase_options.dart +75 -0
- package/templates/firebase/lib/google_auth_options.dart +10 -0
- package/templates/firebase/lib/i18n/app_locale_display.dart +17 -0
- package/templates/firebase/lib/i18n/en.i18n.json +514 -0
- package/templates/firebase/lib/i18n/es.i18n.json +514 -0
- package/templates/firebase/lib/i18n/pt.i18n.json +514 -0
- package/templates/firebase/lib/i18n/widgets/locale_code_badge.dart +47 -0
- package/templates/firebase/lib/main.dart +263 -0
- package/templates/firebase/lib/router.dart +226 -0
- package/templates/firebase/login-redesign-preview.png +0 -0
- package/templates/firebase/pubspec.yaml +177 -0
- package/templates/firebase/scripts/bump-ios-version.js +38 -0
- package/templates/firebase/scripts/release-ios.sh +137 -0
- package/templates/firebase/slang.yaml +7 -0
- package/templates/firebase/storage.rules +12 -0
- package/templates/firebase/test/core/data/api/analytics_api_fake.dart +25 -0
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +45 -0
- package/templates/firebase/test/core/data/api/storage_api_fake.dart +28 -0
- package/templates/firebase/test/core/data/models/subscription_test.dart +42 -0
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +56 -0
- package/templates/firebase/test/core/guards/guard_test.dart +78 -0
- package/templates/firebase/test/core/navigation/kasy_page_transition_test.dart +30 -0
- package/templates/firebase/test/core/onstart_service_test.dart +18 -0
- package/templates/firebase/test/core/rating/rating_api_fake.dart +58 -0
- package/templates/firebase/test/core/rating/rating_banner_test.dart +117 -0
- package/templates/firebase/test/core/rating/rating_test.dart +60 -0
- package/templates/firebase/test/core/security/secured_storage_fake.dart +33 -0
- package/templates/firebase/test/core/states/event_dispatcher_test.dart +269 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +244 -0
- package/templates/firebase/test/core/widgets/responsive_layout_test.dart +50 -0
- package/templates/firebase/test/device_test_utils.dart +98 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +169 -0
- package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +86 -0
- package/templates/firebase/test/features/authentication/lost_password_page_test.dart +59 -0
- package/templates/firebase/test/features/authentication/repositories/authentication_repository_test.dart +88 -0
- package/templates/firebase/test/features/authentication/signin_page_test.dart +133 -0
- package/templates/firebase/test/features/authentication/signup_page_test.dart +136 -0
- package/templates/firebase/test/features/notifications/data/device_api_fake.dart +66 -0
- package/templates/firebase/test/features/notifications/data/fake_facebook_api.dart +40 -0
- package/templates/firebase/test/features/notifications/data/local_notifier_fake.dart +59 -0
- package/templates/firebase/test/features/notifications/data/notifications_api_fake.dart +120 -0
- package/templates/firebase/test/features/notifications/data/notifications_settings_fake.dart +34 -0
- package/templates/firebase/test/features/notifications/providers/device_repository_test.dart +68 -0
- package/templates/firebase/test/features/notifications/providers/notifications_repository_test.dart +74 -0
- package/templates/firebase/test/features/notifications/ui/notifications_page_test.dart +69 -0
- package/templates/firebase/test/features/subscription/api/fake_inapp_subscription_api.dart +85 -0
- package/templates/firebase/test/features/subscription/api/fake_revenuecat_product.dart +186 -0
- package/templates/firebase/test/features/subscription/api/fake_subscription_api.dart +11 -0
- package/templates/firebase/test/features/subscription/subscription_page_test.dart +119 -0
- package/templates/firebase/test/firebase_test_utils.dart +25 -0
- package/templates/firebase/test/test_utils.dart +272 -0
- package/templates/firebase/web/favicon.png +0 -0
- package/templates/firebase/web/firebase-messaging-sw.js +27 -0
- package/templates/firebase/web/icons/Icon-192.png +0 -0
- package/templates/firebase/web/icons/Icon-512.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
- package/templates/firebase/web/index.html +110 -0
- package/templates/firebase/web/local_notifications.js +31 -0
- package/templates/firebase/web/manifest.json +35 -0
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase "create from scratch" — automate project creation for beginners.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Create GCP project (gcloud projects create)
|
|
6
|
+
* 2. Link billing account
|
|
7
|
+
* 3. Enable required APIs (incl. firebase.googleapis.com) — must be before addFirebase
|
|
8
|
+
* 4. Add Firebase to project (Firebase Management API)
|
|
9
|
+
* 5. Create Android, iOS and Web apps in Firebase
|
|
10
|
+
* 6. Extract SHA-1 from debug.keystore
|
|
11
|
+
* 7. Add SHA-1 to Android app (Firebase Management API)
|
|
12
|
+
*
|
|
13
|
+
* Requires: firebase login (or gcloud auth login), gcloud CLI.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { exec } = require('node:child_process');
|
|
17
|
+
const { promisify } = require('node:util');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const fs = require('fs-extra');
|
|
20
|
+
const os = require('node:os');
|
|
21
|
+
|
|
22
|
+
const execAsync = promisify(exec);
|
|
23
|
+
|
|
24
|
+
function sleep(ms) {
|
|
25
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEBUG_KEYSTORE = path.join(os.homedir(), '.android', 'debug.keystore');
|
|
29
|
+
const KASY_KEYS_DIR = path.join(os.homedir(), '.kasy', 'keys');
|
|
30
|
+
|
|
31
|
+
/** Returns a persistent path for the firebase service account key (survives reboots). */
|
|
32
|
+
function getServiceAccountKeyPath(projectId) {
|
|
33
|
+
return path.join(KASY_KEYS_DIR, `firebase_key_${projectId}.json`);
|
|
34
|
+
}
|
|
35
|
+
const REQUIRED_APIS = [
|
|
36
|
+
'compute.googleapis.com', // Compute Engine (required for Compute SA, used by Cloud Run/Functions)
|
|
37
|
+
'secretmanager.googleapis.com',
|
|
38
|
+
'storage.googleapis.com',
|
|
39
|
+
'firebase.googleapis.com',
|
|
40
|
+
'firebasestorage.googleapis.com', // Firebase Storage (default bucket)
|
|
41
|
+
'firestore.googleapis.com', // Firestore (required for firestore:rules deploy)
|
|
42
|
+
'identitytoolkit.googleapis.com', // Firebase Auth
|
|
43
|
+
'cloudfunctions.googleapis.com', // Cloud Functions (required for functions deploy)
|
|
44
|
+
'cloudbuild.googleapis.com', // Cloud Build (required by Cloud Functions v2)
|
|
45
|
+
'run.googleapis.com', // Cloud Run (required by Cloud Functions v2)
|
|
46
|
+
'artifactregistry.googleapis.com', // Artifact Registry (required by Cloud Functions v2)
|
|
47
|
+
'eventarc.googleapis.com', // Eventarc (required for Cloud Functions v2 event triggers)
|
|
48
|
+
'logging.googleapis.com', // Cloud Logging (Cloud Build writes build logs here)
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
async function run(cmd, cwd = process.cwd()) {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
54
|
+
cwd,
|
|
55
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
});
|
|
58
|
+
return { ok: true, stdout: (stdout || '').trim(), stderr: (stderr || '').trim() };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
error: err.message,
|
|
63
|
+
stdout: (err.stdout || '').trim(),
|
|
64
|
+
stderr: (err.stderr || '').trim(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate a valid GCP project ID from app name.
|
|
71
|
+
* Format: 6-30 chars, lowercase, numbers, hyphens. Must be globally unique.
|
|
72
|
+
*/
|
|
73
|
+
function generateProjectId(appName) {
|
|
74
|
+
const slug = appName
|
|
75
|
+
.normalize('NFD')
|
|
76
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.replace(/[^a-z0-9]/g, '-')
|
|
79
|
+
.replace(/-+/g, '-')
|
|
80
|
+
.replace(/^-|-$/g, '');
|
|
81
|
+
const base = (slug || 'app').slice(0, 20);
|
|
82
|
+
const suffix = Date.now().toString(36).slice(-6);
|
|
83
|
+
return `${base}-${suffix}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if gcloud is installed and user is logged in.
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* Check if gcloud is installed and logged in.
|
|
91
|
+
* Works on Windows, macOS and Linux — gcloud CLI is cross-platform.
|
|
92
|
+
*/
|
|
93
|
+
async function checkGcloudAuth() {
|
|
94
|
+
const version = await run('gcloud --version');
|
|
95
|
+
if (!version.ok) return { ok: false, error: version.error, missing: 'gcloud' };
|
|
96
|
+
|
|
97
|
+
const auth = await run('gcloud auth print-access-token');
|
|
98
|
+
if (!auth.ok) return { ok: false, error: 'Not logged in. Run: gcloud auth login', missing: 'auth' };
|
|
99
|
+
|
|
100
|
+
return { ok: true };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get platform-specific install instructions for gcloud CLI.
|
|
105
|
+
*/
|
|
106
|
+
function getGcloudInstallInstructions() {
|
|
107
|
+
const platform = process.platform;
|
|
108
|
+
if (platform === 'darwin') {
|
|
109
|
+
return { install: 'brew install --cask google-cloud-sdk', after: 'gcloud auth login', url: 'https://cloud.google.com/sdk/docs/install' };
|
|
110
|
+
}
|
|
111
|
+
if (platform === 'win32') {
|
|
112
|
+
return { install: null, hint: 'Download and run the installer from the link below, then restart the terminal.', after: 'gcloud auth login', url: 'https://cloud.google.com/sdk/docs/install#windows' };
|
|
113
|
+
}
|
|
114
|
+
return { install: 'curl https://sdk.cloud.google.com | bash', after: 'gcloud auth login', url: 'https://cloud.google.com/sdk/docs/install' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* List billing accounts the user has access to (open/active only).
|
|
119
|
+
* @returns {{ ok: boolean, accounts?: Array<{ id: string, name: string }>, error?: string }}
|
|
120
|
+
*/
|
|
121
|
+
async function listBillingAccounts() {
|
|
122
|
+
const result = await run('gcloud billing accounts list --filter=open=true --format=json');
|
|
123
|
+
if (!result.ok) return { ok: false, error: result.stderr || result.error };
|
|
124
|
+
try {
|
|
125
|
+
const data = JSON.parse(result.stdout || '[]');
|
|
126
|
+
const accounts = (Array.isArray(data) ? data : []).map((a) => ({
|
|
127
|
+
id: (a.name || '').replace('billingAccounts/', ''),
|
|
128
|
+
name: a.displayName || a.name || '',
|
|
129
|
+
})).filter((a) => a.id);
|
|
130
|
+
return { ok: true, accounts };
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return { ok: false, error: `Failed to parse billing accounts: ${e.message}` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* List GCP organizations the user has access to.
|
|
138
|
+
* Returns an empty list (not an error) for personal accounts with no organization.
|
|
139
|
+
* @returns {{ ok: boolean, organizations?: Array<{ id: string, name: string }>, error?: string }}
|
|
140
|
+
*/
|
|
141
|
+
async function listGcpOrganizations() {
|
|
142
|
+
const result = await run('gcloud organizations list --format=json');
|
|
143
|
+
if (!result.ok) return { ok: true, organizations: [] }; // non-fatal: personal accounts return an error here
|
|
144
|
+
try {
|
|
145
|
+
const data = JSON.parse(result.stdout || '[]');
|
|
146
|
+
const organizations = (Array.isArray(data) ? data : []).map((o) => ({
|
|
147
|
+
id: (o.name || '').replace('organizations/', ''),
|
|
148
|
+
name: o.displayName || o.name || '',
|
|
149
|
+
})).filter((o) => o.id);
|
|
150
|
+
return { ok: true, organizations };
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return { ok: true, organizations: [] }; // parse failure treated as no org
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Detect if billing error is a QuotaFailure (e.g. too many projects linked to billing).
|
|
158
|
+
*/
|
|
159
|
+
function isBillingQuotaError(error) {
|
|
160
|
+
const s = String(error || '');
|
|
161
|
+
return /QuotaFailure|billing quota exceeded|quota exceeded/i.test(s);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Link a billing account to a project (required for Blaze/Cloud Functions).
|
|
166
|
+
*/
|
|
167
|
+
async function linkBillingAccount(projectId, billingAccountId) {
|
|
168
|
+
const result = await run(
|
|
169
|
+
`gcloud billing projects link ${projectId} --billing-account=${billingAccountId}`
|
|
170
|
+
);
|
|
171
|
+
if (!result.ok) {
|
|
172
|
+
return { ok: false, error: result.stderr || result.error };
|
|
173
|
+
}
|
|
174
|
+
return { ok: true };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get OAuth access token for Firebase Management API.
|
|
179
|
+
*/
|
|
180
|
+
async function getAccessToken() {
|
|
181
|
+
const result = await run('gcloud auth print-access-token');
|
|
182
|
+
if (!result.ok) throw new Error(result.error || 'Failed to get access token');
|
|
183
|
+
return result.stdout;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create GCP project.
|
|
188
|
+
* GCP requires display_name >= 4 chars. Pads short names.
|
|
189
|
+
*/
|
|
190
|
+
async function createGcpProject(projectId, projectName, options = {}) {
|
|
191
|
+
const { organizationId } = options;
|
|
192
|
+
const displayName = (projectName || projectId).trim().length >= 4
|
|
193
|
+
? projectName
|
|
194
|
+
: `${projectName || projectId} app`;
|
|
195
|
+
const orgFlag = organizationId ? ` --organization=${organizationId}` : '';
|
|
196
|
+
const cmd = `gcloud projects create ${projectId} --name="${displayName.replace(/"/g, '\\"')}"${orgFlag}`;
|
|
197
|
+
const result = await run(cmd);
|
|
198
|
+
if (!result.ok) {
|
|
199
|
+
if (result.stderr?.includes('already exists') || result.error?.includes('already exists')) {
|
|
200
|
+
return { ok: true, projectId, existed: true };
|
|
201
|
+
}
|
|
202
|
+
return { ok: false, error: result.stderr || result.error };
|
|
203
|
+
}
|
|
204
|
+
return { ok: true, projectId, existed: false };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Add Firebase via REST API (uses gcloud token).
|
|
209
|
+
*/
|
|
210
|
+
async function addFirebaseToProjectRest(projectId) {
|
|
211
|
+
const token = await getAccessToken();
|
|
212
|
+
const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}:addFirebase`;
|
|
213
|
+
const res = await fetch(url, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: {
|
|
216
|
+
Authorization: `Bearer ${token}`,
|
|
217
|
+
'Content-Type': 'application/json',
|
|
218
|
+
'X-Goog-User-Project': projectId,
|
|
219
|
+
},
|
|
220
|
+
body: JSON.stringify({}),
|
|
221
|
+
});
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
const text = await res.text();
|
|
224
|
+
if (res.status === 409 || text.includes('ALREADY_EXISTS')) return { ok: true, existed: true };
|
|
225
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
226
|
+
}
|
|
227
|
+
return { ok: true, existed: false };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Add Firebase to existing GCP project.
|
|
232
|
+
* Tries Firebase CLI first (handles propagation); falls back to REST (gcloud auth).
|
|
233
|
+
* Retries on 404 (project not yet propagated) with delay.
|
|
234
|
+
*/
|
|
235
|
+
async function addFirebaseToProject(projectId, options = {}) {
|
|
236
|
+
const { onProgress = () => {}, maxRetries = 3 } = options;
|
|
237
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
238
|
+
let result = await run(`firebase projects:addfirebase ${projectId} --non-interactive`);
|
|
239
|
+
if (result.ok) return { ok: true, existed: false };
|
|
240
|
+
if (result.stderr?.includes('ALREADY_EXISTS') || result.stderr?.includes('already has Firebase')) {
|
|
241
|
+
return { ok: true, existed: true };
|
|
242
|
+
}
|
|
243
|
+
const is404 = result.stderr?.includes('404') || result.stderr?.includes('not found');
|
|
244
|
+
const is400 = result.stderr?.includes('400') || result.stderr?.includes('INVALID_ARGUMENT') || result.stderr?.includes('invalid argument');
|
|
245
|
+
const isAuthError = result.stderr?.includes('login') || result.stderr?.includes('auth') || result.stderr?.includes('Must be logged in');
|
|
246
|
+
if (isAuthError || is400) {
|
|
247
|
+
const restResult = await addFirebaseToProjectRest(projectId);
|
|
248
|
+
if (restResult.ok) return restResult;
|
|
249
|
+
if (is400) return { ok: false, error: restResult.error };
|
|
250
|
+
if (isAuthError) return { ok: false, error: restResult.error };
|
|
251
|
+
}
|
|
252
|
+
if (is404 && attempt < maxRetries) {
|
|
253
|
+
onProgress('wait-propagate');
|
|
254
|
+
await sleep(15000);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
return { ok: false, error: result.stderr || result.error };
|
|
258
|
+
}
|
|
259
|
+
return { ok: false, error: 'Max retries exceeded' };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create Firestore database (required for firestore:rules deploy).
|
|
264
|
+
* Uses gcloud firestore databases create. Retries up to 3x (API propagation).
|
|
265
|
+
*/
|
|
266
|
+
async function createFirestoreDatabase(projectId, location = 'us-central1') {
|
|
267
|
+
const maxAttempts = 3;
|
|
268
|
+
const delayMs = 15000;
|
|
269
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
270
|
+
const result = await run(
|
|
271
|
+
`gcloud firestore databases create --location=${location} --project=${projectId} --quiet`
|
|
272
|
+
);
|
|
273
|
+
if (result.ok) return { ok: true, existed: false };
|
|
274
|
+
if (result.stderr?.includes('ALREADY_EXISTS') || result.stderr?.includes('already exists')) {
|
|
275
|
+
return { ok: true, existed: true };
|
|
276
|
+
}
|
|
277
|
+
if (attempt < maxAttempts) {
|
|
278
|
+
await sleep(delayMs);
|
|
279
|
+
} else {
|
|
280
|
+
return { ok: false, error: result.stderr || result.error };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return { ok: false, error: 'Max retries exceeded' };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Create Firebase Storage default bucket (avoids manual "Get Started" in Console).
|
|
288
|
+
* Requires Blaze plan. Uses firebasestorage.googleapis.com REST API. Retries up to 3x.
|
|
289
|
+
* As of Oct 2024, the API requires location in the request body.
|
|
290
|
+
*/
|
|
291
|
+
async function createFirebaseStorageBucket(projectId, location = 'us-central1') {
|
|
292
|
+
const maxAttempts = 3;
|
|
293
|
+
const delayMs = 15000;
|
|
294
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
295
|
+
const token = await getAccessToken();
|
|
296
|
+
const url = `https://firebasestorage.googleapis.com/v1alpha/projects/${projectId}/defaultBucket`;
|
|
297
|
+
const res = await fetch(url, {
|
|
298
|
+
method: 'POST',
|
|
299
|
+
headers: {
|
|
300
|
+
Authorization: `Bearer ${token}`,
|
|
301
|
+
'Content-Type': 'application/json',
|
|
302
|
+
'X-Goog-User-Project': projectId,
|
|
303
|
+
},
|
|
304
|
+
body: JSON.stringify({ location }),
|
|
305
|
+
});
|
|
306
|
+
if (res.ok) return { ok: true, existed: false };
|
|
307
|
+
const text = await res.text();
|
|
308
|
+
if (res.status === 409 || text.includes('ALREADY_EXISTS') || text.includes('already exists')) {
|
|
309
|
+
return { ok: true, existed: true };
|
|
310
|
+
}
|
|
311
|
+
if (attempt < maxAttempts) {
|
|
312
|
+
await sleep(delayMs);
|
|
313
|
+
} else {
|
|
314
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return { ok: false, error: 'Max retries exceeded' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Enable required Google Cloud APIs.
|
|
322
|
+
*/
|
|
323
|
+
async function enableApis(projectId) {
|
|
324
|
+
const apis = REQUIRED_APIS.join(' ');
|
|
325
|
+
const result = await run(`gcloud services enable ${apis} --project=${projectId}`);
|
|
326
|
+
if (!result.ok) {
|
|
327
|
+
return { ok: false, error: result.stderr || result.error };
|
|
328
|
+
}
|
|
329
|
+
return { ok: true };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Poll a Firebase long-running operation until it completes.
|
|
334
|
+
* Returns { ok: true, response } or { ok: false, error }.
|
|
335
|
+
*/
|
|
336
|
+
async function pollOperation(operationName, token, { maxAttempts = 20, delayMs = 3000, projectId } = {}) {
|
|
337
|
+
// Extract projectId from operation name if not passed explicitly (e.g. "projects/my-proj/operations/abc")
|
|
338
|
+
const quotaProject = projectId || (operationName.startsWith('projects/') ? operationName.split('/')[1] : null);
|
|
339
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
340
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
341
|
+
if (quotaProject) headers['X-Goog-User-Project'] = quotaProject;
|
|
342
|
+
const res = await fetch(`https://firebase.googleapis.com/v1beta1/${operationName}`, { headers });
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
const text = await res.text();
|
|
345
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
346
|
+
}
|
|
347
|
+
const data = await res.json();
|
|
348
|
+
if (data.done) {
|
|
349
|
+
if (data.error) return { ok: false, error: data.error.message || JSON.stringify(data.error) };
|
|
350
|
+
return { ok: true, response: data.response };
|
|
351
|
+
}
|
|
352
|
+
await sleep(delayMs);
|
|
353
|
+
}
|
|
354
|
+
return { ok: false, error: 'Operation timed out waiting for Firebase app creation' };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Create Android app in Firebase project.
|
|
359
|
+
*/
|
|
360
|
+
async function createAndroidApp(projectId, packageName, appName) {
|
|
361
|
+
const token = await getAccessToken();
|
|
362
|
+
const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/androidApps`;
|
|
363
|
+
const res = await fetch(url, {
|
|
364
|
+
method: 'POST',
|
|
365
|
+
headers: {
|
|
366
|
+
Authorization: `Bearer ${token}`,
|
|
367
|
+
'Content-Type': 'application/json',
|
|
368
|
+
'X-Goog-User-Project': projectId,
|
|
369
|
+
},
|
|
370
|
+
body: JSON.stringify({
|
|
371
|
+
packageName,
|
|
372
|
+
displayName: appName || packageName,
|
|
373
|
+
}),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
if (!res.ok) {
|
|
377
|
+
const text = await res.text();
|
|
378
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const operation = await res.json();
|
|
382
|
+
// Firebase returns a long-running operation — poll until done to get the real appId
|
|
383
|
+
const pollResult = await pollOperation(operation.name, token, { projectId });
|
|
384
|
+
if (!pollResult.ok) return { ok: false, error: pollResult.error };
|
|
385
|
+
const app = pollResult.response;
|
|
386
|
+
return { ok: true, appId: app.appId, fullName: app.name };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Create iOS app in Firebase project.
|
|
391
|
+
*/
|
|
392
|
+
async function createIosApp(projectId, bundleId, appName) {
|
|
393
|
+
const token = await getAccessToken();
|
|
394
|
+
const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/iosApps`;
|
|
395
|
+
const res = await fetch(url, {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
headers: {
|
|
398
|
+
Authorization: `Bearer ${token}`,
|
|
399
|
+
'Content-Type': 'application/json',
|
|
400
|
+
'X-Goog-User-Project': projectId,
|
|
401
|
+
},
|
|
402
|
+
body: JSON.stringify({
|
|
403
|
+
bundleId,
|
|
404
|
+
displayName: appName || bundleId,
|
|
405
|
+
}),
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (!res.ok) {
|
|
409
|
+
const text = await res.text();
|
|
410
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const operation = await res.json();
|
|
414
|
+
const pollResult = await pollOperation(operation.name, token, { projectId });
|
|
415
|
+
if (!pollResult.ok) return { ok: false, error: pollResult.error };
|
|
416
|
+
const app = pollResult.response;
|
|
417
|
+
return { ok: true, appId: app.appId, fullName: app.name };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Create Web app in Firebase project.
|
|
422
|
+
*/
|
|
423
|
+
async function createWebApp(projectId, appName) {
|
|
424
|
+
const token = await getAccessToken();
|
|
425
|
+
const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/webApps`;
|
|
426
|
+
const res = await fetch(url, {
|
|
427
|
+
method: 'POST',
|
|
428
|
+
headers: {
|
|
429
|
+
Authorization: `Bearer ${token}`,
|
|
430
|
+
'Content-Type': 'application/json',
|
|
431
|
+
'X-Goog-User-Project': projectId,
|
|
432
|
+
},
|
|
433
|
+
body: JSON.stringify({
|
|
434
|
+
displayName: appName || 'Web app',
|
|
435
|
+
}),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
if (!res.ok) {
|
|
439
|
+
const text = await res.text();
|
|
440
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const operation = await res.json();
|
|
444
|
+
const pollResult = await pollOperation(operation.name, token, { projectId });
|
|
445
|
+
if (!pollResult.ok) return { ok: false, error: pollResult.error };
|
|
446
|
+
const app = pollResult.response;
|
|
447
|
+
return { ok: true, appId: app.appId, fullName: app.name };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Ensure debug.keystore exists. Creates it if missing (standard Android debug key).
|
|
452
|
+
*/
|
|
453
|
+
async function ensureDebugKeystore() {
|
|
454
|
+
const androidDir = path.dirname(DEBUG_KEYSTORE);
|
|
455
|
+
if (!(await fs.pathExists(DEBUG_KEYSTORE))) {
|
|
456
|
+
await fs.ensureDir(androidDir);
|
|
457
|
+
const createResult = await run(
|
|
458
|
+
`keytool -genkeypair -v -keystore "${DEBUG_KEYSTORE}" -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US" -noprompt`
|
|
459
|
+
);
|
|
460
|
+
if (!createResult.ok) return { ok: false, error: createResult.stderr || createResult.error };
|
|
461
|
+
}
|
|
462
|
+
return { ok: true };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Extract SHA-1 from debug.keystore.
|
|
467
|
+
* Creates debug.keystore first if it doesn't exist.
|
|
468
|
+
*/
|
|
469
|
+
async function extractSha1() {
|
|
470
|
+
const ensureResult = await ensureDebugKeystore();
|
|
471
|
+
if (!ensureResult.ok) return ensureResult;
|
|
472
|
+
|
|
473
|
+
const result = await run(
|
|
474
|
+
`keytool -list -v -keystore "${DEBUG_KEYSTORE}" -alias androiddebugkey -storepass android -keypass android`
|
|
475
|
+
);
|
|
476
|
+
if (!result.ok) return { ok: false, error: result.stderr || result.error };
|
|
477
|
+
|
|
478
|
+
const output = (result.stdout || '') + '\n' + (result.stderr || '');
|
|
479
|
+
const sha1Match = output.match(/SHA-?1[:\s]+([A-Fa-f0-9:]+)/i);
|
|
480
|
+
const sha1 = sha1Match?.[1]?.trim().replace(/\s+/g, '');
|
|
481
|
+
if (!sha1 || !/^[A-Fa-f0-9:]+$/.test(sha1) || sha1.replace(/:/g, '').length !== 40) {
|
|
482
|
+
return { ok: false, error: 'Could not parse SHA1 from keytool output' };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return { ok: true, sha1 };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Add SHA-1 certificate to Android app.
|
|
490
|
+
* Uses Firebase CLI first, falls back to REST API. Retries up to 3 times (project propagation).
|
|
491
|
+
*/
|
|
492
|
+
async function addSha1ToApp(projectId, androidAppId, sha1) {
|
|
493
|
+
if (!sha1 || typeof sha1 !== 'string') {
|
|
494
|
+
return { ok: false, error: 'SHA-1 hash is missing. Run the app once in Android Studio to create debug.keystore.' };
|
|
495
|
+
}
|
|
496
|
+
const maxAttempts = 3;
|
|
497
|
+
const delayMs = 10000;
|
|
498
|
+
let lastError = null;
|
|
499
|
+
|
|
500
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
501
|
+
const result = await run(
|
|
502
|
+
`firebase apps:android:sha:create ${androidAppId} "${sha1}" --project=${projectId} --non-interactive`
|
|
503
|
+
);
|
|
504
|
+
if (result.ok) return { ok: true, existed: false };
|
|
505
|
+
if (result.stderr?.includes('ALREADY_EXISTS') || result.stderr?.includes('already exists')) {
|
|
506
|
+
return { ok: true, existed: true };
|
|
507
|
+
}
|
|
508
|
+
lastError = result.stderr || result.error;
|
|
509
|
+
|
|
510
|
+
const restResult = await addSha1ToAppRest(projectId, androidAppId, sha1);
|
|
511
|
+
if (restResult.ok) return restResult;
|
|
512
|
+
lastError = restResult.error;
|
|
513
|
+
|
|
514
|
+
if (attempt < maxAttempts) {
|
|
515
|
+
await sleep(delayMs);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return { ok: false, error: lastError };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function addSha1ToAppRest(projectId, androidAppId, sha1) {
|
|
522
|
+
const token = await getAccessToken();
|
|
523
|
+
const parent = `projects/${projectId}/androidApps/${androidAppId}`;
|
|
524
|
+
const url = `https://firebase.googleapis.com/v1beta1/${parent}/sha`;
|
|
525
|
+
// REST API expects SHA-1 without colons, 40 hex chars lowercase
|
|
526
|
+
const shaHash = sha1.replace(/:/g, '').toLowerCase();
|
|
527
|
+
if (shaHash.length !== 40 || !/^[a-f0-9]+$/.test(shaHash)) {
|
|
528
|
+
return { ok: false, error: `Invalid SHA-1 format: expected 40 hex chars, got ${shaHash.length}` };
|
|
529
|
+
}
|
|
530
|
+
const res = await fetch(url, {
|
|
531
|
+
method: 'POST',
|
|
532
|
+
headers: {
|
|
533
|
+
Authorization: `Bearer ${token}`,
|
|
534
|
+
'Content-Type': 'application/json',
|
|
535
|
+
'X-Goog-User-Project': projectId,
|
|
536
|
+
},
|
|
537
|
+
body: JSON.stringify({
|
|
538
|
+
shaHash,
|
|
539
|
+
certType: 'SHA_1',
|
|
540
|
+
}),
|
|
541
|
+
});
|
|
542
|
+
if (!res.ok) {
|
|
543
|
+
const text = await res.text();
|
|
544
|
+
if (res.status === 409 || text.includes('ALREADY_EXISTS')) return { ok: true, existed: true };
|
|
545
|
+
let errMsg = `${res.status}: ${text}`;
|
|
546
|
+
try {
|
|
547
|
+
const json = JSON.parse(text);
|
|
548
|
+
if (json?.error?.message) errMsg = `${res.status}: ${json.error.message}`;
|
|
549
|
+
} catch (_) {}
|
|
550
|
+
return { ok: false, error: errMsg };
|
|
551
|
+
}
|
|
552
|
+
return { ok: true, existed: false };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Create service account and download key for deploy.
|
|
557
|
+
*/
|
|
558
|
+
async function createServiceAccountAndKey(projectId, outputPath) {
|
|
559
|
+
const saName = 'kasy-deploy';
|
|
560
|
+
const saEmail = `${saName}@${projectId}.iam.gserviceaccount.com`;
|
|
561
|
+
|
|
562
|
+
const createSa = await run(
|
|
563
|
+
`gcloud iam service-accounts create ${saName} --display-name="Kasy Deploy" --project=${projectId}`
|
|
564
|
+
);
|
|
565
|
+
if (!createSa.ok && !createSa.stderr?.includes('already exists')) {
|
|
566
|
+
return { ok: false, error: createSa.stderr || createSa.error };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const roles = [
|
|
570
|
+
'roles/editor',
|
|
571
|
+
'roles/firebase.admin', // Firebase-specific deploy operations
|
|
572
|
+
'roles/iam.serviceAccountUser', // Required for Cloud Functions deployment
|
|
573
|
+
];
|
|
574
|
+
for (const role of roles) {
|
|
575
|
+
const bind = await run(
|
|
576
|
+
`gcloud projects add-iam-policy-binding ${projectId} --member="serviceAccount:${saEmail}" --role="${role}" --quiet`
|
|
577
|
+
);
|
|
578
|
+
if (!bind.ok) {
|
|
579
|
+
return { ok: false, error: `Failed to add role ${role}: ${bind.stderr || bind.error}` };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const keyResult = await run(
|
|
584
|
+
`gcloud iam service-accounts keys create "${outputPath}" --iam-account=${saEmail} --project=${projectId}`
|
|
585
|
+
);
|
|
586
|
+
if (!keyResult.ok) return { ok: false, error: keyResult.stderr || keyResult.error };
|
|
587
|
+
|
|
588
|
+
return { ok: true, keyPath: outputPath };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Check if billing (Blaze plan) is enabled for a GCP project.
|
|
593
|
+
* @param {string} projectId
|
|
594
|
+
* @returns {{ ok: boolean, enabled?: boolean, error?: string }}
|
|
595
|
+
*/
|
|
596
|
+
async function checkBillingEnabled(projectId) {
|
|
597
|
+
const result = await run(`gcloud billing projects describe ${projectId} --format="value(billingEnabled)"`);
|
|
598
|
+
if (!result.ok) return { ok: false, error: result.stderr || result.error };
|
|
599
|
+
return { ok: true, enabled: result.stdout.trim().toLowerCase() === 'true' };
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Enable Firebase Auth sign-in providers: Email/Password, Anonymous and Google.
|
|
604
|
+
* Uses the Identity Toolkit Admin v2 REST API with gcloud credentials.
|
|
605
|
+
*
|
|
606
|
+
* Flow:
|
|
607
|
+
* 1. Call identityPlatform:initializeAuth to create the auth config for the project
|
|
608
|
+
* (required for new projects — PATCH fails with CONFIGURATION_NOT_FOUND without it).
|
|
609
|
+
* 2. PATCH /config to enable Email/Password and Anonymous.
|
|
610
|
+
* 3. POST defaultSupportedIdpConfigs to enable Google Sign-In.
|
|
611
|
+
* Google Sign-In requires an OAuth client_id that Firebase creates when the user
|
|
612
|
+
* enables it in the Console — if the client doesn't exist yet this step is skipped
|
|
613
|
+
* and googleSignInSkipped=true is returned so the caller can show a targeted hint.
|
|
614
|
+
*
|
|
615
|
+
* Non-fatal: returns { ok: false, error } without throwing if the API call fails.
|
|
616
|
+
*
|
|
617
|
+
* @param {string} projectId
|
|
618
|
+
* @returns {{ ok: boolean, googleSignInSkipped?: boolean, error?: string }}
|
|
619
|
+
*/
|
|
620
|
+
async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 15000 } = {}) {
|
|
621
|
+
let token;
|
|
622
|
+
try {
|
|
623
|
+
token = await getAccessToken();
|
|
624
|
+
} catch (_) {
|
|
625
|
+
return { ok: false, error: 'Could not get access token' };
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Step 1: Initialize Firebase Auth for the project.
|
|
629
|
+
// New projects have no auth config — PATCH returns CONFIGURATION_NOT_FOUND without this.
|
|
630
|
+
// The endpoint is idempotent: it returns {} on success or if already initialized.
|
|
631
|
+
const initUrl = `https://identitytoolkit.googleapis.com/v2/projects/${projectId}/identityPlatform:initializeAuth`;
|
|
632
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
633
|
+
const initRes = await fetch(initUrl, {
|
|
634
|
+
method: 'POST',
|
|
635
|
+
headers: {
|
|
636
|
+
Authorization: `Bearer ${token}`,
|
|
637
|
+
'Content-Type': 'application/json',
|
|
638
|
+
'X-Goog-User-Project': projectId,
|
|
639
|
+
},
|
|
640
|
+
body: JSON.stringify({}),
|
|
641
|
+
});
|
|
642
|
+
if (initRes.ok) break;
|
|
643
|
+
await initRes.text(); // consume body to release connection
|
|
644
|
+
if (attempt < maxRetries && (initRes.status === 404 || initRes.status === 503)) {
|
|
645
|
+
await sleep(retryDelayMs);
|
|
646
|
+
try { token = await getAccessToken(); } catch (_) {}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
// Non-fatal — still attempt PATCH below in case auth was already initialized.
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Step 2: Enable Email/Password and Anonymous auth providers.
|
|
654
|
+
const configUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config?updateMask=signIn.email,signIn.anonymous`;
|
|
655
|
+
let lastError;
|
|
656
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
657
|
+
try { token = await getAccessToken(); } catch (_) {
|
|
658
|
+
return { ok: false, error: 'Could not get access token' };
|
|
659
|
+
}
|
|
660
|
+
const res = await fetch(configUrl, {
|
|
661
|
+
method: 'PATCH',
|
|
662
|
+
headers: {
|
|
663
|
+
Authorization: `Bearer ${token}`,
|
|
664
|
+
'Content-Type': 'application/json',
|
|
665
|
+
'X-Goog-User-Project': projectId,
|
|
666
|
+
},
|
|
667
|
+
body: JSON.stringify({
|
|
668
|
+
signIn: {
|
|
669
|
+
email: { enabled: true, passwordRequired: true },
|
|
670
|
+
anonymous: { enabled: true },
|
|
671
|
+
},
|
|
672
|
+
}),
|
|
673
|
+
});
|
|
674
|
+
if (res.ok) {
|
|
675
|
+
// Step 3: Enable Google Sign-In via defaultSupportedIdpConfigs.
|
|
676
|
+
// This requires an OAuth client_id. Firebase creates one automatically when the user
|
|
677
|
+
// enables Google Sign-In in the Console. If the client doesn't exist yet, the POST
|
|
678
|
+
// returns 400 INVALID_CONFIG — skip and return googleSignInSkipped=true.
|
|
679
|
+
const googleUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/defaultSupportedIdpConfigs?idpId=google.com`;
|
|
680
|
+
const googleRes = await fetch(googleUrl, {
|
|
681
|
+
method: 'POST',
|
|
682
|
+
headers: {
|
|
683
|
+
Authorization: `Bearer ${token}`,
|
|
684
|
+
'Content-Type': 'application/json',
|
|
685
|
+
'X-Goog-User-Project': projectId,
|
|
686
|
+
},
|
|
687
|
+
body: JSON.stringify({
|
|
688
|
+
name: `projects/${projectId}/defaultSupportedIdpConfigs/google.com`,
|
|
689
|
+
enabled: true,
|
|
690
|
+
}),
|
|
691
|
+
});
|
|
692
|
+
if (!googleRes.ok) {
|
|
693
|
+
const googleText = await googleRes.text();
|
|
694
|
+
// 409 = already exists — update it to ensure it's enabled.
|
|
695
|
+
if (googleRes.status === 409 || googleText.includes('ALREADY_EXISTS')) {
|
|
696
|
+
const patchUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/defaultSupportedIdpConfigs/google.com?updateMask=enabled`;
|
|
697
|
+
await fetch(patchUrl, {
|
|
698
|
+
method: 'PATCH',
|
|
699
|
+
headers: {
|
|
700
|
+
Authorization: `Bearer ${token}`,
|
|
701
|
+
'Content-Type': 'application/json',
|
|
702
|
+
'X-Goog-User-Project': projectId,
|
|
703
|
+
},
|
|
704
|
+
body: JSON.stringify({ enabled: true }),
|
|
705
|
+
});
|
|
706
|
+
return { ok: true };
|
|
707
|
+
}
|
|
708
|
+
// 400 INVALID_CONFIG (client_id empty) = no OAuth client created yet.
|
|
709
|
+
// Email/Password and Anonymous are already enabled — just skip Google.
|
|
710
|
+
return { ok: true, googleSignInSkipped: true };
|
|
711
|
+
}
|
|
712
|
+
return { ok: true };
|
|
713
|
+
}
|
|
714
|
+
const text = await res.text();
|
|
715
|
+
lastError = `${res.status}: ${text}`;
|
|
716
|
+
// Retry on 404 (API not yet active) or 503 (transient). Fatal on 400/403.
|
|
717
|
+
if (attempt < maxRetries && (res.status === 404 || res.status === 503)) {
|
|
718
|
+
await sleep(retryDelayMs);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
return { ok: false, error: lastError };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Full setup from scratch.
|
|
728
|
+
* @param {object} options.resumeFromBilling - { projectId } to skip create and resume from billing
|
|
729
|
+
* @returns {{ ok: boolean, projectId?: string, error?: string, billingFailed?: boolean }}
|
|
730
|
+
*/
|
|
731
|
+
async function setupFromScratch(appName, bundleId, options = {}) {
|
|
732
|
+
const { onProgress = () => {}, includeWeb = true, region = 'us-central1', tr, resumeFromBilling, billingAccountId: preferredBillingId, organizationId } = options;
|
|
733
|
+
const projectId = resumeFromBilling?.projectId || generateProjectId(appName);
|
|
734
|
+
|
|
735
|
+
const authCheck = await checkGcloudAuth();
|
|
736
|
+
if (!authCheck.ok) return { ok: false, error: authCheck.error };
|
|
737
|
+
|
|
738
|
+
if (!resumeFromBilling) {
|
|
739
|
+
onProgress('gcp-project');
|
|
740
|
+
const createResult = await createGcpProject(projectId, appName, { organizationId });
|
|
741
|
+
if (!createResult.ok) return { ok: false, error: createResult.error };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
onProgress('billing');
|
|
745
|
+
const billingList = await listBillingAccounts();
|
|
746
|
+
const hasAccounts = billingList.ok && billingList.accounts?.length > 0;
|
|
747
|
+
if (!hasAccounts && !preferredBillingId) {
|
|
748
|
+
return {
|
|
749
|
+
ok: false,
|
|
750
|
+
error: `Project ${projectId} was created but no billing account found. Create one at https://console.cloud.google.com/billing and link it. Then use "Use existing project" with ID: ${projectId}`,
|
|
751
|
+
projectId,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const billingAccountId = preferredBillingId || billingList.accounts[0].id;
|
|
755
|
+
const linkResult = await linkBillingAccount(projectId, billingAccountId);
|
|
756
|
+
if (!linkResult.ok) {
|
|
757
|
+
// Manage URL: remove projects from billing to free quota, then retry
|
|
758
|
+
const manageLink = `https://console.cloud.google.com/billing/${billingAccountId}/manage?project=${projectId}`;
|
|
759
|
+
const rawError = linkResult.error;
|
|
760
|
+
const isQuota = isBillingQuotaError(rawError);
|
|
761
|
+
const shortError = isQuota
|
|
762
|
+
? 'Cloud billing quota exceeded (too many projects linked to this billing account)'
|
|
763
|
+
: rawError;
|
|
764
|
+
return {
|
|
765
|
+
ok: false,
|
|
766
|
+
error: `Project created but billing link failed: ${shortError}. Manage projects: ${manageLink}`,
|
|
767
|
+
projectId,
|
|
768
|
+
billingFailed: true,
|
|
769
|
+
billingManualLink: manageLink,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
onProgress('enable-apis');
|
|
774
|
+
const apisResult = await enableApis(projectId);
|
|
775
|
+
if (!apisResult.ok) return { ok: false, error: apisResult.error };
|
|
776
|
+
|
|
777
|
+
// Set ADC quota project so Firebase REST API calls (createAndroidApp etc.) work with user credentials.
|
|
778
|
+
// Non-fatal when ADC has not been initialized (gcloud auth application-default login not run):
|
|
779
|
+
// in that case the user token from gcloud auth print-access-token + X-Goog-User-Project header is sufficient.
|
|
780
|
+
const quotaResult = await run(`gcloud auth application-default set-quota-project ${projectId}`);
|
|
781
|
+
if (!quotaResult.ok) {
|
|
782
|
+
const adcNotSetup = (quotaResult.error + quotaResult.stderr).includes('Application default credentials have not been set up');
|
|
783
|
+
if (!adcNotSetup) {
|
|
784
|
+
return { ok: false, error: `Failed to set ADC quota project: ${quotaResult.error}`, projectId };
|
|
785
|
+
}
|
|
786
|
+
// ADC not configured — continue; user token + X-Goog-User-Project header covers quota.
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
onProgress('wait-propagate');
|
|
790
|
+
await sleep(20000);
|
|
791
|
+
|
|
792
|
+
onProgress('add-firebase');
|
|
793
|
+
const addResult = await addFirebaseToProject(projectId, { onProgress });
|
|
794
|
+
if (!addResult.ok) return { ok: false, error: `[addFirebase] ${addResult.error}`, projectId };
|
|
795
|
+
|
|
796
|
+
// Enable Email/Password, Anonymous and Google Sign-In auth providers automatically.
|
|
797
|
+
// Non-fatal — project setup continues even if this call fails.
|
|
798
|
+
const authResult = await enableAuthProviders(projectId);
|
|
799
|
+
if (!authResult.ok) {
|
|
800
|
+
onProgress('auth-providers-warn', {
|
|
801
|
+
error: authResult.error,
|
|
802
|
+
url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
|
|
803
|
+
});
|
|
804
|
+
} else if (authResult.googleSignInSkipped) {
|
|
805
|
+
// Email/Password and Anonymous were enabled. Google Sign-In needs an OAuth client
|
|
806
|
+
// that Firebase creates when you enable it in the Console for the first time.
|
|
807
|
+
onProgress('auth-google-warn', {
|
|
808
|
+
url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
onProgress('firestore');
|
|
813
|
+
const firestoreResult = await createFirestoreDatabase(projectId, region);
|
|
814
|
+
if (!firestoreResult.ok) {
|
|
815
|
+
onProgress('firestore-skipped', {
|
|
816
|
+
error: firestoreResult.error,
|
|
817
|
+
url: `https://console.firebase.google.com/project/${projectId}/firestore`,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
onProgress('storage');
|
|
822
|
+
const storageResult = await createFirebaseStorageBucket(projectId, region);
|
|
823
|
+
if (!storageResult.ok) {
|
|
824
|
+
onProgress('storage-skipped', {
|
|
825
|
+
error: storageResult.error,
|
|
826
|
+
url: `https://console.firebase.google.com/project/${projectId}/storage`,
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
onProgress('android-app');
|
|
831
|
+
const appResult = await createAndroidApp(projectId, bundleId, appName);
|
|
832
|
+
if (!appResult.ok) return { ok: false, error: `[createAndroidApp] ${appResult.error}`, projectId };
|
|
833
|
+
|
|
834
|
+
onProgress('ios-app');
|
|
835
|
+
const iosResult = await createIosApp(projectId, bundleId, appName);
|
|
836
|
+
if (!iosResult.ok) return { ok: false, error: `[createIosApp] ${iosResult.error}`, projectId };
|
|
837
|
+
|
|
838
|
+
if (includeWeb) {
|
|
839
|
+
onProgress('web-app');
|
|
840
|
+
const webResult = await createWebApp(projectId, appName);
|
|
841
|
+
if (!webResult.ok) return { ok: false, error: `[createWebApp] ${webResult.error}`, projectId };
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
let sha1Skipped = null;
|
|
845
|
+
let sha1Error = null;
|
|
846
|
+
const shaResult = await extractSha1();
|
|
847
|
+
if (shaResult.ok) {
|
|
848
|
+
onProgress('sha1');
|
|
849
|
+
const shaAddResult = await addSha1ToApp(projectId, appResult.appId, shaResult.sha1);
|
|
850
|
+
if (!shaAddResult.ok) {
|
|
851
|
+
sha1Skipped = 'api_failed';
|
|
852
|
+
sha1Error = shaAddResult.error;
|
|
853
|
+
}
|
|
854
|
+
} else {
|
|
855
|
+
const knownError = 'Could not parse SHA1 from keytool output';
|
|
856
|
+
sha1Skipped = (typeof tr === 'function' && shaResult.error === knownError)
|
|
857
|
+
? tr('new.firebase.create.sha1ParseError')
|
|
858
|
+
: shaResult.error;
|
|
859
|
+
sha1Error = shaResult.error;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
ok: true,
|
|
864
|
+
projectId,
|
|
865
|
+
authEnabled: authResult.ok,
|
|
866
|
+
googleSignInSkipped: authResult.ok && !!authResult.googleSignInSkipped,
|
|
867
|
+
sha1Skipped,
|
|
868
|
+
sha1Error,
|
|
869
|
+
sha1ManualUrl: `https://console.firebase.google.com/project/${projectId}/settings/general/android:${bundleId}`,
|
|
870
|
+
firestoreCreated: firestoreResult.ok,
|
|
871
|
+
firestoreError: firestoreResult.ok ? null : firestoreResult.error,
|
|
872
|
+
firestoreUrl: `https://console.firebase.google.com/project/${projectId}/firestore`,
|
|
873
|
+
storageCreated: storageResult.ok,
|
|
874
|
+
storageError: storageResult.ok ? null : storageResult.error,
|
|
875
|
+
storageUrl: `https://console.firebase.google.com/project/${projectId}/storage`,
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Setup an existing Firebase project: enable missing APIs, create Firestore and
|
|
881
|
+
* Storage if they don't exist yet, and create a service account key for deploy.
|
|
882
|
+
*
|
|
883
|
+
* Non-fatal failures (Firestore, Storage, SHA-1, service account) are reported
|
|
884
|
+
* but do not abort the flow — the user gets URLs for manual action.
|
|
885
|
+
*
|
|
886
|
+
* @param {string} projectId Existing Firebase project ID
|
|
887
|
+
* @param {string} bundleId App bundle ID (for SHA-1 URL)
|
|
888
|
+
* @param {object} options
|
|
889
|
+
* @returns {{ ok: boolean, ... }}
|
|
890
|
+
*/
|
|
891
|
+
async function setupExistingProject(projectId, options = {}) {
|
|
892
|
+
const { onProgress = () => {}, region = 'us-central1' } = options;
|
|
893
|
+
|
|
894
|
+
const authCheck = await checkGcloudAuth();
|
|
895
|
+
if (!authCheck.ok) return { ok: false, error: authCheck.error };
|
|
896
|
+
|
|
897
|
+
onProgress('enable-apis');
|
|
898
|
+
const apisResult = await enableApis(projectId);
|
|
899
|
+
if (!apisResult.ok) {
|
|
900
|
+
// non-fatal: user may not have gcloud set to this project — warn but continue
|
|
901
|
+
onProgress('enable-apis-warn', { error: apisResult.error });
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Enable Email/Password, Anonymous and Google Sign-In auth providers automatically.
|
|
905
|
+
// Non-fatal — continue even if this fails (e.g. API not yet propagated for very new projects).
|
|
906
|
+
const authResult = await enableAuthProviders(projectId);
|
|
907
|
+
if (!authResult.ok) {
|
|
908
|
+
onProgress('auth-providers-warn', {
|
|
909
|
+
error: authResult.error,
|
|
910
|
+
url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
|
|
911
|
+
});
|
|
912
|
+
} else if (authResult.googleSignInSkipped) {
|
|
913
|
+
onProgress('auth-google-warn', {
|
|
914
|
+
url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
onProgress('firestore');
|
|
919
|
+
const firestoreResult = await createFirestoreDatabase(projectId);
|
|
920
|
+
if (!firestoreResult.ok) {
|
|
921
|
+
onProgress('firestore-skipped', {
|
|
922
|
+
error: firestoreResult.error,
|
|
923
|
+
url: `https://console.firebase.google.com/project/${projectId}/firestore`,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
onProgress('storage');
|
|
928
|
+
const storageResult = await createFirebaseStorageBucket(projectId, region);
|
|
929
|
+
if (!storageResult.ok) {
|
|
930
|
+
onProgress('storage-skipped', {
|
|
931
|
+
error: storageResult.error,
|
|
932
|
+
url: `https://console.firebase.google.com/project/${projectId}/storage`,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
ok: true,
|
|
938
|
+
projectId,
|
|
939
|
+
firestoreCreated: firestoreResult.ok,
|
|
940
|
+
firestoreError: firestoreResult.ok ? null : firestoreResult.error,
|
|
941
|
+
firestoreUrl: `https://console.firebase.google.com/project/${projectId}/firestore`,
|
|
942
|
+
storageCreated: storageResult.ok,
|
|
943
|
+
storageError: storageResult.ok ? null : storageResult.error,
|
|
944
|
+
storageUrl: `https://console.firebase.google.com/project/${projectId}/storage`,
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Find the Firebase Android app ID for a given bundle ID (package name).
|
|
950
|
+
* Used to register SHA-1 on existing Firebase projects where the app was created
|
|
951
|
+
* by `flutterfire configure` rather than by `setupFromScratch`.
|
|
952
|
+
*/
|
|
953
|
+
async function getAndroidAppId(projectId, bundleId) {
|
|
954
|
+
const token = await getAccessToken();
|
|
955
|
+
const url = `https://firebase.googleapis.com/v1beta1/projects/${projectId}/androidApps?pageSize=100`;
|
|
956
|
+
const res = await fetch(url, {
|
|
957
|
+
headers: {
|
|
958
|
+
Authorization: `Bearer ${token}`,
|
|
959
|
+
'X-Goog-User-Project': projectId,
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
if (!res.ok) {
|
|
963
|
+
const text = await res.text();
|
|
964
|
+
return { ok: false, error: `${res.status}: ${text}` };
|
|
965
|
+
}
|
|
966
|
+
const data = await res.json();
|
|
967
|
+
const app = (data.apps || []).find((a) => a.packageName === bundleId);
|
|
968
|
+
if (!app) return { ok: false, error: `No Android app found for package ${bundleId}` };
|
|
969
|
+
return { ok: true, appId: app.appId };
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Register the debug keystore SHA-1 for a Firebase Android app by bundle ID.
|
|
974
|
+
* Safe to call even if SHA-1 is already registered (ALREADY_EXISTS is treated as success).
|
|
975
|
+
* Called for all backends when using an existing Firebase project, after flutterfire configure.
|
|
976
|
+
*
|
|
977
|
+
* @param {string} projectId Firebase project ID
|
|
978
|
+
* @param {string} bundleId Android package name (e.g. com.myapp.app)
|
|
979
|
+
* @returns {{ ok: boolean, existed?: boolean, sha1Error?: string }}
|
|
980
|
+
*/
|
|
981
|
+
async function registerDebugSha1(projectId, bundleId) {
|
|
982
|
+
const sha1Result = await extractSha1();
|
|
983
|
+
if (!sha1Result.ok) return { ok: false, sha1Error: sha1Result.error };
|
|
984
|
+
|
|
985
|
+
const appResult = await getAndroidAppId(projectId, bundleId);
|
|
986
|
+
if (!appResult.ok) return { ok: false, sha1Error: appResult.error };
|
|
987
|
+
|
|
988
|
+
const addResult = await addSha1ToApp(projectId, appResult.appId, sha1Result.sha1);
|
|
989
|
+
if (!addResult.ok) return { ok: false, sha1Error: addResult.error };
|
|
990
|
+
|
|
991
|
+
return { ok: true, existed: !!addResult.existed };
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
module.exports = {
|
|
995
|
+
setupFromScratch,
|
|
996
|
+
setupExistingProject,
|
|
997
|
+
checkBillingEnabled,
|
|
998
|
+
enableAuthProviders,
|
|
999
|
+
listBillingAccounts,
|
|
1000
|
+
listGcpOrganizations,
|
|
1001
|
+
checkGcloudAuth,
|
|
1002
|
+
getGcloudInstallInstructions,
|
|
1003
|
+
generateProjectId,
|
|
1004
|
+
extractSha1,
|
|
1005
|
+
registerDebugSha1,
|
|
1006
|
+
createServiceAccountAndKey,
|
|
1007
|
+
getServiceAccountKeyPath,
|
|
1008
|
+
};
|