smoonb 0.0.6 → 0.0.8

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.
Files changed (35) hide show
  1. package/.smoonbrc +7 -6
  2. package/.smoonbrc.example +2 -1
  3. package/README.md +164 -164
  4. package/backups/backup-2025-10-17T19-52-20-211Z/auth-config.json +7 -0
  5. package/backups/backup-2025-10-17T19-52-20-211Z/backup-manifest.json +19 -0
  6. package/backups/backup-2025-10-17T19-52-20-211Z/database-2025-10-17T19-52-20-215Z.dump +0 -0
  7. package/backups/backup-2025-10-17T19-52-20-211Z/functions/README.md +4 -0
  8. package/backups/backup-2025-10-17T19-52-20-211Z/realtime-config.json +7 -0
  9. package/backups/backup-2025-10-17T19-52-20-211Z/storage/storage-config.json +6 -0
  10. package/backups/backup-2025-10-17T20-38-13-188Z/auth-config.json +7 -0
  11. package/backups/backup-2025-10-17T20-38-13-188Z/backup-manifest.json +19 -0
  12. package/backups/backup-2025-10-17T20-38-13-188Z/database-2025-10-17T20-38-13-194Z.dump +0 -0
  13. package/backups/backup-2025-10-17T20-38-13-188Z/functions/README.md +4 -0
  14. package/backups/backup-2025-10-17T20-38-13-188Z/realtime-config.json +7 -0
  15. package/backups/backup-2025-10-17T20-38-13-188Z/storage/storage-config.json +6 -0
  16. package/bin/smoonb.js +16 -32
  17. package/package.json +1 -1
  18. package/src/commands/backup.js +140 -239
  19. package/src/commands/check.js +209 -349
  20. package/src/commands/config.js +78 -77
  21. package/src/commands/functions.js +123 -349
  22. package/src/commands/restore.js +122 -294
  23. package/src/index.js +12 -21
  24. package/src/services/introspect.js +299 -0
  25. package/src/utils/cli.js +87 -0
  26. package/src/utils/config.js +140 -0
  27. package/src/utils/fsx.js +110 -0
  28. package/src/utils/hash.js +40 -0
  29. package/src/utils/supabase.js +447 -387
  30. package/src/commands/secrets.js +0 -361
  31. /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/auth-config.json +0 -0
  32. /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/backup-manifest.json +0 -0
  33. /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/functions/README.md +0 -0
  34. /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/realtime-config.json +0 -0
  35. /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/storage/storage-config.json +0 -0
@@ -1,276 +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 } = 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: smoonb backup --project-id <seu-project-id>'));
33
- console.log(chalk.gray(' 2. Configure: 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
- process.exit(1);
37
- }
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
38
 
39
- 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);
40
46
 
41
- // Criar diretório de backup com timestamp
42
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
43
- const backupDir = path.resolve(options.output, `backup-${timestamp}`);
44
- 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}`));
45
49
 
46
- 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);
47
53
 
48
- // 1. BACKUP DA DATABASE (formato Custom - mais confiável)
49
- console.log(chalk.blue.bold('\n📊 1/5 - Backup da Database PostgreSQL...'));
50
- const dbBackupFile = await backupDatabase(projectId, backupDir);
51
- if (dbBackupFile) {
52
- console.log(chalk.green('✅ Database backupado:'), path.basename(dbBackupFile));
53
- } else {
54
- console.log(chalk.yellow('⚠️ Database não foi backupada (credenciais não configuradas)'));
55
- }
54
+ // 2. Gerar inventário real
55
+ console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
56
+ await generateInventory(config, backupDir);
56
57
 
57
- // 2. BACKUP DAS EDGE FUNCTIONS
58
- if (options.includeFunctions) {
59
- console.log(chalk.blue.bold('\n⚡ 2/5 - Backup das Edge Functions...'));
60
- const functionsDir = await backupEdgeFunctions(projectId, backupDir);
61
- console.log(chalk.green('✅ Edge Functions backupadas:'), functionsDir);
62
- }
58
+ // 3. Backup das Edge Functions locais
59
+ console.log(chalk.blue('\n⚡ 3/3 - Backup das Edge Functions locais...'));
60
+ await backupLocalFunctions(backupDir);
63
61
 
64
- // 3. BACKUP DAS CONFIGURAÇÕES DE AUTH
65
- if (options.includeAuth) {
66
- console.log(chalk.blue.bold('\n🔐 3/5 - Backup das configurações de Auth...'));
67
- const authConfig = await backupAuthSettings(projectId, backupDir);
68
- console.log(chalk.green('✅ Auth settings backupadas:'), authConfig);
69
- }
62
+ // Gerar manifesto do backup
63
+ await generateBackupManifest(config, backupDir);
70
64
 
71
- // 4. BACKUP DOS STORAGE OBJECTS
72
- if (options.includeStorage) {
73
- console.log(chalk.blue.bold('\n📁 4/5 - Backup dos Storage Objects...'));
74
- const storageBackup = await backupStorageObjects(projectId, backupDir);
75
- console.log(chalk.green('✅ Storage Objects backupados:'), storageBackup);
76
- }
65
+ console.log(chalk.green('\n🎉 Backup completo finalizado!'));
66
+ console.log(chalk.blue(`📁 Localização: ${backupDir}`));
77
67
 
78
- // 5. BACKUP DAS CONFIGURAÇÕES DE REALTIME
79
- if (options.includeRealtime) {
80
- console.log(chalk.blue.bold('\n🔄 5/5 - Backup das configurações de Realtime...'));
81
- const realtimeConfig = await backupRealtimeSettings(projectId, backupDir);
82
- 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);
83
71
  }
72
+ });
84
73
 
85
- // Criar arquivo de manifesto do backup
86
- const manifest = {
87
- timestamp: new Date().toISOString(),
88
- projectId: projectId,
89
- version: '0.1.0-beta',
90
- components: {
91
- database: !!dbBackupFile,
92
- functions: options.includeFunctions,
93
- auth: options.includeAuth,
94
- storage: options.includeStorage,
95
- realtime: options.includeRealtime
96
- },
97
- files: {
98
- database: dbBackupFile ? path.basename(dbBackupFile) : null,
99
- functions: options.includeFunctions ? 'functions/' : null,
100
- auth: options.includeAuth ? 'auth-config.json' : null,
101
- storage: options.includeStorage ? 'storage/' : null,
102
- realtime: options.includeRealtime ? 'realtime-config.json' : null
103
- }
104
- };
105
-
106
- const manifestPath = path.join(backupDir, 'backup-manifest.json');
107
- await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
108
-
109
- console.log(chalk.green.bold('\n🎉 BACKUP COMPLETO FINALIZADO COM SUCESSO!'));
110
- console.log(chalk.blue('📁 Diretório:'), backupDir);
111
- console.log(chalk.blue('🆔 Project ID:'), options.projectId);
112
- console.log(chalk.blue('📋 Manifesto:'), 'backup-manifest.json');
113
- console.log(chalk.yellow('\n💡 Este backup inclui TODOS os componentes do Supabase!'));
114
- console.log(chalk.yellow('🔄 Use "smoonb restore" para restaurar em outro projeto'));
115
-
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'));
116
93
  } catch (error) {
117
- console.error(chalk.red.bold('❌ Erro durante o backup:'), error.message);
118
- console.error(chalk.gray('Stack trace:'), error.stack);
119
- process.exit(1);
94
+ throw new Error(`Falha no backup da database: ${error.message}`);
120
95
  }
121
96
  }
122
97
 
123
- /**
124
- * Backup da database PostgreSQL usando pg_dump com formato Custom (-Fc)
125
- * Formato Custom é mais confiável para restauração
126
- */
127
- async function backupDatabase(projectId, outputDir) {
98
+ // Gerar inventário completo
99
+ async function generateInventory(config, backupDir) {
128
100
  try {
129
- // Obter URL de conexão da configuração
130
- const dbUrl = getDatabaseUrl(projectId);
131
-
132
- if (!dbUrl) {
133
- console.log(chalk.yellow('⚠️ Database URL não configurada'));
134
- console.log(chalk.gray(' - Configure DATABASE_URL no ambiente'));
135
- console.log(chalk.gray(' - Ou edite ~/.smoonbrc e configure databaseUrl'));
136
- console.log(chalk.gray(' - Ou use smoonb config --init'));
137
- return null;
138
- }
139
-
140
- // Verificar se a URL contém placeholder de senha
141
- if (dbUrl.includes('[password]')) {
142
- console.log(chalk.yellow('⚠️ Database URL contém placeholder [password]'));
143
- console.log(chalk.gray(' - Substitua [password] pela senha real da database'));
144
- console.log(chalk.gray(' - Ou configure DATABASE_URL completa no ambiente'));
145
- 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);
146
111
  }
147
-
148
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
149
- const filename = `database-${timestamp}.dump`;
150
- const filepath = path.join(outputDir, filename);
151
-
152
- console.log(chalk.gray(' - Executando pg_dump com formato Custom (-Fc)...'));
153
-
154
- // Usar formato Custom (-Fc) para restauração mais segura
155
- const command = `pg_dump "${dbUrl}" -Fc -f "${filepath}"`;
156
- execSync(command, { stdio: 'pipe' });
157
-
158
- return filepath;
112
+
113
+ console.log(chalk.green('✅ Inventário completo gerado'));
159
114
  } catch (error) {
160
- console.log(chalk.yellow('⚠️ Backup da database falhou:'), error.message);
161
- console.log(chalk.gray(' - Verifique se DATABASE_URL está correta'));
162
- console.log(chalk.gray(' - Verifique se pg_dump está instalado'));
163
- console.log(chalk.gray(' - Verifique se as credenciais estão corretas'));
164
- return null;
115
+ console.log(chalk.yellow(`⚠️ Erro ao gerar inventário: ${error.message}`));
165
116
  }
166
117
  }
167
118
 
168
- /**
169
- * Backup das Edge Functions via Supabase CLI
170
- */
171
- async function backupEdgeFunctions(projectId, outputDir) {
119
+ // Backup das Edge Functions locais
120
+ async function backupLocalFunctions(backupDir) {
121
+ const localFunctionsPath = 'supabase/functions';
122
+
172
123
  try {
173
- const functionsBackupDir = path.join(outputDir, 'functions');
174
- await fs.promises.mkdir(functionsBackupDir, { recursive: true });
175
-
176
- // Verificar se existe pasta supabase/functions no projeto atual
177
- if (fs.existsSync('supabase/functions')) {
178
- console.log(chalk.gray(' - Copiando código das Edge Functions...'));
179
-
180
- // Copiar código das functions (Windows compatible)
181
- const { execSync } = require('child_process');
182
- 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'));
183
129
  } else {
184
- console.log(chalk.gray(' - Nenhuma Edge Function local encontrada'));
185
-
186
- // Criar arquivo placeholder
187
- const placeholderPath = path.join(functionsBackupDir, 'README.md');
188
- await fs.promises.writeFile(placeholderPath,
189
- '# Edge Functions Backup\n\nNenhuma Edge Function local foi encontrada.\nUse o Supabase CLI para fazer backup das functions remotas.'
190
- );
130
+ console.log(chalk.yellow('⚠️ Diretório supabase/functions não encontrado'));
191
131
  }
192
-
193
- return functionsBackupDir;
194
132
  } catch (error) {
195
- console.log(chalk.yellow('⚠️ Backup das Edge Functions falhou:'), error.message);
196
- return null;
133
+ console.log(chalk.yellow(`⚠️ Erro ao copiar Edge Functions: ${error.message}`));
197
134
  }
198
135
  }
199
136
 
200
- /**
201
- * Backup das configurações de Auth
202
- */
203
- async function backupAuthSettings(projectId, outputDir) {
204
- try {
205
- // TODO: Implementar busca real via Supabase API
206
- const authConfig = {
207
- timestamp: new Date().toISOString(),
208
- projectId: projectId,
209
- providers: [],
210
- policies: [],
211
- settings: {}
212
- };
213
-
214
- const authConfigPath = path.join(outputDir, 'auth-config.json');
215
- await fs.promises.writeFile(authConfigPath, JSON.stringify(authConfig, null, 2));
216
-
217
- console.log(chalk.gray(' - Configurações de Auth exportadas'));
218
- return authConfigPath;
219
- } catch (error) {
220
- console.log(chalk.yellow('⚠️ Backup das configurações de Auth falhou:'), error.message);
221
- 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
+ }
222
160
  }
223
- }
224
161
 
225
- /**
226
- * Backup dos Storage Objects
227
- */
228
- async function backupStorageObjects(projectId, outputDir) {
229
- try {
230
- const storageBackupDir = path.join(outputDir, 'storage');
231
- await fs.promises.mkdir(storageBackupDir, { recursive: true });
232
-
233
- // TODO: Implementar backup real dos objetos de storage
234
- const storageConfig = {
235
- timestamp: new Date().toISOString(),
236
- projectId: projectId,
237
- buckets: [],
238
- objects: []
239
- };
240
-
241
- const storageConfigPath = path.join(storageBackupDir, 'storage-config.json');
242
- await fs.promises.writeFile(storageConfigPath, JSON.stringify(storageConfig, null, 2));
243
-
244
- console.log(chalk.gray(' - Configurações de Storage exportadas'));
245
- return storageBackupDir;
246
- } catch (error) {
247
- console.log(chalk.yellow('⚠️ Backup dos Storage Objects falhou:'), error.message);
248
- 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
+ }, {});
249
171
  }
250
- }
251
172
 
252
- /**
253
- * Backup das configurações de Realtime
254
- */
255
- async function backupRealtimeSettings(projectId, outputDir) {
256
- try {
257
- const realtimeConfig = {
258
- timestamp: new Date().toISOString(),
259
- projectId: projectId,
260
- enabled: false,
261
- channels: [],
262
- settings: {}
263
- };
264
-
265
- const realtimeConfigPath = path.join(outputDir, 'realtime-config.json');
266
- await fs.promises.writeFile(realtimeConfigPath, JSON.stringify(realtimeConfig, null, 2));
267
-
268
- console.log(chalk.gray(' - Configurações de Realtime exportadas'));
269
- return realtimeConfigPath;
270
- } catch (error) {
271
- console.log(chalk.yellow('⚠️ Backup das configurações de Realtime falhou:'), error.message);
272
- return null;
273
- }
173
+ const manifestPath = path.join(backupDir, 'backup-manifest.json');
174
+ await writeJson(manifestPath, manifest);
274
175
  }
275
176
 
276
- module.exports = backupCommand;
177
+ module.exports = backupCommand;