kasy-cli 1.2.1 → 1.3.0
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/README.md
CHANGED
|
@@ -5,13 +5,13 @@ CLI for scaffolding production-ready Flutter SaaS apps.
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install -g
|
|
8
|
+
npm install -g kasy-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Or run without installing:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx
|
|
14
|
+
npx kasy-cli new
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Quick start
|
package/lib/utils/i18n.js
CHANGED
|
@@ -104,7 +104,10 @@ const MESSAGES = {
|
|
|
104
104
|
'license.required': '🔑 License key required to use Kasy CLI',
|
|
105
105
|
'license.checking': 'Validating license key...',
|
|
106
106
|
'license.saved': '✅ License validated successfully',
|
|
107
|
-
'license.invalid': 'Invalid license
|
|
107
|
+
'license.invalid': 'Invalid license key. Check your key or contact support at kasy.dev.',
|
|
108
|
+
'license.expired': '❌ Your license has expired. Renew at kasy.dev.',
|
|
109
|
+
'license.inactive': '❌ Your license has been deactivated. Contact support at kasy.dev.',
|
|
110
|
+
'license.offlineWarning': '⚠️ Could not reach license server — continuing offline.',
|
|
108
111
|
'prompt.license.enter': '👉 Enter your license key (XXXX-XXXX-XXXX-XXXX)',
|
|
109
112
|
'prompt.license.invalid': 'Invalid format. Use XXXX-XXXX-XXXX-XXXX.',
|
|
110
113
|
'prompt.appName.enter': 'Enter the name of your app',
|
|
@@ -789,7 +792,10 @@ const MESSAGES = {
|
|
|
789
792
|
'license.required': '🔑 Chave de ativação necessária para usar o Kasy CLI',
|
|
790
793
|
'license.checking': 'Validando chave de ativação...',
|
|
791
794
|
'license.saved': '✅ Chave validada com sucesso',
|
|
792
|
-
'license.invalid': '
|
|
795
|
+
'license.invalid': 'Chave inválida. Verifique sua chave ou entre em contato em kasy.dev.',
|
|
796
|
+
'license.expired': '❌ Sua assinatura expirou. Renove em kasy.dev.',
|
|
797
|
+
'license.inactive': '❌ Sua chave foi desativada. Entre em contato em kasy.dev.',
|
|
798
|
+
'license.offlineWarning': '⚠️ Servidor fora do ar — continuando no modo offline.',
|
|
793
799
|
'prompt.license.enter': '👉 Digite sua chave de ativação (XXXX-XXXX-XXXX-XXXX)',
|
|
794
800
|
'prompt.license.invalid': 'Formato inválido. Use XXXX-XXXX-XXXX-XXXX.',
|
|
795
801
|
'prompt.appName.enter': 'Digite o nome do seu app',
|
|
@@ -1474,7 +1480,10 @@ const MESSAGES = {
|
|
|
1474
1480
|
'license.required': '🔑 Se requiere clave de activación para usar Kasy CLI',
|
|
1475
1481
|
'license.checking': 'Validando clave de activación...',
|
|
1476
1482
|
'license.saved': '✅ Clave validada correctamente',
|
|
1477
|
-
'license.invalid': '
|
|
1483
|
+
'license.invalid': 'Clave inválida. Verifica tu clave o contacta soporte en kasy.dev.',
|
|
1484
|
+
'license.expired': '❌ Tu suscripción ha expirado. Renueva en kasy.dev.',
|
|
1485
|
+
'license.inactive': '❌ Tu clave fue desactivada. Contacta soporte en kasy.dev.',
|
|
1486
|
+
'license.offlineWarning': '⚠️ No se pudo conectar al servidor — continuando sin conexión.',
|
|
1478
1487
|
'prompt.license.enter': '👉 Ingresa tu clave de activación (XXXX-XXXX-XXXX-XXXX)',
|
|
1479
1488
|
'prompt.license.invalid': 'Formato inválido. Usa XXXX-XXXX-XXXX-XXXX.',
|
|
1480
1489
|
'prompt.appName.enter': 'Ingresa el nombre de tu app',
|
|
@@ -1,72 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const https = require('node:https');
|
|
1
4
|
const kleur = require('kleur');
|
|
2
|
-
const { getStoredLicenseKey, isLicenseFormatValid, setStoredLicenseKey } = require('./license');
|
|
5
|
+
const { getStoredLicenseKey, isLicenseFormatValid, setStoredLicenseKey, readLocalConfig, writeLocalConfig } = require('./license');
|
|
3
6
|
const { promptLicenseKey } = require('./prompts');
|
|
4
7
|
const { createTranslator, detectDefaultLanguage } = require('./i18n');
|
|
5
8
|
|
|
9
|
+
const VALIDATE_URL = 'https://xkefozfsrmqjtesiitvk.supabase.co/functions/v1/validate-license';
|
|
10
|
+
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
11
|
+
|
|
12
|
+
const FREE_COMMANDS = new Set(['run', 'doctor', 'features', 'version', 'help', 'uninstall']);
|
|
13
|
+
|
|
6
14
|
function resolveCommandFromArgv(argv = []) {
|
|
7
15
|
for (let i = 0; i < argv.length; i += 1) {
|
|
8
16
|
const arg = argv[i];
|
|
9
|
-
|
|
10
17
|
if (!arg) continue;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (arg === '--lang' || arg === '-l') {
|
|
14
|
-
i += 1;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Skip long flags with inline value and regular flags.
|
|
19
|
-
if (arg.startsWith('--') || (arg.startsWith('-') && arg.length > 1)) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
|
|
18
|
+
if (arg === '--lang' || arg === '-l') { i += 1; continue; }
|
|
19
|
+
if (arg.startsWith('--') || (arg.startsWith('-') && arg.length > 1)) continue;
|
|
23
20
|
return arg;
|
|
24
21
|
}
|
|
25
|
-
|
|
26
22
|
return null;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
function shouldRequireLicenseForArgv(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
function shouldRequireLicenseForArgv(argv = []) {
|
|
26
|
+
const command = resolveCommandFromArgv(argv);
|
|
27
|
+
return !!command && !FREE_COMMANDS.has(command);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function validateOnServer(key) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const body = JSON.stringify({ key });
|
|
33
|
+
const url = new URL(VALIDATE_URL);
|
|
34
|
+
const options = {
|
|
35
|
+
hostname: url.hostname,
|
|
36
|
+
path: url.pathname,
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
39
|
+
timeout: 5000,
|
|
40
|
+
};
|
|
41
|
+
const req = https.request(options, (res) => {
|
|
42
|
+
let data = '';
|
|
43
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
44
|
+
res.on('end', () => {
|
|
45
|
+
try { resolve(JSON.parse(data)); }
|
|
46
|
+
catch { resolve(null); }
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
req.on('error', () => resolve(null));
|
|
50
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
51
|
+
req.write(body);
|
|
52
|
+
req.end();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getCachedValidation() {
|
|
57
|
+
const config = readLocalConfig();
|
|
58
|
+
const cache = config.licenseCache;
|
|
59
|
+
if (!cache || !cache.checkedAt) return null;
|
|
60
|
+
if (Date.now() - cache.checkedAt > CACHE_TTL_MS) return null;
|
|
61
|
+
return cache;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function setCachedValidation(result) {
|
|
65
|
+
const config = readLocalConfig();
|
|
66
|
+
writeLocalConfig({ ...config, licenseCache: { ...result, checkedAt: Date.now() } });
|
|
33
67
|
}
|
|
34
68
|
|
|
35
69
|
async function ensureLicenseKey(options = {}) {
|
|
36
70
|
const language = options.language || detectDefaultLanguage();
|
|
37
71
|
const t = createTranslator(language);
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log(kleur.green(t('license.saved')));
|
|
73
|
+
let key = getStoredLicenseKey();
|
|
74
|
+
|
|
75
|
+
// No key stored — ask the user
|
|
76
|
+
if (!key || !isLicenseFormatValid(key)) {
|
|
44
77
|
console.log('');
|
|
45
|
-
|
|
78
|
+
console.log(kleur.yellow(t('license.required')));
|
|
79
|
+
console.log('');
|
|
80
|
+
const entered = await promptLicenseKey('', { t });
|
|
81
|
+
key = entered.trim().toUpperCase();
|
|
82
|
+
if (!isLicenseFormatValid(key)) throw new Error(t('license.invalid'));
|
|
46
83
|
}
|
|
47
84
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
85
|
+
// Check local cache first (avoids network on every command)
|
|
86
|
+
const cached = getCachedValidation();
|
|
87
|
+
if (cached && cached.valid) {
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
51
90
|
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
// Validate against server
|
|
92
|
+
process.stdout.write(kleur.cyan(t('license.checking')) + ' ');
|
|
93
|
+
const result = await validateOnServer(key);
|
|
54
94
|
|
|
55
|
-
|
|
56
|
-
|
|
95
|
+
if (!result) {
|
|
96
|
+
// Network error — allow if key format is valid (offline tolerance)
|
|
97
|
+
console.log(kleur.yellow(t('license.offlineWarning') || '(offline — using local key)'));
|
|
98
|
+
console.log('');
|
|
99
|
+
return key;
|
|
100
|
+
}
|
|
57
101
|
|
|
58
|
-
if (!
|
|
102
|
+
if (!result.valid) {
|
|
103
|
+
const reason = result.reason;
|
|
104
|
+
if (reason === 'expired') {
|
|
105
|
+
throw new Error(t('license.expired') || 'Your license has expired. Renew at kasy.dev.');
|
|
106
|
+
}
|
|
107
|
+
if (reason === 'inactive') {
|
|
108
|
+
throw new Error(t('license.inactive') || 'Your license has been deactivated. Contact support at kasy.dev.');
|
|
109
|
+
}
|
|
110
|
+
// Key not found — clear stored key so user can re-enter
|
|
111
|
+
setStoredLicenseKey('');
|
|
112
|
+
setCachedValidation({ valid: false });
|
|
59
113
|
throw new Error(t('license.invalid'));
|
|
60
114
|
}
|
|
61
115
|
|
|
62
|
-
|
|
63
|
-
|
|
116
|
+
// Valid — save key and cache result
|
|
117
|
+
setStoredLicenseKey(key);
|
|
118
|
+
setCachedValidation({ valid: true, plan: result.plan, expires_at: result.expires_at });
|
|
119
|
+
console.log(kleur.green('✓'));
|
|
64
120
|
console.log('');
|
|
65
|
-
|
|
66
|
-
return normalized;
|
|
121
|
+
return key;
|
|
67
122
|
}
|
|
68
123
|
|
|
69
|
-
module.exports = {
|
|
70
|
-
ensureLicenseKey,
|
|
71
|
-
shouldRequireLicenseForArgv
|
|
72
|
-
};
|
|
124
|
+
module.exports = { ensureLicenseKey, shouldRequireLicenseForArgv };
|
package/lib/utils/license.js
CHANGED
package/package.json
CHANGED
|
@@ -554,6 +554,8 @@ class _PreviewControlsState extends State<_PreviewControls> {
|
|
|
554
554
|
bool _minimized = true;
|
|
555
555
|
bool _platformBtnHovered = false;
|
|
556
556
|
bool _toolsBtnHovered = false;
|
|
557
|
+
bool _shadowVisible = true;
|
|
558
|
+
Timer? _shadowTimer;
|
|
557
559
|
|
|
558
560
|
static const _platformLabels = ['iOS', 'Android', 'iPad'];
|
|
559
561
|
|
|
@@ -594,13 +596,33 @@ class _PreviewControlsState extends State<_PreviewControls> {
|
|
|
594
596
|
});
|
|
595
597
|
}
|
|
596
598
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
599
|
+
@override
|
|
600
|
+
void dispose() {
|
|
601
|
+
_shadowTimer?.cancel();
|
|
602
|
+
super.dispose();
|
|
603
|
+
}
|
|
602
604
|
|
|
603
|
-
void
|
|
605
|
+
void _hideShadowDuringAnimation() {
|
|
606
|
+
_shadowTimer?.cancel();
|
|
607
|
+
setState(() => _shadowVisible = false);
|
|
608
|
+
_shadowTimer = Timer(const Duration(milliseconds: 140), () {
|
|
609
|
+
if (mounted) setState(() => _shadowVisible = true);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
void _minimize() {
|
|
614
|
+
setState(() {
|
|
615
|
+
_minimized = true;
|
|
616
|
+
_menuOpen = false;
|
|
617
|
+
_toolsMenuOpen = false;
|
|
618
|
+
});
|
|
619
|
+
_hideShadowDuringAnimation();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
void _maximize() {
|
|
623
|
+
setState(() => _minimized = false);
|
|
624
|
+
_hideShadowDuringAnimation();
|
|
625
|
+
}
|
|
604
626
|
|
|
605
627
|
@override
|
|
606
628
|
Widget build(BuildContext context) {
|
|
@@ -617,7 +639,22 @@ class _PreviewControlsState extends State<_PreviewControls> {
|
|
|
617
639
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
618
640
|
mainAxisSize: MainAxisSize.min,
|
|
619
641
|
children: [
|
|
620
|
-
|
|
642
|
+
AnimatedOpacity(
|
|
643
|
+
opacity: _shadowVisible ? 1.0 : 0.0,
|
|
644
|
+
duration: const Duration(milliseconds: 80),
|
|
645
|
+
child: DecoratedBox(
|
|
646
|
+
decoration: const BoxDecoration(
|
|
647
|
+
borderRadius: BorderRadius.all(Radius.circular(24)),
|
|
648
|
+
boxShadow: _pillShadow,
|
|
649
|
+
),
|
|
650
|
+
child: AnimatedSize(
|
|
651
|
+
duration: const Duration(milliseconds: 140),
|
|
652
|
+
curve: Curves.easeInOutCubic,
|
|
653
|
+
alignment: Alignment.centerRight,
|
|
654
|
+
child: _minimized ? _buildMinimizedPill() : _buildPill(),
|
|
655
|
+
),
|
|
656
|
+
),
|
|
657
|
+
),
|
|
621
658
|
if (_menuOpen && !_minimized) _buildMenu(),
|
|
622
659
|
if (_toolsMenuOpen && !_minimized) _buildToolsMenu(),
|
|
623
660
|
],
|
|
@@ -629,7 +666,7 @@ class _PreviewControlsState extends State<_PreviewControls> {
|
|
|
629
666
|
return Container(
|
|
630
667
|
height: 40,
|
|
631
668
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
632
|
-
decoration: _pillDecoration
|
|
669
|
+
decoration: _pillDecoration,
|
|
633
670
|
child: Row(
|
|
634
671
|
mainAxisSize: MainAxisSize.min,
|
|
635
672
|
children: [
|
|
@@ -653,7 +690,7 @@ class _PreviewControlsState extends State<_PreviewControls> {
|
|
|
653
690
|
return Container(
|
|
654
691
|
height: 40,
|
|
655
692
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
656
|
-
decoration: _pillDecoration
|
|
693
|
+
decoration: _pillDecoration,
|
|
657
694
|
child: Row(
|
|
658
695
|
mainAxisSize: MainAxisSize.min,
|
|
659
696
|
children: [
|