kasy-cli 1.13.0 → 1.15.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 +140 -12
- package/lib/commands/add.js +2 -2
- package/lib/commands/codemagic.js +11 -4
- package/lib/commands/deploy.js +3 -3
- package/lib/commands/favicon.js +115 -0
- package/lib/commands/icon.js +143 -0
- package/lib/commands/ios.js +28 -7
- package/lib/commands/new.js +8 -20
- package/lib/commands/remove.js +1 -1
- package/lib/commands/reset.js +385 -0
- package/lib/commands/run.js +24 -17
- package/lib/commands/splash.js +14 -4
- package/lib/commands/update.js +1 -1
- package/lib/scaffold/backends/api/patch/README.md +1 -1
- package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +53 -0
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +11 -1
- package/lib/scaffold/backends/firebase/tokens.js +2 -2
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -2
- package/lib/scaffold/backends/supabase/migrations/20240101000011_dedupe_device_tokens.sql +34 -0
- package/lib/scaffold/backends/supabase/patch/README.md +1 -1
- package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +43 -0
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +11 -1
- package/lib/utils/apple-release.js +115 -16
- package/lib/utils/checks.js +45 -107
- package/lib/utils/debug.js +75 -0
- package/lib/utils/flutter-run.js +173 -0
- package/lib/utils/friendly-error.js +91 -0
- package/lib/utils/i18n/messages-en.js +970 -0
- package/lib/utils/i18n/messages-es.js +968 -0
- package/lib/utils/i18n/messages-pt.js +968 -0
- package/lib/utils/i18n.js +21 -2483
- package/lib/utils/mobile-identity.js +35 -0
- package/lib/utils/png-padding.js +120 -0
- package/lib/utils/ui.js +114 -0
- package/package.json +8 -4
- 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/build.gradle.kts +10 -1
- package/templates/firebase/android/app/src/main/AndroidManifest.xml +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +25 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +161 -11
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +15 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +12 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +5 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +17 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +5 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.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/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.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-mdpi/android12splash.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-xxhdpi/android12splash.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-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.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/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.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/ic_launcher_background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_loading.xml +8 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +53 -0
- package/templates/firebase/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
- package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/xml/mywidget_info.xml +9 -3
- package/templates/firebase/assets/images/favicon.png +0 -0
- package/templates/firebase/assets/images/icon.png +0 -0
- package/templates/firebase/assets/images/icon_android.png +0 -0
- package/templates/firebase/assets/images/icon_foreground_empty.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/firestore.indexes.json +10 -0
- package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +3 -0
- package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +17 -1
- package/templates/firebase/functions/src/index.ts +1 -0
- package/templates/firebase/functions/src/notifications/device_triggers.ts +58 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +116 -33
- package/templates/firebase/ios/Runner/AppDelegate.swift +17 -1
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- package/templates/firebase/ios/Runner/Info.plist +2 -2
- package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +1 -1
- package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +1 -1
- package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +1 -1
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +88 -57
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +116 -74
- package/templates/firebase/lib/components/kasy_button.dart +8 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +431 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +18 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +73 -19
- package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +22 -8
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +16 -4
- package/templates/firebase/lib/core/states/components/maybeshow_component.dart +4 -8
- package/templates/firebase/lib/core/states/user_state_notifier.dart +13 -1
- package/templates/firebase/lib/features/home/home_components_page.dart +1 -1
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +316 -93
- package/templates/firebase/lib/features/home/home_page.dart +0 -6
- package/templates/firebase/lib/features/notifications/api/device_api.dart +57 -0
- package/templates/firebase/lib/features/notifications/providers/models/notification.dart +11 -1
- package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +9 -0
- package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +1 -4
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +28 -8
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +44 -11
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +31 -29
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +21 -5
- package/templates/firebase/lib/i18n/en.i18n.json +4 -1
- package/templates/firebase/lib/i18n/es.i18n.json +4 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +4 -1
- package/templates/firebase/pubspec.yaml +10 -3
- package/templates/firebase/test/features/notifications/data/device_api_fake.dart +9 -0
- package/templates/firebase/web/favicon.png +0 -0
- package/templates/firebase/web/icons/Icon-192.png +0 -0
- package/templates/firebase/web/icons/Icon-512.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
- package/templates/firebase/web/index.html +9 -0
- package/templates/firebase/web/manifest.json +3 -3
- package/templates/firebase/assets/images/app_icon.png +0 -0
- package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
- package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +0 -88
- package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +0 -144
package/lib/commands/new.js
CHANGED
|
@@ -15,22 +15,10 @@ const path = require('node:path');
|
|
|
15
15
|
const crypto = require('node:crypto');
|
|
16
16
|
const kleur = require('kleur');
|
|
17
17
|
|
|
18
|
-
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
19
|
-
|
|
20
18
|
function generateWebhookKey() {
|
|
21
19
|
return 'rc_wh_' + crypto.randomBytes(16).toString('hex');
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
async function waitWithCountdown(seconds, label) {
|
|
27
|
-
for (let i = seconds; i > 0; i--) {
|
|
28
|
-
process.stdout.write(`\r ${label} ${i}s… `);
|
|
29
|
-
await sleep(1000);
|
|
30
|
-
}
|
|
31
|
-
process.stdout.write(`\r ${label} pronto! \n`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
22
|
function openUrl(url) {
|
|
35
23
|
try {
|
|
36
24
|
const { exec } = require('node:child_process');
|
|
@@ -716,7 +704,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
716
704
|
const selectedOrgId = await promptOrganizationIfNeeded(tr, cancel);
|
|
717
705
|
let selectedBillingId = await promptBillingAccountIfNeeded(tr, cancel);
|
|
718
706
|
ui.log.info(`${tr('new.firebase.create.estimatedTime')}\n${tr('new.internet.warning')}`);
|
|
719
|
-
const ps1 = ui.
|
|
707
|
+
const ps1 = ui.makeTimedStepper();
|
|
720
708
|
ps1.next(tr('new.firebase.create.creating'));
|
|
721
709
|
const setupResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
|
|
722
710
|
includeWeb: firebaseIncludeWeb,
|
|
@@ -796,7 +784,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
796
784
|
}
|
|
797
785
|
ui.log.message(tr('new.firebase.create.billingRetry.retrying'));
|
|
798
786
|
selectedBillingId = await promptBillingAccountIfNeeded(tr, cancel);
|
|
799
|
-
const ps2 = ui.
|
|
787
|
+
const ps2 = ui.makeTimedStepper();
|
|
800
788
|
ps2.next(tr('new.firebase.create.creating'));
|
|
801
789
|
lastResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
|
|
802
790
|
includeWeb: firebaseIncludeWeb,
|
|
@@ -889,7 +877,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
889
877
|
const selectedOrgId = await promptOrganizationIfNeeded(tr, cancel);
|
|
890
878
|
const selectedBillingId = await promptBillingAccountIfNeeded(tr, cancel);
|
|
891
879
|
ui.log.info(tr('new.internet.warning'));
|
|
892
|
-
const ps3 = ui.
|
|
880
|
+
const ps3 = ui.makeTimedStepper();
|
|
893
881
|
ps3.next(tr('new.firebase.create.creatingPush'));
|
|
894
882
|
const setupResult = await setupFromScratch(core.appName.trim(), core.bundleId.trim(), {
|
|
895
883
|
includeWeb: true,
|
|
@@ -990,7 +978,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
990
978
|
});
|
|
991
979
|
supabaseDbPassword = dbPassword;
|
|
992
980
|
ui.log.info(tr('new.internet.warning'));
|
|
993
|
-
const createSpinner = ui.
|
|
981
|
+
const createSpinner = ui.timedSpinner();
|
|
994
982
|
createSpinner.start(tr('new.supabase.creating'));
|
|
995
983
|
supabaseCreateResult = await createProjectAndGetKeys(
|
|
996
984
|
core.appName.trim().replace(/\s+/g, '-').toLowerCase(),
|
|
@@ -1087,7 +1075,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1087
1075
|
|
|
1088
1076
|
// ── Firebase existing project: enable APIs + create Firestore/Storage ───
|
|
1089
1077
|
if (backend === 'firebase' && firebaseSetupMode === 'existing' && core.firebaseProjectId) {
|
|
1090
|
-
const ps4 = ui.
|
|
1078
|
+
const ps4 = ui.makeTimedStepper();
|
|
1091
1079
|
ps4.next(stepProgress('enable-apis', language));
|
|
1092
1080
|
const existingSetup = await setupExistingProject(core.firebaseProjectId, {
|
|
1093
1081
|
onProgress: (key, data) => {
|
|
@@ -1388,7 +1376,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1388
1376
|
// Stepper shows each step closing as ✦ and the next starting as ⠙ — so the
|
|
1389
1377
|
// user gets explicit "X done → Y starting" feedback instead of a single
|
|
1390
1378
|
// spinner with a mutating message.
|
|
1391
|
-
const stepper = ui.
|
|
1379
|
+
const stepper = ui.makeTimedStepper();
|
|
1392
1380
|
// First step started here so even silent prep work shows progress.
|
|
1393
1381
|
stepper.next(stepProgress('project-setup', language));
|
|
1394
1382
|
|
|
@@ -1512,7 +1500,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1512
1500
|
// ── FCM Service Account key (best effort via gcloud — Firebase uses ADC, Supabase needs JSON) ──
|
|
1513
1501
|
let fcmServiceAccountJson = null;
|
|
1514
1502
|
if (answers.firebaseProjectId) {
|
|
1515
|
-
const fcmSpinner = ui.
|
|
1503
|
+
const fcmSpinner = ui.timedSpinner();
|
|
1516
1504
|
fcmSpinner.start(tr('new.fcm.generating'));
|
|
1517
1505
|
const fcmResult = await createFcmServiceAccountKey(answers.firebaseProjectId);
|
|
1518
1506
|
fcmSpinner.stop(tr('new.fcm.generating'));
|
|
@@ -1562,7 +1550,7 @@ async function runNew(directory, { language: langHint = null, backend: backendHi
|
|
|
1562
1550
|
|
|
1563
1551
|
// ── API: FCM Service Account key — save to .kasy/ for server configuration ──
|
|
1564
1552
|
if (backend === 'api' && answers.firebaseProjectId) {
|
|
1565
|
-
const fcmSpinner = ui.
|
|
1553
|
+
const fcmSpinner = ui.timedSpinner();
|
|
1566
1554
|
fcmSpinner.start(tr('new.fcm.generating'));
|
|
1567
1555
|
const fcmResult = await createFcmServiceAccountKey(answers.firebaseProjectId);
|
|
1568
1556
|
fcmSpinner.stop(tr('new.fcm.generating'));
|
package/lib/commands/remove.js
CHANGED
|
@@ -444,7 +444,7 @@ async function runRemove(module, options = {}) {
|
|
|
444
444
|
'revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback',
|
|
445
445
|
].includes(normalized);
|
|
446
446
|
if (needsBuildRunner) {
|
|
447
|
-
const spinner = ui.
|
|
447
|
+
const spinner = ui.timedSpinner();
|
|
448
448
|
spinner.start(t('remove.buildRunner'));
|
|
449
449
|
try {
|
|
450
450
|
await execAsync(
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const { spawnSync } = require('node:child_process');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const kleur = require('kleur');
|
|
5
|
+
const ui = require('../utils/ui');
|
|
6
|
+
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
7
|
+
const { printCompactHeader } = require('../utils/brand');
|
|
8
|
+
const { readBundleId, readPackageName } = require('../utils/mobile-identity');
|
|
9
|
+
const { spawnFlutterWithSpinner } = require('../utils/flutter-run');
|
|
10
|
+
|
|
11
|
+
function runCmd(cmd, args) {
|
|
12
|
+
const res = spawnSync(cmd, args, { encoding: 'utf8' });
|
|
13
|
+
return {
|
|
14
|
+
code: res.status,
|
|
15
|
+
stdout: (res.stdout || '').trim(),
|
|
16
|
+
stderr: (res.stderr || '').trim(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function listFlutterDevices(projectDir) {
|
|
21
|
+
const res = spawnSync('flutter', ['devices', '--machine'], {
|
|
22
|
+
cwd: projectDir,
|
|
23
|
+
encoding: 'utf8',
|
|
24
|
+
});
|
|
25
|
+
if (res.status !== 0) return [];
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(res.stdout);
|
|
28
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function classifyTarget(device) {
|
|
35
|
+
const platform = (device.targetPlatform || '').toLowerCase();
|
|
36
|
+
if (platform === 'ios') {
|
|
37
|
+
return device.emulator ? 'ios-simulator' : 'ios-device';
|
|
38
|
+
}
|
|
39
|
+
if (platform.startsWith('android')) {
|
|
40
|
+
return device.emulator ? 'android-emulator' : 'android-device';
|
|
41
|
+
}
|
|
42
|
+
if (platform.startsWith('web')) {
|
|
43
|
+
return 'web';
|
|
44
|
+
}
|
|
45
|
+
return 'unknown';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function filterDevices(devices, options) {
|
|
49
|
+
return devices.filter((d) => {
|
|
50
|
+
const kind = classifyTarget(d);
|
|
51
|
+
if (options.device && d.id === options.device) return true;
|
|
52
|
+
if (options.ios && (kind === 'ios-simulator' || kind === 'ios-device')) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (
|
|
56
|
+
options.android &&
|
|
57
|
+
(kind === 'android-emulator' || kind === 'android-device')
|
|
58
|
+
) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (options.web && kind === 'web') return true;
|
|
62
|
+
if (!options.ios && !options.android && !options.web && !options.device) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function pickDevice(devices, t) {
|
|
70
|
+
if (devices.length === 0) return null;
|
|
71
|
+
if (devices.length === 1) return devices[0];
|
|
72
|
+
const choice = await ui.select({
|
|
73
|
+
message: t('reset.prompt.pickDevice'),
|
|
74
|
+
options: devices.map((d) => ({
|
|
75
|
+
value: d.id,
|
|
76
|
+
label: `${d.name} ${kleur.dim(`(${classifyTarget(d)})`)}`,
|
|
77
|
+
})),
|
|
78
|
+
});
|
|
79
|
+
return devices.find((d) => d.id === choice) || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resetIosSimulator(device, bundleId, t) {
|
|
83
|
+
ui.log.message(kleur.dim(`xcrun simctl uninstall ${device.id} ${bundleId}`));
|
|
84
|
+
const res = runCmd('xcrun', ['simctl', 'uninstall', device.id, bundleId]);
|
|
85
|
+
if (res.code !== 0) {
|
|
86
|
+
// simctl returns 0 even when app wasn't installed; non-zero is a real error
|
|
87
|
+
ui.log.warn(res.stderr || t('reset.warn.iosUninstallFailed'));
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
ui.log.success(t('reset.success.uninstalled'));
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resetIosDevice(device, bundleId, t) {
|
|
95
|
+
// Try Apple's devicectl (ships with Xcode 15+). Falls back to a manual
|
|
96
|
+
// instruction when devicectl isn't available or the uninstall fails.
|
|
97
|
+
const probe = runCmd('xcrun', ['devicectl', '--version']);
|
|
98
|
+
if (probe.code !== 0) {
|
|
99
|
+
noticeIosPhysical(t);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
ui.log.message(
|
|
103
|
+
kleur.dim(
|
|
104
|
+
`xcrun devicectl device uninstall app --device ${device.id} ${bundleId}`
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
const res = runCmd('xcrun', [
|
|
108
|
+
'devicectl',
|
|
109
|
+
'device',
|
|
110
|
+
'uninstall',
|
|
111
|
+
'app',
|
|
112
|
+
'--device',
|
|
113
|
+
device.id,
|
|
114
|
+
bundleId,
|
|
115
|
+
]);
|
|
116
|
+
if (res.code !== 0) {
|
|
117
|
+
ui.log.warn(res.stderr || t('reset.warn.iosDeviceUninstallFailed'));
|
|
118
|
+
noticeIosPhysical(t);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
ui.log.success(t('reset.success.uninstalled'));
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resetAndroid(device, packageName, t) {
|
|
126
|
+
// Force-stop first — `adb uninstall` returns DELETE_FAILED_INTERNAL_ERROR
|
|
127
|
+
// when the app has lingering processes or pending PM state (common on
|
|
128
|
+
// Pixel emulators after a crash or hot-restart).
|
|
129
|
+
runCmd('adb', ['-s', device.id, 'shell', 'am', 'force-stop', packageName]);
|
|
130
|
+
|
|
131
|
+
ui.log.message(kleur.dim(`adb -s ${device.id} uninstall ${packageName}`));
|
|
132
|
+
const res = runCmd('adb', ['-s', device.id, 'uninstall', packageName]);
|
|
133
|
+
const out = `${res.stdout}\n${res.stderr}`.toLowerCase();
|
|
134
|
+
if (out.includes('success')) {
|
|
135
|
+
ui.log.success(t('reset.success.uninstalled'));
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (out.includes('not installed')) {
|
|
139
|
+
ui.log.message(kleur.dim(`– ${t('reset.info.notInstalled')}`));
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fallback: `pm uninstall --user 0` bypasses several PM states that
|
|
144
|
+
// `adb uninstall` can't (DELETE_FAILED_INTERNAL_ERROR, multi-user, etc).
|
|
145
|
+
ui.log.message(
|
|
146
|
+
kleur.dim(`adb -s ${device.id} shell pm uninstall --user 0 ${packageName}`)
|
|
147
|
+
);
|
|
148
|
+
const fallback = runCmd('adb', [
|
|
149
|
+
'-s', device.id, 'shell', 'pm', 'uninstall', '--user', '0', packageName,
|
|
150
|
+
]);
|
|
151
|
+
const fallbackOut = `${fallback.stdout}\n${fallback.stderr}`.toLowerCase();
|
|
152
|
+
if (fallbackOut.includes('success')) {
|
|
153
|
+
ui.log.success(t('reset.success.uninstalled'));
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (fallbackOut.includes('not installed') || fallbackOut.includes('unknown package')) {
|
|
157
|
+
ui.log.message(kleur.dim(`– ${t('reset.info.notInstalled')}`));
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ui.log.warn(
|
|
162
|
+
res.stdout || res.stderr || fallback.stdout || fallback.stderr ||
|
|
163
|
+
t('reset.warn.androidUninstallFailed')
|
|
164
|
+
);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Reads the package name of the device's default home launcher.
|
|
169
|
+
/// Used so we can clear ITS cache (not the app's) — the launcher caches
|
|
170
|
+
/// widget previews aggressively and a gray preview persists across
|
|
171
|
+
/// uninstalls of the app itself.
|
|
172
|
+
function detectAndroidLauncher(device) {
|
|
173
|
+
const res = runCmd('adb', [
|
|
174
|
+
'-s', device.id,
|
|
175
|
+
'shell', 'cmd', 'package', 'resolve-activity',
|
|
176
|
+
'--brief',
|
|
177
|
+
'-c', 'android.intent.category.HOME',
|
|
178
|
+
'-a', 'android.intent.action.MAIN',
|
|
179
|
+
]);
|
|
180
|
+
if (res.code !== 0) return null;
|
|
181
|
+
// Output ends with a line like:
|
|
182
|
+
// com.google.android.apps.nexuslauncher/.NexusLauncherActivity
|
|
183
|
+
const lines = res.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
184
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
185
|
+
const line = lines[i];
|
|
186
|
+
if (line.includes('/') && !line.startsWith('priority=')) {
|
|
187
|
+
return line.split('/')[0];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Clears the launcher's preview cache and force-stops it.
|
|
194
|
+
/// pm clear-cache removes cached widget previews from disk; force-stop
|
|
195
|
+
/// kicks any in-memory composition. Neither resets the user's home
|
|
196
|
+
/// screen — that would be `pm clear` (which we intentionally do NOT do).
|
|
197
|
+
function clearAndroidLauncherCache(device, launcherPkg, t) {
|
|
198
|
+
ui.log.message(
|
|
199
|
+
kleur.dim(`adb -s ${device.id} shell pm clear-cache ${launcherPkg}`)
|
|
200
|
+
);
|
|
201
|
+
runCmd('adb', [
|
|
202
|
+
'-s', device.id, 'shell', 'pm', 'clear-cache', launcherPkg,
|
|
203
|
+
]);
|
|
204
|
+
ui.log.message(
|
|
205
|
+
kleur.dim(`adb -s ${device.id} shell am force-stop ${launcherPkg}`)
|
|
206
|
+
);
|
|
207
|
+
const stop = runCmd('adb', [
|
|
208
|
+
'-s', device.id, 'shell', 'am', 'force-stop', launcherPkg,
|
|
209
|
+
]);
|
|
210
|
+
if (stop.code !== 0) {
|
|
211
|
+
ui.log.warn(t('reset.warn.launcherCacheFailed'));
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
ui.log.success(t('reset.success.launcherCleared', { pkg: launcherPkg }));
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function noticeIosPhysical(t) {
|
|
219
|
+
ui.log.warn(t('reset.warn.iosDeviceManual'));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isXcodeRunning() {
|
|
223
|
+
const res = spawnSync('pgrep', ['-x', 'Xcode'], { encoding: 'utf8' });
|
|
224
|
+
return res.status === 0 && (res.stdout || '').trim().length > 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function ensureXcodeClosed(t) {
|
|
228
|
+
if (!isXcodeRunning()) return true;
|
|
229
|
+
const choice = await ui.select({
|
|
230
|
+
message: t('reset.prompt.xcodeOpen'),
|
|
231
|
+
options: [
|
|
232
|
+
{ value: 'close', label: t('reset.prompt.xcodeOpen.close') },
|
|
233
|
+
{ value: 'skip', label: t('reset.prompt.xcodeOpen.skip') },
|
|
234
|
+
{ value: 'cancel', label: t('reset.prompt.xcodeOpen.cancel') },
|
|
235
|
+
],
|
|
236
|
+
initialValue: 'close',
|
|
237
|
+
});
|
|
238
|
+
if (choice === 'cancel') return false;
|
|
239
|
+
if (choice === 'close') {
|
|
240
|
+
spawnSync('osascript', ['-e', 'tell application "Xcode" to quit'], {
|
|
241
|
+
encoding: 'utf8',
|
|
242
|
+
});
|
|
243
|
+
// Give Xcode a moment to release the debug session before we proceed.
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
245
|
+
ui.log.message(kleur.dim(t('reset.info.xcodeClosed')));
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function noticeWeb(t) {
|
|
251
|
+
ui.log.warn(t('reset.warn.webIncognito'));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function runFlutterOnDevice(device, projectDir, t) {
|
|
255
|
+
return spawnFlutterWithSpinner(['run', '-d', device.id], projectDir, t);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function runReset(directory, options = {}) {
|
|
259
|
+
const t = createTranslator(options.language || detectDefaultLanguage());
|
|
260
|
+
const projectDir = path.resolve(directory || '.');
|
|
261
|
+
|
|
262
|
+
if (!(await fs.pathExists(path.join(projectDir, 'pubspec.yaml')))) {
|
|
263
|
+
throw new Error(t('reset.error.notFlutterProject'));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
printCompactHeader(t);
|
|
267
|
+
ui.intro(t('reset.title'));
|
|
268
|
+
|
|
269
|
+
const [bundleId, packageName] = await Promise.all([
|
|
270
|
+
readBundleId(projectDir),
|
|
271
|
+
readPackageName(projectDir),
|
|
272
|
+
]);
|
|
273
|
+
if (!bundleId && !packageName) {
|
|
274
|
+
throw new Error(t('reset.error.noIdentifier'));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
ui.log.message(`${kleur.dim('iOS bundle id:')} ${bundleId || kleur.dim('—')}`);
|
|
278
|
+
ui.log.message(`${kleur.dim('Android package:')} ${packageName || kleur.dim('—')}`);
|
|
279
|
+
|
|
280
|
+
const scanSpinner = ui.spinner();
|
|
281
|
+
scanSpinner.start(t('reset.scanning'));
|
|
282
|
+
const allDevices = await listFlutterDevices(projectDir);
|
|
283
|
+
const devices = filterDevices(allDevices, options);
|
|
284
|
+
scanSpinner.stop(t('reset.scanning'));
|
|
285
|
+
|
|
286
|
+
if (devices.length === 0) {
|
|
287
|
+
ui.log.warn(t('reset.warn.noDevices'));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const target = await pickDevice(devices, t);
|
|
292
|
+
if (!target) {
|
|
293
|
+
ui.log.warn(t('reset.warn.nothingSelected'));
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const kind = classifyTarget(target);
|
|
298
|
+
|
|
299
|
+
if (kind === 'ios-device') {
|
|
300
|
+
const proceed = await ensureXcodeClosed(t);
|
|
301
|
+
if (!proceed) {
|
|
302
|
+
ui.outro(t('reset.outro.cancelled'));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
ui.log.step(kleur.bold(`${t('reset.resetting')}: ${target.name}`));
|
|
308
|
+
|
|
309
|
+
let didReset = false;
|
|
310
|
+
switch (kind) {
|
|
311
|
+
case 'ios-simulator':
|
|
312
|
+
if (!bundleId) {
|
|
313
|
+
ui.log.error(t('reset.error.noBundleId'));
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
didReset = resetIosSimulator(target, bundleId, t);
|
|
317
|
+
break;
|
|
318
|
+
case 'ios-device':
|
|
319
|
+
if (!bundleId) {
|
|
320
|
+
ui.log.error(t('reset.error.noBundleId'));
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
didReset = resetIosDevice(target, bundleId, t);
|
|
324
|
+
break;
|
|
325
|
+
case 'android-emulator':
|
|
326
|
+
case 'android-device':
|
|
327
|
+
if (!packageName) {
|
|
328
|
+
ui.log.error(t('reset.error.noPackageName'));
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
didReset = resetAndroid(target, packageName, t);
|
|
332
|
+
break;
|
|
333
|
+
case 'web':
|
|
334
|
+
noticeWeb(t);
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
ui.log.warn(`${t('reset.warn.unknownPlatform')}: ${kind}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!didReset) {
|
|
341
|
+
ui.outro(t('reset.outro.skipped'));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// On Android, also clear the launcher cache so widget previews refresh.
|
|
346
|
+
// Without this, the gallery keeps showing the gray placeholder even after
|
|
347
|
+
// the app is reinstalled (the launcher caches preview drawables by
|
|
348
|
+
// package + component name, which don't change between installs).
|
|
349
|
+
if (
|
|
350
|
+
(kind === 'android-emulator' || kind === 'android-device') &&
|
|
351
|
+
options.clearLauncher !== false
|
|
352
|
+
) {
|
|
353
|
+
const launcherPkg = detectAndroidLauncher(target);
|
|
354
|
+
if (launcherPkg) {
|
|
355
|
+
const proceedClear = await ui.confirm({
|
|
356
|
+
message: t('reset.prompt.clearLauncherCache', { pkg: launcherPkg }),
|
|
357
|
+
initialValue: true,
|
|
358
|
+
});
|
|
359
|
+
if (proceedClear) {
|
|
360
|
+
clearAndroidLauncherCache(target, launcherPkg, t);
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
ui.log.message(kleur.dim(t('reset.warn.launcherNotDetected')));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (options.reinstall === false) {
|
|
368
|
+
ui.outro(t('reset.outro.uninstalledOnly'));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const proceed = await ui.confirm({
|
|
373
|
+
message: t('reset.prompt.reinstallNow'),
|
|
374
|
+
initialValue: true,
|
|
375
|
+
});
|
|
376
|
+
if (!proceed) {
|
|
377
|
+
ui.outro(t('reset.outro.uninstalledOnly'));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
ui.log.step(kleur.bold(t('reset.reinstalling')));
|
|
382
|
+
await runFlutterOnDevice(target, projectDir, t);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = { runReset };
|
package/lib/commands/run.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const { spawn } = require('node:child_process');
|
|
2
1
|
const path = require('node:path');
|
|
3
2
|
const fs = require('fs-extra');
|
|
4
3
|
const kleur = require('kleur');
|
|
5
4
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
6
5
|
const { printCompactHeader } = require('../utils/brand');
|
|
6
|
+
const { spawnFlutterWithSpinner } = require('../utils/flutter-run');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Read dart-define args from .vscode/launch.json.
|
|
@@ -54,25 +54,32 @@ async function runRun(directory, options = {}) {
|
|
|
54
54
|
|
|
55
55
|
const args = ['run', ...deviceArgs, ...dartDefines];
|
|
56
56
|
|
|
57
|
+
const envDefine = dartDefines.find((a) => a.startsWith('--dart-define=ENV='));
|
|
58
|
+
const envValue = envDefine ? envDefine.split('=').pop() : null;
|
|
59
|
+
const deviceLabel = options.web
|
|
60
|
+
? 'chrome'
|
|
61
|
+
: options.ios
|
|
62
|
+
? 'ios'
|
|
63
|
+
: options.android
|
|
64
|
+
? 'android'
|
|
65
|
+
: options.device || null;
|
|
66
|
+
const summaryParts = [];
|
|
67
|
+
if (envValue) summaryParts.push(`ENV=${envValue}`);
|
|
68
|
+
if (deviceLabel) summaryParts.push(`device: ${deviceLabel}`);
|
|
69
|
+
const summary = summaryParts.length ? ` (${summaryParts.join(', ')})` : '';
|
|
70
|
+
|
|
57
71
|
printCompactHeader(t);
|
|
58
|
-
console.log(kleur.bold(`${t('run.launching')}`));
|
|
59
|
-
console.log(kleur.dim(` flutter ${args.join(' ')}`));
|
|
72
|
+
console.log(kleur.bold(`${t('run.launching')}${summary}`));
|
|
60
73
|
console.log(kleur.dim(` ✦ ${t('run.updateHint.prefix')} `) + kleur.cyan('kasy update') + kleur.dim(` ${t('run.updateHint.suffix')}\n`));
|
|
61
74
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
reject(new Error(t('run.error.flutterNotFound')));
|
|
71
|
-
} else {
|
|
72
|
-
reject(err);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
});
|
|
75
|
+
try {
|
|
76
|
+
await spawnFlutterWithSpinner(args, projectDir, t);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err.code === 'ENOENT') {
|
|
79
|
+
throw new Error(t('run.error.flutterNotFound'));
|
|
80
|
+
}
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
module.exports = { runRun };
|
package/lib/commands/splash.js
CHANGED
|
@@ -7,12 +7,15 @@ const kleur = require('kleur');
|
|
|
7
7
|
const ui = require('../utils/ui');
|
|
8
8
|
const { printCompactHeader } = require('../utils/brand');
|
|
9
9
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
10
|
+
const { writeAndroid12Variant } = require('../utils/png-padding');
|
|
10
11
|
|
|
11
12
|
const execAsync = promisify(exec);
|
|
12
13
|
|
|
13
14
|
const ASSETS_DIR = path.join('assets', 'images');
|
|
14
15
|
const LIGHT_NAME = 'splash_logo_light.png';
|
|
15
16
|
const DARK_NAME = 'splash_logo_dark.png';
|
|
17
|
+
const LIGHT_ANDROID12_NAME = 'splash_logo_light_android12.png';
|
|
18
|
+
const DARK_ANDROID12_NAME = 'splash_logo_dark_android12.png';
|
|
16
19
|
|
|
17
20
|
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
18
21
|
|
|
@@ -76,9 +79,8 @@ async function assertKasyProject(projectDir, t) {
|
|
|
76
79
|
|
|
77
80
|
/**
|
|
78
81
|
* @param {string} flagValue
|
|
79
|
-
* @param {string} role 'light' | 'dark'
|
|
80
82
|
*/
|
|
81
|
-
function resolveInputPath(flagValue
|
|
83
|
+
function resolveInputPath(flagValue) {
|
|
82
84
|
if (!flagValue) return null;
|
|
83
85
|
const expanded = flagValue.startsWith('~')
|
|
84
86
|
? path.join(require('node:os').homedir(), flagValue.slice(1))
|
|
@@ -99,8 +101,8 @@ async function runSplash(projectDir, options = {}) {
|
|
|
99
101
|
|
|
100
102
|
await assertKasyProject(projectDir, t);
|
|
101
103
|
|
|
102
|
-
const lightPath = resolveInputPath(options.light
|
|
103
|
-
const darkPath = resolveInputPath(options.dark
|
|
104
|
+
const lightPath = resolveInputPath(options.light);
|
|
105
|
+
const darkPath = resolveInputPath(options.dark);
|
|
104
106
|
|
|
105
107
|
if (!lightPath || !darkPath) {
|
|
106
108
|
ui.log.error(t('splash.error.bothRequired'));
|
|
@@ -160,6 +162,8 @@ async function runSplash(projectDir, options = {}) {
|
|
|
160
162
|
|
|
161
163
|
const destLight = path.join(projectDir, ASSETS_DIR, LIGHT_NAME);
|
|
162
164
|
const destDark = path.join(projectDir, ASSETS_DIR, DARK_NAME);
|
|
165
|
+
const destLightA12 = path.join(projectDir, ASSETS_DIR, LIGHT_ANDROID12_NAME);
|
|
166
|
+
const destDarkA12 = path.join(projectDir, ASSETS_DIR, DARK_ANDROID12_NAME);
|
|
163
167
|
|
|
164
168
|
const copySpinner = ui.spinner();
|
|
165
169
|
copySpinner.start(t('splash.copying'));
|
|
@@ -167,6 +171,12 @@ async function runSplash(projectDir, options = {}) {
|
|
|
167
171
|
await fs.copy(darkPath, destDark, { overwrite: true });
|
|
168
172
|
copySpinner.stop(t('splash.copied'));
|
|
169
173
|
|
|
174
|
+
const a12Spinner = ui.spinner();
|
|
175
|
+
a12Spinner.start(t('splash.android12Generating'));
|
|
176
|
+
await writeAndroid12Variant(destLight, destLightA12);
|
|
177
|
+
await writeAndroid12Variant(destDark, destDarkA12);
|
|
178
|
+
a12Spinner.stop(t('splash.android12Generated'));
|
|
179
|
+
|
|
170
180
|
if (options.skipGenerate) {
|
|
171
181
|
ui.note(t('splash.skipGenerate.hint'), t('splash.skipGenerate.title'));
|
|
172
182
|
ui.outro(t('splash.done'));
|
package/lib/commands/update.js
CHANGED
|
@@ -374,7 +374,7 @@ async function runUpdate(module, options = {}) {
|
|
|
374
374
|
|
|
375
375
|
// build_runner (only for modules that generate code)
|
|
376
376
|
if (NEEDS_BUILD_RUNNER.includes(normalized)) {
|
|
377
|
-
const spinner = ui.
|
|
377
|
+
const spinner = ui.timedSpinner();
|
|
378
378
|
spinner.start(t('update.buildRunner'));
|
|
379
379
|
try {
|
|
380
380
|
await execAsync(
|