kasy-cli 1.17.0 → 1.18.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 +15 -2
- package/lib/commands/add.js +7 -7
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +17 -0
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +403 -238
- package/lib/commands/run.js +1 -1
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +44 -5
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/post-build.js +8 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +58 -5
- package/lib/utils/i18n/messages-es.js +58 -5
- package/lib/utils/i18n/messages-pt.js +59 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -1
- package/templates/firebase/README.en.md +1 -1
- package/templates/firebase/README.es.md +1 -1
- package/templates/firebase/README.md +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +3 -3
- package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_date_picker.dart +834 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +145 -61
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +565 -77
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
- package/templates/firebase/lib/i18n/en.i18n.json +2 -1
- package/templates/firebase/lib/i18n/es.i18n.json +2 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
- package/templates/firebase/pubspec.yaml +1 -1
- package/templates/firebase/web/index.html +9 -0
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
package/lib/commands/run.js
CHANGED
|
@@ -334,7 +334,7 @@ async function runRun(directory, options = {}) {
|
|
|
334
334
|
console.log(kleur.dim(` ✦ ${t('run.updateHint.prefix')} `) + kleur.cyan('kasy update') + kleur.dim(` ${t('run.updateHint.suffix')}\n`));
|
|
335
335
|
|
|
336
336
|
try {
|
|
337
|
-
await spawnFlutterWithSpinner(args, projectDir, t);
|
|
337
|
+
await spawnFlutterWithSpinner(args, projectDir, t, { raw: Boolean(options.raw) });
|
|
338
338
|
} catch (err) {
|
|
339
339
|
if (err.code === 'ENOENT') {
|
|
340
340
|
throw new Error(t('run.error.flutterNotFound'));
|
package/lib/commands/splash.js
CHANGED
|
@@ -5,7 +5,7 @@ const { exec } = require('node:child_process');
|
|
|
5
5
|
const { promisify } = require('node:util');
|
|
6
6
|
const kleur = require('kleur');
|
|
7
7
|
const ui = require('../utils/ui');
|
|
8
|
-
const { printCompactHeader } = require('../utils/brand');
|
|
8
|
+
const { printCompactHeader, paintLime } = require('../utils/brand');
|
|
9
9
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
10
10
|
const { writeAndroid12Variant } = require('../utils/png-padding');
|
|
11
11
|
|
|
@@ -117,7 +117,7 @@ async function runSplash(projectDir, options = {}) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const inspectSpinner = ui.spinner();
|
|
120
|
+
const inspectSpinner = ui.spinner({ color: paintLime });
|
|
121
121
|
inspectSpinner.start(t('splash.validating'));
|
|
122
122
|
|
|
123
123
|
let lightInfo;
|
|
@@ -165,13 +165,13 @@ async function runSplash(projectDir, options = {}) {
|
|
|
165
165
|
const destLightA12 = path.join(projectDir, ASSETS_DIR, LIGHT_ANDROID12_NAME);
|
|
166
166
|
const destDarkA12 = path.join(projectDir, ASSETS_DIR, DARK_ANDROID12_NAME);
|
|
167
167
|
|
|
168
|
-
const copySpinner = ui.spinner();
|
|
168
|
+
const copySpinner = ui.spinner({ color: paintLime });
|
|
169
169
|
copySpinner.start(t('splash.copying'));
|
|
170
170
|
await fs.copy(lightPath, destLight, { overwrite: true });
|
|
171
171
|
await fs.copy(darkPath, destDark, { overwrite: true });
|
|
172
172
|
copySpinner.stop(t('splash.copied'));
|
|
173
173
|
|
|
174
|
-
const a12Spinner = ui.spinner();
|
|
174
|
+
const a12Spinner = ui.spinner({ color: paintLime });
|
|
175
175
|
a12Spinner.start(t('splash.android12Generating'));
|
|
176
176
|
await writeAndroid12Variant(destLight, destLightA12);
|
|
177
177
|
await writeAndroid12Variant(destDark, destDarkA12);
|
|
@@ -183,7 +183,7 @@ async function runSplash(projectDir, options = {}) {
|
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
const genSpinner = ui.spinner();
|
|
186
|
+
const genSpinner = ui.spinner({ color: paintLime });
|
|
187
187
|
genSpinner.start(t('splash.generating'));
|
|
188
188
|
const result = await runFlutterNativeSplash(projectDir);
|
|
189
189
|
if (result.ok) {
|
package/lib/commands/update.js
CHANGED
|
@@ -7,7 +7,7 @@ const fs = require('fs-extra');
|
|
|
7
7
|
const pkg = require('../../package.json');
|
|
8
8
|
const kleur = require('kleur');
|
|
9
9
|
const ui = require('../utils/ui');
|
|
10
|
-
const { printCompactHeader } = require('../utils/brand');
|
|
10
|
+
const { printCompactHeader, paintLime } = require('../utils/brand');
|
|
11
11
|
const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
|
|
12
12
|
const {
|
|
13
13
|
AVAILABLE_FEATURES,
|
|
@@ -185,7 +185,7 @@ async function runUpdate(module, options = {}) {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
const spinner = ui.spinner();
|
|
188
|
+
const spinner = ui.spinner({ color: paintLime });
|
|
189
189
|
spinner.start(t('update.applyingComponents'));
|
|
190
190
|
try {
|
|
191
191
|
const filesApplied = await applyBaseComponents(projectDir);
|
|
@@ -201,7 +201,7 @@ async function runUpdate(module, options = {}) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
{
|
|
204
|
-
const spinnerPubGet = ui.spinner();
|
|
204
|
+
const spinnerPubGet = ui.spinner({ color: paintLime });
|
|
205
205
|
spinnerPubGet.start(t('update.pubGet'));
|
|
206
206
|
try {
|
|
207
207
|
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
@@ -233,7 +233,7 @@ async function runUpdate(module, options = {}) {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
const spinner = ui.spinner();
|
|
236
|
+
const spinner = ui.spinner({ color: paintLime });
|
|
237
237
|
spinner.start(t('update.applyingCore'));
|
|
238
238
|
try {
|
|
239
239
|
const filesApplied = await applyCoreFiles(projectDir);
|
|
@@ -249,7 +249,7 @@ async function runUpdate(module, options = {}) {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
{
|
|
252
|
-
const spinnerPubGet = ui.spinner();
|
|
252
|
+
const spinnerPubGet = ui.spinner({ color: paintLime });
|
|
253
253
|
spinnerPubGet.start(t('update.pubGet'));
|
|
254
254
|
try {
|
|
255
255
|
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
@@ -284,7 +284,7 @@ async function runUpdate(module, options = {}) {
|
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
|
-
const spinner = ui.spinner();
|
|
287
|
+
const spinner = ui.spinner({ color: paintLime });
|
|
288
288
|
spinner.start(t('update.applying', { module: IOS_RELEASE_UPDATE_TARGET }));
|
|
289
289
|
try {
|
|
290
290
|
const { tokens, pathReplacements } = buildTokens({
|
|
@@ -345,7 +345,7 @@ async function runUpdate(module, options = {}) {
|
|
|
345
345
|
|
|
346
346
|
// Re-apply patch
|
|
347
347
|
{
|
|
348
|
-
const spinner = ui.spinner();
|
|
348
|
+
const spinner = ui.spinner({ color: paintLime });
|
|
349
349
|
spinner.start(t('update.applying', { module: normalized }));
|
|
350
350
|
try {
|
|
351
351
|
const { tokens, pathReplacements } = buildTokens({
|
|
@@ -362,7 +362,7 @@ async function runUpdate(module, options = {}) {
|
|
|
362
362
|
|
|
363
363
|
// flutter pub get
|
|
364
364
|
{
|
|
365
|
-
const spinner = ui.spinner();
|
|
365
|
+
const spinner = ui.spinner({ color: paintLime });
|
|
366
366
|
spinner.start(t('update.pubGet'));
|
|
367
367
|
try {
|
|
368
368
|
await execAsync('flutter pub get', { cwd: projectDir, timeout: 300_000 });
|
|
@@ -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.timedSpinner();
|
|
377
|
+
const spinner = ui.timedSpinner({ color: paintLime });
|
|
378
378
|
spinner.start(t('update.buildRunner'));
|
|
379
379
|
try {
|
|
380
380
|
await execAsync(
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.18.0": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"components": {
|
|
5
|
+
"pt": "Novo KasyDatePicker (seleção de data com modo intervalo). KasyTabs redesenhado seguindo o Figma — espaçamento corrigido, transição suave (fade + slide) ao trocar de aba. KasyAvatar refatorado com gradientes estruturados (mais fácil de customizar).",
|
|
6
|
+
"en": "New KasyDatePicker (date selection with range mode). KasyTabs redesigned to match Figma — fixed spacing, smooth fade + slide transition when switching tabs. KasyAvatar refactored with structured gradients (easier to customize).",
|
|
7
|
+
"es": "Nuevo KasyDatePicker (selección de fecha con modo rango). KasyTabs rediseñado siguiendo el Figma — espaciado corregido, transición suave (fade + slide) al cambiar de pestaña. KasyAvatar refactorizado con gradientes estructurados (más fácil de personalizar)."
|
|
8
|
+
},
|
|
9
|
+
"widget": {
|
|
10
|
+
"pt": "Autor da citação agora aparece junto da quote no widget. Locale carregado sincronamente — corrige caso em que o widget abria em inglês mesmo com o app em pt/es por causa de race condition. Cores do widget Android passaram a vir de res/values/colors.xml (branding consistente, fácil de trocar). Layout do widget responde ao tamanho real (pequeno/médio/grande) sem cortar conteúdo.",
|
|
11
|
+
"en": "Quote author now appears alongside the quote in the widget. Locale loaded synchronously — fixes case where the widget opened in English even with the app in pt/es due to a race condition. Android widget colors now come from res/values/colors.xml (consistent branding, easy to swap). Widget layout responds to actual size (small/medium/large) without clipping content.",
|
|
12
|
+
"es": "El autor de la cita ahora aparece junto a la quote en el widget. Locale cargado sincrónicamente — corrige el caso en que el widget abría en inglés aunque la app estuviera en pt/es por una race condition. Los colores del widget Android ahora vienen de res/values/colors.xml (branding consistente, fácil de cambiar). El layout del widget responde al tamaño real (pequeño/mediano/grande) sin cortar contenido."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
2
16
|
"1.17.0": {
|
|
3
17
|
"modules": {
|
|
4
18
|
"revenuecat": {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enable Firebase Auth providers using the official Firebase CLI flow:
|
|
3
|
+
*
|
|
4
|
+
* 1. Merge `firebase.json` with `auth.providers.{anonymous, emailPassword, googleSignIn}`
|
|
5
|
+
* 2. Run `firebase deploy --only auth --project <id>`
|
|
6
|
+
*
|
|
7
|
+
* Docs: https://firebase.google.com/docs/auth/configure-providers-cli
|
|
8
|
+
*
|
|
9
|
+
* This is the only documented way to activate Google Sign-In without manually
|
|
10
|
+
* clicking "Enable" in the Firebase Console: the deploy creates the OAuth 2.0
|
|
11
|
+
* Web Client automatically (same backend the Console hits internally).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
const { promisify } = require('node:util');
|
|
16
|
+
const execAsync = promisify(require('node:child_process').exec);
|
|
17
|
+
const fs = require('fs-extra');
|
|
18
|
+
|
|
19
|
+
const DEPLOY_TIMEOUT_MS = 5 * 60 * 1000; // 5 min — auth deploys are fast but allow slack
|
|
20
|
+
|
|
21
|
+
async function getGcloudAccountEmail() {
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await execAsync('gcloud config get-value account 2>/dev/null');
|
|
24
|
+
const email = (stdout || '').trim();
|
|
25
|
+
return email && email !== '(unset)' ? email : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Merge auth.providers into the project's firebase.json without touching
|
|
33
|
+
* other top-level keys (functions, firestore, storage, flutter, etc.).
|
|
34
|
+
*/
|
|
35
|
+
async function mergeAuthIntoFirebaseJson(projectDir, providers) {
|
|
36
|
+
const firebaseJsonPath = path.join(projectDir, 'firebase.json');
|
|
37
|
+
if (!(await fs.pathExists(firebaseJsonPath))) {
|
|
38
|
+
return { ok: false, error: 'firebase.json not found in project root' };
|
|
39
|
+
}
|
|
40
|
+
let config;
|
|
41
|
+
try {
|
|
42
|
+
config = await fs.readJson(firebaseJsonPath);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return { ok: false, error: `Failed to parse firebase.json: ${err.message}` };
|
|
45
|
+
}
|
|
46
|
+
config.auth = {
|
|
47
|
+
...(config.auth || {}),
|
|
48
|
+
providers: {
|
|
49
|
+
...(config.auth?.providers || {}),
|
|
50
|
+
...providers,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
await fs.writeJson(firebaseJsonPath, config, { spaces: 2 });
|
|
54
|
+
return { ok: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {object} options
|
|
59
|
+
* @param {string} options.projectDir
|
|
60
|
+
* @param {string} options.projectId
|
|
61
|
+
* @param {string} options.appName
|
|
62
|
+
* @param {string} [options.supportEmail] - falls back to active gcloud account
|
|
63
|
+
* @returns {{ ok: boolean, error?: string, supportEmail?: string }}
|
|
64
|
+
*/
|
|
65
|
+
async function enableAuthViaFirebaseCli({ projectDir, projectId, appName, supportEmail }) {
|
|
66
|
+
// 1. Resolve support email (required by Google's OAuth consent screen)
|
|
67
|
+
let email = (supportEmail || '').trim();
|
|
68
|
+
if (!email) email = await getGcloudAccountEmail();
|
|
69
|
+
if (!email) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: 'support_email_required',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2. Merge firebase.json
|
|
77
|
+
const merge = await mergeAuthIntoFirebaseJson(projectDir, {
|
|
78
|
+
anonymous: true,
|
|
79
|
+
emailPassword: true,
|
|
80
|
+
googleSignIn: {
|
|
81
|
+
oAuthBrandDisplayName: appName,
|
|
82
|
+
supportEmail: email,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
if (!merge.ok) return merge;
|
|
86
|
+
|
|
87
|
+
// 3. Deploy. --non-interactive prevents the CLI from prompting on edge cases.
|
|
88
|
+
const cmd = `firebase deploy --only auth --project ${projectId} --non-interactive`;
|
|
89
|
+
try {
|
|
90
|
+
await execAsync(cmd, { cwd: projectDir, timeout: DEPLOY_TIMEOUT_MS, maxBuffer: 10 * 1024 * 1024 });
|
|
91
|
+
return { ok: true, supportEmail: email };
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const stderr = (err.stderr || '').toString();
|
|
94
|
+
const stdout = (err.stdout || '').toString();
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
error: (stderr || err.message || '').slice(0, 800),
|
|
98
|
+
stdout: stdout.slice(0, 800),
|
|
99
|
+
supportEmail: email,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
enableAuthViaFirebaseCli,
|
|
106
|
+
getGcloudAccountEmail,
|
|
107
|
+
mergeAuthIntoFirebaseJson,
|
|
108
|
+
};
|
|
@@ -689,6 +689,8 @@ async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 1
|
|
|
689
689
|
enabled: true,
|
|
690
690
|
}),
|
|
691
691
|
});
|
|
692
|
+
let googleEnabled = false;
|
|
693
|
+
let googleSignInSkipped = false;
|
|
692
694
|
if (!googleRes.ok) {
|
|
693
695
|
const googleText = await googleRes.text();
|
|
694
696
|
// 409 = already exists — update it to ensure it's enabled.
|
|
@@ -703,13 +705,49 @@ async function enableAuthProviders(projectId, { maxRetries = 3, retryDelayMs = 1
|
|
|
703
705
|
},
|
|
704
706
|
body: JSON.stringify({ enabled: true }),
|
|
705
707
|
});
|
|
706
|
-
|
|
708
|
+
googleEnabled = true;
|
|
709
|
+
} else {
|
|
710
|
+
// 400 INVALID_CONFIG (client_id empty) = no OAuth client created yet.
|
|
711
|
+
// Email/Password and Anonymous are already enabled — just mark Google as skipped.
|
|
712
|
+
googleSignInSkipped = true;
|
|
707
713
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return { ok: true, googleSignInSkipped: true };
|
|
714
|
+
} else {
|
|
715
|
+
googleEnabled = true;
|
|
711
716
|
}
|
|
712
|
-
|
|
717
|
+
|
|
718
|
+
// Step 4: Apple Sign-In (best effort). Apple requires Service ID + JWT client secret
|
|
719
|
+
// that we cannot generate without the user's Apple Developer credentials, but the
|
|
720
|
+
// provider entry itself can be created so the Console shows the row ready for the
|
|
721
|
+
// user to fill in when they ship to iOS. Failure here is silent.
|
|
722
|
+
const appleUrl = `https://identitytoolkit.googleapis.com/admin/v2/projects/${projectId}/defaultSupportedIdpConfigs?idpId=apple.com`;
|
|
723
|
+
let appleEnabled = false;
|
|
724
|
+
try {
|
|
725
|
+
const appleRes = await fetch(appleUrl, {
|
|
726
|
+
method: 'POST',
|
|
727
|
+
headers: {
|
|
728
|
+
Authorization: `Bearer ${token}`,
|
|
729
|
+
'Content-Type': 'application/json',
|
|
730
|
+
'X-Goog-User-Project': projectId,
|
|
731
|
+
},
|
|
732
|
+
body: JSON.stringify({
|
|
733
|
+
name: `projects/${projectId}/defaultSupportedIdpConfigs/apple.com`,
|
|
734
|
+
enabled: true,
|
|
735
|
+
}),
|
|
736
|
+
});
|
|
737
|
+
if (appleRes.ok) {
|
|
738
|
+
appleEnabled = true;
|
|
739
|
+
} else {
|
|
740
|
+
const appleText = await appleRes.text();
|
|
741
|
+
if (appleRes.status === 409 || appleText.includes('ALREADY_EXISTS')) {
|
|
742
|
+
appleEnabled = true;
|
|
743
|
+
}
|
|
744
|
+
// Any other failure is non-fatal — Apple is a nice-to-have.
|
|
745
|
+
}
|
|
746
|
+
} catch (_) {
|
|
747
|
+
// Network error — skip silently.
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return { ok: true, googleSignInSkipped, googleEnabled, appleEnabled };
|
|
713
751
|
}
|
|
714
752
|
const text = await res.text();
|
|
715
753
|
lastError = `${res.status}: ${text}`;
|
|
@@ -766,6 +804,7 @@ async function setupFromScratch(appName, bundleId, options = {}) {
|
|
|
766
804
|
error: `Project created but billing link failed: ${shortError}. Manage projects: ${manageLink}`,
|
|
767
805
|
projectId,
|
|
768
806
|
billingFailed: true,
|
|
807
|
+
billingQuotaError: isQuota,
|
|
769
808
|
billingManualLink: manageLink,
|
|
770
809
|
};
|
|
771
810
|
}
|
package/lib/scaffold/generate.js
CHANGED
|
@@ -66,7 +66,7 @@ const {
|
|
|
66
66
|
removeDevelopmentTeam,
|
|
67
67
|
localizeReleaseDocs,
|
|
68
68
|
} = require('./shared/generator-utils');
|
|
69
|
-
const { pubGet, slangGenerate, buildRunner, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, patchFirebaseServiceWorker } = require('./shared/post-build');
|
|
69
|
+
const { pubGet, slangGenerate, buildRunner, dartFix, flutterfireConfigure, writeGoogleAuthOptions, writeGoogleIosUrlScheme, patchFirebaseServiceWorker } = require('./shared/post-build');
|
|
70
70
|
const { FIREBASE_SOURCE_DIR } = require('./shared/backend-config');
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -97,6 +97,7 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
97
97
|
onProgress = () => {},
|
|
98
98
|
includeWeb = true,
|
|
99
99
|
language = 'en',
|
|
100
|
+
deferGoogleAuthPatches = false,
|
|
100
101
|
} = options;
|
|
101
102
|
|
|
102
103
|
const { applyBackendSetup = null, postBuild = null } = hooks;
|
|
@@ -325,7 +326,12 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
325
326
|
// Android: populate kGoogleWebClientId in lib/google_auth_options.dart.
|
|
326
327
|
// Skip for Supabase — it defines kGoogleIosClientId too, handled separately
|
|
327
328
|
// in new.js after credentials are resolved (readSupabaseGoogleCredentials).
|
|
328
|
-
|
|
329
|
+
//
|
|
330
|
+
// When `deferGoogleAuthPatches` is set, the caller will re-run flutterfire
|
|
331
|
+
// and these patches AFTER enabling Google Sign-In (which creates the OAuth
|
|
332
|
+
// Web Client + REVERSED_CLIENT_ID). Running them here would fail because the
|
|
333
|
+
// IDs don't exist yet, surfacing scary red errors that look like real bugs.
|
|
334
|
+
if (backend !== 'supabase' && !deferGoogleAuthPatches) {
|
|
329
335
|
const gaResult = await writeGoogleAuthOptions(targetDir);
|
|
330
336
|
steps.push({
|
|
331
337
|
name: 'google-auth-options',
|
|
@@ -335,12 +341,14 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
// iOS: register REVERSED_CLIENT_ID as a URL scheme in ios/Runner/Info.plist
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
+
if (!deferGoogleAuthPatches) {
|
|
345
|
+
const iosSchemeResult = await writeGoogleIosUrlScheme(targetDir);
|
|
346
|
+
steps.push({
|
|
347
|
+
name: 'google-ios-url-scheme',
|
|
348
|
+
ok: iosSchemeResult.ok,
|
|
349
|
+
detail: iosSchemeResult.ok ? null : iosSchemeResult.error,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
344
352
|
|
|
345
353
|
// Web: patch firebase-messaging-sw.js with real config values (only when web module is selected)
|
|
346
354
|
if (modules.includes('web')) {
|
|
@@ -364,6 +372,14 @@ async function generateProject(targetDir, backend, options, hooks = {}) {
|
|
|
364
372
|
}
|
|
365
373
|
}
|
|
366
374
|
|
|
375
|
+
// ── 4. Auto-fix lints (directives_ordering, unused_import, etc.) ───────────
|
|
376
|
+
// Runs last so generated code, patches and post-build edits are all covered.
|
|
377
|
+
// Non-fatal: if it fails we still ship a working project; the user can run
|
|
378
|
+
// `dart fix --apply` manually later.
|
|
379
|
+
onProgress('dart-fix');
|
|
380
|
+
const dartFixResult = await dartFix(targetDir);
|
|
381
|
+
steps.push({ name: 'dart-fix', ok: dartFixResult.ok, detail: dartFixResult.ok ? null : dartFixResult.error });
|
|
382
|
+
|
|
367
383
|
return { steps, packageName, appName, bundleId, firebaseProjectId, ...returnExtra };
|
|
368
384
|
}
|
|
369
385
|
|
|
@@ -45,6 +45,13 @@ async function buildRunner(projectDir) {
|
|
|
45
45
|
return run('dart run build_runner build --delete-conflicting-outputs', projectDir, 600_000); // 10 min
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Auto-applies fixable lints (directives_ordering, unused_import, etc.) so the
|
|
49
|
+
// generated project opens in the user's IDE with zero warnings. Runs after
|
|
50
|
+
// build_runner so generated files are reordered too where applicable.
|
|
51
|
+
async function dartFix(projectDir) {
|
|
52
|
+
return run('dart fix --apply', projectDir, 180_000); // 3 min
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
async function flutterfireConfigure(projectDir, firebaseProjectId, options = {}) {
|
|
49
56
|
const { includeWeb = true } = options;
|
|
50
57
|
const platforms = includeWeb ? 'android,ios,web' : 'android,ios';
|
|
@@ -861,6 +868,7 @@ module.exports = {
|
|
|
861
868
|
pubGet,
|
|
862
869
|
slangGenerate,
|
|
863
870
|
buildRunner,
|
|
871
|
+
dartFix,
|
|
864
872
|
flutterfireConfigure,
|
|
865
873
|
writeGoogleAuthOptions,
|
|
866
874
|
writeGoogleIosUrlScheme,
|
package/lib/utils/brand.js
CHANGED
|
@@ -10,22 +10,26 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const kleur = require('kleur');
|
|
13
|
-
const gradient = require('gradient-string');
|
|
14
13
|
const boxenPackage = require('boxen');
|
|
15
14
|
const boxen = boxenPackage.default || boxenPackage;
|
|
16
15
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
// Brand palette (memory/kasy_brand_colors.md). Single source of truth for the
|
|
17
|
+
// CLI's only brand color: change BRAND_RGB and every spinner, header, logo and
|
|
18
|
+
// success card across all commands updates automatically.
|
|
19
|
+
//
|
|
20
|
+
// kleur 4 in this repo doesn't ship `.hex()`, so we emit 24-bit ANSI directly.
|
|
21
|
+
// Truecolor is supported by every macOS/Linux terminal we care about.
|
|
22
|
+
const BRAND_RGB = { r: 210, g: 245, b: 30 }; // #D2F51E lime
|
|
23
|
+
const paintLime = (text) => `\x1b[38;2;${BRAND_RGB.r};${BRAND_RGB.g};${BRAND_RGB.b}m${text}\x1b[0m`;
|
|
24
|
+
const wordmark = paintLime;
|
|
21
25
|
const DOMAIN_SUFFIX = kleur.gray('.dev');
|
|
22
26
|
|
|
23
27
|
function printBanner(_tr) {
|
|
24
28
|
const bar = kleur.gray('─────────────────────────────────────────────────');
|
|
25
29
|
const logo = [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`${
|
|
30
|
+
wordmark(' ╦╔═ ╔═╗ ╔═╗ ╦ ╦'),
|
|
31
|
+
wordmark(' ╠╩╗ ╠═╣ ╚═╗ ╚╦╝'),
|
|
32
|
+
`${wordmark(' ╩ ╩ ╩ ╩ ╚═╝ ╩ ')} ${DOMAIN_SUFFIX}`,
|
|
29
33
|
].join('\n');
|
|
30
34
|
|
|
31
35
|
console.log(`\n${bar}\n`);
|
|
@@ -35,18 +39,18 @@ function printBanner(_tr) {
|
|
|
35
39
|
|
|
36
40
|
function printCompactHeader(_tr) {
|
|
37
41
|
console.log('');
|
|
38
|
-
console.log(` ${
|
|
42
|
+
console.log(` ${wordmark('✦ KASY')}${DOMAIN_SUFFIX}`);
|
|
39
43
|
console.log('');
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
function successBox(title, body, { padding = 1, marginTop = 1, marginBottom = 1 } = {}) {
|
|
43
47
|
return boxen(
|
|
44
|
-
`${
|
|
48
|
+
`${paintLime(`✦ ${title}`)}\n\n${body}`,
|
|
45
49
|
{
|
|
46
50
|
padding,
|
|
47
51
|
margin: { top: marginTop, bottom: marginBottom, left: 1, right: 1 },
|
|
48
52
|
borderStyle: 'round',
|
|
49
|
-
borderColor: '
|
|
53
|
+
borderColor: 'gray',
|
|
50
54
|
}
|
|
51
55
|
);
|
|
52
56
|
}
|
|
@@ -64,7 +68,7 @@ function infoBox(title, body, { padding = 1, marginTop = 1, marginBottom = 1 } =
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
module.exports = {
|
|
67
|
-
|
|
71
|
+
paintLime,
|
|
68
72
|
printBanner,
|
|
69
73
|
printCompactHeader,
|
|
70
74
|
successBox,
|