kasy-cli 1.19.1 → 1.20.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 +1 -0
- package/lib/commands/new.js +9 -0
- package/lib/commands/run.js +22 -6
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +91 -2
- package/lib/scaffold/engine.js +5 -0
- package/lib/scaffold/generate.js +4 -0
- package/lib/scaffold/shared/generator-utils.js +38 -1
- package/lib/utils/flutter-run.js +16 -4
- package/lib/utils/i18n/messages-en.js +2 -1
- package/lib/utils/i18n/messages-es.js +2 -1
- package/lib/utils/i18n/messages-pt.js +2 -1
- package/package.json +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
- package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1148 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
- package/templates/firebase/lib/components/kasy_text_field.dart +39 -29
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +90 -69
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +30 -7
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +439 -232
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
- package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
- package/templates/firebase/lib/core/theme/colors.dart +6 -2
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -0
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +202 -79
- package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
- package/templates/firebase/lib/i18n/en.i18n.json +3 -1
- package/templates/firebase/lib/i18n/es.i18n.json +3 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
- package/templates/firebase/pubspec.yaml +6 -4
- package/templates/firebase/web/index.html +7 -17
- package/templates/firebase/lib/firebase_options.dart +0 -75
package/bin/kasy.js
CHANGED
|
@@ -357,6 +357,7 @@ function buildProgram(language) {
|
|
|
357
357
|
.option('--web', 'Run on web — prints localhost URL in lime so you open it in your own browser (your extensions, your accounts)')
|
|
358
358
|
.option('--open', 'With --web: auto-launch a clean Chrome window (Flutter profile, no extensions) instead of just printing the URL')
|
|
359
359
|
.option('--web-port <port>', 'Fixed port for web (default 5555) — keeps the origin stable so Firebase Auth sessions persist between runs')
|
|
360
|
+
.option('--web-hostname <host>', 'Host for web (default localhost) — localhost is a Firebase-authorized domain by default, so Google sign-in works without console changes')
|
|
360
361
|
.option('-d, --device <id>', 'Run on specific device ID')
|
|
361
362
|
.option('--prod', 'Use production dart-defines (from launch.json)')
|
|
362
363
|
.option('--no-defines', 'Skip dart-defines from launch.json')
|
package/lib/commands/new.js
CHANGED
|
@@ -772,6 +772,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
772
772
|
} else if (key === 'auth-providers-warn') {
|
|
773
773
|
ps1.stop();
|
|
774
774
|
ui.log.warn(`${tr('new.firebase.interactive.authWarn')}\n${kleur.cyan(data?.url || '')}`);
|
|
775
|
+
} else if (key === 'auth-localhost-warn') {
|
|
776
|
+
ps1.stop();
|
|
777
|
+
ui.log.warn(`${tr('new.firebase.localhostWarn')}\n${kleur.cyan(data?.url || '')}`);
|
|
775
778
|
}
|
|
776
779
|
};
|
|
777
780
|
let setupResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
|
|
@@ -1179,6 +1182,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1179
1182
|
} else if (key === 'auth-google-warn') {
|
|
1180
1183
|
ps4.stop();
|
|
1181
1184
|
ui.log.warn(`${tr('new.firebase.existing.googleSignInManual')}\n${kleur.cyan(data?.url || '')}`);
|
|
1185
|
+
} else if (key === 'auth-localhost-warn') {
|
|
1186
|
+
ps4.stop();
|
|
1187
|
+
ui.log.warn(`${tr('new.firebase.localhostWarn')}\n${kleur.cyan(data?.url || '')}`);
|
|
1182
1188
|
}
|
|
1183
1189
|
},
|
|
1184
1190
|
});
|
|
@@ -1804,6 +1810,9 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1804
1810
|
if (appleResult.appleEnabled) {
|
|
1805
1811
|
printStepResult({ name: 'apple sign-in', ok: true }, language);
|
|
1806
1812
|
}
|
|
1813
|
+
if (appleResult.localhostAuthorized === false) {
|
|
1814
|
+
ui.log.warn(`${tr('new.firebase.localhostWarn')}\n${kleur.cyan(`https://console.firebase.google.com/project/${answers.firebaseProjectId}/authentication/settings`)}`);
|
|
1815
|
+
}
|
|
1807
1816
|
}
|
|
1808
1817
|
|
|
1809
1818
|
// APNs key (iOS push) is intentionally not mentioned here — it only becomes
|
package/lib/commands/run.js
CHANGED
|
@@ -292,6 +292,13 @@ async function runRun(directory, options = {}) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
if (isChromeTarget || isWebServerTarget) {
|
|
295
|
+
// Pin the host to `localhost` so the app's origin is always an authorized
|
|
296
|
+
// domain. Firebase Auth authorizes `localhost` by default but NOT
|
|
297
|
+
// `127.0.0.1`, and `flutter run -d chrome` would otherwise auto-launch
|
|
298
|
+
// Chrome at 127.0.0.1 — which breaks Google sign-in with
|
|
299
|
+
// [firebase_auth/unauthorized-domain]. Forcing localhost makes Google
|
|
300
|
+
// login work out of the box, no Firebase Console changes required.
|
|
301
|
+
deviceArgs.push('--web-hostname', options.webHostname || 'localhost');
|
|
295
302
|
// Pin a fixed port so the Chrome origin stays the same between runs.
|
|
296
303
|
// Firebase Auth persists sessions per-origin (IndexedDB) — a random port
|
|
297
304
|
// each run means the user gets logged out every restart.
|
|
@@ -343,11 +350,6 @@ async function runRun(directory, options = {}) {
|
|
|
343
350
|
|
|
344
351
|
printCompactHeader(t);
|
|
345
352
|
console.log(kleur.bold(`${t('run.launching')}${summary}`));
|
|
346
|
-
if (isWebServerTarget) {
|
|
347
|
-
const url = `http://localhost:${options.webPort || '5555'}`;
|
|
348
|
-
console.log(` ${paintLime(`✦ ${t('run.web.open')}: ${url}`)}`);
|
|
349
|
-
console.log(kleur.dim(` ${t('run.web.openHint')}`));
|
|
350
|
-
}
|
|
351
353
|
if (rcInfo && rcInfo.mode !== 'legacy') {
|
|
352
354
|
const mode = (options.rc || 'auto').toLowerCase();
|
|
353
355
|
let label;
|
|
@@ -361,8 +363,22 @@ async function runRun(directory, options = {}) {
|
|
|
361
363
|
}
|
|
362
364
|
console.log(kleur.dim(` ✦ ${t('run.updateHint.prefix')} `) + kleur.cyan('kasy update') + kleur.dim(` ${t('run.updateHint.suffix')}\n`));
|
|
363
365
|
|
|
366
|
+
// Print the open-in-browser URL only AFTER Flutter signals "ready" — otherwise
|
|
367
|
+
// the user clicks too early and lands on a blank/splash page because the
|
|
368
|
+
// web-server is still compiling.
|
|
369
|
+
const onReady = isWebServerTarget
|
|
370
|
+
? () => {
|
|
371
|
+
const url = `http://localhost:${options.webPort || '5555'}`;
|
|
372
|
+
const modifier = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
|
|
373
|
+
console.log('');
|
|
374
|
+
console.log(` ${paintLime(`✦ ${t('run.web.open')}: ${url}`)}`);
|
|
375
|
+
console.log(kleur.dim(` ${t('run.web.openHint', { modifier })}`));
|
|
376
|
+
console.log('');
|
|
377
|
+
}
|
|
378
|
+
: undefined;
|
|
379
|
+
|
|
364
380
|
try {
|
|
365
|
-
await spawnFlutterWithSpinner(args, projectDir, t, { raw: Boolean(options.raw) });
|
|
381
|
+
await spawnFlutterWithSpinner(args, projectDir, t, { raw: Boolean(options.raw), onReady });
|
|
366
382
|
} catch (err) {
|
|
367
383
|
if (err.code === 'ENOENT') {
|
|
368
384
|
throw new Error(t('run.error.flutterNotFound'));
|
|
@@ -659,6 +659,54 @@ async function checkBillingEnabled(projectId) {
|
|
|
659
659
|
return { ok: true, enabled: result.stdout.trim().toLowerCase() === 'true' };
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
+
/**
|
|
663
|
+
* Ensure `localhost` and `127.0.0.1` are in the project's authorized domains so
|
|
664
|
+
* Google/social sign-in works on Flutter web during local development.
|
|
665
|
+
*
|
|
666
|
+
* Projects created programmatically (via the Firebase Management API) do NOT get
|
|
667
|
+
* `localhost` seeded automatically — only `<project>.firebaseapp.com` and
|
|
668
|
+
* `<project>.web.app`. Without `localhost`, `signInWithPopup` fails with
|
|
669
|
+
* [firebase_auth/unauthorized-domain] when running `flutter run -d chrome` or
|
|
670
|
+
* `-d web-server`. The Firebase Console adds localhost by default; the API does
|
|
671
|
+
* not, so we add it here.
|
|
672
|
+
*
|
|
673
|
+
* Read-modify-write so the existing default domains are preserved — a blind PATCH
|
|
674
|
+
* with only [localhost] would wipe firebaseapp.com / web.app. Idempotent: it's a
|
|
675
|
+
* no-op when both entries are already present.
|
|
676
|
+
*
|
|
677
|
+
* @returns {{ ok: boolean, added?: string[], error?: string }}
|
|
678
|
+
*/
|
|
679
|
+
async function ensureLocalhostAuthorizedDomains(projectId, token) {
|
|
680
|
+
const base = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/config`;
|
|
681
|
+
const headers = {
|
|
682
|
+
Authorization: `Bearer ${token}`,
|
|
683
|
+
'Content-Type': 'application/json',
|
|
684
|
+
'X-Goog-User-Project': projectId,
|
|
685
|
+
};
|
|
686
|
+
// 1. Read the current authorized domains.
|
|
687
|
+
const getRes = await fetch(base, { headers });
|
|
688
|
+
if (!getRes.ok) {
|
|
689
|
+
const text = await getRes.text();
|
|
690
|
+
return { ok: false, error: `${getRes.status}: ${text}` };
|
|
691
|
+
}
|
|
692
|
+
const config = await getRes.json();
|
|
693
|
+
const current = Array.isArray(config.authorizedDomains) ? config.authorizedDomains : [];
|
|
694
|
+
const required = ['localhost', '127.0.0.1'];
|
|
695
|
+
const missing = required.filter((d) => !current.includes(d));
|
|
696
|
+
if (missing.length === 0) return { ok: true, added: [] };
|
|
697
|
+
// 2. Merge and write back, keeping every domain that was already there.
|
|
698
|
+
const patchRes = await fetch(`${base}?updateMask=authorizedDomains`, {
|
|
699
|
+
method: 'PATCH',
|
|
700
|
+
headers,
|
|
701
|
+
body: JSON.stringify({ authorizedDomains: [...current, ...missing] }),
|
|
702
|
+
});
|
|
703
|
+
if (!patchRes.ok) {
|
|
704
|
+
const text = await patchRes.text();
|
|
705
|
+
return { ok: false, error: `${patchRes.status}: ${text}` };
|
|
706
|
+
}
|
|
707
|
+
return { ok: true, added: missing };
|
|
708
|
+
}
|
|
709
|
+
|
|
662
710
|
/**
|
|
663
711
|
* Enable Firebase Auth sign-in providers: Email/Password, Anonymous and Google.
|
|
664
712
|
* Uses the Identity Toolkit Admin v2 REST API with gcloud credentials.
|
|
@@ -807,7 +855,18 @@ async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 1
|
|
|
807
855
|
// Network error — skip silently.
|
|
808
856
|
}
|
|
809
857
|
|
|
810
|
-
|
|
858
|
+
// Step 5: Authorize localhost / 127.0.0.1 so web sign-in works in dev.
|
|
859
|
+
// Best effort — failure here doesn't block the providers we just enabled.
|
|
860
|
+
const domainsResult = await ensureLocalhostAuthorizedDomains(projectId, token);
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
ok: true,
|
|
864
|
+
googleSignInSkipped,
|
|
865
|
+
googleEnabled,
|
|
866
|
+
appleEnabled,
|
|
867
|
+
localhostAuthorized: domainsResult.ok,
|
|
868
|
+
authorizedDomainsAdded: domainsResult.added || [],
|
|
869
|
+
};
|
|
811
870
|
}
|
|
812
871
|
const text = await res.text();
|
|
813
872
|
lastError = `${res.status}: ${text}`;
|
|
@@ -818,7 +877,24 @@ async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 1
|
|
|
818
877
|
}
|
|
819
878
|
break;
|
|
820
879
|
}
|
|
821
|
-
|
|
880
|
+
// The provider PATCH failed after retries. Still try to authorize localhost as a
|
|
881
|
+
// best-effort: the auth config may already exist (a transient failure here, or
|
|
882
|
+
// auth initialized elsewhere), and web sign-in depends on localhost being on the
|
|
883
|
+
// list. This keeps the function self-sufficient instead of relying on a later
|
|
884
|
+
// re-invocation to backfill localhost.
|
|
885
|
+
let fallbackDomains = { ok: false, added: [] };
|
|
886
|
+
try {
|
|
887
|
+
const freshToken = await getAccessToken();
|
|
888
|
+
fallbackDomains = await ensureLocalhostAuthorizedDomains(projectId, freshToken);
|
|
889
|
+
} catch (_) {
|
|
890
|
+
// Best effort — ignore.
|
|
891
|
+
}
|
|
892
|
+
return {
|
|
893
|
+
ok: false,
|
|
894
|
+
error: lastError,
|
|
895
|
+
localhostAuthorized: fallbackDomains.ok,
|
|
896
|
+
authorizedDomainsAdded: fallbackDomains.added || [],
|
|
897
|
+
};
|
|
822
898
|
}
|
|
823
899
|
|
|
824
900
|
/**
|
|
@@ -907,6 +983,13 @@ async function setupFromScratch(appName, bundleId, options = {}) {
|
|
|
907
983
|
url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
|
|
908
984
|
});
|
|
909
985
|
}
|
|
986
|
+
// Providers were enabled but localhost couldn't be authorized — warn so the user
|
|
987
|
+
// isn't surprised by [firebase_auth/unauthorized-domain] on web sign-in.
|
|
988
|
+
if (authResult.ok && authResult.localhostAuthorized === false) {
|
|
989
|
+
onProgress('auth-localhost-warn', {
|
|
990
|
+
url: `https://console.firebase.google.com/project/${projectId}/authentication/settings`,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
910
993
|
|
|
911
994
|
onProgress('firestore');
|
|
912
995
|
const firestoreResult = await createFirestoreDatabase(projectId, region);
|
|
@@ -1022,6 +1105,11 @@ async function setupExistingProject(projectId, options = {}) {
|
|
|
1022
1105
|
url: `https://console.firebase.google.com/project/${projectId}/authentication/providers`,
|
|
1023
1106
|
});
|
|
1024
1107
|
}
|
|
1108
|
+
if (authResult.ok && authResult.localhostAuthorized === false) {
|
|
1109
|
+
onProgress('auth-localhost-warn', {
|
|
1110
|
+
url: `https://console.firebase.google.com/project/${projectId}/authentication/settings`,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1025
1113
|
|
|
1026
1114
|
onProgress('firestore');
|
|
1027
1115
|
const firestoreResult = await createFirestoreDatabase(projectId);
|
|
@@ -1114,6 +1202,7 @@ module.exports = {
|
|
|
1114
1202
|
applyStorageCors,
|
|
1115
1203
|
checkBillingEnabled,
|
|
1116
1204
|
enableAuthProviders,
|
|
1205
|
+
ensureLocalhostAuthorizedDomains,
|
|
1117
1206
|
listBillingAccounts,
|
|
1118
1207
|
listGcpOrganizations,
|
|
1119
1208
|
checkGcloudAuth,
|
package/lib/scaffold/engine.js
CHANGED
|
@@ -41,6 +41,11 @@ const ALWAYS_EXCLUDE_BASENAMES = new Set([
|
|
|
41
41
|
'google-services.json', // Firebase Android config — client must generate their own
|
|
42
42
|
'GoogleService-Info.plist', // Firebase iOS config — client must generate their own
|
|
43
43
|
'firebase_options_dev.dart', // Dev Firebase options — internal only
|
|
44
|
+
'firebase_options.dart', // Kit's own Firebase options — dead code in generated
|
|
45
|
+
// projects (main.dart imports firebase_options_dev.dart,
|
|
46
|
+
// which flutterfire regenerates per-user). Shipping it
|
|
47
|
+
// would leak the kit's project id (fir-kit-8e56b) into
|
|
48
|
+
// every generated app. Never copy it.
|
|
44
49
|
]);
|
|
45
50
|
|
|
46
51
|
// File extensions that may contain generated Dart code — skip these
|
package/lib/scaffold/generate.js
CHANGED
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
writeEnvExample,
|
|
49
49
|
writeEnvFileIfMissing,
|
|
50
50
|
writeFirebaserc,
|
|
51
|
+
cleanFirebaseJsonKitRefs,
|
|
51
52
|
writeFeaturesConfig,
|
|
52
53
|
writeKitSetup,
|
|
53
54
|
writeMakefile,
|
|
@@ -362,6 +363,9 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
362
363
|
}
|
|
363
364
|
|
|
364
365
|
await writeFirebaserc(targetDir, firebaseProjectId);
|
|
366
|
+
// Drop the stale firebase.json entry that still points at the kit project
|
|
367
|
+
// (the dead lib/firebase_options.dart we no longer ship).
|
|
368
|
+
await cleanFirebaseJsonKitRefs(targetDir);
|
|
365
369
|
|
|
366
370
|
// ── 3. Post-build específico do backend ────────────────────────────────────
|
|
367
371
|
if (postBuild) {
|
|
@@ -229,6 +229,39 @@ async function writeFirebaserc(projectDir, firebaseProjectId) {
|
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Strip the kit's own Firebase project from the generated firebase.json.
|
|
234
|
+
*
|
|
235
|
+
* flutterfire configure rewrites the android/ios/web platform entries for the
|
|
236
|
+
* user's project, but it keys the dart entry by its --out path
|
|
237
|
+
* (lib/firebase_options_dev.dart) and leaves the template's stale
|
|
238
|
+
* `flutter.platforms.dart["lib/firebase_options.dart"]` entry untouched — which
|
|
239
|
+
* still points at the kit project (fir-kit-8e56b). We no longer ship that file
|
|
240
|
+
* (see ALWAYS_EXCLUDE_BASENAMES), so this drops the dead entry to guarantee the
|
|
241
|
+
* user's firebase.json never references the kit project.
|
|
242
|
+
*
|
|
243
|
+
* Best effort: no-ops when firebase.json is missing, unparseable, or already clean.
|
|
244
|
+
*
|
|
245
|
+
* @param {string} projectDir
|
|
246
|
+
* @returns {Promise<{ ok: boolean, changed: boolean }>}
|
|
247
|
+
*/
|
|
248
|
+
async function cleanFirebaseJsonKitRefs(projectDir) {
|
|
249
|
+
const firebaseJsonPath = path.join(projectDir, 'firebase.json');
|
|
250
|
+
if (!(await fs.pathExists(firebaseJsonPath))) return { ok: true, changed: false };
|
|
251
|
+
let config;
|
|
252
|
+
try {
|
|
253
|
+
config = await fs.readJson(firebaseJsonPath);
|
|
254
|
+
} catch {
|
|
255
|
+
return { ok: false, changed: false };
|
|
256
|
+
}
|
|
257
|
+
const dartMap = config && config.flutter && config.flutter.platforms && config.flutter.platforms.dart;
|
|
258
|
+
if (!dartMap || typeof dartMap !== 'object') return { ok: true, changed: false };
|
|
259
|
+
if (!('lib/firebase_options.dart' in dartMap)) return { ok: true, changed: false };
|
|
260
|
+
delete dartMap['lib/firebase_options.dart'];
|
|
261
|
+
await fs.writeJson(firebaseJsonPath, config, { spaces: 2 });
|
|
262
|
+
return { ok: true, changed: true };
|
|
263
|
+
}
|
|
264
|
+
|
|
232
265
|
/**
|
|
233
266
|
* Write environnements.dart overrides for backend-specific config.
|
|
234
267
|
*
|
|
@@ -812,7 +845,10 @@ async function writeMainDart(projectDir, backend, modules, packageName, options
|
|
|
812
845
|
lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
|
|
813
846
|
lines.push(` ),`);
|
|
814
847
|
lines.push(` ProdEnvironment() => Firebase.initializeApp(`);
|
|
815
|
-
lines.push(` //
|
|
848
|
+
lines.push(` // SETUP REQUIRED: For production, create a separate Firebase project and run:`);
|
|
849
|
+
lines.push(` // flutterfire configure --project=<your-prod-project> --out=lib/firebase_options_prod.dart`);
|
|
850
|
+
lines.push(` // Then import firebase_options_prod.dart and use it here instead.`);
|
|
851
|
+
lines.push(` // Reusing the dev project in production is only safe for early-stage development.`);
|
|
816
852
|
lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
|
|
817
853
|
lines.push(` ),`);
|
|
818
854
|
lines.push(` };`);
|
|
@@ -1687,6 +1723,7 @@ module.exports = {
|
|
|
1687
1723
|
writeEnvExample,
|
|
1688
1724
|
writeEnvFileIfMissing,
|
|
1689
1725
|
writeFirebaserc,
|
|
1726
|
+
cleanFirebaseJsonKitRefs,
|
|
1690
1727
|
writeEnvironnementsOverrides,
|
|
1691
1728
|
writeFeaturesConfig,
|
|
1692
1729
|
writeKitSetup,
|
package/lib/utils/flutter-run.js
CHANGED
|
@@ -125,9 +125,10 @@ function relLogPath(logPath) {
|
|
|
125
125
|
function spawnFlutterWithSpinner(args, projectDir, t, options = {}) {
|
|
126
126
|
const raw = Boolean(options.raw) || !process.stdout.isTTY;
|
|
127
127
|
const log = openRunLog(projectDir);
|
|
128
|
+
const onReady = typeof options.onReady === 'function' ? options.onReady : null;
|
|
128
129
|
|
|
129
|
-
if (raw) return spawnRaw(args, projectDir, t, log);
|
|
130
|
-
return spawnWithSpinner(args, projectDir, t, log);
|
|
130
|
+
if (raw) return spawnRaw(args, projectDir, t, log, onReady);
|
|
131
|
+
return spawnWithSpinner(args, projectDir, t, log, onReady);
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
/**
|
|
@@ -137,16 +138,24 @@ function spawnFlutterWithSpinner(args, projectDir, t, options = {}) {
|
|
|
137
138
|
* Stdin is forwarded directly so hot-reload keys (r/R/q) work without any
|
|
138
139
|
* raw-mode toggling — the user is already on a "dumb" terminal here.
|
|
139
140
|
*/
|
|
140
|
-
function spawnRaw(args, projectDir, t, log) {
|
|
141
|
+
function spawnRaw(args, projectDir, t, log, onReady) {
|
|
141
142
|
return new Promise((resolve, reject) => {
|
|
142
143
|
const proc = spawn('flutter', args, {
|
|
143
144
|
cwd: projectDir,
|
|
144
145
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
145
146
|
});
|
|
146
147
|
|
|
148
|
+
let readyFired = false;
|
|
149
|
+
const fireReady = () => {
|
|
150
|
+
if (readyFired || !onReady) return;
|
|
151
|
+
readyFired = true;
|
|
152
|
+
try { onReady(); } catch (_) {}
|
|
153
|
+
};
|
|
154
|
+
|
|
147
155
|
const tee = (chunk, sink) => {
|
|
148
156
|
sink.write(chunk);
|
|
149
157
|
if (log) log.stream.write(stripAnsi(chunk.toString()));
|
|
158
|
+
if (!readyFired && FLUTTER_READY_RE.test(chunk.toString())) fireReady();
|
|
150
159
|
};
|
|
151
160
|
|
|
152
161
|
proc.stdout.on('data', (c) => tee(c, process.stdout));
|
|
@@ -181,7 +190,7 @@ function spawnRaw(args, projectDir, t, log) {
|
|
|
181
190
|
* ready. On early exit, the buffer is replayed so the user sees the real
|
|
182
191
|
* Flutter error.
|
|
183
192
|
*/
|
|
184
|
-
function spawnWithSpinner(args, projectDir, t, log) {
|
|
193
|
+
function spawnWithSpinner(args, projectDir, t, log, onReady) {
|
|
185
194
|
return new Promise((resolve, reject) => {
|
|
186
195
|
const proc = spawn('flutter', args, {
|
|
187
196
|
cwd: projectDir,
|
|
@@ -208,6 +217,9 @@ function spawnWithSpinner(args, projectDir, t, log) {
|
|
|
208
217
|
clearInterval(tick);
|
|
209
218
|
const total = formatElapsed(startTime);
|
|
210
219
|
spinner.stop(`${t('run.spinner.ready')} ${kleur.dim(`[${total}]`)}`);
|
|
220
|
+
if (onReady) {
|
|
221
|
+
try { onReady(); } catch (_) {}
|
|
222
|
+
}
|
|
211
223
|
for (const chunk of buffer) process.stdout.write(chunk);
|
|
212
224
|
buffer.length = 0;
|
|
213
225
|
// Pipe stdin so the user can type r / R / q to control Flutter.
|
|
@@ -633,6 +633,7 @@ module.exports = {
|
|
|
633
633
|
'new.firebase.interactive.billingWaiting': 'Checking Blaze status...',
|
|
634
634
|
'new.firebase.interactive.billingTimeout': 'Blaze plan not confirmed after timeout. Deploy skipped — run manually when ready.',
|
|
635
635
|
'new.firebase.interactive.authWarn': 'Could not enable Email/Password and Anonymous auth automatically. Enable manually:',
|
|
636
|
+
'new.firebase.localhostWarn': 'Could not authorize localhost for web sign-in. To test login in the browser, add "localhost" under Authorized domains:',
|
|
636
637
|
'new.firebase.existing.apisFailed': 'Could not activate APIs:',
|
|
637
638
|
'new.firebase.existing.googleSignInManual': 'Google Sign-In: enable manually in Authentication → Sign-in method → Google',
|
|
638
639
|
|
|
@@ -717,7 +718,7 @@ module.exports = {
|
|
|
717
718
|
'cli.command.run.description': 'Run your app on phone, simulator, or browser',
|
|
718
719
|
'run.launching': 'Launching Flutter app...',
|
|
719
720
|
'run.web.open': 'Open in your browser',
|
|
720
|
-
'run.web.openHint': '
|
|
721
|
+
'run.web.openHint': '{modifier}+click the link above (or copy/paste it). Use --open to auto-launch a dedicated Flutter Chrome window instead.',
|
|
721
722
|
'run.prompt.pickDevice': 'Multiple devices detected. Which one do you want to run on?',
|
|
722
723
|
'run.warn.nothingSelected': 'No device selected.',
|
|
723
724
|
'run.updateHint.prefix': 'Project improvements available —',
|
|
@@ -633,6 +633,7 @@ module.exports = {
|
|
|
633
633
|
'new.firebase.interactive.billingWaiting': 'Verificando estado del Blaze...',
|
|
634
634
|
'new.firebase.interactive.billingTimeout': 'Plan Blaze no confirmado tras el tiempo límite. Despliegue omitido — ejecuta manualmente cuando estés listo.',
|
|
635
635
|
'new.firebase.interactive.authWarn': 'No se pudo activar Email/Contraseña y Anónimo automáticamente. Actívalos manualmente:',
|
|
636
|
+
'new.firebase.localhostWarn': 'No se pudo autorizar localhost para el inicio de sesión web. Para probar el login en el navegador, agrega "localhost" en Authorized domains:',
|
|
636
637
|
'new.firebase.existing.apisFailed': 'No se pudieron activar las APIs:',
|
|
637
638
|
'new.firebase.existing.googleSignInManual': 'Google Sign-In: activa manualmente en Authentication → Sign-in method → Google',
|
|
638
639
|
|
|
@@ -750,7 +751,7 @@ module.exports = {
|
|
|
750
751
|
'reset.warn.launcherNotDetected': 'Launcher por defecto no detectado — saltando limpieza de caché.',
|
|
751
752
|
'run.launching': 'Iniciando app Flutter...',
|
|
752
753
|
'run.web.open': 'Abre en tu navegador',
|
|
753
|
-
'run.web.openHint': '
|
|
754
|
+
'run.web.openHint': '{modifier}+clic en el enlace de arriba (o copia/pega). Usa --open para abrir automáticamente una ventana dedicada de Chrome de Flutter.',
|
|
754
755
|
'run.prompt.pickDevice': 'Varios dispositivos detectados. ¿En cuál quieres ejecutar?',
|
|
755
756
|
'run.warn.nothingSelected': 'Ningún dispositivo seleccionado.',
|
|
756
757
|
'run.updateHint.prefix': 'Mejoras disponibles para el proyecto —',
|
|
@@ -633,6 +633,7 @@ module.exports = {
|
|
|
633
633
|
'new.firebase.interactive.billingWaiting': 'Verificando status do Blaze...',
|
|
634
634
|
'new.firebase.interactive.billingTimeout': 'Plano Blaze não confirmado apos o tempo limite. Deploy ignorado — rode manualmente quando estiver pronto.',
|
|
635
635
|
'new.firebase.interactive.authWarn': 'Não foi possível ativar Email/Senha e Anônimo automaticamente. Ative manualmente:',
|
|
636
|
+
'new.firebase.localhostWarn': 'Não foi possível autorizar localhost para login web. Se for testar login no navegador, adicione "localhost" em Authorized domains:',
|
|
636
637
|
'new.firebase.existing.apisFailed': 'Não foi possível ativar APIs:',
|
|
637
638
|
'new.firebase.existing.googleSignInManual': 'Google Sign-In: ative manualmente em Authentication → Sign-in method → Google',
|
|
638
639
|
|
|
@@ -750,7 +751,7 @@ module.exports = {
|
|
|
750
751
|
'reset.warn.launcherNotDetected': 'Launcher padrão não detectado — pulando limpeza de cache.',
|
|
751
752
|
'run.launching': 'Iniciando app Flutter...',
|
|
752
753
|
'run.web.open': 'Abra no seu navegador',
|
|
753
|
-
'run.web.openHint': '
|
|
754
|
+
'run.web.openHint': '{modifier}+clique no link acima (ou copie e cole). Use --open pra abrir automaticamente num Chrome dedicado do Flutter.',
|
|
754
755
|
'run.prompt.pickDevice': 'Vários dispositivos detectados. Em qual deles rodar?',
|
|
755
756
|
'run.warn.nothingSelected': 'Nenhum dispositivo selecionado.',
|
|
756
757
|
'run.updateHint.prefix': 'Melhorias disponíveis para o projeto —',
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<item name="android:windowFullscreen">true</item>
|
|
7
7
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
|
8
8
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
9
|
-
<item name="android:windowSplashScreenBackground">#
|
|
9
|
+
<item name="android:windowSplashScreenBackground">#060608</item>
|
|
10
10
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
|
11
11
|
</style>
|
|
12
12
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<item name="android:windowFullscreen">true</item>
|
|
7
7
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
|
8
8
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
9
|
-
<item name="android:windowSplashScreenBackground">#
|
|
9
|
+
<item name="android:windowSplashScreenBackground">#F7F7F7</item>
|
|
10
10
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
|
11
11
|
</style>
|
|
12
12
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
CHANGED
|
Binary file
|
package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png
CHANGED
|
Binary file
|
|
@@ -987,6 +987,14 @@ class _KasyDatePickerState extends State<KasyDatePicker>
|
|
|
987
987
|
hasErrorText ? widget.errorText : widget.description;
|
|
988
988
|
final Color labelColor = hasInvalidState ? c.error : c.fieldLabel;
|
|
989
989
|
|
|
990
|
+
// Kit-wide rule: disabled colors are softened by blending toward the
|
|
991
|
+
// surface (opaque), never by raw opacity. See KasyButton / KasyTextField.
|
|
992
|
+
final Color blendSurface = c.surface;
|
|
993
|
+
Color dimDisabled(Color base, {double alpha = 0.55}) {
|
|
994
|
+
if (!isDisabled) return base;
|
|
995
|
+
return Color.alphaBlend(base.withValues(alpha: alpha), blendSurface);
|
|
996
|
+
}
|
|
997
|
+
|
|
990
998
|
return Column(
|
|
991
999
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
992
1000
|
mainAxisSize: MainAxisSize.min,
|
|
@@ -1000,9 +1008,7 @@ class _KasyDatePickerState extends State<KasyDatePicker>
|
|
|
1000
1008
|
Text(
|
|
1001
1009
|
widget.label!,
|
|
1002
1010
|
style: context.textTheme.bodyMedium?.copyWith(
|
|
1003
|
-
color:
|
|
1004
|
-
? labelColor.withValues(alpha: 0.46)
|
|
1005
|
-
: labelColor,
|
|
1011
|
+
color: dimDisabled(labelColor),
|
|
1006
1012
|
fontWeight: FontWeight.w500,
|
|
1007
1013
|
),
|
|
1008
1014
|
),
|
|
@@ -1066,11 +1072,11 @@ class _KasyDatePickerState extends State<KasyDatePicker>
|
|
|
1066
1072
|
child: Text(
|
|
1067
1073
|
footerText,
|
|
1068
1074
|
style: context.textTheme.bodySmall?.copyWith(
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1075
|
+
// Soften the helper text via the kit's blend-toward-surface
|
|
1076
|
+
// rule so the field as a whole reads as disabled without any
|
|
1077
|
+
// transparency.
|
|
1078
|
+
color:
|
|
1079
|
+
dimDisabled(hasErrorText ? c.error : c.muted, alpha: 0.45),
|
|
1074
1080
|
),
|
|
1075
1081
|
),
|
|
1076
1082
|
),
|