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
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/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)' },
|
|
@@ -1215,16 +1217,23 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1215
1217
|
// --with flag was passed: use those modules directly, skip preset prompt.
|
|
1216
1218
|
modules = preselectedModules;
|
|
1217
1219
|
} else if (isQuick) {
|
|
1218
|
-
// Quick mode: ship all features
|
|
1219
|
-
// it requires App ID + token that we can't auto-generate — the user
|
|
1220
|
-
// it later with `kasy add facebook` when they have the credentials.
|
|
1221
|
-
|
|
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));
|
|
1222
1229
|
} else {
|
|
1223
1230
|
section('new.advanced.section.features');
|
|
1224
1231
|
// Advanced mode: full multiselect — built from catalog, filtered by audience + backend.
|
|
1225
1232
|
const visibleFeatures = getVisibleFeatures({ audience: KASY_AUDIENCE, backend });
|
|
1226
1233
|
|
|
1227
|
-
//
|
|
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.
|
|
1228
1237
|
const isWebExcluded = backend === 'firebase' && firebaseSetupMode === 'create';
|
|
1229
1238
|
|
|
1230
1239
|
// Visual groups in display order (header key → feature ids in this group)
|
|
@@ -1479,7 +1488,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1479
1488
|
firebaseProjectId: core.firebaseProjectId.trim(),
|
|
1480
1489
|
modules: modules || [],
|
|
1481
1490
|
backend,
|
|
1482
|
-
|
|
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'),
|
|
1483
1494
|
supabaseUrl: core.supabaseUrl?.trim(),
|
|
1484
1495
|
supabaseAnonKey: core.supabaseAnonKey?.trim(),
|
|
1485
1496
|
apiBaseUrl: core.apiBaseUrl?.trim(),
|
|
@@ -1587,38 +1598,66 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1587
1598
|
: (supabaseExistingResult?.ok ? { projectRef: supabaseExistingResult.projectRef, dbPassword: supabaseExistingResult.dbPassword } : null);
|
|
1588
1599
|
|
|
1589
1600
|
if (backend === 'supabase') {
|
|
1590
|
-
// ──
|
|
1601
|
+
// ── Google Sign-In: create a real Google OAuth client, then push it to Supabase ──
|
|
1591
1602
|
const flutterfireOk = result.steps.find((s) => s.name === 'flutterfire')?.ok;
|
|
1592
|
-
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.
|
|
1593
1631
|
const fileCreds = await readSupabaseGoogleCredentials(targetDir);
|
|
1594
1632
|
googleWebClientId = fileCreds.webClientId;
|
|
1595
1633
|
googleIosClientId = fileCreds.iosClientId;
|
|
1596
1634
|
|
|
1597
|
-
//
|
|
1598
|
-
//
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
printStepResult({ name: 'google sign-in', ok: true }, language);
|
|
1606
|
-
}
|
|
1607
|
-
if (authResult.appleEnabled) {
|
|
1608
|
-
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
|
+
}
|
|
1609
1643
|
}
|
|
1610
1644
|
}
|
|
1611
1645
|
|
|
1612
|
-
//
|
|
1613
|
-
if (answers.firebaseProjectId && googleWebClientId) {
|
|
1614
|
-
googleClientSecret = await getGoogleClientSecretViaGcloud(answers.firebaseProjectId);
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
// 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.
|
|
1618
1647
|
if (googleWebClientId) {
|
|
1619
1648
|
const gaResult = await writeSupabaseGoogleAuthOptions(targetDir, googleWebClientId, googleIosClientId);
|
|
1620
1649
|
printStepResult({ name: 'google-auth-options', ok: gaResult.ok, detail: gaResult.error }, language);
|
|
1621
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
|
+
}
|
|
1622
1661
|
}
|
|
1623
1662
|
|
|
1624
1663
|
if (supabaseSetupPayload) {
|
|
@@ -1631,9 +1670,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1631
1670
|
fcmSpinner.stop(tr('new.fcm.generating'));
|
|
1632
1671
|
if (fcmResult.ok) {
|
|
1633
1672
|
fcmServiceAccountJson = fcmResult.json;
|
|
1634
|
-
printStepResult({ name: 'fcm-key', ok: true }, language);
|
|
1673
|
+
printStepResult({ name: 'fcm-key', ok: true, detail: tr('new.fcm.ok') }, language);
|
|
1635
1674
|
} else {
|
|
1636
|
-
printStepResult({ name: 'fcm-key', ok: false, detail: '
|
|
1675
|
+
printStepResult({ name: 'fcm-key', ok: false, detail: tr('new.fcm.failSupabase') }, language);
|
|
1637
1676
|
}
|
|
1638
1677
|
}
|
|
1639
1678
|
|
|
@@ -1657,12 +1696,14 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1657
1696
|
const google = googleWebClientId && googleClientSecret
|
|
1658
1697
|
? { webClientId: googleWebClientId, clientSecret: googleClientSecret }
|
|
1659
1698
|
: {};
|
|
1699
|
+
const apple = answers.bundleId ? { bundleId: answers.bundleId } : {};
|
|
1660
1700
|
const setupSteps = await setupLinkedProject(
|
|
1661
1701
|
targetDir,
|
|
1662
1702
|
supabaseSetupPayload.projectRef,
|
|
1663
1703
|
supabaseSetupPayload.dbPassword,
|
|
1664
1704
|
secrets,
|
|
1665
|
-
google
|
|
1705
|
+
google,
|
|
1706
|
+
apple
|
|
1666
1707
|
);
|
|
1667
1708
|
setupSpinner.stop(tr('new.supabase.setup'));
|
|
1668
1709
|
setupSteps.forEach((s) => printStepResult(s, language));
|
|
@@ -1724,7 +1765,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1724
1765
|
printStepResult({ name: 'fcm-key-saved', ok: false, detail: 'salvar arquivo de chave falhou' }, language);
|
|
1725
1766
|
}
|
|
1726
1767
|
} else {
|
|
1727
|
-
printStepResult({ name: 'fcm-key', ok: false, detail: '
|
|
1768
|
+
printStepResult({ name: 'fcm-key', ok: false, detail: tr('new.fcm.failApi') }, language);
|
|
1728
1769
|
}
|
|
1729
1770
|
}
|
|
1730
1771
|
|
|
@@ -1765,12 +1806,12 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1765
1806
|
}
|
|
1766
1807
|
|
|
1767
1808
|
// ── Google + Apple Sign-In: use Firebase CLI for Google (creates OAuth client),
|
|
1768
|
-
// then REST API for Apple as best-effort.
|
|
1809
|
+
// then REST API for Apple as best-effort.
|
|
1769
1810
|
// Firebase CLI's `deploy --only auth` is the only documented path that auto-
|
|
1770
|
-
// 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
|
|
1771
1812
|
// backend that the Console hits internally.
|
|
1772
|
-
// (Supabase backend
|
|
1773
|
-
//
|
|
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.)
|
|
1774
1815
|
if (backend === 'firebase' && answers.firebaseProjectId && flutterfireStep?.ok) {
|
|
1775
1816
|
const googleSpinner = ui.spinner();
|
|
1776
1817
|
googleSpinner.start(tr('new.google.enabling'));
|
package/lib/commands/run.js
CHANGED
|
@@ -303,6 +303,13 @@ async function runRun(directory, options = {}) {
|
|
|
303
303
|
// Firebase Auth persists sessions per-origin (IndexedDB) — a random port
|
|
304
304
|
// each run means the user gets logged out every restart.
|
|
305
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');
|
|
306
313
|
}
|
|
307
314
|
|
|
308
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
|
///
|