kasy-cli 1.15.0 → 1.17.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.
Files changed (44) hide show
  1. package/bin/kasy.js +1 -0
  2. package/lib/commands/add.js +45 -12
  3. package/lib/commands/doctor.js +37 -6
  4. package/lib/commands/icon.js +29 -1
  5. package/lib/commands/new.js +34 -8
  6. package/lib/commands/remove.js +14 -3
  7. package/lib/commands/run.js +264 -3
  8. package/lib/scaffold/CHANGELOG.json +9 -0
  9. package/lib/scaffold/backends/api/patch/README.md +3 -2
  10. package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
  11. package/lib/scaffold/backends/supabase/patch/README.md +3 -2
  12. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
  13. package/lib/scaffold/shared/generator-utils.js +52 -8
  14. package/lib/scaffold/shared/post-build.js +105 -31
  15. package/lib/scaffold/shared/template-strings.js +6 -0
  16. package/lib/utils/i18n/messages-en.js +34 -2
  17. package/lib/utils/i18n/messages-es.js +34 -2
  18. package/lib/utils/i18n/messages-pt.js +34 -2
  19. package/lib/utils/png-padding.js +134 -2
  20. package/package.json +1 -1
  21. package/templates/firebase/README.en.md +17 -7
  22. package/templates/firebase/README.es.md +17 -7
  23. package/templates/firebase/README.md +17 -7
  24. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +15 -0
  25. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +3 -19
  26. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +37 -0
  27. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/OpenAppAction.kt +26 -0
  28. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  29. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  30. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  31. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  32. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  33. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  34. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  35. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  36. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  37. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  38. package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
  39. package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
  40. package/templates/firebase/docs/revenuecat-setup.es.md +28 -8
  41. package/templates/firebase/docs/revenuecat-setup.pt.md +28 -8
  42. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +39 -41
  43. package/templates/firebase/lib/router.dart +15 -1
  44. package/templates/firebase/web/index.html +3 -0
@@ -13,6 +13,36 @@ const pkg = require('../../../package.json');
13
13
  /** Backend IDs: firebase | supabase | api */
14
14
  const BACKENDS = Object.freeze(['firebase', 'supabase', 'api']);
15
15
 
16
+ /**
17
+ * Resolve the default RC keys to bake into launch.json / Makefile.
18
+ *
19
+ * `kasy run` overrides these at runtime based on the device picked (test_ for
20
+ * simulator/emulator, appl_/goog_ for physical). The values here are only used
21
+ * when the user runs Flutter directly (e.g. F5 in VS Code, `make run`) without
22
+ * going through `kasy run`. We default to test_ since that's what works in the
23
+ * iOS Simulator and Android Emulator — the common dev case.
24
+ *
25
+ * Legacy support: projects generated before this refactor only ask for a single
26
+ * Android/iOS key (`rcAndroidKey` / `rcIosKey`). When those are passed we honor
27
+ * them as-is.
28
+ */
29
+ function resolveDefaultRcKeys(answers) {
30
+ // Legacy single-key format (pre-test/prod split): keep behavior unchanged.
31
+ if ((answers.rcAndroidKey || answers.rcIosKey) && !answers.rcTestKey && !answers.rcIosProdKey && !answers.rcAndroidProdKey) {
32
+ return {
33
+ android: answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY',
34
+ ios: answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY',
35
+ };
36
+ }
37
+ const test = (answers.rcTestKey || '').trim();
38
+ const iosProd = (answers.rcIosProdKey || '').trim();
39
+ const androidProd = (answers.rcAndroidProdKey || '').trim();
40
+ return {
41
+ android: test || androidProd || 'YOUR_REVENUECAT_ANDROID_KEY',
42
+ ios: test || iosProd || 'YOUR_REVENUECAT_IOS_KEY',
43
+ };
44
+ }
45
+
16
46
  /**
17
47
  * Build dart-define args for dev and prod environments.
18
48
  * Backend-specific vars: Firebase (none extra), Supabase (BACKEND_URL, SUPABASE_TOKEN), API (BACKEND_URL).
@@ -40,12 +70,11 @@ function buildDartDefines(backend, modules, answers) {
40
70
  }
41
71
 
42
72
  if (modules.includes('revenuecat')) {
43
- const androidKey = answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY';
44
- const iosKey = answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY';
45
- dev.push(`--dart-define=RC_ANDROID_API_KEY=${androidKey}`);
46
- dev.push(`--dart-define=RC_IOS_API_KEY=${iosKey}`);
47
- prod.push(`--dart-define=RC_ANDROID_API_KEY=${androidKey}`);
48
- prod.push(`--dart-define=RC_IOS_API_KEY=${iosKey}`);
73
+ const { android: androidDefault, ios: iosDefault } = resolveDefaultRcKeys(answers);
74
+ dev.push(`--dart-define=RC_ANDROID_API_KEY=${androidDefault}`);
75
+ dev.push(`--dart-define=RC_IOS_API_KEY=${iosDefault}`);
76
+ prod.push(`--dart-define=RC_ANDROID_API_KEY=${androidDefault}`);
77
+ prod.push(`--dart-define=RC_IOS_API_KEY=${iosDefault}`);
49
78
  if (answers.revenuecatWeb) {
50
79
  const webKey = answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY';
51
80
  dev.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
@@ -144,8 +173,11 @@ async function writeEnvExample(projectDir, modules, answers, language = 'en') {
144
173
  if (modules.includes('revenuecat')) {
145
174
  lines.push('');
146
175
  lines.push(t.revenuecat);
147
- lines.push(`RC_ANDROID_API_KEY=${answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY'}`);
148
- lines.push(`RC_IOS_API_KEY=${answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY'}`);
176
+ if (t.revenuecatTest) lines.push(t.revenuecatTest);
177
+ lines.push(`RC_TEST_KEY=${(answers.rcTestKey || '').trim()}`);
178
+ if (t.revenuecatProd) lines.push(t.revenuecatProd);
179
+ lines.push(`RC_IOS_PROD_KEY=${(answers.rcIosProdKey || '').trim()}`);
180
+ lines.push(`RC_ANDROID_PROD_KEY=${(answers.rcAndroidProdKey || '').trim()}`);
149
181
  if (answers.revenuecatWeb) {
150
182
  lines.push(`RC_WEB_API_KEY=${answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY'}`);
151
183
  }
@@ -468,6 +500,16 @@ const bool revenuecatWeb = ${revenuecatWeb};
468
500
  async function writeKitSetup(projectDir, options) {
469
501
  const { appName, bundleId, backend, modules = [], firebaseProjectId, supabaseUrl, supabaseAnonKey, moduleAnswers = {} } = options;
470
502
  const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && moduleAnswers.revenuecatWeb);
503
+ // Booleans tracking which RC keys the user configured. We never persist key
504
+ // values to kit_setup.json — those live in .env (gitignored). These flags
505
+ // let `kasy doctor` warn about release readiness without re-reading .env.
506
+ const revenuecatKeys = modules.includes('revenuecat')
507
+ ? {
508
+ test: !!(moduleAnswers.rcTestKey && moduleAnswers.rcTestKey.trim()),
509
+ iosProd: !!(moduleAnswers.rcIosProdKey && moduleAnswers.rcIosProdKey.trim()),
510
+ androidProd: !!(moduleAnswers.rcAndroidProdKey && moduleAnswers.rcAndroidProdKey.trim()),
511
+ }
512
+ : undefined;
471
513
  const config = {
472
514
  appName: appName || 'App',
473
515
  bundleId: bundleId || 'com.example.app',
@@ -480,6 +522,7 @@ async function writeKitSetup(projectDir, options) {
480
522
  storageProvider: backend === 'firebase' ? 'firebase' : backend === 'supabase' ? 'supabase' : 'api',
481
523
  webCompat: modules.includes('web'),
482
524
  revenuecatWeb,
525
+ ...(revenuecatKeys ? { revenuecatKeys } : {}),
483
526
  internationalization: true,
484
527
  useSentry: modules.includes('sentry'),
485
528
  withOnboarding: modules.includes('onboarding'),
@@ -1638,6 +1681,7 @@ async function localizeReleaseDocs(projectDir, language = 'en') {
1638
1681
  module.exports = {
1639
1682
  BACKENDS,
1640
1683
  buildDartDefines,
1684
+ resolveDefaultRcKeys,
1641
1685
  writeRouter,
1642
1686
  writeVsCodeLaunch,
1643
1687
  writeEnvExample,
@@ -665,49 +665,123 @@ async function validateFacebookAndroidStrings(projectDir) {
665
665
  }
666
666
 
667
667
  /**
668
- * Validate RevenueCat API keys (RC_IOS_API_KEY, RC_ANDROID_API_KEY) from the project Makefile
669
- * and return the webhook URL derived from kit_setup.json backend config.
668
+ * Read a project `.env` file into a plain object. Returns `{}` if the file is
669
+ * missing. Supports KEY=VALUE lines; ignores comments and blank lines.
670
670
  */
671
- async function validateRevenueCat(projectDir, config = {}) {
672
- const makefilePath = path.join(projectDir, 'Makefile');
673
- if (!(await fs.pathExists(makefilePath))) {
674
- return { ok: true, skipped: true, reason: 'no_makefile' };
675
- }
676
-
671
+ async function readDotenv(projectDir) {
672
+ const envPath = path.join(projectDir, '.env');
673
+ if (!(await fs.pathExists(envPath))) return {};
677
674
  let content;
678
675
  try {
679
- content = await fs.readFile(makefilePath, 'utf8');
680
- } catch (err) {
681
- return { ok: false, error: `Failed to read Makefile: ${err.message}` };
676
+ content = await fs.readFile(envPath, 'utf8');
677
+ } catch {
678
+ return {};
679
+ }
680
+ const env = {};
681
+ for (const rawLine of content.split('\n')) {
682
+ const line = rawLine.trim();
683
+ if (!line || line.startsWith('#')) continue;
684
+ const eq = line.indexOf('=');
685
+ if (eq === -1) continue;
686
+ const key = line.slice(0, eq).trim();
687
+ let value = line.slice(eq + 1).trim();
688
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
689
+ value = value.slice(1, -1);
690
+ }
691
+ if (key) env[key] = value;
682
692
  }
693
+ return env;
694
+ }
683
695
 
684
- // Only consider non-commented lines
685
- const activeLines = content.split('\n').filter((l) => !/^\s*#/.test(l)).join('\n');
686
-
687
- const iosMatch = activeLines.match(/RC_IOS_API_KEY=([^\s\\]+)/);
688
- const androidMatch = activeLines.match(/RC_ANDROID_API_KEY=([^\s\\]+)/);
689
-
690
- const iosKey = iosMatch ? iosMatch[1].trim() : '';
691
- const androidKey = androidMatch ? androidMatch[1].trim() : '';
692
-
693
- const iosEmpty = !iosKey || iosKey === 'xxx';
694
- const androidEmpty = !androidKey || androidKey === 'xxx';
695
-
696
- const iosTest = iosKey.startsWith('test_');
697
- const androidTest = androidKey.startsWith('test_');
696
+ /**
697
+ * Validate RevenueCat keys. The new `.env` is the source of truth:
698
+ * RC_TEST_KEY (test_xxx — simulator/emulator)
699
+ * RC_IOS_PROD_KEY (appl_xxx — physical iOS)
700
+ * RC_ANDROID_PROD_KEY (goog_xxx — physical Android)
701
+ *
702
+ * Legacy fallback: if none of the new vars are set we read the old
703
+ * RC_ANDROID_API_KEY/RC_IOS_API_KEY from `.env`, and finally the Makefile
704
+ * (oldest projects). Returns flags `doctor.js` uses to render granular hints.
705
+ */
706
+ async function validateRevenueCat(projectDir, config = {}) {
707
+ const env = await readDotenv(projectDir);
698
708
 
709
+ // Webhook URL (only Supabase exposes it directly; Firebase points to Console).
699
710
  let webhookUrl = null;
700
711
  if (config.backendProvider === 'supabase' && config.supabaseProjectId) {
701
712
  webhookUrl = `https://${config.supabaseProjectId}.supabase.co/functions/v1/revenuecat-webhook`;
702
713
  }
703
714
 
715
+ const testKey = (env.RC_TEST_KEY || '').trim();
716
+ const iosProdKey = (env.RC_IOS_PROD_KEY || '').trim();
717
+ const androidProdKey = (env.RC_ANDROID_PROD_KEY || '').trim();
718
+ const hasNewKeys = !!(testKey || iosProdKey || androidProdKey);
719
+
720
+ // Legacy single-key path (for projects generated before the test/prod split).
721
+ if (!hasNewKeys) {
722
+ let iosKey = (env.RC_IOS_API_KEY || '').trim();
723
+ let androidKey = (env.RC_ANDROID_API_KEY || '').trim();
724
+
725
+ // Last resort: read from Makefile (oldest projects).
726
+ if (!iosKey && !androidKey) {
727
+ const makefilePath = path.join(projectDir, 'Makefile');
728
+ if (await fs.pathExists(makefilePath)) {
729
+ try {
730
+ const content = await fs.readFile(makefilePath, 'utf8');
731
+ const activeLines = content.split('\n').filter((l) => !/^\s*#/.test(l)).join('\n');
732
+ const iosMatch = activeLines.match(/RC_IOS_API_KEY=([^\s\\]+)/);
733
+ const androidMatch = activeLines.match(/RC_ANDROID_API_KEY=([^\s\\]+)/);
734
+ iosKey = iosMatch ? iosMatch[1].trim() : '';
735
+ androidKey = androidMatch ? androidMatch[1].trim() : '';
736
+ } catch { /* ignore */ }
737
+ }
738
+ }
739
+
740
+ const iosEmpty = !iosKey || iosKey === 'xxx';
741
+ const androidEmpty = !androidKey || androidKey === 'xxx';
742
+ const iosTest = iosKey.startsWith('test_');
743
+ const androidTest = androidKey.startsWith('test_');
744
+
745
+ return {
746
+ ok: !iosEmpty && !androidEmpty,
747
+ legacy: true,
748
+ iosKey,
749
+ androidKey,
750
+ iosEmpty,
751
+ androidEmpty,
752
+ bothTest: iosTest && androidTest,
753
+ webhookUrl,
754
+ };
755
+ }
756
+
757
+ // New 3-key scheme.
758
+ const testOk = !!testKey && /^test_/.test(testKey);
759
+ const testBadPrefix = !!testKey && !/^test_/.test(testKey);
760
+ const iosProdOk = !!iosProdKey && /^appl_/.test(iosProdKey);
761
+ const iosProdBadPrefix = !!iosProdKey && !/^appl_/.test(iosProdKey);
762
+ const androidProdOk = !!androidProdKey && /^goog_/.test(androidProdKey);
763
+ const androidProdBadPrefix = !!androidProdKey && !/^goog_/.test(androidProdKey);
764
+
765
+ // OK if user can run at least one device flow end-to-end. Test alone is fine
766
+ // for development; prod-only configurations also work but skip the simulator.
767
+ const anyOk = testOk || iosProdOk || androidProdOk;
768
+
704
769
  return {
705
- ok: !iosEmpty && !androidEmpty,
706
- iosKey,
707
- androidKey,
708
- iosEmpty,
709
- androidEmpty,
710
- bothTest: iosTest && androidTest,
770
+ ok: anyOk,
771
+ legacy: false,
772
+ testKey,
773
+ iosProdKey,
774
+ androidProdKey,
775
+ testOk,
776
+ testBadPrefix,
777
+ iosProdOk,
778
+ iosProdBadPrefix,
779
+ androidProdOk,
780
+ androidProdBadPrefix,
781
+ testMissing: !testKey,
782
+ iosProdMissing: !iosProdKey,
783
+ androidProdMissing: !androidProdKey,
784
+ onlyTest: testOk && !iosProdOk && !androidProdOk,
711
785
  webhookUrl,
712
786
  };
713
787
  }
@@ -33,6 +33,8 @@ const TEMPLATE_STRINGS = {
33
33
  supabase: '# Supabase',
34
34
  apiRest: '# API REST',
35
35
  revenuecat: '# RevenueCat',
36
+ revenuecatTest: '# Test Store key (test_xxx) — auto-used on simulator/emulator',
37
+ revenuecatProd: '# Production keys — auto-used on physical devices',
36
38
  sentry: '# Sentry (prod only)',
37
39
  mixpanel: '# Mixpanel',
38
40
  llmChat: '# LLM Chat — Cloud/Edge Function endpoint (API key stays on the server, not here)',
@@ -65,6 +67,8 @@ const TEMPLATE_STRINGS = {
65
67
  supabase: '# Supabase',
66
68
  apiRest: '# API REST',
67
69
  revenuecat: '# RevenueCat',
70
+ revenuecatTest: '# Chave Test Store (test_xxx) — usada automaticamente em simulador/emulador',
71
+ revenuecatProd: '# Chaves de produção — usadas automaticamente em dispositivo físico',
68
72
  sentry: '# Sentry (apenas prod)',
69
73
  mixpanel: '# Mixpanel',
70
74
  llmChat: '# LLM Chat — endpoint da Cloud/Edge Function (a chave de API fica no servidor, não aqui)',
@@ -97,6 +101,8 @@ const TEMPLATE_STRINGS = {
97
101
  supabase: '# Supabase',
98
102
  apiRest: '# API REST',
99
103
  revenuecat: '# RevenueCat',
104
+ revenuecatTest: '# Clave Test Store (test_xxx) — se usa automáticamente en simulador/emulador',
105
+ revenuecatProd: '# Claves de producción — se usan automáticamente en dispositivo físico',
100
106
  sentry: '# Sentry (solo prod)',
101
107
  mixpanel: '# Mixpanel',
102
108
  llmChat: '# LLM Chat — endpoint de Cloud/Edge Function (la clave de API queda en el servidor, no aquí)',
@@ -419,8 +419,15 @@ module.exports = {
419
419
 
420
420
  'doctor.revenuecat.title': 'RevenueCat',
421
421
  'doctor.revenuecat.keysOk': 'API keys configured (iOS + Android)',
422
- 'doctor.revenuecat.keysEmpty': 'API keys not configured — set RC_IOS_API_KEY and RC_ANDROID_API_KEY in Makefile and .vscode/launch.json',
423
- 'doctor.revenuecat.keysTest': 'Using Test Store keys (test_) — replace with appl_ and goog_ for production',
422
+ 'doctor.revenuecat.keysEmpty': 'No keys configured — set at least RC_TEST_KEY in .env (kasy run uses it on simulator/emulator)',
423
+ 'doctor.revenuecat.testKeyOk': 'RC_TEST_KEY configured (test_) — used on simulator/emulator',
424
+ 'doctor.revenuecat.testKeyMissing': 'RC_TEST_KEY missing — subscription flow will not work on simulator/emulator',
425
+ 'doctor.revenuecat.iosProdOk': 'RC_IOS_PROD_KEY configured (appl_) — used on physical iPhone',
426
+ 'doctor.revenuecat.iosProdMissing': 'RC_IOS_PROD_KEY missing — kasy run on physical iPhone will fall back to the test key',
427
+ 'doctor.revenuecat.androidProdOk': 'RC_ANDROID_PROD_KEY configured (goog_) — used on physical Android',
428
+ 'doctor.revenuecat.androidProdMissing': 'RC_ANDROID_PROD_KEY missing — kasy run on physical Android will fall back to the test key',
429
+ 'doctor.revenuecat.prefixMismatch': 'Key has wrong prefix: {key} should start with {expected}',
430
+ 'doctor.revenuecat.keysTest': 'Only Test Store keys (test_) configured — store releases require appl_/goog_, otherwise the app crashes',
424
431
  'doctor.revenuecat.webhookUrlSupabase': 'Webhook URL (paste in RevenueCat → Integrations → Webhooks)',
425
432
  'doctor.revenuecat.webhookUrlFirebase': 'Webhook URL: Firebase Console → Functions → subscriptionsOnRcPremiumUpdate',
426
433
 
@@ -488,6 +495,14 @@ module.exports = {
488
495
  'new.firebase.q.revenuecat.webhookKey.hint': 'Save this value. In RevenueCat dashboard, paste as: Bearer <this-value>',
489
496
  'new.firebase.q.revenuecat.metaToken': 'Meta Access Token (for Ads Conversions API, optional)',
490
497
  'new.firebase.q.revenuecat.metaDataset': 'Meta Dataset ID / Pixel ID (optional)',
498
+ 'new.firebase.q.revenuecat.test': 'Test Store key (test_xxx) — optional, works for both iOS+Android and runs in the simulator',
499
+ 'new.firebase.q.revenuecat.test.invalid': 'Test Store key must start with test_ (e.g. test_xxxxxxxxxxxxxxxxxxxx).',
500
+ 'new.firebase.q.revenuecat.iosProd': 'iOS production key (appl_xxx) — optional, used only on physical iPhone',
501
+ 'new.firebase.q.revenuecat.iosProd.invalid': 'iOS production key must start with appl_ (e.g. appl_xxxxxxxxxxxxxxx).',
502
+ 'new.firebase.q.revenuecat.androidProd': 'Android production key (goog_xxx) — optional, used only on physical Android',
503
+ 'new.firebase.q.revenuecat.androidProd.invalid': 'Android production key must start with goog_ (e.g. goog_xxxxxxxxxxxxxxx).',
504
+ 'new.firebase.q.revenuecat.atLeastOne': 'Configure at least one key (Test, iOS prod or Android prod). You can add the others later in .env.',
505
+ // Legacy keys — kept for projects/scripts that still reference them.
491
506
  'new.firebase.q.revenuecat.android': 'RevenueCat API key for Android',
492
507
  'new.firebase.q.revenuecat.ios': 'RevenueCat API key for iOS',
493
508
  'new.firebase.q.paywall': 'Which paywall style?',
@@ -645,6 +660,8 @@ module.exports = {
645
660
  // run command
646
661
  'cli.command.run.description': 'Run your app on phone, simulator, or browser',
647
662
  'run.launching': 'Launching Flutter app...',
663
+ 'run.prompt.pickDevice': 'Multiple devices detected. Which one do you want to run on?',
664
+ 'run.warn.nothingSelected': 'No device selected.',
648
665
  'run.updateHint.prefix': 'Project improvements available —',
649
666
  'run.updateHint.suffix': 'to see what\'s new',
650
667
  'run.spinner.building': 'Starting Flutter…',
@@ -659,6 +676,12 @@ module.exports = {
659
676
  'run.stage.buildSuccess': 'Build done — launching app…',
660
677
  'run.error.notFlutterProject': 'No pubspec.yaml found. Run this command from inside a Flutter project.',
661
678
  'run.error.flutterNotFound': 'Flutter not found. Make sure Flutter is installed and on your PATH.',
679
+ 'run.rc.usingTest': 'RevenueCat: using test key (test_) — simulator/emulator',
680
+ 'run.rc.usingProd': 'RevenueCat: using production keys — physical device',
681
+ 'run.rc.fallbackToTest': 'RevenueCat: production key missing ({platform}) — falling back to test_; set RC_{var} in .env to test real in-app purchases',
682
+ 'run.rc.forcedTest': 'RevenueCat: --rc=test forced',
683
+ 'run.rc.forcedProd': 'RevenueCat: --rc=prod forced',
684
+ 'run.rc.forcedProdMissing': 'RevenueCat: --rc=prod requested but RC_IOS_PROD_KEY/RC_ANDROID_PROD_KEY not set in .env',
662
685
 
663
686
  // reset command
664
687
  'cli.command.reset.description': 'Uninstall the app on a simulator/emulator/device so you can test as a fresh install',
@@ -746,6 +769,11 @@ module.exports = {
746
769
  'icon.validated': 'PNG looks good',
747
770
  'icon.copying': 'Copying icon to assets/images/icon.png...',
748
771
  'icon.copied': 'Icon copied',
772
+ 'icon.adaptive.generating': 'Generating Android adaptive variant (fills the launcher circle)...',
773
+ 'icon.adaptive.generated': 'Android adaptive icon generated',
774
+ 'icon.adaptive.failed': 'Failed to generate Android adaptive icon',
775
+ 'icon.adaptive.fallbackTitle': 'Android background',
776
+ 'icon.adaptive.fallbackColor': 'Could not sample a background color from the PNG (transparent). Used white. Edit icon_android.png if you want a different color.',
749
777
  'icon.generating': 'Regenerating native icons (Android + iOS)...',
750
778
  'icon.generated': 'Native icons regenerated',
751
779
  'icon.done': 'Done. Uninstall the app from the device and reinstall to see the new icon.',
@@ -833,6 +861,10 @@ module.exports = {
833
861
  'add.cancelled': 'Cancelled.',
834
862
  'add.prompt.sentryDsn': 'Sentry DSN (leave blank to configure later):',
835
863
  'add.prompt.mixpanelToken': 'Mixpanel Token (leave blank to configure later):',
864
+ 'add.prompt.rcTestKey': 'Test Store key (test_xxx) — optional, works for both iOS+Android and on simulator:',
865
+ 'add.prompt.rcIosProdKey': 'iOS production key (appl_xxx) — optional, only on physical device:',
866
+ 'add.prompt.rcAndroidProdKey': 'Android production key (goog_xxx) — optional, only on physical device:',
867
+ // Legacy keys — kept for compatibility with old scripts.
836
868
  'add.prompt.rcAndroidKey': 'RevenueCat Android API key (leave blank to configure later):',
837
869
  'add.prompt.rcIosKey': 'RevenueCat iOS API key (leave blank to configure later):',
838
870
  'add.note.facebook': 'Add your Facebook App ID and token in .vscode/launch.json (FB_APP_ID, FB_TOKEN).',
@@ -421,8 +421,15 @@ module.exports = {
421
421
 
422
422
  'doctor.revenuecat.title': 'RevenueCat',
423
423
  'doctor.revenuecat.keysOk': 'Claves de API configuradas (iOS + Android)',
424
- 'doctor.revenuecat.keysEmpty': 'Claves de API no configuradas — define RC_IOS_API_KEY y RC_ANDROID_API_KEY en Makefile y .vscode/launch.json',
425
- 'doctor.revenuecat.keysTest': 'Usando claves Test Store (test_) — reemplaza por appl_ y goog_ para producción',
424
+ 'doctor.revenuecat.keysEmpty': 'Ninguna clave configurada — define al menos RC_TEST_KEY en .env (kasy run la usa en simulador/emulador)',
425
+ 'doctor.revenuecat.testKeyOk': 'RC_TEST_KEY configurada (test_) — usada en simulador/emulador',
426
+ 'doctor.revenuecat.testKeyMissing': 'RC_TEST_KEY ausente — el flujo de suscripción no funciona en simulador/emulador',
427
+ 'doctor.revenuecat.iosProdOk': 'RC_IOS_PROD_KEY configurada (appl_) — usada en iPhone físico',
428
+ 'doctor.revenuecat.iosProdMissing': 'RC_IOS_PROD_KEY ausente — kasy run en iPhone físico va a usar la clave de prueba',
429
+ 'doctor.revenuecat.androidProdOk': 'RC_ANDROID_PROD_KEY configurada (goog_) — usada en Android físico',
430
+ 'doctor.revenuecat.androidProdMissing': 'RC_ANDROID_PROD_KEY ausente — kasy run en Android físico va a usar la clave de prueba',
431
+ 'doctor.revenuecat.prefixMismatch': 'Clave con prefijo incorrecto: {key} debe empezar con {expected}',
432
+ 'doctor.revenuecat.keysTest': 'Solo claves Test Store (test_) configuradas — los releases en la tienda requieren appl_/goog_, sino el app crashea',
426
433
  'doctor.revenuecat.webhookUrlSupabase': 'URL del webhook (pega en RevenueCat → Integrations → Webhooks)',
427
434
  'doctor.revenuecat.webhookUrlFirebase': 'URL del webhook: Firebase Console → Functions → subscriptionsOnRcPremiumUpdate',
428
435
 
@@ -488,6 +495,14 @@ module.exports = {
488
495
  'new.firebase.q.revenuecat.webhookKey.hint': 'Guarda este valor. En el panel RevenueCat, pega como: Bearer <este-valor>',
489
496
  'new.firebase.q.revenuecat.metaToken': 'Meta Access Token (Conversions API, opcional)',
490
497
  'new.firebase.q.revenuecat.metaDataset': 'Meta Dataset ID / Pixel ID (opcional)',
498
+ 'new.firebase.q.revenuecat.test': 'Clave Test Store (test_xxx) — opcional, sirve para iOS+Android y funciona en simulador',
499
+ 'new.firebase.q.revenuecat.test.invalid': 'Clave Test Store debe empezar con test_ (ej: test_xxxxxxxxxxxxxxxxxxxx).',
500
+ 'new.firebase.q.revenuecat.iosProd': 'Clave iOS de producción (appl_xxx) — opcional, se usa solo en dispositivo físico',
501
+ 'new.firebase.q.revenuecat.iosProd.invalid': 'Clave iOS de producción debe empezar con appl_ (ej: appl_xxxxxxxxxxxxxxx).',
502
+ 'new.firebase.q.revenuecat.androidProd': 'Clave Android de producción (goog_xxx) — opcional, se usa solo en dispositivo físico',
503
+ 'new.firebase.q.revenuecat.androidProd.invalid': 'Clave Android de producción debe empezar con goog_ (ej: goog_xxxxxxxxxxxxxxx).',
504
+ 'new.firebase.q.revenuecat.atLeastOne': 'Configura al menos una clave (Test, iOS prod o Android prod). Puedes agregar las demás después en .env.',
505
+ // Legacy keys — kept for projects/scripts that still reference them.
491
506
  'new.firebase.q.revenuecat.android': 'Clave API RevenueCat para Android',
492
507
  'new.firebase.q.revenuecat.ios': 'Clave API RevenueCat para iOS',
493
508
  'new.firebase.q.paywall': '¿Qué estilo de paywall?',
@@ -678,6 +693,8 @@ module.exports = {
678
693
  'reset.warn.launcherCacheFailed': 'No se pudo limpiar la caché del launcher.',
679
694
  'reset.warn.launcherNotDetected': 'Launcher por defecto no detectado — saltando limpieza de caché.',
680
695
  'run.launching': 'Iniciando app Flutter...',
696
+ 'run.prompt.pickDevice': 'Varios dispositivos detectados. ¿En cuál quieres ejecutar?',
697
+ 'run.warn.nothingSelected': 'Ningún dispositivo seleccionado.',
681
698
  'run.updateHint.prefix': 'Mejoras disponibles para el proyecto —',
682
699
  'run.updateHint.suffix': 'para ver las novedades',
683
700
  'run.spinner.building': 'Iniciando Flutter…',
@@ -692,6 +709,12 @@ module.exports = {
692
709
  'run.stage.buildSuccess': 'Build listo — abriendo la app…',
693
710
  'run.error.notFlutterProject': 'No se encontro pubspec.yaml. Ejecuta este comando dentro de un proyecto Flutter.',
694
711
  'run.error.flutterNotFound': 'Flutter no encontrado. Verifica que Flutter este instalado y en el PATH.',
712
+ 'run.rc.usingTest': 'RevenueCat: usando clave de prueba (test_) — simulador/emulador',
713
+ 'run.rc.usingProd': 'RevenueCat: usando claves de producción — dispositivo físico',
714
+ 'run.rc.fallbackToTest': 'RevenueCat: clave de producción ausente ({platform}) — usando test_; configura RC_{var} en .env para probar in-app purchase real',
715
+ 'run.rc.forcedTest': 'RevenueCat: --rc=test forzado',
716
+ 'run.rc.forcedProd': 'RevenueCat: --rc=prod forzado',
717
+ 'run.rc.forcedProdMissing': 'RevenueCat: --rc=prod pedido pero RC_IOS_PROD_KEY/RC_ANDROID_PROD_KEY no configuradas en .env',
695
718
 
696
719
  // doctor project checks
697
720
  'doctor.project.title': 'Proyecto',
@@ -744,6 +767,11 @@ module.exports = {
744
767
  'icon.validated': 'PNG está bien',
745
768
  'icon.copying': 'Copiando ícono a assets/images/icon.png...',
746
769
  'icon.copied': 'Ícono copiado',
770
+ 'icon.adaptive.generating': 'Generando variante adaptive para Android (llena el círculo del launcher)...',
771
+ 'icon.adaptive.generated': 'Adaptive icon Android generado',
772
+ 'icon.adaptive.failed': 'Fallo al generar adaptive icon Android',
773
+ 'icon.adaptive.fallbackTitle': 'Fondo Android',
774
+ 'icon.adaptive.fallbackColor': 'No pude muestrear el color de fondo del PNG (transparente). Usé blanco. Edita icon_android.png si quieres otro color.',
747
775
  'icon.generating': 'Regenerando íconos nativos (Android + iOS)...',
748
776
  'icon.generated': 'Íconos nativos regenerados',
749
777
  'icon.done': 'Listo. Desinstala el app del dispositivo y reinstala para ver el nuevo ícono.',
@@ -831,6 +859,10 @@ module.exports = {
831
859
  'add.cancelled': 'Cancelado.',
832
860
  'add.prompt.sentryDsn': 'Sentry DSN (deja en blanco para configurar después):',
833
861
  'add.prompt.mixpanelToken': 'Mixpanel Token (deja en blanco para configurar después):',
862
+ 'add.prompt.rcTestKey': 'Clave Test Store (test_xxx) — opcional, sirve para iOS+Android y simulador:',
863
+ 'add.prompt.rcIosProdKey': 'Clave iOS de producción (appl_xxx) — opcional, solo en dispositivo físico:',
864
+ 'add.prompt.rcAndroidProdKey': 'Clave Android de producción (goog_xxx) — opcional, solo en dispositivo físico:',
865
+ // Legacy keys — kept for compatibility with old scripts.
834
866
  'add.prompt.rcAndroidKey': 'RevenueCat Android API key (deja en blanco para configurar después):',
835
867
  'add.prompt.rcIosKey': 'RevenueCat iOS API key (deja en blanco para configurar después):',
836
868
  'add.note.facebook': 'Agrega tu Facebook App ID y token en .vscode/launch.json (FB_APP_ID, FB_TOKEN).',
@@ -419,8 +419,15 @@ module.exports = {
419
419
 
420
420
  'doctor.revenuecat.title': 'RevenueCat',
421
421
  'doctor.revenuecat.keysOk': 'Chaves de API configuradas (iOS + Android)',
422
- 'doctor.revenuecat.keysEmpty': 'Chaves de API não configuradas — defina RC_IOS_API_KEY e RC_ANDROID_API_KEY no Makefile e .vscode/launch.json',
423
- 'doctor.revenuecat.keysTest': 'Usando chaves Test Store (test_) — substitua por appl_ e goog_ para produção',
422
+ 'doctor.revenuecat.keysEmpty': 'Nenhuma chave configurada — defina pelo menos RC_TEST_KEY no .env (kasy run usa em simulador/emulador)',
423
+ 'doctor.revenuecat.testKeyOk': 'RC_TEST_KEY configurada (test_) — usada em simulador/emulador',
424
+ 'doctor.revenuecat.testKeyMissing': 'RC_TEST_KEY ausente — fluxo de assinatura não funciona em simulador/emulador',
425
+ 'doctor.revenuecat.iosProdOk': 'RC_IOS_PROD_KEY configurada (appl_) — usada em iPhone físico',
426
+ 'doctor.revenuecat.iosProdMissing': 'RC_IOS_PROD_KEY ausente — kasy run em iPhone físico vai usar a chave de teste',
427
+ 'doctor.revenuecat.androidProdOk': 'RC_ANDROID_PROD_KEY configurada (goog_) — usada em Android físico',
428
+ 'doctor.revenuecat.androidProdMissing': 'RC_ANDROID_PROD_KEY ausente — kasy run em Android físico vai usar a chave de teste',
429
+ 'doctor.revenuecat.prefixMismatch': 'Chave com prefixo errado: {key} deveria começar com {expected}',
430
+ 'doctor.revenuecat.keysTest': 'Apenas chaves Test Store (test_) configuradas — releases na loja exigem appl_/goog_, senão o app crasha',
424
431
  'doctor.revenuecat.webhookUrlSupabase': 'URL do webhook (cole no RevenueCat → Integrations → Webhooks)',
425
432
  'doctor.revenuecat.webhookUrlFirebase': 'URL do webhook: Firebase Console → Functions → subscriptionsOnRcPremiumUpdate',
426
433
 
@@ -488,6 +495,14 @@ module.exports = {
488
495
  'new.firebase.q.revenuecat.webhookKey.hint': 'Salve esse valor. No painel RevenueCat, cole como: Bearer <esse-valor>',
489
496
  'new.firebase.q.revenuecat.metaToken': 'Meta Access Token (Conversions API, opcional)',
490
497
  'new.firebase.q.revenuecat.metaDataset': 'Meta Dataset ID / Pixel ID (opcional)',
498
+ 'new.firebase.q.revenuecat.test': 'Chave Test Store (test_xxx) — opcional, serve pra iOS+Android e funciona em simulador',
499
+ 'new.firebase.q.revenuecat.test.invalid': 'Chave Test Store deve começar com test_ (ex: test_xxxxxxxxxxxxxxxxxxxx).',
500
+ 'new.firebase.q.revenuecat.iosProd': 'Chave iOS de produção (appl_xxx) — opcional, usada só em dispositivo físico',
501
+ 'new.firebase.q.revenuecat.iosProd.invalid': 'Chave iOS de produção deve começar com appl_ (ex: appl_xxxxxxxxxxxxxxx).',
502
+ 'new.firebase.q.revenuecat.androidProd': 'Chave Android de produção (goog_xxx) — opcional, usada só em dispositivo físico',
503
+ 'new.firebase.q.revenuecat.androidProd.invalid': 'Chave Android de produção deve começar com goog_ (ex: goog_xxxxxxxxxxxxxxx).',
504
+ 'new.firebase.q.revenuecat.atLeastOne': 'Configure pelo menos uma chave (Test, iOS prod ou Android prod). Você pode adicionar as outras depois no .env.',
505
+ // Legacy keys — kept for projects/scripts that still reference them.
491
506
  'new.firebase.q.revenuecat.android': 'Chave de API RevenueCat para Android',
492
507
  'new.firebase.q.revenuecat.ios': 'Chave de API RevenueCat para iOS',
493
508
  'new.firebase.q.paywall': 'Qual estilo de paywall?',
@@ -678,6 +693,8 @@ module.exports = {
678
693
  'reset.warn.launcherCacheFailed': 'Não foi possível limpar o cache do launcher.',
679
694
  'reset.warn.launcherNotDetected': 'Launcher padrão não detectado — pulando limpeza de cache.',
680
695
  'run.launching': 'Iniciando app Flutter...',
696
+ 'run.prompt.pickDevice': 'Vários dispositivos detectados. Em qual deles rodar?',
697
+ 'run.warn.nothingSelected': 'Nenhum dispositivo selecionado.',
681
698
  'run.updateHint.prefix': 'Melhorias disponíveis para o projeto —',
682
699
  'run.updateHint.suffix': 'para ver o que há de novo',
683
700
  'run.spinner.building': 'Iniciando Flutter…',
@@ -692,6 +709,12 @@ module.exports = {
692
709
  'run.stage.buildSuccess': 'Build pronto — abrindo o app…',
693
710
  'run.error.notFlutterProject': 'Nenhum pubspec.yaml encontrado. Execute este comando dentro de um projeto Flutter.',
694
711
  'run.error.flutterNotFound': 'Flutter não encontrado. Verifique se o Flutter está instalado e no PATH.',
712
+ 'run.rc.usingTest': 'RevenueCat: usando chave de teste (test_) — simulador/emulador',
713
+ 'run.rc.usingProd': 'RevenueCat: usando chaves de produção — dispositivo físico',
714
+ 'run.rc.fallbackToTest': 'RevenueCat: chave de produção ausente ({platform}) — caindo para test_; configure RC_{var} no .env para testar in-app purchase real',
715
+ 'run.rc.forcedTest': 'RevenueCat: --rc=test forçado',
716
+ 'run.rc.forcedProd': 'RevenueCat: --rc=prod forçado',
717
+ 'run.rc.forcedProdMissing': 'RevenueCat: --rc=prod pedido mas RC_IOS_PROD_KEY/RC_ANDROID_PROD_KEY não configuradas no .env',
695
718
 
696
719
  // doctor project checks
697
720
  'doctor.project.title': 'Projeto',
@@ -744,6 +767,11 @@ module.exports = {
744
767
  'icon.validated': 'PNG está bom',
745
768
  'icon.copying': 'Copiando ícone para assets/images/icon.png...',
746
769
  'icon.copied': 'Ícone copiado',
770
+ 'icon.adaptive.generating': 'Gerando versão adaptive para Android (preenche o círculo do launcher)...',
771
+ 'icon.adaptive.generated': 'Adaptive icon Android gerado',
772
+ 'icon.adaptive.failed': 'Falha ao gerar adaptive icon Android',
773
+ 'icon.adaptive.fallbackTitle': 'Fundo Android',
774
+ 'icon.adaptive.fallbackColor': 'Não consegui amostrar a cor de fundo do PNG (transparente). Usei branco. Se quiser outra cor, edite icon_android.png à mão.',
747
775
  'icon.generating': 'Regenerando ícones nativos (Android + iOS)...',
748
776
  'icon.generated': 'Ícones nativos regenerados',
749
777
  'icon.done': 'Pronto. Desinstale o app do dispositivo e reinstale para ver o novo ícone.',
@@ -831,6 +859,10 @@ module.exports = {
831
859
  'add.cancelled': 'Cancelado.',
832
860
  'add.prompt.sentryDsn': 'Sentry DSN (deixe em branco para configurar depois):',
833
861
  'add.prompt.mixpanelToken': 'Mixpanel Token (deixe em branco para configurar depois):',
862
+ 'add.prompt.rcTestKey': 'Chave Test Store (test_xxx) — opcional, serve pra iOS+Android e simulador:',
863
+ 'add.prompt.rcIosProdKey': 'Chave iOS de produção (appl_xxx) — opcional, só em dispositivo físico:',
864
+ 'add.prompt.rcAndroidProdKey': 'Chave Android de produção (goog_xxx) — opcional, só em dispositivo físico:',
865
+ // Legacy keys — kept for compatibility with old scripts.
834
866
  'add.prompt.rcAndroidKey': 'RevenueCat Android API key (deixe em branco para configurar depois):',
835
867
  'add.prompt.rcIosKey': 'RevenueCat iOS API key (deixe em branco para configurar depois):',
836
868
  'add.note.facebook': 'Adicione seu Facebook App ID e token no .vscode/launch.json (FB_APP_ID, FB_TOKEN).',