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,1664 @@
1
+ /**
2
+ * Shared utilities for all backend generators.
3
+ * Single source of truth for dart-defines, launch.json, .env.example, environnements.
4
+ *
5
+ * @module scaffold/shared/generator-utils
6
+ */
7
+
8
+ const path = require('node:path');
9
+ const fs = require('fs-extra');
10
+ const { getStrings } = require('./template-strings');
11
+ const pkg = require('../../../package.json');
12
+
13
+ /** Backend IDs: firebase | supabase | api */
14
+ const BACKENDS = Object.freeze(['firebase', 'supabase', 'api']);
15
+
16
+ /**
17
+ * Build dart-define args for dev and prod environments.
18
+ * Backend-specific vars: Firebase (none extra), Supabase (BACKEND_URL, SUPABASE_TOKEN), API (BACKEND_URL).
19
+ *
20
+ * @param {'firebase'|'supabase'|'api'} backend
21
+ * @param {string[]} modules - e.g. ['revenuecat','sentry','analytics']
22
+ * @param {object} answers - moduleAnswers + backend-specific (supabaseUrl, supabaseAnonKey, apiBaseUrl)
23
+ * @returns {{ dev: string[], prod: string[] }}
24
+ */
25
+ function buildDartDefines(backend, modules, answers) {
26
+ const dev = ['--dart-define=ENV=dev'];
27
+ const prod = ['--dart-define=ENV=prod'];
28
+
29
+ if (backend === 'supabase') {
30
+ const url = (answers.supabaseUrl || '').replace(/"/g, '\\"');
31
+ const token = (answers.supabaseAnonKey || '').replace(/"/g, '\\"');
32
+ dev.push(`--dart-define=BACKEND_URL=${url}`);
33
+ dev.push(`--dart-define=SUPABASE_TOKEN=${token}`);
34
+ prod.push(`--dart-define=BACKEND_URL=${url}`);
35
+ prod.push(`--dart-define=SUPABASE_TOKEN=${token}`);
36
+ } else if (backend === 'api') {
37
+ const url = (answers.apiBaseUrl || 'https://YOUR_API_URL.com').replace(/"/g, '\\"');
38
+ dev.push(`--dart-define=BACKEND_URL=${url}`);
39
+ prod.push(`--dart-define=BACKEND_URL=${url}`);
40
+ }
41
+
42
+ if (modules.includes('revenuecat')) {
43
+ const androidKey = answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY';
44
+ const iosKey = answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY';
45
+ dev.push(`--dart-define=RC_ANDROID_API_KEY=${androidKey}`);
46
+ dev.push(`--dart-define=RC_IOS_API_KEY=${iosKey}`);
47
+ prod.push(`--dart-define=RC_ANDROID_API_KEY=${androidKey}`);
48
+ prod.push(`--dart-define=RC_IOS_API_KEY=${iosKey}`);
49
+ if (answers.revenuecatWeb) {
50
+ const webKey = answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY';
51
+ dev.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
52
+ prod.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
53
+ }
54
+ }
55
+ if (modules.includes('sentry')) {
56
+ const dsn = answers.sentryDsn || 'YOUR_SENTRY_DSN';
57
+ prod.push(`--dart-define=SENTRY_DSN=${dsn}`);
58
+ }
59
+ if (modules.includes('analytics')) {
60
+ const token = answers.mixpanelToken || 'YOUR_MIXPANEL_TOKEN';
61
+ dev.push(`--dart-define=MIXPANEL_TOKEN=${token}`);
62
+ prod.push(`--dart-define=MIXPANEL_TOKEN=${token}`);
63
+ }
64
+ if (modules.includes('llm_chat')) {
65
+ let endpoint = answers.llmChatEndpoint || '';
66
+ if (!endpoint) {
67
+ if (backend === 'supabase' && answers.supabaseUrl) {
68
+ endpoint = `${answers.supabaseUrl}/functions/v1/llm-chat`;
69
+ } else if (backend === 'api') {
70
+ endpoint = answers.apiBaseUrl ? `${answers.apiBaseUrl}/llm-chat` : 'YOUR_LLM_ENDPOINT';
71
+ } else {
72
+ // firebase — use the region configured for this project
73
+ const projectId = answers.firebaseProjectId || 'YOUR_PROJECT_ID';
74
+ const region = answers.functionsRegion || 'us-central1';
75
+ endpoint = `https://${region}-${projectId}.cloudfunctions.net/llmChat`;
76
+ }
77
+ }
78
+ dev.push(`--dart-define=LLM_CHAT_ENDPOINT=${endpoint}`);
79
+ prod.push(`--dart-define=LLM_CHAT_ENDPOINT=${endpoint}`);
80
+ }
81
+
82
+ return { dev, prod };
83
+ }
84
+
85
+ /**
86
+ * Write .vscode/launch.json with pre-configured dart-define entries.
87
+ *
88
+ * @param {string} projectDir - Project root
89
+ * @param {string} appName - Display name
90
+ * @param {'firebase'|'supabase'|'api'} backend
91
+ * @param {string[]} modules
92
+ * @param {object} answers
93
+ * @param {string} [language='en'] - User's CLI language (en, pt, es)
94
+ */
95
+ async function writeVsCodeLaunch(projectDir, appName, backend, modules, answers, language = 'en') {
96
+ const { dev, prod } = buildDartDefines(backend, modules, answers);
97
+ const l = getStrings(language).launch;
98
+ const launchJson = {
99
+ version: '0.2.0',
100
+ configurations: [
101
+ { name: `${appName} (${l.dev})`, request: 'launch', type: 'dart', args: dev },
102
+ { name: `${appName} (${l.prod})`, request: 'launch', type: 'dart', args: prod },
103
+ ],
104
+ };
105
+ await fs.ensureDir(path.join(projectDir, '.vscode'));
106
+ await fs.outputFile(
107
+ path.join(projectDir, '.vscode', 'launch.json'),
108
+ JSON.stringify(launchJson, null, 2) + '\n',
109
+ 'utf8'
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Write .env.example as reference for developers.
115
+ *
116
+ * @param {string} projectDir
117
+ * @param {string[]} modules
118
+ * @param {object} answers
119
+ * @param {string} [language='en'] - User's CLI language (en, pt, es)
120
+ */
121
+ async function writeEnvExample(projectDir, modules, answers, language = 'en') {
122
+ const t = getStrings(language).envExample;
123
+ const lines = [
124
+ t.generated,
125
+ t.dartDefine,
126
+ t.envSource,
127
+ '',
128
+ t.envDev,
129
+ ];
130
+
131
+ // Backend-specific variables
132
+ if (answers.supabaseUrl) {
133
+ lines.push('');
134
+ lines.push(t.supabase);
135
+ lines.push(`BACKEND_URL=${answers.supabaseUrl || 'https://YOUR_PROJECT.supabase.co'}`);
136
+ lines.push(`SUPABASE_TOKEN=${answers.supabaseAnonKey || 'YOUR_SUPABASE_ANON_KEY'}`);
137
+ } else if (answers.apiBaseUrl) {
138
+ lines.push('');
139
+ lines.push(t.apiRest);
140
+ lines.push(`BACKEND_URL=${answers.apiBaseUrl || 'https://YOUR_API_URL.com'}`);
141
+ }
142
+
143
+ // Optional module variables
144
+ if (modules.includes('revenuecat')) {
145
+ lines.push('');
146
+ lines.push(t.revenuecat);
147
+ lines.push(`RC_ANDROID_API_KEY=${answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY'}`);
148
+ lines.push(`RC_IOS_API_KEY=${answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY'}`);
149
+ if (answers.revenuecatWeb) {
150
+ lines.push(`RC_WEB_API_KEY=${answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY'}`);
151
+ }
152
+ }
153
+ if (modules.includes('sentry')) {
154
+ lines.push('');
155
+ lines.push(t.sentry);
156
+ lines.push(`SENTRY_DSN=${answers.sentryDsn || 'YOUR_SENTRY_DSN'}`);
157
+ }
158
+ if (modules.includes('analytics')) {
159
+ lines.push('');
160
+ lines.push(t.mixpanel);
161
+ lines.push(`MIXPANEL_TOKEN=${answers.mixpanelToken || 'YOUR_MIXPANEL_TOKEN'}`);
162
+ }
163
+ if (modules.includes('llm_chat')) {
164
+ lines.push('');
165
+ lines.push(t.llmChat);
166
+ lines.push(`LLM_CHAT_ENDPOINT=${answers.llmChatEndpoint || 'YOUR_FUNCTION_URL'}`);
167
+ }
168
+
169
+ await fs.outputFile(path.join(projectDir, '.env.example'), lines.join('\n') + '\n', 'utf8');
170
+ }
171
+
172
+ /**
173
+ * Create .env from .env.example on first scaffold.
174
+ *
175
+ * @param {string} projectDir
176
+ */
177
+ async function writeEnvFileIfMissing(projectDir) {
178
+ const envPath = path.join(projectDir, '.env');
179
+ if (await fs.pathExists(envPath)) return;
180
+ const envExamplePath = path.join(projectDir, '.env.example');
181
+ if (!(await fs.pathExists(envExamplePath))) return;
182
+ const content = await fs.readFile(envExamplePath, 'utf8');
183
+ await fs.outputFile(envPath, content, 'utf8');
184
+ }
185
+
186
+ /**
187
+ * Write .firebaserc with project ID.
188
+ *
189
+ * @param {string} projectDir
190
+ * @param {string} firebaseProjectId
191
+ */
192
+ async function writeFirebaserc(projectDir, firebaseProjectId) {
193
+ await fs.outputFile(
194
+ path.join(projectDir, '.firebaserc'),
195
+ JSON.stringify({ projects: { default: firebaseProjectId } }, null, 2) + '\n',
196
+ 'utf8'
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Write environnements.dart overrides for backend-specific config.
202
+ *
203
+ * @param {string} projectDir
204
+ * @param {'firebase'|'supabase'|'api'} backend
205
+ * @param {Record<string,string>} tokens
206
+ * @param {object} answers - supabaseUrl, supabaseAnonKey, apiBaseUrl
207
+ */
208
+ async function writeEnvironnementsOverrides(projectDir, backend, tokens, answers) {
209
+ const envPath = path.join(projectDir, 'lib', 'environnements.dart');
210
+ if (!(await fs.pathExists(envPath))) return;
211
+
212
+ let content = await fs.readFile(envPath, 'utf8');
213
+
214
+ if (backend === 'supabase') {
215
+ const url = (answers.supabaseUrl || 'https://YOUR_PROJECT.supabase.co').replace(/'/g, "\\'");
216
+ const token = (answers.supabaseAnonKey || 'YOUR_SUPABASE_ANON_KEY').replace(/'/g, "\\'");
217
+ content = content.replace(
218
+ /defaultValue:\s*'https:\/\/supabaseplaceholder\.supabase\.co'/g,
219
+ `defaultValue: '${url}'`
220
+ );
221
+ content = content.replace(
222
+ /defaultValue:\s*'sb_publishable_placeholder'/g,
223
+ `defaultValue: '${token}'`
224
+ );
225
+ } else if (backend === 'api') {
226
+ const url = (answers.apiBaseUrl || 'https://YOUR_API_URL.com').replace(/'/g, "\\'");
227
+ content = content.replace(
228
+ /(\/\/ please replace it with your own backend URL\s*\n\s*defaultValue: )''/,
229
+ `$1'${url}'`
230
+ );
231
+ }
232
+
233
+ for (const [from, to] of Object.entries(tokens)) {
234
+ content = content.replaceAll(from, to);
235
+ }
236
+ await fs.outputFile(envPath, content, 'utf8');
237
+ }
238
+
239
+ /**
240
+ * Generate lib/router.dart based on selected modules.
241
+ * Routes and imports are only included for selected modules,
242
+ * eliminating unused code and import errors at compile time.
243
+ *
244
+ * @param {string} projectDir
245
+ * @param {string[]} modules - Selected module IDs
246
+ * @param {string} packageName - Dart package name (e.g. my_app)
247
+ */
248
+ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'basic') {
249
+ const pkg = packageName;
250
+ const withFeedback = modules.includes('feedback');
251
+ const withLlmChat = modules.includes('llm_chat');
252
+ const withLocalNotifications = modules.includes('local_notifications');
253
+ const withOnboarding = modules.includes('onboarding');
254
+ const withRevenuecat = modules.includes('revenuecat');
255
+ const withAnalytics = modules.includes('analytics');
256
+
257
+ const fallback = withOnboarding ? '/onboarding' : '/signin';
258
+
259
+ const lines = [];
260
+
261
+ // ── Imports ────────────────────────────────────────────────────────────────
262
+ lines.push(`import 'package:flutter/material.dart';`);
263
+ lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
264
+ lines.push(`import 'package:go_router/go_router.dart';`);
265
+ lines.push(`import 'package:${pkg}/core/bottom_menu/bottom_menu.dart';`);
266
+ if (withAnalytics) {
267
+ lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
268
+ }
269
+ lines.push(`import 'package:${pkg}/core/guards/user_info_guard.dart';`);
270
+ lines.push(`import 'package:${pkg}/core/widgets/page_not_found.dart';`);
271
+ lines.push(`import 'package:${pkg}/features/authentication/ui/phone_auth_page.dart';`);
272
+ lines.push(`import 'package:${pkg}/features/authentication/ui/recover_password_page.dart';`);
273
+ lines.push(`import 'package:${pkg}/features/authentication/ui/signin_page.dart';`);
274
+ lines.push(`import 'package:${pkg}/features/authentication/ui/signup_page.dart';`);
275
+ if (withFeedback) {
276
+ lines.push(`import 'package:${pkg}/features/feedbacks/ui/component/add_feature_form.dart';`);
277
+ lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
278
+ }
279
+ if (withLlmChat) {
280
+ lines.push(`import 'package:${pkg}/features/llm_chat/llm_chat_page.dart';`);
281
+ }
282
+ if (withLocalNotifications) {
283
+ lines.push(`import 'package:${pkg}/features/local_reminder/ui/reminder_page.dart';`);
284
+ }
285
+ if (withOnboarding) {
286
+ lines.push(`import 'package:${pkg}/features/onboarding/ui/onboarding_page.dart';`);
287
+ }
288
+ if (withRevenuecat) {
289
+ lines.push(`import 'package:${pkg}/features/subscription/ui/component/premium_page_factory.dart';`);
290
+ lines.push(`import 'package:${pkg}/features/subscription/ui/premium_page.dart';`);
291
+ }
292
+
293
+ // ── Provider ──────────────────────────────────────────────────────────────
294
+ lines.push('');
295
+ lines.push(`final goRouterProvider = Provider<GoRouter>((ref) => generateRouter());`);
296
+ lines.push('');
297
+ lines.push(`extension GoRouterRiverpod on Ref {`);
298
+ lines.push(` GoRouter get goRouter => read(goRouterProvider);`);
299
+ lines.push(`}`);
300
+ lines.push('');
301
+ lines.push(`final navigatorKey = GlobalKey<NavigatorState>();`);
302
+ lines.push('');
303
+
304
+ // ── generateRouter ─────────────────────────────────────────────────────────
305
+ lines.push(`GoRouter generateRouter({`);
306
+ lines.push(` String? initialLocation,`);
307
+ lines.push(` List<GoRoute>? additionalRoutes,`);
308
+ lines.push(` List<NavigatorObserver>? observers,`);
309
+ lines.push(`}) {`);
310
+ lines.push(` return GoRouter(`);
311
+ lines.push(` initialLocation: '/',`);
312
+ lines.push(` navigatorKey: navigatorKey,`);
313
+ lines.push(` errorBuilder: (context, state) => const PageNotFound(),`);
314
+ lines.push(` observers: [`);
315
+ if (withAnalytics) {
316
+ lines.push(` AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),`);
317
+ }
318
+ lines.push(` ...?observers,`);
319
+ lines.push(` ],`);
320
+ lines.push(` routes: [`);
321
+
322
+ // Home
323
+ lines.push(` GoRoute(`);
324
+ lines.push(` name: 'home',`);
325
+ lines.push(` path: '/',`);
326
+ lines.push(` builder: (context, state) => const UserInfosGuard(`);
327
+ lines.push(` fallbackRoute: '${fallback}',`);
328
+ lines.push(` child: BottomMenu(),`);
329
+ lines.push(` ),`);
330
+ lines.push(` ),`);
331
+
332
+ // Onboarding
333
+ if (withOnboarding) {
334
+ lines.push(` GoRoute(`);
335
+ lines.push(` name: 'onboarding',`);
336
+ lines.push(` path: '/onboarding',`);
337
+ lines.push(` builder: (context, state) => const OnboardingPage(),`);
338
+ lines.push(` ),`);
339
+ }
340
+
341
+ // Auth (always)
342
+ lines.push(` GoRoute(`);
343
+ lines.push(` name: 'signup',`);
344
+ lines.push(` path: '/signup',`);
345
+ lines.push(` builder: (context, state) => const SignupPage(),`);
346
+ lines.push(` ),`);
347
+ lines.push(` GoRoute(`);
348
+ lines.push(` name: 'signin',`);
349
+ lines.push(` path: '/signin',`);
350
+ lines.push(` builder: (context, state) => const SigninPage(),`);
351
+ lines.push(` ),`);
352
+ lines.push(` GoRoute(`);
353
+ lines.push(` name: 'signinWithPhone',`);
354
+ lines.push(` path: '/signinWithPhone',`);
355
+ lines.push(` builder: (context, state) => const PhoneAuthPage(),`);
356
+ lines.push(` ),`);
357
+
358
+ // Premium (revenuecat)
359
+ if (withRevenuecat) {
360
+ const paywallFactory = ['basic', 'minimal', 'withSwitch', 'basicRow'].includes(defaultPaywall)
361
+ ? `PaywallFactory.${defaultPaywall}`
362
+ : 'PaywallFactory.basic';
363
+ lines.push(` GoRoute(`);
364
+ lines.push(` name: 'premium',`);
365
+ lines.push(` path: '/premium',`);
366
+ lines.push(` builder: (context, state) => const PremiumPage(paywall: ${paywallFactory}),`);
367
+ lines.push(` ),`);
368
+ }
369
+
370
+ // Feedback
371
+ if (withFeedback) {
372
+ lines.push(` GoRoute(`);
373
+ lines.push(` name: 'feedback',`);
374
+ lines.push(` path: '/feedback',`);
375
+ lines.push(` builder: (context, state) => const FeedbackPage(),`);
376
+ lines.push(` ),`);
377
+ lines.push(` GoRoute(`);
378
+ lines.push(` name: 'feedback_new',`);
379
+ lines.push(` path: '/feedback/new',`);
380
+ lines.push(` builder: (context, state) => const AddFeatureComponent(),`);
381
+ lines.push(` ),`);
382
+ }
383
+
384
+ // LLM Chat
385
+ if (withLlmChat) {
386
+ lines.push(` GoRoute(`);
387
+ lines.push(` name: 'assistant',`);
388
+ lines.push(` path: '/assistant',`);
389
+ lines.push(` builder: (context, state) => const LlmChatPage(),`);
390
+ lines.push(` ),`);
391
+ }
392
+
393
+ // Local notifications reminder
394
+ if (withLocalNotifications) {
395
+ lines.push(` GoRoute(`);
396
+ lines.push(` name: 'reminder',`);
397
+ lines.push(` path: '/reminder',`);
398
+ lines.push(` builder: (context, state) => const ReminderPage(),`);
399
+ lines.push(` ),`);
400
+ }
401
+
402
+ // Recover password + 404 (always)
403
+ lines.push(` GoRoute(`);
404
+ lines.push(` name: 'recover_password',`);
405
+ lines.push(` path: '/recover_password',`);
406
+ lines.push(` builder: (context, state) => const RecoverPasswordPage(),`);
407
+ lines.push(` ),`);
408
+ lines.push(` GoRoute(`);
409
+ lines.push(` name: '404',`);
410
+ lines.push(` path: '/404',`);
411
+ lines.push(` builder: (context, state) => const PageNotFound(),`);
412
+ lines.push(` ),`);
413
+
414
+ lines.push(` ],`);
415
+ lines.push(` );`);
416
+ lines.push(`}`);
417
+
418
+ await fs.ensureDir(path.join(projectDir, 'lib'));
419
+ await fs.outputFile(
420
+ path.join(projectDir, 'lib', 'router.dart'),
421
+ lines.join('\n') + '\n',
422
+ 'utf8'
423
+ );
424
+ }
425
+
426
+ /**
427
+ * Write lib/core/config/features.dart with feature flags from user choices.
428
+ *
429
+ * @param {string} projectDir
430
+ * @param {string[]} modules
431
+ * @param {object} [answers={}] - moduleAnswers (revenuecatWeb, etc.)
432
+ * @param {string} [language='en'] - User's CLI language (en, pt, es)
433
+ */
434
+ async function writeFeaturesConfig(projectDir, modules, answers = {}, language = 'en') {
435
+ const withOnboarding = modules.includes('onboarding');
436
+ const withLlmChat = modules.includes('llm_chat');
437
+ const withFeedback = modules.includes('feedback');
438
+ const withRevenuecat = modules.includes('revenuecat');
439
+ const withLocalNotifications = modules.includes('local_notifications');
440
+ const withWeb = modules.includes('web');
441
+ const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && answers.revenuecatWeb);
442
+
443
+ const f = getStrings(language).features;
444
+ const content = `${f.comment1}
445
+ ${f.comment2}
446
+ // ignore_for_file: avoid_redundant_argument_values
447
+ const bool withOnboarding = ${withOnboarding};
448
+ const bool withLlmChat = ${withLlmChat};
449
+ const bool withFeedback = ${withFeedback};
450
+ const bool withRevenuecat = ${withRevenuecat};
451
+ const bool withLocalNotifications = ${withLocalNotifications};
452
+ ${f.comment3}
453
+ ${f.comment4}
454
+ ${f.comment5}
455
+ ${f.comment6}
456
+ const bool withWeb = ${withWeb};
457
+ ${f.comment7}
458
+ const bool revenuecatWeb = ${revenuecatWeb};
459
+ `;
460
+ await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'config'));
461
+ await fs.outputFile(
462
+ path.join(projectDir, 'lib', 'core', 'config', 'features.dart'),
463
+ content,
464
+ 'utf8'
465
+ );
466
+ }
467
+
468
+ /**
469
+ * Write kit_setup.json reflecting user choices (backend, modules).
470
+ *
471
+ * @param {string} projectDir
472
+ * @param {object} options - appName, bundleId, backend, modules, firebaseProjectId, supabaseUrl, supabaseAnonKey
473
+ */
474
+ async function writeKitSetup(projectDir, options) {
475
+ const { appName, bundleId, backend, modules = [], firebaseProjectId, supabaseUrl, supabaseAnonKey, moduleAnswers = {} } = options;
476
+ const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && moduleAnswers.revenuecatWeb);
477
+ const config = {
478
+ appName: appName || 'App',
479
+ bundleId: bundleId || 'com.example.app',
480
+ backendProvider: backend || 'firebase',
481
+ functionsRegion: moduleAnswers.functionsRegion || 'us-central1',
482
+ subscriptionModule: modules.includes('revenuecat'),
483
+ feedbackModule: modules.includes('feedback'),
484
+ useFirebaseAuth: true,
485
+ useFirebaseFirestore: backend === 'firebase',
486
+ storageProvider: backend === 'firebase' ? 'firebase' : backend === 'supabase' ? 'supabase' : 'api',
487
+ webCompat: modules.includes('web'),
488
+ revenuecatWeb,
489
+ internationalization: true,
490
+ useSentry: modules.includes('sentry'),
491
+ withOnboarding: modules.includes('onboarding'),
492
+ remoteConfigProvider: backend === 'firebase' ? 'firebase' : null,
493
+ analyticsProvider: modules.includes('analytics') ? 'mixpanel' : null,
494
+ anonymousSignup: true,
495
+ withFacebookPixel: modules.includes('facebook'),
496
+ socialAuthList: ['google', 'googlePlay', 'facebook', 'apple'],
497
+ firebaseProjectId: firebaseProjectId || null,
498
+ supabaseProjectId: supabaseUrl ? supabaseUrl.replace(/^https:\/\/([^.]+).*/, '$1') : null,
499
+ withDashboard: false,
500
+ supabasePublishableKey: supabaseAnonKey || null,
501
+ templateVersion: '1.0.0',
502
+ cliVersion: pkg.version,
503
+ };
504
+ await fs.outputFile(
505
+ path.join(projectDir, 'kit_setup.json'),
506
+ JSON.stringify(config, null, 2) + '\n',
507
+ 'utf8'
508
+ );
509
+ }
510
+
511
+ /**
512
+ * Write Makefile with run targets for iOS, Android, and default device.
513
+ * Inlines dart-define flags so `make run` works without VS Code.
514
+ *
515
+ * @param {string} projectDir - Project root
516
+ * @param {string} [language='en'] - User's CLI language (en, pt, es)
517
+ * @param {string} [backend] - backend id (firebase|supabase|api)
518
+ * @param {string[]} [modules] - selected modules
519
+ * @param {object} [answers] - user answers (credentials)
520
+ */
521
+ async function writeMakefile(projectDir, language = 'en', backend, modules, answers) {
522
+ const m = getStrings(language).makefile;
523
+
524
+ let definesList = '';
525
+ if (backend && modules && answers) {
526
+ const { dev } = buildDartDefines(backend, modules, answers);
527
+ if (dev.length > 0) {
528
+ definesList = dev.map((d, i) =>
529
+ i === 0 ? ` ${d} \\` : ` ${d} \\`
530
+ ).join('\n');
531
+ // Remove trailing backslash from last line
532
+ definesList = definesList.replace(/ \\$/, '');
533
+ }
534
+ }
535
+
536
+ const definesBlock = definesList
537
+ ? `DEFINES := \\\n${definesList}\n\n`
538
+ : '';
539
+ const runCmd = definesList ? 'flutter run $(DEFINES)' : 'flutter run';
540
+ const runWebCmd = definesList ? 'flutter run -d chrome $(DEFINES)' : 'flutter run -d chrome';
541
+
542
+ const content = `${m.comment1}
543
+ ${m.comment2}
544
+ ${m.comment3}
545
+ ${m.comment4}
546
+
547
+ ${definesBlock}.PHONY: run run-ios run-android run-web ${m.releaseIosTarget}
548
+
549
+ run:
550
+ \t${runCmd}
551
+
552
+ run-ios:
553
+ \t${runCmd}
554
+
555
+ run-android:
556
+ \t${runCmd}
557
+
558
+ run-web:
559
+ \t${runWebCmd}
560
+
561
+ ${m.releaseIos}
562
+ ${m.releaseIosTarget}:
563
+ \tbash scripts/release-ios.sh
564
+ `;
565
+ await fs.outputFile(path.join(projectDir, 'Makefile'), content, 'utf8');
566
+ }
567
+
568
+ /**
569
+ * Write lib/core/data/api/analytics_api.dart as a no-op stub (no Mixpanel dependency).
570
+ * Called when the user does NOT select the analytics module.
571
+ *
572
+ * @param {string} projectDir
573
+ * @param {string} packageName
574
+ */
575
+ async function writeNoOpAnalyticsApi(projectDir, packageName) {
576
+ const pkg = packageName;
577
+ const content = `import 'package:flutter/material.dart';
578
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
579
+ import 'package:${pkg}/core/data/models/user.dart';
580
+ import 'package:${pkg}/core/initializer/onstart_service.dart';
581
+
582
+ // No-op analytics. Run: kasy add analytics to activate Mixpanel.
583
+ final analyticsApiProvider = Provider<AnalyticsApi>((ref) => const NoOpAnalyticsApi());
584
+
585
+ abstract class AnalyticsApi implements OnStartService {
586
+ Future<void> logEvent(String name, Map<String, dynamic> params);
587
+ Future<void> logSignout();
588
+ Future<void> identify(User user);
589
+ }
590
+
591
+ class NoOpAnalyticsApi implements AnalyticsApi {
592
+ const NoOpAnalyticsApi();
593
+ @override Future<void> init() async {}
594
+ @override Future<void> logEvent(String name, Map<String, dynamic> params) async {}
595
+ @override Future<void> logSignout() async {}
596
+ @override Future<void> identify(User user) async {}
597
+ }
598
+
599
+ class AnalyticsObserver extends RouteObserver<ModalRoute<dynamic>> {
600
+ final AnalyticsApi analyticsApi;
601
+ final String? prefix;
602
+ AnalyticsObserver({required this.analyticsApi, this.prefix});
603
+ @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {}
604
+ @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {}
605
+ @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {}
606
+ @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {}
607
+ @override void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {}
608
+ @override void didStopUserGesture() {}
609
+ }
610
+ `;
611
+ await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'data', 'api'));
612
+ await fs.outputFile(
613
+ path.join(projectDir, 'lib', 'core', 'data', 'api', 'analytics_api.dart'),
614
+ content,
615
+ 'utf8'
616
+ );
617
+ }
618
+
619
+ /**
620
+ * Write lib/core/data/api/tracking_api.dart as a no-op stub (no facebook/purchases/sentry deps).
621
+ * Called when the user does NOT select the facebook module.
622
+ *
623
+ * @param {string} projectDir
624
+ * @param {string} packageName
625
+ */
626
+ async function writeNoOpTrackingApi(projectDir, packageName) {
627
+ const pkg = packageName;
628
+ const content = `import 'package:flutter_riverpod/flutter_riverpod.dart';
629
+ import 'package:${pkg}/core/initializer/onstart_service.dart';
630
+ import 'package:${pkg}/core/states/models/user_state.dart';
631
+ import 'package:${pkg}/core/states/user_state_notifier.dart';
632
+ import 'package:${pkg}/features/notifications/repositories/device_repository.dart';
633
+
634
+ // No-op tracking. Run: kasy add facebook to activate Facebook Events.
635
+ final facebookEventApiProvider = Provider(
636
+ (ref) => FacebookEventApi(
637
+ deviceRepository: ref.watch(deviceRepositoryProvider),
638
+ userState: ref.watch(userStateNotifierProvider),
639
+ ),
640
+ );
641
+
642
+ class FacebookEventApi implements OnStartService {
643
+ final DeviceRepository deviceRepository;
644
+ final UserState userState;
645
+ FacebookEventApi({required this.deviceRepository, required this.userState});
646
+ @override Future<void> init() async {}
647
+ Future<void> initUser(String userId) async {}
648
+ Future<void> requestIDFA() async {}
649
+ Future<void> logMetaStartTrial(String orderId, double price, String currency) async {}
650
+ Future<void> logMetaSubscribe(String orderId, double price, String currency) async {}
651
+ }
652
+ `;
653
+ await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'data', 'api'));
654
+ await fs.outputFile(
655
+ path.join(projectDir, 'lib', 'core', 'data', 'api', 'tracking_api.dart'),
656
+ content,
657
+ 'utf8'
658
+ );
659
+ }
660
+
661
+ /**
662
+ * Generate lib/main.dart based on backend + selected modules.
663
+ *
664
+ * @param {string} projectDir
665
+ * @param {'firebase'|'supabase'|'api'} backend
666
+ * @param {string[]} modules
667
+ * @param {string} packageName
668
+ * @param {object} [options={}] - supabaseUrl, supabaseAnonKey
669
+ */
670
+ async function writeMainDart(projectDir, backend, modules, packageName, options = {}) {
671
+ const pkg = packageName;
672
+ const withSentry = modules.includes('sentry');
673
+ const withWidget = modules.includes('widget');
674
+ const withRevenuecat = modules.includes('revenuecat');
675
+ const withSupabase = backend === 'supabase';
676
+ const withFirebase = backend === 'firebase' || backend === 'supabase'; // Firebase used for auth/notifications in all backends currently
677
+
678
+ const lines = [];
679
+
680
+ // ── Imports ────────────────────────────────────────────────────────────────
681
+ if (withFirebase) {
682
+ lines.push(`import 'package:firebase_core/firebase_core.dart';`);
683
+ }
684
+ lines.push(`import 'package:flutter/foundation.dart';`);
685
+ lines.push(`import 'package:flutter/material.dart';`);
686
+ lines.push(`import 'package:flutter/services.dart';`);
687
+ lines.push(`import 'package:flutter_localizations/flutter_localizations.dart';`);
688
+ lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
689
+ lines.push(`import 'package:logger/logger.dart';`);
690
+ lines.push(`import 'package:${pkg}/core/config/app_env.dart';`);
691
+ lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
692
+ lines.push(`import 'package:${pkg}/core/data/api/remote_config_api.dart';`);
693
+ lines.push(`import 'package:${pkg}/core/data/api/tracking_api.dart';`);
694
+ if (withWidget) {
695
+ lines.push(`import 'package:${pkg}/core/home_widgets/home_widget_service.dart';`);
696
+ }
697
+ lines.push(`import 'package:${pkg}/core/initializer/onstart_widget.dart';`);
698
+ lines.push(`import 'package:${pkg}/core/shared_preferences/shared_preferences.dart';`);
699
+ lines.push(`import 'package:${pkg}/core/states/user_state_notifier.dart';`);
700
+ lines.push(`import 'package:${pkg}/core/theme/theme.dart';`);
701
+ lines.push(`import 'package:${pkg}/environnements.dart';`);
702
+ if (withFirebase) {
703
+ lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
704
+ }
705
+ lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
706
+ lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
707
+ lines.push(`import 'package:${pkg}/features/notifications/api/local_notifier.dart';`);
708
+ lines.push(`import 'package:${pkg}/features/notifications/repositories/notifications_repository.dart';`);
709
+ if (withRevenuecat) {
710
+ lines.push(`import 'package:${pkg}/features/subscription/repositories/subscription_repository.dart';`);
711
+ }
712
+ lines.push(`import 'package:${pkg}/router.dart';`);
713
+ if (withSentry) {
714
+ lines.push(`import 'package:sentry_flutter/sentry_flutter.dart';`);
715
+ }
716
+ lines.push(`import 'package:shared_preferences/shared_preferences.dart';`);
717
+ if (withSupabase) {
718
+ lines.push(`import 'package:supabase_flutter/supabase_flutter.dart';`);
719
+ }
720
+ lines.push('');
721
+
722
+ // ── main() ────────────────────────────────────────────────────────────────
723
+ lines.push(`void main() async {`);
724
+ lines.push(` WidgetsFlutterBinding.ensureInitialized();`);
725
+ lines.push(` await AppEnv.load();`);
726
+ lines.push(` final env = Environment.fromEnv();`);
727
+ lines.push(` final logger = Logger();`);
728
+ lines.push(` logger.i('Starting app in \${env.name} mode');`);
729
+ lines.push(` // I like to force portrait mode on mobile devices`);
730
+ lines.push(` // What is the last time you used an app in landscape mode?`);
731
+ lines.push(` SystemChrome.setPreferredOrientations([`);
732
+ lines.push(` DeviceOrientation.portraitUp,`);
733
+ lines.push(` DeviceOrientation.portraitDown,`);
734
+ lines.push(` ]);`);
735
+ lines.push('');
736
+ lines.push(` // initialize shared preferences for theme and locale`);
737
+ lines.push(` final sharedPrefs = await SharedPreferences.getInstance();`);
738
+ lines.push('');
739
+ lines.push(` // Restore saved locale; fall back to device locale if none saved.`);
740
+ lines.push(` // Supported locales: en, pt, es. If the device locale is not one of these,`);
741
+ lines.push(` // the base locale (en) is used as fallback (configured in slang.yaml).`);
742
+ lines.push(` // To change the default locale, update \`base_locale\` in slang.yaml.`);
743
+ lines.push(` final savedLocale = sharedPrefs.getString('app_locale');`);
744
+ lines.push(` if (savedLocale != null) {`);
745
+ lines.push(` final appLocale = AppLocale.values.firstWhere(`);
746
+ lines.push(` (l) => l.languageCode == savedLocale,`);
747
+ lines.push(` orElse: () => AppLocale.en,`);
748
+ lines.push(` );`);
749
+ lines.push(` LocaleSettings.setLocale(appLocale);`);
750
+ lines.push(` } else {`);
751
+ lines.push(` LocaleSettings.useDeviceLocale();`);
752
+ lines.push(` }`);
753
+ lines.push('');
754
+
755
+ if (withSupabase) {
756
+ lines.push(` // initialize Supabase`);
757
+ lines.push(` await Supabase.initialize(`);
758
+ lines.push(` url: AppEnv.backendUrl,`);
759
+ lines.push(` anonKey: AppEnv.supabaseToken,`);
760
+ lines.push(` );`);
761
+ lines.push('');
762
+ }
763
+
764
+ if (withFirebase) {
765
+ lines.push(` // initialize firebase app for notifications`);
766
+ lines.push(` await switch (env) {`);
767
+ lines.push(` DevEnvironment() => Firebase.initializeApp(`);
768
+ lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
769
+ lines.push(` ),`);
770
+ lines.push(` ProdEnvironment() => Firebase.initializeApp(`);
771
+ lines.push(` // TODO replace with your own firebase options for production environment (if needed)`);
772
+ lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
773
+ lines.push(` ),`);
774
+ lines.push(` };`);
775
+ lines.push('');
776
+ }
777
+
778
+ if (withSentry) {
779
+ lines.push(` // initialize sentry for error reporting in production only`);
780
+ lines.push(` // run the app with Sentry for production environment`);
781
+ lines.push(` if (env is DevEnvironment) {`);
782
+ lines.push(` run(sharedPrefs);`);
783
+ lines.push(` } else if (env is ProdEnvironment) {`);
784
+ lines.push(` SentryFlutter.init((options) {`);
785
+ lines.push(` options.dsn = env.sentryDsn;`);
786
+ lines.push(` // 20% of traces will be sent to Sentry server. You should start with 1 and decrease it once you have more users.`);
787
+ lines.push(` options.tracesSampleRate = 0.2;`);
788
+ lines.push(` options.environment = env.name;`);
789
+ lines.push(` }, appRunner: () => run(sharedPrefs));`);
790
+ lines.push(` }`);
791
+ } else {
792
+ lines.push(` run(sharedPrefs);`);
793
+ }
794
+
795
+ lines.push(`}`);
796
+ lines.push('');
797
+
798
+ // ── run() ────────────────────────────────────────────────────────────────
799
+ lines.push(`void run(SharedPreferences prefs) => runApp(`);
800
+ lines.push(` TranslationProvider(`);
801
+ lines.push(` child: ProviderScope(child: MyApp(sharedPreferences: prefs)),`);
802
+ lines.push(` ),`);
803
+ lines.push(`);`);
804
+ lines.push('');
805
+
806
+ // ── MyApp widget ─────────────────────────────────────────────────────────
807
+ lines.push(`// use this if you want to define different themes for different platforms`);
808
+ lines.push(`// notifier: AppTheme.adaptive(`);
809
+ lines.push(`// defaultTextTheme: KasyTextTheme.build(),`);
810
+ lines.push(`// ios: const IosThemeFactory(),`);
811
+ lines.push(`// android: const AndroidThemeFactory(),`);
812
+ lines.push(`// web: const WebThemeFactory(),`);
813
+ lines.push(`// lightColors: KasyColors.light(),`);
814
+ lines.push(`// darkColors: KasyColors.dark(),`);
815
+ lines.push(`// mode: ThemeMode.dark,`);
816
+ lines.push(`// ),`);
817
+ lines.push(`// See ./docs/theme.md for more details`);
818
+ lines.push(`class MyApp extends ConsumerWidget {`);
819
+ lines.push(` final SharedPreferences sharedPreferences;`);
820
+ lines.push('');
821
+ lines.push(` const MyApp({super.key, required this.sharedPreferences});`);
822
+ lines.push('');
823
+ lines.push(` // This widget is the root of your application.`);
824
+ lines.push(` @override`);
825
+ lines.push(` Widget build(BuildContext context, WidgetRef ref) {`);
826
+ lines.push(` ErrorWidget.builder = (FlutterErrorDetails details) {`);
827
+ lines.push(` return AppErrorWidget(error: details);`);
828
+ lines.push(` };`);
829
+ lines.push(` final goRouter = ref.watch(goRouterProvider);`);
830
+ lines.push('');
831
+ lines.push(` return ThemeProvider(`);
832
+ lines.push(` notifier: AppTheme.uniform(`);
833
+ lines.push(` sharedPreferences: sharedPreferences,`);
834
+ lines.push(` themeFactory: const UniversalThemeFactory(),`);
835
+ lines.push(` lightColors: KasyColors.light(),`);
836
+ lines.push(` darkColors: KasyColors.dark(),`);
837
+ lines.push(` textTheme: KasyTextTheme.build(),`);
838
+ lines.push(` defaultMode: ThemeMode.light,`);
839
+ lines.push(` ),`);
840
+ lines.push(` child: Builder(`);
841
+ lines.push(` builder: (context) {`);
842
+ lines.push(` return MaterialApp.router(`);
843
+ lines.push(` title: 'Kasy',`);
844
+ lines.push(` theme: ThemeProvider.of(context).light,`);
845
+ lines.push(` darkTheme: ThemeProvider.of(context).dark,`);
846
+ lines.push(` themeMode: ThemeProvider.of(context).mode,`);
847
+ lines.push(` routerConfig: goRouter,`);
848
+ lines.push(` localizationsDelegates: const [`);
849
+ lines.push(` GlobalMaterialLocalizations.delegate,`);
850
+ lines.push(` GlobalWidgetsLocalizations.delegate,`);
851
+ lines.push(` GlobalCupertinoLocalizations.delegate,`);
852
+ lines.push(` ],`);
853
+ lines.push(` locale: TranslationProvider.of(context).flutterLocale,`);
854
+ lines.push(` supportedLocales: AppLocaleUtils.supportedLocales,`);
855
+ lines.push(` // Initializer is a widget that allows us to run some code before the app is ready`);
856
+ lines.push(` builder: (context, child) => Initializer(`);
857
+ lines.push(` services: [`);
858
+ lines.push(` authenticationApiProvider,`);
859
+ lines.push(` // shared preferences must be loaded`);
860
+ lines.push(` sharedPreferencesProvider,`);
861
+ lines.push(` // remote config api`);
862
+ lines.push(` remoteConfigApiProvider,`);
863
+ lines.push(` // notifications`);
864
+ lines.push(` notificationsSettingsProvider,`);
865
+ lines.push(` notificationRepositoryProvider,`);
866
+ lines.push(` // user state`);
867
+ if (withRevenuecat) {
868
+ lines.push(` subscriptionRepositoryProvider,`);
869
+ }
870
+ lines.push(` userStateNotifierProvider.notifier,`);
871
+ if (withWidget) {
872
+ lines.push(` homeWidgetsManagerProvider,`);
873
+ }
874
+ lines.push(` // analytics`);
875
+ lines.push(` analyticsApiProvider,`);
876
+ lines.push(` facebookEventApiProvider,`);
877
+ lines.push(` ],`);
878
+ lines.push(` onReady: child!,`);
879
+ lines.push(` onError: (_, error) => InitializationErrorPage(error: error),`);
880
+ lines.push(` onLoading: Scaffold(`);
881
+ lines.push(` body: Center(`);
882
+ lines.push(` child: CircularProgressIndicator.adaptive(`);
883
+ lines.push(` valueColor: AlwaysStoppedAnimation<Color>(`);
884
+ lines.push(` context.colors.primary,`);
885
+ lines.push(` ),`);
886
+ lines.push(` ),`);
887
+ lines.push(` ),`);
888
+ lines.push(` ),`);
889
+ lines.push(` ),`);
890
+ lines.push(` );`);
891
+ lines.push(` },`);
892
+ lines.push(` ),`);
893
+ lines.push(` );`);
894
+ lines.push(` }`);
895
+ lines.push(`}`);
896
+ lines.push('');
897
+
898
+ // ── AppErrorWidget ────────────────────────────────────────────────────────
899
+ lines.push(`/// This is an example of a more user friendly error widget`);
900
+ lines.push(`/// By default Flutter will show a red screen with the error in debug mode`);
901
+ lines.push(`/// and a grey screen in release mode`);
902
+ lines.push(`class AppErrorWidget extends StatelessWidget {`);
903
+ lines.push(` final FlutterErrorDetails? error;`);
904
+ lines.push('');
905
+ lines.push(` const AppErrorWidget({super.key, this.error});`);
906
+ lines.push('');
907
+ lines.push(` @override`);
908
+ lines.push(` Widget build(BuildContext context) {`);
909
+ lines.push(` return Container(`);
910
+ lines.push(` padding: const EdgeInsets.all(16),`);
911
+ lines.push(` color: Colors.orangeAccent,`);
912
+ lines.push(` child: Column(`);
913
+ lines.push(` mainAxisAlignment: MainAxisAlignment.center,`);
914
+ lines.push(` children: [`);
915
+ lines.push(` const Text(`);
916
+ lines.push(` 'Oups!',`);
917
+ lines.push(` style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),`);
918
+ lines.push(` ),`);
919
+ lines.push(` const SizedBox(height: 8),`);
920
+ lines.push(` const Text(`);
921
+ lines.push(` 'Sorry, Something went wrong',`);
922
+ lines.push(` style: TextStyle(color: Colors.white),`);
923
+ lines.push(` ),`);
924
+ lines.push(` const SizedBox(height: 8),`);
925
+ lines.push(` Text(`);
926
+ lines.push(` '\${error?.exception}\\n',`);
927
+ lines.push(` style: const TextStyle(color: Colors.white, fontSize: 10),`);
928
+ lines.push(` ),`);
929
+ lines.push(` ],`);
930
+ lines.push(` ),`);
931
+ lines.push(` );`);
932
+ lines.push(` }`);
933
+ lines.push(`}`);
934
+ lines.push('');
935
+
936
+ // ── InitializationErrorPage ───────────────────────────────────────────────
937
+ lines.push(`class InitializationErrorPage extends StatelessWidget {`);
938
+ lines.push(` final String error;`);
939
+ lines.push('');
940
+ lines.push(` const InitializationErrorPage({super.key, required this.error});`);
941
+ lines.push('');
942
+ lines.push(` @override`);
943
+ lines.push(` Widget build(BuildContext context) {`);
944
+ lines.push(` return Scaffold(`);
945
+ lines.push(` body: Padding(`);
946
+ lines.push(` padding: const EdgeInsets.all(24.0),`);
947
+ lines.push(` child: Column(`);
948
+ lines.push(` mainAxisAlignment: MainAxisAlignment.center,`);
949
+ lines.push(` crossAxisAlignment: CrossAxisAlignment.stretch,`);
950
+ lines.push(` spacing: 8,`);
951
+ lines.push(` children: [`);
952
+ lines.push(` Text('Cannot start app', style: context.textTheme.titleLarge),`);
953
+ lines.push(` Text(`);
954
+ lines.push(` 'Check your internet connection and start again',`);
955
+ lines.push(` style: context.textTheme.bodyLarge?.copyWith(`);
956
+ lines.push(` color: context.colors.muted,`);
957
+ lines.push(` ),`);
958
+ lines.push(` ),`);
959
+ lines.push(` if (kDebugMode)`);
960
+ lines.push(` Text(`);
961
+ lines.push(` "developper mode error: \$error",`);
962
+ lines.push(` style: context.textTheme.bodyLarge?.copyWith(`);
963
+ lines.push(` color: context.colors.error,`);
964
+ lines.push(` ),`);
965
+ lines.push(` ),`);
966
+ lines.push(` ],`);
967
+ lines.push(` ),`);
968
+ lines.push(` ),`);
969
+ lines.push(` );`);
970
+ lines.push(` }`);
971
+ lines.push(`}`);
972
+ lines.push('');
973
+
974
+ await fs.ensureDir(path.join(projectDir, 'lib'));
975
+ await fs.outputFile(
976
+ path.join(projectDir, 'lib', 'main.dart'),
977
+ lines.join('\n'),
978
+ 'utf8'
979
+ );
980
+ }
981
+
982
+ /**
983
+ * Remove optional module directories that were NOT selected by the user.
984
+ * The base template (Firebase/) has all modules; this function removes the
985
+ * directories for modules the user did not select to avoid dead code.
986
+ *
987
+ * @param {string} projectDir
988
+ * @param {string[]} modules - Selected modules (e.g. ['camera', 'analytics'])
989
+ */
990
+ async function removeModuleDirs(projectDir, modules) {
991
+ const removable = [
992
+ { module: 'feedback', dir: path.join('lib', 'features', 'feedbacks') },
993
+ { module: 'llm_chat', dir: path.join('lib', 'features', 'llm_chat') },
994
+ { module: 'onboarding',dir: path.join('lib', 'features', 'onboarding') },
995
+ // home_widgets/ imports home_widget package — remove when widget not selected
996
+ { module: 'widget', dir: path.join('lib', 'core', 'home_widgets') },
997
+ // subscription/ imports purchases_flutter — remove when revenuecat not selected
998
+ { module: 'revenuecat', dir: path.join('lib', 'features', 'subscription') },
999
+ // local_reminder/ uses flutter_timezone/timezone — remove when local_notifications not selected
1000
+ { module: 'local_notifications', dir: path.join('lib', 'features', 'local_reminder') },
1001
+ ];
1002
+
1003
+ for (const { module, dir } of removable) {
1004
+ if (!modules.includes(module)) {
1005
+ const fullPath = path.join(projectDir, dir);
1006
+ if (await fs.pathExists(fullPath)) {
1007
+ await fs.remove(fullPath);
1008
+ }
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ /**
1014
+ * Remove Android native widget artifacts when the widget module is not selected.
1015
+ * Deletes MyWidget.kt, MyWidgetReceiver.kt, mywidget_info.xml, and removes
1016
+ * the <receiver> block from AndroidManifest.xml so the widget does not appear
1017
+ * in the Android widget picker.
1018
+ *
1019
+ * @param {string} projectDir
1020
+ * @param {string} bundleId - e.g. "com.example.myapp"
1021
+ */
1022
+ /**
1023
+ * Removes Facebook SDK meta-data entries from AndroidManifest.xml when the
1024
+ * Facebook module is not selected. Without this, the Facebook SDK initializes
1025
+ * on app start and logs OAuthException error 190 (invalid App ID) for every
1026
+ * network request.
1027
+ *
1028
+ * @param {string} projectDir
1029
+ */
1030
+ /**
1031
+ * Remove FacebookSigninComponent from signin_page.dart and signup_page.dart
1032
+ * when the facebook module is not selected.
1033
+ * Removes both the import line and the widget line (package-name-agnostic regex).
1034
+ *
1035
+ * @param {string} projectDir
1036
+ */
1037
+ async function removeFacebookSigninFromAuthPages(projectDir) {
1038
+ const pages = [
1039
+ path.join(projectDir, 'lib', 'features', 'authentication', 'ui', 'signin_page.dart'),
1040
+ path.join(projectDir, 'lib', 'features', 'authentication', 'ui', 'signup_page.dart'),
1041
+ ];
1042
+ for (const p of pages) {
1043
+ if (!(await fs.pathExists(p))) continue;
1044
+ let content = await fs.readFile(p, 'utf8');
1045
+ // Remove import line
1046
+ content = content.replace(/^import 'package:[^']+\/features\/authentication\/ui\/components\/facebook_signin\.dart';\n/m, '');
1047
+ // Remove component line (any leading whitespace)
1048
+ content = content.replace(/[ \t]*const FacebookSigninComponent\(\),\n/g, '');
1049
+ await fs.writeFile(p, content, 'utf8');
1050
+ }
1051
+ }
1052
+
1053
+ async function removeAndroidFacebookMetadata(projectDir) {
1054
+ const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
1055
+ if (!(await fs.pathExists(manifestPath))) return;
1056
+
1057
+ let content = await fs.readFile(manifestPath, 'utf8');
1058
+ // Remove both facebook meta-data lines (ApplicationId and ClientToken)
1059
+ content = content.replace(/[ \t]*<meta-data android:name="com\.facebook\.sdk\.ApplicationId"[^\n]*\n?/g, '');
1060
+ content = content.replace(/[ \t]*<meta-data android:name="com\.facebook\.sdk\.ClientToken"[^\n]*\n?/g, '');
1061
+ await fs.writeFile(manifestPath, content, 'utf8');
1062
+ }
1063
+
1064
+ async function removeAndroidWidgetArtifacts(projectDir, bundleId) {
1065
+ const bundlePath = (bundleId || '').replace(/\./g, '/');
1066
+ const kotlinDir = path.join(projectDir, 'android', 'app', 'src', 'main', 'kotlin', bundlePath);
1067
+
1068
+ for (const file of ['MyWidget.kt', 'MyWidgetReceiver.kt']) {
1069
+ const p = path.join(kotlinDir, file);
1070
+ if (await fs.pathExists(p)) await fs.remove(p);
1071
+ }
1072
+
1073
+ const widgetXml = path.join(projectDir, 'android', 'app', 'src', 'main', 'res', 'xml', 'mywidget_info.xml');
1074
+ if (await fs.pathExists(widgetXml)) await fs.remove(widgetXml);
1075
+
1076
+ const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
1077
+ if (await fs.pathExists(manifestPath)) {
1078
+ let content = await fs.readFile(manifestPath, 'utf8');
1079
+ // Remove the MyWidgetReceiver <receiver> block (multiline)
1080
+ content = content.replace(/[ \t]*<receiver[\s\S]*?MyWidgetReceiver[\s\S]*?<\/receiver>\n?/m, '');
1081
+ await fs.writeFile(manifestPath, content, 'utf8');
1082
+ }
1083
+ }
1084
+
1085
+ /**
1086
+ * Writes a NoOp admin_home_widgets.dart when the widget module is not selected.
1087
+ * The real version imports home_widget_mywidget_service.dart which won't exist.
1088
+ *
1089
+ * @param {string} projectDir
1090
+ */
1091
+ async function writeNoOpAdminHomeWidgets(projectDir) {
1092
+ const content = `import 'package:flutter/material.dart';
1093
+ // No-op home widgets admin panel. Run: kasy add widget to activate.
1094
+ class AdminHomeWidgets extends StatelessWidget {
1095
+ const AdminHomeWidgets({super.key});
1096
+
1097
+ @override
1098
+ Widget build(BuildContext context) => const SizedBox.shrink();
1099
+ }
1100
+ `;
1101
+ await fs.outputFile(
1102
+ path.join(projectDir, 'lib', 'features', 'settings', 'ui', 'components', 'admin', 'admin_home_widgets.dart'),
1103
+ content,
1104
+ 'utf8',
1105
+ );
1106
+ }
1107
+
1108
+ /**
1109
+ * Writes a NoOp feature_request_repository.dart when the feedback module is not selected.
1110
+ * premium_page_provider.dart imports this provider — without it the project won't compile.
1111
+ *
1112
+ * @param {string} projectDir
1113
+ * @param {string} packageName
1114
+ */
1115
+ async function writeNoOpFeatureRequestRepository(projectDir, packageName) {
1116
+ const content = `import 'package:flutter_riverpod/flutter_riverpod.dart';
1117
+ // No-op feedback repository. Run: kasy add feedback to activate.
1118
+ final featureRequestRepositoryProvider = Provider<FeatureRequestRepository>(
1119
+ (ref) => const FeatureRequestRepository(),
1120
+ );
1121
+
1122
+ class FeatureRequestRepository {
1123
+ const FeatureRequestRepository();
1124
+
1125
+ Future<List<dynamic>> getActiveFeatureRequests() async => [];
1126
+ Future<List<dynamic>> getUserVotes(String userId) async => [];
1127
+ Future<void> createNewFeatureSuggestion({
1128
+ required String userId,
1129
+ required String title,
1130
+ required String description,
1131
+ }) async {}
1132
+ }
1133
+
1134
+ class UserAlreadyVotedException implements Exception {
1135
+ final String? message;
1136
+ UserAlreadyVotedException({this.message});
1137
+ }
1138
+ `;
1139
+ await fs.outputFile(
1140
+ path.join(projectDir, 'lib', 'features', 'feedbacks', 'repositories', 'feature_request_repository.dart'),
1141
+ content,
1142
+ 'utf8',
1143
+ );
1144
+ }
1145
+
1146
+ /**
1147
+ * Strip optional pubspec.yaml dependencies that are not needed based on selected modules.
1148
+ * Reads pubspec.yaml as text and removes lines matching " <dep>: ..." for each dep.
1149
+ *
1150
+ * @param {string} projectDir
1151
+ * @param {string[]} modules - Selected modules
1152
+ */
1153
+ async function stripPubspecDeps(projectDir, modules) {
1154
+ const pubspecPath = path.join(projectDir, 'pubspec.yaml');
1155
+ if (!(await fs.pathExists(pubspecPath))) return;
1156
+
1157
+ // Map: module → list of dep package names to remove when module NOT selected.
1158
+ const depMap = [
1159
+ { module: 'analytics', deps: ['mixpanel_flutter'] },
1160
+ { module: 'revenuecat', deps: ['purchases_flutter'] },
1161
+ { module: 'widget', deps: ['home_widget', 'background_fetch'] },
1162
+ ];
1163
+
1164
+ let content = await fs.readFile(pubspecPath, 'utf8');
1165
+
1166
+ for (const { module, deps } of depMap) {
1167
+ if (!modules.includes(module)) {
1168
+ for (const dep of deps) {
1169
+ // Remove lines like " mixpanel_flutter: ^2.5.0" (with any amount of leading spaces)
1170
+ content = content.replace(new RegExp(`^[ \\t]*${dep}:.*\\n`, 'gm'), '');
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ // sentry_flutter can be stripped only when none of the modules that use it are selected:
1176
+ // - revenuecat: revenuecat tracking_api.dart uses Sentry
1177
+ // - facebook: facebook tracking_api.dart uses Sentry
1178
+ // - sentry: explicitly selected
1179
+ // When none of these are selected, writeNoOpSentryUsages patches core files so they
1180
+ // no longer import sentry_flutter, allowing the dep to be removed.
1181
+ if (!modules.includes('sentry') && !modules.includes('revenuecat') && !modules.includes('facebook')) {
1182
+ content = content.replace(new RegExp(`^[ \\t]*sentry_flutter:.*\\n`, 'gm'), '');
1183
+ }
1184
+
1185
+ await fs.outputFile(pubspecPath, content, 'utf8');
1186
+ }
1187
+
1188
+ /**
1189
+ * Patches core files that always import sentry_flutter, removing the dependency
1190
+ * when neither sentry, revenuecat, nor facebook modules are selected.
1191
+ * Affected files:
1192
+ * - lib/core/initializer/onstart_widget.dart
1193
+ * - lib/core/data/api/remote_config_api.dart
1194
+ *
1195
+ * @param {string} projectDir
1196
+ */
1197
+ async function writeNoOpSentryUsages(projectDir) {
1198
+ const sentryImport = `import 'package:sentry_flutter/sentry_flutter.dart';\n`;
1199
+
1200
+ const files = [
1201
+ path.join(projectDir, 'lib', 'core', 'initializer', 'onstart_widget.dart'),
1202
+ path.join(projectDir, 'lib', 'core', 'data', 'api', 'remote_config_api.dart'),
1203
+ ];
1204
+
1205
+ for (const filePath of files) {
1206
+ if (!(await fs.pathExists(filePath))) continue;
1207
+ let content = await fs.readFile(filePath, 'utf8');
1208
+ // Remove sentry import line
1209
+ content = content.replace(sentryImport, '');
1210
+ // Remove single-line Sentry.captureException(...); calls (with leading whitespace)
1211
+ content = content.replace(/^[ \t]*Sentry\.captureException\([^)]+\);\n/gm, '');
1212
+ await fs.outputFile(filePath, content, 'utf8');
1213
+ }
1214
+ }
1215
+
1216
+ /**
1217
+ * Adds a dependency to pubspec.yaml if not already present.
1218
+ * Inserts alphabetically within the dependencies block.
1219
+ *
1220
+ * @param {string} projectDir
1221
+ * @param {string} depName - e.g. 'mixpanel_flutter'
1222
+ * @param {string} depVersion - e.g. '^2.5.0'
1223
+ */
1224
+ async function addPubspecDep(projectDir, depName, depVersion) {
1225
+ const pubspecPath = path.join(projectDir, 'pubspec.yaml');
1226
+ if (!(await fs.pathExists(pubspecPath))) return;
1227
+
1228
+ let content = await fs.readFile(pubspecPath, 'utf8');
1229
+
1230
+ // Skip if already present
1231
+ if (new RegExp(`^[ \\t]*${depName}:`, 'm').test(content)) return;
1232
+
1233
+ const newLine = ` ${depName}: ${depVersion}`;
1234
+
1235
+ // Find the dependencies block and insert alphabetically
1236
+ const lines = content.split('\n');
1237
+ let inDeps = false;
1238
+ let insertAt = -1;
1239
+
1240
+ for (let i = 0; i < lines.length; i++) {
1241
+ const line = lines[i];
1242
+ if (/^dependencies:/.test(line)) {
1243
+ inDeps = true;
1244
+ continue;
1245
+ }
1246
+ if (inDeps) {
1247
+ // End of dependencies block
1248
+ if (/^[a-zA-Z]/.test(line) || /^dev_dependencies:/.test(line)) {
1249
+ if (insertAt === -1) insertAt = i; // fallback: insert just before this
1250
+ break;
1251
+ }
1252
+ // Only consider simple dep lines (2 spaces + name:)
1253
+ const match = line.match(/^ ([a-z_][a-z_0-9]*)\s*:/);
1254
+ if (match) {
1255
+ if (match[1] > depName) {
1256
+ insertAt = i;
1257
+ break;
1258
+ }
1259
+ insertAt = i + 1; // advance past this dep
1260
+ }
1261
+ }
1262
+ }
1263
+
1264
+ if (insertAt === -1) {
1265
+ // Fallback: append before dev_dependencies
1266
+ const devIdx = lines.findIndex(l => /^dev_dependencies:/.test(l));
1267
+ insertAt = devIdx !== -1 ? devIdx : lines.length;
1268
+ }
1269
+
1270
+ lines.splice(insertAt, 0, newLine);
1271
+ await fs.outputFile(pubspecPath, lines.join('\n'), 'utf8');
1272
+ }
1273
+
1274
+ /**
1275
+ * Writes no-op stubs for the subscription module when revenuecat is not selected.
1276
+ * Removes purchases_flutter-dependent test files and creates minimal stubs so that
1277
+ * core files that reference subscription types still compile.
1278
+ *
1279
+ * @param {string} projectDir
1280
+ * @param {string} packageName
1281
+ */
1282
+ async function writeNoOpSubscriptionStubs(projectDir, packageName) {
1283
+ const pkg = packageName;
1284
+
1285
+ // 0. Replace lib/core/data/models/subscription.dart with stripped version (no purchases_flutter)
1286
+ await fs.outputFile(
1287
+ path.join(projectDir, 'lib', 'core', 'data', 'models', 'subscription.dart'),
1288
+ `import 'package:flutter/material.dart';
1289
+ import 'package:freezed_annotation/freezed_annotation.dart';
1290
+
1291
+ part 'subscription.freezed.dart';
1292
+
1293
+ abstract class SubscriptionProduct {
1294
+ String get skuId;
1295
+ String get id;
1296
+ String get description;
1297
+ String get label;
1298
+ double get price;
1299
+ Duration get duration;
1300
+ String? get promotion;
1301
+ String formattedPrice(BuildContext context);
1302
+ DurationType get durationType;
1303
+ String? get title;
1304
+ int? get trialDays;
1305
+ List<String>? get features;
1306
+ String get priceString;
1307
+ String get currency;
1308
+ String pricePerMonth(BuildContext context);
1309
+ String? pricePerYear(BuildContext context);
1310
+ }
1311
+
1312
+ // RevenueCatProduct is provided by the revenuecat feature module.
1313
+ // Run: kasy add revenuecat to activate in-app purchases.
1314
+
1315
+ enum DurationType { week, month, threeMonth, sixMonth, year, lifetime }
1316
+
1317
+ @freezed
1318
+ sealed class Subscription with _$Subscription {
1319
+ const factory Subscription.active({
1320
+ SubscriptionProduct? activeOffer,
1321
+ }) = SubscriptionStateData;
1322
+
1323
+ const factory Subscription.inactive({
1324
+ required int hoursBetweenTwoRequests,
1325
+ DateTime? lastAskingDate,
1326
+ }) = SubscriptionInactiveStateData;
1327
+
1328
+ const factory Subscription.loading() = SubscriptionStateLoading;
1329
+
1330
+ const Subscription._();
1331
+
1332
+ bool get canPurchase => switch (this) {
1333
+ SubscriptionStateData() => false,
1334
+ SubscriptionInactiveStateData() => true,
1335
+ SubscriptionStateLoading() => false,
1336
+ };
1337
+
1338
+ bool get isActive => switch (this) {
1339
+ SubscriptionStateData() => true,
1340
+ SubscriptionInactiveStateData() => false,
1341
+ SubscriptionStateLoading() => false,
1342
+ };
1343
+
1344
+ // Returns false without RevenueCat — run: kasy add revenuecat to activate
1345
+ bool get isInTrial => false;
1346
+
1347
+ // Returns false without RevenueCat — run: kasy add revenuecat to activate
1348
+ bool get hasRenewal => false;
1349
+
1350
+ bool get isLifetime => switch (this) {
1351
+ SubscriptionStateData(:final activeOffer) =>
1352
+ activeOffer?.durationType == DurationType.lifetime,
1353
+ SubscriptionInactiveStateData() => false,
1354
+ SubscriptionStateLoading() => false,
1355
+ };
1356
+
1357
+ bool get shouldAskForSubscription {
1358
+ final now = DateTime.now();
1359
+ final (lastAskingDate, hoursBetweenTwoRequests) = switch (this) {
1360
+ SubscriptionStateData() => (null, null),
1361
+ SubscriptionInactiveStateData(
1362
+ :final lastAskingDate,
1363
+ :final hoursBetweenTwoRequests,
1364
+ ) =>
1365
+ (lastAskingDate, hoursBetweenTwoRequests),
1366
+ SubscriptionStateLoading() => (null, null),
1367
+ };
1368
+ if (lastAskingDate == null) {
1369
+ return true;
1370
+ }
1371
+ final diff = now.difference(lastAskingDate);
1372
+ return diff.inHours >= hoursBetweenTwoRequests!;
1373
+ }
1374
+ }
1375
+ `,
1376
+ 'utf8',
1377
+ );
1378
+
1379
+ // 0b. Strip EntitlementInfo from user_state_notifier.dart (purchases_flutter must be removed)
1380
+ const userStateFile = path.join(projectDir, 'lib', 'core', 'states', 'user_state_notifier.dart');
1381
+ if (await fs.pathExists(userStateFile)) {
1382
+ let userStateContent = await fs.readFile(userStateFile, 'utf8');
1383
+ userStateContent = userStateContent
1384
+ .replaceAll("import 'package:purchases_flutter/models/entitlement_info_wrapper.dart';\n", '')
1385
+ .replaceAll('\n List<EntitlementInfo>? entitlements,', '')
1386
+ .replace(/,\n\s+entitlements: entitlements,/g, ',');
1387
+ await fs.writeFile(userStateFile, userStateContent, 'utf8');
1388
+ }
1389
+
1390
+ // 1. No-op subscription_repository.dart (used by user_repository, onboarding, home_widget_background_task)
1391
+ await fs.outputFile(
1392
+ path.join(projectDir, 'lib', 'features', 'subscription', 'repositories', 'subscription_repository.dart'),
1393
+ `import 'package:flutter_riverpod/flutter_riverpod.dart';
1394
+ import 'package:${pkg}/core/data/models/subscription.dart';
1395
+ import 'package:${pkg}/core/initializer/onstart_service.dart';
1396
+ // No-op subscription repository. Run: kasy add revenuecat to activate.
1397
+ final subscriptionRepositoryProvider = Provider<SubscriptionRepository>(
1398
+ (ref) => SubscriptionRepository(),
1399
+ );
1400
+
1401
+ class SubscriptionRepository implements OnStartService {
1402
+ SubscriptionRepository({
1403
+ dynamic subscriptionApi,
1404
+ dynamic inAppSubscriptionApi,
1405
+ dynamic prefs,
1406
+ });
1407
+
1408
+ @override
1409
+ Future<void> init() async {}
1410
+
1411
+ Future<void> initUser(String userId) async {}
1412
+
1413
+ Future<Subscription> get(String userId) async =>
1414
+ const Subscription.inactive(hoursBetweenTwoRequests: 24);
1415
+
1416
+ Future<List<SubscriptionProduct>> getOffers({String? offerId}) async => [];
1417
+
1418
+ Future<bool> checkPermission(String permissionToCheck) async => false;
1419
+
1420
+ Future<List<dynamic>?> purchase(SubscriptionProduct product) async => null;
1421
+
1422
+ Future<void> unsubscribe() async {}
1423
+
1424
+ Future<void> restorePurchase() async {}
1425
+
1426
+ Future<void> saveLastAskingDate() async {}
1427
+
1428
+ DateTime? get lastAskingDate => null;
1429
+ }
1430
+
1431
+ class UserCancelledPurchaseException implements Exception {}
1432
+ `,
1433
+ 'utf8',
1434
+ );
1435
+
1436
+ // 2. No-op maybeshow_premium.dart (used by home_page — must implement MaybeShowWithRef)
1437
+ await fs.outputFile(
1438
+ path.join(projectDir, 'lib', 'features', 'subscription', 'shared', 'maybeshow_premium.dart'),
1439
+ `import 'package:flutter_riverpod/flutter_riverpod.dart';
1440
+ import 'package:${pkg}/core/states/components/maybeshow_component.dart';
1441
+ import 'package:${pkg}/core/states/models/event_model.dart';
1442
+ // No-op premium paywall trigger. Run: kasy add revenuecat to activate.
1443
+ class MaybeShowPremiumPage implements MaybeShowWithRef {
1444
+ @override
1445
+ Future<bool> handle(WidgetRef ref, AppEvent event) async => false;
1446
+ }
1447
+ `,
1448
+ 'utf8',
1449
+ );
1450
+
1451
+ // 3. No-op premium_page.dart (used by onboarding_page with args: PremiumPageArgs(redirect:))
1452
+ await fs.outputFile(
1453
+ path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'premium_page.dart'),
1454
+ `import 'package:flutter/material.dart';
1455
+ // No-op premium page. Run: kasy add revenuecat to activate.
1456
+ class PremiumPage extends StatelessWidget {
1457
+ const PremiumPage({super.key, this.paywall, this.args});
1458
+ final dynamic paywall;
1459
+ final PremiumPageArgs? args;
1460
+ @override
1461
+ Widget build(BuildContext context) => const SizedBox.shrink();
1462
+ }
1463
+
1464
+ class PremiumPageArgs {
1465
+ const PremiumPageArgs({this.redirect});
1466
+ final String? redirect;
1467
+ }
1468
+ `,
1469
+ 'utf8',
1470
+ );
1471
+
1472
+ // 4. No-op premium_page_factory.dart (used by admin_paywalls)
1473
+ await fs.outputFile(
1474
+ path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'component', 'premium_page_factory.dart'),
1475
+ `// No-op paywall factory. Run: kasy add revenuecat to activate.
1476
+ enum PaywallFactory { basic }
1477
+ `,
1478
+ 'utf8',
1479
+ );
1480
+
1481
+ // 5. No-op subscription_entity.dart (used by test_utils, fake_subscription_api)
1482
+ await fs.outputFile(
1483
+ path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'entities', 'subscription_entity.dart'),
1484
+ `// No-op subscription entity. Run: kasy add revenuecat to activate.
1485
+ enum SubscriptionStatus { ACTIVE, PAUSED, EXPIRED, LIFETIME, CANCELLED }
1486
+
1487
+ class SubscriptionEntity {
1488
+ final String skuId;
1489
+ final String? offerId;
1490
+ final DateTime? creationDate;
1491
+ final DateTime? periodEndDate;
1492
+ final SubscriptionStatus status;
1493
+ const SubscriptionEntity({
1494
+ required this.skuId,
1495
+ required this.status,
1496
+ this.offerId,
1497
+ this.creationDate,
1498
+ this.periodEndDate,
1499
+ });
1500
+ }
1501
+ `,
1502
+ 'utf8',
1503
+ );
1504
+
1505
+ // 6. No-op subscription_api.dart (provider + abstract class used by test_utils and fake)
1506
+ await fs.outputFile(
1507
+ path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'subscription_api.dart'),
1508
+ `import 'package:flutter_riverpod/flutter_riverpod.dart';
1509
+ import 'package:${pkg}/features/subscription/api/entities/subscription_entity.dart';
1510
+ // No-op subscription API. Run: kasy add revenuecat to activate.
1511
+ final subscriptionApiProvider = Provider<SubscriptionApi>(
1512
+ (ref) => _NoOpSubscriptionApi(),
1513
+ );
1514
+
1515
+ abstract class SubscriptionApi {
1516
+ Future<SubscriptionEntity?> get(String userId);
1517
+ }
1518
+
1519
+ class _NoOpSubscriptionApi implements SubscriptionApi {
1520
+ @override
1521
+ Future<SubscriptionEntity?> get(String userId) async => null;
1522
+ }
1523
+ `,
1524
+ 'utf8',
1525
+ );
1526
+
1527
+ // 7. No-op inapp_subscription_api.dart (provider + class used by test_utils and fake)
1528
+ await fs.outputFile(
1529
+ path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'inapp_subscription_api.dart'),
1530
+ `import 'package:flutter_riverpod/flutter_riverpod.dart';
1531
+ import 'package:${pkg}/core/data/models/subscription.dart';
1532
+ // No-op in-app subscription API. Run: kasy add revenuecat to activate.
1533
+ final inAppSubscriptionApiProvider = Provider<RevenueCatPaymentApi>(
1534
+ (ref) => RevenueCatPaymentApi(),
1535
+ );
1536
+
1537
+ class RevenueCatPaymentApi {
1538
+ Future<void> init() async {}
1539
+ Future<void> initUser(String userId) async {}
1540
+ Future<void> disconnectUser() async {}
1541
+ Future<List<SubscriptionProduct>> getOffers(String? offerId) async => [];
1542
+ Future<void> unsubscribe() async {}
1543
+ Future<void> restorePurchase() async {}
1544
+ Future<List<SubscriptionProduct>> getActiveSubscription() async => [];
1545
+ Future<SubscriptionProduct?> getFromProductId(String productId) async => null;
1546
+ Future<List<dynamic>> getLastPurchase() async => [];
1547
+ Future<void> presentCodeRedemptionSheet() async {}
1548
+ Future<List<dynamic>> getPermissions() async => [];
1549
+ Future<List<dynamic>?> getEntitlements() async => [];
1550
+ }
1551
+ `,
1552
+ 'utf8',
1553
+ );
1554
+
1555
+ // 8. Remove test/features/subscription/ entirely (imports purchases_flutter and RevenueCat types)
1556
+ const testSubscriptionDir = path.join(projectDir, 'test', 'features', 'subscription');
1557
+ if (await fs.pathExists(testSubscriptionDir)) {
1558
+ await fs.remove(testSubscriptionDir);
1559
+ }
1560
+
1561
+ // 9. Restore fake_subscription_api.dart with currentFake setter (required by test_utils.dart)
1562
+ await fs.outputFile(
1563
+ path.join(projectDir, 'test', 'features', 'subscription', 'api', 'fake_subscription_api.dart'),
1564
+ `import 'package:${pkg}/features/subscription/api/entities/subscription_entity.dart';
1565
+ import 'package:${pkg}/features/subscription/api/subscription_api.dart';
1566
+ // No-op subscription API fake. Run: kasy add revenuecat to activate.
1567
+ class SubscriptionApiFake implements SubscriptionApi {
1568
+ SubscriptionEntity? currentFake;
1569
+
1570
+ @override
1571
+ Future<SubscriptionEntity?> get(String userId) async => currentFake;
1572
+ }
1573
+ `,
1574
+ 'utf8',
1575
+ );
1576
+
1577
+ // 10. Restore fake_inapp_subscription_api.dart without purchases_flutter dependency
1578
+ await fs.outputFile(
1579
+ path.join(projectDir, 'test', 'features', 'subscription', 'api', 'fake_inapp_subscription_api.dart'),
1580
+ `import 'package:${pkg}/features/subscription/api/inapp_subscription_api.dart';
1581
+ // No-op in-app subscription API fake. Run: kasy add revenuecat to activate.
1582
+ class InAppSubscriptionApiFake extends RevenueCatPaymentApi {}
1583
+ `,
1584
+ 'utf8',
1585
+ );
1586
+ }
1587
+
1588
+ /**
1589
+ * Remove DEVELOPMENT_TEAM entries from the generated iOS project.pbxproj.
1590
+ * Clients use their own Apple team — leaving the internal team ID would cause
1591
+ * signing failures for any account that doesn't belong to AICRUS.
1592
+ *
1593
+ * @param {string} projectDir
1594
+ */
1595
+ async function removeDevelopmentTeam(projectDir) {
1596
+ const pbxprojPath = path.join(projectDir, 'ios', 'Runner.xcodeproj', 'project.pbxproj');
1597
+ if (!(await fs.pathExists(pbxprojPath))) return;
1598
+ let content = await fs.readFile(pbxprojPath, 'utf8');
1599
+ content = content.replace(/^\s*DEVELOPMENT_TEAM = [A-Z0-9]+;\n/gm, '');
1600
+ await fs.outputFile(pbxprojPath, content, 'utf8');
1601
+ }
1602
+
1603
+ const RELEASE_DOC_BASES = ['ios-release', 'codemagic-release', 'auth-setup'];
1604
+ const RELEASE_DOC_LANGS = ['pt', 'en', 'es'];
1605
+
1606
+ /**
1607
+ * Deliver a single localized release doc per topic (no pt/en/es duplicates).
1608
+ * Source templates in the kit repo use `{base}.{lang}.md`; projects receive `{base}.md`.
1609
+ *
1610
+ * @param {string} projectDir
1611
+ * @param {string} [language='en'] - CLI language (en, pt, es)
1612
+ */
1613
+ async function localizeReleaseDocs(projectDir, language = 'en') {
1614
+ const lang = RELEASE_DOC_LANGS.includes(language) ? language : 'en';
1615
+ const docsDir = path.join(projectDir, 'docs');
1616
+ if (!(await fs.pathExists(docsDir))) return;
1617
+
1618
+ for (const base of RELEASE_DOC_BASES) {
1619
+ const sourcePath = path.join(docsDir, `${base}.${lang}.md`);
1620
+ const targetPath = path.join(docsDir, `${base}.md`);
1621
+ if (!(await fs.pathExists(sourcePath))) continue;
1622
+
1623
+ let content = await fs.readFile(sourcePath, 'utf8');
1624
+ content = content
1625
+ .replace(/ios-release\.(?:pt|en|es)\.md/g, 'ios-release.md')
1626
+ .replace(/codemagic-release\.(?:pt|en|es)\.md/g, 'codemagic-release.md')
1627
+ .replace(/auth-setup\.(?:pt|en|es)\.md/g, 'auth-setup.md');
1628
+ await fs.outputFile(targetPath, content, 'utf8');
1629
+
1630
+ for (const l of RELEASE_DOC_LANGS) {
1631
+ const localized = path.join(docsDir, `${base}.${l}.md`);
1632
+ if (await fs.pathExists(localized)) await fs.remove(localized);
1633
+ }
1634
+ }
1635
+ }
1636
+
1637
+ module.exports = {
1638
+ BACKENDS,
1639
+ buildDartDefines,
1640
+ writeRouter,
1641
+ writeVsCodeLaunch,
1642
+ writeEnvExample,
1643
+ writeEnvFileIfMissing,
1644
+ writeFirebaserc,
1645
+ writeEnvironnementsOverrides,
1646
+ writeFeaturesConfig,
1647
+ writeKitSetup,
1648
+ writeMakefile,
1649
+ writeNoOpAnalyticsApi,
1650
+ writeNoOpTrackingApi,
1651
+ writeMainDart,
1652
+ removeModuleDirs,
1653
+ removeAndroidWidgetArtifacts,
1654
+ removeFacebookSigninFromAuthPages,
1655
+ removeAndroidFacebookMetadata,
1656
+ writeNoOpAdminHomeWidgets,
1657
+ writeNoOpFeatureRequestRepository,
1658
+ writeNoOpSubscriptionStubs,
1659
+ writeNoOpSentryUsages,
1660
+ stripPubspecDeps,
1661
+ addPubspecDep,
1662
+ removeDevelopmentTeam,
1663
+ localizeReleaseDocs,
1664
+ };