smoonb 0.0.27 → 0.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -108,32 +108,37 @@ async function performFullBackup(config, options) {
108
108
  };
109
109
 
110
110
  // 1. Backup Database via pg_dumpall Docker (idêntico ao Dashboard)
111
- console.log(chalk.blue('\n📊 1/6 - Backup da Database PostgreSQL via pg_dumpall Docker...'));
111
+ console.log(chalk.blue('\n📊 1/7 - Backup da Database PostgreSQL via pg_dumpall Docker...'));
112
112
  const databaseResult = await backupDatabase(config.supabase.projectId, backupDir);
113
113
  manifest.components.database = databaseResult;
114
114
 
115
115
  // 2. Backup Edge Functions via Docker
116
- console.log(chalk.blue('\n⚡ 2/6 - Backup das Edge Functions via Docker...'));
116
+ console.log(chalk.blue('\n⚡ 2/7 - Backup das Edge Functions via Docker...'));
117
117
  const functionsResult = await backupEdgeFunctionsWithDocker(config.supabase.projectId, config.supabase.accessToken, backupDir);
118
118
  manifest.components.edge_functions = functionsResult;
119
119
 
120
120
  // 3. Backup Auth Settings via API
121
- console.log(chalk.blue('\n🔐 3/6 - Backup das Auth Settings via API...'));
121
+ console.log(chalk.blue('\n🔐 3/7 - Backup das Auth Settings via API...'));
122
122
  const authResult = await backupAuthSettings(config.supabase.projectId, config.supabase.accessToken, backupDir);
123
123
  manifest.components.auth_settings = authResult;
124
124
 
125
125
  // 4. Backup Storage via API
126
- console.log(chalk.blue('\n📦 4/6 - Backup do Storage via API...'));
126
+ console.log(chalk.blue('\n📦 4/7 - Backup do Storage via API...'));
127
127
  const storageResult = await backupStorage(config.supabase.projectId, config.supabase.accessToken, backupDir);
128
128
  manifest.components.storage = storageResult;
129
129
 
130
130
  // 5. Backup Custom Roles via SQL
131
- console.log(chalk.blue('\n👥 5/6 - Backup dos Custom Roles via SQL...'));
131
+ console.log(chalk.blue('\n👥 5/7 - Backup dos Custom Roles via SQL...'));
132
132
  const rolesResult = await backupCustomRoles(config.supabase.databaseUrl, backupDir);
133
133
  manifest.components.custom_roles = rolesResult;
134
134
 
135
- // 6. Backup Realtime Settings via Captura Interativa
136
- console.log(chalk.blue('\n🔄 6/6 - Backup das Realtime Settings via Captura Interativa...'));
135
+ // 6. Backup das Database Extensions and Settings via SQL
136
+ console.log(chalk.blue('\n🔧 6/7 - Backup das Database Extensions and Settings via SQL...'));
137
+ const databaseSettingsResult = await backupDatabaseSettings(config.supabase.projectId, backupDir);
138
+ manifest.components.database_settings = databaseSettingsResult;
139
+
140
+ // 7. Backup Realtime Settings via Captura Interativa
141
+ console.log(chalk.blue('\n🔄 7/7 - Backup das Realtime Settings via Captura Interativa...'));
137
142
  const realtimeResult = await backupRealtimeSettings(config.supabase.projectId, backupDir, options.skipRealtime);
138
143
  manifest.components.realtime = realtimeResult;
139
144
 
@@ -143,6 +148,7 @@ async function performFullBackup(config, options) {
143
148
  console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO VIA DOCKER!'));
144
149
  console.log(chalk.blue(`📁 Localização: ${backupDir}`));
145
150
  console.log(chalk.green(`📊 Database: ${databaseResult.fileName} (${databaseResult.size} KB) - Idêntico ao Dashboard`));
151
+ console.log(chalk.green(`🔧 Database Settings: ${databaseSettingsResult.fileName} (${databaseSettingsResult.size} KB) - Extensions e Configurações`));
146
152
  console.log(chalk.green(`⚡ Edge Functions: ${functionsResult.success_count || 0}/${functionsResult.functions_count || 0} functions baixadas via Docker`));
147
153
  console.log(chalk.green(`🔐 Auth Settings: ${authResult.success ? 'Exportadas via API' : 'Falharam'}`));
148
154
  console.log(chalk.green(`📦 Storage: ${storageResult.buckets?.length || 0} buckets verificados via API`));
@@ -533,6 +539,187 @@ async function backupCustomRoles(databaseUrl, backupDir) {
533
539
  }
534
540
  }
535
541
 
542
+ // Backup das Database Extensions and Settings via SQL
543
+ async function backupDatabaseSettings(projectId, backupDir) {
544
+ try {
545
+ console.log(chalk.gray(' - Capturando Database Extensions and Settings...'));
546
+
547
+ const { execSync } = require('child_process');
548
+ const config = await readConfig();
549
+
550
+ // Extrair credenciais da databaseUrl
551
+ const dbUrl = config.supabase.databaseUrl;
552
+ const urlMatch = dbUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
553
+
554
+ if (!urlMatch) {
555
+ throw new Error('Database URL inválida');
556
+ }
557
+
558
+ const [, username, password, host, port, database] = urlMatch;
559
+
560
+ // Gerar nome do arquivo
561
+ const now = new Date();
562
+ const day = String(now.getDate()).padStart(2, '0');
563
+ const month = String(now.getMonth() + 1).padStart(2, '0');
564
+ const year = now.getFullYear();
565
+ const hours = String(now.getHours()).padStart(2, '0');
566
+ const minutes = String(now.getMinutes()).padStart(2, '0');
567
+ const seconds = String(now.getSeconds()).padStart(2, '0');
568
+
569
+ const fileName = `database-settings-${day}-${month}-${year}@${hours}-${minutes}-${seconds}.json`;
570
+
571
+ // Usar caminho absoluto igual às outras funções
572
+ const backupDirAbs = path.resolve(backupDir);
573
+
574
+ // Script SQL para capturar todas as configurações
575
+ const sqlScript = `
576
+ -- Database Extensions and Settings Backup
577
+ -- Generated at: ${new Date().toISOString()}
578
+
579
+ -- 1. Capturar extensões instaladas
580
+ SELECT json_agg(
581
+ json_build_object(
582
+ 'name', extname,
583
+ 'version', extversion,
584
+ 'schema', extnamespace::regnamespace
585
+ )
586
+ ) as extensions
587
+ FROM pg_extension
588
+ ORDER BY extname;
589
+
590
+ -- 2. Capturar configurações PostgreSQL importantes
591
+ SELECT json_agg(
592
+ json_build_object(
593
+ 'name', name,
594
+ 'setting', setting,
595
+ 'unit', unit,
596
+ 'context', context,
597
+ 'description', short_desc
598
+ )
599
+ ) as postgres_settings
600
+ FROM pg_settings
601
+ WHERE name IN (
602
+ 'statement_timeout',
603
+ 'idle_in_transaction_session_timeout',
604
+ 'lock_timeout',
605
+ 'shared_buffers',
606
+ 'work_mem',
607
+ 'maintenance_work_mem',
608
+ 'effective_cache_size',
609
+ 'max_connections',
610
+ 'log_statement',
611
+ 'log_min_duration_statement',
612
+ 'timezone',
613
+ 'log_timezone',
614
+ 'default_transaction_isolation',
615
+ 'default_transaction_read_only',
616
+ 'checkpoint_completion_target',
617
+ 'wal_buffers',
618
+ 'max_wal_size',
619
+ 'min_wal_size'
620
+ )
621
+ ORDER BY name;
622
+
623
+ -- 3. Capturar configurações específicas dos roles Supabase
624
+ SELECT json_agg(
625
+ json_build_object(
626
+ 'role', rolname,
627
+ 'config', rolconfig
628
+ )
629
+ ) as role_configurations
630
+ FROM pg_roles
631
+ WHERE rolname IN ('anon', 'authenticated', 'authenticator', 'postgres', 'service_role')
632
+ AND rolconfig IS NOT NULL
633
+ ORDER BY rolname;
634
+
635
+ -- 4. Capturar configurações de PGAudit (se existir)
636
+ SELECT json_agg(
637
+ json_build_object(
638
+ 'role', rolname,
639
+ 'config', rolconfig
640
+ )
641
+ ) as pgaudit_configurations
642
+ FROM pg_roles
643
+ WHERE rolconfig IS NOT NULL
644
+ AND EXISTS (
645
+ SELECT 1 FROM unnest(rolconfig) AS config
646
+ WHERE config LIKE '%pgaudit%'
647
+ )
648
+ ORDER BY rolname;
649
+ `;
650
+
651
+ // Salvar script SQL temporário
652
+ const sqlFile = path.join(backupDir, 'temp_settings.sql');
653
+ await fs.writeFile(sqlFile, sqlScript);
654
+
655
+ // Executar via Docker
656
+ const dockerCmd = [
657
+ 'docker run --rm --network host',
658
+ `-v "${backupDirAbs}:/host"`,
659
+ `-e PGPASSWORD="${password}"`,
660
+ 'postgres:17 psql',
661
+ `-h ${host}`,
662
+ `-p ${port}`,
663
+ `-U ${username}`,
664
+ `-d ${database}`,
665
+ '-f /host/temp_settings.sql',
666
+ '-t', // Tuples only
667
+ '-A', // Unaligned output
668
+ '-F', // Field separator
669
+ '|'
670
+ ].join(' ');
671
+
672
+ console.log(chalk.gray(' - Executando queries de configurações via Docker...'));
673
+ const output = execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
674
+
675
+ // Processar output e criar JSON estruturado
676
+ const lines = output.trim().split('\n').filter(line => line.trim());
677
+
678
+ const result = {
679
+ database_settings: {
680
+ note: "Configurações específicas do database Supabase capturadas via SQL",
681
+ captured_at: new Date().toISOString(),
682
+ project_id: projectId,
683
+ extensions: lines[0] ? JSON.parse(lines[0]) : [],
684
+ postgres_settings: lines[1] ? JSON.parse(lines[1]) : [],
685
+ role_configurations: lines[2] ? JSON.parse(lines[2]) : [],
686
+ pgaudit_configurations: lines[3] ? JSON.parse(lines[3]) : [],
687
+ restore_instructions: {
688
+ note: "Estas configurações precisam ser aplicadas manualmente após a restauração do database",
689
+ steps: [
690
+ "1. Restaurar o database usando o arquivo .backup.gz",
691
+ "2. Aplicar configurações de Postgres via SQL:",
692
+ " ALTER DATABASE postgres SET setting_name TO 'value';",
693
+ "3. Aplicar configurações de roles via SQL:",
694
+ " ALTER ROLE role_name SET setting_name TO 'value';",
695
+ "4. Habilitar extensões necessárias via Dashboard ou SQL:",
696
+ " CREATE EXTENSION IF NOT EXISTS extension_name;",
697
+ "5. Verificar configurações aplicadas:",
698
+ " SELECT name, setting FROM pg_settings WHERE name IN (...);"
699
+ ]
700
+ }
701
+ }
702
+ };
703
+
704
+ // Salvar arquivo JSON
705
+ const jsonFile = path.join(backupDir, fileName);
706
+ await fs.writeFile(jsonFile, JSON.stringify(result, null, 2));
707
+
708
+ // Limpar arquivo temporário
709
+ await fs.promises.unlink(sqlFile);
710
+
711
+ const stats = fs.statSync(jsonFile);
712
+ const sizeKB = (stats.size / 1024).toFixed(1);
713
+
714
+ console.log(chalk.green(` ✅ Database Settings: ${fileName} (${sizeKB} KB)`));
715
+
716
+ return { success: true, size: sizeKB, fileName: fileName };
717
+ } catch (error) {
718
+ console.log(chalk.yellow(` ⚠️ Erro no backup das Database Settings: ${error.message}`));
719
+ return { success: false };
720
+ }
721
+ }
722
+
536
723
  // Backup das Realtime Settings via Captura Interativa
537
724
  async function backupRealtimeSettings(projectId, backupDir, skipInteractive = false) {
538
725
  try {