kasy-cli 1.34.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/kasy.js +24 -2
- package/docs/cli-reference.md +7 -7
- package/lib/commands/new.js +11 -9
- package/lib/commands/release-version.js +234 -0
- package/lib/commands/update.js +27 -0
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +15 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +27 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +35 -21
- package/lib/scaffold/backends/patch-base-hashes.json +66 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +36 -23
- package/lib/scaffold/backends/supabase/migrations/20240101000014_subscription_trial_end.sql +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +82 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +36 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/entities/subscription_entity.dart +1 -0
- package/lib/scaffold/generate.js +53 -4
- package/lib/utils/i18n/messages-en.js +23 -0
- package/lib/utils/i18n/messages-es.js +23 -0
- package/lib/utils/i18n/messages-pt.js +23 -0
- package/package.json +5 -2
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +73 -0
- package/templates/firebase/AGENTS.md +83 -0
- package/templates/firebase/DESIGN_SYSTEM.md +37 -2
- package/templates/firebase/docs/auth-setup.en.md +2 -0
- package/templates/firebase/docs/auth-setup.es.md +2 -0
- package/templates/firebase/docs/auth-setup.pt.md +2 -0
- package/templates/firebase/firebase.json +56 -1
- package/templates/firebase/functions/src/authentication/triggers.ts +10 -6
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +5 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +9 -1
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +27 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +3 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +4 -0
- package/templates/firebase/lib/components/kasy_alert.dart +0 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +31 -16
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +0 -1
- package/templates/firebase/lib/components/kasy_date_picker.dart +0 -5
- package/templates/firebase/lib/components/kasy_dialog.dart +0 -1
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +1 -1
- package/templates/firebase/lib/components/kasy_sidebar.dart +189 -120
- package/templates/firebase/lib/components/kasy_text_area.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field.dart +0 -1
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +0 -1
- package/templates/firebase/lib/components/kasy_toast.dart +107 -41
- package/templates/firebase/lib/core/app_update/app_update_repository.dart +54 -0
- package/templates/firebase/lib/core/app_update/app_update_status.dart +14 -0
- package/templates/firebase/lib/core/app_update/update_available_sheet.dart +70 -0
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +40 -3
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +82 -34
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +6 -6
- package/templates/firebase/lib/core/bottom_menu/web_url.dart +20 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +38 -1
- package/templates/firebase/lib/core/guards/guard.dart +16 -2
- package/templates/firebase/lib/core/icons/kasy_icons.dart +3 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +2 -5
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +5 -3
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +14 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_available.dart +32 -0
- package/templates/firebase/lib/core/states/logout_action.dart +5 -1
- package/templates/firebase/lib/core/states/user_state_notifier.dart +85 -14
- package/templates/firebase/lib/core/theme/responsive_text_theme.dart +69 -0
- package/templates/firebase/lib/core/theme/texts.dart +90 -57
- package/templates/firebase/lib/core/theme/type_scale.dart +77 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +20 -6
- package/templates/firebase/lib/core/utils/image_bytes_loader.dart +12 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_io.dart +28 -0
- package/templates/firebase/lib/core/utils/image_bytes_loader_web.dart +56 -0
- package/templates/firebase/lib/core/web_screen_width.dart +15 -0
- package/templates/firebase/lib/core/web_screen_width_io.dart +3 -0
- package/templates/firebase/lib/core/web_screen_width_web.dart +10 -0
- package/templates/firebase/lib/core/web_viewport_scale.dart +61 -25
- package/templates/firebase/lib/core/widgets/focus_visibility.dart +89 -0
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +1 -2
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +2 -3
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +1 -2
- package/templates/firebase/lib/features/authentication/api/auth_web_support.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/auth_web_support_web.dart +25 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +205 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +22 -0
- package/templates/firebase/lib/features/authentication/navigation/post_login_navigation.dart +32 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +7 -7
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +34 -10
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +2 -2
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +37 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +8 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +59 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +2 -2
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +2 -2
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -1
- package/templates/firebase/lib/features/home/design_system_page.dart +134 -67
- package/templates/firebase/lib/features/home/home_components_page.dart +4 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +1 -3
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +154 -56
- package/templates/firebase/lib/features/home/home_page.dart +4 -0
- package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +8 -3
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +10 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +8 -3
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +11 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +30 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +13 -4
- package/templates/firebase/lib/features/settings/settings_page.dart +152 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +43 -15
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +12 -12
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +1 -4
- package/templates/firebase/lib/features/settings/ui/components/create_password_sheet.dart +141 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +0 -1
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +42 -38
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +17 -2
- package/templates/firebase/lib/features/subscriptions/api/entities/subscription_entity.dart +3 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +12 -11
- package/templates/firebase/lib/features/subscriptions/repositories/subscription_repository.dart +25 -2
- package/templates/firebase/lib/features/subscriptions/ui/component/active_premium_content.dart +319 -143
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_minimal.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_row.dart +1 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/paywall_with_switch.dart +2 -5
- package/templates/firebase/lib/features/subscriptions/ui/component/premium_content.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/feature_line.dart +1 -2
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_banner.dart +2 -7
- package/templates/firebase/lib/features/subscriptions/ui/widgets/premium_feature.dart +2 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_col.dart +1 -4
- package/templates/firebase/lib/features/subscriptions/ui/widgets/selectable_row.dart +0 -3
- package/templates/firebase/lib/i18n/en.i18n.json +49 -3
- package/templates/firebase/lib/i18n/es.i18n.json +49 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +49 -3
- package/templates/firebase/lib/main.dart +11 -2
- package/templates/firebase/lib/router.dart +92 -13
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +14 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +47 -3
- package/templates/firebase/test/core/web_viewport_scale_test.dart +68 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +15 -0
- package/templates/firebase/web/index.html +162 -14
- package/templates/firebase/lib/core/guards/user_info_guard.dart +0 -61
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api/android/app/src/main/AndroidManifest.xml": "0579c57068ee50d43837ef9c7bee884b6232e92975301c8305ce389282f68291",
|
|
3
|
+
"api/ios/Runner/AppDelegate.swift": "9f1027a03ad1fef2dab45464fbd7e98364b6a9cb6d2a6abe770ee9c45fa19122",
|
|
4
|
+
"api/lib/core/data/api/storage_api.dart": "d77fecc6923ea9544b6ec20571dcab5c8f3d38be81ec618b0cf4f1b08be7bbae",
|
|
5
|
+
"api/lib/core/data/api/user_api.dart": "06f491ebf4548b87431e2d6983148f4082e120ec7d6a928108cba954d16d8c56",
|
|
6
|
+
"api/lib/core/data/entities/json_converters.dart": "92a5efd595a03a862fa03e77ab3ca3d556523d0c9e0daca6e296e35d24b760a8",
|
|
7
|
+
"api/lib/core/data/entities/user_entity.dart": "5c1e56e4be3ba2ed95cdcd276112a02b526f2a3fa7ee5bba08a3ec2b51153688",
|
|
8
|
+
"api/lib/environments.dart": "c41f0843a4d627b717579eec5607b7ff5c16369bdd4b2a82e081058a20ffb7a5",
|
|
9
|
+
"api/lib/features/ai_chat/api/ai_chat_api.dart": "749047d30390e8d624c5a17d421096b05651e28b34a42071a9b73efc71d0a5f7",
|
|
10
|
+
"api/lib/features/ai_chat/api/ai_chat_conversation_entity.dart": "1c4e4223e802d7fe8e93d9e7a09fcc6be81a41792d722e8b7e697837cde4737f",
|
|
11
|
+
"api/lib/features/ai_chat/api/ai_chat_message_entity.dart": "5aca4fdb5b1c38df04c9d377c6e6eeb6930ef85b2a32689fe6403e7cc7645373",
|
|
12
|
+
"api/lib/features/ai_chat/providers/ai_chat_notifier.dart": "f16ea3f3266e63a5591ceff9bc1900845f0b96e9bbbc5463ec6ab894f878eae3",
|
|
13
|
+
"api/lib/features/authentication/api/authentication_api_interface.dart": "ef9219237babd73e4b3ac148a24ebbbdf39ab98166d21f578c359074b528e5da",
|
|
14
|
+
"api/lib/features/authentication/api/authentication_api.dart": "8b460722bb3bee7eee015f34aaa39c1c97cb264df1024b2855060dac232c620a",
|
|
15
|
+
"api/lib/features/authentication/repositories/authentication_repository.dart": "8e98ca0d39df3aac1dab159e6bdfe8d2bbb8e43931459984eb4ecfb676698ca2",
|
|
16
|
+
"api/lib/features/feedbacks/api/entities/feature_request_entity.dart": "e72c683c7321bf1f3c19fc1c6d54c6964bd99970e87c9ff31d8918efa93f0090",
|
|
17
|
+
"api/lib/features/feedbacks/api/entities/feature_vote_entity.dart": "a13ccfc493d8f5277beffc8e28f77ec322ab0c0a56358c3669353206b7c6756b",
|
|
18
|
+
"api/lib/features/feedbacks/api/feature_request_api.dart": "a741395509b95e8cae135bc8bdcca1ba0d48785b1e1b201732219659d271346b",
|
|
19
|
+
"api/lib/features/feedbacks/api/feature_vote_api.dart": "89dcaeea29460a7f219c2ede3759707b45066272fe6837c29a40f3d79fade4e9",
|
|
20
|
+
"api/lib/features/notifications/api/device_api.dart": "0d6bccb6813cceec29c54220daac9d0cd35687c4fa9f10ffde07dad88b204ed3",
|
|
21
|
+
"api/lib/features/notifications/api/entities/device_entity.dart": "54fce6274c5fe60ba663d8693e0c9d88fd6cd6fcd9ec906e021621a2e51176b7",
|
|
22
|
+
"api/lib/features/notifications/api/entities/notifications_entity.dart": "f9ee62111a6f122657e105df317f1f5f803e3a525a96686c0a2728ff02cd8964",
|
|
23
|
+
"api/lib/features/notifications/api/notifications_api.dart": "30cdb1bc52c3249b2cda6b1c7eb7fb6a977db5744da5a9c2980784f41e75d9e7",
|
|
24
|
+
"api/lib/features/onboarding/api/entities/user_info_entity.dart": "cd5aabc68310025f3ab9676cab126e1f755431f9e8752f868ee9fcd6f5892ce2",
|
|
25
|
+
"api/lib/features/onboarding/api/user_infos_api.dart": "cf728bcef364259912ee1e87151c4f791faee0c0d49232c9af260bb5681bd429",
|
|
26
|
+
"api/lib/features/onboarding/models/user_info.dart": "946fd34a33b630a34a3b6248594cbca717f4fab9d23f669510145b9032bd1321",
|
|
27
|
+
"api/lib/features/onboarding/repositories/user_infos_repository.dart": "487a5177dcaed7f87e613b2b4f04329c4995274e6a52809d4917c037cb3c3c55",
|
|
28
|
+
"api/lib/features/settings/ui/components/admin/admin_users_api.dart": "0ffb232ffb193154c0366d6f7da0ca499b3e06fed4fd71af572b8dc53ecd844e",
|
|
29
|
+
"api/lib/features/settings/ui/widgets/avatar_utils.dart": "bb9126409bbbb245f2dec613bd096ac53c208a56bd55f3d2ab2599e43534904f",
|
|
30
|
+
"api/lib/features/subscriptions/api/entities/subscription_entity.dart": "20c1d75ed9d88acb96e94a592dcb4de0f63c792302ea07df53cebbdeb6d0cf7e",
|
|
31
|
+
"api/lib/features/subscriptions/api/stripe_backend_api.dart": "e370bd05211462f2fc94c69af1749eb5330f997e9ec5e73e5c4e119999bf666f",
|
|
32
|
+
"api/lib/features/subscriptions/api/subscription_api.dart": "c7484c9301d16245748025a3d420a89397e9e956b2211b7ed001c073ff2e4449",
|
|
33
|
+
"api/README.md": "1f30fc1ebf8fe02df6f3ee2f94c17f4bb4952abd5b125312d2c43eb6374eb354",
|
|
34
|
+
"supabase/android/app/src/main/AndroidManifest.xml": "0579c57068ee50d43837ef9c7bee884b6232e92975301c8305ce389282f68291",
|
|
35
|
+
"supabase/ios/Runner/AppDelegate.swift": "9f1027a03ad1fef2dab45464fbd7e98364b6a9cb6d2a6abe770ee9c45fa19122",
|
|
36
|
+
"supabase/lib/core/data/api/storage_api.dart": "d77fecc6923ea9544b6ec20571dcab5c8f3d38be81ec618b0cf4f1b08be7bbae",
|
|
37
|
+
"supabase/lib/core/data/api/user_api.dart": "06f491ebf4548b87431e2d6983148f4082e120ec7d6a928108cba954d16d8c56",
|
|
38
|
+
"supabase/lib/core/data/entities/json_converters.dart": "92a5efd595a03a862fa03e77ab3ca3d556523d0c9e0daca6e296e35d24b760a8",
|
|
39
|
+
"supabase/lib/core/data/entities/user_entity.dart": "5c1e56e4be3ba2ed95cdcd276112a02b526f2a3fa7ee5bba08a3ec2b51153688",
|
|
40
|
+
"supabase/lib/environments.dart": "c41f0843a4d627b717579eec5607b7ff5c16369bdd4b2a82e081058a20ffb7a5",
|
|
41
|
+
"supabase/lib/features/ai_chat/api/ai_chat_api.dart": "749047d30390e8d624c5a17d421096b05651e28b34a42071a9b73efc71d0a5f7",
|
|
42
|
+
"supabase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart": "1c4e4223e802d7fe8e93d9e7a09fcc6be81a41792d722e8b7e697837cde4737f",
|
|
43
|
+
"supabase/lib/features/ai_chat/api/ai_chat_message_entity.dart": "5aca4fdb5b1c38df04c9d377c6e6eeb6930ef85b2a32689fe6403e7cc7645373",
|
|
44
|
+
"supabase/lib/features/ai_chat/providers/ai_chat_notifier.dart": "f16ea3f3266e63a5591ceff9bc1900845f0b96e9bbbc5463ec6ab894f878eae3",
|
|
45
|
+
"supabase/lib/features/authentication/api/authentication_api.dart": "8b460722bb3bee7eee015f34aaa39c1c97cb264df1024b2855060dac232c620a",
|
|
46
|
+
"supabase/lib/features/authentication/repositories/authentication_repository.dart": "8e98ca0d39df3aac1dab159e6bdfe8d2bbb8e43931459984eb4ecfb676698ca2",
|
|
47
|
+
"supabase/lib/features/feedbacks/api/entities/feature_request_entity.dart": "e72c683c7321bf1f3c19fc1c6d54c6964bd99970e87c9ff31d8918efa93f0090",
|
|
48
|
+
"supabase/lib/features/feedbacks/api/entities/feature_vote_entity.dart": "a13ccfc493d8f5277beffc8e28f77ec322ab0c0a56358c3669353206b7c6756b",
|
|
49
|
+
"supabase/lib/features/feedbacks/api/feature_request_api.dart": "a741395509b95e8cae135bc8bdcca1ba0d48785b1e1b201732219659d271346b",
|
|
50
|
+
"supabase/lib/features/feedbacks/api/feature_vote_api.dart": "89dcaeea29460a7f219c2ede3759707b45066272fe6837c29a40f3d79fade4e9",
|
|
51
|
+
"supabase/lib/features/notifications/api/device_api.dart": "0d6bccb6813cceec29c54220daac9d0cd35687c4fa9f10ffde07dad88b204ed3",
|
|
52
|
+
"supabase/lib/features/notifications/api/entities/device_entity.dart": "54fce6274c5fe60ba663d8693e0c9d88fd6cd6fcd9ec906e021621a2e51176b7",
|
|
53
|
+
"supabase/lib/features/notifications/api/entities/notifications_entity.dart": "f9ee62111a6f122657e105df317f1f5f803e3a525a96686c0a2728ff02cd8964",
|
|
54
|
+
"supabase/lib/features/notifications/api/notifications_api.dart": "30cdb1bc52c3249b2cda6b1c7eb7fb6a977db5744da5a9c2980784f41e75d9e7",
|
|
55
|
+
"supabase/lib/features/onboarding/api/entities/user_info_entity.dart": "cd5aabc68310025f3ab9676cab126e1f755431f9e8752f868ee9fcd6f5892ce2",
|
|
56
|
+
"supabase/lib/features/onboarding/api/user_infos_api.dart": "cf728bcef364259912ee1e87151c4f791faee0c0d49232c9af260bb5681bd429",
|
|
57
|
+
"supabase/lib/features/onboarding/models/user_info.dart": "946fd34a33b630a34a3b6248594cbca717f4fab9d23f669510145b9032bd1321",
|
|
58
|
+
"supabase/lib/features/onboarding/repositories/user_infos_repository.dart": "487a5177dcaed7f87e613b2b4f04329c4995274e6a52809d4917c037cb3c3c55",
|
|
59
|
+
"supabase/lib/features/settings/ui/components/admin/admin_users_api.dart": "0ffb232ffb193154c0366d6f7da0ca499b3e06fed4fd71af572b8dc53ecd844e",
|
|
60
|
+
"supabase/lib/features/settings/ui/widgets/avatar_utils.dart": "bb9126409bbbb245f2dec613bd096ac53c208a56bd55f3d2ab2599e43534904f",
|
|
61
|
+
"supabase/lib/features/subscriptions/api/entities/subscription_entity.dart": "20c1d75ed9d88acb96e94a592dcb4de0f63c792302ea07df53cebbdeb6d0cf7e",
|
|
62
|
+
"supabase/lib/features/subscriptions/api/stripe_backend_api.dart": "e370bd05211462f2fc94c69af1749eb5330f997e9ec5e73e5c4e119999bf666f",
|
|
63
|
+
"supabase/lib/features/subscriptions/api/subscription_api.dart": "c7484c9301d16245748025a3d420a89397e9e956b2211b7ed001c073ff2e4449",
|
|
64
|
+
"supabase/lib/google_auth_options.dart": "9d3ab9be5928f3100b9b217864d916edd1223d186e60ed130c35628812cace66",
|
|
65
|
+
"supabase/README.md": "1f30fc1ebf8fe02df6f3ee2f94c17f4bb4952abd5b125312d2c43eb6374eb354"
|
|
66
|
+
}
|
|
@@ -101,11 +101,13 @@ Deno.serve(async (req: Request) => {
|
|
|
101
101
|
const item = sub.items?.data?.[0];
|
|
102
102
|
const priceId = item?.price?.id ?? "";
|
|
103
103
|
const ms = periodEndMs(sub);
|
|
104
|
+
const trialMs = sub.trial_end ? sub.trial_end * 1000 : null;
|
|
104
105
|
const now = new Date().toISOString();
|
|
105
106
|
const payload = {
|
|
106
107
|
status: statusFromStripe(sub),
|
|
107
108
|
last_update_date: now,
|
|
108
109
|
period_end_date: ms ? new Date(ms).toISOString() : null,
|
|
110
|
+
trial_end: trialMs ? new Date(trialMs).toISOString() : null,
|
|
109
111
|
sku_id: priceId,
|
|
110
112
|
offer_id: priceId,
|
|
111
113
|
store: "STRIPE",
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
-- Send a welcome notification
|
|
1
|
+
-- Send a welcome notification ONCE per account when a device is registered.
|
|
2
2
|
--
|
|
3
3
|
-- Why on device registration and not on user creation?
|
|
4
|
-
-- At user creation time no device token exists yet. By firing on
|
|
5
|
-
--
|
|
6
|
-
--
|
|
4
|
+
-- At user creation time no device token exists yet. By firing on INSERT into
|
|
5
|
+
-- `devices` we guarantee the locale is set and the notification can be localised
|
|
6
|
+
-- accurately.
|
|
7
7
|
--
|
|
8
|
-
--
|
|
9
|
-
--
|
|
8
|
+
-- Why a `welcome_sent` flag and not a device count?
|
|
9
|
+
-- Logout removes the device row and login re-creates it, so a device count
|
|
10
|
+
-- ("is this the first device?") sees a brand-new first device on every
|
|
11
|
+
-- re-login and re-sent the welcome each time. A one-shot flag on the user,
|
|
12
|
+
-- claimed atomically, sends it exactly once for the life of the account.
|
|
10
13
|
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
14
|
+
-- notify_user = false: the user is already inside the app at registration time,
|
|
15
|
+
-- so no push is needed — the notification is saved to the DB only.
|
|
16
|
+
--
|
|
17
|
+
-- Anonymous users (no email in public.users) are skipped until they have one.
|
|
18
|
+
|
|
19
|
+
-- One-time welcome guard (idempotent so re-running the migration is safe).
|
|
20
|
+
ALTER TABLE public.users
|
|
21
|
+
ADD COLUMN IF NOT EXISTS welcome_sent boolean NOT NULL DEFAULT false;
|
|
13
22
|
|
|
14
23
|
CREATE OR REPLACE FUNCTION public.trigger_welcome_notification()
|
|
15
24
|
RETURNS TRIGGER AS $$
|
|
16
25
|
DECLARE
|
|
17
|
-
v_email
|
|
18
|
-
v_locale
|
|
19
|
-
|
|
20
|
-
v_title
|
|
21
|
-
v_body
|
|
26
|
+
v_email text;
|
|
27
|
+
v_locale text;
|
|
28
|
+
v_claimed int;
|
|
29
|
+
v_title text;
|
|
30
|
+
v_body text;
|
|
22
31
|
BEGIN
|
|
23
|
-
--
|
|
24
|
-
|
|
25
|
-
FROM public.devices
|
|
26
|
-
WHERE user_id = NEW.user_id AND id != NEW.id;
|
|
27
|
-
|
|
28
|
-
IF v_count > 0 THEN
|
|
29
|
-
RETURN NEW;
|
|
30
|
-
END IF;
|
|
31
|
-
|
|
32
|
-
-- Skip anonymous users (no email set yet).
|
|
32
|
+
-- Skip anonymous users (no email yet); they get welcomed once they have one,
|
|
33
|
+
-- WITHOUT consuming the one-time claim below.
|
|
33
34
|
SELECT email, locale INTO v_email, v_locale
|
|
34
35
|
FROM public.users
|
|
35
36
|
WHERE id = NEW.user_id;
|
|
@@ -38,6 +39,18 @@ BEGIN
|
|
|
38
39
|
RETURN NEW;
|
|
39
40
|
END IF;
|
|
40
41
|
|
|
42
|
+
-- Atomically claim the one-time welcome: flip welcome_sent to true only if it
|
|
43
|
+
-- isn't already. ROW_COUNT is 1 for the very first claim, 0 for any later
|
|
44
|
+
-- device registration — including one re-created after a logout/login cycle.
|
|
45
|
+
UPDATE public.users
|
|
46
|
+
SET welcome_sent = true
|
|
47
|
+
WHERE id = NEW.user_id AND welcome_sent = false;
|
|
48
|
+
|
|
49
|
+
GET DIAGNOSTICS v_claimed = ROW_COUNT;
|
|
50
|
+
IF v_claimed = 0 THEN
|
|
51
|
+
RETURN NEW;
|
|
52
|
+
END IF;
|
|
53
|
+
|
|
41
54
|
-- Localised welcome message (falls back to English).
|
|
42
55
|
IF v_locale = 'pt' THEN
|
|
43
56
|
v_title := 'Bem-vindo!';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Adds trial_end to subscriptions so trial periods are persisted and the app
|
|
2
|
+
-- can detect an active trial. Mirrors the Firebase backend, where the Stripe
|
|
3
|
+
-- webhook writes trial_end (RevenueCat trials are read from the SDK, not the DB).
|
|
4
|
+
-- Idempotent: safe to run on a fresh project or an already-deployed database.
|
|
5
|
+
ALTER TABLE public.subscriptions
|
|
6
|
+
ADD COLUMN IF NOT EXISTS trial_end TIMESTAMPTZ;
|
package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import 'dart:async';
|
|
1
2
|
import 'dart:convert';
|
|
2
3
|
import 'package:crypto/crypto.dart';
|
|
3
4
|
import 'package:firebase_auth/firebase_auth.dart' as fb_auth;
|
|
@@ -11,6 +12,8 @@ import 'package:kasy_kit/core/data/api/base_api_exceptions.dart';
|
|
|
11
12
|
import 'package:kasy_kit/core/data/entities/user_entity.dart';
|
|
12
13
|
import 'package:kasy_kit/environments.dart';
|
|
13
14
|
import 'package:kasy_kit/google_auth_options.dart';
|
|
15
|
+
import 'package:kasy_kit/features/authentication/api/auth_web_support.dart'
|
|
16
|
+
if (dart.library.js_interop) 'package:kasy_kit/features/authentication/api/auth_web_support_web.dart';
|
|
14
17
|
import 'package:kasy_kit/features/authentication/api/authentication_api_interface.dart';
|
|
15
18
|
import 'package:kasy_kit/features/authentication/repositories/exceptions/authentication_exceptions.dart';
|
|
16
19
|
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
|
|
@@ -34,7 +37,41 @@ class SupabaseAuthenticationApi implements AuthenticationApi {
|
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
@override
|
|
37
|
-
Future<void> init() async {
|
|
40
|
+
Future<void> init() async {
|
|
41
|
+
if (!kIsWeb) return;
|
|
42
|
+
// Complete a pending mobile-web Google redirect sign-in. Firebase handled the
|
|
43
|
+
// full-page redirect (we reuse its web OAuth client to mint the Google ID
|
|
44
|
+
// token — see [signinWithGoogle]), so on return we exchange that token for a
|
|
45
|
+
// Supabase session. No-op when no redirect is pending or a session already
|
|
46
|
+
// exists.
|
|
47
|
+
try {
|
|
48
|
+
final result = await fb_auth.FirebaseAuth.instance.getRedirectResult();
|
|
49
|
+
final idToken = (result.credential as fb_auth.OAuthCredential?)?.idToken;
|
|
50
|
+
if (idToken != null && client.auth.currentUser == null) {
|
|
51
|
+
await client.auth.signInWithIdToken(
|
|
52
|
+
provider: OAuthProvider.google,
|
|
53
|
+
idToken: idToken,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
_logger.w('Google redirect sign-in completion failed: $e');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Mobile-web Google sign-in: start a full-page Firebase redirect instead of a
|
|
62
|
+
/// popup. Mobile browsers handle popups unreliably (the result is often lost
|
|
63
|
+
/// when the browser reclaims the backgrounded opener tab), leaving the app
|
|
64
|
+
/// stuck "signing in". The Supabase session is established at startup by [init]
|
|
65
|
+
/// via getRedirectResult. The page is navigating away as soon as
|
|
66
|
+
/// [signInWithRedirect] resolves, so this future intentionally never completes
|
|
67
|
+
/// — the caller stays in its loading state until the browser leaves.
|
|
68
|
+
/// See [isMobileWebBrowser].
|
|
69
|
+
Future<Credentials> _googleSignInWithRedirectWeb() async {
|
|
70
|
+
await fb_auth.FirebaseAuth.instance.signInWithRedirect(
|
|
71
|
+
fb_auth.GoogleAuthProvider(),
|
|
72
|
+
);
|
|
73
|
+
return Completer<Credentials>().future;
|
|
74
|
+
}
|
|
38
75
|
|
|
39
76
|
@override
|
|
40
77
|
Future<void> recoverPassword(String email) {
|
|
@@ -225,6 +262,7 @@ Note: wait a minute after enabling anonymous sign-in before trying again. It tak
|
|
|
225
262
|
@override
|
|
226
263
|
Future<Credentials> signinWithGoogle() async {
|
|
227
264
|
if (kIsWeb) {
|
|
265
|
+
if (isMobileWebBrowser()) return _googleSignInWithRedirectWeb();
|
|
228
266
|
// google_sign_in v7 can't do imperative auth on web. Get the Google ID token
|
|
229
267
|
// via Firebase's popup (zero manual config — reuses the Firebase web OAuth
|
|
230
268
|
// client + authorized domains, which the kit already sets up) and sign into
|
|
@@ -368,9 +406,21 @@ Note: wait a minute after enabling anonymous sign-in before trying again. It tak
|
|
|
368
406
|
@override
|
|
369
407
|
Future<Credentials> signupFromAnonymousWithGoogle() async {
|
|
370
408
|
if (kIsWeb) {
|
|
371
|
-
|
|
372
|
-
//
|
|
409
|
+
if (isMobileWebBrowser()) return _googleSignInWithRedirectWeb();
|
|
410
|
+
// Web: get the Google ID token via Firebase's popup (see signinWithGoogle).
|
|
373
411
|
final idToken = await _googleIdTokenFromWebPopup();
|
|
412
|
+
// On web the app runs in authRequired mode, so there is usually no anonymous
|
|
413
|
+
// Supabase session to link to (the user state is just a local placeholder).
|
|
414
|
+
// Linking without a session sends the anon key, which has no `sub`, and Supabase
|
|
415
|
+
// rejects it with "missing sub claim". When there's no session, sign in normally
|
|
416
|
+
// — it creates the account from the Google id_token. Mirrors the Firebase backend.
|
|
417
|
+
if (client.auth.currentUser == null) {
|
|
418
|
+
final res = await client.auth.signInWithIdToken(
|
|
419
|
+
provider: OAuthProvider.google,
|
|
420
|
+
idToken: idToken,
|
|
421
|
+
);
|
|
422
|
+
return Credentials(id: res.user!.id, token: res.session?.accessToken ?? '');
|
|
423
|
+
}
|
|
374
424
|
try {
|
|
375
425
|
final response = await client.auth.linkIdentityWithIdToken(
|
|
376
426
|
provider: OAuthProvider.google,
|
|
@@ -605,6 +655,35 @@ Note: wait a minute after enabling anonymous sign-in before trying again. It tak
|
|
|
605
655
|
Future<String?> getCurrentUserDisplayName() async =>
|
|
606
656
|
client.auth.currentUser?.userMetadata?['full_name'] as String?;
|
|
607
657
|
|
|
658
|
+
@override
|
|
659
|
+
Future<String?> getCurrentUserPhotoUrl() async {
|
|
660
|
+
final meta = client.auth.currentUser?.userMetadata;
|
|
661
|
+
return (meta?['avatar_url'] ?? meta?['picture']) as String?;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
@override
|
|
665
|
+
Future<List<String>> getLinkedProviders() async {
|
|
666
|
+
// Supabase exposes the linked identities in app_metadata['providers'].
|
|
667
|
+
final raw = client.auth.currentUser?.appMetadata['providers'];
|
|
668
|
+
if (raw is! List) return const [];
|
|
669
|
+
return raw.map((p) => p.toString()).toList();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
@override
|
|
673
|
+
Future<void> setPassword(String password) async {
|
|
674
|
+
// Supabase keeps the same user; sets/updates the password so a social-only
|
|
675
|
+
// account can also sign in with email + password.
|
|
676
|
+
await client.auth.updateUser(UserAttributes(password: password));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Supabase auto-links identities with the same (confirmed) email on sign-in,
|
|
680
|
+
// so there is no manual-link step: nothing to offer and nothing to do.
|
|
681
|
+
@override
|
|
682
|
+
Future<List<String>> linkableSocialProviders() async => const [];
|
|
683
|
+
|
|
684
|
+
@override
|
|
685
|
+
Future<void> linkSocialProvider(String provider) => Future.value();
|
|
686
|
+
|
|
608
687
|
String _normalizePhoneNumber(String phoneNumber) {
|
|
609
688
|
String normalized = phoneNumber.replaceAll(RegExp(r'\D'), '');
|
|
610
689
|
if (!normalized.startsWith('+')) {
|
|
@@ -68,6 +68,22 @@ abstract class AuthenticationRepository {
|
|
|
68
68
|
/// Returns the display name of the authenticated user.
|
|
69
69
|
Future<String?> getCurrentUserDisplayName();
|
|
70
70
|
|
|
71
|
+
/// Returns the photo URL of the current user (e.g. Google picture), or null.
|
|
72
|
+
Future<String?> getCurrentUserPhotoUrl();
|
|
73
|
+
|
|
74
|
+
/// Returns all sign-in providers linked to the current account.
|
|
75
|
+
Future<List<String>> getLinkedProviders();
|
|
76
|
+
|
|
77
|
+
/// Sets or updates an email/password credential for the current user, so a
|
|
78
|
+
/// social-only account can also sign in with email + password.
|
|
79
|
+
Future<void> setPassword(String password);
|
|
80
|
+
|
|
81
|
+
/// Social providers the current user can still link to their account.
|
|
82
|
+
Future<List<String>> linkableSocialProviders();
|
|
83
|
+
|
|
84
|
+
/// Links a social provider to the current account.
|
|
85
|
+
Future<void> linkSocialProvider(String provider);
|
|
86
|
+
|
|
71
87
|
/// Signin with Google Play Games account on Android
|
|
72
88
|
Future<void> signinWithGooglePlayGames();
|
|
73
89
|
|
|
@@ -268,6 +284,26 @@ class HttpAuthenticationRepository implements AuthenticationRepository {
|
|
|
268
284
|
Future<String?> getCurrentUserDisplayName() =>
|
|
269
285
|
_authenticationApi.getCurrentUserDisplayName();
|
|
270
286
|
|
|
287
|
+
@override
|
|
288
|
+
Future<String?> getCurrentUserPhotoUrl() =>
|
|
289
|
+
_authenticationApi.getCurrentUserPhotoUrl();
|
|
290
|
+
|
|
291
|
+
@override
|
|
292
|
+
Future<List<String>> getLinkedProviders() =>
|
|
293
|
+
_authenticationApi.getLinkedProviders();
|
|
294
|
+
|
|
295
|
+
@override
|
|
296
|
+
Future<void> setPassword(String password) =>
|
|
297
|
+
_authenticationApi.setPassword(password);
|
|
298
|
+
|
|
299
|
+
@override
|
|
300
|
+
Future<List<String>> linkableSocialProviders() =>
|
|
301
|
+
_authenticationApi.linkableSocialProviders();
|
|
302
|
+
|
|
303
|
+
@override
|
|
304
|
+
Future<void> linkSocialProvider(String provider) =>
|
|
305
|
+
_authenticationApi.linkSocialProvider(provider);
|
|
306
|
+
|
|
271
307
|
@override
|
|
272
308
|
Future<void> signinWithGooglePlayGames() async {
|
|
273
309
|
try {
|
|
@@ -37,6 +37,7 @@ sealed class SubscriptionEntity with _$SubscriptionEntity {
|
|
|
37
37
|
@JsonKey(name: 'creation_date') DateTime? creationDate,
|
|
38
38
|
@JsonKey(name: 'last_update_date') DateTime? lastUpdateDate,
|
|
39
39
|
@JsonKey(name: 'period_end_date') DateTime? periodEndDate,
|
|
40
|
+
@JsonKey(name: 'trial_end') DateTime? trialEnd,
|
|
40
41
|
@JsonKey(name: 'status') required SubscriptionStatus status,
|
|
41
42
|
@JsonKey(name: 'store', unknownEnumValue: SubscriptionStore.unknown)
|
|
42
43
|
SubscriptionStore? store,
|
package/lib/scaffold/generate.js
CHANGED
|
@@ -86,11 +86,50 @@ const { FIREBASE_SOURCE_DIR } = require('./shared/backend-config');
|
|
|
86
86
|
* @param {object} [options.moduleAnswers={}]
|
|
87
87
|
* @param {Function} [options.onProgress] - Callback to update the spinner in the CLI.
|
|
88
88
|
* @param {object} [options.deploy] - Used by the Firebase postBuild hook.
|
|
89
|
+
* @param {boolean} [options.offline=false] - Validation mode: never touch the
|
|
90
|
+
* network or the live Firebase project. Skips flutterfire configure, the Google
|
|
91
|
+
* auth/iOS/SW patches it feeds, and the Firestore rules deploy; writes a
|
|
92
|
+
* compilable stub for firebase_options_dev.dart so `flutter analyze` still works.
|
|
89
93
|
* @param {object} [hooks={}]
|
|
90
94
|
* @param {Function|null} [hooks.applyBackendSetup]
|
|
91
95
|
* @param {Function|null} [hooks.postBuild]
|
|
92
96
|
* @returns {Promise<{steps: object[], packageName: string, appName: string, bundleId: string, firebaseProjectId: string}>}
|
|
93
97
|
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Writes a compilable placeholder for lib/firebase_options_dev.dart, the file
|
|
101
|
+
* `flutterfire configure` normally generates from the real Firebase project.
|
|
102
|
+
*
|
|
103
|
+
* Used only by offline validation (`npm run validate:backends`): every backend's
|
|
104
|
+
* main.dart imports this file (Firebase powers FCM/remote config everywhere), so
|
|
105
|
+
* without it `flutter analyze` fails with "uri doesn't exist". The stub exposes
|
|
106
|
+
* the single member main.dart uses — DefaultFirebaseOptions.currentPlatform — with
|
|
107
|
+
* dummy values, so the analyzer type-checks the whole project without any network.
|
|
108
|
+
*/
|
|
109
|
+
async function writeFirebaseOptionsDevStub(targetDir) {
|
|
110
|
+
const stub = `// Offline validation stub written by \`npm run validate:backends\`.
|
|
111
|
+
// NOT shipped to clients: \`kasy new\` runs \`flutterfire configure\`, which
|
|
112
|
+
// generates the real file from their Firebase project. Present only so
|
|
113
|
+
// \`flutter analyze\` can type-check without touching the network.
|
|
114
|
+
// ignore_for_file: type=lint
|
|
115
|
+
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
116
|
+
|
|
117
|
+
class DefaultFirebaseOptions {
|
|
118
|
+
static FirebaseOptions get currentPlatform => const FirebaseOptions(
|
|
119
|
+
apiKey: 'offline-validation-stub',
|
|
120
|
+
appId: 'offline-validation-stub',
|
|
121
|
+
messagingSenderId: 'offline-validation-stub',
|
|
122
|
+
projectId: 'offline-validation-stub',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
await fs.outputFile(
|
|
127
|
+
path.join(targetDir, 'lib', 'firebase_options_dev.dart'),
|
|
128
|
+
stub,
|
|
129
|
+
'utf8',
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
94
133
|
async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
95
134
|
const {
|
|
96
135
|
appName,
|
|
@@ -102,6 +141,7 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
102
141
|
includeWeb = true,
|
|
103
142
|
language = 'en',
|
|
104
143
|
deferGoogleAuthPatches = false,
|
|
144
|
+
offline = false,
|
|
105
145
|
} = options;
|
|
106
146
|
|
|
107
147
|
const { applyBackendSetup = null, postBuild = null } = hooks;
|
|
@@ -361,9 +401,18 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
361
401
|
steps.push({ name: 'build-runner', skipped: true });
|
|
362
402
|
}
|
|
363
403
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
404
|
+
// Offline validation never touches the network or the live Firebase project, so
|
|
405
|
+
// skip flutterfire and write a compilable stub for the file it would generate.
|
|
406
|
+
// ffResult.ok stays false → the flutterfire-dependent patches below are skipped.
|
|
407
|
+
let ffResult = { ok: false };
|
|
408
|
+
if (offline) {
|
|
409
|
+
await writeFirebaseOptionsDevStub(targetDir);
|
|
410
|
+
steps.push({ name: 'flutterfire', skipped: true, detail: 'offline (stub written)' });
|
|
411
|
+
} else {
|
|
412
|
+
onProgress('flutterfire');
|
|
413
|
+
ffResult = await flutterfireConfigure(targetDir, firebaseProjectId, { includeWeb });
|
|
414
|
+
steps.push({ name: 'flutterfire', ok: ffResult.ok, detail: ffResult.ok ? null : ffResult.error });
|
|
415
|
+
}
|
|
367
416
|
|
|
368
417
|
// After flutterfire: patch Android, iOS and Web config files.
|
|
369
418
|
if (ffResult.ok) {
|
|
@@ -421,7 +470,7 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
421
470
|
// ONLY for FCM push — there is no Firestore there, so deploying firestore:rules
|
|
422
471
|
// is incoherent (it targets a database that doesn't exist) and just hangs for
|
|
423
472
|
// minutes. The data layer for those backends is Supabase/the REST API.
|
|
424
|
-
if (backend === 'firebase' && firebaseProjectId) {
|
|
473
|
+
if (!offline && backend === 'firebase' && firebaseProjectId) {
|
|
425
474
|
onProgress('firestore-rules');
|
|
426
475
|
const rulesResult = await deployFirestoreRules(targetDir, firebaseProjectId);
|
|
427
476
|
steps.push({ name: 'firestore-rules', ok: rulesResult.ok, detail: rulesResult.ok ? null : rulesResult.error });
|
|
@@ -336,6 +336,25 @@ module.exports = {
|
|
|
336
336
|
'new.firebase.success.deployStep': '• Deploy backend (from inside the project folder):',
|
|
337
337
|
|
|
338
338
|
'cli.command.deploy.description': 'Publish the server to Firebase or Supabase',
|
|
339
|
+
'cli.command.releaseVersion.description': 'Announce a new app version to existing users (Firebase Remote Config)',
|
|
340
|
+
'releaseVersion.intro': 'Announce version → Firebase Remote Config',
|
|
341
|
+
'releaseVersion.notKasy': 'Not a Kasy project (kit_setup.json not found).',
|
|
342
|
+
'releaseVersion.noFirebase': 'This command needs a Firebase project (firebase.json not found). Run it inside a project with the Firebase backend.',
|
|
343
|
+
'releaseVersion.noProject': 'Could not detect the Firebase project. Pass --project <id>.',
|
|
344
|
+
'releaseVersion.current': 'Installed version (pubspec): {version}',
|
|
345
|
+
'releaseVersion.detectedProject': 'Firebase project: {id}',
|
|
346
|
+
'releaseVersion.qVersion': 'Which version is now live in the stores?',
|
|
347
|
+
'releaseVersion.qForced': 'Force this update? (blocks the app until the user updates)',
|
|
348
|
+
'releaseVersion.forcedHint': 'Only force when an old version is truly broken — a wrong value locks every user out.',
|
|
349
|
+
'releaseVersion.summaryOptional': 'Optional update → app_latest_version = {version}',
|
|
350
|
+
'releaseVersion.summaryForced': 'FORCED update → app_min_version = {version} (+ app_latest_version)',
|
|
351
|
+
'releaseVersion.qConfirm': 'Publish this to Firebase Remote Config?',
|
|
352
|
+
'releaseVersion.fetching': 'Reading current Remote Config…',
|
|
353
|
+
'releaseVersion.deploying': 'Publishing to Remote Config…',
|
|
354
|
+
'releaseVersion.done': 'Done — {version} is live in Remote Config.',
|
|
355
|
+
'releaseVersion.failed': 'Failed to update Remote Config (is the Firebase CLI installed and are you logged in? try: firebase login).',
|
|
356
|
+
'releaseVersion.badVersion': 'Enter a valid SemVer like 1.4.0',
|
|
357
|
+
'releaseVersion.aborted': 'Aborted.',
|
|
339
358
|
'cli.command.configure.description': 'Configure optional keys (RevenueCat, Sentry, Mixpanel...) — skippable, picks up where you left off',
|
|
340
359
|
'configure.title': 'App credential setup',
|
|
341
360
|
'configure.notKasyProject': 'This folder is not a Kasy project (no pubspec.yaml). Cd into the project and run again.',
|
|
@@ -1134,6 +1153,10 @@ module.exports = {
|
|
|
1134
1153
|
'update.success': 'Feature "{module}" updated successfully.',
|
|
1135
1154
|
'update.componentsSuccess': 'Base components updated successfully.',
|
|
1136
1155
|
'update.coreSuccess': 'Core files updated successfully.',
|
|
1156
|
+
'update.mergeHint.title': 'Merge your changes with what is new',
|
|
1157
|
+
'update.mergeHint.body': '{count} file(s) changed. Check with `git diff`. If you had customized any, just merge yours with the new version.',
|
|
1158
|
+
'update.mergeHint.aiPrompt': 'With AI: paste into your assistant → "I ran kasy update; merge my changes from the previous commit with the new kit version in these files, keeping both."',
|
|
1159
|
+
'update.mergeHint.seeAgents': 'Step by step in the project AGENTS.md.',
|
|
1137
1160
|
'check.intro': 'Kasy Check — Push Notifications',
|
|
1138
1161
|
'check.firebase.detected': 'Firebase backend',
|
|
1139
1162
|
'check.firebase.adcInfo': 'Firebase uses Application Default Credentials — no extra setup needed.',
|
|
@@ -338,6 +338,25 @@ module.exports = {
|
|
|
338
338
|
'new.firebase.success.deployStep': '• Desplegar backend (desde dentro de la carpeta del proyecto):',
|
|
339
339
|
|
|
340
340
|
'cli.command.deploy.description': 'Publica el servidor en Firebase o Supabase',
|
|
341
|
+
'cli.command.releaseVersion.description': 'Avisa a los usuarios actuales sobre una nueva versión de la app (Firebase Remote Config)',
|
|
342
|
+
'releaseVersion.intro': 'Anunciar versión → Firebase Remote Config',
|
|
343
|
+
'releaseVersion.notKasy': 'No es un proyecto Kasy (kit_setup.json no encontrado).',
|
|
344
|
+
'releaseVersion.noFirebase': 'Este comando necesita un proyecto Firebase (firebase.json no encontrado). Ejecútalo dentro de un proyecto con backend Firebase.',
|
|
345
|
+
'releaseVersion.noProject': 'No se pudo detectar el proyecto Firebase. Usa --project <id>.',
|
|
346
|
+
'releaseVersion.current': 'Versión instalada (pubspec): {version}',
|
|
347
|
+
'releaseVersion.detectedProject': 'Proyecto Firebase: {id}',
|
|
348
|
+
'releaseVersion.qVersion': '¿Qué versión está publicada ya en las tiendas?',
|
|
349
|
+
'releaseVersion.qForced': '¿Forzar esta actualización? (bloquea la app hasta que el usuario actualice)',
|
|
350
|
+
'releaseVersion.forcedHint': 'Solo fuerza cuando una versión antigua esté realmente rota — un valor incorrecto bloquea a todos los usuarios.',
|
|
351
|
+
'releaseVersion.summaryOptional': 'Actualización opcional → app_latest_version = {version}',
|
|
352
|
+
'releaseVersion.summaryForced': 'Actualización OBLIGATORIA → app_min_version = {version} (+ app_latest_version)',
|
|
353
|
+
'releaseVersion.qConfirm': '¿Publicar esto en Firebase Remote Config?',
|
|
354
|
+
'releaseVersion.fetching': 'Leyendo el Remote Config actual…',
|
|
355
|
+
'releaseVersion.deploying': 'Publicando en Remote Config…',
|
|
356
|
+
'releaseVersion.done': 'Listo — {version} está activo en Remote Config.',
|
|
357
|
+
'releaseVersion.failed': 'No se pudo actualizar Remote Config (¿está instalado el Firebase CLI y has iniciado sesión? prueba: firebase login).',
|
|
358
|
+
'releaseVersion.badVersion': 'Ingresa un SemVer válido, como 1.4.0',
|
|
359
|
+
'releaseVersion.aborted': 'Cancelado.',
|
|
341
360
|
'cli.command.configure.description': 'Configura claves opcionales (RevenueCat, Sentry, Mixpanel...) — puede omitir, recuerda lo que falta',
|
|
342
361
|
'configure.title': 'Configuración de claves del app',
|
|
343
362
|
'configure.notKasyProject': 'Esta carpeta no es un proyecto Kasy (no se encontró pubspec.yaml). Entra en la carpeta del proyecto y ejecuta de nuevo.',
|
|
@@ -1132,6 +1151,10 @@ module.exports = {
|
|
|
1132
1151
|
'update.success': 'Feature "{module}" actualizada exitosamente.',
|
|
1133
1152
|
'update.componentsSuccess': 'Componentes base actualizados exitosamente.',
|
|
1134
1153
|
'update.coreSuccess': 'Archivos de core actualizados exitosamente.',
|
|
1154
|
+
'update.mergeHint.title': 'Combina tus cambios con lo nuevo',
|
|
1155
|
+
'update.mergeHint.body': '{count} archivo(s) cambiaron. Revisa con `git diff`. Si habías personalizado alguno, solo combina el tuyo con la versión nueva.',
|
|
1156
|
+
'update.mergeHint.aiPrompt': 'Con IA: pega en tu asistente → "Ejecuté kasy update; combina mis cambios del commit anterior con la versión nueva del kit en estos archivos, manteniendo ambos."',
|
|
1157
|
+
'update.mergeHint.seeAgents': 'Paso a paso en el AGENTS.md del proyecto.',
|
|
1135
1158
|
'check.intro': 'Kasy Check — Notificaciones Push',
|
|
1136
1159
|
'check.firebase.detected': 'Backend Firebase',
|
|
1137
1160
|
'check.firebase.adcInfo': 'Firebase usa Application Default Credentials — no requiere configuración extra.',
|
|
@@ -336,6 +336,25 @@ module.exports = {
|
|
|
336
336
|
'new.firebase.success.deployStep': '• Deploy do backend (de dentro da pasta do projeto):',
|
|
337
337
|
|
|
338
338
|
'cli.command.deploy.description': 'Publica o servidor no Firebase ou Supabase',
|
|
339
|
+
'cli.command.releaseVersion.description': 'Avisa os usuários atuais sobre uma nova versão do app (Firebase Remote Config)',
|
|
340
|
+
'releaseVersion.intro': 'Anunciar versão → Firebase Remote Config',
|
|
341
|
+
'releaseVersion.notKasy': 'Não é um projeto Kasy (kit_setup.json não encontrado).',
|
|
342
|
+
'releaseVersion.noFirebase': 'Este comando precisa de um projeto Firebase (firebase.json não encontrado). Rode dentro de um projeto com backend Firebase.',
|
|
343
|
+
'releaseVersion.noProject': 'Não foi possível detectar o projeto Firebase. Passe --project <id>.',
|
|
344
|
+
'releaseVersion.current': 'Versão instalada (pubspec): {version}',
|
|
345
|
+
'releaseVersion.detectedProject': 'Projeto Firebase: {id}',
|
|
346
|
+
'releaseVersion.qVersion': 'Qual versão já está publicada nas lojas?',
|
|
347
|
+
'releaseVersion.qForced': 'Forçar esta atualização? (bloqueia o app até o usuário atualizar)',
|
|
348
|
+
'releaseVersion.forcedHint': 'Só force quando uma versão antiga estiver realmente quebrada — um valor errado trava todos os usuários.',
|
|
349
|
+
'releaseVersion.summaryOptional': 'Atualização opcional → app_latest_version = {version}',
|
|
350
|
+
'releaseVersion.summaryForced': 'Atualização OBRIGATÓRIA → app_min_version = {version} (+ app_latest_version)',
|
|
351
|
+
'releaseVersion.qConfirm': 'Publicar isto no Firebase Remote Config?',
|
|
352
|
+
'releaseVersion.fetching': 'Lendo o Remote Config atual…',
|
|
353
|
+
'releaseVersion.deploying': 'Publicando no Remote Config…',
|
|
354
|
+
'releaseVersion.done': 'Pronto — {version} está no ar no Remote Config.',
|
|
355
|
+
'releaseVersion.failed': 'Falha ao atualizar o Remote Config (o Firebase CLI está instalado e você fez login? tente: firebase login).',
|
|
356
|
+
'releaseVersion.badVersion': 'Informe um SemVer válido, tipo 1.4.0',
|
|
357
|
+
'releaseVersion.aborted': 'Cancelado.',
|
|
339
358
|
'cli.command.configure.description': 'Configura chaves opcionais (RevenueCat, Sentry, Mixpanel...) — pode pular, lembra do que falta',
|
|
340
359
|
'configure.title': 'Configuração de chaves do app',
|
|
341
360
|
'configure.notKasyProject': 'Esta pasta não parece um projeto Kasy (não encontrei pubspec.yaml). Entre na pasta do projeto e rode novamente.',
|
|
@@ -1132,6 +1151,10 @@ module.exports = {
|
|
|
1132
1151
|
'update.success': 'Feature "{module}" atualizada com sucesso.',
|
|
1133
1152
|
'update.componentsSuccess': 'Componentes base atualizados com sucesso.',
|
|
1134
1153
|
'update.coreSuccess': 'Arquivos de core atualizados com sucesso.',
|
|
1154
|
+
'update.mergeHint.title': 'Juntar suas mudanças com o que veio novo',
|
|
1155
|
+
'update.mergeHint.body': '{count} arquivo(s) mudaram. Veja com `git diff`. Se você tinha customizado algum, é só juntar o seu com o novo.',
|
|
1156
|
+
'update.mergeHint.aiPrompt': 'Com IA: cole no seu assistente → "Rodei kasy update; junte minhas mudanças do commit anterior com a versão nova do kit nestes arquivos, mantendo as duas coisas."',
|
|
1157
|
+
'update.mergeHint.seeAgents': 'Passo a passo no AGENTS.md do projeto.',
|
|
1135
1158
|
'check.intro': 'Kasy Check — Notificações Push',
|
|
1136
1159
|
'check.firebase.detected': 'Backend Firebase',
|
|
1137
1160
|
'check.firebase.adcInfo': 'Firebase usa Application Default Credentials — nenhuma configuração extra necessária.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kasy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kasy": "./bin/kasy.js"
|
|
@@ -31,14 +31,17 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"prepack": "node scripts/check-feature-patches.js && node scripts/bundle-template.js && node test/generated-matches-kit.test.js && node test/supabase-verify-jwt.test.js && node test/supabase-cors.test.js && node test/supabase-google-web.test.js && node test/backend-pubspec-local-reminders.test.js && node test/stripe-webhook-orphan-guard.test.js && node test/facebook-strip.test.js && node test/path-provider-pin.test.js && node test/features-flags-parity.test.js && node test/apple-web.test.js && node test/facebook.test.js",
|
|
34
|
+
"prepack": "node scripts/check-feature-patches.js && node scripts/check-backend-patches.js && node scripts/bundle-template.js && node test/generated-matches-kit.test.js && node test/supabase-verify-jwt.test.js && node test/supabase-cors.test.js && node test/supabase-google-web.test.js && node test/backend-pubspec-local-reminders.test.js && node test/stripe-webhook-orphan-guard.test.js && node test/facebook-strip.test.js && node test/path-provider-pin.test.js && node test/features-flags-parity.test.js && node test/apple-web.test.js && node test/facebook.test.js",
|
|
35
35
|
"start": "node ./bin/kasy.js",
|
|
36
36
|
"setup": "node ./bin/kasy.js setup",
|
|
37
37
|
"doctor": "node ./bin/kasy.js doctor",
|
|
38
38
|
"features": "node ./bin/kasy.js features",
|
|
39
39
|
"validate": "node ./bin/kasy.js validate --analyze-only",
|
|
40
|
+
"validate:backends": "node ./scripts/bundle-template.js && node ./scripts/validate-backends.js",
|
|
40
41
|
"extract:patch": "node ./scripts/extract_patch.js",
|
|
41
42
|
"check:firebase": "node ./scripts/check-firebase-template.js",
|
|
43
|
+
"patch:check": "node ./scripts/check-backend-patches.js",
|
|
44
|
+
"patch:bless": "node ./scripts/check-backend-patches.js --bless",
|
|
42
45
|
"test": "for f in test/*.test.js; do node \"$f\" || exit 1; done",
|
|
43
46
|
"test:google-ios": "node ./test/google-ios-url-scheme.test.js",
|
|
44
47
|
"test:apple-release": "node ./test/apple-release.test.js",
|