smoonb 0.0.25 → 0.0.27

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.25",
3
+ "version": "0.0.27",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -101,20 +101,16 @@ async function performFullBackup(config, options) {
101
101
  created_at: new Date().toISOString(),
102
102
  project_id: config.supabase.projectId,
103
103
  smoonb_version: require('../../package.json').version,
104
- backup_type: 'complete_docker',
104
+ backup_type: 'pg_dumpall_docker_dashboard_compatible',
105
105
  docker_version: await getDockerVersion(),
106
+ dashboard_compatible: true,
106
107
  components: {}
107
108
  };
108
109
 
109
- // 1. Backup Database via Docker
110
- console.log(chalk.blue('\n📊 1/6 - Backup da Database PostgreSQL via Docker...'));
111
- const dbResult = await backupDatabaseWithDocker(config.supabase.databaseUrl, backupDir);
112
- manifest.components.database = {
113
- success: dbResult.success,
114
- method: 'docker',
115
- files: dbResult.files?.length || 0,
116
- total_size_kb: dbResult.totalSizeKB || '0.0'
117
- };
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...'));
112
+ const databaseResult = await backupDatabase(config.supabase.projectId, backupDir);
113
+ manifest.components.database = databaseResult;
118
114
 
119
115
  // 2. Backup Edge Functions via Docker
120
116
  console.log(chalk.blue('\n⚡ 2/6 - Backup das Edge Functions via Docker...'));
@@ -146,12 +142,21 @@ async function performFullBackup(config, options) {
146
142
 
147
143
  console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO VIA DOCKER!'));
148
144
  console.log(chalk.blue(`📁 Localização: ${backupDir}`));
149
- console.log(chalk.green(`🐳 Database: ${dbResult.files?.length || 0} arquivos SQL gerados via Docker`));
145
+ console.log(chalk.green(`📊 Database: ${databaseResult.fileName} (${databaseResult.size} KB) - Idêntico ao Dashboard`));
150
146
  console.log(chalk.green(`⚡ Edge Functions: ${functionsResult.success_count || 0}/${functionsResult.functions_count || 0} functions baixadas via Docker`));
151
147
  console.log(chalk.green(`🔐 Auth Settings: ${authResult.success ? 'Exportadas via API' : 'Falharam'}`));
152
148
  console.log(chalk.green(`📦 Storage: ${storageResult.buckets?.length || 0} buckets verificados via API`));
153
149
  console.log(chalk.green(`👥 Custom Roles: ${rolesResult.roles?.length || 0} roles exportados via SQL`));
154
- console.log(chalk.green(`🔄 Realtime: ${realtimeResult.success ? 'Configurações capturadas interativamente' : 'Falharam'}`));
150
+ // Determinar mensagem correta baseada no método usado
151
+ let realtimeMessage = 'Falharam';
152
+ if (realtimeResult.success) {
153
+ if (options.skipRealtime) {
154
+ realtimeMessage = 'Configurações copiadas do backup anterior';
155
+ } else {
156
+ realtimeMessage = 'Configurações capturadas interativamente';
157
+ }
158
+ }
159
+ console.log(chalk.green(`🔄 Realtime: ${realtimeMessage}`));
155
160
 
156
161
  return { success: true, backupDir, manifest };
157
162
  }
@@ -217,96 +222,72 @@ function showDockerMessagesAndExit(reason) {
217
222
  process.exit(1);
218
223
  }
219
224
 
220
- // Backup da database usando Docker
221
- async function backupDatabaseWithDocker(databaseUrl, backupDir) {
225
+ // Backup da database usando pg_dumpall via Docker (idêntico ao Supabase Dashboard)
226
+ async function backupDatabase(projectId, backupDir) {
222
227
  try {
223
- console.log(chalk.gray('🐳 Iniciando backup de database via Docker...'));
228
+ console.log(chalk.gray(' - Criando backup completo via pg_dumpall...'));
224
229
 
225
- const files = [];
226
- let success = true;
227
- let totalSizeKB = 0;
228
-
229
- // 1. Backup do Schema
230
- console.log(chalk.gray(' - Exportando schema...'));
231
- const schemaFile = path.join(backupDir, 'schema.sql');
230
+ const { execSync } = require('child_process');
231
+ const config = await readConfig();
232
232
 
233
- try {
234
- await execAsync(`supabase db dump --db-url "${databaseUrl}" -f "${schemaFile}"`);
235
-
236
- const schemaValidation = await validateSqlFile(schemaFile);
237
- if (schemaValidation.valid) {
238
- files.push({
239
- filename: 'schema.sql',
240
- size: schemaValidation.size,
241
- sizeKB: schemaValidation.sizeKB
242
- });
243
- totalSizeKB += parseFloat(schemaValidation.sizeKB);
244
- console.log(chalk.green(` ✅ Schema exportado: ${schemaValidation.sizeKB} KB`));
245
- } else {
246
- console.log(chalk.red(` ❌ Arquivo schema.sql inválido: ${schemaValidation.error}`));
247
- success = false;
248
- }
249
- } catch (error) {
250
- console.log(chalk.red(` ❌ Erro ao exportar schema: ${error.message}`));
251
- success = false;
252
- }
253
-
254
- // 2. Backup dos Dados
255
- console.log(chalk.gray(' - Exportando dados...'));
256
- const dataFile = path.join(backupDir, 'data.sql');
233
+ // Extrair credenciais da databaseUrl
234
+ const dbUrl = config.supabase.databaseUrl;
235
+ const urlMatch = dbUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
257
236
 
258
- try {
259
- await execAsync(`supabase db dump --db-url "${databaseUrl}" --data-only -f "${dataFile}"`);
260
-
261
- const dataValidation = await validateSqlFile(dataFile);
262
- if (dataValidation.valid) {
263
- files.push({
264
- filename: 'data.sql',
265
- size: dataValidation.size,
266
- sizeKB: dataValidation.sizeKB
267
- });
268
- totalSizeKB += parseFloat(dataValidation.sizeKB);
269
- console.log(chalk.green(` ✅ Dados exportados: ${dataValidation.sizeKB} KB`));
270
- } else {
271
- console.log(chalk.red(` ❌ Arquivo data.sql inválido: ${dataValidation.error}`));
272
- success = false;
273
- }
274
- } catch (error) {
275
- console.log(chalk.red(` ❌ Erro ao exportar dados: ${error.message}`));
276
- success = false;
237
+ if (!urlMatch) {
238
+ throw new Error('Database URL inválida');
277
239
  }
278
-
279
- // 3. Backup dos Roles
280
- console.log(chalk.gray(' - Exportando roles...'));
281
- const rolesFile = path.join(backupDir, 'roles.sql');
282
240
 
283
- try {
284
- await execAsync(`supabase db dump --db-url "${databaseUrl}" --role-only -f "${rolesFile}"`);
285
-
286
- const rolesValidation = await validateSqlFile(rolesFile);
287
- if (rolesValidation.valid) {
288
- files.push({
289
- filename: 'roles.sql',
290
- size: rolesValidation.size,
291
- sizeKB: rolesValidation.sizeKB
292
- });
293
- totalSizeKB += parseFloat(rolesValidation.sizeKB);
294
- console.log(chalk.green(` ✅ Roles exportados: ${rolesValidation.sizeKB} KB`));
295
- } else {
296
- console.log(chalk.red(` ❌ Arquivo roles.sql inválido: ${rolesValidation.error}`));
297
- success = false;
298
- }
299
- } catch (error) {
300
- console.log(chalk.red(` ❌ Erro ao exportar roles: ${error.message}`));
301
- success = false;
302
- }
303
-
304
- console.log(chalk.green(`✅ Backup de database concluído via Docker`));
305
- return { success, files, totalSizeKB: totalSizeKB.toFixed(1) };
306
-
241
+ const [, username, password, host, port, database] = urlMatch;
242
+
243
+ // Gerar nome do arquivo igual ao dashboard
244
+ const now = new Date();
245
+ const day = String(now.getDate()).padStart(2, '0');
246
+ const month = String(now.getMonth() + 1).padStart(2, '0');
247
+ const year = now.getFullYear();
248
+ const hours = String(now.getHours()).padStart(2, '0');
249
+ const minutes = String(now.getMinutes()).padStart(2, '0');
250
+ const seconds = String(now.getSeconds()).padStart(2, '0');
251
+
252
+ const fileName = `db_cluster-${day}-${month}-${year}@${hours}-${minutes}-${seconds}.backup`;
253
+
254
+ // CORREÇÃO: Usar caminho absoluto igual às Edge Functions
255
+ const backupDirAbs = path.resolve(backupDir);
256
+
257
+ // Comando pg_dumpall via Docker (mesma abordagem das Edge Functions)
258
+ const dockerCmd = [
259
+ 'docker run --rm --network host',
260
+ `-v "${backupDirAbs}:/host"`,
261
+ `-e PGPASSWORD="${password}"`,
262
+ 'postgres:17 pg_dumpall',
263
+ `-h ${host}`,
264
+ `-p ${port}`,
265
+ `-U ${username}`,
266
+ `-f /host/${fileName}`
267
+ ].join(' ');
268
+
269
+ console.log(chalk.gray(` - Executando pg_dumpall via Docker...`));
270
+ execSync(dockerCmd, { stdio: 'pipe' });
271
+
272
+ // Compactar igual ao Supabase Dashboard
273
+ const gzipCmd = [
274
+ 'docker run --rm',
275
+ `-v "${backupDirAbs}:/host"`,
276
+ `postgres:17 gzip /host/${fileName}`
277
+ ].join(' ');
278
+
279
+ execSync(gzipCmd, { stdio: 'pipe' });
280
+
281
+ const finalFileName = `${fileName}.gz`;
282
+ const stats = fs.statSync(path.join(backupDir, finalFileName));
283
+ const sizeKB = (stats.size / 1024).toFixed(1);
284
+
285
+ console.log(chalk.green(` ✅ Database backup: ${finalFileName} (${sizeKB} KB)`));
286
+
287
+ return { success: true, size: sizeKB, fileName: finalFileName };
307
288
  } catch (error) {
308
- console.log(chalk.red(`❌ Erro no backup de database: ${error.message}`));
309
- return { success: false, error: error.message };
289
+ console.log(chalk.yellow(` ⚠️ Erro no backup do database: ${error.message}`));
290
+ return { success: false };
310
291
  }
311
292
  }
312
293
 
@@ -571,33 +552,3 @@ async function backupRealtimeSettings(projectId, backupDir, skipInteractive = fa
571
552
  }
572
553
  }
573
554
 
574
- // Validar arquivo SQL
575
- async function validateSqlFile(filePath) {
576
- try {
577
- if (!fs.existsSync(filePath)) {
578
- return { valid: false, error: 'Arquivo não existe', size: 0, sizeKB: '0.0' };
579
- }
580
-
581
- const stats = fs.statSync(filePath);
582
- const sizeKB = (stats.size / 1024).toFixed(1);
583
-
584
- if (stats.size === 0) {
585
- return { valid: false, error: 'Arquivo vazio', size: 0, sizeKB: '0.0' };
586
- }
587
-
588
- const content = fs.readFileSync(filePath, 'utf8');
589
-
590
- const sqlKeywords = ['CREATE', 'INSERT', 'COPY', 'ALTER', 'DROP', 'GRANT', 'REVOKE'];
591
- const hasValidContent = sqlKeywords.some(keyword =>
592
- content.toUpperCase().includes(keyword)
593
- );
594
-
595
- if (!hasValidContent) {
596
- return { valid: false, error: 'Sem conteúdo SQL válido', size: stats.size, sizeKB };
597
- }
598
-
599
- return { valid: true, error: null, size: stats.size, sizeKB };
600
- } catch (error) {
601
- return { valid: false, error: error.message, size: 0, sizeKB: '0.0' };
602
- }
603
- }
@@ -17,7 +17,7 @@ async function captureRealtimeSettings(projectId, backupDir, skipInteractive = f
17
17
  const previousSettings = await getPreviousRealtimeSettings(backupDir);
18
18
 
19
19
  if (skipInteractive && previousSettings) {
20
- console.log('📋 Usando configurações de Realtime Settings do backup anterior...');
20
+ console.log('📋 Copiando Realtime Settings do backup anterior...');
21
21
  await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
22
22
  return previousSettings;
23
23
  }