kasy-cli 1.21.1 → 1.21.3

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/bin/kasy.js CHANGED
@@ -436,9 +436,26 @@ function buildProgram(language) {
436
436
  .description(t('cli.command.upgrade.description'))
437
437
  .action(() => {
438
438
  const { spawnSync } = require('node:child_process');
439
+ const path = require('node:path');
439
440
  printCompactHeader(t);
440
441
  console.log(kleur.cyan(t('cli.command.upgrade.running')) + '\n');
441
- const result = spawnSync('npm', ['install', '-g', 'kasy-cli'], { stdio: 'inherit', shell: true });
442
+ // Update INTO the prefix the CLI actually lives in. The installer uses
443
+ // `npm install -g --prefix ~/.kasy`, so a bare `npm install -g` would
444
+ // land in npm's default global prefix and the old copy on PATH would
445
+ // keep winning. Derive the prefix from this file's own location:
446
+ // unix: <prefix>/lib/node_modules/kasy-cli/bin → drop lib + node_modules
447
+ // win: <prefix>/node_modules/kasy-cli/bin → drop node_modules
448
+ const segs = __dirname.split(path.sep);
449
+ const nm = segs.lastIndexOf('node_modules');
450
+ let prefix = null;
451
+ if (nm > 0) {
452
+ let p = segs.slice(0, nm);
453
+ if (p[p.length - 1] === 'lib') p = p.slice(0, -1);
454
+ prefix = p.join(path.sep) || null;
455
+ }
456
+ const args = ['install', '-g', 'kasy-cli@latest'];
457
+ if (prefix) args.push('--prefix', prefix);
458
+ const result = spawnSync('npm', args, { stdio: 'inherit', shell: true });
442
459
  if (result.status === 0) {
443
460
  console.log('\n' + kleur.green('✓ ' + t('cli.command.upgrade.done')) + '\n');
444
461
  } else {
@@ -6,9 +6,10 @@ const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
6
6
  const {
7
7
  getBaseChecks,
8
8
  getPlatformChecks,
9
- getBackendChecks,
10
9
  runChecks,
11
- hasRequiredFailures
10
+ hasRequiredFailures,
11
+ probeTools,
12
+ BACKEND_TOOLS,
12
13
  } = require('../utils/checks');
13
14
  const {
14
15
  validateAppleSetup,
@@ -21,11 +22,35 @@ const { validateGoogleIosUrlScheme, validateAppleSignInEntitlement, validateFace
21
22
  const { listBillingAccounts, checkGcloudAuth } = require('../scaffold/backends/firebase/setup-from-scratch');
22
23
  const { printCompactHeader } = require('../utils/brand');
23
24
 
24
- function collectOptionalBackendChecks() {
25
- return [
26
- ...getBackendChecks('firebase'),
27
- ...getBackendChecks('supabase')
28
- ];
25
+ /**
26
+ * Read-only backend tools status + a per-backend "what you need" view, so
27
+ * `kasy doctor` answers "what do I have, what's missing, and for which backend".
28
+ */
29
+ async function printBackendReadiness(t) {
30
+ const status = await probeTools();
31
+
32
+ ui.log.step(kleur.bold(t('doctor.tools.title')));
33
+ for (const key of ['firebase', 'flutterfire', 'supabase', 'gcloud']) {
34
+ const s = status[key];
35
+ if (s.ok) {
36
+ ui.log.success(`${s.name}${s.version ? ` — ${s.version}` : ''}`);
37
+ } else if (key === 'gcloud') {
38
+ ui.log.message(kleur.dim(`○ ${s.name} — ${t('doctor.gcloud.note')}`));
39
+ } else {
40
+ ui.log.warn(`${s.name} — ${t('checks.notFound.short')}`);
41
+ }
42
+ }
43
+
44
+ ui.log.step(kleur.bold(t('doctor.byBackend.title')));
45
+ const labels = { firebase: '🔥 Firebase', supabase: '🟢 Supabase', api: '🔗 API REST' };
46
+ for (const [backend, req] of Object.entries(BACKEND_TOOLS)) {
47
+ const missing = req.required.filter((k) => !status[k].ok).map((k) => status[k].name);
48
+ if (missing.length === 0) {
49
+ ui.log.success(`${labels[backend]} — ${t('doctor.backend.ready')}`);
50
+ } else {
51
+ ui.log.warn(`${labels[backend]} — ${t('doctor.backend.missing', { tools: missing.join(', ') })}`);
52
+ }
53
+ }
29
54
  }
30
55
 
31
56
  /**
@@ -242,12 +267,7 @@ async function runDoctor(options = {}) {
242
267
  { t, compact: true, spinnerLabel: t('doctor.baseEnvironment'), doneLabel: t('doctor.baseEnvironment') }
243
268
  );
244
269
 
245
- const optionalBackend = collectOptionalBackendChecks();
246
- if (optionalBackend.length > 0) {
247
- await runChecks(optionalBackend, t('doctor.optionalBackend'), {
248
- t, compact: true, spinnerLabel: t('doctor.optionalBackend'), doneLabel: t('doctor.optionalBackend'),
249
- });
250
- }
270
+ await printBackendReadiness(t);
251
271
 
252
272
  // ── Google Cloud billing account (Firebase Blaze) ─────────────────────
253
273
  // Only meaningful when gcloud is authenticated; otherwise users already saw
@@ -92,7 +92,16 @@ const PLATFORM_CHECKS = {
92
92
  linux: []
93
93
  };
94
94
 
95
- /** Firebase CLI + FlutterFire — required for push notifications (FCM) on all backends */
95
+ /**
96
+ * Firebase CLI + FlutterFire — required for push notifications (FCM) on EVERY
97
+ * backend (Firebase, Supabase, API), since FCM is the shared push layer.
98
+ *
99
+ * gcloud is deliberately NOT in this list. It's only needed to create a Firebase
100
+ * project FROM SCRATCH (an opt-in path), so checking it for every backend made
101
+ * the Supabase/API setup offer to install gcloud for no reason — and the user
102
+ * doesn't even need it there. The create-from-scratch flow verifies gcloud on
103
+ * its own (checkGcloudAuth in new.js), exactly when it's actually required.
104
+ */
96
105
  const FIREBASE_CHECKS = [
97
106
  {
98
107
  name: 'Firebase CLI',
@@ -113,22 +122,6 @@ const FIREBASE_CHECKS = [
113
122
  pubGlobalBin: 'flutterfire',
114
123
  tryInstallMessageKey: 'setup.flutterfire.installing',
115
124
  },
116
- {
117
- name: 'gcloud CLI (create-from-scratch)',
118
- command: 'gcloud --version',
119
- required: false,
120
- installGuide: () => getInstallGuide('gcloud'),
121
- confirmInstall: true,
122
- waitPromptKey: 'checks.waitPrompt.gcloud.install',
123
- },
124
- {
125
- name: 'gcloud auth (create-from-scratch)',
126
- command: 'gcloud auth print-access-token',
127
- required: false,
128
- showVersion: false,
129
- failHint: 'gcloud auth login',
130
- waitPromptKey: 'checks.waitPrompt.gcloud.auth',
131
- },
132
125
  ];
133
126
 
134
127
  const BACKEND_CHECKS = {
@@ -413,6 +406,39 @@ function hasRequiredFailures(results) {
413
406
  return results.some((result) => result.required && !result.ok);
414
407
  }
415
408
 
409
+ // Backend tools probed by `kasy doctor`, plus which backend needs which. gcloud
410
+ // is listed only as Firebase-optional (create-from-scratch), matching the rule
411
+ // that Supabase/API never need it. Keep in sync with BACKEND_CHECKS above.
412
+ const PROBE_TOOLS = [
413
+ { key: 'firebase', name: 'Firebase CLI', command: 'firebase --version' },
414
+ { key: 'flutterfire', name: 'FlutterFire CLI', command: 'flutterfire --version', pubGlobalBin: 'flutterfire' },
415
+ { key: 'supabase', name: 'Supabase CLI', command: 'supabase --version' },
416
+ { key: 'gcloud', name: 'gcloud', command: 'gcloud --version' },
417
+ ];
418
+
419
+ const BACKEND_TOOLS = {
420
+ firebase: { required: ['firebase', 'flutterfire'], optional: ['gcloud'] },
421
+ supabase: { required: ['firebase', 'flutterfire', 'supabase'], optional: [] },
422
+ api: { required: ['firebase', 'flutterfire'], optional: [] },
423
+ };
424
+
425
+ /**
426
+ * Read-only probe of the backend tools (never installs anything) — for the
427
+ * doctor's "what do I have / what's missing" view. Runs in parallel.
428
+ * @returns {Promise<Object<string, {name, ok, version}>>}
429
+ */
430
+ async function probeTools(tools = PROBE_TOOLS) {
431
+ const entries = await Promise.all(tools.map(async (tool) => {
432
+ const res = await verifyTool(tool);
433
+ return [tool.key, {
434
+ name: tool.name,
435
+ ok: res.ok,
436
+ version: res.ok ? extractVersion(res.stdout, tool.name) : null,
437
+ }];
438
+ }));
439
+ return Object.fromEntries(entries);
440
+ }
441
+
416
442
  module.exports = {
417
443
  getBaseChecks,
418
444
  getPlatformChecks,
@@ -420,4 +446,6 @@ module.exports = {
420
446
  getInstallGuide,
421
447
  runChecks,
422
448
  hasRequiredFailures,
449
+ probeTools,
450
+ BACKEND_TOOLS,
423
451
  };
@@ -80,6 +80,11 @@ module.exports = {
80
80
  'doctor.title': 'Kasy Doctor',
81
81
  'doctor.baseEnvironment': 'Base environment',
82
82
  'doctor.optionalBackend': 'Optional backend tooling',
83
+ 'doctor.tools.title': 'Backend tools',
84
+ 'doctor.byBackend.title': 'What each backend needs',
85
+ 'doctor.backend.ready': 'all set',
86
+ 'doctor.backend.missing': 'still missing: {tools}',
87
+ 'doctor.gcloud.note': 'optional, only to create a Firebase project from scratch',
83
88
  'doctor.gcpBilling.title': 'Google Cloud Billing (Firebase Blaze)',
84
89
  'doctor.gcpBilling.found': '{count} active billing account(s):',
85
90
  'doctor.gcpBilling.missing': 'No billing account found. Create one before running `kasy new` with Firebase:',
@@ -80,6 +80,11 @@ module.exports = {
80
80
  'doctor.title': 'Kasy Doctor',
81
81
  'doctor.baseEnvironment': 'Entorno base',
82
82
  'doctor.optionalBackend': 'Herramientas opcionales de backend',
83
+ 'doctor.tools.title': 'Herramientas de backend',
84
+ 'doctor.byBackend.title': 'Qué necesita cada backend',
85
+ 'doctor.backend.ready': 'todo listo',
86
+ 'doctor.backend.missing': 'falta instalar: {tools}',
87
+ 'doctor.gcloud.note': 'opcional, solo para crear un proyecto Firebase desde cero',
83
88
  'doctor.gcpBilling.title': 'Google Cloud Billing (Firebase Blaze)',
84
89
  'doctor.gcpBilling.found': '{count} cuenta(s) de facturación activa(s):',
85
90
  'doctor.gcpBilling.missing': 'No se encontró ninguna cuenta de facturación. Crea una antes de ejecutar `kasy new` con Firebase:',
@@ -80,6 +80,11 @@ module.exports = {
80
80
  'doctor.title': 'Kasy Doctor',
81
81
  'doctor.baseEnvironment': 'Ambiente base',
82
82
  'doctor.optionalBackend': 'Ferramentas opcionais de backend',
83
+ 'doctor.tools.title': 'Ferramentas de backend',
84
+ 'doctor.byBackend.title': 'O que cada backend precisa',
85
+ 'doctor.backend.ready': 'tudo pronto',
86
+ 'doctor.backend.missing': 'falta instalar: {tools}',
87
+ 'doctor.gcloud.note': 'opcional, só para criar um projeto Firebase do zero',
83
88
  'doctor.gcpBilling.title': 'Google Cloud Billing (Firebase Blaze)',
84
89
  'doctor.gcpBilling.found': '{count} conta(s) de faturamento ativa(s):',
85
90
  'doctor.gcpBilling.missing': 'Nenhuma conta de faturamento encontrada. Crie uma antes de rodar `kasy new` com Firebase:',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.21.1",
3
+ "version": "1.21.3",
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"