smoonb 0.0.40 → 0.0.44

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/smoonb.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * smoonb - Complete Supabase backup and migration tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.40",
3
+ "version": "0.0.44",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const fs = require('fs');
4
4
  const { readConfig, getSourceProject, getTargetProject } = require('../utils/config');
5
5
  const { showBetaBanner } = require('../utils/banner');
6
+ const inquirer = require('inquirer');
6
7
 
7
8
  module.exports = async (options) => {
8
9
  showBetaBanner();
@@ -28,6 +29,12 @@ module.exports = async (options) => {
28
29
  // 3. Perguntar quais componentes restaurar
29
30
  const components = await askRestoreComponents(selectedBackup.path);
30
31
 
32
+ // Validar que pelo menos um componente foi selecionado
33
+ if (!Object.values(components).some(Boolean)) {
34
+ console.error(chalk.red('\n❌ Nenhum componente selecionado para restauração!'));
35
+ process.exit(1);
36
+ }
37
+
31
38
  // 4. Mostrar resumo
32
39
  showRestoreSummary(selectedBackup, components, targetProject);
33
40
 
@@ -41,27 +48,29 @@ module.exports = async (options) => {
41
48
  // 6. Executar restauração
42
49
  console.log(chalk.blue('\n🚀 Iniciando restauração...'));
43
50
 
44
- // 6.1 Database
45
- await restoreDatabaseGz(
46
- path.join(selectedBackup.path, selectedBackup.backupFile),
47
- targetProject.targetDatabaseUrl
48
- );
51
+ // 6.1 Database (se selecionado)
52
+ if (components.database) {
53
+ await restoreDatabaseGz(
54
+ path.join(selectedBackup.path, selectedBackup.backupFile),
55
+ targetProject.targetDatabaseUrl
56
+ );
57
+ }
49
58
 
50
59
  // 6.2 Edge Functions (se selecionado)
51
60
  if (components.edgeFunctions) {
52
61
  await restoreEdgeFunctions(selectedBackup.path, targetProject);
53
62
  }
54
63
 
55
- // 6.3 Storage Buckets (se selecionado)
56
- if (components.storage) {
57
- await restoreStorageBuckets(selectedBackup.path, targetProject);
58
- }
59
-
60
- // 6.4 Auth Settings (se selecionado)
64
+ // 6.3 Auth Settings (se selecionado)
61
65
  if (components.authSettings) {
62
66
  await restoreAuthSettings(selectedBackup.path, targetProject);
63
67
  }
64
68
 
69
+ // 6.4 Storage Buckets (se selecionado)
70
+ if (components.storage) {
71
+ await restoreStorageBuckets(selectedBackup.path, targetProject);
72
+ }
73
+
65
74
  // 6.5 Database Settings (se selecionado)
66
75
  if (components.databaseSettings) {
67
76
  await restoreDatabaseSettings(selectedBackup.path, targetProject);
@@ -80,7 +89,7 @@ module.exports = async (options) => {
80
89
  }
81
90
  };
82
91
 
83
- // Listar backups válidos (apenas com .backup.gz)
92
+ // Listar backups válidos (aceita .backup.gz e .backup)
84
93
  async function listValidBackups(backupsDir) {
85
94
  if (!fs.existsSync(backupsDir)) {
86
95
  return [];
@@ -93,7 +102,10 @@ async function listValidBackups(backupsDir) {
93
102
  if (item.isDirectory() && item.name.startsWith('backup-')) {
94
103
  const backupPath = path.join(backupsDir, item.name);
95
104
  const files = fs.readdirSync(backupPath);
96
- const backupFile = files.find(file => file.endsWith('.backup.gz'));
105
+ // Aceitar tanto .backup.gz quanto .backup
106
+ const backupFile = files.find(file =>
107
+ file.endsWith('.backup.gz') || file.endsWith('.backup')
108
+ );
97
109
 
98
110
  if (backupFile) {
99
111
  const manifestPath = path.join(backupPath, 'backup-manifest.json');
@@ -169,59 +181,80 @@ async function selectBackupInteractive(backups) {
169
181
 
170
182
  // Perguntar quais componentes restaurar
171
183
  async function askRestoreComponents(backupPath) {
172
- const components = {
173
- edgeFunctions: true,
174
- storage: false,
175
- authSettings: false,
176
- databaseSettings: false,
177
- realtimeSettings: false
178
- };
184
+ const questions = [];
179
185
 
180
- const readline = require('readline').createInterface({
181
- input: process.stdin,
182
- output: process.stdout
186
+ // Database
187
+ questions.push({
188
+ type: 'confirm',
189
+ name: 'restoreDatabase',
190
+ message: 'Deseja restaurar Database?',
191
+ default: true
183
192
  });
184
193
 
185
- const question = (query) => new Promise(resolve => readline.question(query, resolve));
186
-
187
- console.log(chalk.blue('\n📦 Selecione os componentes para restaurar:'));
188
-
189
194
  // Edge Functions
190
195
  const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
191
196
  if (fs.existsSync(edgeFunctionsDir) && fs.readdirSync(edgeFunctionsDir).length > 0) {
192
- const edgeChoice = await question('Deseja restaurar Edge Functions? (S/n): ');
193
- components.edgeFunctions = edgeChoice.toLowerCase() !== 'n';
197
+ questions.push({
198
+ type: 'confirm',
199
+ name: 'restoreEdgeFunctions',
200
+ message: 'Deseja restaurar Edge Functions?',
201
+ default: true
202
+ });
203
+ }
204
+
205
+ // Auth Settings
206
+ if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
207
+ questions.push({
208
+ type: 'confirm',
209
+ name: 'restoreAuthSettings',
210
+ message: 'Deseja restaurar Auth Settings (interativo)?',
211
+ default: true
212
+ });
194
213
  }
195
214
 
196
215
  // Storage Buckets
197
216
  const storageDir = path.join(backupPath, 'storage');
198
217
  if (fs.existsSync(storageDir) && fs.readdirSync(storageDir).length > 0) {
199
- const storageChoice = await question('Deseja restaurar Storage Buckets? (s/N): ');
200
- components.storage = storageChoice.toLowerCase() === 's';
201
- }
202
-
203
- // Auth Settings
204
- if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
205
- const authChoice = await question('Deseja restaurar Auth Settings? (s/N): ');
206
- components.authSettings = authChoice.toLowerCase() === 's';
218
+ questions.push({
219
+ type: 'confirm',
220
+ name: 'restoreStorage',
221
+ message: 'Deseja ver informações de Storage Buckets?',
222
+ default: false
223
+ });
207
224
  }
208
225
 
209
- // Database Settings
226
+ // Database Extensions and Settings
210
227
  const dbSettingsFiles = fs.readdirSync(backupPath)
211
228
  .filter(file => file.startsWith('database-settings-') && file.endsWith('.json'));
212
229
  if (dbSettingsFiles.length > 0) {
213
- const dbChoice = await question('Deseja restaurar Database Extensions and Settings? (s/N): ');
214
- components.databaseSettings = dbChoice.toLowerCase() === 's';
230
+ questions.push({
231
+ type: 'confirm',
232
+ name: 'restoreDatabaseSettings',
233
+ message: 'Deseja restaurar Database Extensions and Settings?',
234
+ default: false
235
+ });
215
236
  }
216
237
 
217
238
  // Realtime Settings
218
239
  if (fs.existsSync(path.join(backupPath, 'realtime-settings.json'))) {
219
- const realtimeChoice = await question('Deseja restaurar Realtime Settings? (s/N): ');
220
- components.realtimeSettings = realtimeChoice.toLowerCase() === 's';
240
+ questions.push({
241
+ type: 'confirm',
242
+ name: 'restoreRealtimeSettings',
243
+ message: 'Deseja restaurar Realtime Settings (interativo)?',
244
+ default: true
245
+ });
221
246
  }
222
247
 
223
- readline.close();
224
- return components;
248
+ const answers = await inquirer.prompt(questions);
249
+
250
+ return {
251
+ database: answers.restoreDatabase,
252
+ edgeFunctions: answers.restoreEdgeFunctions || false,
253
+ storage: answers.restoreStorage || false,
254
+ authSettings: answers.restoreAuthSettings || false,
255
+ databaseSettings: answers.restoreDatabaseSettings || false,
256
+ realtimeSettings: answers.restoreRealtimeSettings || false
257
+ };
225
258
  }
226
259
 
227
260
  // Mostrar resumo da restauração
@@ -235,7 +268,9 @@ function showRestoreSummary(backup, components, targetProject) {
235
268
  console.log(chalk.cyan('Componentes que serão restaurados:'));
236
269
  console.log('');
237
270
 
238
- console.log('✅ Database (psql -f via Docker)');
271
+ if (components.database) {
272
+ console.log('✅ Database (psql -f via Docker)');
273
+ }
239
274
 
240
275
  if (components.edgeFunctions) {
241
276
  const edgeFunctionsDir = path.join(backup.path, 'edge-functions');
@@ -246,20 +281,20 @@ function showRestoreSummary(backup, components, targetProject) {
246
281
  functions.forEach(func => console.log(` - ${func}`));
247
282
  }
248
283
 
249
- if (components.storage) {
250
- console.log('📦 Storage Buckets: Restaurar buckets e objetos');
284
+ if (components.authSettings) {
285
+ console.log('🔐 Auth Settings: Exibir URL e valores para configuração manual');
251
286
  }
252
287
 
253
- if (components.authSettings) {
254
- console.log('🔐 Auth Settings: Restaurar configurações de autenticação');
288
+ if (components.storage) {
289
+ console.log('📦 Storage Buckets: Exibir informações e instruções do Google Colab');
255
290
  }
256
291
 
257
292
  if (components.databaseSettings) {
258
- console.log('🔧 Database Settings: Restaurar extensões e configurações');
293
+ console.log('🔧 Database Extensions and Settings: Restaurar via SQL');
259
294
  }
260
295
 
261
296
  if (components.realtimeSettings) {
262
- console.log('🔄 Realtime Settings: Restaurar configurações do Realtime');
297
+ console.log('🔄 Realtime Settings: Exibir URL e valores para configuração manual');
263
298
  }
264
299
 
265
300
  console.log('');
@@ -281,9 +316,9 @@ async function confirmExecution() {
281
316
  }
282
317
 
283
318
  // Restaurar Database via psql (conforme documentação oficial Supabase: https://supabase.com/docs/guides/platform/migrating-within-supabase/dashboard-restore)
319
+ // Aceita tanto arquivos .backup.gz quanto .backup já descompactados
284
320
  async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
285
321
  console.log(chalk.blue('📊 Restaurando Database...'));
286
- console.log(chalk.gray(' - Descompactando backup (se necessário)...'));
287
322
 
288
323
  try {
289
324
  const { execSync } = require('child_process');
@@ -292,9 +327,11 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
292
327
  const fileName = path.basename(backupFilePath);
293
328
  let uncompressedFile = fileName;
294
329
 
295
- // Descompactar .gz se necessário
296
- if (fileName.endsWith('.gz')) {
330
+ // Verificar se é arquivo .backup.gz (compactado) ou .backup (descompactado)
331
+ if (fileName.endsWith('.backup.gz')) {
332
+ console.log(chalk.gray(' - Arquivo .backup.gz detectado'));
297
333
  console.log(chalk.gray(' - Extraindo arquivo .gz...'));
334
+
298
335
  const unzipCmd = [
299
336
  'docker run --rm',
300
337
  `-v "${backupDirAbs}:/host"`,
@@ -304,6 +341,11 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
304
341
  execSync(unzipCmd, { stdio: 'pipe' });
305
342
  uncompressedFile = fileName.replace('.gz', '');
306
343
  console.log(chalk.gray(' - Arquivo descompactado: ' + uncompressedFile));
344
+ } else if (fileName.endsWith('.backup')) {
345
+ console.log(chalk.gray(' - Arquivo .backup detectado (já descompactado)'));
346
+ console.log(chalk.gray(' - Prosseguindo com restauração direta'));
347
+ } else {
348
+ throw new Error(`Formato de arquivo inválido. Esperado .backup.gz ou .backup, recebido: ${fileName}`);
307
349
  }
308
350
 
309
351
  // Extrair credenciais da URL de conexão
@@ -354,37 +396,251 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
354
396
  }
355
397
  }
356
398
 
357
- // Restaurar Edge Functions (placeholder - implementar via Management API)
399
+ // Restaurar Edge Functions via supabase functions deploy
358
400
  async function restoreEdgeFunctions(backupPath, targetProject) {
359
- console.log(chalk.blue('⚡ Restaurando Edge Functions...'));
360
- console.log(chalk.yellow(' ℹ️ Deploy de Edge Functions via Management API ainda não implementado'));
361
- // TODO: Implementar deploy via Supabase Management API
401
+ console.log(chalk.blue('\n⚡ Restaurando Edge Functions...'));
402
+
403
+ try {
404
+ const { execSync } = require('child_process');
405
+ const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
406
+
407
+ if (!fs.existsSync(edgeFunctionsDir)) {
408
+ console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
409
+ return;
410
+ }
411
+
412
+ const functions = fs.readdirSync(edgeFunctionsDir).filter(item =>
413
+ fs.statSync(path.join(edgeFunctionsDir, item)).isDirectory()
414
+ );
415
+
416
+ if (functions.length === 0) {
417
+ console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
418
+ return;
419
+ }
420
+
421
+ console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
422
+
423
+ // Link com projeto target
424
+ console.log(chalk.gray(` - Linkando com projeto ${targetProject.targetProjectId}...`));
425
+
426
+ try {
427
+ execSync(`supabase link --project-ref ${targetProject.targetProjectId}`, {
428
+ stdio: 'pipe',
429
+ encoding: 'utf8'
430
+ });
431
+ } catch (linkError) {
432
+ console.log(chalk.yellow(` ⚠️ Link pode já existir, continuando...`));
433
+ }
434
+
435
+ // Deploy de cada função
436
+ for (const funcName of functions) {
437
+ console.log(chalk.gray(` - Deployando ${funcName}...`));
438
+
439
+ try {
440
+ const functionPath = path.join(edgeFunctionsDir, funcName);
441
+
442
+ execSync(`supabase functions deploy ${funcName}`, {
443
+ cwd: functionPath,
444
+ stdio: 'pipe',
445
+ encoding: 'utf8'
446
+ });
447
+
448
+ console.log(chalk.green(` ✅ ${funcName} deployada com sucesso!`));
449
+ } catch (deployError) {
450
+ console.log(chalk.yellow(` ⚠️ ${funcName} - deploy falhou: ${deployError.message}`));
451
+ }
452
+ }
453
+
454
+ } catch (error) {
455
+ console.error(chalk.red(` ❌ Erro ao restaurar Edge Functions: ${error.message}`));
456
+ }
362
457
  }
363
458
 
364
- // Restaurar Storage Buckets (placeholder)
459
+ // Restaurar Storage Buckets (interativo - exibir informações)
365
460
  async function restoreStorageBuckets(backupPath, targetProject) {
366
- console.log(chalk.blue('📦 Restaurando Storage Buckets...'));
367
- console.log(chalk.yellow(' ℹ️ Restauração de Storage Buckets ainda não implementado'));
368
- // TODO: Implementar restauração via Management API
461
+ console.log(chalk.blue('\n📦 Restaurando Storage Buckets...'));
462
+
463
+ try {
464
+ const storageDir = path.join(backupPath, 'storage');
465
+
466
+ if (!fs.existsSync(storageDir)) {
467
+ console.log(chalk.yellow(' ⚠️ Nenhum bucket de Storage encontrado no backup'));
468
+ return;
469
+ }
470
+
471
+ const manifestPath = path.join(backupPath, 'backup-manifest.json');
472
+ let manifest = null;
473
+
474
+ if (fs.existsSync(manifestPath)) {
475
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
476
+ }
477
+
478
+ const buckets = manifest?.components?.storage?.buckets || [];
479
+
480
+ if (buckets.length === 0) {
481
+ console.log(chalk.gray(' ℹ️ Nenhum bucket para restaurar'));
482
+ return;
483
+ }
484
+
485
+ console.log(chalk.green(`\n ✅ ${buckets.length} bucket(s) encontrado(s) no backup`));
486
+ buckets.forEach(bucket => {
487
+ console.log(chalk.gray(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
488
+ });
489
+
490
+ const colabUrl = 'https://colab.research.google.com/github/PLyn/supabase-storage-migrate/blob/main/Supabase_Storage_migration.ipynb';
491
+
492
+ console.log(chalk.yellow('\n ⚠️ Migração de objetos de Storage requer processo manual'));
493
+ console.log(chalk.cyan(` ℹ️ Use o script do Google Colab: ${colabUrl}`));
494
+ console.log(chalk.gray('\n 📋 Instruções:'));
495
+ console.log(chalk.gray(' 1. Execute o script no Google Colab'));
496
+ console.log(chalk.gray(' 2. Configure as credenciais dos projetos (origem e destino)'));
497
+ console.log(chalk.gray(' 3. Execute a migração'));
498
+
499
+ await inquirer.prompt([{
500
+ type: 'input',
501
+ name: 'continue',
502
+ message: 'Pressione Enter para continuar'
503
+ }]);
504
+
505
+ } catch (error) {
506
+ console.error(chalk.red(` ❌ Erro ao processar Storage: ${error.message}`));
507
+ }
369
508
  }
370
509
 
371
- // Restaurar Auth Settings (placeholder)
510
+ // Restaurar Auth Settings (interativo - exibir URL e valores)
372
511
  async function restoreAuthSettings(backupPath, targetProject) {
373
- console.log(chalk.blue('🔐 Restaurando Auth Settings...'));
374
- console.log(chalk.yellow(' ℹ️ Restauração de Auth Settings ainda não implementado'));
375
- // TODO: Implementar via Management API
512
+ console.log(chalk.blue('\n🔐 Restaurando Auth Settings...'));
513
+
514
+ try {
515
+ const authSettingsPath = path.join(backupPath, 'auth-settings.json');
516
+
517
+ if (!fs.existsSync(authSettingsPath)) {
518
+ console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Auth encontrada no backup'));
519
+ return;
520
+ }
521
+
522
+ const authSettings = JSON.parse(fs.readFileSync(authSettingsPath, 'utf8'));
523
+ const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/auth/url-config`;
524
+
525
+ console.log(chalk.green('\n ✅ URL para configuração manual:'));
526
+ console.log(chalk.cyan(` ${dashboardUrl}`));
527
+ console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
528
+
529
+ if (authSettings.auth_url_config) {
530
+ Object.entries(authSettings.auth_url_config).forEach(([key, value]) => {
531
+ console.log(chalk.gray(` - ${key}: ${value}`));
532
+ });
533
+ }
534
+
535
+ console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
536
+
537
+ await inquirer.prompt([{
538
+ type: 'input',
539
+ name: 'continue',
540
+ message: 'Pressione Enter para continuar'
541
+ }]);
542
+
543
+ console.log(chalk.green(' ✅ Auth Settings processados'));
544
+
545
+ } catch (error) {
546
+ console.error(chalk.red(` ❌ Erro ao processar Auth Settings: ${error.message}`));
547
+ }
376
548
  }
377
549
 
378
- // Restaurar Database Settings (placeholder)
550
+ // Restaurar Database Settings (via SQL)
379
551
  async function restoreDatabaseSettings(backupPath, targetProject) {
380
- console.log(chalk.blue('🔧 Restaurando Database Settings...'));
381
- console.log(chalk.yellow(' ℹ️ Restauração de Database Settings ainda não implementado'));
382
- // TODO: Aplicar extensões e configurações via SQL
552
+ console.log(chalk.blue('\n🔧 Restaurando Database Settings...'));
553
+
554
+ try {
555
+ const files = fs.readdirSync(backupPath);
556
+ const dbSettingsFile = files.find(f => f.startsWith('database-settings-') && f.endsWith('.json'));
557
+
558
+ if (!dbSettingsFile) {
559
+ console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Database encontrada no backup'));
560
+ return;
561
+ }
562
+
563
+ const dbSettings = JSON.parse(fs.readFileSync(path.join(backupPath, dbSettingsFile), 'utf8'));
564
+ const { execSync } = require('child_process');
565
+
566
+ if (dbSettings.extensions && dbSettings.extensions.length > 0) {
567
+ console.log(chalk.gray(` - Habilitando ${dbSettings.extensions.length} extension(s)...`));
568
+
569
+ for (const ext of dbSettings.extensions) {
570
+ console.log(chalk.gray(` - ${ext}`));
571
+
572
+ const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${ext};`;
573
+
574
+ const urlMatch = targetProject.targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
575
+
576
+ if (!urlMatch) {
577
+ console.log(chalk.yellow(` ⚠️ URL inválida para ${ext}`));
578
+ continue;
579
+ }
580
+
581
+ const dockerCmd = [
582
+ 'docker run --rm',
583
+ '--network host',
584
+ `-e PGPASSWORD="${encodeURIComponent(urlMatch[2])}"`,
585
+ 'postgres:17 psql',
586
+ `-d "${targetProject.targetDatabaseUrl}"`,
587
+ `-c "${sqlCommand}"`
588
+ ].join(' ');
589
+
590
+ try {
591
+ execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
592
+ } catch (sqlError) {
593
+ console.log(chalk.yellow(` ⚠️ ${ext} - extension já existe ou não pode ser habilitada`));
594
+ }
595
+ }
596
+ }
597
+
598
+ console.log(chalk.green(' ✅ Database Settings restaurados com sucesso!'));
599
+
600
+ } catch (error) {
601
+ console.error(chalk.red(` ❌ Erro ao restaurar Database Settings: ${error.message}`));
602
+ }
383
603
  }
384
604
 
385
- // Restaurar Realtime Settings (placeholder)
605
+ // Restaurar Realtime Settings (interativo - exibir URL e valores)
386
606
  async function restoreRealtimeSettings(backupPath, targetProject) {
387
- console.log(chalk.blue('🔄 Restaurando Realtime Settings...'));
388
- console.log(chalk.yellow(' ℹ️ Realtime Settings requerem configuração manual no Dashboard'));
389
- // TODO: Adicionar instruções de configuração manual
607
+ console.log(chalk.blue('\n🔄 Restaurando Realtime Settings...'));
608
+
609
+ try {
610
+ const realtimeSettingsPath = path.join(backupPath, 'realtime-settings.json');
611
+
612
+ if (!fs.existsSync(realtimeSettingsPath)) {
613
+ console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Realtime encontrada no backup'));
614
+ return;
615
+ }
616
+
617
+ const realtimeSettings = JSON.parse(fs.readFileSync(realtimeSettingsPath, 'utf8'));
618
+ const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/realtime/settings`;
619
+
620
+ console.log(chalk.green('\n ✅ URL para configuração manual:'));
621
+ console.log(chalk.cyan(` ${dashboardUrl}`));
622
+ console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
623
+
624
+ if (realtimeSettings.realtime_settings?.settings) {
625
+ Object.entries(realtimeSettings.realtime_settings.settings).forEach(([key, setting]) => {
626
+ console.log(chalk.gray(` - ${setting.label}: ${setting.value}`));
627
+ if (setting.description) {
628
+ console.log(chalk.gray(` ${setting.description}`));
629
+ }
630
+ });
631
+ }
632
+
633
+ console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
634
+
635
+ await inquirer.prompt([{
636
+ type: 'input',
637
+ name: 'continue',
638
+ message: 'Pressione Enter para continuar'
639
+ }]);
640
+
641
+ console.log(chalk.green(' ✅ Realtime Settings processados'));
642
+
643
+ } catch (error) {
644
+ console.error(chalk.red(` ❌ Erro ao processar Realtime Settings: ${error.message}`));
645
+ }
390
646
  }