kasy-cli 1.20.0 → 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/lib/commands/new.js +78 -37
- package/lib/commands/run.js +7 -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/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/generate.js +19 -3
- package/lib/scaffold/shared/generator-utils.js +265 -55
- package/lib/scaffold/shared/post-build.js +11 -0
- package/lib/utils/i18n/messages-en.js +5 -1
- package/lib/utils/i18n/messages-es.js +5 -1
- package/lib/utils/i18n/messages-pt.js +5 -1
- package/package.json +1 -1
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +3 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +38 -128
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -125
- package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
- package/templates/firebase/lib/features/home/home_components_page.dart +8 -14
- package/templates/firebase/lib/features/home/home_page.dart +7 -8
- package/templates/firebase/lib/router.dart +60 -0
- package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
- 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
|
@@ -52,6 +52,7 @@ dependencies:
|
|
|
52
52
|
intl: ^0.20.2
|
|
53
53
|
jiffy: ^6.4.4
|
|
54
54
|
json_annotation: ^4.9.0
|
|
55
|
+
local_auth: ^3.0.1
|
|
55
56
|
logger: ^2.6.2
|
|
56
57
|
lucide_icons_flutter: ^3.1.10
|
|
57
58
|
mixpanel_flutter: ^2.5.0
|
|
@@ -73,6 +74,7 @@ dependencies:
|
|
|
73
74
|
universal_html: ^2.3.0
|
|
74
75
|
universal_io: ^2.3.1
|
|
75
76
|
url_launcher: ^6.3.2
|
|
77
|
+
web: ^1.1.1
|
|
76
78
|
|
|
77
79
|
dev_dependencies:
|
|
78
80
|
build_runner: ^2.11.1
|
package/lib/scaffold/catalog.js
CHANGED
|
@@ -50,12 +50,12 @@ const FEATURE_CATALOG = [
|
|
|
50
50
|
{ id: 'revenuecat', displayName: 'RevenueCat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['saas', 'full'], enhances: 'subscription' },
|
|
51
51
|
// features
|
|
52
52
|
{ id: 'onboarding', displayName: 'Onboarding', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['starter', 'saas', 'content', 'full'] },
|
|
53
|
-
{ id: 'web', displayName: 'Web Support (PWA)', status: 'public', availableIn: ['firebase'
|
|
53
|
+
{ id: 'web', displayName: 'Web Support (PWA)', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
54
54
|
{ id: 'widget', displayName: 'Home Widget', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
55
55
|
{ id: 'llm_chat', displayName: 'AI Chat', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['content', 'full'] },
|
|
56
56
|
{ id: 'local_notifications', displayName: 'Local Reminders', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: [] },
|
|
57
57
|
// feedback (Firebase/Supabase only)
|
|
58
|
-
{ id: 'feedback', displayName: 'Feature Requests', status: 'public', availableIn: ['firebase', 'supabase'],
|
|
58
|
+
{ id: 'feedback', displayName: 'Feature Requests', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['saas', 'full'], tag: 'requiresDb' },
|
|
59
59
|
// ci/cd
|
|
60
60
|
{ id: 'ci', displayName: 'CI/CD', status: 'public', availableIn: ['firebase', 'supabase', 'api'], defaultInPresets: ['full'] },
|
|
61
61
|
];
|
package/lib/scaffold/generate.js
CHANGED
|
@@ -58,6 +58,7 @@ const {
|
|
|
58
58
|
removeFacebookSigninFromAuthPages,
|
|
59
59
|
removeAndroidFacebookMetadata,
|
|
60
60
|
writeNoOpAdminHomeWidgets,
|
|
61
|
+
patchLanguageSwitcherNoWidget,
|
|
61
62
|
writeNoOpFeatureRequestRepository,
|
|
62
63
|
writeNoOpSubscriptionStubs,
|
|
63
64
|
writeNoOpSentryUsages,
|
|
@@ -67,7 +68,7 @@ const {
|
|
|
67
68
|
removeDevelopmentTeam,
|
|
68
69
|
localizeReleaseDocs,
|
|
69
70
|
} = require('./shared/generator-utils');
|
|
70
|
-
const { pubGet, slangGenerate, buildRunner, dartFix, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, patchFirebaseServiceWorker } = require('./shared/post-build');
|
|
71
|
+
const { pubGet, slangGenerate, buildRunner, dartFix, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, patchFirebaseServiceWorker, deployFirestoreRules } = require('./shared/post-build');
|
|
71
72
|
const { FIREBASE_SOURCE_DIR } = require('./shared/backend-config');
|
|
72
73
|
|
|
73
74
|
/**
|
|
@@ -264,6 +265,9 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
264
265
|
// (imports home_widget_mywidget_service which only exists when widget is selected)
|
|
265
266
|
if (!modules.includes('widget')) {
|
|
266
267
|
await writeNoOpAdminHomeWidgets(targetDir);
|
|
268
|
+
// The language switcher pushes locale changes to the home widget — strip
|
|
269
|
+
// that hook so it doesn't reference the (now absent) widget provider.
|
|
270
|
+
await patchLanguageSwitcherNoWidget(targetDir);
|
|
267
271
|
// Remove Android native widget files and unregister the receiver from the manifest
|
|
268
272
|
// so the widget does not appear in the Android widget picker when not selected
|
|
269
273
|
await removeAndroidWidgetArtifacts(targetDir, bundleId);
|
|
@@ -341,8 +345,10 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
341
345
|
});
|
|
342
346
|
}
|
|
343
347
|
|
|
344
|
-
// iOS: register REVERSED_CLIENT_ID as a URL scheme in ios/Runner/Info.plist
|
|
345
|
-
|
|
348
|
+
// iOS: register REVERSED_CLIENT_ID as a URL scheme in ios/Runner/Info.plist.
|
|
349
|
+
// Skip for Supabase — REVERSED_CLIENT_ID isn't present until Google Sign-In is
|
|
350
|
+
// enabled later in new.js; that flow calls writeGoogleIosUrlSchemeFromClientId instead.
|
|
351
|
+
if (backend !== 'supabase' && !deferGoogleAuthPatches) {
|
|
346
352
|
const iosSchemeResult = await writeGoogleIosUrlScheme(targetDir);
|
|
347
353
|
steps.push({
|
|
348
354
|
name: 'google-ios-url-scheme',
|
|
@@ -367,6 +373,16 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
367
373
|
// (the dead lib/firebase_options.dart we no longer ship).
|
|
368
374
|
await cleanFirebaseJsonKitRefs(targetDir);
|
|
369
375
|
|
|
376
|
+
// Deploy Firestore security rules so the app works immediately after
|
|
377
|
+
// `kasy new` without needing `kasy deploy` first. Fast (<30s), billing-free.
|
|
378
|
+
// Without this the project gets Firebase's default deny-all rules, causing
|
|
379
|
+
// every Firestore read to throw permission-denied and the app to log the user out.
|
|
380
|
+
if (firebaseProjectId) {
|
|
381
|
+
onProgress('firestore-rules');
|
|
382
|
+
const rulesResult = await deployFirestoreRules(targetDir, firebaseProjectId);
|
|
383
|
+
steps.push({ name: 'firestore-rules', ok: rulesResult.ok, detail: rulesResult.ok ? null : rulesResult.error });
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
// ── 3. Post-build específico do backend ────────────────────────────────────
|
|
371
387
|
if (postBuild) {
|
|
372
388
|
try {
|
|
@@ -318,12 +318,14 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
318
318
|
const withOnboarding = modules.includes('onboarding');
|
|
319
319
|
const withRevenuecat = modules.includes('revenuecat');
|
|
320
320
|
const withAnalytics = modules.includes('analytics');
|
|
321
|
+
const withWidget = modules.includes('widget');
|
|
321
322
|
|
|
322
323
|
const fallback = withOnboarding ? '/onboarding' : '/signin';
|
|
323
324
|
|
|
324
325
|
const lines = [];
|
|
325
326
|
|
|
326
327
|
// ── Imports ────────────────────────────────────────────────────────────────
|
|
328
|
+
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
327
329
|
lines.push(`import 'package:flutter/material.dart';`);
|
|
328
330
|
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
329
331
|
lines.push(`import 'package:go_router/go_router.dart';`);
|
|
@@ -340,6 +342,12 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
340
342
|
if (withFeedback) {
|
|
341
343
|
lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
|
|
342
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';`);
|
|
343
351
|
if (withLlmChat) {
|
|
344
352
|
lines.push(`import 'package:${pkg}/features/llm_chat/llm_chat_page.dart';`);
|
|
345
353
|
}
|
|
@@ -349,10 +357,22 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
349
357
|
if (withOnboarding) {
|
|
350
358
|
lines.push(`import 'package:${pkg}/features/onboarding/ui/onboarding_page.dart';`);
|
|
351
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';`);
|
|
352
371
|
if (withRevenuecat) {
|
|
353
372
|
lines.push(`import 'package:${pkg}/features/subscription/ui/component/premium_page_factory.dart';`);
|
|
354
373
|
lines.push(`import 'package:${pkg}/features/subscription/ui/premium_page.dart';`);
|
|
355
374
|
}
|
|
375
|
+
lines.push(`import 'package:logger/logger.dart';`);
|
|
356
376
|
|
|
357
377
|
// ── Provider ──────────────────────────────────────────────────────────────
|
|
358
378
|
lines.push('');
|
|
@@ -374,7 +394,16 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
374
394
|
lines.push(` return GoRouter(`);
|
|
375
395
|
lines.push(` initialLocation: '/',`);
|
|
376
396
|
lines.push(` navigatorKey: navigatorKey,`);
|
|
377
|
-
|
|
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(` },`);
|
|
378
407
|
lines.push(` observers: [`);
|
|
379
408
|
if (withAnalytics) {
|
|
380
409
|
lines.push(` AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),`);
|
|
@@ -393,6 +422,42 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
393
422
|
lines.push(` ),`);
|
|
394
423
|
lines.push(` ),`);
|
|
395
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
|
+
|
|
396
461
|
// Onboarding
|
|
397
462
|
if (withOnboarding) {
|
|
398
463
|
lines.push(` GoRoute(`);
|
|
@@ -458,6 +523,64 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
|
|
|
458
523
|
lines.push(` ),`);
|
|
459
524
|
}
|
|
460
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
|
+
|
|
461
584
|
// Recover password + 404 (always)
|
|
462
585
|
lines.push(` GoRoute(`);
|
|
463
586
|
lines.push(` name: 'recover_password',`);
|
|
@@ -750,19 +873,28 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
750
873
|
const withWidget = modules.includes('widget');
|
|
751
874
|
const withRevenuecat = modules.includes('revenuecat');
|
|
752
875
|
const withSupabase = backend === 'supabase';
|
|
753
|
-
|
|
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;
|
|
754
882
|
|
|
755
883
|
const lines = [];
|
|
756
884
|
|
|
757
885
|
// ── Imports ────────────────────────────────────────────────────────────────
|
|
886
|
+
lines.push(`import 'package:device_preview/device_preview.dart';`);
|
|
758
887
|
if (withFirebase) {
|
|
759
888
|
lines.push(`import 'package:firebase_core/firebase_core.dart';`);
|
|
889
|
+
lines.push(`import 'package:firebase_messaging/firebase_messaging.dart';`);
|
|
760
890
|
}
|
|
761
891
|
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
762
892
|
lines.push(`import 'package:flutter/material.dart';`);
|
|
763
893
|
lines.push(`import 'package:flutter/services.dart';`);
|
|
764
894
|
lines.push(`import 'package:flutter_localizations/flutter_localizations.dart';`);
|
|
895
|
+
lines.push(`import 'package:flutter_native_splash/flutter_native_splash.dart';`);
|
|
765
896
|
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
897
|
+
lines.push(`import 'package:jiffy/jiffy.dart';`);
|
|
766
898
|
lines.push(`import 'package:logger/logger.dart';`);
|
|
767
899
|
lines.push(`import 'package:${pkg}/core/config/app_env.dart';`);
|
|
768
900
|
lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
|
|
@@ -772,9 +904,12 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
772
904
|
lines.push(`import 'package:${pkg}/core/home_widgets/home_widget_service.dart';`);
|
|
773
905
|
}
|
|
774
906
|
lines.push(`import 'package:${pkg}/core/initializer/onstart_widget.dart';`);
|
|
907
|
+
lines.push(`import 'package:${pkg}/core/keyboard_fix/keyboard_flicker_fix.dart';`);
|
|
775
908
|
lines.push(`import 'package:${pkg}/core/shared_preferences/shared_preferences.dart';`);
|
|
776
909
|
lines.push(`import 'package:${pkg}/core/states/user_state_notifier.dart';`);
|
|
910
|
+
lines.push(`import 'package:${pkg}/core/dev_inspector/dev_inspector.dart';`);
|
|
777
911
|
lines.push(`import 'package:${pkg}/core/theme/theme.dart';`);
|
|
912
|
+
lines.push(`import 'package:${pkg}/core/web_device_preview/web_device_preview.dart';`);
|
|
778
913
|
lines.push(`import 'package:${pkg}/environnements.dart';`);
|
|
779
914
|
if (withFirebase) {
|
|
780
915
|
lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
|
|
@@ -782,6 +917,9 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
782
917
|
lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
|
|
783
918
|
lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
|
|
784
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
|
+
}
|
|
785
923
|
lines.push(`import 'package:${pkg}/features/notifications/repositories/notifications_repository.dart';`);
|
|
786
924
|
if (withRevenuecat) {
|
|
787
925
|
lines.push(`import 'package:${pkg}/features/subscription/repositories/subscription_repository.dart';`);
|
|
@@ -798,7 +936,8 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
798
936
|
|
|
799
937
|
// ── main() ────────────────────────────────────────────────────────────────
|
|
800
938
|
lines.push(`void main() async {`);
|
|
801
|
-
lines.push(`
|
|
939
|
+
lines.push(` final widgetsBinding = KasyBinding.ensureInitialized();`);
|
|
940
|
+
lines.push(` FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);`);
|
|
802
941
|
lines.push(` await AppEnv.load();`);
|
|
803
942
|
lines.push(` final env = Environment.fromEnv();`);
|
|
804
943
|
lines.push(` final logger = Logger();`);
|
|
@@ -853,6 +992,13 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
853
992
|
lines.push(` ),`);
|
|
854
993
|
lines.push(` };`);
|
|
855
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('');
|
|
856
1002
|
}
|
|
857
1003
|
|
|
858
1004
|
if (withSentry) {
|
|
@@ -895,73 +1041,102 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
895
1041
|
lines.push(`// mode: ThemeMode.dark,`);
|
|
896
1042
|
lines.push(`// ),`);
|
|
897
1043
|
lines.push(`// See ./docs/theme.md for more details`);
|
|
898
|
-
lines.push(`class MyApp extends
|
|
1044
|
+
lines.push(`class MyApp extends ConsumerStatefulWidget {`);
|
|
899
1045
|
lines.push(` final SharedPreferences sharedPreferences;`);
|
|
900
1046
|
lines.push('');
|
|
901
1047
|
lines.push(` const MyApp({super.key, required this.sharedPreferences});`);
|
|
902
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('');
|
|
903
1075
|
lines.push(` // This widget is the root of your application.`);
|
|
904
1076
|
lines.push(` @override`);
|
|
905
|
-
lines.push(` Widget build(BuildContext context
|
|
1077
|
+
lines.push(` Widget build(BuildContext context) {`);
|
|
906
1078
|
lines.push(` ErrorWidget.builder = (FlutterErrorDetails details) {`);
|
|
907
1079
|
lines.push(` return AppErrorWidget(error: details);`);
|
|
908
1080
|
lines.push(` };`);
|
|
909
1081
|
lines.push(` final goRouter = ref.watch(goRouterProvider);`);
|
|
910
1082
|
lines.push('');
|
|
911
1083
|
lines.push(` return ThemeProvider(`);
|
|
912
|
-
lines.push(` notifier:
|
|
913
|
-
lines.push(` sharedPreferences: sharedPreferences,`);
|
|
914
|
-
lines.push(` themeFactory: const UniversalThemeFactory(),`);
|
|
915
|
-
lines.push(` lightColors: KasyColors.light(),`);
|
|
916
|
-
lines.push(` darkColors: KasyColors.dark(),`);
|
|
917
|
-
lines.push(` textTheme: KasyTextTheme.build(),`);
|
|
918
|
-
lines.push(` defaultMode: ThemeMode.light,`);
|
|
919
|
-
lines.push(` ),`);
|
|
1084
|
+
lines.push(` notifier: _appTheme,`);
|
|
920
1085
|
lines.push(` child: Builder(`);
|
|
921
1086
|
lines.push(` builder: (context) {`);
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
lines.push(`
|
|
927
|
-
lines.push(`
|
|
928
|
-
lines.push(`
|
|
929
|
-
lines.push(`
|
|
930
|
-
lines.push(`
|
|
931
|
-
lines.push(`
|
|
932
|
-
lines.push(`
|
|
933
|
-
lines.push(`
|
|
934
|
-
lines.push(`
|
|
935
|
-
lines.push(`
|
|
936
|
-
lines.push(`
|
|
937
|
-
lines.push(`
|
|
938
|
-
lines.push(`
|
|
939
|
-
lines.push(`
|
|
940
|
-
lines.push(`
|
|
941
|
-
lines.push(`
|
|
942
|
-
lines.push(`
|
|
943
|
-
lines.push(` //
|
|
944
|
-
lines.push(`
|
|
945
|
-
lines.push(`
|
|
946
|
-
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`);
|
|
947
1120
|
if (withRevenuecat) {
|
|
948
|
-
lines.push(`
|
|
1121
|
+
lines.push(` subscriptionRepositoryProvider,`);
|
|
949
1122
|
}
|
|
950
|
-
lines.push(`
|
|
1123
|
+
lines.push(` userStateNotifierProvider.notifier,`);
|
|
951
1124
|
if (withWidget) {
|
|
952
|
-
lines.push(`
|
|
1125
|
+
lines.push(` homeWidgetsManagerProvider,`);
|
|
953
1126
|
}
|
|
954
|
-
lines.push(`
|
|
955
|
-
lines.push(`
|
|
956
|
-
lines.push(`
|
|
957
|
-
lines.push(`
|
|
958
|
-
lines.push(`
|
|
959
|
-
lines.push(`
|
|
960
|
-
lines.push(`
|
|
961
|
-
lines.push(`
|
|
962
|
-
lines.push(`
|
|
963
|
-
lines.push(`
|
|
964
|
-
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(` ),`);
|
|
965
1140
|
lines.push(` ),`);
|
|
966
1141
|
lines.push(` ),`);
|
|
967
1142
|
lines.push(` ),`);
|
|
@@ -1185,6 +1360,30 @@ class AdminHomeWidgets extends StatelessWidget {
|
|
|
1185
1360
|
);
|
|
1186
1361
|
}
|
|
1187
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
|
+
|
|
1188
1387
|
/**
|
|
1189
1388
|
* Writes a NoOp feature_request_repository.dart when the feedback module is not selected.
|
|
1190
1389
|
* premium_page_provider.dart imports this provider — without it the project won't compile.
|
|
@@ -1286,7 +1485,12 @@ async function writeNoOpSentryUsages(projectDir) {
|
|
|
1286
1485
|
if (!(await fs.pathExists(filePath))) continue;
|
|
1287
1486
|
let content = await fs.readFile(filePath, 'utf8');
|
|
1288
1487
|
content = content.replace(sentryImport, '');
|
|
1289
|
-
|
|
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
|
+
);
|
|
1290
1494
|
await fs.outputFile(filePath, content, 'utf8');
|
|
1291
1495
|
}
|
|
1292
1496
|
}
|
|
@@ -1466,9 +1670,13 @@ sealed class Subscription with _$Subscription {
|
|
|
1466
1670
|
}
|
|
1467
1671
|
|
|
1468
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.
|
|
1469
1676
|
await fs.outputFile(
|
|
1470
1677
|
path.join(projectDir, 'lib', 'features', 'subscription', 'repositories', 'subscription_repository.dart'),
|
|
1471
|
-
|
|
1678
|
+
`// ignore_for_file: avoid_unused_constructor_parameters
|
|
1679
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1472
1680
|
import 'package:${pkg}/core/data/models/subscription.dart';
|
|
1473
1681
|
import 'package:${pkg}/core/initializer/onstart_service.dart';
|
|
1474
1682
|
// No-op subscription repository. Run: kasy add revenuecat to activate.
|
|
@@ -1561,7 +1769,8 @@ enum PaywallFactory { basic, basicRow, minimal, withSwitch }
|
|
|
1561
1769
|
// 5. No-op subscription_entity.dart (used by test_utils, fake_subscription_api)
|
|
1562
1770
|
await fs.outputFile(
|
|
1563
1771
|
path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'entities', 'subscription_entity.dart'),
|
|
1564
|
-
`//
|
|
1772
|
+
`// ignore_for_file: constant_identifier_names
|
|
1773
|
+
// No-op subscription entity. Run: kasy add revenuecat to activate.
|
|
1565
1774
|
enum SubscriptionStatus { ACTIVE, PAUSED, EXPIRED, LIFETIME, CANCELLED }
|
|
1566
1775
|
|
|
1567
1776
|
class SubscriptionEntity {
|
|
@@ -1736,6 +1945,7 @@ module.exports = {
|
|
|
1736
1945
|
removeFacebookSigninFromAuthPages,
|
|
1737
1946
|
removeAndroidFacebookMetadata,
|
|
1738
1947
|
writeNoOpAdminHomeWidgets,
|
|
1948
|
+
patchLanguageSwitcherNoWidget,
|
|
1739
1949
|
writeNoOpFeatureRequestRepository,
|
|
1740
1950
|
writeNoOpSubscriptionStubs,
|
|
1741
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)',
|
|
@@ -692,6 +692,10 @@ module.exports = {
|
|
|
692
692
|
'new.google.refreshConfigs': 'Updating google-services.json and GoogleService-Info.plist with Google Client IDs…',
|
|
693
693
|
'new.google.manualHint': 'Google Sign-In: enable manually in the Console (Google provider):',
|
|
694
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',
|
|
695
699
|
'new.sha1.registering': 'Registering SHA-1 for Google Sign-In (Android)…',
|
|
696
700
|
'new.sha1.failed': 'SHA-1 not added automatically: {error}',
|
|
697
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)',
|
|
@@ -692,6 +692,10 @@ module.exports = {
|
|
|
692
692
|
'new.google.refreshConfigs': 'Actualizando google-services.json y GoogleService-Info.plist con los Client IDs de Google…',
|
|
693
693
|
'new.google.manualHint': 'Inicio de sesión con Google: actívalo manualmente en la consola (proveedor Google):',
|
|
694
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',
|
|
695
699
|
'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
|
|
696
700
|
'new.sha1.failed': 'SHA-1 no añadido automaticamente: {error}',
|
|
697
701
|
'new.sha1.manual': 'Agregalo manualmente para que Google Sign-In funcione en Android:',
|