kasy-cli 1.21.5 → 1.21.7

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, getGcloudInstallInstructions, enableAuthProviders, registerDebugSha1 } = require('../scaffold/backends/firebase/setup-from-scratch');
47
+ const { setupFromScratch, setupExistingProject, listBillingAccounts, listGcpOrganizations, checkGcloudAuth, getFirebaseAccount, getGcloudInstallInstructions, enableAuthProviders, 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
 
@@ -123,6 +123,78 @@ async function promptOrganizationIfNeeded(tr, onCancel) {
123
123
 
124
124
  // ── Helpers ───────────────────────────────────────────────────────────────────
125
125
 
126
+ /**
127
+ * Before creating any cloud project, show WHICH account each service is logged
128
+ * in as (gcloud, Firebase, and Supabase when relevant), so the user never
129
+ * creates the project on the wrong account. Warns if gcloud and Firebase are
130
+ * different accounts (a common cause of mid-flow failures), and lets the user
131
+ * bail out with the exact commands to switch.
132
+ */
133
+ async function confirmIdentities(backend, gcloudAccount, tr, cancel) {
134
+ let firebaseAccount = await getFirebaseAccount();
135
+ let supabaseLoggedIn = backend === 'supabase' ? (await checkLoggedIn()).ok : true;
136
+
137
+ const showStatus = () => {
138
+ const notLogged = kleur.yellow(tr('new.accounts.notLoggedIn'));
139
+ const lines = [
140
+ `🔑 Google Cloud: ${gcloudAccount ? kleur.cyan(gcloudAccount) : notLogged}`,
141
+ `🔥 Firebase: ${firebaseAccount ? kleur.cyan(firebaseAccount) : notLogged}`,
142
+ ];
143
+ if (backend === 'supabase') {
144
+ lines.push(`🟢 Supabase: ${supabaseLoggedIn ? kleur.cyan(tr('new.accounts.connected')) : notLogged}`);
145
+ }
146
+ ui.note(lines.join('\n'), tr('new.accounts.title'));
147
+ };
148
+ showStatus();
149
+
150
+ // For any required service that's NOT logged in, offer to log in right here:
151
+ // Enter runs the service's own login command (which opens the browser / asks
152
+ // for the token), then we re-check. gcloud was already verified above.
153
+ const offerLogin = async (name, cmd, revalidate) => {
154
+ const doLogin = await ui.confirm({ message: tr('new.accounts.loginPrompt', { name }), initialValue: true, onCancel: cancel });
155
+ if (!doLogin) return;
156
+ ui.log.message(tr('new.accounts.loggingIn', { name }));
157
+ const { spawnSync } = require('node:child_process');
158
+ spawnSync(cmd, { stdio: 'inherit', shell: true });
159
+ await revalidate();
160
+ };
161
+
162
+ if (!firebaseAccount) {
163
+ await offerLogin('Firebase', 'firebase login', async () => { firebaseAccount = await getFirebaseAccount(); });
164
+ }
165
+ if (backend === 'supabase' && !supabaseLoggedIn) {
166
+ await offerLogin('Supabase', 'supabase login', async () => { supabaseLoggedIn = (await checkLoggedIn()).ok; });
167
+ }
168
+
169
+ // Still missing a required login (user declined or it failed)? Stop with the
170
+ // exact command so they can do it and re-run.
171
+ const stillMissing = [];
172
+ if (!firebaseAccount) stillMissing.push(`🔥 Firebase: ${kleur.cyan('firebase login')}`);
173
+ if (backend === 'supabase' && !supabaseLoggedIn) stillMissing.push(`🟢 Supabase: ${kleur.cyan('supabase login')}`);
174
+ if (stillMissing.length > 0) {
175
+ ui.log.error(tr('new.accounts.needLogin'));
176
+ ui.note(stillMissing.join('\n'), tr('new.accounts.loginTitle'));
177
+ ui.cancel(tr('new.accounts.rerun'));
178
+ process.exit(0);
179
+ }
180
+
181
+ if (gcloudAccount && firebaseAccount && gcloudAccount !== firebaseAccount) {
182
+ ui.log.warn(`⚠ ${tr('new.accounts.mismatch')}`);
183
+ }
184
+
185
+ const ok = await ui.confirm({ message: tr('new.accounts.confirm'), initialValue: true, onCancel: cancel });
186
+ if (!ok) {
187
+ const sw = [
188
+ `🔑 gcloud: ${kleur.cyan('gcloud auth login')}`,
189
+ `🔥 Firebase: ${kleur.cyan('firebase login --reauth')}`,
190
+ ];
191
+ if (backend === 'supabase') sw.push(`🟢 Supabase: ${kleur.cyan('supabase login')}`);
192
+ ui.note(sw.join('\n'), tr('new.accounts.switchTitle'));
193
+ ui.cancel(tr('new.accounts.rerun'));
194
+ process.exit(0);
195
+ }
196
+ }
197
+
126
198
  function printPrerequisites(tr, backend, firebaseSetupMode = 'existing', checkResults = []) {
127
199
  const gcloudOk = checkResults.every(
128
200
  (r) => !r.name?.includes('gcloud') || r.ok
@@ -580,6 +652,10 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
580
652
  process.exit(1);
581
653
  }
582
654
 
655
+ // Show which account each service is logged in as before creating anything,
656
+ // so the project never lands on the wrong account.
657
+ await confirmIdentities(backend, gcloudCheck.account, tr, cancel);
658
+
583
659
  // ── Billing account check — Firebase needs an active billing account (Blaze) ─
584
660
  // Without it, project creation succeeds but Storage and Cloud Functions fail later.
585
661
  // Catching it here saves the user from a confusing mid-flow error.
@@ -108,7 +108,16 @@ async function checkGcloudAuth() {
108
108
  const auth = await run('gcloud auth print-access-token');
109
109
  if (!auth.ok) return { ok: false, error: 'Not logged in. Run: gcloud auth login', missing: 'auth' };
110
110
 
111
- return { ok: true };
111
+ const acct = await run('gcloud config get-value account');
112
+ return { ok: true, account: acct.ok ? acct.stdout.trim() : null };
113
+ }
114
+
115
+ /** The Google account the Firebase CLI is logged in as, or null. */
116
+ async function getFirebaseAccount() {
117
+ const r = await run('firebase login:list');
118
+ if (!r.ok) return null;
119
+ const m = (r.stdout || '').match(/Logged in as ([^\s@]+@[^\s]+)/);
120
+ return m ? m[1] : null;
112
121
  }
113
122
 
114
123
  /**
@@ -1232,6 +1241,7 @@ module.exports = {
1232
1241
  listBillingAccounts,
1233
1242
  listGcpOrganizations,
1234
1243
  checkGcloudAuth,
1244
+ getFirebaseAccount,
1235
1245
  getGcloudInstallInstructions,
1236
1246
  generateProjectId,
1237
1247
  extractSha1,
@@ -101,11 +101,21 @@ async function createFcmServiceAccountKey(projectId) {
101
101
 
102
102
  const tmpPath = path.join(os.tmpdir(), `kasy-fcm-${Date.now()}.json`);
103
103
 
104
- const keyResult = await run(
105
- `gcloud iam service-accounts keys create "${tmpPath}"` +
106
- ` --iam-account="${saEmail}"` +
107
- ` --project=${projectId.trim()}`
108
- );
104
+ // Right after granting the FCM admin role (and on a brand-new project), the
105
+ // IAM permission takes a moment to propagate — so the first key-create often
106
+ // fails with "permission still propagating". Back off and retry a few times
107
+ // before giving up, instead of leaving push half-configured.
108
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
109
+ let keyResult;
110
+ for (let attempt = 1; attempt <= 4; attempt++) {
111
+ keyResult = await run(
112
+ `gcloud iam service-accounts keys create "${tmpPath}"` +
113
+ ` --iam-account="${saEmail}"` +
114
+ ` --project=${projectId.trim()}`
115
+ );
116
+ if (keyResult.ok) break;
117
+ if (attempt < 4) await sleep(7000);
118
+ }
109
119
 
110
120
  if (!keyResult.ok) {
111
121
  await fs.remove(tmpPath).catch(() => {});
@@ -216,6 +216,17 @@ module.exports = {
216
216
  'new.firebase.create.success': 'Firebase project created successfully.',
217
217
  'new.firebase.create.failed': 'Could not create project',
218
218
  'new.firebase.create.gcloudRequired': 'gcloud CLI is required for "create from scratch". Without it, the full Firebase flow cannot run.',
219
+ 'new.accounts.title': 'Connected accounts (where the project will be created)',
220
+ 'new.accounts.notLoggedIn': 'not connected',
221
+ 'new.accounts.connected': 'connected',
222
+ 'new.accounts.mismatch': 'gcloud and Firebase are on different accounts: this can fail while creating the project.',
223
+ 'new.accounts.confirm': 'Create the project on these accounts?',
224
+ 'new.accounts.switchTitle': 'To switch accounts, run and try again:',
225
+ 'new.accounts.rerun': 'Switch the account and run `kasy new` again.',
226
+ 'new.accounts.needLogin': 'You need to be logged in to continue.',
227
+ 'new.accounts.loginTitle': 'Log in and run `kasy new` again:',
228
+ 'new.accounts.loginPrompt': 'Log in to {name} now?',
229
+ 'new.accounts.loggingIn': 'Opening {name} login…',
219
230
  'new.firebase.billing.required': 'You do not have a billing account on Google Cloud yet. Firebase needs the Blaze plan to use Storage and Cloud Functions.',
220
231
  'new.firebase.billing.create.steps': 'Opening the billing account creation page. Create the account (credit card required, no charges within the free quota) and come back here:',
221
232
  'new.firebase.billing.created.ready': 'I created the billing account, ready to continue?',
@@ -216,6 +216,17 @@ module.exports = {
216
216
  'new.firebase.create.success': 'Proyecto Firebase creado correctamente.',
217
217
  'new.firebase.create.failed': 'No se pudo crear el proyecto',
218
218
  'new.firebase.create.gcloudRequired': 'gcloud CLI es obligatorio para "crear desde cero". Sin él, el flujo completo de Firebase no puede ejecutarse.',
219
+ 'new.accounts.title': 'Cuentas conectadas (donde se creará el proyecto)',
220
+ 'new.accounts.notLoggedIn': 'no conectado',
221
+ 'new.accounts.connected': 'conectado',
222
+ 'new.accounts.mismatch': 'gcloud y Firebase están en cuentas distintas: esto puede fallar al crear el proyecto.',
223
+ 'new.accounts.confirm': '¿Crear el proyecto en estas cuentas?',
224
+ 'new.accounts.switchTitle': 'Para cambiar de cuenta, ejecuta y prueba de nuevo:',
225
+ 'new.accounts.rerun': 'Cambia la cuenta y ejecuta `kasy new` de nuevo.',
226
+ 'new.accounts.needLogin': 'Necesitas estar conectado para continuar.',
227
+ 'new.accounts.loginTitle': 'Inicia sesión y ejecuta `kasy new` de nuevo:',
228
+ 'new.accounts.loginPrompt': '¿Iniciar sesión en {name} ahora?',
229
+ 'new.accounts.loggingIn': 'Abriendo el inicio de sesión de {name}…',
219
230
  'new.firebase.billing.required': 'Aún no tienes una cuenta de facturación en Google Cloud. Firebase necesita el plan Blaze para usar Storage y Cloud Functions.',
220
231
  'new.firebase.billing.create.steps': 'Abriendo la página de creación de cuenta de facturación. Crea la cuenta (tarjeta de crédito requerida, sin cargos dentro de la cuota gratuita) y vuelve aquí:',
221
232
  'new.firebase.billing.created.ready': 'Ya creé la cuenta de facturación, ¿puedo continuar?',
@@ -216,6 +216,17 @@ module.exports = {
216
216
  'new.firebase.create.success': 'Projeto Firebase criado com sucesso.',
217
217
  'new.firebase.create.failed': 'Não foi possível criar o projeto',
218
218
  'new.firebase.create.gcloudRequired': 'gcloud CLI é obrigatório para "criar do zero". Sem ele, o fluxo completo do Firebase não pode rodar.',
219
+ 'new.accounts.title': 'Contas conectadas (onde o projeto será criado)',
220
+ 'new.accounts.notLoggedIn': 'não conectado',
221
+ 'new.accounts.connected': 'conectado',
222
+ 'new.accounts.mismatch': 'gcloud e Firebase estão em contas diferentes: isso pode falhar ao criar o projeto.',
223
+ 'new.accounts.confirm': 'É nessas contas que você quer criar o projeto?',
224
+ 'new.accounts.switchTitle': 'Para trocar de conta, rode e tente de novo:',
225
+ 'new.accounts.rerun': 'Troque a conta e rode `kasy new` novamente.',
226
+ 'new.accounts.needLogin': 'Você precisa estar logado para continuar.',
227
+ 'new.accounts.loginTitle': 'Faça login e rode `kasy new` de novo:',
228
+ 'new.accounts.loginPrompt': 'Fazer login no {name} agora?',
229
+ 'new.accounts.loggingIn': 'Abrindo login do {name}…',
219
230
  'new.firebase.billing.required': 'Você ainda não tem uma conta de faturamento no Google Cloud. O Firebase precisa do plano Blaze para usar Storage e Cloud Functions.',
220
231
  'new.firebase.billing.create.steps': 'Vou abrir a página de criação de conta de faturamento. Crie a conta (cartão de crédito, sem cobrança até bater a cota gratuita) e volte aqui:',
221
232
  'new.firebase.billing.created.ready': 'Já criei a conta de faturamento, pode seguir?',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.21.5",
3
+ "version": "1.21.7",
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"