kasy-cli 1.26.0 → 1.27.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.
@@ -205,16 +205,17 @@ async function confirmIdentities(backend, gcloudAccount, tr, cancel) {
205
205
  * id elsewhere). Never throws.
206
206
  */
207
207
  async function ensureGcloudReady(tr) {
208
- const { spawnSync } = require('node:child_process');
208
+ const { spawnSync, exec } = require('node:child_process');
209
209
  const { augmentedEnv } = require('../utils/env-tools');
210
210
 
211
211
  let check = await checkGcloudAuth();
212
212
  if (check.ok) return check;
213
213
 
214
- // 1) Not installed → offer to install it for them.
214
+ // 1) Not installed → tell the user it's missing, then offer to install it.
215
215
  if (check.missing === 'gcloud') {
216
216
  const guide = getGcloudInstallInstructions();
217
217
  if (guide.install) {
218
+ ui.log.warn(tr('new.firebase.create.gcloudMissing'));
218
219
  const doInstall = await ui.confirm({
219
220
  message: tr('new.firebase.create.gcloudInstallConfirm'),
220
221
  initialValue: true,
@@ -222,11 +223,20 @@ async function ensureGcloudReady(tr) {
222
223
  if (doInstall) {
223
224
  const spinner = ui.timedSpinner();
224
225
  spinner.start(tr('new.firebase.create.gcloudInstalling'));
225
- spawnSync(guide.install, { stdio: 'pipe', shell: true });
226
+ // Run async (NOT spawnSync): a synchronous child blocks the event loop,
227
+ // which freezes the spinner — the user stares at a dead screen for
228
+ // minutes. exec keeps the clock ticking so it's clear it's working.
229
+ await new Promise((resolve) => {
230
+ exec(
231
+ guide.install,
232
+ { env: augmentedEnv(), timeout: 600_000, windowsHide: true, maxBuffer: 50 * 1024 * 1024 },
233
+ () => resolve()
234
+ );
235
+ });
226
236
  spinner.stop(tr('new.firebase.create.gcloudInstalling'));
227
- // checkGcloudAuth / the later billing+project calls run with the plain
228
- // process PATH, which doesn't see a tool winget just dropped on the
229
- // machine PATH. Inject the known gcloud dir so the rest of the flow works.
237
+ // checkGcloudAuth and the later billing/project calls use the plain
238
+ // process PATH, which doesn't see a tool winget just put on the machine
239
+ // PATH. Inject the known gcloud dir so the rest of the flow works.
230
240
  const env = augmentedEnv();
231
241
  if (env.PATH) process.env.PATH = env.PATH;
232
242
  if (process.platform === 'win32' && env.Path) process.env.Path = env.Path;
@@ -238,6 +238,7 @@ module.exports = {
238
238
  'new.firebase.create.installAfter': 'Then log in',
239
239
  'new.firebase.create.installUrl': 'Or download from',
240
240
  'new.firebase.create.authCommand': 'Run: gcloud auth login',
241
+ 'new.firebase.create.gcloudMissing': 'The Google Cloud CLI (gcloud) is required to create the Firebase project from scratch, and it is not installed yet.',
241
242
  'new.firebase.create.gcloudInstallConfirm': 'Install the Google Cloud CLI (gcloud) automatically now?',
242
243
  'new.firebase.create.gcloudInstalling': 'Installing the Google Cloud CLI…',
243
244
  'new.firebase.create.gcloudAuthOpening': 'Opening the Google sign-in in your browser — log in with your account…',
@@ -238,6 +238,7 @@ module.exports = {
238
238
  'new.firebase.create.installAfter': 'Luego inicia sesión',
239
239
  'new.firebase.create.installUrl': 'O descarga en',
240
240
  'new.firebase.create.authCommand': 'Ejecuta: gcloud auth login',
241
+ 'new.firebase.create.gcloudMissing': 'El Google Cloud CLI (gcloud) es necesario para crear el proyecto Firebase desde cero, y aún no está instalado.',
241
242
  'new.firebase.create.gcloudInstallConfirm': '¿Instalar el Google Cloud CLI (gcloud) automáticamente ahora?',
242
243
  'new.firebase.create.gcloudInstalling': 'Instalando el Google Cloud CLI…',
243
244
  'new.firebase.create.gcloudAuthOpening': 'Abriendo el inicio de sesión de Google en el navegador — entra con tu cuenta…',
@@ -238,6 +238,7 @@ module.exports = {
238
238
  'new.firebase.create.installAfter': 'Depois faca login',
239
239
  'new.firebase.create.installUrl': 'Ou baixe em',
240
240
  'new.firebase.create.authCommand': 'Execute: gcloud auth login',
241
+ 'new.firebase.create.gcloudMissing': 'O Google Cloud CLI (gcloud) é necessário para criar o projeto Firebase do zero, e ainda não está instalado.',
241
242
  'new.firebase.create.gcloudInstallConfirm': 'Instalar o Google Cloud CLI (gcloud) automaticamente agora?',
242
243
  'new.firebase.create.gcloudInstalling': 'Instalando o Google Cloud CLI…',
243
244
  'new.firebase.create.gcloudAuthOpening': 'Abrindo o login do Google no navegador — entre com a sua conta…',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kasy-cli",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "CLI for scaffolding production-ready Flutter SaaS apps with Firebase, Supabase, or API REST backends.",
5
5
  "bin": {
6
6
  "kasy": "./bin/kasy.js"
@@ -227,28 +227,24 @@ class _FocusableSidebarState extends State<_FocusableSidebar> {
227
227
 
228
228
  @override
229
229
  Widget build(BuildContext context) {
230
- // Ordered so the first Tab reveals the "skip to content" link (order 0),
231
- // then the sidebar (order 1). The header (2) and content (3) come after,
232
- // ordered by the scaffold's OrderedTraversalPolicy. The invisible anchor
233
- // holds the initial focus so that very first Tab lands on the skip link.
230
+ // Reading-order group (NOT an ordered policy): this is the exact structure
231
+ // that made the anchor hold the initial focus. The anchor sits at (0,0) and
232
+ // is skipped by Tab; the skip link is positioned at the very top, so reading
233
+ // order makes it the FIRST Tab stop, then the sidebar items, then (via the
234
+ // scaffold) the header and content. Swapping in an OrderedTraversalPolicy
235
+ // here broke the anchor, so we keep reading order and rely on position.
234
236
  return FocusTraversalGroup(
235
- policy: OrderedTraversalPolicy(),
236
237
  child: Stack(
237
238
  clipBehavior: Clip.none,
238
239
  children: [
239
- FocusTraversalOrder(
240
- order: const NumericFocusOrder(1),
241
- child: FocusTraversalGroup(child: widget.child),
242
- ),
240
+ widget.child,
243
241
  // Zero-size sibling; only holds the initial keyboard focus.
244
242
  Focus(focusNode: _anchor, child: const SizedBox.shrink()),
243
+ // Topmost on screen, so reading order makes it the first Tab stop.
245
244
  Positioned(
246
245
  top: KasySpacing.sm,
247
246
  left: KasySpacing.sm,
248
- child: FocusTraversalOrder(
249
- order: const NumericFocusOrder(0),
250
- child: _SkipToContentLink(onSkip: _skipToContent),
251
- ),
247
+ child: _SkipToContentLink(onSkip: _skipToContent),
252
248
  ),
253
249
  ],
254
250
  ),