kasy-cli 1.5.1 → 1.5.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/lib/commands/new.js +160 -161
- package/lib/commands/run.js +2 -1
- package/lib/utils/checks.js +22 -4
- package/lib/utils/i18n.js +30 -0
- package/lib/utils/ui.js +82 -0
- package/lib/utils/updates.js +38 -1
- package/package.json +2 -1
package/lib/commands/new.js
CHANGED
|
@@ -69,10 +69,12 @@ function openUrl(url) {
|
|
|
69
69
|
} catch (_) {}
|
|
70
70
|
}
|
|
71
71
|
const prompts = require('prompts');
|
|
72
|
+
const ui = require('../utils/ui');
|
|
72
73
|
const fs = require('fs-extra');
|
|
73
74
|
const { createTranslator } = require('../utils/i18n');
|
|
74
75
|
const { getStoredLanguage, setStoredLanguage } = require('../utils/license');
|
|
75
76
|
const { promptLanguage } = require('../utils/prompts');
|
|
77
|
+
const { warnIfOutdatedBeforeNew } = require('../utils/updates');
|
|
76
78
|
const {
|
|
77
79
|
getBaseChecks,
|
|
78
80
|
getPlatformChecks,
|
|
@@ -517,6 +519,11 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
517
519
|
const tr = createTranslator(language);
|
|
518
520
|
const cancel = () => onCancel(tr);
|
|
519
521
|
|
|
522
|
+
// ── 1b. Version check — warn if outdated, let user decide ───────────────
|
|
523
|
+
if (!yes) {
|
|
524
|
+
await warnIfOutdatedBeforeNew(tr);
|
|
525
|
+
}
|
|
526
|
+
|
|
520
527
|
// ── 2. Environment checks (non-blocking — only warnings) ────────────────
|
|
521
528
|
const envChecks = [...getBaseChecks(), ...getPlatformChecks()];
|
|
522
529
|
await runChecks(envChecks, tr('new.checks.environment'), {
|
|
@@ -528,6 +535,8 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
528
535
|
|
|
529
536
|
printBanner(tr);
|
|
530
537
|
|
|
538
|
+
ui.intro(kleur.bold(tr('new.subtitle2')));
|
|
539
|
+
|
|
531
540
|
// Whether an explicit target directory was provided by the user
|
|
532
541
|
const hasExplicitDir = directory && directory !== '.';
|
|
533
542
|
|
|
@@ -547,21 +556,16 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
547
556
|
// ── 3. Backend selection (support pre-selection via --backend flag) ─────
|
|
548
557
|
let backend = normalizeBackend(backendHint);
|
|
549
558
|
if (!backend) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
initial: 0,
|
|
561
|
-
},
|
|
562
|
-
{ onCancel: cancel }
|
|
563
|
-
);
|
|
564
|
-
backend = selectedBackend;
|
|
559
|
+
backend = await ui.select({
|
|
560
|
+
message: tr('new.q.backend'),
|
|
561
|
+
initialValue: 'firebase',
|
|
562
|
+
options: [
|
|
563
|
+
{ value: 'firebase', label: '🔥 Firebase', hint: tr('new.q.backend.firebase.desc') },
|
|
564
|
+
{ value: 'supabase', label: '🟢 Supabase', hint: tr('new.q.backend.supabase.desc') },
|
|
565
|
+
{ value: 'api', label: '🔗 API REST', hint: tr('new.q.backend.api.desc') },
|
|
566
|
+
],
|
|
567
|
+
onCancel: cancel,
|
|
568
|
+
});
|
|
565
569
|
} else {
|
|
566
570
|
const backendLabel = { firebase: '🔥 Firebase', supabase: '🟢 Supabase', api: '🔗 API REST' }[backend] || backend;
|
|
567
571
|
console.log(kleur.gray(` Backend: ${kleur.white(backendLabel)}`));
|
|
@@ -595,65 +599,160 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
595
599
|
}
|
|
596
600
|
}
|
|
597
601
|
|
|
598
|
-
// ──
|
|
599
|
-
let
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
602
|
+
// ── App identity: name + bundle ID — first things first ────────────────────
|
|
603
|
+
let core;
|
|
604
|
+
if (yes) {
|
|
605
|
+
if (!hasExplicitDir) {
|
|
606
|
+
console.error(kleur.red(`\n ✗ --yes requires an app name: kasy new MyApp --yes\n`));
|
|
607
|
+
process.exit(1);
|
|
608
|
+
}
|
|
609
|
+
const appName = path.basename(targetDir);
|
|
610
|
+
const slug = appName
|
|
611
|
+
.normalize('NFD')
|
|
612
|
+
.replace(/[̀-ͯ]/g, '')
|
|
613
|
+
.toLowerCase()
|
|
614
|
+
.replace(/[^a-z0-9]/g, '');
|
|
615
|
+
const bundleId = (slug && !/^\d/.test(slug)) ? `com.${slug}.app` : 'com.example.app';
|
|
616
|
+
core = { appName, bundleId };
|
|
617
|
+
console.log(kleur.gray(` App: ${kleur.white(appName)}`));
|
|
618
|
+
console.log(kleur.gray(` Bundle: ${kleur.white(bundleId)}`));
|
|
619
|
+
} else {
|
|
620
|
+
const appName = await ui.text({
|
|
621
|
+
message: tr('new.firebase.q.appName'),
|
|
622
|
+
placeholder: tr('new.firebase.q.appName.hint'),
|
|
623
|
+
initialValue: hasExplicitDir ? path.basename(targetDir) : '',
|
|
624
|
+
validate: (v) => (v && v.trim() ? undefined : tr('new.firebase.q.appName.required')),
|
|
625
|
+
onCancel: cancel,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const defaultBundleId = (() => {
|
|
629
|
+
const name = (appName || '').trim();
|
|
630
|
+
if (!name) return 'com.example.app';
|
|
631
|
+
const slug = name
|
|
632
|
+
.normalize('NFD')
|
|
633
|
+
.replace(/[̀-ͯ]/g, '')
|
|
634
|
+
.toLowerCase()
|
|
635
|
+
.replace(/[^a-z0-9]/g, '');
|
|
636
|
+
if (!slug || /^\d/.test(slug)) return 'com.example.app';
|
|
637
|
+
return `com.${slug}.app`;
|
|
638
|
+
})();
|
|
639
|
+
|
|
640
|
+
const bundleId = await ui.text({
|
|
641
|
+
message: tr('new.firebase.q.bundleId'),
|
|
642
|
+
placeholder: tr('new.firebase.q.bundleId.hint'),
|
|
643
|
+
initialValue: defaultBundleId,
|
|
644
|
+
validate: (v) => {
|
|
645
|
+
if (!v || !v.trim()) return tr('new.firebase.q.bundleId.required');
|
|
646
|
+
return /^[a-zA-Z][\w]*(\.[a-zA-Z][\w]*)+$/.test(v.trim())
|
|
647
|
+
? undefined
|
|
648
|
+
: tr('new.firebase.q.bundleId.invalid');
|
|
611
649
|
},
|
|
612
|
-
|
|
613
|
-
);
|
|
614
|
-
|
|
650
|
+
onCancel: cancel,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
core = { appName, bundleId };
|
|
615
654
|
}
|
|
616
655
|
|
|
617
|
-
//
|
|
656
|
+
// Resolve targetDir now that we have the app name
|
|
657
|
+
if (!targetDir) {
|
|
658
|
+
const folderName = toPackageName(core.appName.trim());
|
|
659
|
+
targetDir = path.resolve(process.cwd(), folderName);
|
|
660
|
+
if (await fs.pathExists(targetDir)) {
|
|
661
|
+
const contents = await fs.readdir(targetDir);
|
|
662
|
+
if (contents.length > 0) {
|
|
663
|
+
throw new Error(tr('new.firebase.error.dirNotEmpty', { path: targetDir }));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ── Firebase setup mode (create vs existing) ────────────────────────────────
|
|
618
669
|
// Firebase backend: full setup. Supabase/API: Firebase only for push notifications (FCM).
|
|
619
670
|
let firebaseSetupMode = 'existing';
|
|
620
671
|
if (!yes) {
|
|
621
672
|
if (backend === 'firebase') {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
initial: 0,
|
|
632
|
-
},
|
|
633
|
-
{ onCancel: cancel }
|
|
634
|
-
);
|
|
635
|
-
firebaseSetupMode = setupMode;
|
|
673
|
+
firebaseSetupMode = await ui.select({
|
|
674
|
+
message: tr('new.firebase.q.setupMode'),
|
|
675
|
+
initialValue: 'create',
|
|
676
|
+
options: [
|
|
677
|
+
{ value: 'create', label: tr('new.firebase.q.setupMode.create') },
|
|
678
|
+
{ value: 'existing', label: tr('new.firebase.q.setupMode.existing') },
|
|
679
|
+
],
|
|
680
|
+
onCancel: cancel,
|
|
681
|
+
});
|
|
636
682
|
} else if (backend === 'supabase' || backend === 'api') {
|
|
637
|
-
|
|
683
|
+
ui.note(tr('new.firebase.q.setupMode.push.explain'));
|
|
684
|
+
firebaseSetupMode = await ui.select({
|
|
685
|
+
message: tr('new.firebase.q.setupMode.push'),
|
|
686
|
+
initialValue: 'create',
|
|
687
|
+
options: [
|
|
688
|
+
{ value: 'create', label: tr('new.firebase.q.setupMode.create') },
|
|
689
|
+
{ value: 'existing', label: tr('new.firebase.q.setupMode.existing') },
|
|
690
|
+
],
|
|
691
|
+
onCancel: cancel,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ── Wizard mode — Quick (few questions) or Full (all options) ───────────────
|
|
697
|
+
// Asked after app name + setup context are clear, so the user understands what it controls.
|
|
698
|
+
let isQuick = yes; // --yes implies Quick mode
|
|
699
|
+
if (!yes) {
|
|
700
|
+
const wizardMode = await ui.select({
|
|
701
|
+
message: tr('new.q.mode'),
|
|
702
|
+
initialValue: 'quick',
|
|
703
|
+
options: [
|
|
704
|
+
{ value: 'quick', label: tr('new.q.mode.quick') },
|
|
705
|
+
{ value: 'advanced', label: tr('new.q.mode.advanced') },
|
|
706
|
+
],
|
|
707
|
+
onCancel: cancel,
|
|
708
|
+
});
|
|
709
|
+
isQuick = wizardMode === 'quick';
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ── Backend-specific prerequisites (dynamic: only show what's not yet verified) ─
|
|
713
|
+
printPrerequisites(tr, backend, firebaseSetupMode, backendCheckResults);
|
|
714
|
+
|
|
715
|
+
// ── Firebase project ID (if using an existing project) ──────────────────────
|
|
716
|
+
if (!yes) {
|
|
717
|
+
const needFirebaseProjectId =
|
|
718
|
+
(backend === 'firebase' && firebaseSetupMode === 'existing') ||
|
|
719
|
+
((backend === 'supabase' || backend === 'api') && firebaseSetupMode === 'existing');
|
|
720
|
+
if (needFirebaseProjectId) {
|
|
721
|
+
const firebaseProjectId = await ui.text({
|
|
722
|
+
message: tr('new.firebase.q.projectId'),
|
|
723
|
+
placeholder: tr('new.firebase.q.projectId.hint') + (backend !== 'firebase' ? ' (FCM + Remote Config)' : ''),
|
|
724
|
+
validate: (v) => {
|
|
725
|
+
if (!v || !v.trim()) return tr('new.firebase.q.projectId.required');
|
|
726
|
+
const id = v.trim();
|
|
727
|
+
if (id.length < 6 || id.length > 30) return 'ID deve ter entre 6 e 30 caracteres (ex: meu-app-123)';
|
|
728
|
+
if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(id)) return 'ID inválido: use letras minúsculas, números e hífens, começando com letra (ex: meu-app-123)';
|
|
729
|
+
return undefined;
|
|
730
|
+
},
|
|
731
|
+
onCancel: cancel,
|
|
732
|
+
});
|
|
733
|
+
core.firebaseProjectId = firebaseProjectId;
|
|
734
|
+
}
|
|
735
|
+
} else {
|
|
736
|
+
let firebaseProjectId = projectHint?.trim() || '';
|
|
737
|
+
if (!firebaseProjectId && backend === 'firebase') {
|
|
738
|
+
const { pid } = await prompts(
|
|
638
739
|
{
|
|
639
|
-
type: '
|
|
640
|
-
name: '
|
|
641
|
-
message: tr('new.firebase.q.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
{ title: tr('new.firebase.q.setupMode.existing'), value: 'existing' },
|
|
645
|
-
],
|
|
646
|
-
initial: 0,
|
|
740
|
+
type: 'text',
|
|
741
|
+
name: 'pid',
|
|
742
|
+
message: tr('new.firebase.q.projectId'),
|
|
743
|
+
hint: tr('new.firebase.q.projectId.hint'),
|
|
744
|
+
validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
|
|
647
745
|
},
|
|
648
746
|
{ onCancel: cancel }
|
|
649
747
|
);
|
|
650
|
-
|
|
748
|
+
firebaseProjectId = pid?.trim() || '';
|
|
749
|
+
}
|
|
750
|
+
if (firebaseProjectId) {
|
|
751
|
+
core.firebaseProjectId = firebaseProjectId;
|
|
752
|
+
console.log(kleur.gray(` Project: ${kleur.white(firebaseProjectId)}`));
|
|
651
753
|
}
|
|
652
754
|
}
|
|
653
755
|
|
|
654
|
-
// ── 4c. Backend-specific prerequisites (dynamic: only show what's not yet verified) ─
|
|
655
|
-
printPrerequisites(tr, backend, firebaseSetupMode, backendCheckResults);
|
|
656
|
-
|
|
657
756
|
// ── Firebase region — Quick mode uses default (us-central1) ──────────
|
|
658
757
|
let firebaseRegion = 'us-central1';
|
|
659
758
|
if (backend === 'firebase' && !isQuick) {
|
|
@@ -674,93 +773,6 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
674
773
|
firebaseRegion = region || 'us-central1';
|
|
675
774
|
}
|
|
676
775
|
|
|
677
|
-
// ── Core questions (appName, bundleId) ────────────────────────────────────
|
|
678
|
-
const coreQuestions = [
|
|
679
|
-
{
|
|
680
|
-
type: 'text',
|
|
681
|
-
name: 'appName',
|
|
682
|
-
message: tr('new.firebase.q.appName'),
|
|
683
|
-
hint: tr('new.firebase.q.appName.hint'),
|
|
684
|
-
initial: hasExplicitDir ? path.basename(targetDir) : '',
|
|
685
|
-
validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.appName.required')),
|
|
686
|
-
},
|
|
687
|
-
{
|
|
688
|
-
type: 'text',
|
|
689
|
-
name: 'bundleId',
|
|
690
|
-
message: tr('new.firebase.q.bundleId'),
|
|
691
|
-
hint: tr('new.firebase.q.bundleId.hint'),
|
|
692
|
-
initial: (prev, values) => {
|
|
693
|
-
const name = (values?.appName || prev || '').trim();
|
|
694
|
-
if (!name) return 'com.example.app';
|
|
695
|
-
const slug = name
|
|
696
|
-
.normalize('NFD')
|
|
697
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
698
|
-
.toLowerCase()
|
|
699
|
-
.replace(/[^a-z0-9]/g, '');
|
|
700
|
-
if (!slug || /^\d/.test(slug)) return 'com.example.app';
|
|
701
|
-
return `com.${slug}.app`;
|
|
702
|
-
},
|
|
703
|
-
validate: (v) => {
|
|
704
|
-
if (!v || !v.trim()) return tr('new.firebase.q.bundleId.required');
|
|
705
|
-
return /^[a-zA-Z][\w]*(\.[a-zA-Z][\w]*)+$/.test(v.trim())
|
|
706
|
-
? true
|
|
707
|
-
: tr('new.firebase.q.bundleId.invalid');
|
|
708
|
-
},
|
|
709
|
-
},
|
|
710
|
-
];
|
|
711
|
-
const needFirebaseProjectIdNow =
|
|
712
|
-
(backend === 'firebase' && firebaseSetupMode === 'existing') ||
|
|
713
|
-
((backend === 'supabase' || backend === 'api') && firebaseSetupMode === 'existing');
|
|
714
|
-
if (needFirebaseProjectIdNow) {
|
|
715
|
-
coreQuestions.push({
|
|
716
|
-
type: 'text',
|
|
717
|
-
name: 'firebaseProjectId',
|
|
718
|
-
message: tr('new.firebase.q.projectId'),
|
|
719
|
-
hint: tr('new.firebase.q.projectId.hint') + (backend !== 'firebase' ? ' (FCM + Remote Config)' : ''),
|
|
720
|
-
validate: (v) => {
|
|
721
|
-
if (!v || !v.trim()) return tr('new.firebase.q.projectId.required');
|
|
722
|
-
const id = v.trim();
|
|
723
|
-
if (id.length < 6 || id.length > 30) return 'ID deve ter entre 6 e 30 caracteres (ex: meu-app-123)';
|
|
724
|
-
if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(id)) return 'ID inválido: use letras minúsculas, números e hífens, começando com letra (ex: meu-app-123)';
|
|
725
|
-
return true;
|
|
726
|
-
},
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
let core;
|
|
730
|
-
if (yes) {
|
|
731
|
-
if (!hasExplicitDir) {
|
|
732
|
-
console.error(kleur.red(`\n ✗ --yes requires an app name: kasy new MyApp --yes\n`));
|
|
733
|
-
process.exit(1);
|
|
734
|
-
}
|
|
735
|
-
const appName = path.basename(targetDir);
|
|
736
|
-
const slug = appName
|
|
737
|
-
.normalize('NFD')
|
|
738
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
739
|
-
.toLowerCase()
|
|
740
|
-
.replace(/[^a-z0-9]/g, '');
|
|
741
|
-
const bundleId = (slug && !/^\d/.test(slug)) ? `com.${slug}.app` : 'com.example.app';
|
|
742
|
-
let firebaseProjectId = projectHint?.trim() || '';
|
|
743
|
-
if (!firebaseProjectId && backend === 'firebase') {
|
|
744
|
-
const { pid } = await prompts(
|
|
745
|
-
{
|
|
746
|
-
type: 'text',
|
|
747
|
-
name: 'pid',
|
|
748
|
-
message: tr('new.firebase.q.projectId'),
|
|
749
|
-
hint: tr('new.firebase.q.projectId.hint'),
|
|
750
|
-
validate: (v) => (v && v.trim() ? true : tr('new.firebase.q.projectId.required')),
|
|
751
|
-
},
|
|
752
|
-
{ onCancel: cancel }
|
|
753
|
-
);
|
|
754
|
-
firebaseProjectId = pid?.trim() || '';
|
|
755
|
-
}
|
|
756
|
-
core = { appName, bundleId, firebaseProjectId };
|
|
757
|
-
console.log(kleur.gray(` App: ${kleur.white(appName)}`));
|
|
758
|
-
console.log(kleur.gray(` Bundle: ${kleur.white(bundleId)}`));
|
|
759
|
-
if (firebaseProjectId) console.log(kleur.gray(` Project: ${kleur.white(firebaseProjectId)}`));
|
|
760
|
-
} else {
|
|
761
|
-
core = await prompts(coreQuestions, { onCancel: cancel });
|
|
762
|
-
}
|
|
763
|
-
|
|
764
776
|
// ── Firebase: create from scratch (when selected) ─────────────────────────
|
|
765
777
|
let firebaseIncludeWeb = true;
|
|
766
778
|
if (backend === 'firebase' && firebaseSetupMode === 'create') {
|
|
@@ -1243,19 +1255,6 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1243
1255
|
Object.assign(core, api);
|
|
1244
1256
|
}
|
|
1245
1257
|
|
|
1246
|
-
// Resolve targetDir now that we have the app name
|
|
1247
|
-
if (!targetDir) {
|
|
1248
|
-
const folderName = toPackageName(core.appName.trim());
|
|
1249
|
-
targetDir = path.resolve(process.cwd(), folderName);
|
|
1250
|
-
// Guard: derived dir must not already exist
|
|
1251
|
-
if (await fs.pathExists(targetDir)) {
|
|
1252
|
-
const contents = await fs.readdir(targetDir);
|
|
1253
|
-
if (contents.length > 0) {
|
|
1254
|
-
throw new Error(tr('new.firebase.error.dirNotEmpty', { path: targetDir }));
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
1258
|
// ── Firebase existing project: enable APIs + create Firestore/Storage ───
|
|
1260
1259
|
if (backend === 'firebase' && firebaseSetupMode === 'existing' && core.firebaseProjectId) {
|
|
1261
1260
|
const ps4 = makeProgressSpinner();
|
|
@@ -1266,7 +1265,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1266
1265
|
ps4.next(stepProgress('enable-apis', language));
|
|
1267
1266
|
} else if (key === 'enable-apis-warn') {
|
|
1268
1267
|
ps4.warn(`${tr('new.firebase.create.failed')}: APIs`);
|
|
1269
|
-
console.log(kleur.yellow(` ⚠
|
|
1268
|
+
console.log(kleur.yellow(` ⚠ ${tr('new.firebase.existing.apisFailed')} ${(data?.error || '').slice(0, 80)}`));
|
|
1270
1269
|
} else if (key === 'firestore') {
|
|
1271
1270
|
ps4.next(stepProgress('firestore', language));
|
|
1272
1271
|
} else if (key === 'storage') {
|
|
@@ -1277,7 +1276,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1277
1276
|
console.log(kleur.cyan(` ${data?.url || ''}`));
|
|
1278
1277
|
} else if (key === 'auth-google-warn') {
|
|
1279
1278
|
ps4.stop();
|
|
1280
|
-
console.log(kleur.yellow(` ⚠
|
|
1279
|
+
console.log(kleur.yellow(` ⚠ ${tr('new.firebase.existing.googleSignInManual')}`));
|
|
1281
1280
|
console.log(kleur.cyan(` ${data?.url || ''}`));
|
|
1282
1281
|
}
|
|
1283
1282
|
},
|
package/lib/commands/run.js
CHANGED
|
@@ -54,7 +54,8 @@ async function runRun(directory, options = {}) {
|
|
|
54
54
|
const args = ['run', ...deviceArgs, ...dartDefines];
|
|
55
55
|
|
|
56
56
|
console.log(kleur.bold(`\n${t('run.launching')}`));
|
|
57
|
-
console.log(kleur.dim(` flutter ${args.join(' ')}
|
|
57
|
+
console.log(kleur.dim(` flutter ${args.join(' ')}`));
|
|
58
|
+
console.log(kleur.dim(` ✦ ${t('run.updateHint.prefix')} `) + kleur.cyan('kasy update') + kleur.dim(` ${t('run.updateHint.suffix')}\n`));
|
|
58
59
|
|
|
59
60
|
return new Promise((resolve, reject) => {
|
|
60
61
|
const proc = spawn('flutter', args, { cwd: projectDir, stdio: 'inherit' });
|
package/lib/utils/checks.js
CHANGED
|
@@ -144,6 +144,17 @@ function getBackendChecks(backend) {
|
|
|
144
144
|
return [...(BACKEND_CHECKS[backend] || [])];
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function diagnoseFailure(err) {
|
|
148
|
+
const text = `${err?.stderr || ''}\n${err?.stdout || ''}\n${err?.message || ''}`.toLowerCase();
|
|
149
|
+
if (text.includes('xcode') && text.includes('license')) {
|
|
150
|
+
return 'xcodeLicense';
|
|
151
|
+
}
|
|
152
|
+
if (text.includes('xcode-select') || text.includes('command line tools') || text.includes('no developer tools')) {
|
|
153
|
+
return 'xcodeCli';
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
function extractVersion(stdout, checkName) {
|
|
148
159
|
const raw = (stdout || '').trim();
|
|
149
160
|
if (!raw) return null;
|
|
@@ -200,7 +211,8 @@ async function runSingleCheck(check, options = {}) {
|
|
|
200
211
|
}
|
|
201
212
|
}
|
|
202
213
|
return { ...check, ok: true, version: version || null };
|
|
203
|
-
} catch {
|
|
214
|
+
} catch (err) {
|
|
215
|
+
const diagnosis = diagnoseFailure(err);
|
|
204
216
|
if (check.tryInstall) {
|
|
205
217
|
if (!silent) spinner.text = t(check.tryInstallMessageKey || 'setup.flutterfire.installing');
|
|
206
218
|
try {
|
|
@@ -242,7 +254,10 @@ async function runSingleCheck(check, options = {}) {
|
|
|
242
254
|
if (!silent) {
|
|
243
255
|
if (check.required) {
|
|
244
256
|
const detail = autoInstallFailed ? ` — ${t('checks.install.failed')}` : '';
|
|
245
|
-
|
|
257
|
+
const diagSuffix = diagnosis ? `\n ${kleur.dim(`→ ${t(`checks.diagnostic.${diagnosis}`, { name: check.name })}`)}` : '';
|
|
258
|
+
spinner.fail(t('checks.missing', { name: check.name }) + detail + diagSuffix);
|
|
259
|
+
} else if (diagnosis) {
|
|
260
|
+
spinner.warn(t(`checks.diagnostic.${diagnosis}`, { name: check.name }));
|
|
246
261
|
} else {
|
|
247
262
|
const hint = !check.waitPrompt && check.failHint ? `\n ${kleur.dim(`→ ${check.failHint}`)}` : '';
|
|
248
263
|
if (check.warnMessage) {
|
|
@@ -255,7 +270,7 @@ async function runSingleCheck(check, options = {}) {
|
|
|
255
270
|
}
|
|
256
271
|
}
|
|
257
272
|
|
|
258
|
-
return { ...check, ok: false, autoInstallFailed };
|
|
273
|
+
return { ...check, ok: false, autoInstallFailed, diagnosis };
|
|
259
274
|
}
|
|
260
275
|
}
|
|
261
276
|
|
|
@@ -293,7 +308,10 @@ async function runChecks(checks, title, options = {}) {
|
|
|
293
308
|
const hint = result.failHint ? `\n ${kleur.dim(`→ ${result.failHint}`)}` : '';
|
|
294
309
|
if (result.required) {
|
|
295
310
|
const detail = result.autoInstallFailed ? ` — ${t('checks.install.failed')}` : '';
|
|
296
|
-
|
|
311
|
+
const diagSuffix = result.diagnosis ? `\n ${kleur.dim(`→ ${t(`checks.diagnostic.${result.diagnosis}`, { name: result.name })}`)}` : '';
|
|
312
|
+
console.log(kleur.red(` ✖ ${t('checks.missing', { name: result.name })}${detail}${diagSuffix}${hint}`));
|
|
313
|
+
} else if (result.diagnosis) {
|
|
314
|
+
console.log(kleur.yellow(` ⚠ ${t(`checks.diagnostic.${result.diagnosis}`, { name: result.name })}`));
|
|
297
315
|
} else if (result.warnMessage) {
|
|
298
316
|
console.log(kleur.yellow(` ⚠ ${result.warnMessage}${hint}`));
|
|
299
317
|
} else if (result.warnMessageKey) {
|
package/lib/utils/i18n.js
CHANGED
|
@@ -96,6 +96,8 @@ const MESSAGES = {
|
|
|
96
96
|
'checks.notFound': '{name} not found',
|
|
97
97
|
'checks.flutter.warn': 'Flutter SDK not found. Install Flutter to build and run apps: https://docs.flutter.dev/get-started/install',
|
|
98
98
|
'checks.install.failed': 'auto-install failed — run the command manually',
|
|
99
|
+
'checks.diagnostic.xcodeLicense': '{name} is installed, but Xcode needs the license accepted. Run: sudo xcodebuild -license',
|
|
100
|
+
'checks.diagnostic.xcodeCli': '{name} is installed, but Xcode Command Line Tools are missing. Run: xcode-select --install',
|
|
99
101
|
'banner.title': 'Kasy CLI · Flutter SaaS Generator',
|
|
100
102
|
'welcome.firstRun': 'Welcome to Kasy CLI!',
|
|
101
103
|
'welcome.chooseLanguage': 'First, choose your language:',
|
|
@@ -132,6 +134,7 @@ const MESSAGES = {
|
|
|
132
134
|
'new.supabase.q.create.existing': '📂 Use existing project',
|
|
133
135
|
'new.firebase.q.setupMode': 'How do you want to set up Firebase?',
|
|
134
136
|
'new.firebase.q.setupMode.push': 'Firebase project for push notifications (FCM):',
|
|
137
|
+
'new.firebase.q.setupMode.push.explain': 'Push notifications (FCM) require a Firebase project, even when your main backend is Supabase or API REST.',
|
|
135
138
|
'new.firebase.q.setupMode.create': '✨ Create from scratch (recommended for beginners)',
|
|
136
139
|
'new.firebase.q.setupMode.existing': '📂 Use existing project',
|
|
137
140
|
'new.firebase.q.region': 'Where are most of your app users located?',
|
|
@@ -510,6 +513,8 @@ const MESSAGES = {
|
|
|
510
513
|
'new.firebase.interactive.billingWaiting': 'Checking Blaze status...',
|
|
511
514
|
'new.firebase.interactive.billingTimeout': 'Blaze plan not confirmed after timeout. Deploy skipped — run manually when ready.',
|
|
512
515
|
'new.firebase.interactive.authWarn': 'Could not enable Email/Password and Anonymous auth automatically. Enable manually:',
|
|
516
|
+
'new.firebase.existing.apisFailed': 'Could not activate APIs:',
|
|
517
|
+
'new.firebase.existing.googleSignInManual': 'Google Sign-In: enable manually in Authentication → Sign-in method → Google',
|
|
513
518
|
|
|
514
519
|
'new.firebase.interactive.ready': 'Ready to deploy Push Notifications + Security Rules now?',
|
|
515
520
|
'new.firebase.interactive.deploying': 'Deploying Cloud Functions + Firestore/Storage rules...',
|
|
@@ -548,6 +553,9 @@ const MESSAGES = {
|
|
|
548
553
|
'new.api.success.fcm': '• Firebase is required for push notifications (FCM) — configure APNs key in Firebase console',
|
|
549
554
|
'new.api.success.auth': '• Implement the social auth endpoints (Google, Apple) on your backend',
|
|
550
555
|
|
|
556
|
+
'new.outdated.hint': "projects created now won't include the latest improvements.",
|
|
557
|
+
'new.outdated.upgradeNow': 'Upgrade to the latest version before creating? (requires active subscription)',
|
|
558
|
+
'new.outdated.upgraded': 'kasy updated! Run kasy new again.',
|
|
551
559
|
'new.success.title': 'Project created successfully!',
|
|
552
560
|
'new.success.nextSteps': 'Next steps:',
|
|
553
561
|
'new.success.step.cd': 'Go to your project folder:',
|
|
@@ -580,6 +588,8 @@ const MESSAGES = {
|
|
|
580
588
|
// run command
|
|
581
589
|
'cli.command.run.description': '▶ Run the Flutter app using .env (launch.json dart-defines fallback)',
|
|
582
590
|
'run.launching': 'Launching Flutter app...',
|
|
591
|
+
'run.updateHint.prefix': 'Project improvements available —',
|
|
592
|
+
'run.updateHint.suffix': 'to see what\'s new',
|
|
583
593
|
'run.error.notFlutterProject': 'No pubspec.yaml found. Run this command from inside a Flutter project.',
|
|
584
594
|
'run.error.flutterNotFound': 'Flutter not found. Make sure Flutter is installed and on your PATH.',
|
|
585
595
|
|
|
@@ -811,6 +821,8 @@ const MESSAGES = {
|
|
|
811
821
|
'checks.notFound': '{name} nao encontrado',
|
|
812
822
|
'checks.flutter.warn': 'Flutter SDK nao encontrado. Instale o Flutter para compilar e executar apps: https://docs.flutter.dev/get-started/install',
|
|
813
823
|
'checks.install.failed': 'instalacao automatica falhou — execute o comando manualmente',
|
|
824
|
+
'checks.diagnostic.xcodeLicense': '{name} esta instalado, mas o Xcode precisa que a licenca seja aceita. Execute: sudo xcodebuild -license',
|
|
825
|
+
'checks.diagnostic.xcodeCli': '{name} esta instalado, mas faltam as Command Line Tools do Xcode. Execute: xcode-select --install',
|
|
814
826
|
'banner.title': 'Kasy CLI · Gerador Flutter SaaS',
|
|
815
827
|
'welcome.firstRun': 'Bem-vindo ao Kasy CLI!',
|
|
816
828
|
'welcome.chooseLanguage': 'Primeiro, escolha seu idioma:',
|
|
@@ -847,6 +859,7 @@ const MESSAGES = {
|
|
|
847
859
|
'new.supabase.q.create.existing': '📂 Usar projeto existente',
|
|
848
860
|
'new.firebase.q.setupMode': 'Como deseja configurar o Firebase?',
|
|
849
861
|
'new.firebase.q.setupMode.push': 'Projeto Firebase para notificações push (FCM):',
|
|
862
|
+
'new.firebase.q.setupMode.push.explain': 'Notificações push (FCM) precisam de um projeto Firebase, mesmo quando seu backend principal é Supabase ou API REST.',
|
|
850
863
|
'new.firebase.q.setupMode.create': '✨ Criar do zero (recomendado para iniciantes)',
|
|
851
864
|
'new.firebase.q.setupMode.existing': '📂 Usar projeto existente',
|
|
852
865
|
'new.firebase.q.region': 'Onde está a maioria dos usuários do seu app?',
|
|
@@ -1225,6 +1238,8 @@ const MESSAGES = {
|
|
|
1225
1238
|
'new.firebase.interactive.billingWaiting': 'Verificando status do Blaze...',
|
|
1226
1239
|
'new.firebase.interactive.billingTimeout': 'Plano Blaze nao confirmado apos o tempo limite. Deploy ignorado — rode manualmente quando estiver pronto.',
|
|
1227
1240
|
'new.firebase.interactive.authWarn': 'Nao foi possivel ativar Email/Senha e Anonimo automaticamente. Ative manualmente:',
|
|
1241
|
+
'new.firebase.existing.apisFailed': 'Nao foi possivel ativar APIs:',
|
|
1242
|
+
'new.firebase.existing.googleSignInManual': 'Google Sign-In: ative manualmente em Authentication → Sign-in method → Google',
|
|
1228
1243
|
|
|
1229
1244
|
'new.firebase.interactive.ready': 'Pronto para publicar Push Notifications + Regras agora?',
|
|
1230
1245
|
'new.firebase.interactive.deploying': 'Fazendo deploy das Cloud Functions + Regras (Storage e Firestore)...',
|
|
@@ -1263,6 +1278,9 @@ const MESSAGES = {
|
|
|
1263
1278
|
'new.api.success.fcm': '• Firebase e necessario para notificacoes push (FCM) — configure a chave APNs no console do Firebase',
|
|
1264
1279
|
'new.api.success.auth': '• Implemente os endpoints de auth social (Google, Apple) no seu backend',
|
|
1265
1280
|
|
|
1281
|
+
'new.outdated.hint': 'projetos criados agora não terão as últimas melhorias.',
|
|
1282
|
+
'new.outdated.upgradeNow': 'Atualizar antes de criar? (requer assinatura ativa)',
|
|
1283
|
+
'new.outdated.upgraded': 'kasy atualizado! Rode kasy new novamente.',
|
|
1266
1284
|
'new.success.title': 'Projeto criado com sucesso!',
|
|
1267
1285
|
'new.success.nextSteps': 'Proximos passos:',
|
|
1268
1286
|
'new.success.step.cd': 'Entre na pasta do projeto:',
|
|
@@ -1295,6 +1313,8 @@ const MESSAGES = {
|
|
|
1295
1313
|
// run command
|
|
1296
1314
|
'cli.command.run.description': '▶ Executa o app Flutter usando .env (fallback para dart-defines do launch.json)',
|
|
1297
1315
|
'run.launching': 'Iniciando app Flutter...',
|
|
1316
|
+
'run.updateHint.prefix': 'Melhorias disponíveis para o projeto —',
|
|
1317
|
+
'run.updateHint.suffix': 'para ver o que há de novo',
|
|
1298
1318
|
'run.error.notFlutterProject': 'Nenhum pubspec.yaml encontrado. Execute este comando dentro de um projeto Flutter.',
|
|
1299
1319
|
'run.error.flutterNotFound': 'Flutter nao encontrado. Verifique se o Flutter esta instalado e no PATH.',
|
|
1300
1320
|
|
|
@@ -1526,6 +1546,8 @@ const MESSAGES = {
|
|
|
1526
1546
|
'checks.notFound': '{name} no encontrado',
|
|
1527
1547
|
'checks.flutter.warn': 'Flutter SDK no encontrado. Instala Flutter para compilar y ejecutar apps: https://docs.flutter.dev/get-started/install',
|
|
1528
1548
|
'checks.install.failed': 'instalación automática falló — ejecuta el comando manualmente',
|
|
1549
|
+
'checks.diagnostic.xcodeLicense': '{name} está instalado, pero Xcode necesita que aceptes la licencia. Ejecuta: sudo xcodebuild -license',
|
|
1550
|
+
'checks.diagnostic.xcodeCli': '{name} está instalado, pero faltan las Command Line Tools de Xcode. Ejecuta: xcode-select --install',
|
|
1529
1551
|
'banner.title': 'Kasy CLI · Generador Flutter SaaS',
|
|
1530
1552
|
'welcome.firstRun': '¡Bienvenido a Kasy CLI!',
|
|
1531
1553
|
'welcome.chooseLanguage': 'Primero, elige tu idioma:',
|
|
@@ -1562,6 +1584,7 @@ const MESSAGES = {
|
|
|
1562
1584
|
'new.supabase.q.create.existing': '📂 Usar proyecto existente',
|
|
1563
1585
|
'new.firebase.q.setupMode': '¿Cómo quieres configurar Firebase?',
|
|
1564
1586
|
'new.firebase.q.setupMode.push': 'Proyecto Firebase para notificaciones push (FCM):',
|
|
1587
|
+
'new.firebase.q.setupMode.push.explain': 'Las notificaciones push (FCM) requieren un proyecto Firebase, incluso cuando tu backend principal es Supabase o API REST.',
|
|
1565
1588
|
'new.firebase.q.setupMode.create': '✨ Crear desde cero (recomendado para principiantes)',
|
|
1566
1589
|
'new.firebase.q.setupMode.existing': '📂 Usar proyecto existente',
|
|
1567
1590
|
'new.firebase.q.region': '¿Dónde están la mayoría de los usuarios de tu app?',
|
|
@@ -1940,6 +1963,8 @@ const MESSAGES = {
|
|
|
1940
1963
|
'new.firebase.interactive.billingWaiting': 'Verificando estado del Blaze...',
|
|
1941
1964
|
'new.firebase.interactive.billingTimeout': 'Plan Blaze no confirmado tras el tiempo límite. Despliegue omitido — ejecuta manualmente cuando estés listo.',
|
|
1942
1965
|
'new.firebase.interactive.authWarn': 'No se pudo activar Email/Contraseña y Anónimo automáticamente. Actívalos manualmente:',
|
|
1966
|
+
'new.firebase.existing.apisFailed': 'No se pudieron activar las APIs:',
|
|
1967
|
+
'new.firebase.existing.googleSignInManual': 'Google Sign-In: activa manualmente en Authentication → Sign-in method → Google',
|
|
1943
1968
|
|
|
1944
1969
|
'new.firebase.interactive.ready': '¿Listo para publicar Push Notifications + Reglas ahora?',
|
|
1945
1970
|
'new.firebase.interactive.deploying': 'Desplegando Cloud Functions + Reglas (Storage y Firestore)...',
|
|
@@ -1978,6 +2003,9 @@ const MESSAGES = {
|
|
|
1978
2003
|
'new.api.success.fcm': '• Firebase es necesario para notificaciones push (FCM) — configura la clave APNs en la consola de Firebase',
|
|
1979
2004
|
'new.api.success.auth': '• Implementa los endpoints de auth social (Google, Apple) en tu backend',
|
|
1980
2005
|
|
|
2006
|
+
'new.outdated.hint': 'los proyectos creados ahora no tendrán las últimas mejoras.',
|
|
2007
|
+
'new.outdated.upgradeNow': '¿Actualizar a la última versión antes de crear? (requiere suscripción activa)',
|
|
2008
|
+
'new.outdated.upgraded': '¡kasy actualizado! Ejecuta kasy new nuevamente.',
|
|
1981
2009
|
'new.success.title': '¡Proyecto creado con exito!',
|
|
1982
2010
|
'new.success.nextSteps': 'Proximos pasos:',
|
|
1983
2011
|
'new.success.step.cd': 'Ve a la carpeta del proyecto:',
|
|
@@ -2010,6 +2038,8 @@ const MESSAGES = {
|
|
|
2010
2038
|
// run command
|
|
2011
2039
|
'cli.command.run.description': '▶ Ejecuta el app Flutter usando .env (fallback a dart-defines del launch.json)',
|
|
2012
2040
|
'run.launching': 'Iniciando app Flutter...',
|
|
2041
|
+
'run.updateHint.prefix': 'Mejoras disponibles para el proyecto —',
|
|
2042
|
+
'run.updateHint.suffix': 'para ver las novedades',
|
|
2013
2043
|
'run.error.notFlutterProject': 'No se encontro pubspec.yaml. Ejecuta este comando dentro de un proyecto Flutter.',
|
|
2014
2044
|
'run.error.flutterNotFound': 'Flutter no encontrado. Verifica que Flutter este instalado y en el PATH.',
|
|
2015
2045
|
|
package/lib/utils/ui.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI wrapper around @clack/prompts.
|
|
3
|
+
*
|
|
4
|
+
* Goal: centralize prompt/styling primitives so commands don't import
|
|
5
|
+
* `@clack/prompts` directly. This keeps the migration from the legacy
|
|
6
|
+
* `prompts` package incremental — commands can adopt this module one
|
|
7
|
+
* by one without touching the rest.
|
|
8
|
+
*
|
|
9
|
+
* Cancel handling: every prompt auto-detects Ctrl+C via `isCancel`
|
|
10
|
+
* and calls the optional `onCancel` callback (or exits with code 0
|
|
11
|
+
* and a friendly message). Callers don't need to wire `onCancel`
|
|
12
|
+
* in each call like they did with the old `prompts` library.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const clack = require('@clack/prompts');
|
|
16
|
+
|
|
17
|
+
function handleCancel(result, onCancel) {
|
|
18
|
+
if (clack.isCancel(result)) {
|
|
19
|
+
if (typeof onCancel === 'function') {
|
|
20
|
+
onCancel();
|
|
21
|
+
} else {
|
|
22
|
+
clack.cancel('Operação cancelada.');
|
|
23
|
+
}
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function select({ message, options, initialValue, onCancel }) {
|
|
30
|
+
const result = await clack.select({ message, options, initialValue });
|
|
31
|
+
return handleCancel(result, onCancel);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function text({ message, placeholder, initialValue, defaultValue, validate, onCancel }) {
|
|
35
|
+
const result = await clack.text({
|
|
36
|
+
message,
|
|
37
|
+
placeholder,
|
|
38
|
+
initialValue,
|
|
39
|
+
defaultValue,
|
|
40
|
+
validate: validate
|
|
41
|
+
? (value) => {
|
|
42
|
+
const out = validate(value);
|
|
43
|
+
if (out === true || out == null) return undefined;
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
});
|
|
48
|
+
return handleCancel(result, onCancel);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function confirm({ message, initialValue = true, onCancel }) {
|
|
52
|
+
const result = await clack.confirm({ message, initialValue });
|
|
53
|
+
return handleCancel(result, onCancel);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function multiselect({ message, options, initialValues, required = false, onCancel }) {
|
|
57
|
+
const result = await clack.multiselect({ message, options, initialValues, required });
|
|
58
|
+
return handleCancel(result, onCancel);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function intro(title) { clack.intro(title); }
|
|
62
|
+
function outro(message) { clack.outro(message); }
|
|
63
|
+
function note(message, title) { clack.note(message, title); }
|
|
64
|
+
function cancel(message) { clack.cancel(message); }
|
|
65
|
+
|
|
66
|
+
function spinner() { return clack.spinner(); }
|
|
67
|
+
|
|
68
|
+
const log = clack.log;
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
select,
|
|
72
|
+
text,
|
|
73
|
+
confirm,
|
|
74
|
+
multiselect,
|
|
75
|
+
intro,
|
|
76
|
+
outro,
|
|
77
|
+
note,
|
|
78
|
+
cancel,
|
|
79
|
+
spinner,
|
|
80
|
+
log,
|
|
81
|
+
isCancel: clack.isCancel,
|
|
82
|
+
};
|
package/lib/utils/updates.js
CHANGED
|
@@ -4,6 +4,7 @@ const https = require('node:https');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { existsSync, readJsonSync, writeJsonSync, ensureDirSync } = require('fs-extra');
|
|
6
6
|
const kleur = require('kleur');
|
|
7
|
+
const prompts = require('prompts');
|
|
7
8
|
const pkg = require('../../package.json');
|
|
8
9
|
const { CONFIG_PATH } = require('./license');
|
|
9
10
|
|
|
@@ -94,4 +95,40 @@ async function checkForUpdates() {
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Chamado antes de kasy new. Se houver versão mais nova no cache,
|
|
100
|
+
* mostra aviso e pergunta se quer continuar ou sair para atualizar.
|
|
101
|
+
* Nunca bloqueia — se o usuário ignorar, o fluxo segue normalmente.
|
|
102
|
+
*/
|
|
103
|
+
async function warnIfOutdatedBeforeNew(t) {
|
|
104
|
+
try {
|
|
105
|
+
const cache = (readConfig().updateCheck) || {};
|
|
106
|
+
if (!cache.latestVersion || !isNewer(cache.latestVersion, pkg.version)) return;
|
|
107
|
+
|
|
108
|
+
const hint = t ? t('new.outdated.hint') : 'projetos criados agora não terão as últimas melhorias.';
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(
|
|
111
|
+
kleur.bgYellow().black(' UPDATE ') + ' ' +
|
|
112
|
+
kleur.bold(`v${cache.latestVersion} disponível`) +
|
|
113
|
+
kleur.dim(` — ${hint}`)
|
|
114
|
+
);
|
|
115
|
+
console.log('');
|
|
116
|
+
|
|
117
|
+
const { upgrade } = await prompts({
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
name: 'upgrade',
|
|
120
|
+
message: t ? t('new.outdated.upgradeNow') : 'Atualizar antes de criar? (requer assinatura ativa)',
|
|
121
|
+
initial: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (upgrade) {
|
|
125
|
+
const { spawnSync } = require('node:child_process');
|
|
126
|
+
const result = spawnSync('kasy', ['upgrade'], { stdio: 'inherit', shell: true });
|
|
127
|
+
process.exit(result.status ?? 0);
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
// Nunca travar o kasy new por causa do aviso de versão
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { checkForUpdates, warnIfOutdatedBeforeNew };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kasy-cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.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"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"test:localize-docs": "node ./test/localize-release-docs.test.js"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"@clack/prompts": "^1.4.0",
|
|
47
48
|
"commander": "^12.0.0",
|
|
48
49
|
"fs-extra": "^11.2.0",
|
|
49
50
|
"gradient-string": "^1.2.0",
|