kasy-cli 1.21.8 → 1.21.9

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.
@@ -44,7 +44,7 @@ const { generateApiProject } = require('../scaffold/backends/api/generator');
44
44
  const { createProjectAndGetKeys, setupLinkedProject, checkLoggedIn, getOrgsList, getProjectsByOrg, getProjectKeys } = require('../scaffold/backends/supabase/deploy');
45
45
  const { writeSupabaseGoogleAuthOptions, readSupabaseGoogleCredentials, getGoogleClientSecretViaGcloud, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, writeGoogleIosUrlSchemeFromClientId } = require('../scaffold/shared/post-build');
46
46
  const { toPackageName } = require('../scaffold/backends/firebase/tokens');
47
- const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getFirebaseAccount, getGcloudInstallInstructions, enableAuthProviders, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
47
+ const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getFirebaseAccount, getGcloudInstallInstructions, enableAuthProviders, ensureFirebaseAuthInitialized, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
48
48
  const { enableAuthViaFirebaseCli } = require('../scaffold/backends/firebase/enable-auth-via-cli');
49
49
  const { createFcmServiceAccountKey } = require('../scaffold/shared/fcm-service-account');
50
50
 
@@ -1689,10 +1689,15 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
1689
1689
  // client, which is why Google used to come out disabled on Supabase projects.
1690
1690
  const googleSpinner = ui.spinner();
1691
1691
  googleSpinner.start(tr('new.google.enabling'));
1692
+ // The deploy below needs the project's Identity Platform config to exist.
1693
+ // Supabase setup runs in fcmOnly mode, which intentionally leaves Firebase
1694
+ // Auth untouched, so initialize it here (idempotent) before the deploy.
1695
+ await ensureFirebaseAuthInitialized(answers.firebaseProjectId);
1692
1696
  const cliResult = await enableAuthViaFirebaseCli({
1693
1697
  projectDir: targetDir,
1694
1698
  projectId: answers.firebaseProjectId,
1695
1699
  appName: answers.appName,
1700
+ googleOnly: true,
1696
1701
  });
1697
1702
  googleSpinner.stop(tr('new.google.enabling'));
1698
1703
 
@@ -63,9 +63,12 @@ async function mergeAuthIntoFirebaseJson(projectDir, providers) {
63
63
  * @param {string} options.projectId
64
64
  * @param {string} options.appName
65
65
  * @param {string} [options.supportEmail] - falls back to active gcloud account
66
+ * @param {boolean} [options.googleOnly] - Supabase: enable ONLY Google (the deploy's
67
+ * side effect creates the OAuth Web Client we need). Anonymous/Email/Password in
68
+ * Firebase Auth would be dead config there, since the app uses Supabase Auth.
66
69
  * @returns {{ ok: boolean, error?: string, supportEmail?: string }}
67
70
  */
68
- async function enableAuthViaFirebaseCli({ projectDir, projectId, appName, supportEmail }) {
71
+ async function enableAuthViaFirebaseCli({ projectDir, projectId, appName, supportEmail, googleOnly = false }) {
69
72
  // 1. Resolve support email (required by Google's OAuth consent screen)
70
73
  let email = (supportEmail || '').trim();
71
74
  if (!email) email = await getGcloudAccountEmail();
@@ -76,15 +79,20 @@ async function enableAuthViaFirebaseCli({ projectDir, projectId, appName, suppor
76
79
  };
77
80
  }
78
81
 
79
- // 2. Merge firebase.json
80
- const merge = await mergeAuthIntoFirebaseJson(projectDir, {
81
- anonymous: true,
82
- emailPassword: true,
82
+ // 2. Merge firebase.json. Google is always enabled (it's the whole point — the
83
+ // deploy creates its OAuth Web Client). Anonymous + Email/Password are added only
84
+ // for the Firebase backend, whose app actually signs in through Firebase Auth.
85
+ const providers = {
83
86
  googleSignIn: {
84
87
  oAuthBrandDisplayName: appName,
85
88
  supportEmail: email,
86
89
  },
87
- });
90
+ };
91
+ if (!googleOnly) {
92
+ providers.anonymous = true;
93
+ providers.emailPassword = true;
94
+ }
95
+ const merge = await mergeAuthIntoFirebaseJson(projectDir, providers);
88
96
  if (!merge.ok) return merge;
89
97
 
90
98
  // 3. Deploy. --non-interactive prevents the CLI from prompting on edge cases.
@@ -730,34 +730,24 @@ async function ensureLocalhostAuthorizedDomains(projectId, token) {
730
730
  }
731
731
 
732
732
  /**
733
- * Enable Firebase Auth sign-in providers: Email/Password, Anonymous and Google.
734
- * Uses the Identity Toolkit Admin v2 REST API with gcloud credentials.
733
+ * Initialize Firebase Auth (Identity Platform) for a project. Brand-new projects
734
+ * have no auth config, so any Admin v2 operation (PATCH /config, or the Firebase
735
+ * CLI `deploy --only auth`) fails with CONFIGURATION_NOT_FOUND until this runs.
735
736
  *
736
- * Flow:
737
- * 1. Call identityPlatform:initializeAuth to create the auth config for the project
738
- * (required for new projects PATCH fails with CONFIGURATION_NOT_FOUND without it).
739
- * 2. PATCH /config to enable Email/Password and Anonymous.
740
- * 3. POST defaultSupportedIdpConfigs to enable Google Sign-In.
741
- * Google Sign-In requires an OAuth client_id that Firebase creates when the user
742
- * enables it in the Console — if the client doesn't exist yet this step is skipped
743
- * and googleSignInSkipped=true is returned so the caller can show a targeted hint.
744
- *
745
- * Non-fatal: returns { ok: false, error } without throwing if the API call fails.
737
+ * The endpoint is idempotent: it returns {} both on first init and when auth was
738
+ * already initialized. Non-fatal by contract callers proceed on failure since
739
+ * the config may already exist.
746
740
  *
747
741
  * @param {string} projectId
748
- * @returns {{ ok: boolean, googleSignInSkipped?: boolean, error?: string }}
742
+ * @returns {{ ok: boolean, error?: string }}
749
743
  */
750
- async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 15000 } = {}) {
744
+ async function ensureFirebaseAuthInitialized(projectId, { maxRetries = 3, retryDelayMs = 15000 } = {}) {
751
745
  let token;
752
746
  try {
753
747
  token = await getAccessToken();
754
748
  } catch (_) {
755
749
  return { ok: false, error: 'Could not get access token' };
756
750
  }
757
-
758
- // Step 1: Initialize Firebase Auth for the project.
759
- // New projects have no auth config — PATCH returns CONFIGURATION_NOT_FOUND without this.
760
- // The endpoint is idempotent: it returns {} on success or if already initialized.
761
751
  const initUrl = `https://identitytoolkit.googleapis.com/v2/projects/${projectId}/identityPlatform:initializeAuth`;
762
752
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
763
753
  const initRes = await fetch(initUrl, {
@@ -769,19 +759,45 @@ async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 1
769
759
  },
770
760
  body: JSON.stringify({}),
771
761
  });
772
- if (initRes.ok) break;
773
- await initRes.text(); // consume body to release connection
762
+ if (initRes.ok) return { ok: true };
763
+ const text = await initRes.text(); // consume body to release connection
774
764
  if (attempt < maxRetries && (initRes.status === 404 || initRes.status === 503)) {
775
765
  await sleep(retryDelayMs);
776
766
  try { token = await getAccessToken(); } catch (_) {}
777
767
  continue;
778
768
  }
779
- // Non-fatal still attempt PATCH below in case auth was already initialized.
780
- break;
769
+ return { ok: false, error: `${initRes.status}: ${text}` };
781
770
  }
771
+ return { ok: false, error: 'initializeAuth: exhausted retries' };
772
+ }
773
+
774
+ /**
775
+ * Enable Firebase Auth sign-in providers: Email/Password, Anonymous and Google.
776
+ * Uses the Identity Toolkit Admin v2 REST API with gcloud credentials.
777
+ *
778
+ * Flow:
779
+ * 1. Call identityPlatform:initializeAuth to create the auth config for the project
780
+ * (required for new projects — PATCH fails with CONFIGURATION_NOT_FOUND without it).
781
+ * 2. PATCH /config to enable Email/Password and Anonymous.
782
+ * 3. POST defaultSupportedIdpConfigs to enable Google Sign-In.
783
+ * Google Sign-In requires an OAuth client_id that Firebase creates when the user
784
+ * enables it in the Console — if the client doesn't exist yet this step is skipped
785
+ * and googleSignInSkipped=true is returned so the caller can show a targeted hint.
786
+ *
787
+ * Non-fatal: returns { ok: false, error } without throwing if the API call fails.
788
+ *
789
+ * @param {string} projectId
790
+ * @returns {{ ok: boolean, googleSignInSkipped?: boolean, error?: string }}
791
+ */
792
+ async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 15000 } = {}) {
793
+ // Step 1: Initialize Firebase Auth (non-fatal — the PATCH below still runs in
794
+ // case auth was already initialized). The shared helper keeps the init logic in
795
+ // one place so the Supabase flow can reuse it before its `deploy --only auth`.
796
+ await ensureFirebaseAuthInitialized(projectId, { maxRetries, retryDelayMs });
782
797
 
783
798
  // Step 2: Enable Email/Password and Anonymous auth providers.
784
799
  const configUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config?updateMask=signIn.email,signIn.anonymous`;
800
+ let token;
785
801
  let lastError;
786
802
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
787
803
  try { token = await getAccessToken(); } catch (_) {
@@ -996,27 +1012,34 @@ async function setupFromScratch(appName, bundleId, options = {}) {
996
1012
  const addResult = await addFirebaseToProject(projectId, { onProgress });
997
1013
  if (!addResult.ok) return { ok: false, error: `[addFirebase] ${addResult.error}`, projectId };
998
1014
 
999
- // Enable Email/Password, Anonymous and Google Sign-In auth providers automatically.
1000
- // Non-fatal project setup continues even if this call fails.
1001
- const authResult = await enableAuthProviders(projectId);
1002
- if (!authResult.ok) {
1003
- onProgress('auth-providers-warn', {
1004
- error: authResult.error,
1005
- url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
1006
- });
1007
- } else if (authResult.googleSignInSkipped) {
1008
- // Email/Password and Anonymous were enabled. Google Sign-In needs an OAuth client
1009
- // that Firebase creates when you enable it in the Console for the first time.
1010
- onProgress('auth-google-warn', {
1011
- url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
1012
- });
1013
- }
1014
- // Providers were enabled but localhost couldn't be authorized — warn so the user
1015
- // isn't surprised by [firebase_auth/unauthorized-domain] on web sign-in.
1016
- if (authResult.ok && authResult.localhostAuthorized === false) {
1017
- onProgress('auth-localhost-warn', {
1018
- url: `https://console.firebase.google.com/project/${projectId}/authentication/settings`,
1019
- });
1015
+ // Firebase Auth providers (Email/Password, Anonymous, Google, Apple) only matter
1016
+ // for the Firebase backend, whose app authenticates against Firebase Auth. In
1017
+ // fcmOnly mode the app is Supabase/API and authenticates elsewhere, so we leave
1018
+ // Firebase Auth untouched — this project exists purely for FCM/push (and, for
1019
+ // Supabase, the Google OAuth client, which new.js creates separately via
1020
+ // `firebase deploy --only auth`). Non-fatal — setup continues even if it fails.
1021
+ let authResult = null;
1022
+ if (!fcmOnly) {
1023
+ authResult = await enableAuthProviders(projectId);
1024
+ if (!authResult.ok) {
1025
+ onProgress('auth-providers-warn', {
1026
+ error: authResult.error,
1027
+ url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
1028
+ });
1029
+ } else if (authResult.googleSignInSkipped) {
1030
+ // Email/Password and Anonymous were enabled. Google Sign-In needs an OAuth client
1031
+ // that Firebase creates when you enable it in the Console for the first time.
1032
+ onProgress('auth-google-warn', {
1033
+ url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
1034
+ });
1035
+ }
1036
+ // Providers were enabled but localhost couldn't be authorized — warn so the user
1037
+ // isn't surprised by [firebase_auth/unauthorized-domain] on web sign-in.
1038
+ if (authResult.ok && authResult.localhostAuthorized === false) {
1039
+ onProgress('auth-localhost-warn', {
1040
+ url: `https://console.firebase.google.com/project/${projectId}/authentication/settings`,
1041
+ });
1042
+ }
1020
1043
  }
1021
1044
 
1022
1045
  // Firestore + Storage are Firebase-backend features (and need Blaze). In
@@ -1089,8 +1112,8 @@ async function setupFromScratch(appName, bundleId, options = {}) {
1089
1112
  return {
1090
1113
  ok: true,
1091
1114
  projectId,
1092
- authEnabled: authResult.ok,
1093
- googleSignInSkipped: authResult.ok && !!authResult.googleSignInSkipped,
1115
+ authEnabled: authResult ? authResult.ok : null,
1116
+ googleSignInSkipped: authResult ? (authResult.ok && !!authResult.googleSignInSkipped) : false,
1094
1117
  sha1Skipped,
1095
1118
  sha1Error,
1096
1119
  sha1ManualUrl: `https://console.firebase.google.com/project/${projectId}/settings/general/android:${bundleId}`,
@@ -1238,6 +1261,7 @@ module.exports = {
1238
1261
  applyStorageCors,
1239
1262
  checkBillingEnabled,
1240
1263
  enableAuthProviders,
1264
+ ensureFirebaseAuthInitialized,
1241
1265
  ensureLocalhostAuthorizedDomains,
1242
1266
  listBillingAccounts,
1243
1267
  listGcpOrganizations,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.21.8",
3
+ "version": "1.21.9",
4
4
  "description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
5
5
  "bin": {
6
6
  "kasy": "./bin/kasy.js"