smoonb 0.0.8 → 0.0.10

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/README.md CHANGED
@@ -107,16 +107,18 @@ backups/backup-2024-01-15T10-30-45-123Z/
107
107
  └── [código das Edge Functions locais]
108
108
  ```
109
109
 
110
- ### Restauração
110
+ ### Restauração Interativa
111
111
  ```bash
112
- npx smoonb restore --backup-dir backups/backup-2024-01-15T10-30-45-123Z
112
+ npx smoonb restore
113
113
  ```
114
114
 
115
- **Processo:**
116
- 1. Verifica se database está vazia (clean restore)
117
- 2. Executa `roles.sql` (roles e permissões)
118
- 3. Executa `schema.sql` (estrutura das tabelas)
119
- 4. Executa `data.sql` (dados com COPY)
115
+ **Processo interativo:**
116
+ 1. Lista todos os backups disponíveis
117
+ 2. Permite seleção numerada do backup desejado
118
+ 3. Verifica se database está vazia (clean restore)
119
+ 4. Executa `roles.sql` (roles e permissões)
120
+ 5. Executa `schema.sql` (estrutura das tabelas)
121
+ 6. Executa `data.sql` (dados com COPY)
120
122
 
121
123
  ### Verificação Pós-Restore
122
124
  ```bash
@@ -145,7 +147,7 @@ npx smoonb functions push
145
147
  | Comando | Descrição |
146
148
  |---------|-----------|
147
149
  | `npx smoonb backup` | Backup completo usando Supabase CLI |
148
- | `npx smoonb restore` | Restauração usando psql |
150
+ | `npx smoonb restore` | Restauração interativa usando psql |
149
151
  | `npx smoonb check` | Verificação de integridade |
150
152
  | `npx smoonb functions` | Gerenciar Edge Functions |
151
153
  | `npx smoonb config` | Configurar credenciais |
@@ -179,8 +181,8 @@ npx smoonb backup
179
181
  # 3. Configurar .smoonbrc com credenciais do novo projeto
180
182
  npx smoonb config --init
181
183
 
182
- # 4. Restaurar backup
183
- npx smoonb restore --backup-dir backups/backup-YYYY-MM-DDTHH-MM-SS-sssZ
184
+ # 4. Restaurar backup (modo interativo)
185
+ npx smoonb restore
184
186
 
185
187
  # 5. Verificar integridade
186
188
  npx smoonb check
package/bin/smoonb.js CHANGED
@@ -74,8 +74,7 @@ program
74
74
 
75
75
  program
76
76
  .command('restore')
77
- .description('Restaurar backup completo usando psql')
78
- .option('-b, --backup-dir <dir>', 'Diretório do backup a ser restaurado')
77
+ .description('Restaurar backup completo usando psql (modo interativo)')
79
78
  .option('--db-url <url>', 'URL da database de destino (override)')
80
79
  .action(commands.restore);
81
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -26,10 +26,10 @@
26
26
  "node": ">=16.0.0"
27
27
  },
28
28
  "dependencies": {
29
- "commander": "^11.1.0",
30
29
  "@supabase/supabase-js": "^2.38.0",
31
30
  "chalk": "^4.1.2",
32
- "inquirer": "^8.2.6"
31
+ "commander": "^11.1.0",
32
+ "inquirer": "^8.2.7"
33
33
  },
34
34
  "type": "commonjs",
35
35
  "repository": {
@@ -1,4 +1,3 @@
1
- const { Command } = require('commander');
2
1
  const chalk = require('chalk');
3
2
  const path = require('path');
4
3
  const { ensureBin, runCommand } = require('../utils/cli');
@@ -8,68 +7,66 @@ const { readConfig, validateFor } = require('../utils/config');
8
7
  const { IntrospectionService } = require('../services/introspect');
9
8
  const { showBetaBanner } = require('../index');
10
9
 
11
- const backupCommand = new Command('backup')
12
- .description('Backup completo do projeto Supabase usando Supabase CLI')
13
- .option('-o, --output <dir>', 'Diretório de saída')
14
- .action(async (options) => {
15
- showBetaBanner();
16
-
17
- try {
18
- // Verificar se Supabase CLI está disponível
19
- const supabasePath = await ensureBin('supabase');
20
- if (!supabasePath) {
21
- console.error(chalk.red(' Supabase CLI não encontrado'));
22
- console.log(chalk.yellow('💡 Instale o Supabase CLI:'));
23
- console.log(chalk.yellow(' npm install -g supabase'));
24
- console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
25
- process.exit(1);
26
- }
27
-
28
- // Carregar e validar configuração
29
- const config = await readConfig();
30
- validateFor(config, 'backup');
31
-
32
- const databaseUrl = config.supabase.databaseUrl;
33
- if (!databaseUrl) {
34
- console.error(chalk.red('❌ databaseUrl não configurada'));
35
- console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc'));
36
- process.exit(1);
37
- }
38
-
39
- // Resolver diretório de saída
40
- const outputDir = options.output || config.backup.outputDir;
41
-
42
- // Criar diretório de backup com timestamp
43
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
44
- const backupDir = path.join(outputDir, `backup-${timestamp}`);
45
- await ensureDir(backupDir);
46
-
47
- console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
48
- console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
49
-
50
- // 1. Backup da Database usando Supabase CLI
51
- console.log(chalk.blue('\n📊 1/3 - Backup da Database PostgreSQL...'));
52
- await backupDatabaseWithSupabaseCLI(databaseUrl, backupDir);
53
-
54
- // 2. Gerar inventário real
55
- console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
56
- await generateInventory(config, backupDir);
57
-
58
- // 3. Backup das Edge Functions locais
59
- console.log(chalk.blue('\n⚡ 3/3 - Backup das Edge Functions locais...'));
60
- await backupLocalFunctions(backupDir);
61
-
62
- // Gerar manifesto do backup
63
- await generateBackupManifest(config, backupDir);
64
-
65
- console.log(chalk.green('\n🎉 Backup completo finalizado!'));
66
- console.log(chalk.blue(`📁 Localização: ${backupDir}`));
67
-
68
- } catch (error) {
69
- console.error(chalk.red(`❌ Erro no backup: ${error.message}`));
10
+ // Exportar FUNÇÃO em vez de objeto Command
11
+ module.exports = async (options) => {
12
+ showBetaBanner();
13
+
14
+ try {
15
+ // Verificar se Supabase CLI está disponível
16
+ const supabasePath = await ensureBin('supabase');
17
+ if (!supabasePath) {
18
+ console.error(chalk.red('❌ Supabase CLI não encontrado'));
19
+ console.log(chalk.yellow('💡 Instale o Supabase CLI:'));
20
+ console.log(chalk.yellow(' npm install -g supabase'));
21
+ console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
22
+ process.exit(1);
23
+ }
24
+
25
+ // Carregar e validar configuração
26
+ const config = await readConfig();
27
+ validateFor(config, 'backup');
28
+
29
+ const databaseUrl = config.supabase.databaseUrl;
30
+ if (!databaseUrl) {
31
+ console.error(chalk.red('❌ databaseUrl não configurada'));
32
+ console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc'));
70
33
  process.exit(1);
71
34
  }
72
- });
35
+
36
+ // Resolver diretório de saída
37
+ const outputDir = options.output || config.backup.outputDir;
38
+
39
+ // Criar diretório de backup com timestamp
40
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
41
+ const backupDir = path.join(outputDir, `backup-${timestamp}`);
42
+ await ensureDir(backupDir);
43
+
44
+ console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
45
+ console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
46
+
47
+ // 1. Backup da Database usando Supabase CLI
48
+ console.log(chalk.blue('\n📊 1/3 - Backup da Database PostgreSQL...'));
49
+ await backupDatabaseWithSupabaseCLI(databaseUrl, backupDir);
50
+
51
+ // 2. Gerar inventário real
52
+ console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
53
+ await generateInventory(config, backupDir);
54
+
55
+ // 3. Backup das Edge Functions locais
56
+ console.log(chalk.blue('\n⚡ 3/3 - Backup das Edge Functions locais...'));
57
+ await backupLocalFunctions(backupDir);
58
+
59
+ // Gerar manifesto do backup
60
+ await generateBackupManifest(config, backupDir);
61
+
62
+ console.log(chalk.green('\n🎉 Backup completo finalizado!'));
63
+ console.log(chalk.blue(`📁 Localização: ${backupDir}`));
64
+
65
+ } catch (error) {
66
+ console.error(chalk.red(`❌ Erro no backup: ${error.message}`));
67
+ process.exit(1);
68
+ }
69
+ };
73
70
 
74
71
  // Backup da database usando Supabase CLI
75
72
  async function backupDatabaseWithSupabaseCLI(databaseUrl, backupDir) {
@@ -172,6 +169,4 @@ async function generateBackupManifest(config, backupDir) {
172
169
 
173
170
  const manifestPath = path.join(backupDir, 'backup-manifest.json');
174
171
  await writeJson(manifestPath, manifest);
175
- }
176
-
177
- module.exports = backupCommand;
172
+ }
@@ -1,59 +1,57 @@
1
- const { Command } = require('commander');
2
1
  const chalk = require('chalk');
3
2
  const path = require('path');
4
3
  const fs = require('fs');
5
4
  const { ensureBin, runCommand } = require('../utils/cli');
6
- const { readConfig, validateFor, writeJson } = require('../utils/config');
5
+ const { readConfig, validateFor } = require('../utils/config');
6
+ const { writeJson } = require('../utils/fsx');
7
7
  const { IntrospectionService } = require('../services/introspect');
8
8
  const { showBetaBanner } = require('../index');
9
9
 
10
- const checkCommand = new Command('check')
11
- .description('Verificar integridade do projeto Supabase após restauração')
12
- .option('-o, --output <file>', 'Arquivo de saída do relatório', 'check-report.json')
13
- .action(async (options) => {
14
- showBetaBanner();
15
-
16
- try {
17
- // Verificar se psql está disponível
18
- const psqlPath = await ensureBin('psql');
19
- if (!psqlPath) {
20
- console.error(chalk.red('❌ psql não encontrado'));
21
- console.log(chalk.yellow('💡 Instale PostgreSQL:'));
22
- console.log(chalk.yellow(' https://www.postgresql.org/download/'));
23
- process.exit(1);
24
- }
10
+ // Exportar FUNÇÃO em vez de objeto Command
11
+ module.exports = async (options) => {
12
+ showBetaBanner();
13
+
14
+ try {
15
+ // Verificar se psql está disponível
16
+ const psqlPath = await ensureBin('psql');
17
+ if (!psqlPath) {
18
+ console.error(chalk.red('❌ psql não encontrado'));
19
+ console.log(chalk.yellow('💡 Instale PostgreSQL:'));
20
+ console.log(chalk.yellow(' https://www.postgresql.org/download/'));
21
+ process.exit(1);
22
+ }
25
23
 
26
- // Carregar configuração
27
- const config = await readConfig();
28
- validateFor(config, 'backup'); // Usar mesma validação do backup
24
+ // Carregar configuração
25
+ const config = await readConfig();
26
+ validateFor(config, 'backup'); // Usar mesma validação do backup
29
27
 
30
- const databaseUrl = config.supabase.databaseUrl;
31
- if (!databaseUrl) {
32
- console.error(chalk.red('❌ databaseUrl não configurada'));
33
- console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc'));
34
- process.exit(1);
35
- }
28
+ const databaseUrl = config.supabase.databaseUrl;
29
+ if (!databaseUrl) {
30
+ console.error(chalk.red('❌ databaseUrl não configurada'));
31
+ console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc'));
32
+ process.exit(1);
33
+ }
36
34
 
37
- console.log(chalk.blue(`🔍 Verificando integridade do projeto: ${config.supabase.projectId}`));
35
+ console.log(chalk.blue(`🔍 Verificando integridade do projeto: ${config.supabase.projectId}`));
38
36
 
39
- // Executar verificações
40
- const report = await performChecks(config, databaseUrl);
37
+ // Executar verificações
38
+ const report = await performChecks(config, databaseUrl);
41
39
 
42
- // Salvar relatório
43
- const reportPath = path.resolve(options.output);
44
- await writeJson(reportPath, report);
40
+ // Salvar relatório
41
+ const reportPath = path.resolve(options.output || 'check-report.json');
42
+ await writeJson(reportPath, report);
45
43
 
46
- // Mostrar resumo
47
- showCheckSummary(report);
44
+ // Mostrar resumo
45
+ showCheckSummary(report);
48
46
 
49
- console.log(chalk.green('\n🎉 Verificação concluída!'));
50
- console.log(chalk.blue(`📋 Relatório salvo em: ${reportPath}`));
47
+ console.log(chalk.green('\n🎉 Verificação concluída!'));
48
+ console.log(chalk.blue(`📋 Relatório salvo em: ${reportPath}`));
51
49
 
52
- } catch (error) {
53
- console.error(chalk.red(`❌ Erro na verificação: ${error.message}`));
54
- process.exit(1);
55
- }
56
- });
50
+ } catch (error) {
51
+ console.error(chalk.red(`❌ Erro na verificação: ${error.message}`));
52
+ process.exit(1);
53
+ }
54
+ };
57
55
 
58
56
  // Executar todas as verificações
59
57
  async function performChecks(config, databaseUrl) {
@@ -267,6 +265,4 @@ function showCheckSummary(report) {
267
265
  } else {
268
266
  console.log(chalk.yellow('⚠️ Algumas verificações falharam. Verifique o relatório completo.'));
269
267
  }
270
- }
271
-
272
- module.exports = checkCommand;
268
+ }
@@ -1,17 +1,12 @@
1
- /**
2
- * Comando de configuração do smoonb
3
- */
4
-
5
1
  const chalk = require('chalk');
6
2
  const fs = require('fs').promises;
7
3
  const path = require('path');
8
4
  const os = require('os');
5
+ const { showBetaBanner } = require('../index');
9
6
 
10
- async function configCommand(options) {
11
- console.log(chalk.red.bold('🚀 smoonb - EXPERIMENTAL VERSION'));
12
- console.log(chalk.red.bold('⚠️ VERSÃO EXPERIMENTAL - NUNCA TESTADA EM PRODUÇÃO!'));
13
- console.log(chalk.red.bold('🚨 USE POR SUA CONTA E RISCO - Pode causar perda de dados!'));
14
- console.log(chalk.red.bold('❌ NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados!\n'));
7
+ // Exportar FUNÇÃO em vez de objeto Command
8
+ module.exports = async (options) => {
9
+ showBetaBanner();
15
10
 
16
11
  console.log(chalk.cyan.bold('⚙️ Configuração do smoonb...\n'));
17
12
 
@@ -19,60 +14,116 @@ async function configCommand(options) {
19
14
  const configPath = path.join(process.cwd(), '.smoonbrc');
20
15
 
21
16
  if (options.init) {
22
- // Inicializar configuração
23
- const defaultConfig = {
24
- supabase: {
25
- projectId: '',
26
- url: '',
27
- serviceKey: '',
28
- anonKey: '',
29
- databaseUrl: ''
30
- },
31
- backup: {
32
- includeFunctions: true,
33
- includeStorage: true,
34
- includeAuth: true,
35
- includeRealtime: true,
36
- outputDir: './backups',
37
- pgDumpPath: 'C:\\Program Files\\PostgreSQL\\17\\bin\\pg_dump.exe'
38
- },
39
- restore: {
40
- cleanRestore: false,
41
- verifyAfterRestore: true
42
- }
43
- };
44
-
45
- await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
46
- console.log(chalk.green('✅ Arquivo de configuração criado:'), configPath);
47
- console.log(chalk.yellow('💡 Edite o arquivo para configurar suas credenciais Supabase'));
48
-
17
+ await initializeConfig(configPath);
49
18
  } else if (options.show) {
50
- // Mostrar configuração atual
51
- try {
52
- const configContent = await fs.readFile(configPath, 'utf8');
53
- const config = JSON.parse(configContent);
54
-
55
- console.log(chalk.green('📋 Configuração atual:'));
56
- console.log(chalk.blue('📁 Arquivo:'), configPath);
57
- console.log(chalk.gray(JSON.stringify(config, null, 2)));
58
-
59
- } catch (error) {
60
- console.log(chalk.yellow('⚠️ Arquivo de configuração não encontrado'));
61
- console.log(chalk.yellow('💡 Use'), chalk.cyan('smoonb config --init'), chalk.yellow('para criar a configuração'));
62
- }
63
-
19
+ await showConfig(configPath);
64
20
  } else {
65
- // Mostrar ajuda
66
21
  console.log(chalk.yellow('💡 Opções disponíveis:'));
67
- console.log(chalk.cyan(' --init'), chalk.gray(' Inicializar arquivo de configuração'));
68
- console.log(chalk.cyan(' --show'), chalk.gray(' Mostrar configuração atual'));
69
- console.log(chalk.blue('\n📁 Arquivo de configuração:'), configPath);
22
+ console.log(chalk.gray(' --init Inicializar configuração'));
23
+ console.log(chalk.gray(' --show Mostrar configuração atual'));
70
24
  }
71
25
 
72
26
  } catch (error) {
73
- console.error(chalk.red.bold('❌ Erro durante a configuração:'), error.message);
27
+ console.error(chalk.red('❌ Erro na configuração:'), error.message);
74
28
  process.exit(1);
75
29
  }
30
+ };
31
+
32
+ // Inicializar configuração
33
+ async function initializeConfig(configPath) {
34
+ console.log(chalk.blue('🔧 Inicializando configuração...'));
35
+
36
+ const defaultConfig = {
37
+ supabase: {
38
+ projectId: 'your-project-id-here',
39
+ url: 'https://your-project-id.supabase.co',
40
+ serviceKey: 'your-service-key-here',
41
+ anonKey: 'your-anon-key-here',
42
+ databaseUrl: 'postgresql://postgres:[password]@db.your-project-id.supabase.co:5432/postgres'
43
+ },
44
+ backup: {
45
+ includeFunctions: true,
46
+ includeStorage: true,
47
+ includeAuth: true,
48
+ includeRealtime: true,
49
+ outputDir: './backups'
50
+ },
51
+ restore: {
52
+ cleanRestore: true,
53
+ verifyAfterRestore: true
54
+ }
55
+ };
56
+
57
+ try {
58
+ await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
59
+ console.log(chalk.green('✅ Arquivo de configuração criado: .smoonbrc'));
60
+ console.log(chalk.yellow('\n📝 Próximos passos:'));
61
+ console.log(chalk.gray(' 1. Edite .smoonbrc com suas credenciais Supabase'));
62
+ console.log(chalk.gray(' 2. Substitua os valores placeholder pelos reais'));
63
+ console.log(chalk.gray(' 3. Execute: npx smoonb backup'));
64
+ } catch (error) {
65
+ throw new Error(`Falha ao criar arquivo de configuração: ${error.message}`);
66
+ }
76
67
  }
77
68
 
78
- module.exports = configCommand;
69
+ // Mostrar configuração atual
70
+ async function showConfig(configPath) {
71
+ console.log(chalk.blue('📋 Configuração atual:'));
72
+
73
+ try {
74
+ const configContent = await fs.readFile(configPath, 'utf8');
75
+ const config = JSON.parse(configContent);
76
+
77
+ console.log(chalk.green('✅ Arquivo de configuração encontrado'));
78
+ console.log(chalk.gray(` - Localização: ${configPath}`));
79
+
80
+ if (config.supabase?.projectId && config.supabase.projectId !== 'your-project-id-here') {
81
+ console.log(chalk.gray(` - Project ID: ${config.supabase.projectId}`));
82
+ } else {
83
+ console.log(chalk.yellow(' - Project ID: Não configurado'));
84
+ }
85
+
86
+ if (config.supabase?.url && config.supabase.url !== 'https://your-project-id.supabase.co') {
87
+ console.log(chalk.gray(` - Supabase URL: ${config.supabase.url}`));
88
+ } else {
89
+ console.log(chalk.yellow(' - Supabase URL: Não configurado'));
90
+ }
91
+
92
+ if (config.supabase?.serviceKey && config.supabase.serviceKey !== 'your-service-key-here') {
93
+ console.log(chalk.gray(' - Service Key: Configurada'));
94
+ } else {
95
+ console.log(chalk.yellow(' - Service Key: Não configurada'));
96
+ }
97
+
98
+ if (config.supabase?.anonKey && config.supabase.anonKey !== 'your-anon-key-here') {
99
+ console.log(chalk.gray(' - Anon Key: Configurada'));
100
+ } else {
101
+ console.log(chalk.yellow(' - Anon Key: Não configurada'));
102
+ }
103
+
104
+ if (config.supabase?.databaseUrl && !config.supabase.databaseUrl.includes('[password]')) {
105
+ console.log(chalk.gray(' - Database URL: Configurada'));
106
+ } else {
107
+ console.log(chalk.yellow(' - Database URL: Não configurada'));
108
+ }
109
+
110
+ console.log(chalk.blue('\n📊 Configurações de backup:'));
111
+ console.log(chalk.gray(` - Output Dir: ${config.backup?.outputDir || './backups'}`));
112
+ console.log(chalk.gray(` - Include Functions: ${config.backup?.includeFunctions || true}`));
113
+ console.log(chalk.gray(` - Include Storage: ${config.backup?.includeStorage || true}`));
114
+ console.log(chalk.gray(` - Include Auth: ${config.backup?.includeAuth || true}`));
115
+ console.log(chalk.gray(` - Include Realtime: ${config.backup?.includeRealtime || true}`));
116
+
117
+ console.log(chalk.blue('\n🔄 Configurações de restore:'));
118
+ console.log(chalk.gray(` - Clean Restore: ${config.restore?.cleanRestore || true}`));
119
+ console.log(chalk.gray(` - Verify After Restore: ${config.restore?.verifyAfterRestore || true}`));
120
+
121
+ } catch (error) {
122
+ if (error.code === 'ENOENT') {
123
+ console.log(chalk.yellow('⚠️ Arquivo de configuração não encontrado'));
124
+ console.log(chalk.gray(' - Use: npx smoonb config --init'));
125
+ } else {
126
+ throw new Error(`Falha ao ler arquivo de configuração: ${error.message}`);
127
+ }
128
+ }
129
+ }
@@ -1,149 +1,133 @@
1
- const { Command } = require('commander');
2
1
  const chalk = require('chalk');
3
2
  const { ensureBin, runCommand } = require('../utils/cli');
4
3
  const { readConfig, validateFor } = require('../utils/config');
5
4
  const { showBetaBanner } = require('../index');
6
5
 
7
- const functionsCommand = new Command('functions')
8
- .description('Gerenciar Edge Functions do Supabase')
9
- .action(async () => {
10
- showBetaBanner();
11
-
12
- try {
13
- // Verificar se Supabase CLI está disponível
14
- const supabasePath = await ensureBin('supabase');
15
- if (!supabasePath) {
16
- console.error(chalk.red(' Supabase CLI não encontrado'));
17
- console.log(chalk.yellow('💡 Instale o Supabase CLI:'));
18
- console.log(chalk.yellow(' npm install -g supabase'));
19
- console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
20
- process.exit(1);
21
- }
22
-
23
- console.log(chalk.blue('⚡ Comandos disponíveis para Edge Functions:'));
24
- console.log(chalk.yellow('\n📋 Listar functions:'));
25
- console.log(chalk.gray(' npx smoonb functions list'));
26
- console.log(chalk.yellow('\n🚀 Deploy functions:'));
27
- console.log(chalk.gray(' npx smoonb functions push'));
28
- console.log(chalk.yellow('\n📥 Pull functions (se disponível):'));
29
- console.log(chalk.gray(' npx smoonb functions pull'));
30
- console.log(chalk.yellow('\n💡 Para mais opções, use o Supabase CLI diretamente:'));
31
- console.log(chalk.gray(' supabase functions --help'));
32
-
33
- } catch (error) {
34
- console.error(chalk.red(`❌ Erro: ${error.message}`));
6
+ // Exportar FUNÇÃO em vez de objeto Command
7
+ module.exports = async (options) => {
8
+ showBetaBanner();
9
+
10
+ try {
11
+ // Verificar se Supabase CLI está disponível
12
+ const supabasePath = await ensureBin('supabase');
13
+ if (!supabasePath) {
14
+ console.error(chalk.red('❌ Supabase CLI não encontrado'));
15
+ console.log(chalk.yellow('💡 Instale o Supabase CLI:'));
16
+ console.log(chalk.yellow(' npm install -g supabase'));
17
+ console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
35
18
  process.exit(1);
36
19
  }
37
- });
38
-
39
- // Subcomando para listar functions
40
- functionsCommand
41
- .command('list')
42
- .description('Listar Edge Functions do projeto')
43
- .action(async () => {
44
- showBetaBanner();
45
-
46
- try {
47
- const config = await readConfig();
48
- validateFor(config, 'inventory');
49
-
50
- console.log(chalk.blue('📋 Listando Edge Functions...'));
51
-
52
- const { stdout, stderr } = await runCommand(
53
- 'supabase functions list',
54
- {
55
- env: {
56
- ...process.env,
57
- SUPABASE_ACCESS_TOKEN: config.supabase.serviceKey
58
- }
59
- }
60
- );
61
20
 
62
- if (stderr && !stderr.includes('WARN')) {
63
- console.log(chalk.yellow(`⚠️ Avisos: ${stderr}`));
21
+ console.log(chalk.blue('⚡ Comandos disponíveis para Edge Functions:'));
22
+ console.log(chalk.yellow('\n📋 Listar functions:'));
23
+ console.log(chalk.gray(' npx smoonb functions list'));
24
+ console.log(chalk.yellow('\n🚀 Deploy functions:'));
25
+ console.log(chalk.gray(' npx smoonb functions push'));
26
+ console.log(chalk.yellow('\n📥 Pull functions (se disponível):'));
27
+ console.log(chalk.gray(' npx smoonb functions pull'));
28
+ console.log(chalk.yellow('\n💡 Para mais opções, use o Supabase CLI diretamente:'));
29
+ console.log(chalk.gray(' supabase functions --help'));
30
+
31
+ } catch (error) {
32
+ console.error(chalk.red(`❌ Erro: ${error.message}`));
33
+ process.exit(1);
34
+ }
35
+ };
36
+
37
+ // Função para listar functions
38
+ async function listFunctions() {
39
+ try {
40
+ const config = await readConfig();
41
+ validateFor(config, 'inventory');
42
+
43
+ console.log(chalk.blue('📋 Listando Edge Functions...'));
44
+
45
+ const { stdout, stderr } = await runCommand(
46
+ 'supabase functions list',
47
+ {
48
+ env: {
49
+ ...process.env,
50
+ SUPABASE_ACCESS_TOKEN: config.supabase.serviceKey
51
+ }
64
52
  }
53
+ );
54
+
55
+ if (stderr && !stderr.includes('WARN')) {
56
+ console.log(chalk.yellow(`⚠️ Avisos: ${stderr}`));
57
+ }
58
+
59
+ console.log(chalk.green('✅ Edge Functions listadas:'));
60
+ console.log(stdout);
61
+
62
+ } catch (error) {
63
+ console.error(chalk.red(`❌ Erro ao listar functions: ${error.message}`));
64
+ process.exit(1);
65
+ }
66
+ }
65
67
 
66
- console.log(chalk.green('✅ Edge Functions listadas:'));
67
- console.log(stdout);
68
+ // Função para deploy de functions
69
+ async function pushFunctions(projectRef) {
70
+ try {
71
+ const config = await readConfig();
72
+ const projectId = projectRef || config.supabase.projectId;
68
73
 
69
- } catch (error) {
70
- console.error(chalk.red(`❌ Erro ao listar functions: ${error.message}`));
74
+ if (!projectId) {
75
+ console.error(chalk.red('❌ Project ID não encontrado'));
76
+ console.log(chalk.yellow('💡 Use: npx smoonb functions push --project-ref <id>'));
71
77
  process.exit(1);
72
78
  }
73
- });
74
-
75
- // Subcomando para deploy de functions
76
- functionsCommand
77
- .command('push')
78
- .description('Fazer deploy das Edge Functions')
79
- .option('--project-ref <ref>', 'ID do projeto Supabase')
80
- .action(async (options) => {
81
- showBetaBanner();
82
-
83
- try {
84
- const config = await readConfig();
85
- const projectRef = options.projectRef || config.supabase.projectId;
86
-
87
- if (!projectRef) {
88
- console.error(chalk.red('❌ Project ID não encontrado'));
89
- console.log(chalk.yellow('💡 Use: npx smoonb functions push --project-ref <id>'));
90
- process.exit(1);
91
- }
92
79
 
93
- console.log(chalk.blue(`🚀 Fazendo deploy das Edge Functions para: ${projectRef}`));
80
+ console.log(chalk.blue(`🚀 Fazendo deploy das Edge Functions para: ${projectId}`));
94
81
 
95
- const { stdout, stderr } = await runCommand(
96
- `supabase functions deploy --project-ref ${projectRef}`,
97
- {
98
- env: {
99
- ...process.env,
100
- SUPABASE_ACCESS_TOKEN: config.supabase.serviceKey
101
- }
82
+ const { stdout, stderr } = await runCommand(
83
+ `supabase functions deploy --project-ref ${projectId}`,
84
+ {
85
+ env: {
86
+ ...process.env,
87
+ SUPABASE_ACCESS_TOKEN: config.supabase.serviceKey
102
88
  }
103
- );
104
-
105
- if (stderr && !stderr.includes('WARN')) {
106
- console.log(chalk.yellow(`⚠️ Avisos: ${stderr}`));
107
89
  }
90
+ );
108
91
 
109
- console.log(chalk.green('✅ Deploy concluído:'));
110
- console.log(stdout);
111
-
112
- } catch (error) {
113
- console.error(chalk.red(`❌ Erro no deploy: ${error.message}`));
114
- process.exit(1);
92
+ if (stderr && !stderr.includes('WARN')) {
93
+ console.log(chalk.yellow(`⚠️ Avisos: ${stderr}`));
115
94
  }
116
- });
117
-
118
- // Subcomando para pull de functions
119
- functionsCommand
120
- .command('pull')
121
- .description('Baixar Edge Functions do projeto remoto')
122
- .option('--project-ref <ref>', 'ID do projeto Supabase')
123
- .action(async (options) => {
124
- showBetaBanner();
125
-
126
- try {
127
- const config = await readConfig();
128
- const projectRef = options.projectRef || config.supabase.projectId;
129
-
130
- if (!projectRef) {
131
- console.error(chalk.red('❌ Project ID não encontrado'));
132
- console.log(chalk.yellow('💡 Use: npx smoonb functions pull --project-ref <id>'));
133
- process.exit(1);
134
- }
135
95
 
136
- console.log(chalk.yellow('⚠️ Pull de Edge Functions não é oficialmente suportado pelo Supabase CLI'));
137
- console.log(chalk.yellow('💡 Para baixar código das functions remotas:'));
138
- console.log(chalk.gray(' 1. Use o Dashboard do Supabase'));
139
- console.log(chalk.gray(' 2. Ou clone o código do seu repositório Git'));
140
- console.log(chalk.gray(' 3. Ou use a API do Supabase diretamente'));
141
- console.log(chalk.blue('\n📚 Documentação: https://supabase.com/docs/guides/functions'));
96
+ console.log(chalk.green(' Deploy concluído:'));
97
+ console.log(stdout);
98
+
99
+ } catch (error) {
100
+ console.error(chalk.red(`❌ Erro no deploy: ${error.message}`));
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ // Função para pull de functions
106
+ async function pullFunctions(projectRef) {
107
+ try {
108
+ const config = await readConfig();
109
+ const projectId = projectRef || config.supabase.projectId;
142
110
 
143
- } catch (error) {
144
- console.error(chalk.red(`❌ Erro: ${error.message}`));
111
+ if (!projectId) {
112
+ console.error(chalk.red('❌ Project ID não encontrado'));
113
+ console.log(chalk.yellow('💡 Use: npx smoonb functions pull --project-ref <id>'));
145
114
  process.exit(1);
146
115
  }
147
- });
148
116
 
149
- module.exports = functionsCommand;
117
+ console.log(chalk.yellow('⚠️ Pull de Edge Functions não é oficialmente suportado pelo Supabase CLI'));
118
+ console.log(chalk.yellow('💡 Para baixar código das functions remotas:'));
119
+ console.log(chalk.gray(' 1. Use o Dashboard do Supabase'));
120
+ console.log(chalk.gray(' 2. Ou clone o código do seu repositório Git'));
121
+ console.log(chalk.gray(' 3. Ou use a API do Supabase diretamente'));
122
+ console.log(chalk.blue('\n📚 Documentação: https://supabase.com/docs/guides/functions'));
123
+
124
+ } catch (error) {
125
+ console.error(chalk.red(`❌ Erro: ${error.message}`));
126
+ process.exit(1);
127
+ }
128
+ }
129
+
130
+ // Exportar funções auxiliares para uso futuro
131
+ module.exports.list = listFunctions;
132
+ module.exports.push = pushFunctions;
133
+ module.exports.pull = pullFunctions;
@@ -1,91 +1,182 @@
1
- const { Command } = require('commander');
2
1
  const chalk = require('chalk');
3
2
  const path = require('path');
4
3
  const fs = require('fs');
4
+ const inquirer = require('inquirer');
5
5
  const { ensureBin, runCommand } = require('../utils/cli');
6
6
  const { readConfig, validateFor } = require('../utils/config');
7
7
  const { showBetaBanner } = require('../index');
8
8
 
9
- const restoreCommand = new Command('restore')
10
- .description('Restaurar backup do projeto Supabase usando psql')
11
- .option('-b, --backup-dir <dir>', 'Diretório do backup')
12
- .option('--db-url <url>', 'URL da database de destino (override)')
13
- .action(async (options) => {
14
- showBetaBanner();
9
+ // Exportar FUNÇÃO em vez de objeto Command
10
+ module.exports = async (options) => {
11
+ showBetaBanner();
12
+
13
+ try {
14
+ // Verificar se psql está disponível
15
+ const psqlPath = await ensureBin('psql');
16
+ if (!psqlPath) {
17
+ console.error(chalk.red('❌ psql não encontrado'));
18
+ console.log(chalk.yellow('💡 Instale PostgreSQL:'));
19
+ console.log(chalk.yellow(' https://www.postgresql.org/download/'));
20
+ process.exit(1);
21
+ }
22
+
23
+ // Carregar configuração
24
+ const config = await readConfig();
25
+ validateFor(config, 'restore');
26
+
27
+ // Resolver URL da database
28
+ const databaseUrl = options.dbUrl || config.supabase.databaseUrl;
29
+ if (!databaseUrl) {
30
+ console.error(chalk.red('❌ databaseUrl não configurada'));
31
+ console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc ou use --db-url'));
32
+ process.exit(1);
33
+ }
34
+
35
+ console.log(chalk.blue(`🔍 Procurando backups em: ${config.backup.outputDir || './backups'}`));
36
+
37
+ // Listar backups disponíveis
38
+ const backups = await listAvailableBackups(config.backup.outputDir || './backups');
15
39
 
16
- try {
17
- // Verificar se psql está disponível
18
- const psqlPath = await ensureBin('psql');
19
- if (!psqlPath) {
20
- console.error(chalk.red('❌ psql não encontrado'));
21
- console.log(chalk.yellow('💡 Instale PostgreSQL:'));
22
- console.log(chalk.yellow(' https://www.postgresql.org/download/'));
23
- process.exit(1);
24
- }
40
+ if (backups.length === 0) {
41
+ console.error(chalk.red('❌ Nenhum backup encontrado'));
42
+ console.log(chalk.yellow('💡 Execute primeiro: npx smoonb backup'));
43
+ process.exit(1);
44
+ }
25
45
 
26
- // Carregar configuração
27
- const config = await readConfig();
28
- validateFor(config, 'restore');
46
+ // Seleção interativa do backup
47
+ const selectedBackup = await selectBackup(backups);
48
+
49
+ console.log(chalk.blue(`🚀 Iniciando restauração do backup: ${selectedBackup.name}`));
50
+ console.log(chalk.blue(`🎯 Database destino: ${databaseUrl.replace(/:[^:]*@/, ':***@')}`));
29
51
 
30
- // Resolver URL da database
31
- const databaseUrl = options.dbUrl || config.supabase.databaseUrl;
32
- if (!databaseUrl) {
33
- console.error(chalk.red('❌ databaseUrl não configurada'));
34
- console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc ou use --db-url'));
35
- process.exit(1);
36
- }
52
+ // Verificar se é clean restore
53
+ if (config.restore.cleanRestore) {
54
+ await checkCleanRestore(databaseUrl);
55
+ }
37
56
 
38
- // Resolver diretório do backup
39
- let backupDir = options.backupDir;
40
- if (!backupDir) {
41
- // Procurar backup mais recente
42
- const backupsDir = config.backup.outputDir || './backups';
43
- if (fs.existsSync(backupsDir)) {
44
- const backups = fs.readdirSync(backupsDir)
45
- .filter(dir => dir.startsWith('backup-'))
46
- .sort()
47
- .reverse();
48
-
49
- if (backups.length > 0) {
50
- backupDir = path.join(backupsDir, backups[0]);
51
- console.log(chalk.blue(`📁 Usando backup mais recente: ${backups[0]}`));
52
- }
53
- }
54
- }
57
+ // Executar restauração
58
+ await performRestore(selectedBackup.path, databaseUrl);
55
59
 
56
- if (!backupDir || !fs.existsSync(backupDir)) {
57
- console.error(chalk.red('❌ Diretório de backup não encontrado'));
58
- console.log(chalk.yellow('💡 Use: npx smoonb restore --backup-dir <caminho>'));
59
- process.exit(1);
60
- }
60
+ // Verificação pós-restore
61
+ if (config.restore.verifyAfterRestore) {
62
+ console.log(chalk.blue('\n🔍 Executando verificação pós-restore...'));
63
+ console.log(chalk.yellow('💡 Execute manualmente: npx smoonb check'));
64
+ }
61
65
 
62
- console.log(chalk.blue(`🚀 Iniciando restauração do backup: ${path.basename(backupDir)}`));
63
- console.log(chalk.blue(`🎯 Database destino: ${databaseUrl.replace(/:[^:]*@/, ':***@')}`));
66
+ console.log(chalk.green('\n🎉 Restauração concluída com sucesso!'));
64
67
 
65
- // Verificar se é clean restore
66
- if (config.restore.cleanRestore) {
67
- await checkCleanRestore(databaseUrl);
68
- }
68
+ } catch (error) {
69
+ console.error(chalk.red(`❌ Erro na restauração: ${error.message}`));
70
+ process.exit(1);
71
+ }
72
+ };
73
+
74
+ // Listar backups disponíveis
75
+ async function listAvailableBackups(backupsDir) {
76
+ if (!fs.existsSync(backupsDir)) {
77
+ return [];
78
+ }
69
79
 
70
- // Executar restauração
71
- await performRestore(backupDir, databaseUrl);
80
+ const items = fs.readdirSync(backupsDir, { withFileTypes: true });
81
+ const backups = [];
72
82
 
73
- // Verificação pós-restore
74
- if (config.restore.verifyAfterRestore) {
75
- console.log(chalk.blue('\n🔍 Executando verificação pós-restore...'));
76
- // TODO: Implementar verificação automática
77
- console.log(chalk.yellow('⚠️ Verificação automática não implementada ainda'));
78
- console.log(chalk.yellow('💡 Execute manualmente: npx smoonb check'));
83
+ for (const item of items) {
84
+ if (item.isDirectory() && item.name.startsWith('backup-')) {
85
+ const backupPath = path.join(backupsDir, item.name);
86
+ const manifestPath = path.join(backupPath, 'backup-manifest.json');
87
+
88
+ let manifest = null;
89
+ if (fs.existsSync(manifestPath)) {
90
+ try {
91
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
92
+ } catch (error) {
93
+ console.warn(chalk.yellow(`⚠️ Erro ao ler manifesto: ${item.name}`));
94
+ }
79
95
  }
80
96
 
81
- console.log(chalk.green('\n🎉 Restauração concluída com sucesso!'));
97
+ const stats = fs.statSync(backupPath);
98
+
99
+ backups.push({
100
+ name: item.name,
101
+ path: backupPath,
102
+ created: manifest?.created_at || stats.birthtime.toISOString(),
103
+ projectId: manifest?.project_id || 'Desconhecido',
104
+ size: getDirectorySize(backupPath),
105
+ manifest: manifest
106
+ });
107
+ }
108
+ }
109
+
110
+ // Ordenar por data de criação (mais recente primeiro)
111
+ return backups.sort((a, b) => new Date(b.created) - new Date(a.created));
112
+ }
82
113
 
83
- } catch (error) {
84
- console.error(chalk.red(`❌ Erro na restauração: ${error.message}`));
85
- process.exit(1);
114
+ // Calcular tamanho do diretório
115
+ function getDirectorySize(dirPath) {
116
+ let totalSize = 0;
117
+
118
+ function calculateSize(itemPath) {
119
+ const stats = fs.statSync(itemPath);
120
+
121
+ if (stats.isDirectory()) {
122
+ const items = fs.readdirSync(itemPath);
123
+ for (const item of items) {
124
+ calculateSize(path.join(itemPath, item));
125
+ }
126
+ } else {
127
+ totalSize += stats.size;
86
128
  }
129
+ }
130
+
131
+ try {
132
+ calculateSize(dirPath);
133
+ } catch (error) {
134
+ // Ignorar erros de acesso
135
+ }
136
+
137
+ return formatBytes(totalSize);
138
+ }
139
+
140
+ // Formatar bytes em formato legível
141
+ function formatBytes(bytes) {
142
+ if (bytes === 0) return '0 Bytes';
143
+
144
+ const k = 1024;
145
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
146
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
147
+
148
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
149
+ }
150
+
151
+ // Seleção interativa do backup
152
+ async function selectBackup(backups) {
153
+ console.log(chalk.blue('\n📋 Backups disponíveis:'));
154
+ console.log(chalk.blue('═'.repeat(80)));
155
+
156
+ const choices = backups.map((backup, index) => {
157
+ const date = new Date(backup.created).toLocaleString('pt-BR');
158
+ const projectInfo = backup.projectId !== 'Desconhecido' ? ` (${backup.projectId})` : '';
159
+
160
+ return {
161
+ name: `${index + 1}. ${backup.name}${projectInfo}\n 📅 ${date} | 📦 ${backup.size}`,
162
+ value: backup,
163
+ short: backup.name
164
+ };
87
165
  });
88
166
 
167
+ const { selectedBackup } = await inquirer.prompt([
168
+ {
169
+ type: 'list',
170
+ name: 'selectedBackup',
171
+ message: 'Selecione o backup para restaurar:',
172
+ choices: choices,
173
+ pageSize: 10
174
+ }
175
+ ]);
176
+
177
+ return selectedBackup;
178
+ }
179
+
89
180
  // Verificar se é possível fazer clean restore
90
181
  async function checkCleanRestore(databaseUrl) {
91
182
  try {
@@ -158,6 +249,4 @@ async function performRestore(backupDir, databaseUrl) {
158
249
  throw new Error(`Falha ao executar ${sqlFile}: ${error.message}`);
159
250
  }
160
251
  }
161
- }
162
-
163
- module.exports = restoreCommand;
252
+ }
@@ -1,5 +1,5 @@
1
1
  const { createClient } = require('@supabase/supabase-js');
2
- const { runCommand } = require('./cli');
2
+ const { runCommand } = require('../utils/cli');
3
3
 
4
4
  /**
5
5
  * Serviço de introspecção do banco de dados Supabase