kasy-cli 1.16.0 → 1.18.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 (99) hide show
  1. package/bin/kasy.js +16 -2
  2. package/lib/commands/add.js +52 -19
  3. package/lib/commands/configure.js +548 -0
  4. package/lib/commands/deploy.js +4 -4
  5. package/lib/commands/doctor.js +54 -6
  6. package/lib/commands/favicon.js +4 -4
  7. package/lib/commands/icon.js +5 -5
  8. package/lib/commands/new.js +404 -213
  9. package/lib/commands/remove.js +14 -3
  10. package/lib/commands/run.js +208 -6
  11. package/lib/commands/splash.js +5 -5
  12. package/lib/commands/update.js +9 -9
  13. package/lib/scaffold/CHANGELOG.json +23 -0
  14. package/lib/scaffold/backends/api/patch/README.md +3 -2
  15. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
  16. package/lib/scaffold/backends/firebase/setup-from-scratch.js +44 -5
  17. package/lib/scaffold/backends/supabase/patch/README.md +3 -2
  18. package/lib/scaffold/generate.js +24 -8
  19. package/lib/scaffold/shared/generator-utils.js +52 -8
  20. package/lib/scaffold/shared/post-build.js +113 -31
  21. package/lib/scaffold/shared/template-strings.js +6 -0
  22. package/lib/utils/brand.js +16 -12
  23. package/lib/utils/flutter-run.js +139 -11
  24. package/lib/utils/i18n/messages-en.js +85 -7
  25. package/lib/utils/i18n/messages-es.js +85 -7
  26. package/lib/utils/i18n/messages-pt.js +86 -8
  27. package/lib/utils/ui.js +79 -4
  28. package/package.json +1 -1
  29. package/templates/firebase/README.en.md +18 -8
  30. package/templates/firebase/README.es.md +18 -8
  31. package/templates/firebase/README.md +18 -8
  32. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +68 -45
  33. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +37 -0
  34. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/OpenAppAction.kt +26 -0
  35. package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
  36. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
  37. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
  38. package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
  39. package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
  40. package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
  41. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  42. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  43. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  44. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  45. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  46. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
  47. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  48. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
  49. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  50. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
  51. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  52. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
  53. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  54. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
  55. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  56. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  57. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  58. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  59. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  60. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  61. package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
  62. package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
  63. package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
  64. package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
  65. package/templates/firebase/assets/images/splash_logo_light.png +0 -0
  66. package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
  67. package/templates/firebase/docs/revenuecat-setup.es.md +28 -8
  68. package/templates/firebase/docs/revenuecat-setup.pt.md +28 -8
  69. package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
  70. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  71. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  72. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  73. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
  74. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
  75. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
  76. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
  77. package/templates/firebase/lib/components/components.dart +1 -0
  78. package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
  79. package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
  80. package/templates/firebase/lib/components/kasy_button.dart +8 -8
  81. package/templates/firebase/lib/components/kasy_date_picker.dart +834 -0
  82. package/templates/firebase/lib/components/kasy_tabs.dart +145 -61
  83. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +45 -53
  84. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +565 -77
  85. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
  86. package/templates/firebase/lib/i18n/en.i18n.json +2 -1
  87. package/templates/firebase/lib/i18n/es.i18n.json +2 -1
  88. package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
  89. package/templates/firebase/lib/router.dart +15 -1
  90. package/templates/firebase/pubspec.yaml +1 -1
  91. package/templates/firebase/web/index.html +9 -0
  92. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  93. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  94. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  95. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  96. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  97. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  98. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  99. package/templates/firebase/web/splash/img/light-4x.png +0 -0
package/bin/kasy.js CHANGED
@@ -8,6 +8,7 @@ const { runDoctor } = require('../lib/commands/doctor');
8
8
  const { runFeatures } = require('../lib/commands/features');
9
9
  const { runValidate } = require('../lib/commands/validate');
10
10
  const { runDeployCommand } = require('../lib/commands/deploy');
11
+ const { runConfigure } = require('../lib/commands/configure');
11
12
  const { runCheck } = require('../lib/commands/check');
12
13
  const { runRun } = require('../lib/commands/run');
13
14
  const { runReset } = require('../lib/commands/reset');
@@ -137,7 +138,7 @@ function createLocalizedHelpConfig(t) {
137
138
  // Group root commands by intent for easier scanning by non-devs.
138
139
  const groups = [
139
140
  { id: 'start', ids: ['new', 'doctor', 'features'] },
140
- { id: 'work', ids: ['add', 'remove', 'update', 'run', 'reset', 'splash', 'icon', 'favicon', 'notifications'] },
141
+ { id: 'work', ids: ['add', 'configure', 'remove', 'update', 'run', 'reset', 'splash', 'icon', 'favicon', 'notifications'] },
141
142
  { id: 'publish', ids: ['deploy', 'check', 'ios', 'codemagic'] },
142
143
  { id: 'maintenance', ids: ['upgrade', 'version', 'uninstall', 'docs'] },
143
144
  { id: 'advanced', ids: ['setup', 'validate'] },
@@ -240,7 +241,7 @@ function buildProgram(language) {
240
241
  .argument('[directory]', 'Target folder (default: asks during setup)', '.')
241
242
  .option('-b, --backend <backend>', t('cli.command.setup.backendOption'))
242
243
  .option('--with <features>', t('cli.command.setup.featuresOption'))
243
- .option('--yes', 'Skip interactive questions Quick mode with Starter preset')
244
+ .option('--yes', 'Skip interactive questions (Quick mode, all features)')
244
245
  .option('-p, --project <id>', 'Firebase Project ID (used with --yes)')
245
246
  .description(t('cli.command.new.description'))
246
247
  .action(async (directory, options) => {
@@ -324,6 +325,17 @@ function buildProgram(language) {
324
325
  t
325
326
  );
326
327
 
328
+ applyLocalizedHelp(
329
+ program
330
+ .command('configure')
331
+ .argument('[directory]', 'Project folder (default: current directory)', '.')
332
+ .description(t('cli.command.configure.description'))
333
+ .action(async (directory) => {
334
+ await runConfigure({ directory, language });
335
+ }),
336
+ t
337
+ );
338
+
327
339
  applyLocalizedHelp(
328
340
  program
329
341
  .command('check')
@@ -346,6 +358,8 @@ function buildProgram(language) {
346
358
  .option('-d, --device <id>', 'Run on specific device ID')
347
359
  .option('--prod', 'Use production dart-defines (from launch.json)')
348
360
  .option('--no-defines', 'Skip dart-defines from launch.json')
361
+ .option('--rc <mode>', 'RevenueCat key mode: auto (default — picks by device), test, or prod')
362
+ .option('--raw', 'Disable spinner and pass Flutter output straight through (auto-on when stdout is not a TTY)')
349
363
  .description(t('cli.command.run.description'))
350
364
  .action(async (directory, options) => {
351
365
  await runRun(directory, { language, ...options });
@@ -6,7 +6,7 @@ const fs = require('fs-extra');
6
6
  const pkg = require('../../package.json');
7
7
  const kleur = require('kleur');
8
8
  const ui = require('../utils/ui');
9
- const { printCompactHeader } = require('../utils/brand');
9
+ const { printCompactHeader, paintLime } = require('../utils/brand');
10
10
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
11
11
  const {
12
12
  AVAILABLE_FEATURES,
@@ -24,7 +24,7 @@ function findBaseDisplayName(id) {
24
24
 
25
25
  const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
26
26
  const { applyPatch } = require('../scaffold/engine');
27
- const { writeRouter, writeNoOpAnalyticsApi, writeNoOpTrackingApi, writeNoOpAdminHomeWidgets, writeNoOpFeatureRequestRepository, writeMainDart, addPubspecDep, localizeReleaseDocs } = require('../scaffold/shared/generator-utils');
27
+ const { writeRouter, writeNoOpAnalyticsApi, writeNoOpTrackingApi, writeNoOpAdminHomeWidgets, writeNoOpFeatureRequestRepository, writeMainDart, addPubspecDep, localizeReleaseDocs, resolveDefaultRcKeys } = require('../scaffold/shared/generator-utils');
28
28
  const { toPackageName, buildTokens } = require('../scaffold/backends/firebase/tokens');
29
29
 
30
30
  const execAsync = promisify(exec);
@@ -217,6 +217,14 @@ function applyKitSetupFlag(config, module, answers) {
217
217
  case 'revenuecat':
218
218
  config.subscriptionModule = true;
219
219
  if (answers.revenuecatWeb) config.revenuecatWeb = true;
220
+ // Track which RC keys the user configured so `kasy doctor` can warn
221
+ // about release readiness without re-reading .env. Booleans only — we
222
+ // never persist the key values themselves to kit_setup.json.
223
+ config.revenuecatKeys = {
224
+ test: !!(answers.rcTestKey && answers.rcTestKey.trim()),
225
+ iosProd: !!(answers.rcIosProdKey && answers.rcIosProdKey.trim()),
226
+ androidProd: !!(answers.rcAndroidProdKey && answers.rcAndroidProdKey.trim()),
227
+ };
220
228
  break;
221
229
  case 'onboarding':
222
230
  config.withOnboarding = true;
@@ -263,14 +271,18 @@ const MODULE_META = {
263
271
  pubspecDeps: { 'facebook_app_events': '^0.24.0', 'flutter_facebook_auth': '^7.1.5' },
264
272
  },
265
273
  revenuecat: {
266
- promptKeys: ['rcAndroidKey', 'rcIosKey'],
267
- defineUpdates: (a) => ({
268
- RC_ANDROID_API_KEY: a.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY',
269
- RC_IOS_API_KEY: a.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY',
270
- }),
274
+ promptKeys: ['rcTestKey', 'rcIosProdKey', 'rcAndroidProdKey'],
275
+ defineUpdates: (a) => {
276
+ const { android, ios } = resolveDefaultRcKeys(a);
277
+ return {
278
+ RC_ANDROID_API_KEY: android,
279
+ RC_IOS_API_KEY: ios,
280
+ };
281
+ },
271
282
  envLines: (a) => [
272
- `RC_ANDROID_API_KEY=${a.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY'}`,
273
- `RC_IOS_API_KEY=${a.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY'}`,
283
+ `RC_TEST_KEY=${(a.rcTestKey || '').trim()}`,
284
+ `RC_IOS_PROD_KEY=${(a.rcIosProdKey || '').trim()}`,
285
+ `RC_ANDROID_PROD_KEY=${(a.rcAndroidProdKey || '').trim()}`,
274
286
  ],
275
287
  featureFlag: null,
276
288
  pubspecDeps: {},
@@ -337,12 +349,33 @@ const PROMPT_QUESTIONS = {
337
349
  onCancel: cancel,
338
350
  }),
339
351
  // RevenueCat SDK keys ship inside the client binary — not real secrets, plain text input.
340
- rcAndroidKey: (t, cancel) => ui.text({
341
- message: t('add.prompt.rcAndroidKey'),
352
+ // Three optional keys: test_ (covers iOS+Android in simulator), appl_ (iOS prod), goog_ (Android prod).
353
+ // kasy run picks the right one based on the device at launch time.
354
+ rcTestKey: (t, cancel) => ui.text({
355
+ message: t('add.prompt.rcTestKey'),
356
+ validate: (v) => {
357
+ const s = (v || '').trim();
358
+ if (!s) return undefined;
359
+ return /^test_/.test(s) ? undefined : t('new.firebase.q.revenuecat.test.invalid');
360
+ },
361
+ onCancel: cancel,
362
+ }),
363
+ rcIosProdKey: (t, cancel) => ui.text({
364
+ message: t('add.prompt.rcIosProdKey'),
365
+ validate: (v) => {
366
+ const s = (v || '').trim();
367
+ if (!s) return undefined;
368
+ return /^appl_/.test(s) ? undefined : t('new.firebase.q.revenuecat.iosProd.invalid');
369
+ },
342
370
  onCancel: cancel,
343
371
  }),
344
- rcIosKey: (t, cancel) => ui.text({
345
- message: t('add.prompt.rcIosKey'),
372
+ rcAndroidProdKey: (t, cancel) => ui.text({
373
+ message: t('add.prompt.rcAndroidProdKey'),
374
+ validate: (v) => {
375
+ const s = (v || '').trim();
376
+ if (!s) return undefined;
377
+ return /^goog_/.test(s) ? undefined : t('new.firebase.q.revenuecat.androidProd.invalid');
378
+ },
346
379
  onCancel: cancel,
347
380
  }),
348
381
  llmProvider: (t, cancel) => ui.select({
@@ -415,7 +448,7 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
415
448
  const refFlag = backend === 'supabase' && projectRef ? ` --project-ref ${projectRef}` : '';
416
449
 
417
450
  if (backend === 'firebase') {
418
- const spinner = ui.spinner();
451
+ const spinner = ui.spinner({ color: paintLime });
419
452
  spinner.start(t('add.llm_chat.settingSecret'));
420
453
  try {
421
454
  // Write to temp file — avoids trailing newline (echo) and shell injection risks
@@ -430,7 +463,7 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
430
463
  ui.log.message(kleur.dim('Run manually: firebase functions:secrets:set LLM_API_KEY'));
431
464
  }
432
465
  } else if (backend === 'supabase') {
433
- const spinner = ui.spinner();
466
+ const spinner = ui.spinner({ color: paintLime });
434
467
  spinner.start(t('add.llm_chat.settingSecret'));
435
468
  // Set LLM_API_KEY, LLM_PROVIDER and LLM_SYSTEM_PROMPT all as Supabase Secrets.
436
469
  // Deployed Edge Functions read from Deno.env.get() = Supabase Secrets, NOT from .env files.
@@ -460,7 +493,7 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
460
493
  // 3. Deploy the LLM function automatically
461
494
  if (backend === 'api') return { deployOk: false, deployAttempted: false };
462
495
 
463
- const deploySpinner = ui.timedSpinner();
496
+ const deploySpinner = ui.timedSpinner({ color: paintLime });
464
497
  deploySpinner.start(t('add.llm_chat.deploying'));
465
498
  try {
466
499
  if (backend === 'firebase') {
@@ -688,7 +721,7 @@ async function runAdd(module, options = {}) {
688
721
  // 8. Apply patch if it exists under features/<module>/
689
722
  const patchDir = path.join(FEATURES_PATCH_DIR, normalized);
690
723
  if (await fs.pathExists(patchDir)) {
691
- const spinner = ui.spinner();
724
+ const spinner = ui.spinner({ color: paintLime });
692
725
  spinner.start(t('add.applyingPatch'));
693
726
  try {
694
727
  const { tokens: patchTokens, pathReplacements: patchPathReplacements } = buildTokens({
@@ -748,7 +781,7 @@ async function runAdd(module, options = {}) {
748
781
 
749
782
  // 10. flutter pub get
750
783
  {
751
- const spinner = ui.spinner();
784
+ const spinner = ui.spinner({ color: paintLime });
752
785
  spinner.start(t('add.pubGet'));
753
786
  try {
754
787
  await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
@@ -761,7 +794,7 @@ async function runAdd(module, options = {}) {
761
794
  // 11. build_runner (only when needed: features with codegen)
762
795
  const needsBuildRunner = ['revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback'].includes(normalized);
763
796
  if (needsBuildRunner) {
764
- const spinner = ui.timedSpinner();
797
+ const spinner = ui.timedSpinner({ color: paintLime });
765
798
  spinner.start(t('add.buildRunner'));
766
799
  try {
767
800
  await execAsync('dart run build_runner build --delete-conflicting-outputs', { cwd: projectDir, timeout: 600_000 });