kasy-cli 1.31.4 → 1.31.5

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.
@@ -8,6 +8,7 @@ const kleur = require('kleur');
8
8
  const ui = require('../utils/ui');
9
9
  const { printCompactHeader, paintLime } = require('../utils/brand');
10
10
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
11
+ const { augmentedEnv } = require('../utils/env-tools');
11
12
  const {
12
13
  AVAILABLE_FEATURES,
13
14
  FEATURES_PATCH_DIR,
@@ -797,7 +798,7 @@ async function runAdd(module, options = {}) {
797
798
  const spinner = ui.spinner({ color: paintLime });
798
799
  spinner.start(t('add.pubGet'));
799
800
  try {
800
- await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
801
+ await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000, env: augmentedEnv() });
801
802
  spinner.stop(t('add.pubGetDone'));
802
803
  } catch {
803
804
  spinner.stop(`⚠ ${t('add.pubGetFailed')}`);
@@ -9,6 +9,7 @@ const kleur = require('kleur');
9
9
  const ui = require('../utils/ui');
10
10
  const { printCompactHeader } = require('../utils/brand');
11
11
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
12
+ const { augmentedEnv } = require('../utils/env-tools');
12
13
  const {
13
14
  AVAILABLE_FEATURES,
14
15
  normalizeFeature,
@@ -471,7 +472,7 @@ async function runRemove(module, options = {}) {
471
472
  const spinner = ui.spinner();
472
473
  spinner.start(t('remove.pubGet'));
473
474
  try {
474
- await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
475
+ await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000, env: augmentedEnv() });
475
476
  spinner.stop(t('remove.pubGetDone'));
476
477
  } catch {
477
478
  spinner.stop(`⚠ ${t('remove.pubGetFailed')}`);
@@ -7,6 +7,9 @@ const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
7
7
  const { printCompactHeader } = require('../utils/brand');
8
8
  const { readBundleId, readPackageName } = require('../utils/mobile-identity');
9
9
  const { spawnFlutterWithSpinner } = require('../utils/flutter-run');
10
+ const { augmentedEnv } = require('../utils/env-tools');
11
+
12
+ const isWindows = process.platform === 'win32';
10
13
 
11
14
  function runCmd(cmd, args) {
12
15
  const res = spawnSync(cmd, args, { encoding: 'utf8' });
@@ -21,6 +24,8 @@ async function listFlutterDevices(projectDir) {
21
24
  const res = spawnSync('flutter', ['devices', '--machine'], {
22
25
  cwd: projectDir,
23
26
  encoding: 'utf8',
27
+ env: augmentedEnv(),
28
+ shell: isWindows,
24
29
  });
25
30
  if (res.status !== 0) return [];
26
31
  try {
@@ -6,11 +6,20 @@ const ui = require('../utils/ui');
6
6
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
7
7
  const { printCompactHeader, paintLime } = require('../utils/brand');
8
8
  const { spawnFlutterWithSpinner } = require('../utils/flutter-run');
9
+ const { augmentedEnv } = require('../utils/env-tools');
10
+
11
+ const isWindows = process.platform === 'win32';
9
12
 
10
13
  function listFlutterDevices(projectDir) {
14
+ // Same as `kasy run`'s launcher: expose a freshly-installed Flutter SDK via
15
+ // augmentedEnv() (the terminal's PATH may not have it yet), and use a shell on
16
+ // Windows so the flutter.bat is found. Without this, a machine that just ran
17
+ // `kasy new` reports "Flutter not found" here even though Flutter is installed.
11
18
  const res = spawnSync('flutter', ['devices', '--machine'], {
12
19
  cwd: projectDir,
13
20
  encoding: 'utf8',
21
+ env: augmentedEnv(),
22
+ shell: isWindows,
14
23
  });
15
24
  if (res.status !== 0) return [];
16
25
  try {
@@ -9,6 +9,7 @@ const kleur = require('kleur');
9
9
  const ui = require('../utils/ui');
10
10
  const { printCompactHeader, paintLime } = require('../utils/brand');
11
11
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
12
+ const { augmentedEnv } = require('../utils/env-tools');
12
13
  const {
13
14
  AVAILABLE_FEATURES,
14
15
  BASE_COMPONENT_FILES,
@@ -204,7 +205,7 @@ async function runUpdate(module, options = {}) {
204
205
  const spinnerPubGet = ui.spinner({ color: paintLime });
205
206
  spinnerPubGet.start(t('update.pubGet'));
206
207
  try {
207
- await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
208
+ await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000, env: augmentedEnv() });
208
209
  spinnerPubGet.stop(t('update.pubGetDone'));
209
210
  } catch {
210
211
  spinnerPubGet.stop(`⚠ ${t('update.pubGetFailed')}`);
@@ -252,7 +253,7 @@ async function runUpdate(module, options = {}) {
252
253
  const spinnerPubGet = ui.spinner({ color: paintLime });
253
254
  spinnerPubGet.start(t('update.pubGet'));
254
255
  try {
255
- await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
256
+ await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000, env: augmentedEnv() });
256
257
  spinnerPubGet.stop(t('update.pubGetDone'));
257
258
  } catch {
258
259
  spinnerPubGet.stop(`⚠ ${t('update.pubGetFailed')}`);
@@ -365,7 +366,7 @@ async function runUpdate(module, options = {}) {
365
366
  const spinner = ui.spinner({ color: paintLime });
366
367
  spinner.start(t('update.pubGet'));
367
368
  try {
368
- await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
369
+ await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000, env: augmentedEnv() });
369
370
  spinner.stop(t('update.pubGetDone'));
370
371
  } catch {
371
372
  spinner.stop(`⚠ ${t('update.pubGetFailed')}`);
@@ -31,6 +31,26 @@ const fs = require('node:fs');
31
31
  const { spawn } = require('node:child_process');
32
32
  const kleur = require('kleur');
33
33
  const ui = require('./ui');
34
+ const { augmentedEnv } = require('./env-tools');
35
+
36
+ const isWindows = process.platform === 'win32';
37
+
38
+ // `flutter` runs from an SDK that may have been installed by `kasy new` in this
39
+ // same session — and Windows only refreshes PATH for NEW terminals, so the
40
+ // current one doesn't see it yet. augmentedEnv() exposes %LOCALAPPDATA%\flutter\bin
41
+ // (and the pub-cache bin) so `kasy run` finds flutter exactly like `kasy new` did.
42
+ // On Windows `flutter` is a .bat, which Node's spawn() refuses to run without a
43
+ // shell — hence shell:true there. With shell:true the args become a command line,
44
+ // so any arg with a space must be quoted (our flutter args normally have none,
45
+ // but a dart-define value could).
46
+ function flutterSpawnOptions(extra = {}) {
47
+ return { env: augmentedEnv(), shell: isWindows, ...extra };
48
+ }
49
+
50
+ function safeArgs(args) {
51
+ if (!isWindows) return args;
52
+ return args.map((a) => (/\s/.test(a) ? `"${a}"` : a));
53
+ }
34
54
 
35
55
  // Markers that tell us the initial build is done and the app is running.
36
56
  const FLUTTER_READY_RE = /Flutter run key commands\.|is listening on|VM Service|Dart VM service|To hot reload|Hot restart/i;
@@ -140,10 +160,10 @@ function spawnFlutterWithSpinner(args, projectDir, t, options = {}) {
140
160
  */
141
161
  function spawnRaw(args, projectDir, t, log, onReady) {
142
162
  return new Promise((resolve, reject) => {
143
- const proc = spawn('flutter', args, {
163
+ const proc = spawn('flutter', safeArgs(args), flutterSpawnOptions({
144
164
  cwd: projectDir,
145
165
  stdio: ['inherit', 'pipe', 'pipe'],
146
- });
166
+ }));
147
167
 
148
168
  let readyFired = false;
149
169
  const fireReady = () => {
@@ -192,10 +212,10 @@ function spawnRaw(args, projectDir, t, log, onReady) {
192
212
  */
193
213
  function spawnWithSpinner(args, projectDir, t, log, onReady) {
194
214
  return new Promise((resolve, reject) => {
195
- const proc = spawn('flutter', args, {
215
+ const proc = spawn('flutter', safeArgs(args), flutterSpawnOptions({
196
216
  cwd: projectDir,
197
217
  stdio: ['pipe', 'pipe', 'pipe'],
198
- });
218
+ }));
199
219
 
200
220
  const spinner = ui.spinner();
201
221
  const startTime = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.31.4",
3
+ "version": "1.31.5",
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"
@@ -1,3 +1,4 @@
1
+ import 'package:flutter/foundation.dart' show kReleaseMode;
1
2
  import 'package:flutter_dotenv/flutter_dotenv.dart';
2
3
 
3
4
  const String _kEnvFromDefine = String.fromEnvironment(
@@ -16,6 +17,15 @@ const String _kRcAndroidApiKeyFromDefine = String.fromEnvironment(
16
17
  const String _kRcIosApiKeyFromDefine = String.fromEnvironment(
17
18
  'RC_IOS_API_KEY',
18
19
  );
20
+ const String _kRcTestKeyFromDefine = String.fromEnvironment(
21
+ 'RC_TEST_KEY',
22
+ );
23
+ const String _kRcIosProdKeyFromDefine = String.fromEnvironment(
24
+ 'RC_IOS_PROD_KEY',
25
+ );
26
+ const String _kRcAndroidProdKeyFromDefine = String.fromEnvironment(
27
+ 'RC_ANDROID_PROD_KEY',
28
+ );
19
29
  const String _kMixpanelTokenFromDefine = String.fromEnvironment(
20
30
  'MIXPANEL_TOKEN',
21
31
  );
@@ -57,13 +67,43 @@ class AppEnv {
57
67
  dartDefineValue: _kSupabaseTokenFromDefine,
58
68
  );
59
69
 
60
- static String get rcAndroidApiKey => _resolve(
61
- key: 'RC_ANDROID_API_KEY',
62
- dartDefineValue: _kRcAndroidApiKeyFromDefine,
70
+ /// RevenueCat "Test Store" public key (`test_…`). Works only against the
71
+ /// RevenueCat fake store (simulator/emulator); the native SDK rejects it in
72
+ /// release builds.
73
+ static String get rcTestKey =>
74
+ _resolve(key: 'RC_TEST_KEY', dartDefineValue: _kRcTestKeyFromDefine);
75
+
76
+ /// RevenueCat production/sandbox iOS key (`appl_…`) — the App Store key used
77
+ /// on physical devices, TestFlight and production.
78
+ static String get rcIosProdKey => _resolve(
79
+ key: 'RC_IOS_PROD_KEY',
80
+ dartDefineValue: _kRcIosProdKeyFromDefine,
63
81
  );
64
82
 
65
- static String get rcIosApiKey =>
66
- _resolve(key: 'RC_IOS_API_KEY', dartDefineValue: _kRcIosApiKeyFromDefine);
83
+ /// RevenueCat production/sandbox Android key (`goog_…`) — the Play Store key
84
+ /// used on physical devices, internal testing and production.
85
+ static String get rcAndroidProdKey => _resolve(
86
+ key: 'RC_ANDROID_PROD_KEY',
87
+ dartDefineValue: _kRcAndroidProdKeyFromDefine,
88
+ );
89
+
90
+ /// Effective RevenueCat key for Android, resolved for the current build.
91
+ static String get rcAndroidApiKey => _resolveRcKey(
92
+ explicit: _resolve(
93
+ key: 'RC_ANDROID_API_KEY',
94
+ dartDefineValue: _kRcAndroidApiKeyFromDefine,
95
+ ),
96
+ prodKey: rcAndroidProdKey,
97
+ );
98
+
99
+ /// Effective RevenueCat key for iOS, resolved for the current build.
100
+ static String get rcIosApiKey => _resolveRcKey(
101
+ explicit: _resolve(
102
+ key: 'RC_IOS_API_KEY',
103
+ dartDefineValue: _kRcIosApiKeyFromDefine,
104
+ ),
105
+ prodKey: rcIosProdKey,
106
+ );
67
107
 
68
108
  static String get mixpanelToken => _resolve(
69
109
  key: 'MIXPANEL_TOKEN',
@@ -91,4 +131,36 @@ class AppEnv {
91
131
  if (dartDefineValue.isNotEmpty) return dartDefineValue;
92
132
  return defaultValue;
93
133
  }
134
+
135
+ /// Resolve the RevenueCat key that actually works for the current build.
136
+ ///
137
+ /// A Test Store key (`test_…`) only works against RevenueCat's fake store
138
+ /// (simulator/emulator); the native SDK refuses it in release builds and the
139
+ /// paywall comes up empty. So:
140
+ ///
141
+ /// - Release builds (TestFlight / App Store / Play Store) always use the
142
+ /// production key (`appl_…` / `goog_…`), read from the bundled `.env`
143
+ /// (`RC_IOS_PROD_KEY` / `RC_ANDROID_PROD_KEY`). Because `.env` ships as an
144
+ /// asset, this holds no matter how the IPA/AAB was built (`kasy ios
145
+ /// release`, Xcode archive, Codemagic, `flutter build …`). An explicit
146
+ /// non-test key (injected by `kasy run` on a device, or by Codemagic
147
+ /// variable groups) still wins.
148
+ /// - Debug builds honor whatever `kasy run` injected (`test_` for
149
+ /// simulators, the prod key for physical devices) and fall back to the
150
+ /// Test Store key.
151
+ static String _resolveRcKey({
152
+ required String explicit,
153
+ required String prodKey,
154
+ }) {
155
+ final bool explicitIsTest = explicit.startsWith('test_');
156
+ if (kReleaseMode) {
157
+ if (explicit.isNotEmpty && !explicitIsTest) return explicit;
158
+ if (prodKey.isNotEmpty) return prodKey;
159
+ return explicit;
160
+ }
161
+ if (explicit.isNotEmpty) return explicit;
162
+ final String test = rcTestKey;
163
+ if (test.isNotEmpty) return test;
164
+ return prodKey;
165
+ }
94
166
  }
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
16
16
  # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17
17
  # In Windows, build-name is used as the major, minor, and patch parts
18
18
  # of the product and file versions while build-number is used as the build suffix.
19
- version: 1.0.0+35
19
+ version: 1.0.0+36
20
20
 
21
21
  environment:
22
22
  sdk: ^3.11.0