kasy-cli 1.19.3 → 1.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/bin/kasy.js +1 -0
- package/lib/commands/new.js +87 -37
- package/lib/commands/run.js +14 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +4 -5
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +317 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +40 -1
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +2 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -2
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +11 -8
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +91 -2
- package/lib/scaffold/backends/supabase/deploy.js +56 -3
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +5 -11
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +2 -2
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +31 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
- package/lib/scaffold/catalog.js +2 -2
- package/lib/scaffold/engine.js +5 -0
- package/lib/scaffold/generate.js +23 -3
- package/lib/scaffold/shared/generator-utils.js +303 -56
- package/lib/scaffold/shared/post-build.js +11 -0
- package/lib/utils/i18n/messages-en.js +6 -1
- package/lib/utils/i18n/messages-es.js +6 -1
- package/lib/utils/i18n/messages-pt.js +6 -1
- package/package.json +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1150 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
- package/templates/firebase/lib/components/kasy_text_field.dart +37 -34
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +13 -82
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -102
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +433 -243
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
- package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
- package/templates/firebase/lib/core/theme/colors.dart +6 -2
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
- package/templates/firebase/lib/features/home/home_components_page.dart +11 -14
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +121 -66
- package/templates/firebase/lib/features/home/home_page.dart +7 -8
- package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
- package/templates/firebase/lib/i18n/en.i18n.json +3 -1
- package/templates/firebase/lib/i18n/es.i18n.json +3 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
- package/templates/firebase/lib/router.dart +60 -0
- package/templates/firebase/pubspec.yaml +6 -4
- package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
- package/templates/firebase/web/index.html +7 -17
- package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +0 -211
- package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +0 -185
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/backends/api/patch/lib/main.dart +0 -275
- package/lib/scaffold/backends/api/patch/lib/router.dart +0 -133
- package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +0 -211
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +0 -174
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +0 -307
- package/lib/scaffold/backends/supabase/patch/lib/router.dart +0 -133
- package/templates/firebase/lib/firebase_options.dart +0 -75
|
@@ -229,6 +229,39 @@ async function writeFirebaserc(projectDir, firebaseProjectId) {
|
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Strip the kit's own Firebase project from the generated firebase.json.
|
|
234
|
+
*
|
|
235
|
+
* flutterfire configure rewrites the android/ios/web platform entries for the
|
|
236
|
+
* user's project, but it keys the dart entry by its --out path
|
|
237
|
+
* (lib/firebase_options_dev.dart) and leaves the template's stale
|
|
238
|
+
* `flutter.platforms.dart["lib/firebase_options.dart"]` entry untouched — which
|
|
239
|
+
* still points at the kit project (fir-kit-8e56b). We no longer ship that file
|
|
240
|
+
* (see ALWAYS_EXCLUDE_BASENAMES), so this drops the dead entry to guarantee the
|
|
241
|
+
* user's firebase.json never references the kit project.
|
|
242
|
+
*
|
|
243
|
+
* Best effort: no-ops when firebase.json is missing, unparseable, or already clean.
|
|
244
|
+
*
|
|
245
|
+
* @param {string} projectDir
|
|
246
|
+
* @returns {Promise<{ ok: boolean, changed: boolean }>}
|
|
247
|
+
*/
|
|
248
|
+
async function cleanFirebaseJsonKitRefs(projectDir) {
|
|
249
|
+
const firebaseJsonPath = path.join(projectDir, 'firebase.json');
|
|
250
|
+
if (!(await fs.pathExists(firebaseJsonPath))) return { ok: true, changed: false };
|
|
251
|
+
let config;
|
|
252
|
+
try {
|
|
253
|
+
config = await fs.readJson(firebaseJsonPath);
|
|
254
|
+
} catch {
|
|
255
|
+
return { ok: false, changed: false };
|
|
256
|
+
}
|
|
257
|
+
const dartMap = config && config.flutter && config.flutter.platforms && config.flutter.platforms.dart;
|
|
258
|
+
if (!dartMap || typeof dartMap !== 'object') return { ok: true, changed: false };
|
|
259
|
+
if (!('lib/firebase_options.dart' in dartMap)) return { ok: true, changed: false };
|
|
260
|
+
delete dartMap['lib/firebase_options.dart'];
|
|
261
|
+
await fs.writeJson(firebaseJsonPath, config, { spaces: 2 });
|
|
262
|
+
return { ok: true, changed: true };
|
|
263
|
+
}
|
|
264
|
+
|
|
232
265
|
/**
|
|
233
266
|
* Write environnements.dart overrides for backend-specific config.
|
|
234
267
|
*
|
|
@@ -285,12 +318,14 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
285
318
|
const withOnboarding = modules.includes('onboarding');
|
|
286
319
|
const withRevenuecat = modules.includes('revenuecat');
|
|
287
320
|
const withAnalytics = modules.includes('analytics');
|
|
321
|
+
const withWidget = modules.includes('widget');
|
|
288
322
|
|
|
289
323
|
const fallback = withOnboarding ? '/onboarding' : '/signin';
|
|
290
324
|
|
|
291
325
|
const lines = [];
|
|
292
326
|
|
|
293
327
|
// ── Imports ────────────────────────────────────────────────────────────────
|
|
328
|
+
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
294
329
|
lines.push(`import 'package:flutter/material.dart';`);
|
|
295
330
|
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
296
331
|
lines.push(`import 'package:go_router/go_router.dart';`);
|
|
@@ -307,6 +342,12 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
307
342
|
if (withFeedback) {
|
|
308
343
|
lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
|
|
309
344
|
}
|
|
345
|
+
// Home showcase detail screens — always present (not module-gated).
|
|
346
|
+
lines.push(`import 'package:${pkg}/features/home/design_system_page.dart';`);
|
|
347
|
+
lines.push(`import 'package:${pkg}/features/home/home_components_page.dart';`);
|
|
348
|
+
lines.push(`import 'package:${pkg}/features/home/home_components_preview_page.dart';`);
|
|
349
|
+
lines.push(`import 'package:${pkg}/features/home/home_components_preview_registry.dart';`);
|
|
350
|
+
lines.push(`import 'package:${pkg}/features/home/home_features_page.dart';`);
|
|
310
351
|
if (withLlmChat) {
|
|
311
352
|
lines.push(`import 'package:${pkg}/features/llm_chat/llm_chat_page.dart';`);
|
|
312
353
|
}
|
|
@@ -316,10 +357,22 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
316
357
|
if (withOnboarding) {
|
|
317
358
|
lines.push(`import 'package:${pkg}/features/onboarding/ui/onboarding_page.dart';`);
|
|
318
359
|
}
|
|
360
|
+
// Admin tools: send-push is always available; paywalls/home-widgets only
|
|
361
|
+
// when their module exists. The screens themselves are gated by kDebugMode.
|
|
362
|
+
// Kept in alphabetical order to satisfy directives_ordering.
|
|
363
|
+
if (withWidget) {
|
|
364
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/admin_home_widgets.dart';`);
|
|
365
|
+
}
|
|
366
|
+
if (withRevenuecat) {
|
|
367
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/admin_paywalls.dart';`);
|
|
368
|
+
}
|
|
369
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/admin_routes.dart';`);
|
|
370
|
+
lines.push(`import 'package:${pkg}/features/settings/ui/components/admin/send_push_notification_page.dart';`);
|
|
319
371
|
if (withRevenuecat) {
|
|
320
372
|
lines.push(`import 'package:${pkg}/features/subscription/ui/component/premium_page_factory.dart';`);
|
|
321
373
|
lines.push(`import 'package:${pkg}/features/subscription/ui/premium_page.dart';`);
|
|
322
374
|
}
|
|
375
|
+
lines.push(`import 'package:logger/logger.dart';`);
|
|
323
376
|
|
|
324
377
|
// ── Provider ──────────────────────────────────────────────────────────────
|
|
325
378
|
lines.push('');
|
|
@@ -341,7 +394,16 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
341
394
|
lines.push(` return GoRouter(`);
|
|
342
395
|
lines.push(` initialLocation: '/',`);
|
|
343
396
|
lines.push(` navigatorKey: navigatorKey,`);
|
|
344
|
-
|
|
397
|
+
// Unknown routes (e.g. a stale deep link) redirect home instead of dead-ending
|
|
398
|
+
// on a 404. The /404 GoRoute below still serves explicit navigation to it.
|
|
399
|
+
lines.push(` onException: (context, state, router) {`);
|
|
400
|
+
lines.push(` Logger().w(`);
|
|
401
|
+
lines.push(` 'GoRouter caught unknown route → "\${state.uri}" '`);
|
|
402
|
+
lines.push(` '(matched: "\${state.matchedLocation}", error: \${state.error}). '`);
|
|
403
|
+
lines.push(` 'Redirecting to "/".',`);
|
|
404
|
+
lines.push(` );`);
|
|
405
|
+
lines.push(` router.go('/');`);
|
|
406
|
+
lines.push(` },`);
|
|
345
407
|
lines.push(` observers: [`);
|
|
346
408
|
if (withAnalytics) {
|
|
347
409
|
lines.push(` AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),`);
|
|
@@ -360,6 +422,42 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
360
422
|
lines.push(` ),`);
|
|
361
423
|
lines.push(` ),`);
|
|
362
424
|
|
|
425
|
+
// Home showcase detail screens. TOP-LEVEL routes (siblings of '/', not children
|
|
426
|
+
// of the BottomMenu shell), so go_router renders them on the root navigator:
|
|
427
|
+
// full-screen, above the bottom bar, URL-addressable. Returning pops back to
|
|
428
|
+
// the tab with its menu intact.
|
|
429
|
+
lines.push(` GoRoute(`);
|
|
430
|
+
lines.push(` name: 'features',`);
|
|
431
|
+
lines.push(` path: '/features',`);
|
|
432
|
+
lines.push(` builder: (context, state) => const HomeFeaturesPage(),`);
|
|
433
|
+
lines.push(` ),`);
|
|
434
|
+
lines.push(` GoRoute(`);
|
|
435
|
+
lines.push(` name: 'design_system',`);
|
|
436
|
+
lines.push(` path: '/design-system',`);
|
|
437
|
+
lines.push(` builder: (context, state) => const DesignSystemPage(),`);
|
|
438
|
+
lines.push(` ),`);
|
|
439
|
+
lines.push(` GoRoute(`);
|
|
440
|
+
lines.push(` name: 'components',`);
|
|
441
|
+
lines.push(` path: '/components',`);
|
|
442
|
+
lines.push(` builder: (context, state) => const HomeComponentsPage(),`);
|
|
443
|
+
lines.push(` routes: [`);
|
|
444
|
+
lines.push(` GoRoute(`);
|
|
445
|
+
lines.push(` name: 'component_preview',`);
|
|
446
|
+
lines.push(` path: ':name',`);
|
|
447
|
+
lines.push(` builder: (context, state) {`);
|
|
448
|
+
lines.push(` final definition = getComponentPreviewDefinition(`);
|
|
449
|
+
lines.push(` state.pathParameters['name'] ?? '',`);
|
|
450
|
+
lines.push(` );`);
|
|
451
|
+
lines.push(` if (definition == null) return const PageNotFound();`);
|
|
452
|
+
lines.push(` return HomeComponentsPreviewPage(`);
|
|
453
|
+
lines.push(` title: definition.title,`);
|
|
454
|
+
lines.push(` variants: definition.variants,`);
|
|
455
|
+
lines.push(` );`);
|
|
456
|
+
lines.push(` },`);
|
|
457
|
+
lines.push(` ),`);
|
|
458
|
+
lines.push(` ],`);
|
|
459
|
+
lines.push(` ),`);
|
|
460
|
+
|
|
363
461
|
// Onboarding
|
|
364
462
|
if (withOnboarding) {
|
|
365
463
|
lines.push(` GoRoute(`);
|
|
@@ -425,6 +523,64 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
425
523
|
lines.push(` ),`);
|
|
426
524
|
}
|
|
427
525
|
|
|
526
|
+
// Notifications + Settings: open the BottomMenu shell on the right tab.
|
|
527
|
+
// These also back the deep links from the settings/admin UI.
|
|
528
|
+
lines.push(` GoRoute(`);
|
|
529
|
+
lines.push(` name: 'notifications',`);
|
|
530
|
+
lines.push(` path: '/notifications',`);
|
|
531
|
+
lines.push(` builder: (context, state) => const UserInfosGuard(`);
|
|
532
|
+
lines.push(` fallbackRoute: '${fallback}',`);
|
|
533
|
+
lines.push(` child: BottomMenu(initialRoute: 'notifications'),`);
|
|
534
|
+
lines.push(` ),`);
|
|
535
|
+
lines.push(` ),`);
|
|
536
|
+
lines.push(` GoRoute(`);
|
|
537
|
+
lines.push(` name: 'settings',`);
|
|
538
|
+
lines.push(` path: '/settings',`);
|
|
539
|
+
lines.push(` builder: (context, state) => const UserInfosGuard(`);
|
|
540
|
+
lines.push(` fallbackRoute: '${fallback}',`);
|
|
541
|
+
lines.push(` child: BottomMenu(initialRoute: 'settings'),`);
|
|
542
|
+
lines.push(` ),`);
|
|
543
|
+
lines.push(` ),`);
|
|
544
|
+
|
|
545
|
+
// Send push notification (admin tool — always routable; the entry point in
|
|
546
|
+
// the settings admin sheet is itself debug-gated).
|
|
547
|
+
lines.push(` GoRoute(`);
|
|
548
|
+
lines.push(` name: 'send_push',`);
|
|
549
|
+
lines.push(` path: adminRouteSendPush,`);
|
|
550
|
+
lines.push(` builder: (context, state) => const SendPushNotificationPage(),`);
|
|
551
|
+
lines.push(` ),`);
|
|
552
|
+
|
|
553
|
+
// Debug-only admin screens (paywall preview / home widgets preview).
|
|
554
|
+
if (withRevenuecat || withWidget) {
|
|
555
|
+
lines.push(` if (kDebugMode) ...[`);
|
|
556
|
+
if (withRevenuecat) {
|
|
557
|
+
lines.push(` GoRoute(`);
|
|
558
|
+
lines.push(` name: 'admin_paywalls',`);
|
|
559
|
+
lines.push(` path: adminRoutePaywalls,`);
|
|
560
|
+
lines.push(` builder: (context, state) => const AdminPaywalls(),`);
|
|
561
|
+
lines.push(` ),`);
|
|
562
|
+
}
|
|
563
|
+
if (withWidget) {
|
|
564
|
+
lines.push(` GoRoute(`);
|
|
565
|
+
lines.push(` name: 'admin_home_widgets',`);
|
|
566
|
+
lines.push(` path: adminRouteHomeWidgets,`);
|
|
567
|
+
lines.push(` builder: (context, state) => const AdminHomeWidgets(),`);
|
|
568
|
+
lines.push(` ),`);
|
|
569
|
+
}
|
|
570
|
+
if (withRevenuecat) {
|
|
571
|
+
lines.push(` GoRoute(`);
|
|
572
|
+
lines.push(` name: 'admin_premium_preview',`);
|
|
573
|
+
lines.push(` path: '/admin/premium/:variant',`);
|
|
574
|
+
lines.push(` builder: (context, state) {`);
|
|
575
|
+
lines.push(` final paywall = paywallFactoryFromAdminRoute(state.pathParameters['variant']);`);
|
|
576
|
+
lines.push(` if (paywall == null) return const PageNotFound();`);
|
|
577
|
+
lines.push(` return PremiumPage(paywall: paywall);`);
|
|
578
|
+
lines.push(` },`);
|
|
579
|
+
lines.push(` ),`);
|
|
580
|
+
}
|
|
581
|
+
lines.push(` ],`);
|
|
582
|
+
}
|
|
583
|
+
|
|
428
584
|
// Recover password + 404 (always)
|
|
429
585
|
lines.push(` GoRoute(`);
|
|
430
586
|
lines.push(` name: 'recover_password',`);
|
|
@@ -717,19 +873,28 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
717
873
|
const withWidget = modules.includes('widget');
|
|
718
874
|
const withRevenuecat = modules.includes('revenuecat');
|
|
719
875
|
const withSupabase = backend === 'supabase';
|
|
720
|
-
|
|
876
|
+
// Firebase powers push notifications (FCM) and remote config in EVERY backend
|
|
877
|
+
// (Supabase additionally uses it for Google Sign-In). Every backend runs
|
|
878
|
+
// `flutterfire configure` during generation (see generate.js), so
|
|
879
|
+
// firebase_options_dev.dart and the google-services files always exist, which
|
|
880
|
+
// makes Firebase.initializeApp safe to call in all of them.
|
|
881
|
+
const withFirebase = true;
|
|
721
882
|
|
|
722
883
|
const lines = [];
|
|
723
884
|
|
|
724
885
|
// ── Imports ────────────────────────────────────────────────────────────────
|
|
886
|
+
lines.push(`import 'package:device_preview/device_preview.dart';`);
|
|
725
887
|
if (withFirebase) {
|
|
726
888
|
lines.push(`import 'package:firebase_core/firebase_core.dart';`);
|
|
889
|
+
lines.push(`import 'package:firebase_messaging/firebase_messaging.dart';`);
|
|
727
890
|
}
|
|
728
891
|
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
729
892
|
lines.push(`import 'package:flutter/material.dart';`);
|
|
730
893
|
lines.push(`import 'package:flutter/services.dart';`);
|
|
731
894
|
lines.push(`import 'package:flutter_localizations/flutter_localizations.dart';`);
|
|
895
|
+
lines.push(`import 'package:flutter_native_splash/flutter_native_splash.dart';`);
|
|
732
896
|
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
897
|
+
lines.push(`import 'package:jiffy/jiffy.dart';`);
|
|
733
898
|
lines.push(`import 'package:logger/logger.dart';`);
|
|
734
899
|
lines.push(`import 'package:${pkg}/core/config/app_env.dart';`);
|
|
735
900
|
lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
|
|
@@ -739,9 +904,12 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
739
904
|
lines.push(`import 'package:${pkg}/core/home_widgets/home_widget_service.dart';`);
|
|
740
905
|
}
|
|
741
906
|
lines.push(`import 'package:${pkg}/core/initializer/onstart_widget.dart';`);
|
|
907
|
+
lines.push(`import 'package:${pkg}/core/keyboard_fix/keyboard_flicker_fix.dart';`);
|
|
742
908
|
lines.push(`import 'package:${pkg}/core/shared_preferences/shared_preferences.dart';`);
|
|
743
909
|
lines.push(`import 'package:${pkg}/core/states/user_state_notifier.dart';`);
|
|
910
|
+
lines.push(`import 'package:${pkg}/core/dev_inspector/dev_inspector.dart';`);
|
|
744
911
|
lines.push(`import 'package:${pkg}/core/theme/theme.dart';`);
|
|
912
|
+
lines.push(`import 'package:${pkg}/core/web_device_preview/web_device_preview.dart';`);
|
|
745
913
|
lines.push(`import 'package:${pkg}/environnements.dart';`);
|
|
746
914
|
if (withFirebase) {
|
|
747
915
|
lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
|
|
@@ -749,6 +917,9 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
749
917
|
lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
|
|
750
918
|
lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
|
|
751
919
|
lines.push(`import 'package:${pkg}/features/notifications/api/local_notifier.dart';`);
|
|
920
|
+
if (withFirebase) {
|
|
921
|
+
lines.push(`import 'package:${pkg}/features/notifications/repositories/background_notification_handler.dart';`);
|
|
922
|
+
}
|
|
752
923
|
lines.push(`import 'package:${pkg}/features/notifications/repositories/notifications_repository.dart';`);
|
|
753
924
|
if (withRevenuecat) {
|
|
754
925
|
lines.push(`import 'package:${pkg}/features/subscription/repositories/subscription_repository.dart';`);
|
|
@@ -765,7 +936,8 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
765
936
|
|
|
766
937
|
// ── main() ────────────────────────────────────────────────────────────────
|
|
767
938
|
lines.push(`void main() async {`);
|
|
768
|
-
lines.push(`
|
|
939
|
+
lines.push(` final widgetsBinding = KasyBinding.ensureInitialized();`);
|
|
940
|
+
lines.push(` FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);`);
|
|
769
941
|
lines.push(` await AppEnv.load();`);
|
|
770
942
|
lines.push(` final env = Environment.fromEnv();`);
|
|
771
943
|
lines.push(` final logger = Logger();`);
|
|
@@ -812,11 +984,21 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
812
984
|
lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
|
|
813
985
|
lines.push(` ),`);
|
|
814
986
|
lines.push(` ProdEnvironment() => Firebase.initializeApp(`);
|
|
815
|
-
lines.push(` //
|
|
987
|
+
lines.push(` // SETUP REQUIRED: For production, create a separate Firebase project and run:`);
|
|
988
|
+
lines.push(` // flutterfire configure --project=<your-prod-project> --out=lib/firebase_options_prod.dart`);
|
|
989
|
+
lines.push(` // Then import firebase_options_prod.dart and use it here instead.`);
|
|
990
|
+
lines.push(` // Reusing the dev project in production is only safe for early-stage development.`);
|
|
816
991
|
lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
|
|
817
992
|
lines.push(` ),`);
|
|
818
993
|
lines.push(` };`);
|
|
819
994
|
lines.push('');
|
|
995
|
+
lines.push(` // Jiffy locale must be set AFTER Firebase init because Firebase resets`);
|
|
996
|
+
lines.push(` // Intl.defaultLocale internally, which would override our setting.`);
|
|
997
|
+
lines.push(` await Jiffy.setLocale(LocaleSettings.instance.currentLocale.languageCode);`);
|
|
998
|
+
lines.push('');
|
|
999
|
+
lines.push(` // MUST be registered at the top-level BEFORE runApp().`);
|
|
1000
|
+
lines.push(` FirebaseMessaging.onBackgroundMessage(onBackgroundMessage);`);
|
|
1001
|
+
lines.push('');
|
|
820
1002
|
}
|
|
821
1003
|
|
|
822
1004
|
if (withSentry) {
|
|
@@ -859,73 +1041,102 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
859
1041
|
lines.push(`// mode: ThemeMode.dark,`);
|
|
860
1042
|
lines.push(`// ),`);
|
|
861
1043
|
lines.push(`// See ./docs/theme.md for more details`);
|
|
862
|
-
lines.push(`class MyApp extends
|
|
1044
|
+
lines.push(`class MyApp extends ConsumerStatefulWidget {`);
|
|
863
1045
|
lines.push(` final SharedPreferences sharedPreferences;`);
|
|
864
1046
|
lines.push('');
|
|
865
1047
|
lines.push(` const MyApp({super.key, required this.sharedPreferences});`);
|
|
866
1048
|
lines.push('');
|
|
1049
|
+
lines.push(` @override`);
|
|
1050
|
+
lines.push(` ConsumerState<MyApp> createState() => _MyAppState();`);
|
|
1051
|
+
lines.push(`}`);
|
|
1052
|
+
lines.push('');
|
|
1053
|
+
lines.push(`class _MyAppState extends ConsumerState<MyApp> {`);
|
|
1054
|
+
lines.push(` late final AppTheme _appTheme;`);
|
|
1055
|
+
lines.push('');
|
|
1056
|
+
lines.push(` @override`);
|
|
1057
|
+
lines.push(` void initState() {`);
|
|
1058
|
+
lines.push(` super.initState();`);
|
|
1059
|
+
lines.push(` _appTheme = AppTheme.uniform(`);
|
|
1060
|
+
lines.push(` sharedPreferences: widget.sharedPreferences,`);
|
|
1061
|
+
lines.push(` themeFactory: const UniversalThemeFactory(),`);
|
|
1062
|
+
lines.push(` lightColors: KasyColors.light(),`);
|
|
1063
|
+
lines.push(` darkColors: KasyColors.dark(),`);
|
|
1064
|
+
lines.push(` textTheme: KasyTextTheme.build(),`);
|
|
1065
|
+
lines.push(` defaultMode: ThemeMode.system,`);
|
|
1066
|
+
lines.push(` );`);
|
|
1067
|
+
lines.push(` }`);
|
|
1068
|
+
lines.push('');
|
|
1069
|
+
lines.push(` @override`);
|
|
1070
|
+
lines.push(` void dispose() {`);
|
|
1071
|
+
lines.push(` _appTheme.dispose();`);
|
|
1072
|
+
lines.push(` super.dispose();`);
|
|
1073
|
+
lines.push(` }`);
|
|
1074
|
+
lines.push('');
|
|
867
1075
|
lines.push(` // This widget is the root of your application.`);
|
|
868
1076
|
lines.push(` @override`);
|
|
869
|
-
lines.push(` Widget build(BuildContext context
|
|
1077
|
+
lines.push(` Widget build(BuildContext context) {`);
|
|
870
1078
|
lines.push(` ErrorWidget.builder = (FlutterErrorDetails details) {`);
|
|
871
1079
|
lines.push(` return AppErrorWidget(error: details);`);
|
|
872
1080
|
lines.push(` };`);
|
|
873
1081
|
lines.push(` final goRouter = ref.watch(goRouterProvider);`);
|
|
874
1082
|
lines.push('');
|
|
875
1083
|
lines.push(` return ThemeProvider(`);
|
|
876
|
-
lines.push(` notifier:
|
|
877
|
-
lines.push(` sharedPreferences: sharedPreferences,`);
|
|
878
|
-
lines.push(` themeFactory: const UniversalThemeFactory(),`);
|
|
879
|
-
lines.push(` lightColors: KasyColors.light(),`);
|
|
880
|
-
lines.push(` darkColors: KasyColors.dark(),`);
|
|
881
|
-
lines.push(` textTheme: KasyTextTheme.build(),`);
|
|
882
|
-
lines.push(` defaultMode: ThemeMode.light,`);
|
|
883
|
-
lines.push(` ),`);
|
|
1084
|
+
lines.push(` notifier: _appTheme,`);
|
|
884
1085
|
lines.push(` child: Builder(`);
|
|
885
1086
|
lines.push(` builder: (context) {`);
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
lines.push(`
|
|
891
|
-
lines.push(`
|
|
892
|
-
lines.push(`
|
|
893
|
-
lines.push(`
|
|
894
|
-
lines.push(`
|
|
895
|
-
lines.push(`
|
|
896
|
-
lines.push(`
|
|
897
|
-
lines.push(`
|
|
898
|
-
lines.push(`
|
|
899
|
-
lines.push(`
|
|
900
|
-
lines.push(`
|
|
901
|
-
lines.push(`
|
|
902
|
-
lines.push(`
|
|
903
|
-
lines.push(`
|
|
904
|
-
lines.push(`
|
|
905
|
-
lines.push(`
|
|
906
|
-
lines.push(`
|
|
907
|
-
lines.push(` //
|
|
908
|
-
lines.push(`
|
|
909
|
-
lines.push(`
|
|
910
|
-
lines.push(`
|
|
1087
|
+
// WebDevicePreview + DevInspector wrap the app so the in-app dev tools work:
|
|
1088
|
+
// • Cmd/Ctrl + Shift + P → widget inspector
|
|
1089
|
+
// • Cmd/Ctrl + Shift + D → device preview (web)
|
|
1090
|
+
// Both are no-ops in release builds.
|
|
1091
|
+
lines.push(` return WebDevicePreview.wrap(`);
|
|
1092
|
+
lines.push(` child: DevInspector.wrap(`);
|
|
1093
|
+
lines.push(` child: MaterialApp.router(`);
|
|
1094
|
+
lines.push(` title: 'Kasy',`);
|
|
1095
|
+
lines.push(` scaffoldMessengerKey: devInspectorRootScaffoldMessengerKey,`);
|
|
1096
|
+
lines.push(` theme: ThemeProvider.of(context).light,`);
|
|
1097
|
+
lines.push(` darkTheme: ThemeProvider.of(context).dark,`);
|
|
1098
|
+
lines.push(` themeMode: ThemeProvider.of(context).mode,`);
|
|
1099
|
+
lines.push(` themeAnimationDuration: Duration.zero,`);
|
|
1100
|
+
lines.push(` routerConfig: goRouter,`);
|
|
1101
|
+
lines.push(` localizationsDelegates: const [`);
|
|
1102
|
+
lines.push(` GlobalMaterialLocalizations.delegate,`);
|
|
1103
|
+
lines.push(` GlobalWidgetsLocalizations.delegate,`);
|
|
1104
|
+
lines.push(` GlobalCupertinoLocalizations.delegate,`);
|
|
1105
|
+
lines.push(` ],`);
|
|
1106
|
+
lines.push(` locale: TranslationProvider.of(context).flutterLocale,`);
|
|
1107
|
+
lines.push(` supportedLocales: AppLocaleUtils.supportedLocales,`);
|
|
1108
|
+
lines.push(` // Initializer is a widget that allows us to run some code before the app is ready`);
|
|
1109
|
+
lines.push(` builder: (context, child) => Initializer(`);
|
|
1110
|
+
lines.push(` services: [`);
|
|
1111
|
+
lines.push(` authenticationApiProvider,`);
|
|
1112
|
+
lines.push(` // shared preferences must be loaded`);
|
|
1113
|
+
lines.push(` sharedPreferencesProvider,`);
|
|
1114
|
+
lines.push(` // remote config api`);
|
|
1115
|
+
lines.push(` remoteConfigApiProvider,`);
|
|
1116
|
+
lines.push(` // notifications`);
|
|
1117
|
+
lines.push(` notificationsSettingsProvider,`);
|
|
1118
|
+
lines.push(` notificationRepositoryProvider,`);
|
|
1119
|
+
lines.push(` // user state`);
|
|
911
1120
|
if (withRevenuecat) {
|
|
912
|
-
lines.push(`
|
|
1121
|
+
lines.push(` subscriptionRepositoryProvider,`);
|
|
913
1122
|
}
|
|
914
|
-
lines.push(`
|
|
1123
|
+
lines.push(` userStateNotifierProvider.notifier,`);
|
|
915
1124
|
if (withWidget) {
|
|
916
|
-
lines.push(`
|
|
1125
|
+
lines.push(` homeWidgetsManagerProvider,`);
|
|
917
1126
|
}
|
|
918
|
-
lines.push(`
|
|
919
|
-
lines.push(`
|
|
920
|
-
lines.push(`
|
|
921
|
-
lines.push(`
|
|
922
|
-
lines.push(`
|
|
923
|
-
lines.push(`
|
|
924
|
-
lines.push(`
|
|
925
|
-
lines.push(`
|
|
926
|
-
lines.push(`
|
|
927
|
-
lines.push(`
|
|
928
|
-
lines.push(`
|
|
1127
|
+
lines.push(` // analytics`);
|
|
1128
|
+
lines.push(` analyticsApiProvider,`);
|
|
1129
|
+
lines.push(` facebookEventApiProvider,`);
|
|
1130
|
+
lines.push(` ],`);
|
|
1131
|
+
lines.push(` onReady: DevicePreview.appBuilder(context, child),`);
|
|
1132
|
+
lines.push(` onError: (_, error) => InitializationErrorPage(error: error),`);
|
|
1133
|
+
lines.push(` onLoading: Scaffold(`);
|
|
1134
|
+
lines.push(` body: Center(`);
|
|
1135
|
+
lines.push(` child: CircularProgressIndicator.adaptive(`);
|
|
1136
|
+
lines.push(` valueColor: AlwaysStoppedAnimation<Color>(`);
|
|
1137
|
+
lines.push(` context.colors.primary,`);
|
|
1138
|
+
lines.push(` ),`);
|
|
1139
|
+
lines.push(` ),`);
|
|
929
1140
|
lines.push(` ),`);
|
|
930
1141
|
lines.push(` ),`);
|
|
931
1142
|
lines.push(` ),`);
|
|
@@ -1149,6 +1360,30 @@ class AdminHomeWidgets extends StatelessWidget {
|
|
|
1149
1360
|
);
|
|
1150
1361
|
}
|
|
1151
1362
|
|
|
1363
|
+
/**
|
|
1364
|
+
* Strips the home-widget hook from language_switcher.dart when the widget module
|
|
1365
|
+
* is not selected. The switcher pushes the new locale to the home widget via
|
|
1366
|
+
* myWidgetHomeWidgetProvider, which only exists with the widget module — without
|
|
1367
|
+
* this the generated project fails to compile (undefined provider + dead import).
|
|
1368
|
+
*
|
|
1369
|
+
* @param {string} projectDir
|
|
1370
|
+
*/
|
|
1371
|
+
async function patchLanguageSwitcherNoWidget(projectDir) {
|
|
1372
|
+
const filePath = path.join(
|
|
1373
|
+
projectDir, 'lib', 'features', 'settings', 'ui', 'components', 'language_switcher.dart',
|
|
1374
|
+
);
|
|
1375
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
1376
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
1377
|
+
// Drop the home-widget service import.
|
|
1378
|
+
content = content.replace(/^import 'package:[^']*home_widget_mywidget_service\.dart';\n/m, '');
|
|
1379
|
+
// Drop the unawaited(...) block that updates the home widget on locale change.
|
|
1380
|
+
content = content.replace(
|
|
1381
|
+
/\n\s*unawaited\(\s*ref[\s\S]*?myWidgetHomeWidgetProvider[\s\S]*?\);/,
|
|
1382
|
+
'',
|
|
1383
|
+
);
|
|
1384
|
+
await fs.outputFile(filePath, content, 'utf8');
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1152
1387
|
/**
|
|
1153
1388
|
* Writes a NoOp feature_request_repository.dart when the feedback module is not selected.
|
|
1154
1389
|
* premium_page_provider.dart imports this provider — without it the project won't compile.
|
|
@@ -1250,7 +1485,12 @@ async function writeNoOpSentryUsages(projectDir) {
|
|
|
1250
1485
|
if (!(await fs.pathExists(filePath))) continue;
|
|
1251
1486
|
let content = await fs.readFile(filePath, 'utf8');
|
|
1252
1487
|
content = content.replace(sentryImport, '');
|
|
1253
|
-
|
|
1488
|
+
// Replace the Sentry call with a comment (not nothing) so the catch block
|
|
1489
|
+
// isn't left empty — an empty catch trips the empty_catches lint.
|
|
1490
|
+
content = content.replace(
|
|
1491
|
+
/^([ \t]*)Sentry\.captureException\([^)]*\);\n/gm,
|
|
1492
|
+
'$1// Error not reported — Sentry is disabled. Run: kasy add sentry.\n',
|
|
1493
|
+
);
|
|
1254
1494
|
await fs.outputFile(filePath, content, 'utf8');
|
|
1255
1495
|
}
|
|
1256
1496
|
}
|
|
@@ -1430,9 +1670,13 @@ sealed class Subscription with _$Subscription {
|
|
|
1430
1670
|
}
|
|
1431
1671
|
|
|
1432
1672
|
// 1. No-op subscription_repository.dart (used by user_repository, onboarding, home_widget_background_task)
|
|
1673
|
+
// The constructor keeps subscriptionApi/inAppSubscriptionApi/prefs params (unused
|
|
1674
|
+
// here) for source-compat with the bundled tests. ignore_for_file stops
|
|
1675
|
+
// `dart fix` from stripping them via avoid_unused_constructor_parameters.
|
|
1433
1676
|
await fs.outputFile(
|
|
1434
1677
|
path.join(projectDir, 'lib', 'features', 'subscription', 'repositories', 'subscription_repository.dart'),
|
|
1435
|
-
|
|
1678
|
+
`// ignore_for_file: avoid_unused_constructor_parameters
|
|
1679
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1436
1680
|
import 'package:${pkg}/core/data/models/subscription.dart';
|
|
1437
1681
|
import 'package:${pkg}/core/initializer/onstart_service.dart';
|
|
1438
1682
|
// No-op subscription repository. Run: kasy add revenuecat to activate.
|
|
@@ -1525,7 +1769,8 @@ enum PaywallFactory { basic, basicRow, minimal, withSwitch }
|
|
|
1525
1769
|
// 5. No-op subscription_entity.dart (used by test_utils, fake_subscription_api)
|
|
1526
1770
|
await fs.outputFile(
|
|
1527
1771
|
path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'entities', 'subscription_entity.dart'),
|
|
1528
|
-
`//
|
|
1772
|
+
`// ignore_for_file: constant_identifier_names
|
|
1773
|
+
// No-op subscription entity. Run: kasy add revenuecat to activate.
|
|
1529
1774
|
enum SubscriptionStatus { ACTIVE, PAUSED, EXPIRED, LIFETIME, CANCELLED }
|
|
1530
1775
|
|
|
1531
1776
|
class SubscriptionEntity {
|
|
@@ -1687,6 +1932,7 @@ module.exports = {
|
|
|
1687
1932
|
writeEnvExample,
|
|
1688
1933
|
writeEnvFileIfMissing,
|
|
1689
1934
|
writeFirebaserc,
|
|
1935
|
+
cleanFirebaseJsonKitRefs,
|
|
1690
1936
|
writeEnvironnementsOverrides,
|
|
1691
1937
|
writeFeaturesConfig,
|
|
1692
1938
|
writeKitSetup,
|
|
@@ -1699,6 +1945,7 @@ module.exports = {
|
|
|
1699
1945
|
removeFacebookSigninFromAuthPages,
|
|
1700
1946
|
removeAndroidFacebookMetadata,
|
|
1701
1947
|
writeNoOpAdminHomeWidgets,
|
|
1948
|
+
patchLanguageSwitcherNoWidget,
|
|
1702
1949
|
writeNoOpFeatureRequestRepository,
|
|
1703
1950
|
writeNoOpSubscriptionStubs,
|
|
1704
1951
|
writeNoOpSentryUsages,
|
|
@@ -864,6 +864,16 @@ async function getGoogleClientSecretViaGcloud(firebaseProjectId) {
|
|
|
864
864
|
}
|
|
865
865
|
}
|
|
866
866
|
|
|
867
|
+
// Deploys only Firestore security rules so the generated app works immediately
|
|
868
|
+
// without needing `kasy deploy`. Fast (<30s) and billing-free.
|
|
869
|
+
async function deployFirestoreRules(projectDir, firebaseProjectId) {
|
|
870
|
+
return run(
|
|
871
|
+
`firebase deploy --only firestore:rules --project ${firebaseProjectId}`,
|
|
872
|
+
projectDir,
|
|
873
|
+
120_000, // 2 min
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
867
877
|
module.exports = {
|
|
868
878
|
pubGet,
|
|
869
879
|
slangGenerate,
|
|
@@ -880,6 +890,7 @@ module.exports = {
|
|
|
880
890
|
validateFacebookAndroidStrings,
|
|
881
891
|
validateRevenueCat,
|
|
882
892
|
patchFirebaseServiceWorker,
|
|
893
|
+
deployFirestoreRules,
|
|
883
894
|
readSupabaseGoogleCredentials,
|
|
884
895
|
getGoogleClientSecretViaGcloud,
|
|
885
896
|
// Exported for focused unit tests; not part of the public CLI surface.
|
|
@@ -524,7 +524,7 @@ module.exports = {
|
|
|
524
524
|
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
525
525
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
526
526
|
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
527
|
-
'new.firebase.module.web': '🌐 Web Support (PWA
|
|
527
|
+
'new.firebase.module.web': '🌐 Web Support (PWA)',
|
|
528
528
|
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
529
529
|
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
530
530
|
'new.firebase.module.local_notifications': '🔔 Local Reminders (no server)',
|
|
@@ -633,6 +633,7 @@ module.exports = {
|
|
|
633
633
|
'new.firebase.interactive.billingWaiting': 'Checking Blaze status...',
|
|
634
634
|
'new.firebase.interactive.billingTimeout': 'Blaze plan not confirmed after timeout. Deploy skipped — run manually when ready.',
|
|
635
635
|
'new.firebase.interactive.authWarn': 'Could not enable Email/Password and Anonymous auth automatically. Enable manually:',
|
|
636
|
+
'new.firebase.localhostWarn': 'Could not authorize localhost for web sign-in. To test login in the browser, add "localhost" under Authorized domains:',
|
|
636
637
|
'new.firebase.existing.apisFailed': 'Could not activate APIs:',
|
|
637
638
|
'new.firebase.existing.googleSignInManual': 'Google Sign-In: enable manually in Authentication → Sign-in method → Google',
|
|
638
639
|
|
|
@@ -691,6 +692,10 @@ module.exports = {
|
|
|
691
692
|
'new.google.refreshConfigs': 'Updating google-services.json and GoogleService-Info.plist with Google Client IDs…',
|
|
692
693
|
'new.google.manualHint': 'Google Sign-In: enable manually in the Console (Google provider):',
|
|
693
694
|
'new.google.manualHint.noEmail': 'Google Sign-In: could not detect a support email (gcloud has no account). Enable manually in the Console:',
|
|
695
|
+
'new.google.supabaseManual': 'Google Sign-In: client created, but the secret was not available yet. Enable it later in the Supabase dashboard (Authentication > Providers > Google).',
|
|
696
|
+
'new.fcm.ok': 'generated automatically',
|
|
697
|
+
'new.fcm.failSupabase': 'not generated (GCP permission still propagating); set FIREBASE_SERVICE_ACCOUNT_JSON in your Supabase secrets',
|
|
698
|
+
'new.fcm.failApi': 'not generated (GCP permission still propagating); run the command again in a few minutes',
|
|
694
699
|
'new.sha1.registering': 'Registering SHA-1 for Google Sign-In (Android)…',
|
|
695
700
|
'new.sha1.failed': 'SHA-1 not added automatically: {error}',
|
|
696
701
|
'new.sha1.manual': 'Add it manually so Google Sign-In works on Android:',
|
|
@@ -524,7 +524,7 @@ module.exports = {
|
|
|
524
524
|
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
525
525
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
526
526
|
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
527
|
-
'new.firebase.module.web': '🌐 Web Support (PWA
|
|
527
|
+
'new.firebase.module.web': '🌐 Web Support (PWA)',
|
|
528
528
|
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
529
529
|
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
530
530
|
'new.firebase.module.local_notifications': '🔔 Local Reminders (sin servidor)',
|
|
@@ -633,6 +633,7 @@ module.exports = {
|
|
|
633
633
|
'new.firebase.interactive.billingWaiting': 'Verificando estado del Blaze...',
|
|
634
634
|
'new.firebase.interactive.billingTimeout': 'Plan Blaze no confirmado tras el tiempo límite. Despliegue omitido — ejecuta manualmente cuando estés listo.',
|
|
635
635
|
'new.firebase.interactive.authWarn': 'No se pudo activar Email/Contraseña y Anónimo automáticamente. Actívalos manualmente:',
|
|
636
|
+
'new.firebase.localhostWarn': 'No se pudo autorizar localhost para el inicio de sesión web. Para probar el login en el navegador, agrega "localhost" en Authorized domains:',
|
|
636
637
|
'new.firebase.existing.apisFailed': 'No se pudieron activar las APIs:',
|
|
637
638
|
'new.firebase.existing.googleSignInManual': 'Google Sign-In: activa manualmente en Authentication → Sign-in method → Google',
|
|
638
639
|
|
|
@@ -691,6 +692,10 @@ module.exports = {
|
|
|
691
692
|
'new.google.refreshConfigs': 'Actualizando google-services.json y GoogleService-Info.plist con los Client IDs de Google…',
|
|
692
693
|
'new.google.manualHint': 'Inicio de sesión con Google: actívalo manualmente en la consola (proveedor Google):',
|
|
693
694
|
'new.google.manualHint.noEmail': 'Inicio de sesión con Google: no detecté un email de soporte (gcloud sin cuenta). Actívalo manualmente en la consola:',
|
|
695
|
+
'new.google.supabaseManual': 'Inicio de sesión con Google: cliente creado, pero el secret aún no estaba disponible. Actívalo luego en el panel de Supabase (Authentication > Providers > Google).',
|
|
696
|
+
'new.fcm.ok': 'generada automáticamente',
|
|
697
|
+
'new.fcm.failSupabase': 'no generada (permiso de GCP aún propagando); define FIREBASE_SERVICE_ACCOUNT_JSON en los secrets de Supabase',
|
|
698
|
+
'new.fcm.failApi': 'no generada (permiso de GCP aún propagando); ejecuta el comando de nuevo en unos minutos',
|
|
694
699
|
'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
|
|
695
700
|
'new.sha1.failed': 'SHA-1 no añadido automaticamente: {error}',
|
|
696
701
|
'new.sha1.manual': 'Agregalo manualmente para que Google Sign-In funcione en Android:',
|
|
@@ -524,7 +524,7 @@ module.exports = {
|
|
|
524
524
|
'new.firebase.module.sentry': '🚨 Crash Reports (Sentry)',
|
|
525
525
|
'new.firebase.module.analytics': '📊 Analytics (Mixpanel)',
|
|
526
526
|
'new.firebase.module.facebook': '👤 Facebook (Login + Ads)',
|
|
527
|
-
'new.firebase.module.web': '🌐 Web Support (PWA
|
|
527
|
+
'new.firebase.module.web': '🌐 Web Support (PWA)',
|
|
528
528
|
'new.firebase.module.widget': '📱 Home Widget (iOS/Android)',
|
|
529
529
|
'new.firebase.module.llm_chat': '🤖 AI Chat (OpenAI/Gemini)',
|
|
530
530
|
'new.firebase.module.local_notifications': '🔔 Local Reminders (sem servidor)',
|
|
@@ -633,6 +633,7 @@ module.exports = {
|
|
|
633
633
|
'new.firebase.interactive.billingWaiting': 'Verificando status do Blaze...',
|
|
634
634
|
'new.firebase.interactive.billingTimeout': 'Plano Blaze não confirmado apos o tempo limite. Deploy ignorado — rode manualmente quando estiver pronto.',
|
|
635
635
|
'new.firebase.interactive.authWarn': 'Não foi possível ativar Email/Senha e Anônimo automaticamente. Ative manualmente:',
|
|
636
|
+
'new.firebase.localhostWarn': 'Não foi possível autorizar localhost para login web. Se for testar login no navegador, adicione "localhost" em Authorized domains:',
|
|
636
637
|
'new.firebase.existing.apisFailed': 'Não foi possível ativar APIs:',
|
|
637
638
|
'new.firebase.existing.googleSignInManual': 'Google Sign-In: ative manualmente em Authentication → Sign-in method → Google',
|
|
638
639
|
|
|
@@ -691,6 +692,10 @@ module.exports = {
|
|
|
691
692
|
'new.google.refreshConfigs': 'Atualizando google-services.json e GoogleService-Info.plist com Client IDs do Google…',
|
|
692
693
|
'new.google.manualHint': 'Login com Google: ative manualmente no Console (provedor Google):',
|
|
693
694
|
'new.google.manualHint.noEmail': 'Login com Google: não consegui detectar um e-mail de suporte (gcloud sem conta). Ative manualmente no Console:',
|
|
695
|
+
'new.google.supabaseManual': 'Login com Google: client criado, mas o secret ainda não estava disponível. Ative depois no painel do Supabase (Authentication > Providers > Google).',
|
|
696
|
+
'new.fcm.ok': 'gerada automaticamente',
|
|
697
|
+
'new.fcm.failSupabase': 'não gerada (permissão do GCP ainda propagando); defina FIREBASE_SERVICE_ACCOUNT_JSON nos secrets do Supabase',
|
|
698
|
+
'new.fcm.failApi': 'não gerada (permissão do GCP ainda propagando); rode o comando de novo em alguns minutos',
|
|
694
699
|
'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
|
|
695
700
|
'new.sha1.failed': 'SHA-1 não adicionado automaticamente: {error}',
|
|
696
701
|
'new.sha1.manual': 'Adicione manualmente para o Google Sign-In funcionar no Android:',
|