kasy-cli 1.13.0 → 1.14.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 +122 -7
- 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 +20 -5
- package/lib/commands/new.js +8 -20
- package/lib/commands/remove.js +1 -1
- package/lib/commands/reset.js +287 -0
- package/lib/commands/run.js +24 -17
- package/lib/commands/splash.js +3 -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 +85 -16
- package/lib/utils/checks.js +4 -105
- package/lib/utils/flutter-run.js +173 -0
- package/lib/utils/i18n.js +335 -0
- package/lib/utils/mobile-identity.js +35 -0
- package/lib/utils/ui.js +114 -0
- 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/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 +160 -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/layout/widget_loading.xml +8 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +46 -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/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/kasy_button.dart +8 -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 +67 -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_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 +6 -1
- 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 +3 -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/bin/kasy.js
CHANGED
|
@@ -10,12 +10,15 @@ const { runValidate } = require('../lib/commands/validate');
|
|
|
10
10
|
const { runDeployCommand } = require('../lib/commands/deploy');
|
|
11
11
|
const { runCheck } = require('../lib/commands/check');
|
|
12
12
|
const { runRun } = require('../lib/commands/run');
|
|
13
|
+
const { runReset } = require('../lib/commands/reset');
|
|
13
14
|
const { runAdd } = require('../lib/commands/add');
|
|
14
15
|
const { runRemove } = require('../lib/commands/remove');
|
|
15
16
|
const { runUpdate } = require('../lib/commands/update');
|
|
16
17
|
const { runDocs } = require('../lib/commands/docs');
|
|
17
18
|
const { runNotificationsText } = require('../lib/commands/notifications');
|
|
18
19
|
const { runSplash } = require('../lib/commands/splash');
|
|
20
|
+
const { runIcon } = require('../lib/commands/icon');
|
|
21
|
+
const { runFavicon } = require('../lib/commands/favicon');
|
|
19
22
|
const {
|
|
20
23
|
runConfigure: runIosConfigure,
|
|
21
24
|
runBuild: runIosBuild,
|
|
@@ -37,6 +40,7 @@ const { promptLanguage } = require('../lib/utils/prompts');
|
|
|
37
40
|
const { ensureLicenseKey, shouldRequireLicenseForArgv } = require('../lib/utils/license-gate');
|
|
38
41
|
const { checkForUpdates } = require('../lib/utils/updates');
|
|
39
42
|
const { printCompactHeader } = require('../lib/utils/brand');
|
|
43
|
+
const ui = require('../lib/utils/ui');
|
|
40
44
|
|
|
41
45
|
function createLocalizedHelpConfig(t) {
|
|
42
46
|
return {
|
|
@@ -131,11 +135,15 @@ function createLocalizedHelpConfig(t) {
|
|
|
131
135
|
// Group root commands by intent for easier scanning by non-devs.
|
|
132
136
|
const groups = [
|
|
133
137
|
{ id: 'start', ids: ['new', 'doctor', 'features'] },
|
|
134
|
-
{ id: 'work', ids: ['add', 'remove', 'update', 'run'] },
|
|
138
|
+
{ id: 'work', ids: ['add', 'remove', 'update', 'run', 'reset', 'splash', 'icon', 'favicon', 'notifications'] },
|
|
135
139
|
{ id: 'publish', ids: ['deploy', 'check', 'ios', 'codemagic'] },
|
|
136
|
-
{ id: 'maintenance', ids: ['upgrade', 'version', 'uninstall', 'docs'
|
|
140
|
+
{ id: 'maintenance', ids: ['upgrade', 'version', 'uninstall', 'docs'] },
|
|
137
141
|
{ id: 'advanced', ids: ['setup', 'validate'] },
|
|
138
142
|
];
|
|
143
|
+
// Commander auto-generates a `help` subcommand. Hide it from the
|
|
144
|
+
// "Other" section — the tip line at the bottom already teaches users
|
|
145
|
+
// how to get per-command help.
|
|
146
|
+
const HIDDEN_FROM_OTHER = new Set(['help']);
|
|
139
147
|
const knownIds = new Set(groups.flatMap((g) => g.ids));
|
|
140
148
|
const byName = new Map(visibleCommands.map((c) => [c.name(), c]));
|
|
141
149
|
for (const group of groups) {
|
|
@@ -157,7 +165,7 @@ function createLocalizedHelpConfig(t) {
|
|
|
157
165
|
]);
|
|
158
166
|
}
|
|
159
167
|
const otherItems = visibleCommands
|
|
160
|
-
.filter((c) => !knownIds.has(c.name()))
|
|
168
|
+
.filter((c) => !knownIds.has(c.name()) && !HIDDEN_FROM_OTHER.has(c.name()))
|
|
161
169
|
.map((sub) =>
|
|
162
170
|
formatItem(
|
|
163
171
|
localizeSubcommandTerm(helper.subcommandTerm(sub)),
|
|
@@ -221,7 +229,7 @@ function buildProgram(language) {
|
|
|
221
229
|
program.addHelpText('beforeAll', `${kleur.bold().cyan(t('cli.tagline'))}\n`);
|
|
222
230
|
program.addHelpText(
|
|
223
231
|
'after',
|
|
224
|
-
|
|
232
|
+
`${t('cli.help.quickStart')} ${kleur.cyan('kasy new')}\n`
|
|
225
233
|
);
|
|
226
234
|
|
|
227
235
|
applyLocalizedHelp(
|
|
@@ -343,6 +351,22 @@ function buildProgram(language) {
|
|
|
343
351
|
t
|
|
344
352
|
);
|
|
345
353
|
|
|
354
|
+
applyLocalizedHelp(
|
|
355
|
+
program
|
|
356
|
+
.command('reset')
|
|
357
|
+
.argument('[directory]', 'Project folder (default: current directory)', '.')
|
|
358
|
+
.option('--ios', 'Reset on iOS simulator/device only')
|
|
359
|
+
.option('--android', 'Reset on Android emulator/device only')
|
|
360
|
+
.option('--web', 'Show web reset instructions only')
|
|
361
|
+
.option('-d, --device <id>', 'Reset specific device ID')
|
|
362
|
+
.option('--no-reinstall', 'Only uninstall, skip reinstall')
|
|
363
|
+
.description(t('cli.command.reset.description'))
|
|
364
|
+
.action(async (directory, options) => {
|
|
365
|
+
await runReset(directory, { language, ...options });
|
|
366
|
+
}),
|
|
367
|
+
t
|
|
368
|
+
);
|
|
369
|
+
|
|
346
370
|
applyLocalizedHelp(
|
|
347
371
|
program
|
|
348
372
|
.command('add')
|
|
@@ -422,7 +446,27 @@ function buildProgram(language) {
|
|
|
422
446
|
t
|
|
423
447
|
);
|
|
424
448
|
|
|
425
|
-
const iosCmd = program
|
|
449
|
+
const iosCmd = program
|
|
450
|
+
.command('ios')
|
|
451
|
+
.description(t('cli.command.ios.description'))
|
|
452
|
+
.addHelpText('before', `\n${t('cli.command.ios.help.before')}`)
|
|
453
|
+
.action(async () => {
|
|
454
|
+
printCompactHeader(t);
|
|
455
|
+
ui.intro(kleur.cyan(t('cli.command.ios.picker.intro')));
|
|
456
|
+
const sub = await ui.select({
|
|
457
|
+
message: t('cli.command.ios.picker.message'),
|
|
458
|
+
options: [
|
|
459
|
+
{ value: 'configure', label: t('cli.command.ios.configure.description') },
|
|
460
|
+
{ value: 'release', label: t('cli.command.ios.release.description') },
|
|
461
|
+
{ value: 'build', label: t('cli.command.ios.build.description') },
|
|
462
|
+
{ value: 'clean', label: t('cli.command.ios.clean.description') },
|
|
463
|
+
],
|
|
464
|
+
});
|
|
465
|
+
if (sub === 'configure') await runIosConfigure('.', { language });
|
|
466
|
+
else if (sub === 'release') await runIosRelease('.', { language });
|
|
467
|
+
else if (sub === 'build') await runIosBuild('.', { language });
|
|
468
|
+
else if (sub === 'clean') await runIosClean('.', { language });
|
|
469
|
+
});
|
|
426
470
|
applyLocalizedHelp(
|
|
427
471
|
iosCmd
|
|
428
472
|
.command('configure')
|
|
@@ -468,7 +512,31 @@ function buildProgram(language) {
|
|
|
468
512
|
t
|
|
469
513
|
);
|
|
470
514
|
|
|
471
|
-
const codemagicCmd = program
|
|
515
|
+
const codemagicCmd = program
|
|
516
|
+
.command('codemagic')
|
|
517
|
+
.description(t('cli.command.codemagic.description'))
|
|
518
|
+
.addHelpText('before', `\n${t('cli.command.codemagic.help.before')}`)
|
|
519
|
+
.action(async () => {
|
|
520
|
+
printCompactHeader(t);
|
|
521
|
+
ui.intro(kleur.cyan(t('cli.command.codemagic.picker.intro')));
|
|
522
|
+
const sub = await ui.select({
|
|
523
|
+
message: t('cli.command.codemagic.picker.message'),
|
|
524
|
+
options: [
|
|
525
|
+
{ value: 'configure', label: t('cli.command.codemagic.configure.description') },
|
|
526
|
+
{ value: 'release', label: t('cli.command.codemagic.release.description') },
|
|
527
|
+
{ value: 'status', label: t('cli.command.codemagic.status.description'), hint: t('cli.command.codemagic.picker.statusHint') },
|
|
528
|
+
],
|
|
529
|
+
});
|
|
530
|
+
if (sub === 'configure') await runCodemagicConfigure('.', { language });
|
|
531
|
+
else if (sub === 'release') await runCodemagicRelease('.', { language });
|
|
532
|
+
else if (sub === 'status') {
|
|
533
|
+
const buildId = await ui.text({
|
|
534
|
+
message: 'Build ID',
|
|
535
|
+
validate: (v) => (v && v.trim() ? undefined : 'Build ID is required'),
|
|
536
|
+
});
|
|
537
|
+
await runCodemagicStatus(buildId?.trim(), '.', { language });
|
|
538
|
+
}
|
|
539
|
+
});
|
|
472
540
|
applyLocalizedHelp(
|
|
473
541
|
codemagicCmd
|
|
474
542
|
.command('configure')
|
|
@@ -531,9 +599,56 @@ function buildProgram(language) {
|
|
|
531
599
|
t
|
|
532
600
|
);
|
|
533
601
|
|
|
602
|
+
applyLocalizedHelp(
|
|
603
|
+
program
|
|
604
|
+
.command('icon')
|
|
605
|
+
.argument('[directory]', 'Project folder (default: current directory)', '.')
|
|
606
|
+
.requiredOption('--image <path>', 'PNG with the app icon (square, 1024x1024 recommended)')
|
|
607
|
+
.option('--skip-generate', 'Only copy file, do not run flutter_launcher_icons', false)
|
|
608
|
+
.description(t('cli.command.icon.description'))
|
|
609
|
+
.action(async (directory, options) => {
|
|
610
|
+
const dir = directory || '.';
|
|
611
|
+
await runIcon(dir, {
|
|
612
|
+
language,
|
|
613
|
+
image: options.image,
|
|
614
|
+
skipGenerate: options.skipGenerate,
|
|
615
|
+
});
|
|
616
|
+
}),
|
|
617
|
+
t
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
applyLocalizedHelp(
|
|
621
|
+
program
|
|
622
|
+
.command('favicon')
|
|
623
|
+
.argument('[directory]', 'Project folder (default: current directory)', '.')
|
|
624
|
+
.requiredOption('--image <path>', 'PNG with the web favicon / PWA icon (square, 512x512+ recommended)')
|
|
625
|
+
.option('--skip-generate', 'Only copy file, do not run flutter_launcher_icons', false)
|
|
626
|
+
.description(t('cli.command.favicon.description'))
|
|
627
|
+
.action(async (directory, options) => {
|
|
628
|
+
const dir = directory || '.';
|
|
629
|
+
await runFavicon(dir, {
|
|
630
|
+
language,
|
|
631
|
+
image: options.image,
|
|
632
|
+
skipGenerate: options.skipGenerate,
|
|
633
|
+
});
|
|
634
|
+
}),
|
|
635
|
+
t
|
|
636
|
+
);
|
|
637
|
+
|
|
534
638
|
const notificationsCmd = program
|
|
535
639
|
.command('notifications')
|
|
536
|
-
.description(t('cli.command.notifications.description'))
|
|
640
|
+
.description(t('cli.command.notifications.description'))
|
|
641
|
+
.action(async () => {
|
|
642
|
+
printCompactHeader(t);
|
|
643
|
+
ui.intro(kleur.cyan(t('cli.command.notifications.picker.intro')));
|
|
644
|
+
const sub = await ui.select({
|
|
645
|
+
message: t('cli.command.notifications.picker.message'),
|
|
646
|
+
options: [
|
|
647
|
+
{ value: 'text', label: t('cli.command.notifications.text.description') },
|
|
648
|
+
],
|
|
649
|
+
});
|
|
650
|
+
if (sub === 'text') await runNotificationsText('.', { language, directory: '.', lang: 'all' });
|
|
651
|
+
});
|
|
537
652
|
applyLocalizedHelp(
|
|
538
653
|
notificationsCmd
|
|
539
654
|
.command('text')
|
package/lib/commands/add.js
CHANGED
|
@@ -460,7 +460,7 @@ async function postAddLlmChat(projectDir, kitSetup, answers, t) {
|
|
|
460
460
|
// 3. Deploy the LLM function automatically
|
|
461
461
|
if (backend === 'api') return { deployOk: false, deployAttempted: false };
|
|
462
462
|
|
|
463
|
-
const deploySpinner = ui.
|
|
463
|
+
const deploySpinner = ui.timedSpinner();
|
|
464
464
|
deploySpinner.start(t('add.llm_chat.deploying'));
|
|
465
465
|
try {
|
|
466
466
|
if (backend === 'firebase') {
|
|
@@ -761,7 +761,7 @@ async function runAdd(module, options = {}) {
|
|
|
761
761
|
// 11. build_runner (only when needed: features with codegen)
|
|
762
762
|
const needsBuildRunner = ['revenuecat', 'analytics', 'sentry', 'onboarding', 'llm_chat', 'feedback'].includes(normalized);
|
|
763
763
|
if (needsBuildRunner) {
|
|
764
|
-
const spinner = ui.
|
|
764
|
+
const spinner = ui.timedSpinner();
|
|
765
765
|
spinner.start(t('add.buildRunner'));
|
|
766
766
|
try {
|
|
767
767
|
await execAsync('dart run build_runner build --delete-conflicting-outputs', { cwd: projectDir, timeout: 600_000 });
|
|
@@ -107,8 +107,11 @@ async function runRelease(directory, options = {}) {
|
|
|
107
107
|
printCompactHeader(t);
|
|
108
108
|
ui.intro(t('codemagic.release.title'));
|
|
109
109
|
|
|
110
|
+
const spinner = ui.spinner();
|
|
111
|
+
spinner.start(t('codemagic.release.spin'));
|
|
110
112
|
try {
|
|
111
113
|
const result = await triggerBuild(projectDir, validation.env);
|
|
114
|
+
spinner.stop(t('codemagic.release.spinDone'));
|
|
112
115
|
const buildId = result.buildId || result._id;
|
|
113
116
|
if (buildId) {
|
|
114
117
|
ui.log.success(t('codemagic.release.triggered'));
|
|
@@ -119,7 +122,7 @@ async function runRelease(directory, options = {}) {
|
|
|
119
122
|
ui.outro(t('codemagic.release.triggered'));
|
|
120
123
|
}
|
|
121
124
|
} catch (err) {
|
|
122
|
-
|
|
125
|
+
spinner.stop(err.message, 2);
|
|
123
126
|
process.exitCode = 1;
|
|
124
127
|
}
|
|
125
128
|
}
|
|
@@ -145,14 +148,18 @@ async function runStatus(buildId, directory, options = {}) {
|
|
|
145
148
|
return;
|
|
146
149
|
}
|
|
147
150
|
|
|
151
|
+
printCompactHeader(t);
|
|
152
|
+
ui.intro(`${t('codemagic.status.title')}: ${buildId}`);
|
|
153
|
+
|
|
154
|
+
const spinner = ui.spinner();
|
|
155
|
+
spinner.start(t('codemagic.status.spin'));
|
|
148
156
|
try {
|
|
149
157
|
const result = await getBuildStatus(buildId, validation.env.CODEMAGIC_API_TOKEN);
|
|
150
|
-
|
|
151
|
-
ui.intro(`${t('codemagic.status.title')}: ${buildId}`);
|
|
158
|
+
spinner.stop(t('codemagic.status.spinDone'));
|
|
152
159
|
ui.note(JSON.stringify(result, null, 2));
|
|
153
160
|
ui.outro('');
|
|
154
161
|
} catch (err) {
|
|
155
|
-
|
|
162
|
+
spinner.stop(err.message, 2);
|
|
156
163
|
process.exitCode = 1;
|
|
157
164
|
}
|
|
158
165
|
}
|
package/lib/commands/deploy.js
CHANGED
|
@@ -122,7 +122,7 @@ async function deployFirebase(projectDir, options, tr) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const spinner = ui.
|
|
125
|
+
const spinner = ui.timedSpinner();
|
|
126
126
|
spinner.start(tr('deploy.firebase.spin'));
|
|
127
127
|
let steps;
|
|
128
128
|
try {
|
|
@@ -173,7 +173,7 @@ async function deploySupabase(projectDir, tr) {
|
|
|
173
173
|
const firebaseProjectId = await readFirebaseProjectId(projectDir);
|
|
174
174
|
|
|
175
175
|
if (firebaseProjectId) {
|
|
176
|
-
const fcmSpinner = ui.
|
|
176
|
+
const fcmSpinner = ui.timedSpinner();
|
|
177
177
|
fcmSpinner.start(tr('deploy.supabase.sakSpin'));
|
|
178
178
|
const fcmResult = await createFcmServiceAccountKey(firebaseProjectId);
|
|
179
179
|
fcmSpinner.stop(tr('deploy.supabase.sakSpinDone'));
|
|
@@ -194,7 +194,7 @@ async function deploySupabase(projectDir, tr) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// ── 3. Deploy edge functions ────────────────────────────────────────────
|
|
197
|
-
const fnSpinner = ui.
|
|
197
|
+
const fnSpinner = ui.timedSpinner();
|
|
198
198
|
fnSpinner.start(tr('deploy.supabase.fnSpin'));
|
|
199
199
|
const fnResult = await deployFunctions(projectDir);
|
|
200
200
|
fnSpinner.stop(tr('deploy.supabase.fnSpinDone'));
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const kleur = require('kleur');
|
|
4
|
+
const ui = require('../utils/ui');
|
|
5
|
+
const { printCompactHeader } = require('../utils/brand');
|
|
6
|
+
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
7
|
+
const { inspectPng } = require('./splash');
|
|
8
|
+
const { runFlutterLauncherIcons } = require('./icon');
|
|
9
|
+
|
|
10
|
+
const ASSETS_DIR = path.join('assets', 'images');
|
|
11
|
+
const FAVICON_NAME = 'favicon.png';
|
|
12
|
+
|
|
13
|
+
async function assertKasyProject(projectDir, t) {
|
|
14
|
+
const kitSetupPath = path.join(projectDir, 'kit_setup.json');
|
|
15
|
+
const pubspecPath = path.join(projectDir, 'pubspec.yaml');
|
|
16
|
+
if (!(await fs.pathExists(kitSetupPath)) && !(await fs.pathExists(pubspecPath))) {
|
|
17
|
+
throw new Error(t('favicon.error.notKasyProject'));
|
|
18
|
+
}
|
|
19
|
+
const assetsDir = path.join(projectDir, ASSETS_DIR);
|
|
20
|
+
await fs.ensureDir(assetsDir);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveInputPath(flagValue) {
|
|
24
|
+
if (!flagValue) return null;
|
|
25
|
+
const expanded = flagValue.startsWith('~')
|
|
26
|
+
? path.join(require('node:os').homedir(), flagValue.slice(1))
|
|
27
|
+
: flagValue;
|
|
28
|
+
return path.resolve(expanded);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function runFavicon(projectDir, options = {}) {
|
|
32
|
+
const language = options.language || detectDefaultLanguage();
|
|
33
|
+
const t = createTranslator(language);
|
|
34
|
+
|
|
35
|
+
printCompactHeader();
|
|
36
|
+
ui.intro(kleur.bold().cyan(t('favicon.intro')));
|
|
37
|
+
|
|
38
|
+
await assertKasyProject(projectDir, t);
|
|
39
|
+
|
|
40
|
+
const imagePath = resolveInputPath(options.image);
|
|
41
|
+
|
|
42
|
+
if (!imagePath) {
|
|
43
|
+
ui.log.error(t('favicon.error.imageRequired'));
|
|
44
|
+
ui.log.message(kleur.dim('kasy favicon --image <favicon.png>'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!(await fs.pathExists(imagePath))) {
|
|
49
|
+
ui.log.error(t('favicon.error.fileNotFound', { path: imagePath }));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const inspectSpinner = ui.spinner();
|
|
54
|
+
inspectSpinner.start(t('favicon.validating'));
|
|
55
|
+
|
|
56
|
+
let info;
|
|
57
|
+
try {
|
|
58
|
+
info = await inspectPng(imagePath);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
inspectSpinner.stop(`✖ ${err.message || t('favicon.error.notPng')}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!info.valid) {
|
|
65
|
+
inspectSpinner.stop(`✖ ${t('favicon.error.notPng')}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
inspectSpinner.stop(t('favicon.validated'));
|
|
70
|
+
|
|
71
|
+
const warnings = [];
|
|
72
|
+
if (info.width !== info.height) {
|
|
73
|
+
warnings.push(t('favicon.warn.notSquare', { w: info.width, h: info.height }));
|
|
74
|
+
}
|
|
75
|
+
if (info.width < 512 || info.height < 512) {
|
|
76
|
+
warnings.push(t('favicon.warn.small', { w: info.width, h: info.height }));
|
|
77
|
+
}
|
|
78
|
+
if (warnings.length > 0) {
|
|
79
|
+
ui.note(warnings.map((w) => `${kleur.yellow('⚠')} ${w}`).join('\n'), t('favicon.warn.title'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const dest = path.join(projectDir, ASSETS_DIR, FAVICON_NAME);
|
|
83
|
+
|
|
84
|
+
const copySpinner = ui.spinner();
|
|
85
|
+
copySpinner.start(t('favicon.copying'));
|
|
86
|
+
await fs.copy(imagePath, dest, { overwrite: true });
|
|
87
|
+
copySpinner.stop(t('favicon.copied'));
|
|
88
|
+
|
|
89
|
+
if (options.skipGenerate) {
|
|
90
|
+
ui.note(t('favicon.skipGenerate.hint'), t('favicon.skipGenerate.title'));
|
|
91
|
+
ui.outro(t('favicon.done'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const genSpinner = ui.spinner();
|
|
96
|
+
genSpinner.start(t('favicon.generating'));
|
|
97
|
+
const result = await runFlutterLauncherIcons(projectDir);
|
|
98
|
+
if (result.ok) {
|
|
99
|
+
genSpinner.stop(t('favicon.generated'));
|
|
100
|
+
} else {
|
|
101
|
+
genSpinner.stop(`⚠ ${t('favicon.error.generateFailed')}`);
|
|
102
|
+
if (result.stderr) {
|
|
103
|
+
ui.log.message(kleur.dim(result.stderr.split('\n').slice(0, 8).join('\n')));
|
|
104
|
+
}
|
|
105
|
+
ui.log.message(kleur.dim('dart run flutter_launcher_icons'));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const summary = `${kleur.bold(t('favicon.summary.favicon'))}: ${kleur.white(FAVICON_NAME)} (${info.width}x${info.height})`;
|
|
110
|
+
ui.note(summary, t('favicon.summary.title'));
|
|
111
|
+
|
|
112
|
+
ui.outro(t('favicon.done'));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { runFavicon };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const { exec } = require('node:child_process');
|
|
4
|
+
const { promisify } = require('node:util');
|
|
5
|
+
const kleur = require('kleur');
|
|
6
|
+
const ui = require('../utils/ui');
|
|
7
|
+
const { printCompactHeader } = require('../utils/brand');
|
|
8
|
+
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
9
|
+
const { inspectPng } = require('./splash');
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
const ASSETS_DIR = path.join('assets', 'images');
|
|
14
|
+
const ICON_NAME = 'icon.png';
|
|
15
|
+
|
|
16
|
+
async function assertKasyProject(projectDir, t) {
|
|
17
|
+
const kitSetupPath = path.join(projectDir, 'kit_setup.json');
|
|
18
|
+
const pubspecPath = path.join(projectDir, 'pubspec.yaml');
|
|
19
|
+
if (!(await fs.pathExists(kitSetupPath)) && !(await fs.pathExists(pubspecPath))) {
|
|
20
|
+
throw new Error(t('icon.error.notKasyProject'));
|
|
21
|
+
}
|
|
22
|
+
const assetsDir = path.join(projectDir, ASSETS_DIR);
|
|
23
|
+
await fs.ensureDir(assetsDir);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveInputPath(flagValue) {
|
|
27
|
+
if (!flagValue) return null;
|
|
28
|
+
const expanded = flagValue.startsWith('~')
|
|
29
|
+
? path.join(require('node:os').homedir(), flagValue.slice(1))
|
|
30
|
+
: flagValue;
|
|
31
|
+
return path.resolve(expanded);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runIcon(projectDir, options = {}) {
|
|
35
|
+
const language = options.language || detectDefaultLanguage();
|
|
36
|
+
const t = createTranslator(language);
|
|
37
|
+
|
|
38
|
+
printCompactHeader();
|
|
39
|
+
ui.intro(kleur.bold().cyan(t('icon.intro')));
|
|
40
|
+
|
|
41
|
+
await assertKasyProject(projectDir, t);
|
|
42
|
+
|
|
43
|
+
const imagePath = resolveInputPath(options.image);
|
|
44
|
+
|
|
45
|
+
if (!imagePath) {
|
|
46
|
+
ui.log.error(t('icon.error.imageRequired'));
|
|
47
|
+
ui.log.message(kleur.dim('kasy icon --image <icon.png>'));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!(await fs.pathExists(imagePath))) {
|
|
52
|
+
ui.log.error(t('icon.error.fileNotFound', { path: imagePath }));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const inspectSpinner = ui.spinner();
|
|
57
|
+
inspectSpinner.start(t('icon.validating'));
|
|
58
|
+
|
|
59
|
+
let info;
|
|
60
|
+
try {
|
|
61
|
+
info = await inspectPng(imagePath);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
inspectSpinner.stop(`✖ ${err.message || t('icon.error.notPng')}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!info.valid) {
|
|
68
|
+
inspectSpinner.stop(`✖ ${t('icon.error.notPng')}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
inspectSpinner.stop(t('icon.validated'));
|
|
73
|
+
|
|
74
|
+
const warnings = [];
|
|
75
|
+
if (info.width !== info.height) {
|
|
76
|
+
warnings.push(t('icon.warn.notSquare', { w: info.width, h: info.height }));
|
|
77
|
+
}
|
|
78
|
+
if (info.width < 1024 || info.height < 1024) {
|
|
79
|
+
warnings.push(t('icon.warn.small', { w: info.width, h: info.height }));
|
|
80
|
+
}
|
|
81
|
+
if (info.hasAlpha) {
|
|
82
|
+
warnings.push(t('icon.warn.hasAlpha'));
|
|
83
|
+
}
|
|
84
|
+
if (warnings.length > 0) {
|
|
85
|
+
ui.note(warnings.map((w) => `${kleur.yellow('⚠')} ${w}`).join('\n'), t('icon.warn.title'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const dest = path.join(projectDir, ASSETS_DIR, ICON_NAME);
|
|
89
|
+
|
|
90
|
+
const copySpinner = ui.spinner();
|
|
91
|
+
copySpinner.start(t('icon.copying'));
|
|
92
|
+
await fs.copy(imagePath, dest, { overwrite: true });
|
|
93
|
+
copySpinner.stop(t('icon.copied'));
|
|
94
|
+
|
|
95
|
+
if (options.skipGenerate) {
|
|
96
|
+
ui.note(t('icon.skipGenerate.hint'), t('icon.skipGenerate.title'));
|
|
97
|
+
ui.outro(t('icon.done'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const genSpinner = ui.spinner();
|
|
102
|
+
genSpinner.start(t('icon.generating'));
|
|
103
|
+
const result = await runFlutterLauncherIcons(projectDir);
|
|
104
|
+
if (result.ok) {
|
|
105
|
+
genSpinner.stop(t('icon.generated'));
|
|
106
|
+
} else {
|
|
107
|
+
genSpinner.stop(`⚠ ${t('icon.error.generateFailed')}`);
|
|
108
|
+
if (result.stderr) {
|
|
109
|
+
ui.log.message(kleur.dim(result.stderr.split('\n').slice(0, 8).join('\n')));
|
|
110
|
+
}
|
|
111
|
+
ui.log.message(kleur.dim('dart run flutter_launcher_icons'));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const summary = `${kleur.bold(t('icon.summary.icon'))}: ${kleur.white(ICON_NAME)} (${info.width}x${info.height})`;
|
|
116
|
+
ui.note(summary, t('icon.summary.title'));
|
|
117
|
+
ui.note(t('icon.reinstall.hint'), t('icon.reinstall.title'));
|
|
118
|
+
|
|
119
|
+
ui.outro(t('icon.done'));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runFlutterLauncherIcons(projectDir) {
|
|
123
|
+
try {
|
|
124
|
+
const { stdout, stderr } = await execAsync(
|
|
125
|
+
'dart run flutter_launcher_icons',
|
|
126
|
+
{
|
|
127
|
+
cwd: projectDir,
|
|
128
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
129
|
+
timeout: 240_000,
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
return { ok: true, stdout, stderr };
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: err.message,
|
|
137
|
+
stdout: err.stdout || '',
|
|
138
|
+
stderr: err.stderr || '',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { runIcon, runFlutterLauncherIcons };
|
package/lib/commands/ios.js
CHANGED
|
@@ -52,6 +52,8 @@ async function runConfigure(directory, options = {}) {
|
|
|
52
52
|
openUrl(URL_APP_STORE_CONNECT_API);
|
|
53
53
|
openUrl(URL_APP_STORE_CONNECT_APPS);
|
|
54
54
|
|
|
55
|
+
ui.note(t('ios.configure.note.body'), t('ios.configure.note.title'));
|
|
56
|
+
|
|
55
57
|
const cancel = () => { ui.cancel(t('ios.configure.cancelled')); process.exit(0); };
|
|
56
58
|
const required = (v) => (v && String(v).trim().length > 0 ? undefined : t('ios.configure.q.required'));
|
|
57
59
|
|
|
@@ -126,12 +128,25 @@ async function runBuildOrRelease(directory, options = {}, mode) {
|
|
|
126
128
|
const validation = await validateAppleSetup(projectDir);
|
|
127
129
|
if (!validation.ok) {
|
|
128
130
|
printCompactHeader(t);
|
|
129
|
-
ui.log.
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
ui.log.warn(t('ios.error.notConfigured'));
|
|
132
|
+
const shouldConfigure = await ui.confirm({
|
|
133
|
+
message: t('ios.release.askConfigure'),
|
|
134
|
+
initialValue: true,
|
|
135
|
+
});
|
|
136
|
+
if (!shouldConfigure) {
|
|
137
|
+
if (validation.issues.includes('missing_env')) {
|
|
138
|
+
ui.log.message(kleur.dim(t('ios.error.runConfigure')));
|
|
139
|
+
}
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await runConfigure(directory, options);
|
|
144
|
+
const recheck = await validateAppleSetup(projectDir);
|
|
145
|
+
if (!recheck.ok) {
|
|
146
|
+
ui.log.error(t('ios.error.notConfigured'));
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return;
|
|
132
149
|
}
|
|
133
|
-
process.exitCode = 1;
|
|
134
|
-
return;
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
152
|
|
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'));
|