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.
Files changed (735) hide show
  1. package/README.md +56 -0
  2. package/bin/kasy.js +529 -0
  3. package/docs/cli-reference.md +251 -0
  4. package/lib/commands/add.js +777 -0
  5. package/lib/commands/check.js +240 -0
  6. package/lib/commands/codemagic.js +161 -0
  7. package/lib/commands/deploy.js +257 -0
  8. package/lib/commands/docs.js +47 -0
  9. package/lib/commands/doctor.js +227 -0
  10. package/lib/commands/features.js +36 -0
  11. package/lib/commands/ios.js +206 -0
  12. package/lib/commands/new.js +1870 -0
  13. package/lib/commands/notifications.js +207 -0
  14. package/lib/commands/remove.js +466 -0
  15. package/lib/commands/run.js +75 -0
  16. package/lib/commands/update.js +382 -0
  17. package/lib/commands/validate.js +131 -0
  18. package/lib/scaffold/CHANGELOG.json +6 -0
  19. package/lib/scaffold/backends/api/.flutter-plugins-dependencies +1 -0
  20. package/lib/scaffold/backends/api/analysis_options.yaml +5 -0
  21. package/lib/scaffold/backends/api/generator.js +76 -0
  22. package/lib/scaffold/backends/api/patch/README.md +85 -0
  23. package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +87 -0
  24. package/lib/scaffold/backends/api/patch/ios/Runner/AppDelegate.swift +61 -0
  25. package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +80 -0
  26. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +117 -0
  27. package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +148 -0
  28. package/lib/scaffold/backends/api/patch/lib/core/data/entities/json_converters.dart +35 -0
  29. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +48 -0
  30. package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +211 -0
  31. package/lib/scaffold/backends/api/patch/lib/environnements.dart +151 -0
  32. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +303 -0
  33. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +80 -0
  34. package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +406 -0
  35. package/lib/scaffold/backends/api/patch/lib/features/camera/xfile_extension.dart +6 -0
  36. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/entities/feature_request_entity.dart +24 -0
  37. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/entities/feature_vote_entity.dart +21 -0
  38. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +52 -0
  39. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +60 -0
  40. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +58 -0
  41. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_message_entity.dart +29 -0
  42. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +398 -0
  43. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/device_entity.dart +39 -0
  44. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +23 -0
  45. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/notifications_api.dart +333 -0
  46. package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +185 -0
  47. package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +26 -0
  48. package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/user_infos_api.dart +107 -0
  49. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +94 -0
  50. package/lib/scaffold/backends/api/patch/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
  51. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +34 -0
  52. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
  53. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
  54. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
  55. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/widgets/avatar_utils.dart +33 -0
  56. package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +34 -0
  57. package/lib/scaffold/backends/api/patch/lib/features/subscription/api/subscription_api.dart +49 -0
  58. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +88 -0
  59. package/lib/scaffold/backends/api/patch/lib/main.dart +256 -0
  60. package/lib/scaffold/backends/api/patch/lib/router.dart +133 -0
  61. package/lib/scaffold/backends/api/pubspec.yaml.tpl +108 -0
  62. package/lib/scaffold/backends/firebase/deploy.js +1006 -0
  63. package/lib/scaffold/backends/firebase/generator.js +24 -0
  64. package/lib/scaffold/backends/firebase/setup-from-scratch.js +1008 -0
  65. package/lib/scaffold/backends/firebase/tokens.js +113 -0
  66. package/lib/scaffold/backends/supabase/.flutter-plugins-dependencies +1 -0
  67. package/lib/scaffold/backends/supabase/analysis_options.yaml +5 -0
  68. package/lib/scaffold/backends/supabase/config.toml +34 -0
  69. package/lib/scaffold/backends/supabase/deploy.js +485 -0
  70. package/lib/scaffold/backends/supabase/edge-functions/delete-user-account/README.md +15 -0
  71. package/lib/scaffold/backends/supabase/edge-functions/delete-user-account/index.ts +97 -0
  72. package/lib/scaffold/backends/supabase/edge-functions/llm-chat/index.ts +156 -0
  73. package/lib/scaffold/backends/supabase/edge-functions/meta-track-event/index.ts +292 -0
  74. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/README.md +30 -0
  75. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +488 -0
  76. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/README.md +35 -0
  77. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +314 -0
  78. package/lib/scaffold/backends/supabase/generator.js +108 -0
  79. package/lib/scaffold/backends/supabase/migrations/20240101000001_initial_schema.sql +149 -0
  80. package/lib/scaffold/backends/supabase/migrations/20240101000002_storage_bucket.sql +40 -0
  81. package/lib/scaffold/backends/supabase/migrations/20240101000005_vote_triggers.sql +34 -0
  82. package/lib/scaffold/backends/supabase/migrations/20240101000006_notification_webhook.sql +36 -0
  83. package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +64 -0
  84. package/lib/scaffold/backends/supabase/migrations/20240101000008_user_feature_requests.sql +2 -0
  85. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +22 -0
  86. package/lib/scaffold/backends/supabase/migrations/20240101000010_notification_image.sql +2 -0
  87. package/lib/scaffold/backends/supabase/patch/README.md +121 -0
  88. package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +87 -0
  89. package/lib/scaffold/backends/supabase/patch/ios/Runner/AppDelegate.swift +61 -0
  90. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/meta_ads_api.dart +75 -0
  91. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +83 -0
  92. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +143 -0
  93. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/json_converters.dart +35 -0
  94. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +48 -0
  95. package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +211 -0
  96. package/lib/scaffold/backends/supabase/patch/lib/environnements.dart +149 -0
  97. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +546 -0
  98. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +410 -0
  99. package/lib/scaffold/backends/supabase/patch/lib/features/camera/xfile_extension.dart +6 -0
  100. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/entities/feature_request_entity.dart +25 -0
  101. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/entities/feature_vote_entity.dart +21 -0
  102. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +59 -0
  103. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_vote_api.dart +85 -0
  104. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +199 -0
  105. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +47 -0
  106. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_message_entity.dart +30 -0
  107. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +266 -0
  108. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +410 -0
  109. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/entities/device_entity.dart +51 -0
  110. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/entities/notifications_entity.dart +26 -0
  111. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/notifications_api.dart +317 -0
  112. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +174 -0
  113. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +26 -0
  114. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/user_infos_api.dart +90 -0
  115. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +94 -0
  116. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
  117. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +34 -0
  118. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
  119. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
  120. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
  121. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/widgets/avatar_utils.dart +35 -0
  122. package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +35 -0
  123. package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/subscription_api.dart +44 -0
  124. package/lib/scaffold/backends/supabase/patch/lib/features/subscription/shared/maybeshow_premium.dart +85 -0
  125. package/lib/scaffold/backends/supabase/patch/lib/google_auth_options.dart +23 -0
  126. package/lib/scaffold/backends/supabase/patch/lib/main.dart +288 -0
  127. package/lib/scaffold/backends/supabase/patch/lib/router.dart +133 -0
  128. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +110 -0
  129. package/lib/scaffold/backends/supabase/tokens.js +9 -0
  130. package/lib/scaffold/catalog.js +177 -0
  131. package/lib/scaffold/engine.js +230 -0
  132. package/lib/scaffold/features/README.md +152 -0
  133. package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +124 -0
  134. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +35 -0
  135. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +35 -0
  136. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +35 -0
  137. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +12 -0
  138. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +12 -0
  139. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +12 -0
  140. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +17 -0
  141. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  142. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +17 -0
  143. package/lib/scaffold/features/ci/.github/dependabot.yml +16 -0
  144. package/lib/scaffold/features/ci/.github/workflows/app.yml +20 -0
  145. package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +14 -0
  146. package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +19 -0
  147. package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +163 -0
  148. package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +28 -0
  149. package/lib/scaffold/features/ci/.gitlab-ci.yml +37 -0
  150. package/lib/scaffold/features/ci/codemagic.yaml +157 -0
  151. package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +111 -0
  152. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +27 -0
  153. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +27 -0
  154. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +50 -0
  155. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +79 -0
  156. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +48 -0
  157. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +42 -0
  158. package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +147 -0
  159. package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +95 -0
  160. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +199 -0
  161. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +175 -0
  162. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +76 -0
  163. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +279 -0
  164. package/lib/scaffold/features/ios-release/.kasy/apple.env.example +8 -0
  165. package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +7 -0
  166. package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +50 -0
  167. package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +50 -0
  168. package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +50 -0
  169. package/lib/scaffold/features/ios-release/docs/ios-release.en.md +41 -0
  170. package/lib/scaffold/features/ios-release/docs/ios-release.es.md +41 -0
  171. package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +41 -0
  172. package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +38 -0
  173. package/lib/scaffold/features/ios-release/scripts/release-ios.sh +137 -0
  174. package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +301 -0
  175. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +81 -0
  176. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +76 -0
  177. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +282 -0
  178. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +24 -0
  179. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +71 -0
  180. package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +92 -0
  181. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +15 -0
  182. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +78 -0
  183. package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
  184. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +30 -0
  185. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
  186. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +72 -0
  187. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +92 -0
  188. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
  189. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +89 -0
  190. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +94 -0
  191. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +80 -0
  192. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +139 -0
  193. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +110 -0
  194. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +84 -0
  195. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
  196. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +45 -0
  197. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +77 -0
  198. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +392 -0
  199. package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +116 -0
  200. package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +322 -0
  201. package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +41 -0
  202. package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +305 -0
  203. package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_background_task.dart +41 -0
  204. package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart +57 -0
  205. package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_service.dart +54 -0
  206. package/lib/scaffold/features/widget/lib/features/settings/ui/components/admin/admin_home_widgets.dart +32 -0
  207. package/lib/scaffold/generate.js +370 -0
  208. package/lib/scaffold/patch.js +395 -0
  209. package/lib/scaffold/shared/backend-config.js +74 -0
  210. package/lib/scaffold/shared/fcm-service-account.js +126 -0
  211. package/lib/scaffold/shared/generator-utils.js +1664 -0
  212. package/lib/scaffold/shared/post-build.js +809 -0
  213. package/lib/scaffold/shared/template-strings.js +112 -0
  214. package/lib/utils/apple-release.js +273 -0
  215. package/lib/utils/checks.js +319 -0
  216. package/lib/utils/codemagic-release.js +127 -0
  217. package/lib/utils/fs.js +122 -0
  218. package/lib/utils/i18n.js +2123 -0
  219. package/lib/utils/license-gate.js +72 -0
  220. package/lib/utils/license.js +64 -0
  221. package/lib/utils/prompts.js +213 -0
  222. package/lib/utils/updates.js +97 -0
  223. package/package.json +55 -0
  224. package/templates/firebase/.env.example +24 -0
  225. package/templates/firebase/.firebaserc +5 -0
  226. package/templates/firebase/.kasy/apple.env.example +8 -0
  227. package/templates/firebase/.kasy/codemagic.env.example +7 -0
  228. package/templates/firebase/.metadata +30 -0
  229. package/templates/firebase/.windsurfrules +95 -0
  230. package/templates/firebase/Makefile +30 -0
  231. package/templates/firebase/README.en.md +180 -0
  232. package/templates/firebase/README.es.md +180 -0
  233. package/templates/firebase/README.md +180 -0
  234. package/templates/firebase/analysis_options.yaml +37 -0
  235. package/templates/firebase/android/app/build.gradle.kts +80 -0
  236. package/templates/firebase/android/app/src/debug/AndroidManifest.xml +7 -0
  237. package/templates/firebase/android/app/src/main/AndroidManifest.xml +93 -0
  238. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +36 -0
  239. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +50 -0
  240. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +7 -0
  241. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  242. package/templates/firebase/android/app/src/main/res/drawable/launch_background.xml +9 -0
  243. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  244. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  245. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  246. package/templates/firebase/android/app/src/main/res/drawable-v21/launch_background.xml +9 -0
  247. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  248. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  249. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  250. package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  251. package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  252. package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  253. package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  254. package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  255. package/templates/firebase/android/app/src/main/res/values/strings.xml +6 -0
  256. package/templates/firebase/android/app/src/main/res/values/styles.xml +22 -0
  257. package/templates/firebase/android/app/src/main/res/values-night/styles.xml +22 -0
  258. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +20 -0
  259. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +20 -0
  260. package/templates/firebase/android/app/src/main/res/xml/locales_config.xml +7 -0
  261. package/templates/firebase/android/app/src/main/res/xml/mywidget_info.xml +11 -0
  262. package/templates/firebase/android/app/src/profile/AndroidManifest.xml +7 -0
  263. package/templates/firebase/android/build.gradle.kts +28 -0
  264. package/templates/firebase/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  265. package/templates/firebase/android/gradle.properties +2 -0
  266. package/templates/firebase/android/settings.gradle.kts +29 -0
  267. package/templates/firebase/assets/icons/apple.png +0 -0
  268. package/templates/firebase/assets/icons/facebook.png +0 -0
  269. package/templates/firebase/assets/icons/google.png +0 -0
  270. package/templates/firebase/assets/icons/google_play_games.png +0 -0
  271. package/templates/firebase/assets/images/app_icon.png +0 -0
  272. package/templates/firebase/assets/images/empty_notifications.jpg +0 -0
  273. package/templates/firebase/assets/images/icon.png +0 -0
  274. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  275. package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
  276. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  277. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  278. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  279. package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
  280. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  281. package/templates/firebase/assets/images/premium-bg.jpg +0 -0
  282. package/templates/firebase/assets/images/premium-switch-header.png +0 -0
  283. package/templates/firebase/assets/images/review.png +0 -0
  284. package/templates/firebase/assets/images/splashscreen.png +0 -0
  285. package/templates/firebase/assets/images/update.png +0 -0
  286. package/templates/firebase/build.yaml +13 -0
  287. package/templates/firebase/devtools_options.yaml +3 -0
  288. package/templates/firebase/docs/auth-setup.en.md +145 -0
  289. package/templates/firebase/docs/auth-setup.es.md +145 -0
  290. package/templates/firebase/docs/auth-setup.pt.md +145 -0
  291. package/templates/firebase/docs/codemagic-release.en.md +50 -0
  292. package/templates/firebase/docs/codemagic-release.es.md +50 -0
  293. package/templates/firebase/docs/codemagic-release.pt.md +50 -0
  294. package/templates/firebase/docs/ios-release.en.md +41 -0
  295. package/templates/firebase/docs/ios-release.es.md +41 -0
  296. package/templates/firebase/docs/ios-release.pt.md +54 -0
  297. package/templates/firebase/docs/navigation-transitions.md +80 -0
  298. package/templates/firebase/docs/revenuecat-setup.es.md +341 -0
  299. package/templates/firebase/docs/revenuecat-setup.pt.md +341 -0
  300. package/templates/firebase/firebase.json +1 -0
  301. package/templates/firebase/firestore.indexes.json +25 -0
  302. package/templates/firebase/firestore.rules +51 -0
  303. package/templates/firebase/functions/.eslintrc.js +45 -0
  304. package/templates/firebase/functions/jest.config.js +16 -0
  305. package/templates/firebase/functions/package-lock.json +6876 -0
  306. package/templates/firebase/functions/package.json +33 -0
  307. package/templates/firebase/functions/src/authentication/functions.ts +30 -0
  308. package/templates/firebase/functions/src/authentication/triggers.ts +103 -0
  309. package/templates/firebase/functions/src/core/api/meta_ads_api.ts +390 -0
  310. package/templates/firebase/functions/src/core/data/entities/entity_utils.ts +87 -0
  311. package/templates/firebase/functions/src/core/data/entities/notification_entity.ts +74 -0
  312. package/templates/firebase/functions/src/core/data/entities/page.ts +4 -0
  313. package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +76 -0
  314. package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +64 -0
  315. package/templates/firebase/functions/src/core/data/entities/user_entity.ts +43 -0
  316. package/templates/firebase/functions/src/core/data/repositories/repositories.ts +16 -0
  317. package/templates/firebase/functions/src/core/data/repositories/subscription_repository.ts +42 -0
  318. package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +31 -0
  319. package/templates/firebase/functions/src/core/data/repositories/user_notifications_repository.ts +58 -0
  320. package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +84 -0
  321. package/templates/firebase/functions/src/core/logger/logger.ts +13 -0
  322. package/templates/firebase/functions/src/core/storage/storage_manager.ts +52 -0
  323. package/templates/firebase/functions/src/core/utils/async_utils.ts +5 -0
  324. package/templates/firebase/functions/src/feature_requests/triggers.ts +3 -0
  325. package/templates/firebase/functions/src/index.ts +24 -0
  326. package/templates/firebase/functions/src/llm_chat/index.ts +181 -0
  327. package/templates/firebase/functions/src/notifications/admin_functions.ts +91 -0
  328. package/templates/firebase/functions/src/notifications/models/notification.ts +61 -0
  329. package/templates/firebase/functions/src/notifications/notifications_api.ts +147 -0
  330. package/templates/firebase/functions/src/notifications/triggers.ts +102 -0
  331. package/templates/firebase/functions/src/subscriptions/models/revenuecat_events.ts +162 -0
  332. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +18 -0
  333. package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +173 -0
  334. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +135 -0
  335. package/templates/firebase/functions/src/subscriptions/triggers.ts +35 -0
  336. package/templates/firebase/functions/src/test/core/data/entities/entity_utils.spec.ts +44 -0
  337. package/templates/firebase/functions/tsconfig.dev.json +5 -0
  338. package/templates/firebase/functions/tsconfig.json +18 -0
  339. package/templates/firebase/home_widget_config.json +30 -0
  340. package/templates/firebase/ios/Flutter/AppFrameworkInfo.plist +24 -0
  341. package/templates/firebase/ios/Flutter/Debug.xcconfig +2 -0
  342. package/templates/firebase/ios/Flutter/Profile.xcconfig +2 -0
  343. package/templates/firebase/ios/Flutter/Release.xcconfig +2 -0
  344. package/templates/firebase/ios/HomeWidgetExtension/HomeWidgetBundle.swift +13 -0
  345. package/templates/firebase/ios/HomeWidgetExtension/Info.plist +29 -0
  346. package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +86 -0
  347. package/templates/firebase/ios/HomeWidgetExtension.entitlements +10 -0
  348. package/templates/firebase/ios/NotificationService/Info.plist +31 -0
  349. package/templates/firebase/ios/NotificationService/NotificationService.swift +76 -0
  350. package/templates/firebase/ios/Podfile +111 -0
  351. package/templates/firebase/ios/Runner/AppDelegate.swift +75 -0
  352. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +1 -0
  353. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  354. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  355. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  356. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  357. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  358. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  359. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  360. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  361. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  362. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  363. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
  364. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
  365. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
  366. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
  367. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  368. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  369. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
  370. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
  371. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  372. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  373. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  374. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +21 -0
  375. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  376. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
  377. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  378. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  379. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  380. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
  381. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +44 -0
  382. package/templates/firebase/ios/Runner/Base.lproj/Main.storyboard +26 -0
  383. package/templates/firebase/ios/Runner/Info.plist +132 -0
  384. package/templates/firebase/ios/Runner/Runner-Bridging-Header.h +1 -0
  385. package/templates/firebase/ios/Runner/Runner.entitlements +14 -0
  386. package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +10 -0
  387. package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +10 -0
  388. package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +10 -0
  389. package/templates/firebase/ios/Runner.xcodeproj/project.pbxproj +1108 -0
  390. package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  391. package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  392. package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  393. package/templates/firebase/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme +58 -0
  394. package/templates/firebase/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  395. package/templates/firebase/ios/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  396. package/templates/firebase/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  397. package/templates/firebase/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  398. package/templates/firebase/ios/RunnerTests/RunnerTests.swift +12 -0
  399. package/templates/firebase/lib/components/components.dart +27 -0
  400. package/templates/firebase/lib/components/kasy_accordion.dart +384 -0
  401. package/templates/firebase/lib/components/kasy_alert.dart +266 -0
  402. package/templates/firebase/lib/components/kasy_app_bar.dart +536 -0
  403. package/templates/firebase/lib/components/kasy_avatar.dart +549 -0
  404. package/templates/firebase/lib/components/kasy_avatar_presets.dart +104 -0
  405. package/templates/firebase/lib/components/kasy_badge.dart +278 -0
  406. package/templates/firebase/lib/components/kasy_bottom_sheet.dart +338 -0
  407. package/templates/firebase/lib/components/kasy_button.dart +926 -0
  408. package/templates/firebase/lib/components/kasy_card.dart +128 -0
  409. package/templates/firebase/lib/components/kasy_checkbox.dart +429 -0
  410. package/templates/firebase/lib/components/kasy_chip.dart +83 -0
  411. package/templates/firebase/lib/components/kasy_dialog.dart +789 -0
  412. package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +233 -0
  413. package/templates/firebase/lib/components/kasy_skeleton.dart +225 -0
  414. package/templates/firebase/lib/components/kasy_text_area.dart +392 -0
  415. package/templates/firebase/lib/components/kasy_text_field.dart +591 -0
  416. package/templates/firebase/lib/components/kasy_text_field_otp.dart +532 -0
  417. package/templates/firebase/lib/components/kasy_toast.dart +803 -0
  418. package/templates/firebase/lib/core/ads/ads_provider.dart +136 -0
  419. package/templates/firebase/lib/core/ads/ads_state.dart +15 -0
  420. package/templates/firebase/lib/core/animations/bottomfade_anim.dart +37 -0
  421. package/templates/firebase/lib/core/animations/movefade_anim.dart +34 -0
  422. package/templates/firebase/lib/core/animations/slideright_anim.dart +38 -0
  423. package/templates/firebase/lib/core/bottom_menu/bart_inner_paths.dart +5 -0
  424. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +127 -0
  425. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +281 -0
  426. package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +22 -0
  427. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +127 -0
  428. package/templates/firebase/lib/core/bottom_menu/notification_bottom_item.dart +48 -0
  429. package/templates/firebase/lib/core/config/app_env.dart +100 -0
  430. package/templates/firebase/lib/core/config/features.dart +15 -0
  431. package/templates/firebase/lib/core/data/api/analytics_api.dart +124 -0
  432. package/templates/firebase/lib/core/data/api/base_api_exceptions.dart +28 -0
  433. package/templates/firebase/lib/core/data/api/firestore_retry.dart +29 -0
  434. package/templates/firebase/lib/core/data/api/http_client.dart +38 -0
  435. package/templates/firebase/lib/core/data/api/image.dart +52 -0
  436. package/templates/firebase/lib/core/data/api/remote_config_api.dart +170 -0
  437. package/templates/firebase/lib/core/data/api/storage_api.dart +115 -0
  438. package/templates/firebase/lib/core/data/api/tracking_api.dart +119 -0
  439. package/templates/firebase/lib/core/data/api/user_api.dart +115 -0
  440. package/templates/firebase/lib/core/data/entities/json_converters.dart +39 -0
  441. package/templates/firebase/lib/core/data/entities/upload_result.dart +48 -0
  442. package/templates/firebase/lib/core/data/entities/user_entity.dart +54 -0
  443. package/templates/firebase/lib/core/data/models/pageable.dart +12 -0
  444. package/templates/firebase/lib/core/data/models/subscription.dart +322 -0
  445. package/templates/firebase/lib/core/data/models/user.dart +107 -0
  446. package/templates/firebase/lib/core/data/repositories/user_repository.dart +130 -0
  447. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +464 -0
  448. package/templates/firebase/lib/core/dev_inspector/dev_inspector_highlight.dart +88 -0
  449. package/templates/firebase/lib/core/dev_inspector/dev_inspector_info.dart +70 -0
  450. package/templates/firebase/lib/core/dev_inspector/dev_inspector_panel.dart +204 -0
  451. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +321 -0
  452. package/templates/firebase/lib/core/extensions/date.ext.dart +32 -0
  453. package/templates/firebase/lib/core/guards/authenticated_guard.dart +37 -0
  454. package/templates/firebase/lib/core/guards/guard.dart +68 -0
  455. package/templates/firebase/lib/core/guards/user_info_guard.dart +55 -0
  456. package/templates/firebase/lib/core/haptics/haptic_feedback_notifier.dart +19 -0
  457. package/templates/firebase/lib/core/haptics/kasy_haptics.dart +27 -0
  458. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +41 -0
  459. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +57 -0
  460. package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +54 -0
  461. package/templates/firebase/lib/core/icons/kasy_icons.dart +86 -0
  462. package/templates/firebase/lib/core/initializer/models/run_state.dart +14 -0
  463. package/templates/firebase/lib/core/initializer/onstart_service.dart +38 -0
  464. package/templates/firebase/lib/core/initializer/onstart_widget.dart +83 -0
  465. package/templates/firebase/lib/core/initializer/pending_notification_handler.dart +48 -0
  466. package/templates/firebase/lib/core/keyboard_fix/keyboard_flicker_fix.dart +164 -0
  467. package/templates/firebase/lib/core/navigation/kasy_fade_page_transitions_builder.dart +34 -0
  468. package/templates/firebase/lib/core/navigation/kasy_material_page_route.dart +41 -0
  469. package/templates/firebase/lib/core/navigation/kasy_navigation_config.dart +29 -0
  470. package/templates/firebase/lib/core/navigation/kasy_page_transition.dart +40 -0
  471. package/templates/firebase/lib/core/navigation/kasy_route_transition.dart +68 -0
  472. package/templates/firebase/lib/core/navigation/kasy_transition_kind.dart +17 -0
  473. package/templates/firebase/lib/core/navigation/navigation.dart +6 -0
  474. package/templates/firebase/lib/core/rating/api/rating_api.dart +99 -0
  475. package/templates/firebase/lib/core/rating/models/rating.dart +52 -0
  476. package/templates/firebase/lib/core/rating/models/review.dart +70 -0
  477. package/templates/firebase/lib/core/rating/providers/rating_repository.dart +75 -0
  478. package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +190 -0
  479. package/templates/firebase/lib/core/rating/widgets/rate_popup.dart +54 -0
  480. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +175 -0
  481. package/templates/firebase/lib/core/security/biometric_copy_slot.dart +20 -0
  482. package/templates/firebase/lib/core/security/biometric_guard.dart +114 -0
  483. package/templates/firebase/lib/core/security/biometric_preference_notifier.dart +17 -0
  484. package/templates/firebase/lib/core/security/biometric_service.dart +79 -0
  485. package/templates/firebase/lib/core/security/biometric_ui_bundle.dart +136 -0
  486. package/templates/firebase/lib/core/security/secured_storage.dart +46 -0
  487. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +70 -0
  488. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +2021 -0
  489. package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +88 -0
  490. package/templates/firebase/lib/core/states/components/maybe_ask_rating.dart +49 -0
  491. package/templates/firebase/lib/core/states/components/maybe_show_update_bottom_sheet.dart +86 -0
  492. package/templates/firebase/lib/core/states/components/maybeshow_component.dart +110 -0
  493. package/templates/firebase/lib/core/states/events_dispatcher.dart +103 -0
  494. package/templates/firebase/lib/core/states/models/event_model.dart +118 -0
  495. package/templates/firebase/lib/core/states/models/user_state.dart +22 -0
  496. package/templates/firebase/lib/core/states/notifications_dispatcher.dart +81 -0
  497. package/templates/firebase/lib/core/states/translations.dart +11 -0
  498. package/templates/firebase/lib/core/states/user_state_notifier.dart +321 -0
  499. package/templates/firebase/lib/core/theme/colors.dart +262 -0
  500. package/templates/firebase/lib/core/theme/extensions/theme_extension.dart +48 -0
  501. package/templates/firebase/lib/core/theme/providers/kasy_theme.dart +76 -0
  502. package/templates/firebase/lib/core/theme/providers/theme_provider.dart +212 -0
  503. package/templates/firebase/lib/core/theme/radius.dart +33 -0
  504. package/templates/firebase/lib/core/theme/shadows.dart +64 -0
  505. package/templates/firebase/lib/core/theme/spacing.dart +44 -0
  506. package/templates/firebase/lib/core/theme/texts.dart +176 -0
  507. package/templates/firebase/lib/core/theme/theme.dart +24 -0
  508. package/templates/firebase/lib/core/theme/theme_data/theme_data.dart +32 -0
  509. package/templates/firebase/lib/core/theme/theme_data/theme_data_factory.dart +15 -0
  510. package/templates/firebase/lib/core/theme/universal_theme.dart +210 -0
  511. package/templates/firebase/lib/core/toast/toast_service.dart +55 -0
  512. package/templates/firebase/lib/core/ui/app_dialog.dart +26 -0
  513. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +1126 -0
  514. package/templates/firebase/lib/core/widgets/debouncer.dart +31 -0
  515. package/templates/firebase/lib/core/widgets/kasy_hover.dart +166 -0
  516. package/templates/firebase/lib/core/widgets/kasy_pressable.dart +2 -0
  517. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +161 -0
  518. package/templates/firebase/lib/core/widgets/kasy_scroll_behavior.dart +17 -0
  519. package/templates/firebase/lib/core/widgets/keyboard_visibility.dart +75 -0
  520. package/templates/firebase/lib/core/widgets/page_background.dart +71 -0
  521. package/templates/firebase/lib/core/widgets/page_not_found.dart +15 -0
  522. package/templates/firebase/lib/core/widgets/responsive_layout.dart +215 -0
  523. package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +192 -0
  524. package/templates/firebase/lib/environnements.dart +160 -0
  525. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +528 -0
  526. package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +84 -0
  527. package/templates/firebase/lib/features/authentication/providers/models/email.dart +36 -0
  528. package/templates/firebase/lib/features/authentication/providers/models/password.dart +37 -0
  529. package/templates/firebase/lib/features/authentication/providers/models/phone_signin_state.dart +21 -0
  530. package/templates/firebase/lib/features/authentication/providers/models/recover_state.dart +21 -0
  531. package/templates/firebase/lib/features/authentication/providers/models/signin_state.dart +20 -0
  532. package/templates/firebase/lib/features/authentication/providers/models/signup_state.dart +20 -0
  533. package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +132 -0
  534. package/templates/firebase/lib/features/authentication/providers/recover_provider.dart +51 -0
  535. package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +166 -0
  536. package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +67 -0
  537. package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +399 -0
  538. package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +85 -0
  539. package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +19 -0
  540. package/templates/firebase/lib/features/authentication/ui/components/facebook_signin.dart +19 -0
  541. package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +32 -0
  542. package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +100 -0
  543. package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +109 -0
  544. package/templates/firebase/lib/features/authentication/ui/phone_auth_page.dart +60 -0
  545. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +101 -0
  546. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +321 -0
  547. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +310 -0
  548. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +35 -0
  549. package/templates/firebase/lib/features/authentication/ui/widgets/auth_page_back_button.dart +149 -0
  550. package/templates/firebase/lib/features/authentication/ui/widgets/otp_input.dart +194 -0
  551. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +62 -0
  552. package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +73 -0
  553. package/templates/firebase/lib/features/authentication/ui/widgets/social_separator.dart +26 -0
  554. package/templates/firebase/lib/features/dev/keyboard_test_page.dart +93 -0
  555. package/templates/firebase/lib/features/feedbacks/api/entities/feature_request_entity.dart +27 -0
  556. package/templates/firebase/lib/features/feedbacks/api/entities/feature_vote_entity.dart +27 -0
  557. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +51 -0
  558. package/templates/firebase/lib/features/feedbacks/api/feature_vote_api.dart +79 -0
  559. package/templates/firebase/lib/features/feedbacks/models/feature_requests.dart +48 -0
  560. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +43 -0
  561. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +147 -0
  562. package/templates/firebase/lib/features/feedbacks/repositories/feature_request_repository.dart +92 -0
  563. package/templates/firebase/lib/features/feedbacks/ui/component/add_feature_form.dart +112 -0
  564. package/templates/firebase/lib/features/feedbacks/ui/feedback_page.dart +184 -0
  565. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +66 -0
  566. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +266 -0
  567. package/templates/firebase/lib/features/home/design_system_page.dart +552 -0
  568. package/templates/firebase/lib/features/home/home_components_page.dart +314 -0
  569. package/templates/firebase/lib/features/home/home_components_preview_page.dart +791 -0
  570. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +7712 -0
  571. package/templates/firebase/lib/features/home/home_features_page.dart +207 -0
  572. package/templates/firebase/lib/features/home/home_page.dart +348 -0
  573. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +50 -0
  574. package/templates/firebase/lib/features/llm_chat/api/llm_chat_message_entity.dart +35 -0
  575. package/templates/firebase/lib/features/llm_chat/llm_chat_page.dart +352 -0
  576. package/templates/firebase/lib/features/llm_chat/providers/llm_chat_notifier.dart +315 -0
  577. package/templates/firebase/lib/features/llm_chat/ui/widgets/llm_chat_avatars.dart +148 -0
  578. package/templates/firebase/lib/features/llm_chat/ui/widgets/llm_chat_composer.dart +124 -0
  579. package/templates/firebase/lib/features/local_reminder/providers/reminder_notifier.dart +81 -0
  580. package/templates/firebase/lib/features/local_reminder/repositories/reminder_preferences.dart +76 -0
  581. package/templates/firebase/lib/features/local_reminder/ui/reminder_page.dart +288 -0
  582. package/templates/firebase/lib/features/notifications/api/device_api.dart +433 -0
  583. package/templates/firebase/lib/features/notifications/api/entities/device_entity.dart +62 -0
  584. package/templates/firebase/lib/features/notifications/api/entities/notifications_entity.dart +35 -0
  585. package/templates/firebase/lib/features/notifications/api/local_notifier.dart +409 -0
  586. package/templates/firebase/lib/features/notifications/api/notifications_api.dart +319 -0
  587. package/templates/firebase/lib/features/notifications/providers/models/device.dart +27 -0
  588. package/templates/firebase/lib/features/notifications/providers/models/notification.dart +196 -0
  589. package/templates/firebase/lib/features/notifications/providers/models/notification_list.dart +38 -0
  590. package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +120 -0
  591. package/templates/firebase/lib/features/notifications/repositories/background_notification_handler.dart +13 -0
  592. package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +137 -0
  593. package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +214 -0
  594. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +75 -0
  595. package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +144 -0
  596. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +169 -0
  597. package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +46 -0
  598. package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +32 -0
  599. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +106 -0
  600. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +202 -0
  601. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +84 -0
  602. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +73 -0
  603. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +362 -0
  604. package/templates/firebase/lib/features/onboarding/api/entities/user_info_entity.dart +24 -0
  605. package/templates/firebase/lib/features/onboarding/api/user_infos_api.dart +71 -0
  606. package/templates/firebase/lib/features/onboarding/models/user_info.dart +92 -0
  607. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +15 -0
  608. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +78 -0
  609. package/templates/firebase/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
  610. package/templates/firebase/lib/features/onboarding/ui/animations/page_transitions.dart +33 -0
  611. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
  612. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +76 -0
  613. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +92 -0
  614. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +76 -0
  615. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +89 -0
  616. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +108 -0
  617. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +80 -0
  618. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +145 -0
  619. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +125 -0
  620. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +84 -0
  621. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
  622. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +44 -0
  623. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +77 -0
  624. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +392 -0
  625. package/templates/firebase/lib/features/settings/settings_page.dart +460 -0
  626. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +301 -0
  627. package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +38 -0
  628. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +53 -0
  629. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +22 -0
  630. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +366 -0
  631. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notifier.dart +40 -0
  632. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +430 -0
  633. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +28 -0
  634. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +163 -0
  635. package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +59 -0
  636. package/templates/firebase/lib/features/settings/ui/widgets/avatar_utils.dart +51 -0
  637. package/templates/firebase/lib/features/settings/ui/widgets/round_progress.dart +151 -0
  638. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +52 -0
  639. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +163 -0
  640. package/templates/firebase/lib/features/subscription/api/entities/subscription_entity.dart +40 -0
  641. package/templates/firebase/lib/features/subscription/api/inapp_subscription_api.dart +389 -0
  642. package/templates/firebase/lib/features/subscription/api/subscription_api.dart +39 -0
  643. package/templates/firebase/lib/features/subscription/providers/models/premium_state.dart +35 -0
  644. package/templates/firebase/lib/features/subscription/providers/premium_page_provider.dart +259 -0
  645. package/templates/firebase/lib/features/subscription/repositories/subscription_repository.dart +177 -0
  646. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +85 -0
  647. package/templates/firebase/lib/features/subscription/ui/component/active_premium_content.dart +171 -0
  648. package/templates/firebase/lib/features/subscription/ui/component/paywall_minimal.dart +146 -0
  649. package/templates/firebase/lib/features/subscription/ui/component/paywall_row.dart +332 -0
  650. package/templates/firebase/lib/features/subscription/ui/component/paywall_with_switch.dart +207 -0
  651. package/templates/firebase/lib/features/subscription/ui/component/premium_content.dart +157 -0
  652. package/templates/firebase/lib/features/subscription/ui/component/premium_page_factory.dart +161 -0
  653. package/templates/firebase/lib/features/subscription/ui/premium_page.dart +139 -0
  654. package/templates/firebase/lib/features/subscription/ui/widgets/comparison_table.dart +346 -0
  655. package/templates/firebase/lib/features/subscription/ui/widgets/feature_line.dart +131 -0
  656. package/templates/firebase/lib/features/subscription/ui/widgets/paywall_empty_state.dart +68 -0
  657. package/templates/firebase/lib/features/subscription/ui/widgets/premium_background.dart +63 -0
  658. package/templates/firebase/lib/features/subscription/ui/widgets/premium_background_gradient.dart +53 -0
  659. package/templates/firebase/lib/features/subscription/ui/widgets/premium_banner.dart +80 -0
  660. package/templates/firebase/lib/features/subscription/ui/widgets/premium_bottom_menu.dart +102 -0
  661. package/templates/firebase/lib/features/subscription/ui/widgets/premium_card.dart +26 -0
  662. package/templates/firebase/lib/features/subscription/ui/widgets/premium_close_button.dart +61 -0
  663. package/templates/firebase/lib/features/subscription/ui/widgets/premium_feature.dart +39 -0
  664. package/templates/firebase/lib/features/subscription/ui/widgets/selectable_col.dart +377 -0
  665. package/templates/firebase/lib/features/subscription/ui/widgets/selectable_row.dart +436 -0
  666. package/templates/firebase/lib/features/subscription/ui/widgets/trial_switcher.dart +85 -0
  667. package/templates/firebase/lib/features/subscription/ui/widgets/unsubscribe_feedback_popup.dart +123 -0
  668. package/templates/firebase/lib/firebase_options.dart +75 -0
  669. package/templates/firebase/lib/google_auth_options.dart +10 -0
  670. package/templates/firebase/lib/i18n/app_locale_display.dart +17 -0
  671. package/templates/firebase/lib/i18n/en.i18n.json +514 -0
  672. package/templates/firebase/lib/i18n/es.i18n.json +514 -0
  673. package/templates/firebase/lib/i18n/pt.i18n.json +514 -0
  674. package/templates/firebase/lib/i18n/widgets/locale_code_badge.dart +47 -0
  675. package/templates/firebase/lib/main.dart +263 -0
  676. package/templates/firebase/lib/router.dart +226 -0
  677. package/templates/firebase/login-redesign-preview.png +0 -0
  678. package/templates/firebase/pubspec.yaml +177 -0
  679. package/templates/firebase/scripts/bump-ios-version.js +38 -0
  680. package/templates/firebase/scripts/release-ios.sh +137 -0
  681. package/templates/firebase/slang.yaml +7 -0
  682. package/templates/firebase/storage.rules +12 -0
  683. package/templates/firebase/test/core/data/api/analytics_api_fake.dart +25 -0
  684. package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +45 -0
  685. package/templates/firebase/test/core/data/api/storage_api_fake.dart +28 -0
  686. package/templates/firebase/test/core/data/models/subscription_test.dart +42 -0
  687. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +56 -0
  688. package/templates/firebase/test/core/guards/guard_test.dart +78 -0
  689. package/templates/firebase/test/core/navigation/kasy_page_transition_test.dart +30 -0
  690. package/templates/firebase/test/core/onstart_service_test.dart +18 -0
  691. package/templates/firebase/test/core/rating/rating_api_fake.dart +58 -0
  692. package/templates/firebase/test/core/rating/rating_banner_test.dart +117 -0
  693. package/templates/firebase/test/core/rating/rating_test.dart +60 -0
  694. package/templates/firebase/test/core/security/secured_storage_fake.dart +33 -0
  695. package/templates/firebase/test/core/states/event_dispatcher_test.dart +269 -0
  696. package/templates/firebase/test/core/states/user_state_notifier_test.dart +244 -0
  697. package/templates/firebase/test/core/widgets/responsive_layout_test.dart +50 -0
  698. package/templates/firebase/test/device_test_utils.dart +98 -0
  699. package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +169 -0
  700. package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +86 -0
  701. package/templates/firebase/test/features/authentication/lost_password_page_test.dart +59 -0
  702. package/templates/firebase/test/features/authentication/repositories/authentication_repository_test.dart +88 -0
  703. package/templates/firebase/test/features/authentication/signin_page_test.dart +133 -0
  704. package/templates/firebase/test/features/authentication/signup_page_test.dart +136 -0
  705. package/templates/firebase/test/features/notifications/data/device_api_fake.dart +66 -0
  706. package/templates/firebase/test/features/notifications/data/fake_facebook_api.dart +40 -0
  707. package/templates/firebase/test/features/notifications/data/local_notifier_fake.dart +59 -0
  708. package/templates/firebase/test/features/notifications/data/notifications_api_fake.dart +120 -0
  709. package/templates/firebase/test/features/notifications/data/notifications_settings_fake.dart +34 -0
  710. package/templates/firebase/test/features/notifications/providers/device_repository_test.dart +68 -0
  711. package/templates/firebase/test/features/notifications/providers/notifications_repository_test.dart +74 -0
  712. package/templates/firebase/test/features/notifications/ui/notifications_page_test.dart +69 -0
  713. package/templates/firebase/test/features/subscription/api/fake_inapp_subscription_api.dart +85 -0
  714. package/templates/firebase/test/features/subscription/api/fake_revenuecat_product.dart +186 -0
  715. package/templates/firebase/test/features/subscription/api/fake_subscription_api.dart +11 -0
  716. package/templates/firebase/test/features/subscription/subscription_page_test.dart +119 -0
  717. package/templates/firebase/test/firebase_test_utils.dart +25 -0
  718. package/templates/firebase/test/test_utils.dart +272 -0
  719. package/templates/firebase/web/favicon.png +0 -0
  720. package/templates/firebase/web/firebase-messaging-sw.js +27 -0
  721. package/templates/firebase/web/icons/Icon-192.png +0 -0
  722. package/templates/firebase/web/icons/Icon-512.png +0 -0
  723. package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
  724. package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
  725. package/templates/firebase/web/index.html +110 -0
  726. package/templates/firebase/web/local_notifications.js +31 -0
  727. package/templates/firebase/web/manifest.json +35 -0
  728. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  729. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  730. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  731. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  732. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  733. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  734. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  735. package/templates/firebase/web/splash/img/light-4x.png +0 -0
@@ -0,0 +1,1870 @@
1
+ /**
2
+ * `kasy new` — unified command to create a complete Flutter project.
3
+ *
4
+ * Flow:
5
+ * 1. Language — use stored preference or ask + save
6
+ * 2. Environment checks (non-blocking warnings)
7
+ * 3. Backend selection (Firebase / Supabase / API)
8
+ * 4. Backend tool checks (non-blocking warnings)
9
+ * 5. Collect answers (name, bundle ID, credentials, modules)
10
+ * 6. Run generator (copy → pub get → build_runner → flutterfire → deploy)
11
+ * 7. Show results + next steps
12
+ */
13
+
14
+ const path = require('node:path');
15
+ const crypto = require('node:crypto');
16
+ const kleur = require('kleur');
17
+ const gradient = require('gradient-string');
18
+ const oraPackage = require('ora');
19
+ const ora = oraPackage.default || oraPackage;
20
+
21
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
22
+
23
+ // Spinner manager for multi-step long-running operations.
24
+ // Each call to .next(text) succeeds the previous step and starts a new one.
25
+ // Call .succeed(text) or .fail(text) to close the last step.
26
+ function makeProgressSpinner() {
27
+ let current = null;
28
+ return {
29
+ next(text) {
30
+ if (current) current.succeed();
31
+ current = ora(` ${text}`).start();
32
+ },
33
+ succeed(text) {
34
+ if (current) { current.succeed(text ? ` ${text}` : undefined); current = null; }
35
+ },
36
+ fail(text) {
37
+ if (current) { current.fail(text ? ` ${text}` : undefined); current = null; }
38
+ },
39
+ warn(text) {
40
+ if (current) { current.warn(text ? ` ${text}` : undefined); current = null; }
41
+ },
42
+ stop() {
43
+ if (current) { current.stop(); current = null; }
44
+ },
45
+ };
46
+ }
47
+
48
+ function generateWebhookKey() {
49
+ return 'rc_wh_' + crypto.randomBytes(16).toString('hex');
50
+ }
51
+
52
+
53
+
54
+ async function waitWithCountdown(seconds, label) {
55
+ for (let i = seconds; i > 0; i--) {
56
+ process.stdout.write(`\r ${label} ${i}s… `);
57
+ await sleep(1000);
58
+ }
59
+ process.stdout.write(`\r ${label} pronto! \n`);
60
+ }
61
+ const prompts = require('prompts');
62
+ const fs = require('fs-extra');
63
+ const { createTranslator } = require('../utils/i18n');
64
+ const { getStoredLanguage, setStoredLanguage } = require('../utils/license');
65
+ const { promptLanguage } = require('../utils/prompts');
66
+ const {
67
+ getBaseChecks,
68
+ getPlatformChecks,
69
+ getBackendChecks,
70
+ runChecks,
71
+ hasRequiredFailures,
72
+ } = require('../utils/checks');
73
+ const { normalizeBackend, getVisibleFeatures } = require('../scaffold/catalog');
74
+
75
+ // Audience gate: set KASY_INTERNAL=1 to reveal beta/internal features.
76
+ const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
77
+ const { generateFirebaseProject } = require('../scaffold/backends/firebase/generator');
78
+ const { generateSupabaseProject } = require('../scaffold/backends/supabase/generator');
79
+ const { generateApiProject } = require('../scaffold/backends/api/generator');
80
+ const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys } = require('../scaffold/backends/supabase/deploy');
81
+ const { writeSupabaseGoogleAuthOptions, readSupabaseGoogleCredentials, getGoogleClientSecretViaGcloud } = require('../scaffold/shared/post-build');
82
+ const { toPackageName } = require('../scaffold/backends/firebase/tokens');
83
+ const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getGcloudInstallInstructions, enableAuthProviders, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
84
+ const { createFcmServiceAccountKey } = require('../scaffold/shared/fcm-service-account');
85
+
86
+ // Região padrão para criação de projetos Supabase via API.
87
+ // Supabase regions: https://supabase.com/docs/guides/platform/regions
88
+ const DEFAULT_SUPABASE_REGION = 'sa-east-1'; // São Paulo
89
+
90
+ const BILLING_OTHER = '__other__';
91
+
92
+ /**
93
+ * Prompts user to select a billing account. Always shows when 1+ accounts exist.
94
+ * Includes "Other - enter ID manually" so user can use another account (e.g. when default has quota exceeded).
95
+ * @param {Function} tr - Translator
96
+ * @param {Function} onCancel - Cancel handler
97
+ * @returns {Promise<string|null>} Selected billing account ID, or null to use default (first)
98
+ */
99
+ async function promptBillingAccountIfNeeded(tr, onCancel) {
100
+ const billingList = await listBillingAccounts();
101
+ if (!billingList.ok) return null;
102
+ if (!billingList.accounts?.length) {
103
+ console.log(kleur.yellow(` ${tr('new.firebase.q.billingAccount.context')}`));
104
+ const { manualId } = await prompts(
105
+ {
106
+ type: 'text',
107
+ name: 'manualId',
108
+ message: tr('new.firebase.q.billingAccount.manualId'),
109
+ hint: tr('new.firebase.q.billingAccount.manualId.hint'),
110
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.billingAccount.manualId.required')),
111
+ },
112
+ { onCancel }
113
+ );
114
+ return manualId?.trim() || null;
115
+ }
116
+ const choices = [
117
+ ...billingList.accounts.map((a) => ({
118
+ title: `${a.name || a.id} (${a.id})`,
119
+ value: a.id,
120
+ })),
121
+ { title: tr('new.firebase.q.billingAccount.other'), value: BILLING_OTHER },
122
+ ];
123
+ console.log(kleur.yellow(` ${tr('new.firebase.q.billingAccount.context')}`));
124
+ const { billingAccountId } = await prompts(
125
+ {
126
+ type: 'select',
127
+ name: 'billingAccountId',
128
+ message: tr('new.firebase.q.billingAccount'),
129
+ hint: tr('new.firebase.q.billingAccount.hint'),
130
+ choices,
131
+ },
132
+ { onCancel }
133
+ );
134
+ if (billingAccountId === BILLING_OTHER) {
135
+ const { manualId } = await prompts(
136
+ {
137
+ type: 'text',
138
+ name: 'manualId',
139
+ message: tr('new.firebase.q.billingAccount.manualId'),
140
+ hint: tr('new.firebase.q.billingAccount.manualId.hint'),
141
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.billingAccount.manualId.required')),
142
+ },
143
+ { onCancel }
144
+ );
145
+ return manualId?.trim() || null;
146
+ }
147
+ return billingAccountId;
148
+ }
149
+
150
+ /**
151
+ * Prompts user to select a GCP organization. Only shows when the account has ≥1 organization.
152
+ * Personal accounts (no orgs) silently skip this prompt.
153
+ * @param {Function} tr - Translator
154
+ * @param {Function} onCancel - Cancel handler
155
+ * @returns {Promise<string|null>} Selected organization ID, or null (no organization / personal account)
156
+ */
157
+ async function promptOrganizationIfNeeded(tr, onCancel) {
158
+ const orgList = await listGcpOrganizations();
159
+ if (!orgList.ok || !orgList.organizations?.length) return null;
160
+ const choices = [
161
+ ...orgList.organizations.map((o) => ({
162
+ title: `${o.name} (${o.id})`,
163
+ value: o.id,
164
+ })),
165
+ { title: tr('new.firebase.q.organization.none'), value: null },
166
+ ];
167
+ const { organizationId } = await prompts(
168
+ {
169
+ type: 'select',
170
+ name: 'organizationId',
171
+ message: tr('new.firebase.q.organization'),
172
+ hint: tr('new.firebase.q.organization.hint'),
173
+ choices,
174
+ },
175
+ { onCancel }
176
+ );
177
+ return organizationId || null;
178
+ }
179
+
180
+ // ── Helpers ───────────────────────────────────────────────────────────────────
181
+
182
+ function printBanner(tr) {
183
+ const bar = kleur.gray('─────────────────────────────────────────────────');
184
+ const logo = [
185
+ ' ██╗ ██╗ █████╗ ███████╗██╗ ██╗',
186
+ ' ██║ ██╔╝██╔══██╗██╔════╝╚██╗ ██╔╝',
187
+ ' █████╔╝ ███████║███████╗ ╚████╔╝ ',
188
+ ' ██╔═██╗ ██╔══██║╚════██║ ╚██╔╝ ',
189
+ ' ██║ ██╗██║ ██║███████║ ██║ ',
190
+ ' ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ',
191
+ ]
192
+ .map((line) => gradient(['#a78bfa', '#60a5fa'])(line))
193
+ .join('\n');
194
+ const title = kleur.bold().white(` ${tr('new.banner')}`);
195
+ console.log(`\n${bar}`);
196
+ console.log(logo);
197
+ console.log('');
198
+ console.log(title);
199
+ console.log(kleur.dim(` ${tr('new.subtitle2')}`));
200
+ console.log(`${bar}\n`);
201
+ }
202
+
203
+ function printPrerequisites(tr, backend, firebaseSetupMode = 'existing', checkResults = []) {
204
+ const gcloudOk = checkResults.every(
205
+ (r) => !r.name?.includes('gcloud') || r.ok
206
+ );
207
+ console.log(kleur.bold().yellow(` ${tr('new.prereq.title')}`));
208
+ const firebaseCreate = firebaseSetupMode === 'create';
209
+ if (backend === 'firebase') {
210
+ if (firebaseCreate) {
211
+ if (!gcloudOk) {
212
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.1')}`));
213
+ }
214
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.2')}`));
215
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.projectQuota')}`));
216
+ console.log(kleur.cyan(` ${tr('new.firebase.prereq.create.note')}`));
217
+ } else {
218
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.1')}`));
219
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.2')}`));
220
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.3')}`));
221
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.4')}`));
222
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.5')}`));
223
+ console.log(kleur.cyan(` ${tr('new.firebase.prereq.doc')}`));
224
+ }
225
+ } else if (backend === 'supabase') {
226
+ if (firebaseCreate) {
227
+ if (!gcloudOk) {
228
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.1')}`));
229
+ }
230
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.2')}`));
231
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.projectQuota')}`));
232
+ console.log(kleur.cyan(` ${tr('new.firebase.prereq.create.pushNote')}`));
233
+ console.log(kleur.gray(` ${tr('new.supabase.prereq.1')}`));
234
+ console.log(kleur.gray(` ${tr('new.supabase.prereq.2')}`));
235
+ console.log(kleur.cyan(` ${tr('new.supabase.prereq.login')}`));
236
+ } else {
237
+ console.log(kleur.gray(` ${tr('new.supabase.prereq.1')}`));
238
+ console.log(kleur.gray(` ${tr('new.supabase.prereq.2')}`));
239
+ console.log(kleur.gray(` ${tr('new.supabase.prereq.3')}`));
240
+ console.log(kleur.cyan(` ${tr('new.supabase.prereq.login')}`));
241
+ }
242
+ } else if (backend === 'api') {
243
+ if (firebaseCreate) {
244
+ if (!gcloudOk) {
245
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.1')}`));
246
+ }
247
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.2')}`));
248
+ console.log(kleur.gray(` ${tr('new.firebase.prereq.create.projectQuota')}`));
249
+ console.log(kleur.cyan(` ${tr('new.firebase.prereq.create.pushNote')}`));
250
+ console.log(kleur.gray(` ${tr('new.api.prereq.1')}`));
251
+ console.log(kleur.gray(` ${tr('new.api.prereq.2')}`));
252
+ } else {
253
+ console.log(kleur.gray(` ${tr('new.api.prereq.1')}`));
254
+ console.log(kleur.gray(` ${tr('new.api.prereq.2')}`));
255
+ console.log(kleur.gray(` ${tr('new.api.prereq.3')}`));
256
+ }
257
+ }
258
+ console.log('');
259
+ }
260
+
261
+ function printSummary(tr, answers) {
262
+ const modules = answers.modules.length > 0
263
+ ? answers.modules.join(', ')
264
+ : tr('new.firebase.confirm.none');
265
+
266
+ const backendLabel = { firebase: '🔥 Firebase', supabase: '🟢 Supabase', api: '🔗 API REST' }[answers.backend] || answers.backend;
267
+ const bar = kleur.gray(' ─────────────────────────────────────────────');
268
+
269
+ console.log(`\n${bar}`);
270
+ console.log(kleur.bold(` 📦 ${tr('new.firebase.confirm.title')}`));
271
+ console.log(bar);
272
+ console.log(` ${kleur.dim(tr('new.firebase.confirm.app') + ':')} ${kleur.white(answers.appName)}`);
273
+ console.log(` ${kleur.dim('Bundle:')} ${kleur.white(answers.bundleId)}`);
274
+ console.log(` ${kleur.dim('Backend:')} ${kleur.white(backendLabel)}`);
275
+ if (answers.firebaseProjectId) {
276
+ console.log(` ${kleur.dim('Firebase:')} ${kleur.white(answers.firebaseProjectId)}`);
277
+ }
278
+ if (answers.supabaseUrl) {
279
+ console.log(` ${kleur.dim('Supabase URL:')} ${kleur.white(answers.supabaseUrl)}`);
280
+ }
281
+ if (answers.apiBaseUrl) {
282
+ console.log(` ${kleur.dim('API URL:')} ${kleur.white(answers.apiBaseUrl)}`);
283
+ }
284
+ console.log(` ${kleur.dim(tr('new.firebase.confirm.modules') + ':')} ${kleur.white(modules)}`);
285
+ console.log(bar);
286
+ }
287
+
288
+ const STEP_LABELS = {
289
+ 'project-setup': { en: 'Project configured', pt: 'Projeto configurado', es: 'Proyecto configurado' },
290
+ 'pub-get': { en: 'Packages installed', pt: 'Pacotes instalados', es: 'Paquetes instalados' },
291
+ 'slang': { en: 'Translations generated', pt: 'Traducoes geradas', es: 'Traducciones generadas' },
292
+ 'build-runner': { en: 'Code generated (Riverpod / Freezed)', pt: 'Codigo gerado (Riverpod / Freezed)', es: 'Codigo generado (Riverpod / Freezed)' },
293
+ 'flutterfire': { en: 'Firebase configured (flutterfire)', pt: 'Firebase configurado (flutterfire)', es: 'Firebase configurado (flutterfire)' },
294
+ 'Service Account key': { en: 'Service Account key', pt: 'Chave de servico', es: 'Clave de servicio' },
295
+ 'firebase_key.json': { en: 'Service account key copied', pt: 'Chave de conta de servico copiada', es: 'Clave de cuenta de servicio copiada' },
296
+ 'npm install (functions)': { en: 'Functions dependencies installed', pt: 'Dependencias das Functions instaladas', es: 'Dependencias de Functions instaladas' },
297
+ 'firebase deploy (Functions + Firestore rules + Storage)': {
298
+ en: 'Backend deployed (Functions + rules)',
299
+ pt: 'Backend publicado (Functions + regras)',
300
+ es: 'Backend desplegado (Functions + reglas)',
301
+ },
302
+ 'firebase-messaging-sw': {
303
+ en: 'Web: firebase-messaging-sw.js configured',
304
+ pt: 'Web: firebase-messaging-sw.js configurado',
305
+ es: 'Web: firebase-messaging-sw.js configurado',
306
+ },
307
+ 'supabase init': { en: 'Supabase init', pt: 'Supabase init', es: 'Supabase init' },
308
+ 'supabase link': { en: 'Supabase linked', pt: 'Supabase vinculado', es: 'Supabase vinculado' },
309
+ 'supabase db push': { en: 'Database migrated', pt: 'Banco migrado', es: 'Base de datos migrada' },
310
+ 'anonymous sign-in': { en: 'Anonymous sign-in enabled', pt: 'Login anonimo ativado', es: 'Inicio de sesion anonimo activado' },
311
+ 'google sign-in': { en: 'Google Sign-In enabled', pt: 'Google Sign-In ativado', es: 'Google Sign-In activado' },
312
+ 'google-auth-options': { en: 'Google client IDs written', pt: 'Client IDs do Google gravados', es: 'Client IDs de Google escritos' },
313
+ 'ios-url-scheme': { en: 'iOS URL scheme registered', pt: 'URL scheme iOS registrado', es: 'URL scheme iOS registrado' },
314
+ 'google-ios-url-scheme': { en: 'iOS Google URL scheme registered', pt: 'URL scheme Google iOS registrado', es: 'URL scheme Google iOS registrado' },
315
+ 'deploy revenuecat-webhook': { en: 'RevenueCat webhook deployed', pt: 'Webhook RevenueCat publicado', es: 'Webhook RevenueCat desplegado' },
316
+ 'secret REVENUECAT_WEBHOOK_KEY': { en: 'RevenueCat webhook secret', pt: 'Secret webhook RevenueCat', es: 'Secret webhook RevenueCat' },
317
+ 'secret META_ACCESS_TOKEN': { en: 'Meta Access Token', pt: 'Meta Access Token', es: 'Meta Access Token' },
318
+ 'secret META_DATASET_ID': { en: 'Meta Dataset ID', pt: 'Meta Dataset ID', es: 'Meta Dataset ID' },
319
+ 'fcm-key': { en: 'FCM Service Account key generated', pt: 'Chave FCM gerada automaticamente', es: 'Clave FCM generada automáticamente' },
320
+ 'fcm-key-saved': { en: 'FCM key saved to .kasy/', pt: 'Chave FCM salva em .kasy/', es: 'Clave FCM guardada en .kasy/' },
321
+ 'secret FIREBASE_SERVICE_ACCOUNT_JSON': { en: 'FCM Service Account configured', pt: 'Service Account FCM configurado', es: 'Service Account FCM configurado' },
322
+ 'gcp-project': { en: 'GCP project created', pt: 'Projeto GCP criado', es: 'Proyecto GCP creado' },
323
+ 'billing': { en: 'Billing account linked (Blaze)', pt: 'Conta de faturamento vinculada (Blaze)', es: 'Cuenta de facturación vinculada (Blaze)' },
324
+ 'add-firebase': { en: 'Firebase added to project', pt: 'Firebase adicionado ao projeto', es: 'Firebase añadido al proyecto' },
325
+ 'enable-apis': { en: 'APIs enabled', pt: 'APIs ativadas', es: 'APIs habilitadas' },
326
+ 'android-app': { en: 'Android app registered', pt: 'App Android registrado', es: 'App Android registrado' },
327
+ 'ios-app': { en: 'iOS app registered', pt: 'App iOS registrado', es: 'App iOS registrado' },
328
+ 'web-app': { en: 'Web app registered', pt: 'App Web registrado', es: 'App Web registrado' },
329
+ 'sha1': { en: 'SHA-1 added for Google Sign-In', pt: 'SHA-1 adicionado para Google Sign-In', es: 'SHA-1 añadido para Google Sign-In' },
330
+ 'service-account': { en: 'Service account key created', pt: 'Chave de conta de servico criada', es: 'Clave de cuenta de servicio creada' },
331
+ 'firestore': { en: 'Firestore database created', pt: 'Banco Firestore criado', es: 'Base de datos Firestore creada' },
332
+ 'storage': { en: 'Firebase Storage bucket created', pt: 'Bucket Firebase Storage criado', es: 'Bucket Firebase Storage creado' },
333
+ };
334
+
335
+ const STEP_PROGRESS = {
336
+ 'project-setup': { en: 'Configuring your project…', pt: 'Configurando seu projeto…', es: 'Configurando tu proyecto…' },
337
+ 'pub-get': { en: 'Installing packages…', pt: 'Instalando pacotes…', es: 'Instalando paquetes…' },
338
+ 'slang': { en: 'Generating translations…', pt: 'Gerando traducoes…', es: 'Generando traducciones…' },
339
+ 'build-runner': { en: 'Generating code (Riverpod / Freezed)…', pt: 'Gerando codigo (pode demorar)…', es: 'Generando codigo (puede tardar)…' },
340
+ 'flutterfire': { en: 'Connecting to Firebase…', pt: 'Conectando ao Firebase…', es: 'Conectando a Firebase…' },
341
+ 'deploy': { en: 'Deploying backend to Firebase…', pt: 'Publicando backend no Firebase…', es: 'Desplegando backend en Firebase…' },
342
+ 'gcp-project': { en: 'Creating GCP project…', pt: 'Criando projeto GCP…', es: 'Creando proyecto GCP…' },
343
+ 'billing': { en: 'Linking billing account (Blaze)…', pt: 'Vinculando conta de faturamento (Blaze)…', es: 'Vinculando cuenta de facturación (Blaze)…' },
344
+ 'add-firebase': { en: 'Adding Firebase…', pt: 'Adicionando Firebase…', es: 'Añadiendo Firebase…' },
345
+ 'enable-apis': { en: 'Enabling APIs…', pt: 'Ativando APIs…', es: 'Habilitando APIs…' },
346
+ 'android-app': { en: 'Registering Android app…', pt: 'Registrando app Android…', es: 'Registrando app Android…' },
347
+ 'ios-app': { en: 'Registering iOS app…', pt: 'Registrando app iOS…', es: 'Registrando app iOS…' },
348
+ 'web-app': { en: 'Registering Web app…', pt: 'Registrando app Web…', es: 'Registrando app Web…' },
349
+ 'sha1': { en: 'Adding SHA-1 for Google Sign-In…', pt: 'Adicionando SHA-1 para Google Sign-In…', es: 'Añadiendo SHA-1 para Google Sign-In…' },
350
+ 'service-account': { en: 'Creating service account key…', pt: 'Criando chave de conta de servico…', es: 'Creando clave de cuenta de servicio…' },
351
+ 'firestore': { en: 'Creating Firestore database…', pt: 'Criando banco Firestore…', es: 'Creando base de datos Firestore…' },
352
+ 'storage': { en: 'Creating Firebase Storage bucket…', pt: 'Criando bucket Firebase Storage…', es: 'Creando bucket Firebase Storage…' },
353
+ 'deploy-retry-wait': { en: 'Waiting 4 min for GCP permissions to propagate… (do not close terminal)', pt: 'Aguardando 4 min para permissões do GCP propagarem… (não feche o terminal)', es: 'Esperando 4 min para propagar permisos de GCP… (no cierres la terminal)' },
354
+ 'deploy-retry-wait-2': { en: 'Retrying with updated permissions, 2 more min… (almost there!)', pt: 'Tentando novamente com permissões atualizadas, mais 2 min… (quase lá!)', es: 'Reintentando con permisos actualizados, 2 min más… (¡casi listo!)' },
355
+ };
356
+
357
+ function stepLabel(key, lang) {
358
+ const map = STEP_LABELS[key];
359
+ return map ? (map[lang] || map.en || key) : key;
360
+ }
361
+
362
+ function stepProgress(key, lang) {
363
+ const map = STEP_PROGRESS[key];
364
+ return map ? (map[lang] || map.en || key) : key;
365
+ }
366
+
367
+ /**
368
+ * Print the post-setup status block for a create-from-scratch result.
369
+ * Shows SHA-1, Firestore, Storage status with URLs for manual action when needed.
370
+ */
371
+ function printCreateFromScratchStatus(result, tr) {
372
+ if (result.sha1Skipped) {
373
+ const err = (result.sha1Error || '').replace(/\s+/g, ' ').slice(0, 120);
374
+ if (result.sha1Skipped === 'api_failed') {
375
+ console.log(kleur.yellow(` ⚠ SHA-1 não adicionado automaticamente. Motivo: ${err}`));
376
+ } else {
377
+ console.log(kleur.yellow(` ⚠ SHA-1 não adicionado: ${result.sha1Skipped}`));
378
+ }
379
+ console.log(kleur.yellow(` Adicione manualmente: Firebase Console → Configurações do projeto → Seus apps → Android → Adicionar impressão digital`));
380
+ if (result.sha1ManualUrl) console.log(kleur.cyan(` ${result.sha1ManualUrl}`));
381
+ } else {
382
+ console.log(kleur.green(` ✓ SHA-1 adicionado (Google Sign-In)`));
383
+ }
384
+
385
+ if (result.firestoreCreated) {
386
+ console.log(kleur.green(` ✓ Firestore criado automaticamente`));
387
+ } else {
388
+ if (result.firestoreError) console.log(kleur.yellow(` ⚠ Firestore não criado. Motivo: ${(result.firestoreError || '').slice(0, 100)}`));
389
+ else console.log(kleur.yellow(` ⚠ Firestore não criado automaticamente`));
390
+ console.log(kleur.yellow(` Ative manualmente no console:`));
391
+ console.log(kleur.cyan(` ${result.firestoreUrl}`));
392
+ }
393
+
394
+ if (result.storageCreated) {
395
+ console.log(kleur.green(` ✓ Firebase Storage criado automaticamente`));
396
+ } else {
397
+ if (result.storageError) console.log(kleur.yellow(` ⚠ Storage não criado. Motivo: ${(result.storageError || '').slice(0, 100)}`));
398
+ else console.log(kleur.yellow(` ⚠ Storage não criado automaticamente`));
399
+ console.log(kleur.yellow(` Ative manualmente no console:`));
400
+ console.log(kleur.cyan(` ${result.storageUrl}`));
401
+ }
402
+ }
403
+
404
+ function printSuccessCard(tr, answers, targetDir) {
405
+ const bar = kleur.gray(' ─────────────────────────────────────────────');
406
+ const folderName = path.basename(targetDir);
407
+ const consoleUrl = answers.backend === 'firebase' && answers.firebaseProjectId
408
+ ? `https://console.firebase.google.com/project/${answers.firebaseProjectId}`
409
+ : answers.backend === 'supabase'
410
+ ? 'https://supabase.com/dashboard'
411
+ : answers.apiBaseUrl || null;
412
+
413
+ console.log(`\n${bar}`);
414
+ console.log(kleur.bold(gradient(['#a78bfa', '#60a5fa'])(` 🎉 ${tr('new.success.title')}`)));
415
+ console.log(bar);
416
+ console.log('');
417
+ console.log(` ${kleur.bold(tr('new.success.nextSteps'))}`);
418
+ console.log('');
419
+ console.log(` ${kleur.dim('1.')} ${kleur.dim(tr('new.success.step.cd'))}`);
420
+ console.log(` ${kleur.cyan(`cd ${folderName}`)}`);
421
+ console.log('');
422
+ console.log(` ${kleur.dim('2.')} ${kleur.dim(tr('new.success.step.run'))}`);
423
+ console.log(` ${kleur.cyan('kasy run')}`);
424
+ console.log(` ${kleur.dim('(ou F5 no VS Code)')}`);
425
+ if (consoleUrl) {
426
+ console.log('');
427
+ console.log(` ${kleur.dim('3.')} ${kleur.dim(tr('new.success.step.console'))}`);
428
+ console.log(` ${kleur.cyan(consoleUrl)}`);
429
+ }
430
+ console.log('');
431
+ console.log(bar);
432
+ console.log('');
433
+ }
434
+
435
+ function printStepResult(step, lang = 'pt') {
436
+ if (step.skipped) {
437
+ const label = stepLabel(step.name, lang);
438
+ console.log(` ${kleur.gray('⏭')} ${kleur.gray(label)} ${kleur.gray('(skipped)')}`);
439
+ return;
440
+ }
441
+ const icon = step.ok ? kleur.green('✓') : kleur.red('✗');
442
+ const label = step.ok
443
+ ? kleur.white(stepLabel(step.name, lang))
444
+ : kleur.red(stepLabel(step.name, lang));
445
+ console.log(` ${icon} ${label}${step.detail ? kleur.gray(` — ${step.detail.split('\n')[0]}`) : ''}`);
446
+ }
447
+
448
+ function onCancel(tr) {
449
+ console.log(kleur.yellow(`\n ${tr('new.firebase.error.aborted')}\n`));
450
+ process.exit(0);
451
+ }
452
+
453
+ async function promptSupabaseManual(tr, cancel) {
454
+ return prompts(
455
+ [
456
+ {
457
+ type: 'text',
458
+ name: 'supabaseUrl',
459
+ message: tr('prompt.supabase.url.enter'),
460
+ hint: 'https://xxxx.supabase.co',
461
+ validate: (v) => (v && v.trim() ? true : tr('prompt.supabase.url.required')),
462
+ },
463
+ {
464
+ type: 'text',
465
+ name: 'supabaseAnonKey',
466
+ message: tr('prompt.supabase.anonKey.enter'),
467
+ validate: (v) => (v && v.trim() ? true : tr('prompt.supabase.anonKey.required')),
468
+ },
469
+ ],
470
+ { onCancel: cancel }
471
+ );
472
+ }
473
+
474
+ // ── Module presets ────────────────────────────────────────────────────────────
475
+
476
+ function buildPresets(audience) {
477
+ const catalog = getVisibleFeatures({ audience });
478
+ const named = ['starter', 'saas', 'content', 'full'];
479
+ const result = { none: [], custom: null };
480
+ for (const name of named) {
481
+ result[name] = catalog.filter((f) => f.defaultInPresets.includes(name)).map((f) => f.id);
482
+ }
483
+ return result;
484
+ }
485
+
486
+ const MODULE_PRESETS = buildPresets(KASY_AUDIENCE);
487
+
488
+ // ── Main wizard ───────────────────────────────────────────────────────────────
489
+
490
+ /**
491
+ * @param {string} directory - target directory (relative or absolute)
492
+ * @param {object} opts
493
+ * @param {string} [opts.language] - language hint ('pt' | 'en' | 'es'). Uses stored preference if omitted.
494
+ * @param {string} [opts.backend] - backend hint ('firebase' | 'supabase' | 'api'). Skips selection prompt if valid.
495
+ * @param {string} [opts.withModules] - comma-separated modules to pre-select (e.g. 'revenuecat,sentry,ci').
496
+ */
497
+ async function runNew(directory, { language: langHint = null, backend: backendHint = null, withModules = null, yes = false, project: projectHint = null } = {}) {
498
+ // ── 1. Language — use stored preference, or ask once and save ───────────
499
+ const storedLang = getStoredLanguage();
500
+ let language = storedLang;
501
+ if (!language) {
502
+ language = await promptLanguage(langHint);
503
+ setStoredLanguage(language);
504
+ }
505
+
506
+ const tr = createTranslator(language);
507
+ const cancel = () => onCancel(tr);
508
+
509
+ // ── 2. Environment checks (non-blocking — only warnings) ────────────────
510
+ const envChecks = [...getBaseChecks(), ...getPlatformChecks()];
511
+ await runChecks(envChecks, tr('new.checks.environment'), {
512
+ t: tr,
513
+ compact: true,
514
+ spinnerLabel: tr('new.checks.environment.checking'),
515
+ doneLabel: tr('new.checks.environment.done'),
516
+ });
517
+
518
+ printBanner(tr);
519
+
520
+ // Whether an explicit target directory was provided by the user
521
+ const hasExplicitDir = directory && directory !== '.';
522
+
523
+ // If no directory was given, we'll determine targetDir AFTER asking the app name
524
+ let targetDir = hasExplicitDir
525
+ ? path.resolve(process.cwd(), directory)
526
+ : null;
527
+
528
+ // Guard for explicit dir: must be empty or not exist
529
+ if (targetDir && await fs.pathExists(targetDir)) {
530
+ const contents = await fs.readdir(targetDir);
531
+ if (contents.length > 0) {
532
+ throw new Error(tr('new.firebase.error.dirNotEmpty', { path: targetDir }));
533
+ }
534
+ }
535
+
536
+ // ── 3. Backend selection (support pre-selection via --backend flag) ─────
537
+ let backend = normalizeBackend(backendHint);
538
+ if (!backend) {
539
+ const { backend: selectedBackend } = await prompts(
540
+ {
541
+ type: 'select',
542
+ name: 'backend',
543
+ message: tr('new.q.backend'),
544
+ choices: [
545
+ { title: '🔥 Firebase', description: tr('new.q.backend.firebase.desc'), value: 'firebase' },
546
+ { title: '🟢 Supabase', description: tr('new.q.backend.supabase.desc'), value: 'supabase' },
547
+ { title: '🔗 API REST', description: tr('new.q.backend.api.desc'), value: 'api' },
548
+ ],
549
+ initial: 0,
550
+ },
551
+ { onCancel: cancel }
552
+ );
553
+ backend = selectedBackend;
554
+ } else {
555
+ const backendLabel = { firebase: '🔥 Firebase', supabase: '🟢 Supabase', api: '🔗 API REST' }[backend] || backend;
556
+ console.log(kleur.gray(` Backend: ${kleur.white(backendLabel)}`));
557
+ }
558
+
559
+ // ── 3b. Backend tool checks (required — blocks if missing) ───────────────
560
+ const backendCheckKey = backend === 'api' ? 'restApi' : backend;
561
+ const backendChecks = getBackendChecks(backendCheckKey);
562
+ let backendCheckResults = [];
563
+ if (backendChecks.length > 0) {
564
+ if (backend === 'supabase' || backend === 'api') {
565
+ console.log(kleur.dim(`\n ℹ ${tr('new.checks.firebaseForPush')}`));
566
+ }
567
+ const backendLabel = tr('setup.checks.backend', { backend }) || ` Checking ${backend} tools…`;
568
+ backendCheckResults = await runChecks(backendChecks, backendLabel, {
569
+ t: tr,
570
+ compact: true,
571
+ spinnerLabel: tr('setup.checks.backend.checking', { backend }) || `Checking ${backend} tools`,
572
+ doneLabel: tr('setup.checks.backend.done', { backend }) || `${backend} tools ready`,
573
+ });
574
+ if (hasRequiredFailures(backendCheckResults)) {
575
+ console.log(kleur.red(`\n ✖ ${tr('new.checks.requiredBlock')}`));
576
+ console.log(kleur.dim(`\n ${tr('new.checks.installFirebase')}`));
577
+ if (backend === 'supabase') {
578
+ const platform = process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'win32' : 'linux';
579
+ const supabaseKey = `new.checks.installSupabase.${platform}`;
580
+ console.log(kleur.dim(` ${tr(supabaseKey) || tr('new.checks.installSupabase')}`));
581
+ }
582
+ console.log('');
583
+ process.exit(1);
584
+ }
585
+ }
586
+
587
+ // ── 3c. Wizard mode — Quick (few questions) or Full (all options) ──────────
588
+ let isQuick = yes; // --yes implies Quick mode
589
+ if (!yes) {
590
+ const { wizardMode } = await prompts(
591
+ {
592
+ type: 'select',
593
+ name: 'wizardMode',
594
+ message: tr('new.q.mode'),
595
+ choices: [
596
+ { title: tr('new.q.mode.quick'), value: 'quick' },
597
+ { title: tr('new.q.mode.advanced'), value: 'advanced' },
598
+ ],
599
+ initial: 0,
600
+ },
601
+ { onCancel: cancel }
602
+ );
603
+ isQuick = wizardMode === 'quick';
604
+ }
605
+
606
+ // ── 4b. Firebase setup mode (create vs existing) — ask first so prereqs are correct ─
607
+ // Firebase backend: full setup. Supabase/API: Firebase only for push notifications (FCM).
608
+ let firebaseSetupMode = 'existing';
609
+ if (!yes) {
610
+ if (backend === 'firebase') {
611
+ const { setupMode } = await prompts(
612
+ {
613
+ type: 'select',
614
+ name: 'setupMode',
615
+ message: tr('new.firebase.q.setupMode'),
616
+ choices: [
617
+ { title: tr('new.firebase.q.setupMode.create'), value: 'create' },
618
+ { title: tr('new.firebase.q.setupMode.existing'), value: 'existing' },
619
+ ],
620
+ initial: 0,
621
+ },
622
+ { onCancel: cancel }
623
+ );
624
+ firebaseSetupMode = setupMode;
625
+ } else if (backend === 'supabase' || backend === 'api') {
626
+ const { setupMode } = await prompts(
627
+ {
628
+ type: 'select',
629
+ name: 'setupMode',
630
+ message: tr('new.firebase.q.setupMode.push'),
631
+ choices: [
632
+ { title: tr('new.firebase.q.setupMode.create'), value: 'create' },
633
+ { title: tr('new.firebase.q.setupMode.existing'), value: 'existing' },
634
+ ],
635
+ initial: 0,
636
+ },
637
+ { onCancel: cancel }
638
+ );
639
+ firebaseSetupMode = setupMode;
640
+ }
641
+ }
642
+
643
+ // ── 4c. Backend-specific prerequisites (dynamic: only show what's not yet verified) ─
644
+ printPrerequisites(tr, backend, firebaseSetupMode, backendCheckResults);
645
+
646
+ // ── Firebase region — Quick mode uses default (us-central1) ──────────
647
+ let firebaseRegion = 'us-central1';
648
+ if (backend === 'firebase' && !isQuick) {
649
+ const { region } = await prompts(
650
+ {
651
+ type: 'select',
652
+ name: 'region',
653
+ message: tr('new.firebase.q.region'),
654
+ choices: [
655
+ { title: tr('new.firebase.q.region.us'), value: 'us-central1' },
656
+ { title: tr('new.firebase.q.region.europe'), value: 'europe-west1' },
657
+ { title: tr('new.firebase.q.region.brazil'), value: 'southamerica-east1' },
658
+ ],
659
+ initial: 0,
660
+ },
661
+ { onCancel: cancel }
662
+ );
663
+ firebaseRegion = region || 'us-central1';
664
+ }
665
+
666
+ // ── Core questions (appName, bundleId) ────────────────────────────────────
667
+ const coreQuestions = [
668
+ {
669
+ type: 'text',
670
+ name: 'appName',
671
+ message: tr('new.firebase.q.appName'),
672
+ hint: tr('new.firebase.q.appName.hint'),
673
+ initial: hasExplicitDir ? path.basename(targetDir) : '',
674
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.appName.required')),
675
+ },
676
+ {
677
+ type: 'text',
678
+ name: 'bundleId',
679
+ message: tr('new.firebase.q.bundleId'),
680
+ hint: tr('new.firebase.q.bundleId.hint'),
681
+ initial: (prev, values) => {
682
+ const name = (values?.appName || prev || '').trim();
683
+ if (!name) return 'com.example.app';
684
+ const slug = name
685
+ .normalize('NFD')
686
+ .replace(/[\u0300-\u036f]/g, '')
687
+ .toLowerCase()
688
+ .replace(/[^a-z0-9]/g, '');
689
+ if (!slug || /^\d/.test(slug)) return 'com.example.app';
690
+ return `com.${slug}.app`;
691
+ },
692
+ validate: (v) => {
693
+ if (!v || !v.trim()) return tr('new.firebase.q.bundleId.required');
694
+ return /^[a-zA-Z][\w]*(\.[a-zA-Z][\w]*)+$/.test(v.trim())
695
+ ? true
696
+ : tr('new.firebase.q.bundleId.invalid');
697
+ },
698
+ },
699
+ ];
700
+ const needFirebaseProjectIdNow =
701
+ (backend === 'firebase' && firebaseSetupMode === 'existing') ||
702
+ ((backend === 'supabase' || backend === 'api') && firebaseSetupMode === 'existing');
703
+ if (needFirebaseProjectIdNow) {
704
+ coreQuestions.push({
705
+ type: 'text',
706
+ name: 'firebaseProjectId',
707
+ message: tr('new.firebase.q.projectId'),
708
+ hint: tr('new.firebase.q.projectId.hint') + (backend !== 'firebase' ? ' (FCM + Remote Config)' : ''),
709
+ validate: (v) => {
710
+ if (!v || !v.trim()) return tr('new.firebase.q.projectId.required');
711
+ const id = v.trim();
712
+ if (id.length < 6 || id.length > 30) return 'ID deve ter entre 6 e 30 caracteres (ex: meu-app-123)';
713
+ if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(id)) return 'ID inválido: use letras minúsculas, números e hífens, começando com letra (ex: meu-app-123)';
714
+ return true;
715
+ },
716
+ });
717
+ }
718
+ let core;
719
+ if (yes) {
720
+ if (!hasExplicitDir) {
721
+ console.error(kleur.red(`\n ✗ --yes requires an app name: kasy new MyApp --yes\n`));
722
+ process.exit(1);
723
+ }
724
+ const appName = path.basename(targetDir);
725
+ const slug = appName
726
+ .normalize('NFD')
727
+ .replace(/[\u0300-\u036f]/g, '')
728
+ .toLowerCase()
729
+ .replace(/[^a-z0-9]/g, '');
730
+ const bundleId = (slug && !/^\d/.test(slug)) ? `com.${slug}.app` : 'com.example.app';
731
+ let firebaseProjectId = projectHint?.trim() || '';
732
+ if (!firebaseProjectId && backend === 'firebase') {
733
+ const { pid } = await prompts(
734
+ {
735
+ type: 'text',
736
+ name: 'pid',
737
+ message: tr('new.firebase.q.projectId'),
738
+ hint: tr('new.firebase.q.projectId.hint'),
739
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
740
+ },
741
+ { onCancel: cancel }
742
+ );
743
+ firebaseProjectId = pid?.trim() || '';
744
+ }
745
+ core = { appName, bundleId, firebaseProjectId };
746
+ console.log(kleur.gray(` App: ${kleur.white(appName)}`));
747
+ console.log(kleur.gray(` Bundle: ${kleur.white(bundleId)}`));
748
+ if (firebaseProjectId) console.log(kleur.gray(` Project: ${kleur.white(firebaseProjectId)}`));
749
+ } else {
750
+ core = await prompts(coreQuestions, { onCancel: cancel });
751
+ }
752
+
753
+ // ── Firebase: create from scratch (when selected) ─────────────────────────
754
+ let firebaseIncludeWeb = true;
755
+ if (backend === 'firebase' && firebaseSetupMode === 'create') {
756
+ const { includeWeb } = await prompts(
757
+ {
758
+ type: 'confirm',
759
+ name: 'includeWeb',
760
+ message: tr('new.firebase.create.includeWeb'),
761
+ initial: true,
762
+ },
763
+ { onCancel: cancel }
764
+ );
765
+ firebaseIncludeWeb = includeWeb !== false;
766
+ const gcloudCheck = await checkGcloudAuth();
767
+ if (!gcloudCheck.ok) {
768
+ console.log(kleur.red(` ${tr('new.firebase.create.gcloudRequired')}`));
769
+ if (gcloudCheck.missing === 'gcloud') {
770
+ const instructions = getGcloudInstallInstructions();
771
+ console.log(kleur.cyan(`\n ${tr('new.firebase.create.installTitle')}`));
772
+ if (instructions.install) {
773
+ console.log(kleur.white(` ${tr('new.firebase.create.installCommand')}:`));
774
+ console.log(kleur.cyan(` ${instructions.install}`));
775
+ }
776
+ if (instructions.hint) {
777
+ console.log(kleur.gray(` ${instructions.hint}`));
778
+ }
779
+ console.log(kleur.white(` ${tr('new.firebase.create.installAfter')}:`));
780
+ console.log(kleur.cyan(` ${instructions.after}`));
781
+ console.log(kleur.gray(` ${tr('new.firebase.create.installUrl')}: ${instructions.url}`));
782
+ console.log('');
783
+ } else {
784
+ console.log(kleur.cyan(` ${tr('new.firebase.create.authCommand')}`));
785
+ console.log('');
786
+ }
787
+ console.log(kleur.yellow(` ${tr('new.firebase.create.fallbackHint')}`));
788
+ const fallback = await prompts(
789
+ {
790
+ type: 'text',
791
+ name: 'firebaseProjectId',
792
+ message: tr('new.firebase.q.projectId'),
793
+ hint: tr('new.firebase.q.projectId.hint'),
794
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
795
+ },
796
+ { onCancel: cancel }
797
+ );
798
+ core.firebaseProjectId = fallback.firebaseProjectId;
799
+ } else {
800
+ const selectedOrgId = await promptOrganizationIfNeeded(tr, cancel);
801
+ let selectedBillingId = await promptBillingAccountIfNeeded(tr, cancel);
802
+ console.log(kleur.dim(`\n ⏱ ${tr('new.firebase.create.estimatedTime')}`));
803
+ console.log(kleur.dim(` ${tr('new.internet.warning')}\n`));
804
+ const ps1 = makeProgressSpinner();
805
+ ps1.next(tr('new.firebase.create.creating'));
806
+ const setupResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
807
+ includeWeb: firebaseIncludeWeb,
808
+ region: firebaseRegion,
809
+ tr,
810
+ billingAccountId: selectedBillingId || undefined,
811
+ organizationId: selectedOrgId || undefined,
812
+ onProgress: (key, data) => {
813
+ if (key === 'wait-propagate') {
814
+ ps1.next(tr('new.firebase.create.waitPropagate'));
815
+ } else if (key === 'firestore') {
816
+ ps1.next(stepProgress('firestore', language));
817
+ } else if (key === 'storage') {
818
+ ps1.next(stepProgress('storage', language));
819
+ } else if (key === 'auth-providers-warn') {
820
+ ps1.stop();
821
+ console.log(kleur.yellow(` ⚠ ${tr('new.firebase.interactive.authWarn')}`));
822
+ console.log(kleur.cyan(` ${data?.url || ''}`));
823
+ }
824
+ },
825
+ });
826
+ if (setupResult.ok) {
827
+ ps1.succeed(tr('new.firebase.create.success'));
828
+ core.firebaseProjectId = setupResult.projectId;
829
+ console.log(kleur.dim(` Project ID: ${core.firebaseProjectId}`));
830
+ printCreateFromScratchStatus(setupResult, tr);
831
+ const authUrl = `https://console.firebase.google.com/project/${core.firebaseProjectId}/authentication/providers`;
832
+ if (setupResult.authEnabled && !setupResult.googleSignInSkipped) {
833
+ // Email/Password, Anonymous AND Google Sign-In were all enabled automatically — skip prompt.
834
+ } else {
835
+ // At least one provider needs manual activation.
836
+ const step1Key = setupResult.googleSignInSkipped
837
+ ? 'new.firebase.create.beforeContinue.step1'
838
+ : 'new.firebase.create.beforeContinue.step1.noAuth';
839
+ const readyKey = setupResult.googleSignInSkipped
840
+ ? 'new.firebase.create.beforeContinue.ready'
841
+ : 'new.firebase.create.beforeContinue.ready.noAuth';
842
+ console.log(kleur.bold().yellow(`\n ${tr('new.firebase.create.beforeContinue.title')}`));
843
+ console.log(kleur.gray(` ${tr(step1Key)}`));
844
+ console.log(kleur.cyan(` ${authUrl}`));
845
+ const { ready } = await prompts(
846
+ {
847
+ type: 'confirm',
848
+ name: 'ready',
849
+ message: tr(readyKey),
850
+ initial: true,
851
+ },
852
+ { onCancel: cancel }
853
+ );
854
+ if (!ready) {
855
+ console.log(kleur.gray(` ${tr('prompt.cancelled')}`));
856
+ process.exit(0);
857
+ }
858
+ }
859
+
860
+ } else {
861
+ ps1.fail(tr('new.firebase.create.failed'));
862
+ let lastResult = setupResult;
863
+ while (lastResult.billingFailed && lastResult.projectId) {
864
+ console.log(kleur.red(` ${tr('new.firebase.create.failed')}: ${lastResult.error}`));
865
+ console.log(kleur.yellow(`\n ${tr('new.firebase.create.billingRetry.title')}`));
866
+ console.log(kleur.cyan(` ${lastResult.billingManualLink}`));
867
+ console.log(kleur.gray(` ${tr('new.firebase.create.billingRetry.hint')}`));
868
+ const { retry } = await prompts(
869
+ {
870
+ type: 'confirm',
871
+ name: 'retry',
872
+ message: tr('new.firebase.create.billingRetry.ready'),
873
+ initial: true,
874
+ },
875
+ { onCancel: cancel }
876
+ );
877
+ if (!retry) {
878
+ console.log(kleur.gray(` ${tr('new.firebase.create.billingRetry.exit')}`));
879
+ console.log(kleur.cyan(` ${tr('new.firebase.create.useExistingHint', { id: lastResult.projectId })}`));
880
+ process.exit(0);
881
+ }
882
+ console.log(kleur.gray(` ${tr('new.firebase.create.billingRetry.retrying')}`));
883
+ selectedBillingId = await promptBillingAccountIfNeeded(tr, cancel);
884
+ const ps2 = makeProgressSpinner();
885
+ ps2.next(tr('new.firebase.create.creating'));
886
+ lastResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
887
+ includeWeb: firebaseIncludeWeb,
888
+ region: firebaseRegion,
889
+ tr,
890
+ resumeFromBilling: { projectId: lastResult.projectId },
891
+ billingAccountId: selectedBillingId || undefined,
892
+ organizationId: selectedOrgId || undefined,
893
+ onProgress: (key) => {
894
+ if (key === 'wait-propagate') {
895
+ ps2.next(tr('new.firebase.create.waitPropagate'));
896
+ } else if (key === 'firestore') {
897
+ ps2.next(stepProgress('firestore', language));
898
+ } else if (key === 'storage') {
899
+ ps2.next(stepProgress('storage', language));
900
+ }
901
+ },
902
+ });
903
+ if (lastResult.ok) {
904
+ ps2.succeed(tr('new.firebase.create.success'));
905
+ core.firebaseProjectId = lastResult.projectId;
906
+ console.log(kleur.dim(` Project ID: ${core.firebaseProjectId}`));
907
+ printCreateFromScratchStatus(lastResult, tr);
908
+ const authUrl = `https://console.firebase.google.com/project/${core.firebaseProjectId}/authentication/providers`;
909
+ if (lastResult.authEnabled && !lastResult.googleSignInSkipped) {
910
+ // All three providers enabled automatically — skip prompt.
911
+ } else {
912
+ const step1Key = lastResult.googleSignInSkipped
913
+ ? 'new.firebase.create.beforeContinue.step1'
914
+ : 'new.firebase.create.beforeContinue.step1.noAuth';
915
+ const lastReadyKey = lastResult.googleSignInSkipped
916
+ ? 'new.firebase.create.beforeContinue.ready'
917
+ : 'new.firebase.create.beforeContinue.ready.noAuth';
918
+ console.log(kleur.bold().yellow(`\n ${tr('new.firebase.create.beforeContinue.title')}`));
919
+ console.log(kleur.gray(` ${tr(step1Key)}`));
920
+ console.log(kleur.cyan(` ${authUrl}`));
921
+ const { ready } = await prompts(
922
+ {
923
+ type: 'confirm',
924
+ name: 'ready',
925
+ message: tr(lastReadyKey),
926
+ initial: true,
927
+ },
928
+ { onCancel: cancel }
929
+ );
930
+ if (!ready) {
931
+ console.log(kleur.gray(` ${tr('prompt.cancelled')}`));
932
+ process.exit(0);
933
+ }
934
+ }
935
+
936
+ break;
937
+ }
938
+ }
939
+ if (!lastResult.ok && !lastResult.billingFailed) {
940
+ const isProjectQuota = /exceeded your allotted project quota|project quota/i.test(String(lastResult.error || ''));
941
+ const msg = isProjectQuota ? tr('new.firebase.create.projectQuotaExceeded') : `${tr('new.firebase.create.failed')}: ${lastResult.error}`;
942
+ console.log(kleur.red(` ${msg}`));
943
+ if (lastResult.projectId) {
944
+ core.firebaseProjectId = lastResult.projectId;
945
+ console.log(kleur.gray(` ${tr('new.firebase.create.usingProjectId', { id: lastResult.projectId })}`));
946
+ } else {
947
+ const fallback = await prompts(
948
+ {
949
+ type: 'text',
950
+ name: 'firebaseProjectId',
951
+ message: tr('new.firebase.q.projectId'),
952
+ hint: tr('new.firebase.q.projectId.hint'),
953
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
954
+ },
955
+ { onCancel: cancel }
956
+ );
957
+ core.firebaseProjectId = fallback.firebaseProjectId;
958
+ }
959
+ }
960
+ }
961
+ }
962
+ }
963
+
964
+ // ── Firebase: create from scratch for Supabase/API (push notifications only) ─
965
+ if ((backend === 'supabase' || backend === 'api') && firebaseSetupMode === 'create') {
966
+ const gcloudCheck = await checkGcloudAuth();
967
+ if (!gcloudCheck.ok) {
968
+ console.log(kleur.red(` ${tr('new.firebase.create.gcloudRequired')}`));
969
+ if (gcloudCheck.missing === 'gcloud') {
970
+ const instructions = getGcloudInstallInstructions();
971
+ console.log(kleur.cyan(`\n ${tr('new.firebase.create.installTitle')}`));
972
+ if (instructions.install) {
973
+ console.log(kleur.white(` ${tr('new.firebase.create.installCommand')}:`));
974
+ console.log(kleur.cyan(` ${instructions.install}`));
975
+ }
976
+ if (instructions.hint) {
977
+ console.log(kleur.gray(` ${instructions.hint}`));
978
+ }
979
+ console.log(kleur.white(` ${tr('new.firebase.create.installAfter')}:`));
980
+ console.log(kleur.cyan(` ${instructions.after}`));
981
+ console.log(kleur.gray(` ${tr('new.firebase.create.installUrl')}: ${instructions.url}`));
982
+ console.log('');
983
+ } else {
984
+ console.log(kleur.cyan(` ${tr('new.firebase.create.authCommand')}`));
985
+ console.log('');
986
+ }
987
+ console.log(kleur.yellow(` ${tr('new.firebase.create.fallbackHint')}`));
988
+ const fallback = await prompts(
989
+ {
990
+ type: 'text',
991
+ name: 'firebaseProjectId',
992
+ message: tr('new.firebase.q.projectId'),
993
+ hint: tr('new.firebase.q.projectId.hint') + ' (FCM + Remote Config)',
994
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
995
+ },
996
+ { onCancel: cancel }
997
+ );
998
+ core.firebaseProjectId = fallback.firebaseProjectId;
999
+ } else {
1000
+ const selectedOrgId = await promptOrganizationIfNeeded(tr, cancel);
1001
+ const selectedBillingId = await promptBillingAccountIfNeeded(tr, cancel);
1002
+ console.log(kleur.dim(`\n ${tr('new.internet.warning')}\n`));
1003
+ const ps3 = makeProgressSpinner();
1004
+ ps3.next(tr('new.firebase.create.creatingPush'));
1005
+ const setupResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
1006
+ includeWeb: true,
1007
+ region: firebaseRegion,
1008
+ tr,
1009
+ billingAccountId: selectedBillingId || undefined,
1010
+ organizationId: selectedOrgId || undefined,
1011
+ onProgress: (key) => {
1012
+ if (key === 'wait-propagate') {
1013
+ ps3.next(tr('new.firebase.create.waitPropagate'));
1014
+ } else if (key === 'firestore') {
1015
+ ps3.next(stepProgress('firestore', language));
1016
+ } else if (key === 'storage') {
1017
+ ps3.next(stepProgress('storage', language));
1018
+ }
1019
+ },
1020
+ });
1021
+ if (setupResult.ok) {
1022
+ ps3.succeed(tr('new.firebase.create.successPush'));
1023
+ core.firebaseProjectId = setupResult.projectId;
1024
+ console.log(kleur.dim(` Project ID: ${core.firebaseProjectId}`));
1025
+ printCreateFromScratchStatus(setupResult, tr);
1026
+ } else {
1027
+ ps3.fail(tr('new.firebase.create.failed'));
1028
+ console.log(kleur.red(` ${tr('new.firebase.create.failed')}: ${setupResult.error}`));
1029
+ if (setupResult.projectId) {
1030
+ core.firebaseProjectId = setupResult.projectId;
1031
+ console.log(kleur.gray(` ${tr('new.firebase.create.usingProjectId', { id: setupResult.projectId })}`));
1032
+ } else {
1033
+ const fallback = await prompts(
1034
+ {
1035
+ type: 'text',
1036
+ name: 'firebaseProjectId',
1037
+ message: tr('new.firebase.q.projectId'),
1038
+ hint: tr('new.firebase.q.projectId.hint') + ' (FCM + Remote Config)',
1039
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
1040
+ },
1041
+ { onCancel: cancel }
1042
+ );
1043
+ core.firebaseProjectId = fallback.firebaseProjectId;
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ // ── Backend-specific questions ───────────────────────────────────────────
1050
+ let supabaseCreate = false;
1051
+ let supabaseDbPassword = null;
1052
+ let supabaseCreateResult = null;
1053
+ /** When using existing project: { projectRef, supabaseUrl, supabaseAnonKey, dbPassword } */
1054
+ let supabaseExistingResult = null;
1055
+
1056
+ if (backend === 'supabase') {
1057
+ const { createSupabase } = await prompts(
1058
+ {
1059
+ type: 'select',
1060
+ name: 'createSupabase',
1061
+ message: tr('new.supabase.q.create'),
1062
+ choices: [
1063
+ { title: tr('new.supabase.q.create.create'), value: true },
1064
+ { title: tr('new.supabase.q.create.existing'), value: false },
1065
+ ],
1066
+ initial: 0,
1067
+ },
1068
+ { onCancel: cancel }
1069
+ );
1070
+ supabaseCreate = createSupabase;
1071
+
1072
+ if (supabaseCreate) {
1073
+ const loginCheck = await checkLoggedIn();
1074
+ if (!loginCheck.ok) {
1075
+ console.log(kleur.yellow(` ${tr('new.supabase.loginRequired')}`));
1076
+ console.log(kleur.cyan(` ${tr('new.supabase.loginCommand')}`));
1077
+ console.log('');
1078
+ }
1079
+ const orgsResult = await getOrgsList();
1080
+ if (!orgsResult.ok || !orgsResult.orgs?.length) {
1081
+ console.log(kleur.red(` ${tr('new.supabase.orgsRequired')}`));
1082
+ supabaseCreateResult = { ok: false, error: tr('new.supabase.orgsRequired') };
1083
+ } else {
1084
+ let orgId = orgsResult.orgs[0].id;
1085
+ if (orgsResult.orgs.length > 1) {
1086
+ const { selectedOrgId } = await prompts(
1087
+ {
1088
+ type: 'select',
1089
+ name: 'selectedOrgId',
1090
+ message: tr('new.supabase.q.orgSelect'),
1091
+ choices: orgsResult.orgs.map((o) => ({ title: o.name, value: o.id })),
1092
+ },
1093
+ { onCancel: cancel }
1094
+ );
1095
+ orgId = selectedOrgId;
1096
+ }
1097
+ let supabaseRegion = DEFAULT_SUPABASE_REGION;
1098
+ if (!isQuick) {
1099
+ const { region } = await prompts(
1100
+ {
1101
+ type: 'select',
1102
+ name: 'region',
1103
+ message: tr('new.supabase.q.region'),
1104
+ choices: [
1105
+ { title: tr('new.supabase.q.region.brazil'), value: 'sa-east-1' },
1106
+ { title: tr('new.supabase.q.region.us'), value: 'us-east-1' },
1107
+ { title: tr('new.supabase.q.region.europe'), value: 'eu-west-1' },
1108
+ { title: tr('new.supabase.q.region.global'), value: 'us-east-1' },
1109
+ ],
1110
+ initial: 0,
1111
+ },
1112
+ { onCancel: cancel }
1113
+ );
1114
+ supabaseRegion = region || DEFAULT_SUPABASE_REGION;
1115
+ }
1116
+ const { dbPassword } = await prompts(
1117
+ {
1118
+ type: 'password',
1119
+ name: 'dbPassword',
1120
+ message: tr('new.supabase.q.dbPassword'),
1121
+ validate: (v) => (v && v.length >= 6 ? true : tr('new.supabase.q.dbPassword.required')),
1122
+ },
1123
+ { onCancel: cancel }
1124
+ );
1125
+ supabaseDbPassword = dbPassword;
1126
+ console.log(kleur.dim(` ${tr('new.internet.warning')}`));
1127
+ console.log(kleur.gray(` ${tr('new.supabase.creating')}`));
1128
+ supabaseCreateResult = await createProjectAndGetKeys(
1129
+ core.appName.trim().replace(/\s+/g, '-').toLowerCase(),
1130
+ supabaseDbPassword,
1131
+ supabaseRegion,
1132
+ orgId
1133
+ );
1134
+ }
1135
+ if (supabaseCreateResult.ok) {
1136
+ core.supabaseUrl = supabaseCreateResult.supabaseUrl;
1137
+ core.supabaseAnonKey = supabaseCreateResult.supabaseAnonKey;
1138
+ console.log(kleur.green(` ✓ ${tr('new.supabase.created')}`));
1139
+ } else {
1140
+ console.log(kleur.red(` ${tr('new.supabase.createFailed')}: ${supabaseCreateResult.error}`));
1141
+ console.log(kleur.gray(` ${tr('new.supabase.loginHint')}`));
1142
+ Object.assign(core, await promptSupabaseManual(tr, cancel));
1143
+ supabaseCreate = false;
1144
+ }
1145
+ } else {
1146
+ // Usar projeto Supabase existente: org → projeto → keys → senha (mesmo fluxo de setup, sem criar)
1147
+ const loginCheck = await checkLoggedIn();
1148
+ if (!loginCheck.ok) {
1149
+ console.log(kleur.yellow(` ${tr('new.supabase.loginRequired')}`));
1150
+ console.log(kleur.cyan(` ${tr('new.supabase.loginCommand')}`));
1151
+ console.log('');
1152
+ Object.assign(core, await promptSupabaseManual(tr, cancel));
1153
+ } else {
1154
+ const orgsResult = await getOrgsList();
1155
+ if (!orgsResult.ok || !orgsResult.orgs?.length) {
1156
+ console.log(kleur.red(` ${tr('new.supabase.orgsRequired')}`));
1157
+ Object.assign(core, await promptSupabaseManual(tr, cancel));
1158
+ } else {
1159
+ let orgId = orgsResult.orgs[0].id;
1160
+ if (orgsResult.orgs.length > 1) {
1161
+ const { selectedOrgId } = await prompts(
1162
+ { type: 'select', name: 'selectedOrgId', message: tr('new.supabase.q.useExisting.orgSelect'), choices: orgsResult.orgs.map((o) => ({ title: o.name, value: o.id })) },
1163
+ { onCancel: cancel }
1164
+ );
1165
+ orgId = selectedOrgId;
1166
+ }
1167
+ const projectsResult = await getProjectsByOrg(orgId);
1168
+ if (!projectsResult.ok || !projectsResult.projects?.length) {
1169
+ console.log(kleur.yellow(` ${tr('new.supabase.projectsRequired')}`));
1170
+ Object.assign(core, await promptSupabaseManual(tr, cancel));
1171
+ } else {
1172
+ const { selectedProjectRef } = await prompts(
1173
+ {
1174
+ type: 'select',
1175
+ name: 'selectedProjectRef',
1176
+ message: tr('new.supabase.q.useExisting.projectSelect'),
1177
+ choices: projectsResult.projects.map((p) => ({ title: `${p.name} (${p.id})`, value: p.id })),
1178
+ },
1179
+ { onCancel: cancel }
1180
+ );
1181
+ const keysResult = await getProjectKeys(selectedProjectRef);
1182
+ if (!keysResult.ok) {
1183
+ console.log(kleur.red(` ${keysResult.error}`));
1184
+ Object.assign(core, await promptSupabaseManual(tr, cancel));
1185
+ } else {
1186
+ core.supabaseUrl = keysResult.supabaseUrl;
1187
+ core.supabaseAnonKey = keysResult.supabaseAnonKey;
1188
+ const { dbPassword } = await prompts(
1189
+ {
1190
+ type: 'password',
1191
+ name: 'dbPassword',
1192
+ message: tr('new.supabase.q.dbPassword.existing'),
1193
+ validate: (v) => (v && v.length >= 6 ? true : tr('new.supabase.q.dbPassword.required')),
1194
+ },
1195
+ { onCancel: cancel }
1196
+ );
1197
+ supabaseExistingResult = {
1198
+ ok: true,
1199
+ projectRef: selectedProjectRef,
1200
+ supabaseUrl: keysResult.supabaseUrl,
1201
+ supabaseAnonKey: keysResult.supabaseAnonKey,
1202
+ dbPassword,
1203
+ };
1204
+ console.log(kleur.green(` ✓ ${tr('new.supabase.existingLinked')}`));
1205
+ }
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+
1212
+ // ── Supabase: Google Sign-In credentials resolved automatically after build ─
1213
+ // (No prompts — web/iOS client IDs come from google-services.json /
1214
+ // GoogleService-Info.plist; secret fetched from Identity Toolkit API)
1215
+ let googleWebClientId = '';
1216
+ let googleClientSecret = '';
1217
+ let googleIosClientId = '';
1218
+
1219
+ if (backend === 'api') {
1220
+ const api = await prompts(
1221
+ {
1222
+ type: 'text',
1223
+ name: 'apiBaseUrl',
1224
+ message: tr('new.api.q.baseUrl'),
1225
+ hint: tr('new.api.q.baseUrl.hint'),
1226
+ initial: 'https://api.example.com',
1227
+ },
1228
+ { onCancel: cancel }
1229
+ );
1230
+ Object.assign(core, api);
1231
+ }
1232
+
1233
+ // Resolve targetDir now that we have the app name
1234
+ if (!targetDir) {
1235
+ const folderName = toPackageName(core.appName.trim());
1236
+ targetDir = path.resolve(process.cwd(), folderName);
1237
+ // Guard: derived dir must not already exist
1238
+ if (await fs.pathExists(targetDir)) {
1239
+ const contents = await fs.readdir(targetDir);
1240
+ if (contents.length > 0) {
1241
+ throw new Error(tr('new.firebase.error.dirNotEmpty', { path: targetDir }));
1242
+ }
1243
+ }
1244
+ }
1245
+
1246
+ // ── Firebase existing project: enable APIs + create Firestore/Storage ───
1247
+ if (backend === 'firebase' && firebaseSetupMode === 'existing' && core.firebaseProjectId) {
1248
+ const ps4 = makeProgressSpinner();
1249
+ ps4.next(stepProgress('enable-apis', language));
1250
+ const existingSetup = await setupExistingProject(core.firebaseProjectId, {
1251
+ onProgress: (key, data) => {
1252
+ if (key === 'enable-apis') {
1253
+ ps4.next(stepProgress('enable-apis', language));
1254
+ } else if (key === 'enable-apis-warn') {
1255
+ ps4.warn(`${tr('new.firebase.create.failed')}: APIs`);
1256
+ console.log(kleur.yellow(` ⚠ Não foi possível ativar APIs: ${(data?.error || '').slice(0, 80)}`));
1257
+ } else if (key === 'firestore') {
1258
+ ps4.next(stepProgress('firestore', language));
1259
+ } else if (key === 'storage') {
1260
+ ps4.next(stepProgress('storage', language));
1261
+ } else if (key === 'auth-providers-warn') {
1262
+ ps4.stop();
1263
+ console.log(kleur.yellow(` ⚠ ${tr('new.firebase.interactive.authWarn')}`));
1264
+ console.log(kleur.cyan(` ${data?.url || ''}`));
1265
+ } else if (key === 'auth-google-warn') {
1266
+ ps4.stop();
1267
+ console.log(kleur.yellow(` ⚠ Google Sign-In: ative manualmente em Authentication → Sign-in method → Google`));
1268
+ console.log(kleur.cyan(` ${data?.url || ''}`));
1269
+ }
1270
+ },
1271
+ });
1272
+ if (existingSetup.firestoreCreated) {
1273
+ ps4.succeed(stepLabel('firestore', language));
1274
+ } else if (existingSetup.firestoreError && !existingSetup.firestoreError.includes('ALREADY_EXISTS')) {
1275
+ ps4.fail(`Firestore: ${(existingSetup.firestoreError || '').slice(0, 80)}`);
1276
+ console.log(kleur.cyan(` ${existingSetup.firestoreUrl}`));
1277
+ } else {
1278
+ ps4.stop();
1279
+ }
1280
+ if (existingSetup.storageCreated) {
1281
+ console.log(kleur.green(` ✔ ${stepLabel('storage', language)}`));
1282
+ } else if (existingSetup.storageError && !existingSetup.storageError.includes('ALREADY_EXISTS')) {
1283
+ console.log(kleur.yellow(` ⚠ Storage: ${(existingSetup.storageError || '').slice(0, 80)}`));
1284
+ console.log(kleur.cyan(` ${existingSetup.storageUrl}`));
1285
+ }
1286
+ console.log('');
1287
+ }
1288
+
1289
+ // ── Optional modules ────────────────────────────────────────────────────
1290
+ // --with flag pre-selects modules and bypasses preset selection entirely.
1291
+ const preselectedModules = withModules
1292
+ ? String(withModules).split(',').map((m) => m.trim().toLowerCase()).filter(Boolean)
1293
+ : [];
1294
+
1295
+ let modules = [];
1296
+
1297
+ if (preselectedModules.length > 0) {
1298
+ // --with flag was passed: use those modules directly, skip preset prompt.
1299
+ modules = preselectedModules;
1300
+ } else if (isQuick) {
1301
+ // Quick mode: show preset picker (no multiselect).
1302
+ const { preset } = await prompts(
1303
+ {
1304
+ type: 'select',
1305
+ name: 'preset',
1306
+ message: tr('new.q.preset'),
1307
+ choices: [
1308
+ { title: tr('new.q.preset.starter'), value: 'starter' },
1309
+ { title: tr('new.q.preset.saas'), value: 'saas' },
1310
+ { title: tr('new.q.preset.content'), value: 'content' },
1311
+ { title: tr('new.q.preset.full'), value: 'full' },
1312
+ { title: tr('new.q.preset.none'), value: 'none' },
1313
+ ],
1314
+ initial: 0,
1315
+ },
1316
+ { onCancel: cancel }
1317
+ );
1318
+ modules = MODULE_PRESETS[preset] || [];
1319
+ } else {
1320
+ // Advanced mode: full multiselect — built from catalog, filtered by audience + backend.
1321
+ const visibleFeatures = getVisibleFeatures({ audience: KASY_AUDIENCE, backend });
1322
+
1323
+ // web has an extra runtime constraint: firebase create-mode only
1324
+ const isWebExcluded = backend === 'firebase' && firebaseSetupMode === 'create';
1325
+
1326
+ // Visual groups in display order (header key → feature ids in this group)
1327
+ const groups = [
1328
+ { header: null, ids: ['sentry', 'analytics', 'facebook'] },
1329
+ { header: 'new.modules.header.monetization', ids: ['revenuecat'] },
1330
+ { header: 'new.modules.header.features', ids: ['onboarding', 'web', 'widget', 'llm_chat', 'local_notifications'] },
1331
+ { header: 'new.modules.header.feedback', ids: ['feedback'] },
1332
+ { header: 'new.modules.header.ci', ids: ['ci'] },
1333
+ ];
1334
+
1335
+ const moduleChoices = [];
1336
+ for (const group of groups) {
1337
+ const inGroup = visibleFeatures.filter((f) => {
1338
+ if (!group.ids.includes(f.id)) return false;
1339
+ if (f.id === 'web' && isWebExcluded) return false;
1340
+ return true;
1341
+ });
1342
+ if (inGroup.length === 0) continue;
1343
+ if (group.header) {
1344
+ moduleChoices.push({ title: tr(group.header), value: `__header_${group.header}__`, disabled: true });
1345
+ }
1346
+ for (const f of inGroup) {
1347
+ const label = tr(`new.firebase.module.${f.id}`) + (f.status === 'internal' ? ' [beta]' : '');
1348
+ moduleChoices.push({ title: label, value: f.id, selected: false });
1349
+ }
1350
+ }
1351
+
1352
+ const { modules: rawModules } = await prompts(
1353
+ {
1354
+ type: 'multiselect',
1355
+ name: 'modules',
1356
+ message: tr('new.firebase.q.modules'),
1357
+ hint: tr('new.firebase.q.modules.hint'),
1358
+ instructions: tr('prompt.multiselect.instructions'),
1359
+ choices: moduleChoices,
1360
+ min: 0,
1361
+ warn: tr('prompt.multiselect.warnDisabled'),
1362
+ },
1363
+ { onCancel: cancel }
1364
+ );
1365
+ modules = (rawModules || []).filter((m) => !String(m).startsWith('__header_'));
1366
+ }
1367
+
1368
+ if (backend === 'firebase' && firebaseSetupMode === 'create' && firebaseIncludeWeb) {
1369
+ modules = [...new Set([...modules, 'web'])];
1370
+ }
1371
+
1372
+ // ── Module-specific questions ───────────────────────────────────────────
1373
+ const moduleAnswers = {};
1374
+
1375
+ if (modules.includes('revenuecat')) {
1376
+ const rc = await prompts(
1377
+ [
1378
+ {
1379
+ type: 'text',
1380
+ name: 'rcAndroidKey',
1381
+ message: tr('new.firebase.q.revenuecat.android'),
1382
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.revenuecat.android.required')),
1383
+ },
1384
+ {
1385
+ type: 'text',
1386
+ name: 'rcIosKey',
1387
+ message: tr('new.firebase.q.revenuecat.ios'),
1388
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.revenuecat.ios.required')),
1389
+ },
1390
+ {
1391
+ type: 'select',
1392
+ name: 'defaultPaywall',
1393
+ message: tr('new.firebase.q.paywall'),
1394
+ hint: tr('new.firebase.q.paywall.hint'),
1395
+ choices: [
1396
+ { title: 'Basic (list of plans)', value: 'basic' },
1397
+ { title: 'With trial switch', value: 'withSwitch' },
1398
+ { title: 'Row + comparison table', value: 'basicRow' },
1399
+ { title: 'Minimal (benefits + CTA)', value: 'minimal' },
1400
+ ],
1401
+ initial: 0,
1402
+ },
1403
+ ],
1404
+ { onCancel: cancel }
1405
+ );
1406
+ Object.assign(moduleAnswers, rc);
1407
+ }
1408
+
1409
+ // RC web key — only in advanced mode (optional credential, can configure later).
1410
+ if (!isQuick && modules.includes('revenuecat') && modules.includes('web')) {
1411
+ const rcWebKey = await prompts(
1412
+ {
1413
+ type: 'text',
1414
+ name: 'rcWebKey',
1415
+ message: tr('new.firebase.q.revenuecat.webKey'),
1416
+ validate: (v) => {
1417
+ if (!v || !v.trim()) return true; // optional — blank is fine
1418
+ return /^rcb_/.test(v.trim()) ? true : tr('new.firebase.q.revenuecat.webKey.invalid');
1419
+ },
1420
+ },
1421
+ { onCancel: cancel }
1422
+ );
1423
+ Object.assign(moduleAnswers, {
1424
+ revenuecatWeb: true,
1425
+ rcWebKey: (rcWebKey.rcWebKey || '').trim(),
1426
+ });
1427
+ } else if (modules.includes('revenuecat') && modules.includes('web')) {
1428
+ // Quick mode: mark web billing as included but key will be configured later.
1429
+ moduleAnswers.revenuecatWeb = true;
1430
+ moduleAnswers.rcWebKey = '';
1431
+ }
1432
+
1433
+ // Sentry DSN — already optional (blank = configure later). Skip in quick mode.
1434
+ if (!isQuick && modules.includes('sentry')) {
1435
+ const s = await prompts(
1436
+ {
1437
+ type: 'text',
1438
+ name: 'sentryDsn',
1439
+ message: tr('new.firebase.q.sentry.dsn'),
1440
+ validate: (v) => {
1441
+ if (!v || !v.trim()) return true; // optional — blank is fine
1442
+ return /^https?:\/\/[^@\s]+@[^/\s]+\/\d+$/.test(v.trim())
1443
+ ? true
1444
+ : tr('new.firebase.q.sentry.dsn.invalid');
1445
+ },
1446
+ },
1447
+ { onCancel: cancel }
1448
+ );
1449
+ Object.assign(moduleAnswers, s);
1450
+ }
1451
+
1452
+ // Mixpanel token — already optional. Skip in quick mode.
1453
+ if (!isQuick && modules.includes('analytics')) {
1454
+ const mx = await prompts(
1455
+ {
1456
+ type: 'text',
1457
+ name: 'mixpanelToken',
1458
+ message: tr('new.firebase.q.mixpanel.token'),
1459
+ },
1460
+ { onCancel: cancel }
1461
+ );
1462
+ Object.assign(moduleAnswers, mx);
1463
+ }
1464
+
1465
+ // LLM Chat credentials — skip in quick mode, all fields optional.
1466
+ if (!isQuick && modules.includes('llm_chat')) {
1467
+ const { configureLlmNow } = await prompts(
1468
+ {
1469
+ type: 'confirm',
1470
+ name: 'configureLlmNow',
1471
+ message: tr('new.q.llm_chat.configureNow'),
1472
+ hint: tr('new.q.llm_chat.configureNow.hint'),
1473
+ initial: true,
1474
+ },
1475
+ { onCancel: cancel }
1476
+ );
1477
+
1478
+ if (configureLlmNow) {
1479
+ const llm = await prompts(
1480
+ [
1481
+ {
1482
+ type: 'select',
1483
+ name: 'llmProvider',
1484
+ message: tr('add.prompt.llmProvider'),
1485
+ choices: [
1486
+ { title: 'OpenAI (gpt-4o-mini)', value: 'openai' },
1487
+ { title: 'Google Gemini (gemini-1.5-flash)', value: 'gemini' },
1488
+ ],
1489
+ initial: 0,
1490
+ },
1491
+ {
1492
+ type: 'text',
1493
+ name: 'llmSystemPrompt',
1494
+ message: tr('add.prompt.llmSystemPrompt'),
1495
+ },
1496
+ {
1497
+ type: 'text',
1498
+ name: 'llmApiKey',
1499
+ message: tr('add.prompt.llmApiKey'),
1500
+ },
1501
+ ],
1502
+ { onCancel: cancel }
1503
+ );
1504
+ Object.assign(moduleAnswers, llm);
1505
+ } else {
1506
+ moduleAnswers.llmConfigureLater = true;
1507
+ }
1508
+ } else if (isQuick && modules.includes('llm_chat')) {
1509
+ moduleAnswers.llmConfigureLater = true;
1510
+ }
1511
+
1512
+ // Facebook — required credentials, always ask.
1513
+ if (modules.includes('facebook')) {
1514
+ const fb = await prompts(
1515
+ [
1516
+ {
1517
+ type: 'text',
1518
+ name: 'fbAppId',
1519
+ message: tr('new.firebase.q.facebook.appId'),
1520
+ validate: (v) => {
1521
+ if (!v || !v.trim()) return tr('new.firebase.q.facebook.appId.required');
1522
+ return /^\d+$/.test(v.trim()) ? true : tr('new.firebase.q.facebook.appId.invalid');
1523
+ },
1524
+ },
1525
+ {
1526
+ type: 'text',
1527
+ name: 'fbToken',
1528
+ message: tr('new.firebase.q.facebook.token'),
1529
+ validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.facebook.token.required')),
1530
+ },
1531
+ ],
1532
+ { onCancel: cancel }
1533
+ );
1534
+ Object.assign(moduleAnswers, fb);
1535
+ }
1536
+
1537
+ // Server secrets (webhook, Meta Ads) — skip in quick mode, configure later via `kasy deploy`.
1538
+ if (!isQuick && modules.includes('revenuecat') && (backend === 'supabase' || backend === 'firebase')) {
1539
+ const { configureSecretsNow } = await prompts(
1540
+ {
1541
+ type: 'confirm',
1542
+ name: 'configureSecretsNow',
1543
+ message: tr('new.firebase.q.secrets.configureNow'),
1544
+ hint: tr('new.firebase.q.secrets.configureNow.hint'),
1545
+ initial: true,
1546
+ },
1547
+ { onCancel: cancel }
1548
+ );
1549
+
1550
+ if (configureSecretsNow) {
1551
+ const rcSecrets = await prompts(
1552
+ [
1553
+ {
1554
+ type: 'text',
1555
+ name: 'rcWebhookKey',
1556
+ message: tr('new.firebase.q.revenuecat.webhookKey'),
1557
+ initial: generateWebhookKey(),
1558
+ hint: tr('new.firebase.q.revenuecat.webhookKey.hint'),
1559
+ },
1560
+ {
1561
+ type: 'text',
1562
+ name: 'metaAccessToken',
1563
+ message: tr('new.firebase.q.revenuecat.metaToken'),
1564
+ },
1565
+ {
1566
+ type: 'text',
1567
+ name: 'metaDatasetId',
1568
+ message: tr('new.firebase.q.revenuecat.metaDataset'),
1569
+ validate: (v) => {
1570
+ if (!v || !v.trim()) return true; // optional — blank is fine
1571
+ return /^\d+$/.test(v.trim()) ? true : tr('new.firebase.q.revenuecat.metaDataset.invalid');
1572
+ },
1573
+ },
1574
+ ],
1575
+ { onCancel: cancel }
1576
+ );
1577
+ Object.assign(moduleAnswers, rcSecrets);
1578
+ } else {
1579
+ moduleAnswers.secretsConfigureLater = true;
1580
+ }
1581
+ } else if (isQuick && modules.includes('revenuecat')) {
1582
+ // Quick mode: always defer secrets to `kasy deploy`.
1583
+ moduleAnswers.secretsConfigureLater = true;
1584
+ }
1585
+
1586
+ // ── Deploy is now a separate command (`kasy deploy`) ─────────────
1587
+ // Deploy is never run during `new` — the project is generated and ready,
1588
+ // and the user deploys when they choose with `kasy deploy`.
1589
+ const deploy = false;
1590
+
1591
+ // ── Summary ─────────────────────────────────────────────────────────────
1592
+ const answers = {
1593
+ appName: core.appName.trim(),
1594
+ bundleId: core.bundleId.trim(),
1595
+ firebaseProjectId: core.firebaseProjectId.trim(),
1596
+ modules: modules || [],
1597
+ backend,
1598
+ includeWeb: backend === 'firebase' ? firebaseIncludeWeb : true,
1599
+ supabaseUrl: core.supabaseUrl?.trim(),
1600
+ supabaseAnonKey: core.supabaseAnonKey?.trim(),
1601
+ apiBaseUrl: core.apiBaseUrl?.trim(),
1602
+ };
1603
+
1604
+ printSummary(tr, answers);
1605
+
1606
+ if (!yes) {
1607
+ const { proceed } = await prompts(
1608
+ {
1609
+ type: 'confirm',
1610
+ name: 'proceed',
1611
+ message: tr('new.firebase.confirm.proceed'),
1612
+ initial: true,
1613
+ },
1614
+ { onCancel: cancel }
1615
+ );
1616
+ if (!proceed) {
1617
+ console.log(kleur.gray(`\n ${tr('prompt.cancelled')}\n`));
1618
+ process.exit(0);
1619
+ }
1620
+ }
1621
+
1622
+ // ── Generate ────────────────────────────────────────────────────────────
1623
+ console.log('');
1624
+ const spinner = ora(stepProgress('project-setup', language)).start();
1625
+
1626
+ let result;
1627
+ try {
1628
+ if (backend === 'supabase') {
1629
+ result = await generateSupabaseProject(targetDir, {
1630
+ appName: answers.appName,
1631
+ bundleId: answers.bundleId,
1632
+ firebaseProjectId: answers.firebaseProjectId,
1633
+ supabaseUrl: answers.supabaseUrl,
1634
+ supabaseAnonKey: answers.supabaseAnonKey,
1635
+ modules: answers.modules,
1636
+ moduleAnswers,
1637
+ language,
1638
+ onProgress: (key) => {
1639
+ spinner.text = kleur.cyan(stepProgress(key, language));
1640
+ },
1641
+ });
1642
+ } else if (backend === 'api') {
1643
+ result = await generateApiProject(targetDir, {
1644
+ appName: answers.appName,
1645
+ bundleId: answers.bundleId,
1646
+ firebaseProjectId: answers.firebaseProjectId,
1647
+ apiBaseUrl: answers.apiBaseUrl,
1648
+ modules: answers.modules,
1649
+ moduleAnswers,
1650
+ language,
1651
+ onProgress: (key) => {
1652
+ spinner.text = kleur.cyan(stepProgress(key, language));
1653
+ },
1654
+ });
1655
+ } else {
1656
+ result = await generateFirebaseProject(targetDir, {
1657
+ appName: answers.appName,
1658
+ bundleId: answers.bundleId,
1659
+ firebaseProjectId: answers.firebaseProjectId,
1660
+ deploy,
1661
+ modules: answers.modules,
1662
+ moduleAnswers,
1663
+ includeWeb: answers.includeWeb !== false,
1664
+ functionsRegion: firebaseRegion,
1665
+ language,
1666
+ onProgress: (key) => {
1667
+ spinner.text = kleur.cyan(stepProgress(key, language));
1668
+ },
1669
+ });
1670
+ }
1671
+ spinner.stop();
1672
+ } catch (err) {
1673
+ spinner.fail(kleur.red(err.message));
1674
+ throw err;
1675
+ }
1676
+
1677
+ // Save deploy metadata for `kasy deploy` auto-detect
1678
+ if (backend === 'firebase') {
1679
+ try {
1680
+ const kasyDir = path.join(targetDir, '.kasy');
1681
+ await fs.ensureDir(kasyDir);
1682
+ await fs.writeJson(path.join(kasyDir, 'config.json'), { functionsRegion: firebaseRegion }, { spaces: 2 });
1683
+ } catch (_) {}
1684
+
1685
+ // Write functions/.env with LLM non-secret vars (provider + system prompt)
1686
+ if (modules.includes('llm_chat') && !moduleAnswers.llmConfigureLater) {
1687
+ try {
1688
+ const envLines = [];
1689
+ if (moduleAnswers.llmProvider) envLines.push(`LLM_PROVIDER=${moduleAnswers.llmProvider}`);
1690
+ if (moduleAnswers.llmSystemPrompt?.trim()) envLines.push(`LLM_SYSTEM_PROMPT=${moduleAnswers.llmSystemPrompt.trim()}`);
1691
+ if (envLines.length > 0) {
1692
+ await fs.ensureDir(path.join(targetDir, 'functions'));
1693
+ await fs.appendFile(path.join(targetDir, 'functions', '.env'), envLines.join('\n') + '\n', 'utf8');
1694
+ }
1695
+ } catch (_) {}
1696
+ }
1697
+ }
1698
+
1699
+ // ── Results ─────────────────────────────────────────────────────────────
1700
+ console.log('');
1701
+ result.steps.forEach((s) => printStepResult(s, language));
1702
+
1703
+ // ── Supabase: link + db push + functions deploy (when project was created or existing selected) ─
1704
+ const supabaseSetupPayload = (supabaseCreate && supabaseCreateResult?.ok)
1705
+ ? { projectRef: supabaseCreateResult.projectRef, dbPassword: supabaseDbPassword }
1706
+ : (supabaseExistingResult?.ok ? { projectRef: supabaseExistingResult.projectRef, dbPassword: supabaseExistingResult.dbPassword } : null);
1707
+
1708
+ if (backend === 'supabase') {
1709
+ // ── Auto-resolve Google credentials from flutterfire output files ────────
1710
+ const flutterfireOk = result.steps.find((s) => s.name === 'flutterfire')?.ok;
1711
+ if (flutterfireOk) {
1712
+ const fileCreds = await readSupabaseGoogleCredentials(targetDir);
1713
+ googleWebClientId = fileCreds.webClientId;
1714
+ googleIosClientId = fileCreds.iosClientId;
1715
+
1716
+ // Enable Google Sign-In in Firebase Auth so Identity Toolkit stores the client secret.
1717
+ // This is the same step that setupFromScratch runs for Firebase backend.
1718
+ // For Supabase, Firebase Auth is not used for auth, but enabling Google here is the
1719
+ // only way to make the client secret available via the Identity Toolkit API.
1720
+ // Non-fatal: silently ignored if it fails (e.g. API not yet propagated).
1721
+ if (answers.firebaseProjectId) {
1722
+ await enableAuthProviders(answers.firebaseProjectId);
1723
+ }
1724
+
1725
+ // Try to get the Client Secret from Identity Toolkit API (requires gcloud auth)
1726
+ if (answers.firebaseProjectId && googleWebClientId) {
1727
+ googleClientSecret = await getGoogleClientSecretViaGcloud(answers.firebaseProjectId);
1728
+ }
1729
+
1730
+ // Always write google_auth_options.dart with whatever credentials we have
1731
+ if (googleWebClientId) {
1732
+ const gaResult = await writeSupabaseGoogleAuthOptions(targetDir, googleWebClientId, googleIosClientId);
1733
+ printStepResult({ name: 'google-auth-options', ok: gaResult.ok, detail: gaResult.error }, language);
1734
+ }
1735
+ }
1736
+
1737
+ if (supabaseSetupPayload) {
1738
+ // ── FCM Service Account key (best effort via gcloud — Firebase uses ADC, Supabase needs JSON) ──
1739
+ let fcmServiceAccountJson = null;
1740
+ if (answers.firebaseProjectId) {
1741
+ const fcmSpinner = ora(kleur.cyan('Gerando chave FCM (Service Account)…')).start();
1742
+ const fcmResult = await createFcmServiceAccountKey(answers.firebaseProjectId);
1743
+ if (fcmResult.ok) {
1744
+ fcmServiceAccountJson = fcmResult.json;
1745
+ fcmSpinner.stop();
1746
+ printStepResult({ name: 'fcm-key', ok: true }, language);
1747
+ } else {
1748
+ fcmSpinner.stop();
1749
+ printStepResult({ name: 'fcm-key', ok: false, detail: 'configure FIREBASE_SERVICE_ACCOUNT_JSON manualmente' }, language);
1750
+ }
1751
+ }
1752
+
1753
+ const setupSpinner = ora(kleur.cyan(tr('new.supabase.setup'))).start();
1754
+ try {
1755
+ const secrets = {
1756
+ firebaseProjectId: answers.firebaseProjectId,
1757
+ ...(fcmServiceAccountJson ? { firebaseServiceAccountJson: fcmServiceAccountJson } : {}),
1758
+ ...(answers.modules?.includes('revenuecat') ? {
1759
+ rcWebhookKey: moduleAnswers.rcWebhookKey,
1760
+ metaAccessToken: moduleAnswers.metaAccessToken,
1761
+ metaDatasetId: moduleAnswers.metaDatasetId,
1762
+ } : {}),
1763
+ ...(answers.modules?.includes('llm_chat') && !moduleAnswers.llmConfigureLater ? {
1764
+ llmApiKey: moduleAnswers.llmApiKey,
1765
+ llmProvider: moduleAnswers.llmProvider,
1766
+ llmSystemPrompt: moduleAnswers.llmSystemPrompt,
1767
+ } : {}),
1768
+ };
1769
+ const google = googleWebClientId && googleClientSecret
1770
+ ? { webClientId: googleWebClientId, clientSecret: googleClientSecret }
1771
+ : {};
1772
+ const setupSteps = await setupLinkedProject(
1773
+ targetDir,
1774
+ supabaseSetupPayload.projectRef,
1775
+ supabaseSetupPayload.dbPassword,
1776
+ secrets,
1777
+ google
1778
+ );
1779
+ setupSpinner.stop();
1780
+ setupSteps.forEach((s) => printStepResult(s, language));
1781
+ } catch (err) {
1782
+ setupSpinner.fail(kleur.red(err.message));
1783
+ console.log(kleur.yellow(` ${tr('new.supabase.setupManual')}`));
1784
+ }
1785
+ }
1786
+ }
1787
+
1788
+ // ── API: FCM Service Account key — save to .kasy/ for server configuration ──
1789
+ if (backend === 'api' && answers.firebaseProjectId) {
1790
+ const fcmSpinner = ora(kleur.cyan('Gerando chave FCM (Service Account)…')).start();
1791
+ const fcmResult = await createFcmServiceAccountKey(answers.firebaseProjectId);
1792
+ if (fcmResult.ok) {
1793
+ fcmSpinner.stop();
1794
+ try {
1795
+ const kasyDir = path.join(targetDir, '.kasy');
1796
+ await fs.ensureDir(kasyDir);
1797
+ await fs.writeFile(path.join(kasyDir, 'fcm-service-account.json'), fcmResult.json, 'utf8');
1798
+ // Ensure this credential file is not committed to git
1799
+ const gitignorePath = path.join(targetDir, '.gitignore');
1800
+ const gitignoreEntry = '.kasy/fcm-service-account.json';
1801
+ if (await fs.pathExists(gitignorePath)) {
1802
+ const existing = await fs.readFile(gitignorePath, 'utf8');
1803
+ if (!existing.includes(gitignoreEntry)) {
1804
+ await fs.appendFile(gitignorePath, `\n# FCM service account key (credentials — do not commit)\n${gitignoreEntry}\n`, 'utf8');
1805
+ }
1806
+ }
1807
+ printStepResult({ name: 'fcm-key-saved', ok: true }, language);
1808
+ console.log(kleur.gray(` Configure no servidor: FIREBASE_SERVICE_ACCOUNT_JSON="$(cat .kasy/fcm-service-account.json)"`));
1809
+ } catch (_) {
1810
+ printStepResult({ name: 'fcm-key-saved', ok: false, detail: 'salvar arquivo de chave falhou' }, language);
1811
+ }
1812
+ } else {
1813
+ fcmSpinner.stop();
1814
+ printStepResult({ name: 'fcm-key', ok: false, detail: 'configure FIREBASE_SERVICE_ACCOUNT_JSON manualmente' }, language);
1815
+ }
1816
+ }
1817
+
1818
+ // ── SHA-1: register debug keystore for existing Firebase projects (all backends) ──
1819
+ // setupFromScratch already handles SHA-1 for new-project flow.
1820
+ // For existing projects, flutterfire configure registers the Android app but does NOT add SHA-1.
1821
+ // Without SHA-1, google_sign_in v7+ Credential Manager fails on Android with:
1822
+ // "CredManProvService: GetCredentialResponse error returned from framework"
1823
+ if (answers.firebaseProjectId && firebaseSetupMode === 'existing') {
1824
+ const flutterfireOkForSha1 = result.steps.find((s) => s.name === 'flutterfire')?.ok;
1825
+ if (flutterfireOkForSha1) {
1826
+ const sha1Spinner = ora(kleur.cyan('Registrando SHA-1 (Google Sign-In Android)…')).start();
1827
+ const sha1Result = await registerDebugSha1(answers.firebaseProjectId, answers.bundleId);
1828
+ sha1Spinner.stop();
1829
+ if (sha1Result.ok) {
1830
+ if (!sha1Result.existed) {
1831
+ printStepResult({ name: 'sha1', ok: true }, language);
1832
+ }
1833
+ // existed === true → already registered, silent success
1834
+ } else {
1835
+ console.log(kleur.yellow(` ⚠ SHA-1 não adicionado automaticamente: ${(sha1Result.sha1Error || '').slice(0, 120)}`));
1836
+ console.log(kleur.yellow(` Adicione manualmente para o Google Sign-In funcionar no Android:`));
1837
+ console.log(kleur.cyan(` https://console.firebase.google.com/project/${answers.firebaseProjectId}/settings/general/android:${answers.bundleId}`));
1838
+ }
1839
+ }
1840
+ }
1841
+
1842
+ const flutterfireStep = result.steps.find((s) => s.name === 'flutterfire');
1843
+ if (flutterfireStep && !flutterfireStep.ok) {
1844
+ const platforms = answers.includeWeb !== false ? 'android,ios,web' : 'android,ios';
1845
+ console.log(kleur.yellow(`\n ${tr('new.firebase.warn.flutterfire')}`));
1846
+ console.log(kleur.yellow(` ${tr('new.firebase.warn.flutterfire.manual')}`));
1847
+ console.log(
1848
+ kleur.cyan(
1849
+ ` dart pub global run flutterfire_cli:flutterfire configure` +
1850
+ ` --project=${answers.firebaseProjectId}` +
1851
+ ` --out lib/firebase_options_dev.dart --platforms=${platforms} --yes`
1852
+ )
1853
+ );
1854
+ }
1855
+
1856
+ // ── APNs reminder (all backends — required for iOS push notifications) ───────
1857
+ // Cannot be automated: the .p8 key only exists in Apple Developer Portal.
1858
+ if (answers.firebaseProjectId) {
1859
+ console.log('');
1860
+ console.log(kleur.bold().yellow(` ⚠ Push iOS: configure a APNs Key no Firebase Console`));
1861
+ console.log(kleur.gray(` 1. Apple Developer Portal → Keys → criar APNs Key (.p8)`));
1862
+ console.log(kleur.gray(` 2. Firebase Console → Cloud Messaging → app iOS → fazer upload da APNs Key`));
1863
+ console.log(kleur.cyan(` https://console.firebase.google.com/project/${answers.firebaseProjectId}/settings/cloudmessaging`));
1864
+ }
1865
+
1866
+ printSuccessCard(tr, answers, targetDir);
1867
+
1868
+ }
1869
+
1870
+ module.exports = { runNew };