kasy-cli 1.17.0 → 1.19.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/bin/kasy.js +16 -2
- package/lib/commands/add.js +7 -7
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +17 -0
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +483 -324
- package/lib/commands/run.js +17 -4
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +123 -5
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/post-build.js +8 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +62 -5
- package/lib/utils/i18n/messages-es.js +62 -5
- package/lib/utils/i18n/messages-pt.js +63 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -2
- package/templates/firebase/README.en.md +1 -1
- package/templates/firebase/README.es.md +1 -1
- package/templates/firebase/README.md +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
- package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_date_picker.dart +2173 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +214 -91
- package/templates/firebase/lib/components/kasy_text_area.dart +9 -4
- package/templates/firebase/lib/components/kasy_text_field.dart +96 -36
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -2
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +88 -35
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +7 -43
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +118 -16
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +14 -20
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/core/security/secured_storage.dart +56 -15
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +18 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -6
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +6 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -2
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +949 -77
- package/templates/firebase/lib/features/home/home_page.dart +17 -40
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +1 -16
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +0 -4
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
- package/templates/firebase/lib/i18n/en.i18n.json +2 -1
- package/templates/firebase/lib/i18n/es.i18n.json +2 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
- package/templates/firebase/lib/main.dart +34 -34
- package/templates/firebase/pubspec.yaml +2 -1
- package/templates/firebase/storage.cors.json +8 -0
- package/templates/firebase/web/index.html +24 -2
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +0 -22
package/lib/utils/flutter-run.js
CHANGED
|
@@ -5,18 +5,29 @@
|
|
|
5
5
|
* stage detection, elapsed timer, hot-reload stdin pass-through and SIGINT
|
|
6
6
|
* forwarding stay consistent in one place.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* stage marker (Gradle, Xcode, install, sync, etc.).
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
8
|
+
* Two modes:
|
|
9
|
+
*
|
|
10
|
+
* - spinner mode (default, TTY only): Spawn flutter with piped stdio. Show a
|
|
11
|
+
* Clack spinner with elapsed time. Update the spinner message when we see a
|
|
12
|
+
* known stage marker (Gradle, Xcode, install, sync, etc.). When Flutter
|
|
13
|
+
* signals "ready" (DAP key commands), flush buffered output, drop into stdio
|
|
14
|
+
* pass-through, and pipe stdin so hot reload works. On early exit, replay
|
|
15
|
+
* the buffer so the user can see the error.
|
|
16
|
+
*
|
|
17
|
+
* - raw mode (auto when stdout is not a TTY, or forced via `--raw`):
|
|
18
|
+
* No spinner, no buffer. Flutter output is piped straight through so devs
|
|
19
|
+
* and AI agents see the unmodified stream.
|
|
20
|
+
*
|
|
21
|
+
* In both modes we always tee the raw (ANSI-stripped) flutter output to
|
|
22
|
+
* `<project>/.kasy/run.log`. This gives AI assistants a stable, single-file
|
|
23
|
+
* source of truth for the last run, even when the spinner cosmetically rewrites
|
|
24
|
+
* the human terminal.
|
|
16
25
|
*/
|
|
17
26
|
|
|
18
27
|
'use strict';
|
|
19
28
|
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
const fs = require('node:fs');
|
|
20
31
|
const { spawn } = require('node:child_process');
|
|
21
32
|
const kleur = require('kleur');
|
|
22
33
|
const ui = require('./ui');
|
|
@@ -24,6 +35,35 @@ const ui = require('./ui');
|
|
|
24
35
|
// Markers that tell us the initial build is done and the app is running.
|
|
25
36
|
const FLUTTER_READY_RE = /Flutter run key commands\.|is listening on|VM Service|Dart VM service|To hot reload|Hot restart/i;
|
|
26
37
|
|
|
38
|
+
// Strip ANSI escape sequences before persisting to the log file so the file
|
|
39
|
+
// is grep-friendly for tooling (CI logs, agents, editors).
|
|
40
|
+
const ANSI_RE = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
41
|
+
|
|
42
|
+
function stripAnsi(text) {
|
|
43
|
+
return String(text).replace(ANSI_RE, '');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Open (and create the parent dir of) the persistent run log. Overwrites any
|
|
48
|
+
* previous run — we want "the last run", not an ever-growing history.
|
|
49
|
+
*
|
|
50
|
+
* Returns a write stream, or null when the file can't be created (read-only
|
|
51
|
+
* project, permissions, etc.) — failure here must never break `kasy run`.
|
|
52
|
+
*/
|
|
53
|
+
function openRunLog(projectDir) {
|
|
54
|
+
const logPath = path.join(projectDir, '.kasy', 'run.log');
|
|
55
|
+
try {
|
|
56
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
57
|
+
const stream = fs.createWriteStream(logPath, { flags: 'w' });
|
|
58
|
+
stream.on('error', () => {});
|
|
59
|
+
const header = `# kasy run — ${new Date().toISOString()}\n`;
|
|
60
|
+
stream.write(header);
|
|
61
|
+
return { stream, logPath };
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
27
67
|
/**
|
|
28
68
|
* Map a chunk of Flutter output to a user-friendly stage label, or null
|
|
29
69
|
* if the chunk doesn't carry any of the known markers.
|
|
@@ -66,10 +106,82 @@ function render(stageMessage, startTime) {
|
|
|
66
106
|
}
|
|
67
107
|
|
|
68
108
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
109
|
+
* Pretty-print the path used for the run log. Falls back to the absolute path
|
|
110
|
+
* when the log lives outside cwd (unusual, but keeps the message accurate).
|
|
71
111
|
*/
|
|
72
|
-
function
|
|
112
|
+
function relLogPath(logPath) {
|
|
113
|
+
const rel = path.relative(process.cwd(), logPath);
|
|
114
|
+
return rel.startsWith('..') ? logPath : rel;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Spawn `flutter` with the given args inside `projectDir`. When `options.raw`
|
|
119
|
+
* is true or stdout is not a TTY, runs in raw pass-through mode. Otherwise,
|
|
120
|
+
* shows a Clack spinner that buffers output until Flutter is ready.
|
|
121
|
+
*
|
|
122
|
+
* Resolves on success, rejects on non-zero exit. The log file path is always
|
|
123
|
+
* announced at the end so callers (and AI agents) can find the full output.
|
|
124
|
+
*/
|
|
125
|
+
function spawnFlutterWithSpinner(args, projectDir, t, options = {}) {
|
|
126
|
+
const raw = Boolean(options.raw) || !process.stdout.isTTY;
|
|
127
|
+
const log = openRunLog(projectDir);
|
|
128
|
+
|
|
129
|
+
if (raw) return spawnRaw(args, projectDir, t, log);
|
|
130
|
+
return spawnWithSpinner(args, projectDir, t, log);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Raw mode: no spinner, no buffer. Flutter stdout/stderr are piped straight
|
|
135
|
+
* through to the parent process, and tee'd (ANSI-stripped) into the log file.
|
|
136
|
+
*
|
|
137
|
+
* Stdin is forwarded directly so hot-reload keys (r/R/q) work without any
|
|
138
|
+
* raw-mode toggling — the user is already on a "dumb" terminal here.
|
|
139
|
+
*/
|
|
140
|
+
function spawnRaw(args, projectDir, t, log) {
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const proc = spawn('flutter', args, {
|
|
143
|
+
cwd: projectDir,
|
|
144
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const tee = (chunk, sink) => {
|
|
148
|
+
sink.write(chunk);
|
|
149
|
+
if (log) log.stream.write(stripAnsi(chunk.toString()));
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
proc.stdout.on('data', (c) => tee(c, process.stdout));
|
|
153
|
+
proc.stderr.on('data', (c) => tee(c, process.stderr));
|
|
154
|
+
|
|
155
|
+
const sigintHandler = () => { try { proc.kill('SIGINT'); } catch (_) {} };
|
|
156
|
+
process.on('SIGINT', sigintHandler);
|
|
157
|
+
|
|
158
|
+
const cleanup = () => {
|
|
159
|
+
process.off('SIGINT', sigintHandler);
|
|
160
|
+
if (log) log.stream.end();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
proc.on('close', (code) => {
|
|
164
|
+
cleanup();
|
|
165
|
+
announceLog(log, t);
|
|
166
|
+
if (code === 0) resolve();
|
|
167
|
+
else reject(new Error(`flutter exited with code ${code}`));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
proc.on('error', (err) => {
|
|
171
|
+
cleanup();
|
|
172
|
+
announceLog(log, t);
|
|
173
|
+
reject(err);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Spinner mode: buffer flutter output during the build phase, surface progress
|
|
180
|
+
* as friendly stage labels, then flush + pass-through once Flutter signals
|
|
181
|
+
* ready. On early exit, the buffer is replayed so the user sees the real
|
|
182
|
+
* Flutter error.
|
|
183
|
+
*/
|
|
184
|
+
function spawnWithSpinner(args, projectDir, t, log) {
|
|
73
185
|
return new Promise((resolve, reject) => {
|
|
74
186
|
const proc = spawn('flutter', args, {
|
|
75
187
|
cwd: projectDir,
|
|
@@ -106,7 +218,12 @@ function spawnFlutterWithSpinner(args, projectDir, t) {
|
|
|
106
218
|
}
|
|
107
219
|
};
|
|
108
220
|
|
|
221
|
+
const teeLog = (chunk) => {
|
|
222
|
+
if (log) log.stream.write(stripAnsi(chunk.toString()));
|
|
223
|
+
};
|
|
224
|
+
|
|
109
225
|
const handleStdout = (chunk) => {
|
|
226
|
+
teeLog(chunk);
|
|
110
227
|
const text = chunk.toString();
|
|
111
228
|
if (!ready) {
|
|
112
229
|
buffer.push(chunk);
|
|
@@ -122,6 +239,7 @@ function spawnFlutterWithSpinner(args, projectDir, t) {
|
|
|
122
239
|
};
|
|
123
240
|
|
|
124
241
|
const handleStderr = (chunk) => {
|
|
242
|
+
teeLog(chunk);
|
|
125
243
|
if (!ready) {
|
|
126
244
|
buffer.push(chunk);
|
|
127
245
|
if (FLUTTER_READY_RE.test(chunk.toString())) flushAndSwitch();
|
|
@@ -144,6 +262,7 @@ function spawnFlutterWithSpinner(args, projectDir, t) {
|
|
|
144
262
|
process.stdin.unpipe(proc.stdin);
|
|
145
263
|
process.stdin.pause();
|
|
146
264
|
}
|
|
265
|
+
if (log) log.stream.end();
|
|
147
266
|
};
|
|
148
267
|
|
|
149
268
|
proc.on('close', (code) => {
|
|
@@ -152,6 +271,7 @@ function spawnFlutterWithSpinner(args, projectDir, t) {
|
|
|
152
271
|
spinner.stop(t('run.spinner.failed'), 2);
|
|
153
272
|
for (const chunk of buffer) process.stdout.write(chunk);
|
|
154
273
|
}
|
|
274
|
+
announceLog(log, t);
|
|
155
275
|
if (code === 0) resolve();
|
|
156
276
|
else reject(new Error(`flutter exited with code ${code}`));
|
|
157
277
|
});
|
|
@@ -159,15 +279,23 @@ function spawnFlutterWithSpinner(args, projectDir, t) {
|
|
|
159
279
|
proc.on('error', (err) => {
|
|
160
280
|
cleanup();
|
|
161
281
|
if (!ready) spinner.stop(t('run.spinner.failed'), 2);
|
|
282
|
+
announceLog(log, t);
|
|
162
283
|
reject(err);
|
|
163
284
|
});
|
|
164
285
|
});
|
|
165
286
|
}
|
|
166
287
|
|
|
288
|
+
function announceLog(log, t) {
|
|
289
|
+
if (!log) return;
|
|
290
|
+
const msg = t('run.log.savedTo', { path: relLogPath(log.logPath) });
|
|
291
|
+
console.log(kleur.dim(`\n ${msg}`));
|
|
292
|
+
}
|
|
293
|
+
|
|
167
294
|
module.exports = {
|
|
168
295
|
spawnFlutterWithSpinner,
|
|
169
296
|
// Exported for tests and reuse in non-flutter spawn helpers.
|
|
170
297
|
detectStage,
|
|
171
298
|
formatElapsed,
|
|
172
299
|
FLUTTER_READY_RE,
|
|
300
|
+
stripAnsi,
|
|
173
301
|
};
|
|
@@ -79,6 +79,9 @@ module.exports = {
|
|
|
79
79
|
'doctor.title': 'Kasy Doctor',
|
|
80
80
|
'doctor.baseEnvironment': 'Base environment',
|
|
81
81
|
'doctor.optionalBackend': 'Optional backend tooling',
|
|
82
|
+
'doctor.gcpBilling.title': 'Google Cloud Billing (Firebase Blaze)',
|
|
83
|
+
'doctor.gcpBilling.found': '{count} active billing account(s):',
|
|
84
|
+
'doctor.gcpBilling.missing': 'No billing account found. Create one before running `kasy new` with Firebase:',
|
|
82
85
|
'doctor.requiredMissing': 'Missing required dependencies. Fix the errors above and rerun doctor.',
|
|
83
86
|
'doctor.requiredPassed': '✓ Required environment checks passed.',
|
|
84
87
|
'modules.backends': 'Available backends:',
|
|
@@ -196,6 +199,10 @@ module.exports = {
|
|
|
196
199
|
'new.firebase.create.success': 'Firebase project created successfully.',
|
|
197
200
|
'new.firebase.create.failed': 'Could not create project',
|
|
198
201
|
'new.firebase.create.gcloudRequired': 'gcloud CLI is required for "create from scratch". Without it, the full Firebase flow cannot run.',
|
|
202
|
+
'new.firebase.billing.required': 'You do not have a billing account on Google Cloud yet. Firebase needs the Blaze plan to use Storage and Cloud Functions.',
|
|
203
|
+
'new.firebase.billing.create.steps': 'Opening the billing account creation page. Create the account (credit card required, no charges within the free quota) and come back here:',
|
|
204
|
+
'new.firebase.billing.created.ready': 'I created the billing account, ready to continue?',
|
|
205
|
+
'new.firebase.billing.stillMissing': 'No active billing account found. Finish the setup in the Console and run `kasy new` again.',
|
|
199
206
|
'new.firebase.create.installTitle': 'To install gcloud CLI, run:',
|
|
200
207
|
'new.firebase.create.installCommand': 'Command',
|
|
201
208
|
'new.firebase.create.installAfter': 'Then log in',
|
|
@@ -212,6 +219,8 @@ module.exports = {
|
|
|
212
219
|
'new.firebase.q.organization': 'In which GCP organization should the project be created?',
|
|
213
220
|
'new.firebase.q.organization.none': 'No organization (personal account / standalone project)',
|
|
214
221
|
'new.firebase.q.organization.hint': 'Organizations linked to your gcloud account',
|
|
222
|
+
'new.firebase.create.billingWait': 'Waiting for billing propagation…',
|
|
223
|
+
'new.firebase.create.billingQuotaError': 'Could not link billing (account quota reached or propagation not finished yet).',
|
|
215
224
|
'new.firebase.create.billingRetry.title': 'Link billing manually (quota exceeded or billing error):',
|
|
216
225
|
'new.firebase.create.billingRetry.hint': 'Tip: Limit is 3 projects per billing account. Remove unused projects at the link above. If you just removed one, wait 2–5 min for changes to propagate, then retry.',
|
|
217
226
|
'new.firebase.create.billingRetry.ready': 'Linked billing. Retry setup?',
|
|
@@ -233,6 +242,7 @@ module.exports = {
|
|
|
233
242
|
'new.supabase.loginHint': 'Run: supabase login. Then enter your existing project URL and key below.',
|
|
234
243
|
'new.supabase.setup': 'Linking project and deploying…',
|
|
235
244
|
'new.supabase.setupManual': 'Run manually: supabase link, supabase db push, supabase functions deploy',
|
|
245
|
+
'new.supabase.passwordSaved': 'Database password auto-generated and saved to .kasy/supabase.json (gitignored).',
|
|
236
246
|
'new.supabase.q.useExisting.orgSelect': 'Which organization is the project in?',
|
|
237
247
|
'new.supabase.q.useExisting.projectSelect': 'Which existing Supabase project do you want to use?',
|
|
238
248
|
'new.supabase.projectsRequired': 'No projects found in this organization.',
|
|
@@ -273,9 +283,9 @@ module.exports = {
|
|
|
273
283
|
'new.q.backend.supabase.desc': 'SQL database (PostgreSQL) with more control',
|
|
274
284
|
'new.q.backend.api.desc': 'You already have your own backend server',
|
|
275
285
|
|
|
276
|
-
'new.q.mode': 'How do you want to
|
|
277
|
-
'new.q.mode.quick': '⚡ Quick
|
|
278
|
-
'new.q.mode.advanced': '🛠
|
|
286
|
+
'new.q.mode': 'How do you want to create the app?',
|
|
287
|
+
'new.q.mode.quick': '⚡ Quick (recommended): everything ready, zero config',
|
|
288
|
+
'new.q.mode.advanced': '🛠 Step by step: pick every detail',
|
|
279
289
|
|
|
280
290
|
'new.q.preset': 'Which features to include?',
|
|
281
291
|
'new.q.preset.starter': '⚡ Starter — analytics + crash reports + onboarding',
|
|
@@ -288,6 +298,40 @@ module.exports = {
|
|
|
288
298
|
'new.firebase.success.deployStep': '• Deploy backend (from inside the project folder):',
|
|
289
299
|
|
|
290
300
|
'cli.command.deploy.description': 'Publish the server to Firebase or Supabase',
|
|
301
|
+
'cli.command.configure.description': 'Configure optional keys (RevenueCat, Sentry, Mixpanel...) — skippable, picks up where you left off',
|
|
302
|
+
'configure.title': 'App credential setup',
|
|
303
|
+
'configure.notKasyProject': 'This folder is not a Kasy project (no pubspec.yaml). Cd into the project and run again.',
|
|
304
|
+
'configure.alreadyFilled': 'Already configured ({count}):',
|
|
305
|
+
'configure.toFill': 'Missing ({count}):',
|
|
306
|
+
'configure.skipHint': 'Press Enter without typing to skip any key. Run `kasy configure` again when you have the credential.',
|
|
307
|
+
'configure.skipPlaceholder': '(leave empty to skip)',
|
|
308
|
+
'configure.saved': 'Saved to {path} ({count} key(s) filled)',
|
|
309
|
+
'configure.stillPending': '{count} key(s) still pending.',
|
|
310
|
+
'configure.runAgainHint': 'Run `kasy configure` again once you have the keys.',
|
|
311
|
+
'configure.allDone': 'All set! You can run the app without missing credentials.',
|
|
312
|
+
'configure.allSkipped': 'No keys filled this time. Run again when ready.',
|
|
313
|
+
'configure.outroSaved': '{count} key(s) updated in .env',
|
|
314
|
+
'configure.outroNoChange': 'No changes to .env',
|
|
315
|
+
'configure.aborted': 'Cancelled. Nothing was written.',
|
|
316
|
+
'configure.section.appStore': 'App Store',
|
|
317
|
+
'configure.section.sentry': 'Sentry (Crash Reports)',
|
|
318
|
+
'configure.section.mixpanel': 'Mixpanel (Analytics)',
|
|
319
|
+
'configure.section.revenuecat': 'RevenueCat (Subscriptions)',
|
|
320
|
+
'configure.section.facebook': 'Facebook (Login + Ads)',
|
|
321
|
+
'configure.section.llmChat': 'AI Chat (LLM)',
|
|
322
|
+
'configure.savedFacebook': 'Facebook credentials written to Info.plist and strings.xml.',
|
|
323
|
+
'configure.facebookPlistMissing': 'ios/Runner/Info.plist not found — Facebook iOS was not updated.',
|
|
324
|
+
'configure.facebookStringsMissing': 'android/.../strings.xml not found — Facebook Android was not updated.',
|
|
325
|
+
'configure.facebookNeedsAppId': 'Facebook credentials need an App ID. Run `kasy configure` again and provide the App ID.',
|
|
326
|
+
'configure.noOptionalFeatures': 'This project has no optional features that need credentials.',
|
|
327
|
+
'configure.statusSummary': '{filled} already configured, {pending} pending',
|
|
328
|
+
'configure.alreadyDone': 'all set',
|
|
329
|
+
'configure.alreadyFilledShort': 'already configured',
|
|
330
|
+
'configure.savedEnv': '{count} key(s) saved to .env',
|
|
331
|
+
'configure.savedFnEnv': '{count} key(s) saved to functions/.env',
|
|
332
|
+
'configure.settingSecrets': 'Setting {count} Firebase Secret(s)…',
|
|
333
|
+
'configure.savedSecrets': '{count} Firebase Secret(s) set',
|
|
334
|
+
'configure.secretFailed': 'Could not set secret {key}. Run manually: firebase functions:secrets:set {key}',
|
|
291
335
|
'cli.command.check.description': 'Check push notifications setup (use --fix to fix it)',
|
|
292
336
|
'deploy.q.project': 'Firebase Project ID:',
|
|
293
337
|
'deploy.q.serviceAccount': 'Path to service account JSON:',
|
|
@@ -531,6 +575,10 @@ module.exports = {
|
|
|
531
575
|
'new.firebase.confirm.modules': 'Features',
|
|
532
576
|
'new.firebase.confirm.none': 'none',
|
|
533
577
|
'new.firebase.confirm.proceed': 'Create project now?',
|
|
578
|
+
'new.advanced.section.config': 'App configuration',
|
|
579
|
+
'new.advanced.section.features': 'Features',
|
|
580
|
+
'new.advanced.section.creds': 'Credentials',
|
|
581
|
+
'new.advanced.q.configureCredsNow': 'Enter credentials now? (you can defer to `kasy configure` later)',
|
|
534
582
|
|
|
535
583
|
'new.firebase.step.copying': 'Setting up your project...',
|
|
536
584
|
'new.firebase.step.pubGet': 'Installing Flutter packages...',
|
|
@@ -629,13 +677,20 @@ module.exports = {
|
|
|
629
677
|
'new.outdated.upgradeNow': 'Upgrade to the latest version before creating? (requires active subscription)',
|
|
630
678
|
'new.outdated.upgraded': 'kasy updated! Run kasy new again.',
|
|
631
679
|
'new.success.title': 'Project created successfully!',
|
|
680
|
+
'new.success.featuresInstalled': 'Features enabled:',
|
|
632
681
|
'new.success.nextSteps': 'Next steps:',
|
|
633
682
|
'new.success.step.cd': 'Go to your project folder:',
|
|
634
|
-
'new.success.step.deploy': '
|
|
683
|
+
'new.success.step.deploy': 'Push the server to Firebase (DB + functions):',
|
|
684
|
+
'new.success.step.configure': 'Configure optional keys when you have them (RevenueCat, Sentry, etc.):',
|
|
685
|
+
'new.success.step.docs': 'Documentation:',
|
|
635
686
|
'new.success.step.run': 'Run your app (with your configured keys):',
|
|
636
687
|
'new.success.step.run.vscode': '(or F5 in VS Code)',
|
|
637
688
|
'new.success.step.console': 'Open your backend console:',
|
|
638
689
|
'new.fcm.generating': 'Generating push notifications key (Firebase Service Account)…',
|
|
690
|
+
'new.google.enabling': 'Enabling Google Sign-In…',
|
|
691
|
+
'new.google.refreshConfigs': 'Updating google-services.json and GoogleService-Info.plist with Google Client IDs…',
|
|
692
|
+
'new.google.manualHint': 'Google Sign-In: enable manually in the Console (Google provider):',
|
|
693
|
+
'new.google.manualHint.noEmail': 'Google Sign-In: could not detect a support email (gcloud has no account). Enable manually in the Console:',
|
|
639
694
|
'new.sha1.registering': 'Registering SHA-1 for Google Sign-In (Android)…',
|
|
640
695
|
'new.sha1.failed': 'SHA-1 not added automatically: {error}',
|
|
641
696
|
'new.sha1.manual': 'Add it manually so Google Sign-In works on Android:',
|
|
@@ -654,7 +709,8 @@ module.exports = {
|
|
|
654
709
|
'new.apns.warning': '⚠ iOS Push: configure the APNs Key in Firebase Console',
|
|
655
710
|
'new.apns.step1': '1. Apple Developer Portal → Keys → create APNs Key (.p8)',
|
|
656
711
|
'new.apns.step2': '2. Firebase Console → Cloud Messaging → iOS app → upload APNs Key',
|
|
657
|
-
'new.
|
|
712
|
+
'new.apns.hint': 'iOS Push (APNs Key) is only needed once you publish for iOS. Step-by-step: https://kasy.dev/docs/apns',
|
|
713
|
+
'new.firebase.create.estimatedTime': '(3–5 min — needs stable internet, do not close the terminal)',
|
|
658
714
|
'new.internet.warning': '📶 Make sure you have a stable internet connection — this step requires network access.',
|
|
659
715
|
|
|
660
716
|
// run command
|
|
@@ -682,6 +738,7 @@ module.exports = {
|
|
|
682
738
|
'run.rc.forcedTest': 'RevenueCat: --rc=test forced',
|
|
683
739
|
'run.rc.forcedProd': 'RevenueCat: --rc=prod forced',
|
|
684
740
|
'run.rc.forcedProdMissing': 'RevenueCat: --rc=prod requested but RC_IOS_PROD_KEY/RC_ANDROID_PROD_KEY not set in .env',
|
|
741
|
+
'run.log.savedTo': 'Full output at {path} (handy to paste into AI/assistant)',
|
|
685
742
|
|
|
686
743
|
// reset command
|
|
687
744
|
'cli.command.reset.description': 'Uninstall the app on a simulator/emulator/device so you can test as a fresh install',
|
|
@@ -79,6 +79,9 @@ module.exports = {
|
|
|
79
79
|
'doctor.title': 'Kasy Doctor',
|
|
80
80
|
'doctor.baseEnvironment': 'Entorno base',
|
|
81
81
|
'doctor.optionalBackend': 'Herramientas opcionales de backend',
|
|
82
|
+
'doctor.gcpBilling.title': 'Google Cloud Billing (Firebase Blaze)',
|
|
83
|
+
'doctor.gcpBilling.found': '{count} cuenta(s) de facturación activa(s):',
|
|
84
|
+
'doctor.gcpBilling.missing': 'No se encontró ninguna cuenta de facturación. Crea una antes de ejecutar `kasy new` con Firebase:',
|
|
82
85
|
'doctor.requiredMissing': 'Faltan dependencias obligatorias. Corrige los errores anteriores y ejecuta doctor nuevamente.',
|
|
83
86
|
'doctor.requiredPassed': '✓ Verificaciones obligatorias de entorno aprobadas.',
|
|
84
87
|
'modules.backends': 'Backends disponibles:',
|
|
@@ -196,6 +199,10 @@ module.exports = {
|
|
|
196
199
|
'new.firebase.create.success': 'Proyecto Firebase creado correctamente.',
|
|
197
200
|
'new.firebase.create.failed': 'No se pudo crear el proyecto',
|
|
198
201
|
'new.firebase.create.gcloudRequired': 'gcloud CLI es obligatorio para "crear desde cero". Sin él, el flujo completo de Firebase no puede ejecutarse.',
|
|
202
|
+
'new.firebase.billing.required': 'Aún no tienes una cuenta de facturación en Google Cloud. Firebase necesita el plan Blaze para usar Storage y Cloud Functions.',
|
|
203
|
+
'new.firebase.billing.create.steps': 'Abriendo la página de creación de cuenta de facturación. Crea la cuenta (tarjeta de crédito requerida, sin cargos dentro de la cuota gratuita) y vuelve aquí:',
|
|
204
|
+
'new.firebase.billing.created.ready': 'Ya creé la cuenta de facturación, ¿puedo continuar?',
|
|
205
|
+
'new.firebase.billing.stillMissing': 'No se encontró ninguna cuenta de facturación activa. Termina la configuración en la consola y ejecuta `kasy new` nuevamente.',
|
|
199
206
|
'new.firebase.create.installTitle': 'Para instalar gcloud CLI, ejecuta:',
|
|
200
207
|
'new.firebase.create.installCommand': 'Comando',
|
|
201
208
|
'new.firebase.create.installAfter': 'Luego inicia sesión',
|
|
@@ -212,6 +219,8 @@ module.exports = {
|
|
|
212
219
|
'new.firebase.q.organization': '¿En qué organización GCP crear el proyecto?',
|
|
213
220
|
'new.firebase.q.organization.none': 'Sin organización (cuenta personal / proyecto independiente)',
|
|
214
221
|
'new.firebase.q.organization.hint': 'Organizaciones vinculadas a tu cuenta gcloud',
|
|
222
|
+
'new.firebase.create.billingWait': 'Esperando propagación de la facturación…',
|
|
223
|
+
'new.firebase.create.billingQuotaError': 'No se pudo vincular la facturación (cuota de la cuenta alcanzada o propagación aún no finalizada).',
|
|
215
224
|
'new.firebase.create.billingRetry.title': 'Vincula la facturación manualmente (cuota excedida o error de billing):',
|
|
216
225
|
'new.firebase.create.billingRetry.hint': 'Consejo: El límite es 3 proyectos por cuenta de facturación. Elimina proyectos no usados en el enlace de arriba. Si acabas de eliminar uno, espera 2–5 min a que se propague el cambio y reintenta.',
|
|
217
226
|
'new.firebase.create.billingRetry.ready': 'He vinculado la facturación. ¿Reintentar?',
|
|
@@ -235,6 +244,7 @@ module.exports = {
|
|
|
235
244
|
'new.supabase.loginHint': 'Ejecuta: supabase login. Luego ingresa la URL y clave de tu proyecto existente.',
|
|
236
245
|
'new.supabase.setup': 'Vinculando proyecto y desplegando…',
|
|
237
246
|
'new.supabase.setupManual': 'Ejecuta manualmente: supabase link, supabase db push, supabase functions deploy',
|
|
247
|
+
'new.supabase.passwordSaved': 'Contraseña de la base generada y guardada en .kasy/supabase.json (gitignored).',
|
|
238
248
|
'new.supabase.q.useExisting.orgSelect': '¿En qué organización está el proyecto?',
|
|
239
249
|
'new.supabase.q.useExisting.projectSelect': '¿Qué proyecto Supabase existente quieres usar?',
|
|
240
250
|
'new.supabase.projectsRequired': 'No se encontraron proyectos en esta organización.',
|
|
@@ -275,9 +285,9 @@ module.exports = {
|
|
|
275
285
|
'new.q.backend.supabase.desc': 'Base de datos SQL (PostgreSQL) con más control',
|
|
276
286
|
'new.q.backend.api.desc': 'Ya tienes tu propio servidor',
|
|
277
287
|
|
|
278
|
-
'new.q.mode': '¿Como quieres
|
|
279
|
-
'new.q.mode.quick': '⚡ Rápido
|
|
280
|
-
'new.q.mode.advanced': '🛠
|
|
288
|
+
'new.q.mode': '¿Como quieres crear la app?',
|
|
289
|
+
'new.q.mode.quick': '⚡ Rápido (recomendado): todo listo, cero configuración',
|
|
290
|
+
'new.q.mode.advanced': '🛠 Paso a paso: elegir cada detalle',
|
|
281
291
|
|
|
282
292
|
'new.q.preset': '¿Qué features incluir?',
|
|
283
293
|
'new.q.preset.starter': '⚡ Starter — analytics + errores + onboarding',
|
|
@@ -290,6 +300,40 @@ module.exports = {
|
|
|
290
300
|
'new.firebase.success.deployStep': '• Desplegar backend (desde dentro de la carpeta del proyecto):',
|
|
291
301
|
|
|
292
302
|
'cli.command.deploy.description': 'Publica el servidor en Firebase o Supabase',
|
|
303
|
+
'cli.command.configure.description': 'Configura claves opcionales (RevenueCat, Sentry, Mixpanel...) — puede omitir, recuerda lo que falta',
|
|
304
|
+
'configure.title': 'Configuración de claves del app',
|
|
305
|
+
'configure.notKasyProject': 'Esta carpeta no es un proyecto Kasy (no se encontró pubspec.yaml). Entra en la carpeta del proyecto y ejecuta de nuevo.',
|
|
306
|
+
'configure.alreadyFilled': 'Ya configurado ({count}):',
|
|
307
|
+
'configure.toFill': 'Falta configurar ({count}):',
|
|
308
|
+
'configure.skipHint': 'Pulsa Enter sin escribir nada para omitir cualquier clave. Ejecuta `kasy configure` de nuevo cuando tengas la credencial.',
|
|
309
|
+
'configure.skipPlaceholder': '(vacío para omitir)',
|
|
310
|
+
'configure.saved': 'Guardado en {path} ({count} clave(s) completada(s))',
|
|
311
|
+
'configure.stillPending': '{count} clave(s) todavía pendiente(s).',
|
|
312
|
+
'configure.runAgainHint': 'Ejecuta `kasy configure` de nuevo cuando tengas las claves.',
|
|
313
|
+
'configure.allDone': '¡Todo listo! Puedes ejecutar la app sin pendientes de credenciales.',
|
|
314
|
+
'configure.allSkipped': 'Ninguna clave completada ahora. Ejecuta de nuevo cuando estés listo.',
|
|
315
|
+
'configure.outroSaved': '{count} clave(s) actualizada(s) en .env',
|
|
316
|
+
'configure.outroNoChange': 'Sin cambios en .env',
|
|
317
|
+
'configure.aborted': 'Cancelado. Nada fue guardado.',
|
|
318
|
+
'configure.section.appStore': 'App Store',
|
|
319
|
+
'configure.section.sentry': 'Sentry (Crash Reports)',
|
|
320
|
+
'configure.section.mixpanel': 'Mixpanel (Analytics)',
|
|
321
|
+
'configure.section.revenuecat': 'RevenueCat (Suscripciones)',
|
|
322
|
+
'configure.section.facebook': 'Facebook (Login + Ads)',
|
|
323
|
+
'configure.section.llmChat': 'AI Chat (LLM)',
|
|
324
|
+
'configure.savedFacebook': 'Credenciales de Facebook escritas en Info.plist y strings.xml.',
|
|
325
|
+
'configure.facebookPlistMissing': 'ios/Runner/Info.plist no encontrado — Facebook iOS no fue actualizado.',
|
|
326
|
+
'configure.facebookStringsMissing': 'android/.../strings.xml no encontrado — Facebook Android no fue actualizado.',
|
|
327
|
+
'configure.facebookNeedsAppId': 'Las credenciales de Facebook requieren un App ID. Ejecuta `kasy configure` de nuevo y proporciona el App ID.',
|
|
328
|
+
'configure.noOptionalFeatures': 'Este proyecto no tiene features opcionales que requieran credenciales.',
|
|
329
|
+
'configure.statusSummary': '{filled} ya configurada(s), {pending} pendiente(s)',
|
|
330
|
+
'configure.alreadyDone': 'todo listo',
|
|
331
|
+
'configure.alreadyFilledShort': 'ya configurada',
|
|
332
|
+
'configure.savedEnv': '{count} clave(s) guardada(s) en .env',
|
|
333
|
+
'configure.savedFnEnv': '{count} clave(s) guardada(s) en functions/.env',
|
|
334
|
+
'configure.settingSecrets': 'Guardando {count} Firebase Secret(s)…',
|
|
335
|
+
'configure.savedSecrets': '{count} Firebase Secret(s) guardado(s)',
|
|
336
|
+
'configure.secretFailed': 'No pude guardar el secret {key}. Ejecuta manualmente: firebase functions:secrets:set {key}',
|
|
293
337
|
'cli.command.check.description': 'Verifica si las notificaciones push están configuradas (usa --fix para arreglar)',
|
|
294
338
|
'deploy.q.project': 'Firebase Project ID:',
|
|
295
339
|
'deploy.q.serviceAccount': 'Ruta al service account JSON:',
|
|
@@ -531,6 +575,10 @@ module.exports = {
|
|
|
531
575
|
'new.firebase.confirm.modules': 'Features',
|
|
532
576
|
'new.firebase.confirm.none': 'ninguno',
|
|
533
577
|
'new.firebase.confirm.proceed': '¿Crear el proyecto ahora?',
|
|
578
|
+
'new.advanced.section.config': 'Configuración del app',
|
|
579
|
+
'new.advanced.section.features': 'Funcionalidades',
|
|
580
|
+
'new.advanced.section.creds': 'Credenciales',
|
|
581
|
+
'new.advanced.q.configureCredsNow': '¿Configurar credenciales ahora? (puedes dejarlo para `kasy configure`)',
|
|
534
582
|
|
|
535
583
|
'new.firebase.step.copying': 'Copiando plantilla del proyecto...',
|
|
536
584
|
'new.firebase.step.pubGet': 'Instalando paquetes Flutter...',
|
|
@@ -629,13 +677,20 @@ module.exports = {
|
|
|
629
677
|
'new.outdated.upgradeNow': '¿Actualizar a la última versión antes de crear? (requiere suscripción activa)',
|
|
630
678
|
'new.outdated.upgraded': '¡kasy actualizado! Ejecuta kasy new nuevamente.',
|
|
631
679
|
'new.success.title': '¡Proyecto creado con exito!',
|
|
680
|
+
'new.success.featuresInstalled': 'Recursos activados:',
|
|
632
681
|
'new.success.nextSteps': 'Proximos pasos:',
|
|
633
682
|
'new.success.step.cd': 'Ve a la carpeta del proyecto:',
|
|
634
|
-
'new.success.step.deploy': '
|
|
683
|
+
'new.success.step.deploy': 'Sube el servidor a Firebase (DB + funciones):',
|
|
684
|
+
'new.success.step.configure': 'Configura claves opcionales cuando las tengas (RevenueCat, Sentry, etc.):',
|
|
685
|
+
'new.success.step.docs': 'Documentación:',
|
|
635
686
|
'new.success.step.run': 'Ejecuta el app (con tus claves configuradas):',
|
|
636
687
|
'new.success.step.run.vscode': '(o F5 en VS Code)',
|
|
637
688
|
'new.success.step.console': 'Abre la consola del backend:',
|
|
638
689
|
'new.fcm.generating': 'Generando clave de Service Account FCM…',
|
|
690
|
+
'new.google.enabling': 'Activando Inicio de sesión con Google…',
|
|
691
|
+
'new.google.refreshConfigs': 'Actualizando google-services.json y GoogleService-Info.plist con los Client IDs de Google…',
|
|
692
|
+
'new.google.manualHint': 'Inicio de sesión con Google: actívalo manualmente en la consola (proveedor Google):',
|
|
693
|
+
'new.google.manualHint.noEmail': 'Inicio de sesión con Google: no detecté un email de soporte (gcloud sin cuenta). Actívalo manualmente en la consola:',
|
|
639
694
|
'new.sha1.registering': 'Registrando SHA-1 para Google Sign-In (Android)…',
|
|
640
695
|
'new.sha1.failed': 'SHA-1 no añadido automaticamente: {error}',
|
|
641
696
|
'new.sha1.manual': 'Agregalo manualmente para que Google Sign-In funcione en Android:',
|
|
@@ -654,7 +709,8 @@ module.exports = {
|
|
|
654
709
|
'new.apns.warning': '⚠ Push iOS: configura la APNs Key en Firebase Console',
|
|
655
710
|
'new.apns.step1': '1. Apple Developer Portal → Keys → crear APNs Key (.p8)',
|
|
656
711
|
'new.apns.step2': '2. Firebase Console → Cloud Messaging → app iOS → subir la APNs Key',
|
|
657
|
-
'new.
|
|
712
|
+
'new.apns.hint': 'Push para iOS (APNs Key) solo se necesita cuando publiques para iOS. Paso a paso: https://kasy.dev/docs/apns',
|
|
713
|
+
'new.firebase.create.estimatedTime': '(3-5 min — requiere internet estable, no cierres la terminal)',
|
|
658
714
|
'new.internet.warning': '📶 Asegúrate de tener una conexión a internet estable — este paso requiere red.',
|
|
659
715
|
|
|
660
716
|
// run command
|
|
@@ -715,6 +771,7 @@ module.exports = {
|
|
|
715
771
|
'run.rc.forcedTest': 'RevenueCat: --rc=test forzado',
|
|
716
772
|
'run.rc.forcedProd': 'RevenueCat: --rc=prod forzado',
|
|
717
773
|
'run.rc.forcedProdMissing': 'RevenueCat: --rc=prod pedido pero RC_IOS_PROD_KEY/RC_ANDROID_PROD_KEY no configuradas en .env',
|
|
774
|
+
'run.log.savedTo': 'Salida completa en {path} (útil para pegar en IA/asistente)',
|
|
718
775
|
|
|
719
776
|
// doctor project checks
|
|
720
777
|
'doctor.project.title': 'Proyecto',
|