smoonb 0.0.7 → 0.0.9

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.
@@ -1,286 +1,177 @@
1
- /**
2
- * Comando de backup completo do projeto Supabase
3
- * Implementação técnica real baseada em pesquisa extensiva
4
- */
5
-
1
+ const { Command } = require('commander');
6
2
  const chalk = require('chalk');
7
- const { execSync } = require('child_process');
8
- const fs = require('fs');
9
3
  const path = require('path');
10
- const { createClient } = require('@supabase/supabase-js');
11
- const { getProjectId, getDatabaseUrl, findPgDumpPath, loadConfig } = require('../utils/supabase');
4
+ const { ensureBin, runCommand } = require('../utils/cli');
5
+ const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
6
+ const { sha256 } = require('../utils/hash');
7
+ const { readConfig, validateFor } = require('../utils/config');
8
+ const { IntrospectionService } = require('../services/introspect');
9
+ const { showBetaBanner } = require('../index');
10
+
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
+ }
12
27
 
13
- /**
14
- * Backup completo do projeto Supabase
15
- * Resolve o problema: ferramentas existentes só fazem backup da database
16
- */
17
- async function backupCommand(options) {
18
- console.log(chalk.red.bold('🚀 smoonb - EXPERIMENTAL VERSION'));
19
- console.log(chalk.red.bold('⚠️ VERSÃO EXPERIMENTAL - NUNCA TESTADA EM PRODUÇÃO!'));
20
- console.log(chalk.red.bold('🚨 USE POR SUA CONTA E RISCO - Pode causar perda de dados!'));
21
- console.log(chalk.red.bold('❌ NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados!\n'));
22
-
23
- console.log(chalk.cyan.bold('🚀 Iniciando backup COMPLETO do projeto Supabase...\n'));
28
+ // Carregar e validar configuração
29
+ const config = await readConfig();
30
+ validateFor(config, 'backup');
24
31
 
25
- try {
26
- // Obter projectId (da opção ou da configuração)
27
- const projectId = options.projectId || getProjectId();
28
-
29
- if (!projectId) {
30
- console.error(chalk.red.bold('❌ Erro: Project ID não encontrado'));
31
- console.log(chalk.yellow('💡 Opções:'));
32
- console.log(chalk.gray(' 1. Use: npx smoonb backup --project-id <seu-project-id>'));
33
- console.log(chalk.gray(' 2. Configure: npx smoonb config --init'));
34
- console.log(chalk.gray(' 3. Ou defina SUPABASE_PROJECT_ID no ambiente'));
35
- console.log(chalk.gray(' 4. Ou edite .smoonbrc e configure o projectId'));
36
- console.log(chalk.gray(' 5. Substitua "your-project-id-here" por seu ID real'));
37
- process.exit(1);
38
- }
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
+ }
39
38
 
40
- console.log(chalk.blue('🆔 Project ID:'), projectId);
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);
41
46
 
42
- // Obter diretório de backup da configuração ou usar opção
43
- const config = loadConfig();
44
- const outputDir = config?.backup?.outputDir || options.output;
45
-
46
- // Criar diretório de backup com timestamp
47
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
48
- const backupDir = path.resolve(outputDir, `backup-${timestamp}`);
49
- await fs.promises.mkdir(backupDir, { recursive: true });
47
+ console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
48
+ console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
50
49
 
51
- console.log(chalk.green('✅ Diretório de backup criado:'), backupDir);
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);
52
53
 
53
- // 1. BACKUP DA DATABASE (formato Custom - mais confiável)
54
- console.log(chalk.blue.bold('\n📊 1/5 - Backup da Database PostgreSQL...'));
55
- const dbBackupFile = await backupDatabase(projectId, backupDir);
56
- if (dbBackupFile) {
57
- console.log(chalk.green('✅ Database backupado:'), path.basename(dbBackupFile));
58
- } else {
59
- console.log(chalk.yellow('⚠️ Database não foi backupada (credenciais não configuradas)'));
60
- }
54
+ // 2. Gerar inventário real
55
+ console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
56
+ await generateInventory(config, backupDir);
61
57
 
62
- // 2. BACKUP DAS EDGE FUNCTIONS
63
- if (options.includeFunctions) {
64
- console.log(chalk.blue.bold('\n⚡ 2/5 - Backup das Edge Functions...'));
65
- const functionsDir = await backupEdgeFunctions(projectId, backupDir);
66
- console.log(chalk.green('✅ Edge Functions backupadas:'), functionsDir);
67
- }
58
+ // 3. Backup das Edge Functions locais
59
+ console.log(chalk.blue('\n⚡ 3/3 - Backup das Edge Functions locais...'));
60
+ await backupLocalFunctions(backupDir);
68
61
 
69
- // 3. BACKUP DAS CONFIGURAÇÕES DE AUTH
70
- if (options.includeAuth) {
71
- console.log(chalk.blue.bold('\n🔐 3/5 - Backup das configurações de Auth...'));
72
- const authConfig = await backupAuthSettings(projectId, backupDir);
73
- console.log(chalk.green('✅ Auth settings backupadas:'), authConfig);
74
- }
62
+ // Gerar manifesto do backup
63
+ await generateBackupManifest(config, backupDir);
75
64
 
76
- // 4. BACKUP DOS STORAGE OBJECTS
77
- if (options.includeStorage) {
78
- console.log(chalk.blue.bold('\n📁 4/5 - Backup dos Storage Objects...'));
79
- const storageBackup = await backupStorageObjects(projectId, backupDir);
80
- console.log(chalk.green('✅ Storage Objects backupados:'), storageBackup);
81
- }
65
+ console.log(chalk.green('\n🎉 Backup completo finalizado!'));
66
+ console.log(chalk.blue(`📁 Localização: ${backupDir}`));
82
67
 
83
- // 5. BACKUP DAS CONFIGURAÇÕES DE REALTIME
84
- if (options.includeRealtime) {
85
- console.log(chalk.blue.bold('\n🔄 5/5 - Backup das configurações de Realtime...'));
86
- const realtimeConfig = await backupRealtimeSettings(projectId, backupDir);
87
- console.log(chalk.green('✅ Realtime settings backupadas:'), realtimeConfig);
68
+ } catch (error) {
69
+ console.error(chalk.red(`❌ Erro no backup: ${error.message}`));
70
+ process.exit(1);
88
71
  }
72
+ });
89
73
 
90
- // Criar arquivo de manifesto do backup
91
- const manifest = {
92
- timestamp: new Date().toISOString(),
93
- projectId: projectId,
94
- version: '0.1.0-beta',
95
- components: {
96
- database: !!dbBackupFile,
97
- functions: options.includeFunctions,
98
- auth: options.includeAuth,
99
- storage: options.includeStorage,
100
- realtime: options.includeRealtime
101
- },
102
- files: {
103
- database: dbBackupFile ? path.basename(dbBackupFile) : null,
104
- functions: options.includeFunctions ? 'functions/' : null,
105
- auth: options.includeAuth ? 'auth-config.json' : null,
106
- storage: options.includeStorage ? 'storage/' : null,
107
- realtime: options.includeRealtime ? 'realtime-config.json' : null
108
- }
109
- };
110
-
111
- const manifestPath = path.join(backupDir, 'backup-manifest.json');
112
- await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
113
-
114
- console.log(chalk.green.bold('\n🎉 BACKUP COMPLETO FINALIZADO COM SUCESSO!'));
115
- console.log(chalk.blue('📁 Diretório:'), backupDir);
116
- console.log(chalk.blue('🆔 Project ID:'), options.projectId);
117
- console.log(chalk.blue('📋 Manifesto:'), 'backup-manifest.json');
118
- console.log(chalk.yellow('\n💡 Este backup inclui TODOS os componentes do Supabase!'));
119
- console.log(chalk.yellow('🔄 Use "smoonb restore" para restaurar em outro projeto'));
120
-
74
+ // Backup da database usando Supabase CLI
75
+ async function backupDatabaseWithSupabaseCLI(databaseUrl, backupDir) {
76
+ try {
77
+ console.log(chalk.blue(' - Exportando roles...'));
78
+ const { stdout: rolesOutput } = await runCommand(
79
+ `supabase db dump --db-url "${databaseUrl}" -f roles.sql --role-only`
80
+ );
81
+
82
+ console.log(chalk.blue(' - Exportando schema...'));
83
+ const { stdout: schemaOutput } = await runCommand(
84
+ `supabase db dump --db-url "${databaseUrl}" -f schema.sql`
85
+ );
86
+
87
+ console.log(chalk.blue(' - Exportando dados...'));
88
+ const { stdout: dataOutput } = await runCommand(
89
+ `supabase db dump --db-url "${databaseUrl}" -f data.sql --use-copy --data-only`
90
+ );
91
+
92
+ console.log(chalk.green('✅ Database exportada com sucesso'));
121
93
  } catch (error) {
122
- console.error(chalk.red.bold('❌ Erro durante o backup:'), error.message);
123
- console.error(chalk.gray('Stack trace:'), error.stack);
124
- process.exit(1);
94
+ throw new Error(`Falha no backup da database: ${error.message}`);
125
95
  }
126
96
  }
127
97
 
128
- /**
129
- * Backup da database PostgreSQL usando pg_dump com formato Custom (-Fc)
130
- * Formato Custom é mais confiável para restauração
131
- */
132
- async function backupDatabase(projectId, outputDir) {
98
+ // Gerar inventário completo
99
+ async function generateInventory(config, backupDir) {
133
100
  try {
134
- // Obter URL de conexão da configuração
135
- const dbUrl = getDatabaseUrl(projectId);
136
-
137
- if (!dbUrl) {
138
- console.log(chalk.yellow('⚠️ Database URL não configurada'));
139
- console.log(chalk.gray(' - Configure DATABASE_URL no ambiente'));
140
- console.log(chalk.gray(' - Ou edite ~/.smoonbrc e configure databaseUrl'));
141
- console.log(chalk.gray(' - Ou use smoonb config --init'));
142
- return null;
143
- }
144
-
145
- // Verificar se a URL contém placeholder de senha
146
- if (dbUrl.includes('[password]')) {
147
- console.log(chalk.yellow('⚠️ Database URL contém placeholder [password]'));
148
- console.log(chalk.gray(' - Substitua [password] pela senha real da database'));
149
- console.log(chalk.gray(' - Ou configure DATABASE_URL completa no ambiente'));
150
- return null;
101
+ const introspection = new IntrospectionService(config);
102
+ const inventory = await introspection.generateFullInventory();
103
+
104
+ // Salvar inventário em arquivos separados
105
+ const inventoryDir = path.join(backupDir, 'inventory');
106
+ await ensureDir(inventoryDir);
107
+
108
+ for (const [component, data] of Object.entries(inventory.components)) {
109
+ const filePath = path.join(inventoryDir, `${component}.json`);
110
+ await writeJson(filePath, data);
151
111
  }
152
-
153
- // Encontrar caminho do pg_dump (detecção automática no Windows)
154
- const pgDumpPath = findPgDumpPath();
155
- console.log(chalk.gray(` - Usando pg_dump: ${pgDumpPath}`));
156
-
157
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
158
- const filename = `database-${timestamp}.dump`;
159
- const filepath = path.join(outputDir, filename);
160
-
161
- console.log(chalk.gray(' - Executando pg_dump com formato Custom (-Fc)...'));
162
-
163
- // Usar formato Custom (-Fc) para restauração mais segura
164
- const command = `"${pgDumpPath}" "${dbUrl}" -Fc -f "${filepath}"`;
165
- execSync(command, { stdio: 'pipe' });
166
-
167
- return filepath;
112
+
113
+ console.log(chalk.green('✅ Inventário completo gerado'));
168
114
  } catch (error) {
169
- console.log(chalk.yellow('⚠️ Backup da database falhou:'), error.message);
170
- console.log(chalk.gray(' - Verifique se DATABASE_URL está correta'));
171
- console.log(chalk.gray(' - Verifique se pg_dump está instalado'));
172
- console.log(chalk.gray(' - Verifique se as credenciais estão corretas'));
173
- console.log(chalk.gray(' - Configure pgDumpPath no .smoonbrc se necessário'));
174
- return null;
115
+ console.log(chalk.yellow(`⚠️ Erro ao gerar inventário: ${error.message}`));
175
116
  }
176
117
  }
177
118
 
178
- /**
179
- * Backup das Edge Functions via Supabase CLI
180
- */
181
- async function backupEdgeFunctions(projectId, outputDir) {
119
+ // Backup das Edge Functions locais
120
+ async function backupLocalFunctions(backupDir) {
121
+ const localFunctionsPath = 'supabase/functions';
122
+
182
123
  try {
183
- const functionsBackupDir = path.join(outputDir, 'functions');
184
- await fs.promises.mkdir(functionsBackupDir, { recursive: true });
185
-
186
- // Verificar se existe pasta supabase/functions no projeto atual
187
- if (fs.existsSync('supabase/functions')) {
188
- console.log(chalk.gray(' - Copiando código das Edge Functions...'));
189
-
190
- // Copiar código das functions (Windows compatible)
191
- const { execSync } = require('child_process');
192
- execSync(`xcopy "supabase\\functions\\*" "${functionsBackupDir}\\" /E /I /Y`, { stdio: 'pipe' });
124
+ const fs = require('fs');
125
+ if (fs.existsSync(localFunctionsPath)) {
126
+ const functionsBackupDir = path.join(backupDir, 'functions');
127
+ await copyDir(localFunctionsPath, functionsBackupDir);
128
+ console.log(chalk.green('✅ Edge Functions locais copiadas'));
193
129
  } else {
194
- console.log(chalk.gray(' - Nenhuma Edge Function local encontrada'));
195
-
196
- // Criar arquivo placeholder
197
- const placeholderPath = path.join(functionsBackupDir, 'README.md');
198
- await fs.promises.writeFile(placeholderPath,
199
- '# Edge Functions Backup\n\nNenhuma Edge Function local foi encontrada.\nUse o Supabase CLI para fazer backup das functions remotas.'
200
- );
130
+ console.log(chalk.yellow('⚠️ Diretório supabase/functions não encontrado'));
201
131
  }
202
-
203
- return functionsBackupDir;
204
132
  } catch (error) {
205
- console.log(chalk.yellow('⚠️ Backup das Edge Functions falhou:'), error.message);
206
- return null;
133
+ console.log(chalk.yellow(`⚠️ Erro ao copiar Edge Functions: ${error.message}`));
207
134
  }
208
135
  }
209
136
 
210
- /**
211
- * Backup das configurações de Auth
212
- */
213
- async function backupAuthSettings(projectId, outputDir) {
214
- try {
215
- // TODO: Implementar busca real via Supabase API
216
- const authConfig = {
217
- timestamp: new Date().toISOString(),
218
- projectId: projectId,
219
- providers: [],
220
- policies: [],
221
- settings: {}
222
- };
223
-
224
- const authConfigPath = path.join(outputDir, 'auth-config.json');
225
- await fs.promises.writeFile(authConfigPath, JSON.stringify(authConfig, null, 2));
226
-
227
- console.log(chalk.gray(' - Configurações de Auth exportadas'));
228
- return authConfigPath;
229
- } catch (error) {
230
- console.log(chalk.yellow('⚠️ Backup das configurações de Auth falhou:'), error.message);
231
- return null;
137
+ // Gerar manifesto do backup
138
+ async function generateBackupManifest(config, backupDir) {
139
+ const manifest = {
140
+ created_at: new Date().toISOString(),
141
+ project_id: config.supabase.projectId,
142
+ smoonb_version: require('../../package.json').version,
143
+ backup_type: 'complete',
144
+ files: {
145
+ roles: 'roles.sql',
146
+ schema: 'schema.sql',
147
+ data: 'data.sql'
148
+ },
149
+ hashes: {},
150
+ inventory: {}
151
+ };
152
+
153
+ // Calcular hashes dos arquivos SQL
154
+ const fs = require('fs');
155
+ for (const [type, filename] of Object.entries(manifest.files)) {
156
+ const filePath = path.join(backupDir, filename);
157
+ if (fs.existsSync(filePath)) {
158
+ manifest.hashes[type] = await sha256(filePath);
159
+ }
232
160
  }
233
- }
234
161
 
235
- /**
236
- * Backup dos Storage Objects
237
- */
238
- async function backupStorageObjects(projectId, outputDir) {
239
- try {
240
- const storageBackupDir = path.join(outputDir, 'storage');
241
- await fs.promises.mkdir(storageBackupDir, { recursive: true });
242
-
243
- // TODO: Implementar backup real dos objetos de storage
244
- const storageConfig = {
245
- timestamp: new Date().toISOString(),
246
- projectId: projectId,
247
- buckets: [],
248
- objects: []
249
- };
250
-
251
- const storageConfigPath = path.join(storageBackupDir, 'storage-config.json');
252
- await fs.promises.writeFile(storageConfigPath, JSON.stringify(storageConfig, null, 2));
253
-
254
- console.log(chalk.gray(' - Configurações de Storage exportadas'));
255
- return storageBackupDir;
256
- } catch (error) {
257
- console.log(chalk.yellow('⚠️ Backup dos Storage Objects falhou:'), error.message);
258
- return null;
162
+ // Adicionar referências ao inventário
163
+ const inventoryDir = path.join(backupDir, 'inventory');
164
+ if (fs.existsSync(inventoryDir)) {
165
+ const inventoryFiles = fs.readdirSync(inventoryDir);
166
+ manifest.inventory = inventoryFiles.reduce((acc, file) => {
167
+ const component = path.basename(file, '.json');
168
+ acc[component] = `inventory/${file}`;
169
+ return acc;
170
+ }, {});
259
171
  }
260
- }
261
172
 
262
- /**
263
- * Backup das configurações de Realtime
264
- */
265
- async function backupRealtimeSettings(projectId, outputDir) {
266
- try {
267
- const realtimeConfig = {
268
- timestamp: new Date().toISOString(),
269
- projectId: projectId,
270
- enabled: false,
271
- channels: [],
272
- settings: {}
273
- };
274
-
275
- const realtimeConfigPath = path.join(outputDir, 'realtime-config.json');
276
- await fs.promises.writeFile(realtimeConfigPath, JSON.stringify(realtimeConfig, null, 2));
277
-
278
- console.log(chalk.gray(' - Configurações de Realtime exportadas'));
279
- return realtimeConfigPath;
280
- } catch (error) {
281
- console.log(chalk.yellow('⚠️ Backup das configurações de Realtime falhou:'), error.message);
282
- return null;
283
- }
173
+ const manifestPath = path.join(backupDir, 'backup-manifest.json');
174
+ await writeJson(manifestPath, manifest);
284
175
  }
285
176
 
286
- module.exports = backupCommand;
177
+ module.exports = backupCommand;