smoonb 0.0.24 → 0.0.26

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.24",
3
+ "version": "0.0.26",
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,19 +101,21 @@ 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...'));
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
112
  const dbResult = await backupDatabaseWithDocker(config.supabase.databaseUrl, backupDir);
112
113
  manifest.components.database = {
113
114
  success: dbResult.success,
114
- method: 'docker',
115
- files: dbResult.files?.length || 0,
116
- total_size_kb: dbResult.totalSizeKB || '0.0'
115
+ method: 'pg_dumpall_docker',
116
+ fileName: dbResult.fileName,
117
+ size_kb: dbResult.size,
118
+ dashboard_compatible: true
117
119
  };
118
120
 
119
121
  // 2. Backup Edge Functions via Docker
@@ -146,12 +148,12 @@ async function performFullBackup(config, options) {
146
148
 
147
149
  console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO VIA DOCKER!'));
148
150
  console.log(chalk.blue(`📁 Localização: ${backupDir}`));
149
- console.log(chalk.green(`🐳 Database: ${dbResult.files?.length || 0} arquivos SQL gerados via Docker`));
151
+ console.log(chalk.green(`📊 Database: ${dbResult.fileName} (${dbResult.size} KB) - Idêntico ao Dashboard`));
150
152
  console.log(chalk.green(`⚡ Edge Functions: ${functionsResult.success_count || 0}/${functionsResult.functions_count || 0} functions baixadas via Docker`));
151
153
  console.log(chalk.green(`🔐 Auth Settings: ${authResult.success ? 'Exportadas via API' : 'Falharam'}`));
152
154
  console.log(chalk.green(`📦 Storage: ${storageResult.buckets?.length || 0} buckets verificados via API`));
153
155
  console.log(chalk.green(`👥 Custom Roles: ${rolesResult.roles?.length || 0} roles exportados via SQL`));
154
- console.log(chalk.green(`🔄 Realtime: ${realtimeResult.success ? 'Configurações exportadas via SQL' : 'Falharam'}`));
156
+ console.log(chalk.green(`🔄 Realtime: ${realtimeResult.success ? 'Configurações capturadas interativamente' : 'Falharam'}`));
155
157
 
156
158
  return { success: true, backupDir, manifest };
157
159
  }
@@ -217,95 +219,82 @@ function showDockerMessagesAndExit(reason) {
217
219
  process.exit(1);
218
220
  }
219
221
 
220
- // Backup da database usando Docker
222
+ // Backup da database usando pg_dumpall via Docker (idêntico ao Supabase Dashboard)
221
223
  async function backupDatabaseWithDocker(databaseUrl, backupDir) {
222
224
  try {
223
- console.log(chalk.gray('🐳 Iniciando backup de database via Docker...'));
225
+ console.log(chalk.gray('🐳 Criando backup completo via pg_dumpall Docker...'));
224
226
 
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');
227
+ // Extrair credenciais da databaseUrl
228
+ const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
232
229
 
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;
230
+ if (!urlMatch) {
231
+ throw new Error('Database URL inválida');
252
232
  }
253
-
254
- // 2. Backup dos Dados
255
- console.log(chalk.gray(' - Exportando dados...'));
256
- const dataFile = path.join(backupDir, 'data.sql');
257
233
 
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;
277
- }
278
-
279
- // 3. Backup dos Roles
280
- console.log(chalk.gray(' - Exportando roles...'));
281
- const rolesFile = path.join(backupDir, 'roles.sql');
234
+ const [, username, password, host, port, database] = urlMatch;
282
235
 
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;
236
+ // Gerar nome do arquivo igual ao dashboard Supabase
237
+ const now = new Date();
238
+ const day = String(now.getDate()).padStart(2, '0');
239
+ const month = String(now.getMonth() + 1).padStart(2, '0');
240
+ const year = now.getFullYear();
241
+ const hours = String(now.getHours()).padStart(2, '0');
242
+ const minutes = String(now.getMinutes()).padStart(2, '0');
243
+ const seconds = String(now.getSeconds()).padStart(2, '0');
244
+
245
+ const fileName = `db_cluster-${day}-${month}-${year}@${hours}-${minutes}-${seconds}.backup`;
246
+ const filePath = path.join(backupDir, fileName);
247
+
248
+ console.log(chalk.gray(` - Arquivo: ${fileName}`));
249
+
250
+ // Comando pg_dumpall via Docker (idêntico ao dashboard)
251
+ const dockerCmd = [
252
+ 'docker run --rm --network host',
253
+ `-v "${backupDir}:/host"`,
254
+ `-e PGPASSWORD="${password}"`,
255
+ 'postgres:17 pg_dumpall',
256
+ `-h ${host}`,
257
+ `-p ${port}`,
258
+ `-U ${username}`,
259
+ `-f /host/${fileName}`
260
+ ].join(' ');
261
+
262
+ console.log(chalk.gray(' - Executando pg_dumpall via Docker...'));
263
+ await execAsync(dockerCmd, { stdio: 'pipe' });
264
+
265
+ // Compactar igual ao Supabase Dashboard
266
+ console.log(chalk.gray(' - Compactando arquivo...'));
267
+ const gzipCmd = [
268
+ 'docker run --rm',
269
+ `-v "${backupDir}:/host"`,
270
+ 'postgres:17 gzip /host/' + fileName
271
+ ].join(' ');
272
+
273
+ await execAsync(gzipCmd, { stdio: 'pipe' });
274
+
275
+ const finalFileName = `${fileName}.gz`;
276
+ const finalFilePath = path.join(backupDir, finalFileName);
277
+
278
+ // Validar arquivo gerado
279
+ if (!fs.existsSync(finalFilePath)) {
280
+ throw new Error('Arquivo de backup não foi criado');
302
281
  }
303
-
304
- console.log(chalk.green(`✅ Backup de database concluído via Docker`));
305
- return { success, files, totalSizeKB: totalSizeKB.toFixed(1) };
282
+
283
+ const stats = fs.statSync(finalFilePath);
284
+ const sizeKB = (stats.size / 1024).toFixed(1);
285
+
286
+ console.log(chalk.green(` ✅ Database backup: ${finalFileName} (${sizeKB} KB)`));
287
+
288
+ return {
289
+ success: true,
290
+ fileName: finalFileName,
291
+ size: sizeKB,
292
+ method: 'pg_dumpall_docker',
293
+ dashboard_compatible: true
294
+ };
306
295
 
307
296
  } catch (error) {
308
- console.log(chalk.red(`❌ Erro no backup de database: ${error.message}`));
297
+ console.log(chalk.red(` ❌ Erro no backup do database: ${error.message}`));
309
298
  return { success: false, error: error.message };
310
299
  }
311
300
  }
@@ -571,33 +560,3 @@ async function backupRealtimeSettings(projectId, backupDir, skipInteractive = fa
571
560
  }
572
561
  }
573
562
 
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
- }
@@ -161,7 +161,7 @@ async function captureSettingsInteractively(projectId, previousSettings) {
161
161
  allow_public_access: {
162
162
  label: "Allow public access",
163
163
  description: "If disabled, only private channels will be allowed",
164
- value: allowPublicAccess === 'true'
164
+ value: allowPublicAccess === 'true' || allowPublicAccess === true
165
165
  },
166
166
  database_connection_pool_size: {
167
167
  label: "Database connection pool size",