kasy-cli 1.31.0 → 1.31.1
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/lib/scaffold/backends/supabase/deploy.js +36 -25
- package/lib/scaffold/shared/fcm-service-account.js +2 -2
- package/lib/scaffold/shared/post-build.js +5 -1
- package/package.json +1 -1
- package/templates/firebase/lib/core/bottom_menu/active_tab_notifier.dart +14 -0
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +2 -12
- package/templates/firebase/lib/core/states/user_state_notifier.dart +4 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +4 -1
- package/templates/firebase/pubspec.yaml +1 -1
|
@@ -188,17 +188,18 @@ async function getProjectKeys(projectRef) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// The Supabase CLI stores its access token via go-keyring under the service
|
|
191
|
-
// "Supabase CLI"
|
|
192
|
-
//
|
|
191
|
+
// "Supabase CLI". The account/key has varied across CLI versions (e.g.
|
|
192
|
+
// "supabase", "access-token"), so we DON'T hardcode it — we read by service
|
|
193
|
+
// only. Each OS keeps this in a different vault. Used by the Management API
|
|
193
194
|
// calls below (auth providers), which the CLI itself has no command for.
|
|
194
195
|
const SUPABASE_KEYRING_SERVICE = 'Supabase CLI';
|
|
195
|
-
const SUPABASE_KEYRING_USER = 'access-token';
|
|
196
196
|
|
|
197
|
-
// Read
|
|
198
|
-
// wincred backend names the target "<service>:<user>"
|
|
199
|
-
//
|
|
200
|
-
// the
|
|
201
|
-
|
|
197
|
+
// Read the Supabase token out of the Windows Credential Manager. go-keyring's
|
|
198
|
+
// wincred backend names the target "<service>:<user>", but since the user part
|
|
199
|
+
// differs by CLI version we ENUMERATE every credential whose target starts with
|
|
200
|
+
// the service name and return the first non-empty blob as base64 — the caller
|
|
201
|
+
// picks the decoding. Robust to the account name we can't see from here.
|
|
202
|
+
async function readWindowsSupabaseTokenBase64() {
|
|
202
203
|
const ps = `
|
|
203
204
|
$ErrorActionPreference = 'Stop'
|
|
204
205
|
$sig = @"
|
|
@@ -206,7 +207,7 @@ using System;
|
|
|
206
207
|
using System.Runtime.InteropServices;
|
|
207
208
|
public class KasyCred {
|
|
208
209
|
[DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
|
|
209
|
-
public static extern bool
|
|
210
|
+
public static extern bool CredEnumerate(string filter, int flag, out int count, out IntPtr creds);
|
|
210
211
|
[DllImport("advapi32.dll")] public static extern void CredFree(IntPtr cred);
|
|
211
212
|
[StructLayout(LayoutKind.Sequential)] public struct CREDENTIAL {
|
|
212
213
|
public int Flags; public int Type; public IntPtr TargetName; public IntPtr Comment;
|
|
@@ -217,13 +218,18 @@ public class KasyCred {
|
|
|
217
218
|
}
|
|
218
219
|
"@
|
|
219
220
|
Add-Type $sig | Out-Null
|
|
220
|
-
$ptr = [IntPtr]::Zero
|
|
221
|
-
if ([KasyCred]::
|
|
222
|
-
$
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
221
|
+
$count = 0; $ptr = [IntPtr]::Zero
|
|
222
|
+
if ([KasyCred]::CredEnumerate('${SUPABASE_KEYRING_SERVICE}*', 0, [ref]$count, [ref]$ptr)) {
|
|
223
|
+
for ($i = 0; $i -lt $count; $i++) {
|
|
224
|
+
$credPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($ptr, $i * [IntPtr]::Size)
|
|
225
|
+
$c = [System.Runtime.InteropServices.Marshal]::PtrToStructure($credPtr, [type]([KasyCred+CREDENTIAL]))
|
|
226
|
+
if ($c.CredentialBlobSize -gt 0) {
|
|
227
|
+
$bytes = New-Object byte[] $c.CredentialBlobSize
|
|
228
|
+
[System.Runtime.InteropServices.Marshal]::Copy($c.CredentialBlob, $bytes, 0, $c.CredentialBlobSize)
|
|
229
|
+
[Convert]::ToBase64String($bytes)
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
}
|
|
227
233
|
}
|
|
228
234
|
`;
|
|
229
235
|
const encoded = Buffer.from(ps, 'utf16le').toString('base64');
|
|
@@ -265,7 +271,7 @@ async function getSupabaseAccessToken() {
|
|
|
265
271
|
|
|
266
272
|
if (process.platform === 'win32') {
|
|
267
273
|
try {
|
|
268
|
-
const b64 = await
|
|
274
|
+
const b64 = await readWindowsSupabaseTokenBase64();
|
|
269
275
|
if (!b64) return null;
|
|
270
276
|
const buf = Buffer.from(b64, 'base64');
|
|
271
277
|
// UTF-16LE blobs have a NUL after most bytes; UTF-8 blobs don't.
|
|
@@ -277,15 +283,20 @@ async function getSupabaseAccessToken() {
|
|
|
277
283
|
}
|
|
278
284
|
}
|
|
279
285
|
|
|
280
|
-
// Linux (libsecret).
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
// Linux (libsecret). The account key has varied by CLI version, so try the
|
|
287
|
+
// ones we've seen before giving up.
|
|
288
|
+
for (const user of ['supabase', 'access-token']) {
|
|
289
|
+
try {
|
|
290
|
+
const { stdout } = await execAsync(
|
|
291
|
+
`secret-tool lookup service "${SUPABASE_KEYRING_SERVICE}" username "${user}"`,
|
|
292
|
+
);
|
|
293
|
+
const token = decodeKeyring(stdout);
|
|
294
|
+
if (token) return token;
|
|
295
|
+
} catch {
|
|
296
|
+
// try next
|
|
297
|
+
}
|
|
288
298
|
}
|
|
299
|
+
return null;
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
/**
|
|
@@ -97,7 +97,7 @@ async function createFcmServiceAccountKey(projectId) {
|
|
|
97
97
|
// the discover+grant with backoff before giving up.
|
|
98
98
|
let saEmail = '';
|
|
99
99
|
let lastErr = 'Firebase Admin SDK service account not found';
|
|
100
|
-
for (let attempt = 1; attempt <=
|
|
100
|
+
for (let attempt = 1; attempt <= 7; attempt++) {
|
|
101
101
|
const saResult = await findFirebaseAdminSdkSA(projectId.trim());
|
|
102
102
|
if (saResult.ok) {
|
|
103
103
|
const roleResult = await grantFcmAdminRole(projectId.trim(), saResult.email);
|
|
@@ -109,7 +109,7 @@ async function createFcmServiceAccountKey(projectId) {
|
|
|
109
109
|
} else {
|
|
110
110
|
lastErr = saResult.error;
|
|
111
111
|
}
|
|
112
|
-
if (attempt <
|
|
112
|
+
if (attempt < 7) await sleep(10000);
|
|
113
113
|
}
|
|
114
114
|
if (!saEmail) {
|
|
115
115
|
return { ok: false, error: lastErr };
|
|
@@ -38,7 +38,11 @@ async function run(cmd, cwd, timeout) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function pubGet(projectDir) {
|
|
41
|
-
|
|
41
|
+
// 15 min: the FIRST `flutter pub get` on a fresh machine downloads the whole
|
|
42
|
+
// dependency tree (this template pulls in firebase, supabase, revenuecat,
|
|
43
|
+
// stripe, sentry…), which timed out at the old 5-min cap on slower Windows
|
|
44
|
+
// connections. It's a ceiling, not a wait — a warm cache still returns fast.
|
|
45
|
+
return run('flutter pub get', projectDir, 900_000);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
async function slangGenerate(projectDir) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import 'package:flutter/foundation.dart';
|
|
2
|
+
|
|
3
|
+
/// The bottom-bar tab the user last opened, held at top level so it outlives the
|
|
4
|
+
/// [BottomMenu] remount that happens whenever the responsive layout flips
|
|
5
|
+
/// small↔large (e.g. toggling the web device preview, which renders the app in a
|
|
6
|
+
/// phone-width frame). Persisting it lets the bottom bar restore the tab instead
|
|
7
|
+
/// of snapping back to the first one on remount or hard reload (F5).
|
|
8
|
+
///
|
|
9
|
+
/// It lives in its own dependency-free file so both [BottomMenu] and the logout
|
|
10
|
+
/// flow can touch it without an import cycle. Cleared on logout so a fresh login
|
|
11
|
+
/// always lands on the default tab. Null until the user opens a tab.
|
|
12
|
+
final ValueNotifier<String?> activeTabRouteNotifier = ValueNotifier<String?>(
|
|
13
|
+
null,
|
|
14
|
+
);
|
|
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|
|
4
4
|
import 'package:flutter/services.dart';
|
|
5
5
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
6
6
|
import 'package:kasy_kit/components/kasy_sidebar.dart';
|
|
7
|
+
import 'package:kasy_kit/core/bottom_menu/active_tab_notifier.dart';
|
|
7
8
|
import 'package:kasy_kit/core/bottom_menu/bottom_router.dart';
|
|
8
9
|
import 'package:kasy_kit/core/bottom_menu/kasy_bottom_bar_factory.dart';
|
|
9
10
|
import 'package:kasy_kit/core/bottom_menu/web_content_wrapper.dart';
|
|
@@ -15,19 +16,8 @@ import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
|
15
16
|
import 'package:kasy_kit/features/settings/ui/widgets/kasy_user_avatar.dart';
|
|
16
17
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
17
18
|
|
|
18
|
-
/// The bottom-bar tab the user last opened, held at top level so it outlives the
|
|
19
|
-
/// [BottomMenu] remount that happens whenever the responsive layout flips
|
|
20
|
-
/// small↔large. Toggling the web device preview does exactly that: the app
|
|
21
|
-
/// renders inside a phone-width frame when on and at full desktop width when
|
|
22
|
-
/// off, so each toggle rebuilds [bart.BartScaffold] from scratch (its index
|
|
23
|
-
/// notifier starts at 0). Persisting the tab here lets [BottomMenu] restore it
|
|
24
|
-
/// instead of snapping back to the first tab. Null until the user opens a tab.
|
|
25
|
-
final ValueNotifier<String?> activeTabRouteNotifier = ValueNotifier<String?>(
|
|
26
|
-
null,
|
|
27
|
-
);
|
|
28
|
-
|
|
29
19
|
/// Records the active tab so it survives the next remount. Wired to
|
|
30
|
-
/// [bart.BartScaffold.onRouteChanged].
|
|
20
|
+
/// [bart.BartScaffold.onRouteChanged]. See [activeTabRouteNotifier].
|
|
31
21
|
void _rememberActiveTab(bart.BartMenuRoute route) {
|
|
32
22
|
activeTabRouteNotifier.value = route.path;
|
|
33
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'dart:async';
|
|
2
2
|
|
|
3
3
|
import 'package:flutter/foundation.dart';
|
|
4
|
+
import 'package:kasy_kit/core/bottom_menu/active_tab_notifier.dart';
|
|
4
5
|
import 'package:kasy_kit/core/config/features.dart';
|
|
5
6
|
import 'package:kasy_kit/core/data/models/entitlement.dart';
|
|
6
7
|
import 'package:kasy_kit/core/data/models/subscription.dart';
|
|
@@ -139,6 +140,9 @@ class UserStateNotifier extends _$UserStateNotifier implements OnStartService {
|
|
|
139
140
|
// Biometric lock is a per-account preference, not a device-wide one.
|
|
140
141
|
// The next user signing in on this install should start without it set.
|
|
141
142
|
await ref.read(sharedPreferencesProvider).setBiometricEnabled(false);
|
|
143
|
+
// Forget the last bottom-bar tab so the next login lands on the default tab
|
|
144
|
+
// (Home) instead of wherever the previous account left off.
|
|
145
|
+
activeTabRouteNotifier.value = null;
|
|
142
146
|
state = const UserState(user: User.anonymous());
|
|
143
147
|
if (mode == AuthenticationMode.anonymous) {
|
|
144
148
|
await _loadAnonymousState();
|
|
@@ -13,7 +13,10 @@ import 'package:provider/provider.dart';
|
|
|
13
13
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
14
14
|
import 'package:universal_html/html.dart' as html;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// Suffixed `_v2` because the default flipped from OFF to ON. Values saved under
|
|
17
|
+
// the old key were written while the default was OFF, so we ignore them and
|
|
18
|
+
// start fresh — every install now gets the new ON default until it's toggled.
|
|
19
|
+
const String webDevicePreviewEnabledPrefKey = 'web_device_preview_enabled_v2';
|
|
17
20
|
const String _platformPrefKey = 'web_device_preview_platform';
|
|
18
21
|
const String _iosIndexPrefKey = 'web_device_preview_ios_index';
|
|
19
22
|
const String _androidIndexPrefKey = 'web_device_preview_android_index';
|
|
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
|
16
16
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
17
17
|
# In Windows, build-name is used as the major, minor, and patch parts
|
|
18
18
|
# of the product and file versions while build-number is used as the build suffix.
|
|
19
|
-
version: 1.0.0+
|
|
19
|
+
version: 1.0.0+35
|
|
20
20
|
|
|
21
21
|
environment:
|
|
22
22
|
sdk: ^3.11.0
|