kasy-cli 1.21.2 → 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
@@ -406,6 +406,39 @@ function hasRequiredFailures(results) {
406
406
  return results.some((result) => result.required && !result.ok);
407
407
  }
408
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
+
409
442
  module.exports = {
410
443
  getBaseChecks,
411
444
  getPlatformChecks,
@@ -413,4 +446,6 @@ module.exports = {
413
446
  getInstallGuide,
414
447
  runChecks,
415
448
  hasRequiredFailures,
449
+ probeTools,
450
+ BACKEND_TOOLS,
416
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.2",
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"