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
package/README.md
CHANGED
|
@@ -4,14 +4,22 @@ CLI for scaffolding production-ready Flutter SaaS apps.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
+
**macOS & Linux**
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
|
-
|
|
10
|
+
curl -fsSL https://kasy.dev/install | bash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Windows (PowerShell)**
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
irm https://kasy.dev/install.ps1 | iex
|
|
9
17
|
```
|
|
10
18
|
|
|
11
|
-
Or
|
|
19
|
+
Or via npm:
|
|
12
20
|
|
|
13
21
|
```bash
|
|
14
|
-
|
|
22
|
+
npm install -g kasy-cli
|
|
15
23
|
```
|
|
16
24
|
|
|
17
25
|
## Quick start
|
package/bin/kasy.js
CHANGED
|
@@ -357,6 +357,7 @@ function buildProgram(language) {
|
|
|
357
357
|
.option('--web', 'Run on web — prints localhost URL in lime so you open it in your own browser (your extensions, your accounts)')
|
|
358
358
|
.option('--open', 'With --web: auto-launch a clean Chrome window (Flutter profile, no extensions) instead of just printing the URL')
|
|
359
359
|
.option('--web-port <port>', 'Fixed port for web (default 5555) — keeps the origin stable so Firebase Auth sessions persist between runs')
|
|
360
|
+
.option('--web-hostname <host>', 'Host for web (default localhost) — localhost is a Firebase-authorized domain by default, so Google sign-in works without console changes')
|
|
360
361
|
.option('-d, --device <id>', 'Run on specific device ID')
|
|
361
362
|
.option('--prod', 'Use production dart-defines (from launch.json)')
|
|
362
363
|
.option('--no-defines', 'Skip dart-defines from launch.json')
|
package/lib/commands/new.js
CHANGED
|
@@ -50,7 +50,7 @@ const { generateFirebaseProject } = require('../scaffold/backends/firebase/gener
|
|
|
50
50
|
const { generateSupabaseProject } = require('../scaffold/backends/supabase/generator');
|
|
51
51
|
const { generateApiProject } = require('../scaffold/backends/api/generator');
|
|
52
52
|
const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys } = require('../scaffold/backends/supabase/deploy');
|
|
53
|
-
const { writeSupabaseGoogleAuthOptions, readSupabaseGoogleCredentials, getGoogleClientSecretViaGcloud, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme } = require('../scaffold/shared/post-build');
|
|
53
|
+
const { writeSupabaseGoogleAuthOptions, readSupabaseGoogleCredentials, getGoogleClientSecretViaGcloud, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, writeGoogleIosUrlSchemeFromClientId } = require('../scaffold/shared/post-build');
|
|
54
54
|
const { toPackageName } = require('../scaffold/backends/firebase/tokens');
|
|
55
55
|
const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getGcloudInstallInstructions, enableAuthProviders, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
|
|
56
56
|
const { enableAuthViaFirebaseCli } = require('../scaffold/backends/firebase/enable-auth-via-cli');
|
|
@@ -212,7 +212,7 @@ const STEP_LABELS = {
|
|
|
212
212
|
'supabase db push': { en: 'Database migrated', pt: 'Banco migrado', es: 'Base de datos migrada' },
|
|
213
213
|
'anonymous sign-in': { en: 'Anonymous sign-in enabled', pt: 'Login anonimo ativado', es: 'Inicio de sesion anonimo activado' },
|
|
214
214
|
'google sign-in': { en: 'Google Sign-In enabled', pt: 'Google Sign-In ativado', es: 'Google Sign-In activado' },
|
|
215
|
-
'apple sign-in': { en: 'Apple Sign-In
|
|
215
|
+
'apple sign-in': { en: 'Apple Sign-In enabled (native iOS ready; for web add a Service ID at kasy.dev/docs/apple)', pt: 'Apple Sign-In ativado (iOS nativo pronto; para web adicione um Service ID em kasy.dev/docs/apple)', es: 'Apple Sign-In activado (iOS nativo listo; para web añade un Service ID en kasy.dev/docs/apple)' },
|
|
216
216
|
'google-auth-options': { en: 'Google client IDs written', pt: 'Client IDs do Google gravados', es: 'Client IDs de Google escritos' },
|
|
217
217
|
'ios-url-scheme': { en: 'iOS URL scheme registered', pt: 'URL scheme iOS registrado', es: 'URL scheme iOS registrado' },
|
|
218
218
|
'google-ios-url-scheme': { en: 'iOS Google URL scheme registered', pt: 'URL scheme Google iOS registrado', es: 'URL scheme Google iOS registrado' },
|
|
@@ -220,7 +220,7 @@ const STEP_LABELS = {
|
|
|
220
220
|
'secret REVENUECAT_WEBHOOK_KEY': { en: 'RevenueCat webhook secret', pt: 'Secret webhook RevenueCat', es: 'Secret webhook RevenueCat' },
|
|
221
221
|
'secret META_ACCESS_TOKEN': { en: 'Meta Access Token', pt: 'Meta Access Token', es: 'Meta Access Token' },
|
|
222
222
|
'secret META_DATASET_ID': { en: 'Meta Dataset ID', pt: 'Meta Dataset ID', es: 'Meta Dataset ID' },
|
|
223
|
-
'fcm-key': { en: 'FCM Service Account key
|
|
223
|
+
'fcm-key': { en: 'FCM Service Account key', pt: 'Chave de Service Account FCM', es: 'Clave de Service Account FCM' },
|
|
224
224
|
'fcm-key-saved': { en: 'FCM key saved to .kasy/', pt: 'Chave FCM salva em .kasy/', es: 'Clave FCM guardada en .kasy/' },
|
|
225
225
|
'secret FIREBASE_SERVICE_ACCOUNT_JSON': { en: 'FCM Service Account configured', pt: 'Service Account FCM configurado', es: 'Service Account FCM configurado' },
|
|
226
226
|
'gcp-project': { en: 'GCP project created', pt: 'Projeto GCP criado', es: 'Proyecto GCP creado' },
|
|
@@ -233,6 +233,7 @@ const STEP_LABELS = {
|
|
|
233
233
|
'sha1': { en: 'SHA-1 added for Google Sign-In', pt: 'SHA-1 adicionado para Google Sign-In', es: 'SHA-1 añadido para Google Sign-In' },
|
|
234
234
|
'service-account': { en: 'Service account key created', pt: 'Chave de conta de servico criada', es: 'Clave de cuenta de servicio creada' },
|
|
235
235
|
'firestore': { en: 'Firestore database created', pt: 'Banco Firestore criado', es: 'Base de datos Firestore creada' },
|
|
236
|
+
'firestore-rules': { en: 'Firestore security rules deployed', pt: 'Regras de seguranca Firestore publicadas', es: 'Reglas de seguridad Firestore desplegadas' },
|
|
236
237
|
'storage': { en: 'Firebase Storage bucket created', pt: 'Bucket Firebase Storage criado', es: 'Bucket Firebase Storage creado' },
|
|
237
238
|
'storage-cors': { en: 'CORS enabled on Storage (web images)', pt: 'CORS ativado no Storage (imagens na web)', es: 'CORS activado en Storage (imágenes en web)' },
|
|
238
239
|
};
|
|
@@ -255,6 +256,7 @@ const STEP_PROGRESS = {
|
|
|
255
256
|
'sha1': { en: 'Adding SHA-1 for Google Sign-In…', pt: 'Adicionando SHA-1 para Google Sign-In…', es: 'Añadiendo SHA-1 para Google Sign-In…' },
|
|
256
257
|
'service-account': { en: 'Creating service account key…', pt: 'Criando chave de conta de servico…', es: 'Creando clave de cuenta de servicio…' },
|
|
257
258
|
'firestore': { en: 'Creating Firestore database…', pt: 'Criando banco Firestore…', es: 'Creando base de datos Firestore…' },
|
|
259
|
+
'firestore-rules': { en: 'Deploying Firestore security rules…', pt: 'Publicando regras de seguranca Firestore…', es: 'Desplegando reglas de seguridad Firestore…' },
|
|
258
260
|
'storage': { en: 'Creating Firebase Storage bucket…', pt: 'Criando bucket Firebase Storage…', es: 'Creando bucket Firebase Storage…' },
|
|
259
261
|
'storage-cors': { en: 'Enabling CORS on Storage…', pt: 'Ativando CORS no Storage…', es: 'Activando CORS en Storage…' },
|
|
260
262
|
'deploy-retry-wait': { en: 'Waiting 4 min for GCP permissions to propagate… (do not close terminal)', pt: 'Aguardando 4 min para permissões do GCP propagarem… (não feche o terminal)', es: 'Esperando 4 min para propagar permisos de GCP… (no cierres la terminal)' },
|
|
@@ -772,6 +774,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
772
774
|
} else if (key === 'auth-providers-warn') {
|
|
773
775
|
ps1.stop();
|
|
774
776
|
ui.log.warn(`${tr('new.firebase.interactive.authWarn')}\n${kleur.cyan(data?.url || '')}`);
|
|
777
|
+
} else if (key === 'auth-localhost-warn') {
|
|
778
|
+
ps1.stop();
|
|
779
|
+
ui.log.warn(`${tr('new.firebase.localhostWarn')}\n${kleur.cyan(data?.url || '')}`);
|
|
775
780
|
}
|
|
776
781
|
};
|
|
777
782
|
let setupResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
|
|
@@ -1179,6 +1184,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1179
1184
|
} else if (key === 'auth-google-warn') {
|
|
1180
1185
|
ps4.stop();
|
|
1181
1186
|
ui.log.warn(`${tr('new.firebase.existing.googleSignInManual')}\n${kleur.cyan(data?.url || '')}`);
|
|
1187
|
+
} else if (key === 'auth-localhost-warn') {
|
|
1188
|
+
ps4.stop();
|
|
1189
|
+
ui.log.warn(`${tr('new.firebase.localhostWarn')}\n${kleur.cyan(data?.url || '')}`);
|
|
1182
1190
|
}
|
|
1183
1191
|
},
|
|
1184
1192
|
});
|
|
@@ -1209,16 +1217,23 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1209
1217
|
// --with flag was passed: use those modules directly, skip preset prompt.
|
|
1210
1218
|
modules = preselectedModules;
|
|
1211
1219
|
} else if (isQuick) {
|
|
1212
|
-
// Quick mode: ship all features
|
|
1213
|
-
// it requires App ID + token that we can't auto-generate — the user
|
|
1214
|
-
// it later with `kasy add facebook` when they have the credentials.
|
|
1215
|
-
|
|
1220
|
+
// Quick mode: ship all features the backend supports. Facebook is excluded
|
|
1221
|
+
// because it requires App ID + token that we can't auto-generate — the user
|
|
1222
|
+
// adds it later with `kasy add facebook` when they have the credentials.
|
|
1223
|
+
// Filter by availableIn so backend-specific features (e.g. feedback needs a
|
|
1224
|
+
// DB) don't leak into a backend that can't support them.
|
|
1225
|
+
const quickAvailable = new Set(
|
|
1226
|
+
getVisibleFeatures({ audience: KASY_AUDIENCE, backend }).map((f) => f.id)
|
|
1227
|
+
);
|
|
1228
|
+
modules = (MODULE_PRESETS.full || []).filter((m) => m !== 'facebook' && quickAvailable.has(m));
|
|
1216
1229
|
} else {
|
|
1217
1230
|
section('new.advanced.section.features');
|
|
1218
1231
|
// Advanced mode: full multiselect — built from catalog, filtered by audience + backend.
|
|
1219
1232
|
const visibleFeatures = getVisibleFeatures({ audience: KASY_AUDIENCE, backend });
|
|
1220
1233
|
|
|
1221
|
-
//
|
|
1234
|
+
// In Firebase create-mode the web app is already decided by the
|
|
1235
|
+
// `firebaseIncludeWeb` prompt above, so hide `web` from the multiselect there
|
|
1236
|
+
// to avoid asking twice. Supabase, API and Firebase existing-mode show it.
|
|
1222
1237
|
const isWebExcluded = backend === 'firebase' && firebaseSetupMode === 'create';
|
|
1223
1238
|
|
|
1224
1239
|
// Visual groups in display order (header key → feature ids in this group)
|
|
@@ -1473,7 +1488,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1473
1488
|
firebaseProjectId: core.firebaseProjectId.trim(),
|
|
1474
1489
|
modules: modules || [],
|
|
1475
1490
|
backend,
|
|
1476
|
-
|
|
1491
|
+
// Web platform follows the `web` feature so the flutterfire web app and the
|
|
1492
|
+
// web/ folder stay in sync across all backends (Firebase, Supabase, API).
|
|
1493
|
+
includeWeb: modules.includes('web'),
|
|
1477
1494
|
supabaseUrl: core.supabaseUrl?.trim(),
|
|
1478
1495
|
supabaseAnonKey: core.supabaseAnonKey?.trim(),
|
|
1479
1496
|
apiBaseUrl: core.apiBaseUrl?.trim(),
|
|
@@ -1581,38 +1598,66 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1581
1598
|
: (supabaseExistingResult?.ok ? { projectRef: supabaseExistingResult.projectRef, dbPassword: supabaseExistingResult.dbPassword } : null);
|
|
1582
1599
|
|
|
1583
1600
|
if (backend === 'supabase') {
|
|
1584
|
-
// ──
|
|
1601
|
+
// ── Google Sign-In: create a real Google OAuth client, then push it to Supabase ──
|
|
1585
1602
|
const flutterfireOk = result.steps.find((s) => s.name === 'flutterfire')?.ok;
|
|
1586
|
-
if (flutterfireOk) {
|
|
1603
|
+
if (flutterfireOk && answers.firebaseProjectId) {
|
|
1604
|
+
// Supabase Google Sign-In needs a genuine OAuth Web Client (id + secret).
|
|
1605
|
+
// The only reliable way to create it is the Firebase CLI auth deploy, the
|
|
1606
|
+
// same path the Firebase backend uses. The REST API cannot create the OAuth
|
|
1607
|
+
// client, which is why Google used to come out disabled on Supabase projects.
|
|
1608
|
+
const googleSpinner = ui.spinner();
|
|
1609
|
+
googleSpinner.start(tr('new.google.enabling'));
|
|
1610
|
+
const cliResult = await enableAuthViaFirebaseCli({
|
|
1611
|
+
projectDir: targetDir,
|
|
1612
|
+
projectId: answers.firebaseProjectId,
|
|
1613
|
+
appName: answers.appName,
|
|
1614
|
+
});
|
|
1615
|
+
googleSpinner.stop(tr('new.google.enabling'));
|
|
1616
|
+
|
|
1617
|
+
if (cliResult.ok) {
|
|
1618
|
+
// The deploy created the OAuth Web Client + iOS client. Re-run flutterfire so
|
|
1619
|
+
// google-services.json and GoogleService-Info.plist pick up the new Client IDs.
|
|
1620
|
+
const rerunSpinner = ui.spinner();
|
|
1621
|
+
rerunSpinner.start(tr('new.google.refreshConfigs'));
|
|
1622
|
+
await flutterfireConfigure(targetDir, answers.firebaseProjectId, {
|
|
1623
|
+
includeWeb: answers.includeWeb !== false,
|
|
1624
|
+
});
|
|
1625
|
+
rerunSpinner.stop(tr('new.google.refreshConfigs'));
|
|
1626
|
+
} else if (cliResult.error === 'support_email_required') {
|
|
1627
|
+
ui.log.warn(tr('new.google.manualHint.noEmail'));
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// Read the Web + iOS Client IDs from the refreshed config files.
|
|
1587
1631
|
const fileCreds = await readSupabaseGoogleCredentials(targetDir);
|
|
1588
1632
|
googleWebClientId = fileCreds.webClientId;
|
|
1589
1633
|
googleIosClientId = fileCreds.iosClientId;
|
|
1590
1634
|
|
|
1591
|
-
//
|
|
1592
|
-
//
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
printStepResult({ name: 'google sign-in', ok: true }, language);
|
|
1600
|
-
}
|
|
1601
|
-
if (authResult.appleEnabled) {
|
|
1602
|
-
printStepResult({ name: 'apple sign-in', ok: true }, language);
|
|
1635
|
+
// Read the OAuth Client Secret from the Identity Toolkit now that the client
|
|
1636
|
+
// exists. It can lag a moment behind the deploy, so retry briefly.
|
|
1637
|
+
if (googleWebClientId) {
|
|
1638
|
+
for (let attempt = 1; attempt <= 3 && !googleClientSecret; attempt++) {
|
|
1639
|
+
googleClientSecret = await getGoogleClientSecretViaGcloud(answers.firebaseProjectId);
|
|
1640
|
+
if (!googleClientSecret && attempt < 3) {
|
|
1641
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1642
|
+
}
|
|
1603
1643
|
}
|
|
1604
1644
|
}
|
|
1605
1645
|
|
|
1606
|
-
//
|
|
1607
|
-
if (answers.firebaseProjectId && googleWebClientId) {
|
|
1608
|
-
googleClientSecret = await getGoogleClientSecretViaGcloud(answers.firebaseProjectId);
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
// Always write google_auth_options.dart with whatever credentials we have
|
|
1646
|
+
// Persist the Client IDs into the app and register the iOS URL scheme.
|
|
1612
1647
|
if (googleWebClientId) {
|
|
1613
1648
|
const gaResult = await writeSupabaseGoogleAuthOptions(targetDir, googleWebClientId, googleIosClientId);
|
|
1614
1649
|
printStepResult({ name: 'google-auth-options', ok: gaResult.ok, detail: gaResult.error }, language);
|
|
1615
1650
|
}
|
|
1651
|
+
if (googleIosClientId) {
|
|
1652
|
+
const iosResult = await writeGoogleIosUrlSchemeFromClientId(targetDir, googleIosClientId);
|
|
1653
|
+
printStepResult({ name: 'google-ios-url-scheme', ok: iosResult.ok, detail: iosResult.error }, language);
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Google is only enabled on Supabase when we have both the Web Client ID and
|
|
1657
|
+
// its secret. If either is missing, point the user to the dashboard to finish.
|
|
1658
|
+
if (!googleWebClientId || !googleClientSecret) {
|
|
1659
|
+
ui.log.warn(tr('new.google.supabaseManual'));
|
|
1660
|
+
}
|
|
1616
1661
|
}
|
|
1617
1662
|
|
|
1618
1663
|
if (supabaseSetupPayload) {
|
|
@@ -1625,9 +1670,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1625
1670
|
fcmSpinner.stop(tr('new.fcm.generating'));
|
|
1626
1671
|
if (fcmResult.ok) {
|
|
1627
1672
|
fcmServiceAccountJson = fcmResult.json;
|
|
1628
|
-
printStepResult({ name: 'fcm-key', ok: true }, language);
|
|
1673
|
+
printStepResult({ name: 'fcm-key', ok: true, detail: tr('new.fcm.ok') }, language);
|
|
1629
1674
|
} else {
|
|
1630
|
-
printStepResult({ name: 'fcm-key', ok: false, detail: '
|
|
1675
|
+
printStepResult({ name: 'fcm-key', ok: false, detail: tr('new.fcm.failSupabase') }, language);
|
|
1631
1676
|
}
|
|
1632
1677
|
}
|
|
1633
1678
|
|
|
@@ -1651,12 +1696,14 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1651
1696
|
const google = googleWebClientId && googleClientSecret
|
|
1652
1697
|
? { webClientId: googleWebClientId, clientSecret: googleClientSecret }
|
|
1653
1698
|
: {};
|
|
1699
|
+
const apple = answers.bundleId ? { bundleId: answers.bundleId } : {};
|
|
1654
1700
|
const setupSteps = await setupLinkedProject(
|
|
1655
1701
|
targetDir,
|
|
1656
1702
|
supabaseSetupPayload.projectRef,
|
|
1657
1703
|
supabaseSetupPayload.dbPassword,
|
|
1658
1704
|
secrets,
|
|
1659
|
-
google
|
|
1705
|
+
google,
|
|
1706
|
+
apple
|
|
1660
1707
|
);
|
|
1661
1708
|
setupSpinner.stop(tr('new.supabase.setup'));
|
|
1662
1709
|
setupSteps.forEach((s) => printStepResult(s, language));
|
|
@@ -1718,7 +1765,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1718
1765
|
printStepResult({ name: 'fcm-key-saved', ok: false, detail: 'salvar arquivo de chave falhou' }, language);
|
|
1719
1766
|
}
|
|
1720
1767
|
} else {
|
|
1721
|
-
printStepResult({ name: 'fcm-key', ok: false, detail: '
|
|
1768
|
+
printStepResult({ name: 'fcm-key', ok: false, detail: tr('new.fcm.failApi') }, language);
|
|
1722
1769
|
}
|
|
1723
1770
|
}
|
|
1724
1771
|
|
|
@@ -1759,12 +1806,12 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1759
1806
|
}
|
|
1760
1807
|
|
|
1761
1808
|
// ── Google + Apple Sign-In: use Firebase CLI for Google (creates OAuth client),
|
|
1762
|
-
// then REST API for Apple as best-effort.
|
|
1809
|
+
// then REST API for Apple as best-effort.
|
|
1763
1810
|
// Firebase CLI's `deploy --only auth` is the only documented path that auto-
|
|
1764
|
-
// creates the OAuth 2.0 Web Client without manual Console clicks
|
|
1811
|
+
// creates the OAuth 2.0 Web Client without manual Console clicks, the same
|
|
1765
1812
|
// backend that the Console hits internally.
|
|
1766
|
-
// (Supabase backend
|
|
1767
|
-
//
|
|
1813
|
+
// (The Supabase backend runs the same CLI deploy earlier, in its own block, then
|
|
1814
|
+
// pushes the resulting Google + Apple credentials to Supabase via the Mgmt API.)
|
|
1768
1815
|
if (backend === 'firebase' && answers.firebaseProjectId && flutterfireStep?.ok) {
|
|
1769
1816
|
const googleSpinner = ui.spinner();
|
|
1770
1817
|
googleSpinner.start(tr('new.google.enabling'));
|
|
@@ -1804,6 +1851,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1804
1851
|
if (appleResult.appleEnabled) {
|
|
1805
1852
|
printStepResult({ name: 'apple sign-in', ok: true }, language);
|
|
1806
1853
|
}
|
|
1854
|
+
if (appleResult.localhostAuthorized === false) {
|
|
1855
|
+
ui.log.warn(`${tr('new.firebase.localhostWarn')}\n${kleur.cyan(`https://console.firebase.google.com/project/${answers.firebaseProjectId}/authentication/settings`)}`);
|
|
1856
|
+
}
|
|
1807
1857
|
}
|
|
1808
1858
|
|
|
1809
1859
|
// APNs key (iOS push) is intentionally not mentioned here — it only becomes
|
package/lib/commands/run.js
CHANGED
|
@@ -292,10 +292,24 @@ async function runRun(directory, options = {}) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
if (isChromeTarget || isWebServerTarget) {
|
|
295
|
+
// Pin the host to `localhost` so the app's origin is always an authorized
|
|
296
|
+
// domain. Firebase Auth authorizes `localhost` by default but NOT
|
|
297
|
+
// `127.0.0.1`, and `flutter run -d chrome` would otherwise auto-launch
|
|
298
|
+
// Chrome at 127.0.0.1 — which breaks Google sign-in with
|
|
299
|
+
// [firebase_auth/unauthorized-domain]. Forcing localhost makes Google
|
|
300
|
+
// login work out of the box, no Firebase Console changes required.
|
|
301
|
+
deviceArgs.push('--web-hostname', options.webHostname || 'localhost');
|
|
295
302
|
// Pin a fixed port so the Chrome origin stays the same between runs.
|
|
296
303
|
// Firebase Auth persists sessions per-origin (IndexedDB) — a random port
|
|
297
304
|
// each run means the user gets logged out every restart.
|
|
298
305
|
deviceArgs.push('--web-port', options.webPort || '5555');
|
|
306
|
+
// Google Sign-In opens an auth popup that must talk back to the app window.
|
|
307
|
+
// The web server's default Cross-Origin-Opener-Policy can sever that link
|
|
308
|
+
// ("Cross-Origin-Opener-Policy policy would block the window.closed call"),
|
|
309
|
+
// so in a plain browser tab (kasy run --web, no dedicated Chrome) the popup
|
|
310
|
+
// can't report success and sign-in appears to fail. `same-origin-allow-popups`
|
|
311
|
+
// keeps the opener relationship intact while staying same-origin safe.
|
|
312
|
+
deviceArgs.push('--web-header', 'Cross-Origin-Opener-Policy=same-origin-allow-popups');
|
|
299
313
|
}
|
|
300
314
|
|
|
301
315
|
// Read dart-defines from .vscode/launch.json (skip if --no-defines)
|
|
@@ -10,7 +10,7 @@ final metaAdsApiProvider = Provider<MetaAdsApi>(
|
|
|
10
10
|
///
|
|
11
11
|
/// Your backend must expose:
|
|
12
12
|
/// POST /meta-track-event
|
|
13
|
-
/// Authorization: Bearer
|
|
13
|
+
/// Authorization: Bearer `<user-token>`
|
|
14
14
|
/// Body: { "event_name": "CompleteRegistration", "custom_data": {} }
|
|
15
15
|
///
|
|
16
16
|
/// The backend is responsible for calling the Meta Graph API server-to-server.
|
|
@@ -34,7 +34,7 @@ abstract class StorageApi {
|
|
|
34
34
|
///
|
|
35
35
|
/// Expected endpoints:
|
|
36
36
|
/// POST /storage/upload — multipart/form-data with fields: file, folder, public
|
|
37
|
-
/// DELETE /storage/file — JSON body: { "path": "<imagePath>" }
|
|
37
|
+
/// DELETE /storage/file — JSON body: `{ "path": "<imagePath>" }`
|
|
38
38
|
///
|
|
39
39
|
/// Expected upload response JSON:
|
|
40
40
|
/// { "path": "folder/filename.jpg", "url": "https://..." }
|
package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart
CHANGED
|
@@ -101,7 +101,7 @@ class HttpAuthenticationApi implements AuthenticationApi {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
@override
|
|
104
|
-
Future<Credentials> signinAnonymously()
|
|
104
|
+
Future<Credentials> signinAnonymously() {
|
|
105
105
|
// REST API backends typically do not support anonymous accounts natively.
|
|
106
106
|
// Option A – skip anonymous sign-in: remove anonymous auth from the
|
|
107
107
|
// onboarding flow and require full registration before using the app.
|
|
@@ -145,7 +145,7 @@ class HttpAuthenticationApi implements AuthenticationApi {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
@override
|
|
148
|
-
Future<Credentials> signinWithGooglePlay()
|
|
148
|
+
Future<Credentials> signinWithGooglePlay() {
|
|
149
149
|
// google_sign_in 7.x removed SignInOption.games.
|
|
150
150
|
// For Play Games support, use the games_services package separately.
|
|
151
151
|
return signinWithGoogle();
|
|
@@ -166,14 +166,13 @@ class HttpAuthenticationApi implements AuthenticationApi {
|
|
|
166
166
|
rethrow;
|
|
167
167
|
}
|
|
168
168
|
throw UnimplementedError('''
|
|
169
|
-
❌ You must edit lib/features/authentication/api/authentication_api.dart
|
|
169
|
+
❌ You must edit lib/features/authentication/api/authentication_api.dart
|
|
170
170
|
to send the Oauth2 token result to your backend
|
|
171
|
+
Available token: ${credential.identityToken}
|
|
171
172
|
----------------
|
|
172
173
|
Please follow the instructions here:
|
|
173
174
|
https://pub.dev/packages/sign_in_with_apple
|
|
174
175
|
''');
|
|
175
|
-
|
|
176
|
-
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
@override
|
|
@@ -11,7 +11,7 @@ final featureRequestApiProvider = Provider<FeatureRequestApi>(
|
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
/// REST endpoints expected:
|
|
14
|
-
/// GET /feature-requests → List<FeatureRequestEntity
|
|
14
|
+
/// GET /feature-requests → `List<FeatureRequestEntity>` (only active)
|
|
15
15
|
/// POST /feature-requests body: {title, description} → 201 Created
|
|
16
16
|
/// Server creates with active: false and stores title/description in all locales.
|
|
17
17
|
class FeatureRequestApi {
|
|
@@ -11,7 +11,7 @@ final featureVoteApiProvider = Provider<FeatureVoteApi>(
|
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
/// REST endpoints expected:
|
|
14
|
-
/// GET /feature-votes → List<UserFeatureVoteEntity
|
|
14
|
+
/// GET /feature-votes → `List<UserFeatureVoteEntity>` (filtered by authenticated user)
|
|
15
15
|
/// POST /feature-votes → UserFeatureVoteEntity body: {feature_id}
|
|
16
16
|
/// DELETE /feature-votes/:id → 204 No Content
|
|
17
17
|
class FeatureVoteApi {
|
|
@@ -9,7 +9,7 @@ final llmChatApiProvider = Provider<LlmChatApi>(
|
|
|
9
9
|
);
|
|
10
10
|
|
|
11
11
|
/// REST endpoints expected on your backend:
|
|
12
|
-
/// GET /llm-messages → List<LlmChatMessageEntity
|
|
12
|
+
/// GET /llm-messages → `List<LlmChatMessageEntity>` (current user)
|
|
13
13
|
/// POST /llm-messages → saves one message
|
|
14
14
|
/// DELETE /llm-messages → clears all messages (current user)
|
|
15
15
|
///
|