smoonb 0.0.17 → 0.0.18

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,43 +1,43 @@
1
- {
2
- "name": "smoonb",
3
- "version": "0.0.17",
4
- "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
- "main": "index.js",
6
- "bin": {
7
- "smoonb": "bin/smoonb.js"
8
- },
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1",
11
- "start": "node bin/smoonb.js"
12
- },
13
- "keywords": [
14
- "supabase",
15
- "backup",
16
- "migration",
17
- "postgresql",
18
- "edge-functions",
19
- "database",
20
- "cli",
21
- "tool"
22
- ],
23
- "author": "Goalmoon Tecnologia LTDA <https://goalmoon.com>",
24
- "license": "SEE LICENSE IN LICENSE.md",
25
- "engines": {
26
- "node": ">=16.0.0"
27
- },
28
- "dependencies": {
29
- "@supabase/supabase-js": "^2.38.0",
30
- "chalk": "^4.1.2",
31
- "commander": "^11.1.0",
32
- "inquirer": "^8.2.7"
33
- },
34
- "type": "commonjs",
35
- "repository": {
36
- "type": "git",
37
- "url": "git+https://github.com/almmello/smoonb.git"
38
- },
39
- "bugs": {
40
- "url": "https://github.com/almmello/smoonb/issues"
41
- },
42
- "homepage": "https://github.com/almmello/smoonb#readme"
43
- }
1
+ {
2
+ "name": "smoonb",
3
+ "version": "0.0.18",
4
+ "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "smoonb": "bin/smoonb.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "start": "node bin/smoonb.js"
12
+ },
13
+ "keywords": [
14
+ "supabase",
15
+ "backup",
16
+ "migration",
17
+ "postgresql",
18
+ "edge-functions",
19
+ "database",
20
+ "cli",
21
+ "tool"
22
+ ],
23
+ "author": "Goalmoon Tecnologia LTDA <https://goalmoon.com>",
24
+ "license": "SEE LICENSE IN LICENSE.md",
25
+ "engines": {
26
+ "node": ">=16.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@supabase/supabase-js": "^2.38.0",
30
+ "chalk": "^4.1.2",
31
+ "commander": "^11.1.0",
32
+ "inquirer": "^8.2.7"
33
+ },
34
+ "type": "commonjs",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/almmello/smoonb.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/almmello/smoonb/issues"
41
+ },
42
+ "homepage": "https://github.com/almmello/smoonb#readme"
43
+ }
@@ -6,6 +6,7 @@ const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
6
6
  const { sha256 } = require('../utils/hash');
7
7
  const { readConfig, validateFor } = require('../utils/config');
8
8
  const { showBetaBanner } = require('../utils/banner');
9
+ const { detectDockerDependencies } = require('../utils/docker');
9
10
  const { createClient } = require('@supabase/supabase-js');
10
11
 
11
12
  // Exportar FUNÇÃO em vez de objeto Command
@@ -98,7 +99,14 @@ module.exports = async (options) => {
98
99
  console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO!'));
99
100
  console.log(chalk.blue(`📁 Localização: ${backupDir}`));
100
101
  console.log(chalk.green(`✅ Database: ${dbBackupResult.files.length} arquivos SQL gerados`));
101
- console.log(chalk.green(`✅ Edge Functions: ${edgeFunctionsResult.functions.length} functions baixadas`));
102
+ if (edgeFunctionsResult.success) {
103
+ console.log(chalk.green(`✅ Edge Functions: ${edgeFunctionsResult.successCount}/${edgeFunctionsResult.functionsCount} functions baixadas`));
104
+ } else {
105
+ console.log(chalk.yellow(`⚠️ Edge Functions: ${edgeFunctionsResult.reason === 'docker_not_installed' ? 'Docker não instalado' :
106
+ edgeFunctionsResult.reason === 'docker_not_running' ? 'Docker não está rodando' :
107
+ edgeFunctionsResult.reason === 'supabase_cli_not_found' ? 'Supabase CLI não encontrado' :
108
+ 'Erro no backup'}`));
109
+ }
102
110
  console.log(chalk.green(`✅ Auth Settings: ${authSettingsResult.success ? 'Exportadas' : 'Falharam'}`));
103
111
  console.log(chalk.green(`✅ Storage: ${storageResult.buckets.length} buckets verificados`));
104
112
  console.log(chalk.green(`✅ Custom Roles: ${customRolesResult.roles.length} roles exportados`));
@@ -109,8 +117,10 @@ module.exports = async (options) => {
109
117
  for (const file of dbBackupResult.files) {
110
118
  console.log(chalk.gray(` - ${file.filename}: ${file.sizeKB} KB`));
111
119
  }
112
- if (edgeFunctionsResult.functions.length > 0) {
113
- console.log(chalk.gray(` - Edge Functions: ${edgeFunctionsResult.functions.length} functions`));
120
+ if (edgeFunctionsResult.success && edgeFunctionsResult.functions.length > 0) {
121
+ console.log(chalk.gray(` - Edge Functions: ${edgeFunctionsResult.successCount}/${edgeFunctionsResult.functionsCount} functions`));
122
+ } else if (!edgeFunctionsResult.success) {
123
+ console.log(chalk.gray(` - Edge Functions: Pulado (${edgeFunctionsResult.reason})`));
114
124
  }
115
125
 
116
126
  } catch (error) {
@@ -253,9 +263,63 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
253
263
  }
254
264
  }
255
265
 
256
- // Backup das Edge Functions via Supabase API
266
+ // Backup das Edge Functions com detecção inteligente do Docker
257
267
  async function backupEdgeFunctions(config, backupDir) {
258
268
  try {
269
+ console.log('🔍 Verificando dependências para backup de Edge Functions...');
270
+
271
+ // 1. Verificar se Docker está instalado e rodando
272
+ const dockerStatus = await detectDockerDependencies();
273
+
274
+ if (!dockerStatus.dockerInstalled) {
275
+ console.log('⚠️ DOCKER DESKTOP NÃO ENCONTRADO');
276
+ console.log('');
277
+ console.log('📋 Para fazer backup das Edge Functions, você precisa:');
278
+ console.log(' 1. Instalar Docker Desktop');
279
+ console.log(' 2. Executar Docker Desktop');
280
+ console.log(' 3. Repetir o comando de backup');
281
+ console.log('');
282
+ console.log('🔗 Download: https://docs.docker.com/desktop/install/');
283
+ console.log('');
284
+ console.log('⏭️ Pulando backup de Edge Functions...');
285
+ console.log('✅ Continuando com outros componentes do backup...');
286
+ return { success: false, reason: 'docker_not_installed', functions: [] };
287
+ }
288
+
289
+ if (!dockerStatus.dockerRunning) {
290
+ console.log('⚠️ DOCKER DESKTOP NÃO ESTÁ EXECUTANDO');
291
+ console.log('');
292
+ console.log('📋 Para fazer backup das Edge Functions, você precisa:');
293
+ console.log(' 1. Abrir Docker Desktop');
294
+ console.log(' 2. Aguardar inicialização completa');
295
+ console.log(' 3. Repetir o comando de backup');
296
+ console.log('');
297
+ console.log('💡 Dica: Docker Desktop deve estar rodando em segundo plano');
298
+ console.log('');
299
+ console.log('⏭️ Pulando backup de Edge Functions...');
300
+ console.log('✅ Continuando com outros componentes do backup...');
301
+ return { success: false, reason: 'docker_not_running', functions: [] };
302
+ }
303
+
304
+ if (!dockerStatus.supabaseCLI) {
305
+ console.log('⚠️ SUPABASE CLI NÃO ENCONTRADO');
306
+ console.log('');
307
+ console.log('📋 Para fazer backup das Edge Functions, você precisa:');
308
+ console.log(' 1. Instalar Supabase CLI');
309
+ console.log(' 2. Repetir o comando de backup');
310
+ console.log('');
311
+ console.log('🔗 Instalação: npm install -g supabase');
312
+ console.log('');
313
+ console.log('⏭️ Pulando backup de Edge Functions...');
314
+ console.log('✅ Continuando com outros componentes do backup...');
315
+ return { success: false, reason: 'supabase_cli_not_found', functions: [] };
316
+ }
317
+
318
+ // 3. Docker está OK, proceder com backup
319
+ console.log('✅ Docker Desktop detectado e funcionando');
320
+ console.log('✅ Supabase CLI detectado');
321
+ console.log('📥 Iniciando backup das Edge Functions...');
322
+
259
323
  const functionsDir = path.join(backupDir, 'edge-functions');
260
324
  await ensureDir(functionsDir);
261
325
 
@@ -271,7 +335,7 @@ async function backupEdgeFunctions(config, backupDir) {
271
335
 
272
336
  if (!functionsResponse.ok) {
273
337
  console.log(chalk.yellow(` ⚠️ Erro ao listar Edge Functions: ${functionsResponse.status} ${functionsResponse.statusText}`));
274
- return { success: false, functions: [] };
338
+ return { success: false, reason: 'api_error', functions: [] };
275
339
  }
276
340
 
277
341
  const functions = await functionsResponse.json();
@@ -281,78 +345,68 @@ async function backupEdgeFunctions(config, backupDir) {
281
345
  await writeJson(path.join(functionsDir, 'README.md'), {
282
346
  message: 'Nenhuma Edge Function encontrada neste projeto'
283
347
  });
284
- return { success: true, functions: [] };
348
+ return { success: true, reason: 'no_functions', functions: [] };
285
349
  }
286
350
 
287
- console.log(chalk.gray(` - Encontradas ${functions.length} Edge Functions`));
351
+ console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
288
352
 
289
353
  const downloadedFunctions = [];
354
+ let successCount = 0;
355
+ let errorCount = 0;
290
356
 
291
- // ✅ Baixar todas as functions encontradas
357
+ // ✅ Baixar cada Edge Function usando Supabase CLI
292
358
  for (const func of functions) {
293
359
  try {
294
- console.log(chalk.gray(` - Baixando: ${func.name}`));
360
+ console.log(chalk.gray(` - Baixando: ${func.name}...`));
295
361
 
296
- // Baixar código da function via Management API com Personal Access Token
297
- const functionResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/functions/${func.name}`, {
298
- headers: {
299
- 'Authorization': `Bearer ${config.supabase.accessToken}`,
300
- 'Content-Type': 'application/json'
301
- }
362
+ // Usar comando oficial do Supabase CLI
363
+ await runCommand(`supabase functions download ${func.name}`, {
364
+ cwd: process.cwd(),
365
+ timeout: 60000 // 60 segundos timeout
302
366
  });
303
367
 
304
- if (!functionResponse.ok) {
305
- console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${functionResponse.status} ${functionResponse.statusText}`));
306
- continue;
307
- }
308
-
309
- const functionData = await functionResponse.json();
310
-
311
- // ✅ Salvar arquivos dinamicamente
312
- const funcDir = path.join(functionsDir, func.name);
313
- await ensureDir(funcDir);
314
-
315
- // ✅ Salvar cada arquivo da function
316
- if (functionData && functionData.files) {
317
- for (const file of functionData.files) {
318
- const fileName = path.basename(file.name);
319
- const filePath = path.join(funcDir, fileName);
320
- await fs.promises.writeFile(filePath, file.content);
321
- }
322
- } else if (functionData && functionData.code) {
323
- // Fallback para estrutura simples
324
- const indexPath = path.join(funcDir, 'index.ts');
325
- await fs.promises.writeFile(indexPath, functionData.code);
368
+ // Mover arquivos baixados para o diretório de backup
369
+ const sourceDir = path.join(process.cwd(), 'supabase', 'functions', func.name);
370
+ const targetDir = path.join(functionsDir, func.name);
371
+
372
+ if (fs.existsSync(sourceDir)) {
373
+ await copyDir(sourceDir, targetDir);
374
+ console.log(chalk.green(` ✅ ${func.name} baixada com sucesso`));
375
+ successCount++;
326
376
 
327
- if (functionData.deno_config) {
328
- const denoPath = path.join(funcDir, 'deno.json');
329
- await writeJson(denoPath, functionData.deno_config);
330
- }
377
+ downloadedFunctions.push({
378
+ name: func.name,
379
+ slug: func.name,
380
+ version: func.version || 'unknown',
381
+ files: fs.existsSync(targetDir) ? fs.readdirSync(targetDir) : []
382
+ });
331
383
  } else {
332
- // Criar arquivo placeholder se não houver código
333
- const indexPath = path.join(funcDir, 'index.ts');
334
- await fs.promises.writeFile(indexPath, `// Edge Function: ${func.name}\n// Code not available via API\n`);
384
+ throw new Error('Diretório não encontrado após download');
335
385
  }
336
-
337
- downloadedFunctions.push({
338
- name: func.name,
339
- slug: func.name,
340
- version: func.version || 'unknown',
341
- files: fs.existsSync(funcDir) ? fs.readdirSync(funcDir) : []
342
- });
343
-
344
- console.log(chalk.green(` ✅ ${func.name} baixada`));
386
+
345
387
  } catch (error) {
346
388
  console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${error.message}`));
389
+ errorCount++;
347
390
  }
348
391
  }
349
-
350
- console.log(chalk.green(`✅ Edge Functions backupadas: ${downloadedFunctions.length} functions`));
351
- return { success: true, functions: downloadedFunctions };
392
+
393
+ console.log(chalk.green(`📊 Backup de Edge Functions concluído:`));
394
+ console.log(chalk.green(` ✅ Sucessos: ${successCount}`));
395
+ console.log(chalk.green(` ❌ Erros: ${errorCount}`));
396
+
397
+ return {
398
+ success: true,
399
+ reason: 'success',
400
+ functions: downloadedFunctions,
401
+ functionsCount: functions.length,
402
+ successCount,
403
+ errorCount
404
+ };
352
405
 
353
406
  } catch (error) {
354
- console.log(chalk.yellow(` ⚠️ Erro no backup das Edge Functions: ${error.message}`));
355
- return { success: false, functions: [] };
407
+ console.log(chalk.yellow(` ⚠️ Erro durante backup de Edge Functions: ${error.message}`));
408
+ console.log('⏭️ Continuando com outros componentes...');
409
+ return { success: false, reason: 'download_error', error: error.message, functions: [] };
356
410
  }
357
411
  }
358
412
 
@@ -620,8 +674,12 @@ async function generateCompleteBackupManifest(config, backupDir, results) {
620
674
  },
621
675
  edge_functions: {
622
676
  success: results.edgeFunctions.success,
623
- functions_count: results.edgeFunctions.functions.length,
624
- functions: results.edgeFunctions.functions.map(f => f.name)
677
+ reason: results.edgeFunctions.reason || null,
678
+ functions_count: results.edgeFunctions.functionsCount || 0,
679
+ success_count: results.edgeFunctions.successCount || 0,
680
+ error_count: results.edgeFunctions.errorCount || 0,
681
+ functions: results.edgeFunctions.functions.map(f => f.name),
682
+ timestamp: new Date().toISOString()
625
683
  },
626
684
  auth_settings: {
627
685
  success: results.authSettings.success
@@ -0,0 +1,71 @@
1
+ const { exec } = require('child_process');
2
+ const { promisify } = require('util');
3
+
4
+ const execAsync = promisify(exec);
5
+
6
+ /**
7
+ * Detecta se Docker Desktop está instalado no sistema
8
+ * @returns {Promise<{installed: boolean, running: boolean}>}
9
+ */
10
+ async function detectDockerInstallation() {
11
+ try {
12
+ // Tentar executar docker --version
13
+ await execAsync('docker --version');
14
+ return { installed: true, running: false }; // Instalado mas não sabemos se está rodando
15
+ } catch (error) {
16
+ return { installed: false, running: false };
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Verifica se Docker está rodando e acessível
22
+ * @returns {Promise<boolean>}
23
+ */
24
+ async function detectDockerRunning() {
25
+ try {
26
+ // Tentar executar docker ps
27
+ await execAsync('docker ps');
28
+ return true;
29
+ } catch (error) {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Verifica se Supabase CLI está disponível
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ async function detectSupabaseCLI() {
39
+ try {
40
+ // Tentar executar supabase --version
41
+ await execAsync('supabase --version');
42
+ return true;
43
+ } catch (error) {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Função principal para detectar todas as dependências do Docker
50
+ * @returns {Promise<{dockerInstalled: boolean, dockerRunning: boolean, supabaseCLI: boolean}>}
51
+ */
52
+ async function detectDockerDependencies() {
53
+ console.log('🔍 Verificando dependências para backup de Edge Functions...');
54
+
55
+ const dockerInstalled = await detectDockerInstallation();
56
+ const dockerRunning = dockerInstalled.installed ? await detectDockerRunning() : false;
57
+ const supabaseCLI = await detectSupabaseCLI();
58
+
59
+ return {
60
+ dockerInstalled: dockerInstalled.installed,
61
+ dockerRunning,
62
+ supabaseCLI
63
+ };
64
+ }
65
+
66
+ module.exports = {
67
+ detectDockerInstallation,
68
+ detectDockerRunning,
69
+ detectSupabaseCLI,
70
+ detectDockerDependencies
71
+ };