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.
- package/README.md +166 -169
- package/bin/smoonb.js +7 -24
- package/package.json +3 -3
- package/src/commands/backup.js +140 -249
- package/src/commands/check.js +210 -349
- package/src/commands/functions.js +123 -349
- package/src/commands/restore.js +203 -281
- package/src/index.js +12 -21
- package/src/services/introspect.js +299 -0
- package/src/utils/cli.js +87 -0
- package/src/utils/config.js +140 -0
- package/src/utils/fsx.js +110 -0
- package/src/utils/hash.js +40 -0
- package/src/commands/secrets.js +0 -361
package/src/commands/restore.js
CHANGED
|
@@ -1,335 +1,257 @@
|
|
|
1
|
-
|
|
2
|
-
* Comando de restauração completa do projeto Supabase
|
|
3
|
-
* Implementação técnica real com processo DROP→CREATE→RESTORE
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*/
|
|
16
|
-
async function restoreCommand(options) {
|
|
17
|
-
console.log(chalk.red.bold('🚀 smoonb - EXPERIMENTAL VERSION'));
|
|
18
|
-
console.log(chalk.red.bold('⚠️ VERSÃO EXPERIMENTAL - NUNCA TESTADA EM PRODUÇÃO!'));
|
|
19
|
-
console.log(chalk.red.bold('🚨 USE POR SUA CONTA E RISCO - Pode causar perda de dados!'));
|
|
20
|
-
console.log(chalk.red.bold('❌ NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados!\n'));
|
|
21
|
-
|
|
22
|
-
console.log(chalk.cyan.bold('🔄 Iniciando restauração COMPLETA do projeto Supabase...\n'));
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
const { ensureBin, runCommand } = require('../utils/cli');
|
|
7
|
+
const { readConfig, validateFor } = require('../utils/config');
|
|
8
|
+
const { showBetaBanner } = require('../index');
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
const restoreCommand = new Command('restore')
|
|
11
|
+
.description('Restaurar backup do projeto Supabase usando psql (modo interativo)')
|
|
12
|
+
.option('--db-url <url>', 'URL da database de destino (override)')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
showBetaBanner();
|
|
27
15
|
|
|
28
|
-
if (!projectId) {
|
|
29
|
-
console.error(chalk.red.bold('❌ Erro: Project ID não encontrado'));
|
|
30
|
-
console.log(chalk.yellow('💡 Opções:'));
|
|
31
|
-
console.log(chalk.gray(' 1. Use: smoonb restore --project-id <seu-project-id>'));
|
|
32
|
-
console.log(chalk.gray(' 2. Configure: smoonb config --init'));
|
|
33
|
-
console.log(chalk.gray(' 3. Ou defina SUPABASE_PROJECT_ID no ambiente'));
|
|
34
|
-
console.log(chalk.gray(' 4. Ou edite ~/.smoonbrc e configure o projectId'));
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
console.log(chalk.blue('🆔 Project ID:'), projectId);
|
|
39
|
-
|
|
40
|
-
if (!options.backupDir) {
|
|
41
|
-
console.error(chalk.red.bold('❌ Erro: Diretório de backup é obrigatório'));
|
|
42
|
-
console.log(chalk.yellow('💡 Use: smoonb restore --project-id <seu-project-id> --backup-dir <diretorio-backup>'));
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Verificar se o diretório de backup existe
|
|
47
|
-
const backupPath = path.resolve(options.backupDir);
|
|
48
16
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let manifest = null;
|
|
58
|
-
try {
|
|
59
|
-
const manifestContent = await fs.promises.readFile(manifestPath, 'utf8');
|
|
60
|
-
manifest = JSON.parse(manifestContent);
|
|
61
|
-
console.log(chalk.green('✅ Manifesto do backup encontrado'));
|
|
62
|
-
console.log(chalk.gray(' - Projeto origem:'), manifest.projectId);
|
|
63
|
-
console.log(chalk.gray(' - Data do backup:'), manifest.timestamp);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.log(chalk.yellow('⚠️ Manifesto do backup não encontrado, continuando...'));
|
|
66
|
-
}
|
|
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
|
+
}
|
|
67
25
|
|
|
68
|
-
|
|
26
|
+
// Carregar configuração
|
|
27
|
+
const config = await readConfig();
|
|
28
|
+
validateFor(config, 'restore');
|
|
69
29
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (dbResult.success) {
|
|
77
|
-
console.log(chalk.green('✅ Database restaurada com sucesso!'));
|
|
78
|
-
} else {
|
|
79
|
-
console.log(chalk.yellow('⚠️ Restauração da database falhou:'), dbResult.error);
|
|
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);
|
|
80
36
|
}
|
|
81
|
-
}
|
|
82
37
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const functionsResult = await restoreEdgeFunctions(functionsDir, options.projectId);
|
|
38
|
+
console.log(chalk.blue(`🔍 Procurando backups em: ${config.backup.outputDir || './backups'}`));
|
|
39
|
+
|
|
40
|
+
// Listar backups disponíveis
|
|
41
|
+
const backups = await listAvailableBackups(config.backup.outputDir || './backups');
|
|
88
42
|
|
|
89
|
-
if (
|
|
90
|
-
console.
|
|
91
|
-
|
|
92
|
-
|
|
43
|
+
if (backups.length === 0) {
|
|
44
|
+
console.error(chalk.red('❌ Nenhum backup encontrado'));
|
|
45
|
+
console.log(chalk.yellow('💡 Execute primeiro: npx smoonb backup'));
|
|
46
|
+
process.exit(1);
|
|
93
47
|
}
|
|
94
|
-
}
|
|
95
48
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
console.log(chalk.blue.bold('\n🔐 3/5 - Restauração das configurações de Auth...'));
|
|
99
|
-
const authConfigPath = path.join(backupPath, 'auth-config.json');
|
|
100
|
-
const authResult = await restoreAuthSettings(authConfigPath, options.projectId);
|
|
49
|
+
// Seleção interativa do backup
|
|
50
|
+
const selectedBackup = await selectBackup(backups);
|
|
101
51
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
52
|
+
console.log(chalk.blue(`🚀 Iniciando restauração do backup: ${selectedBackup.name}`));
|
|
53
|
+
console.log(chalk.blue(`🎯 Database destino: ${databaseUrl.replace(/:[^:]*@/, ':***@')}`));
|
|
54
|
+
|
|
55
|
+
// Verificar se é clean restore
|
|
56
|
+
if (config.restore.cleanRestore) {
|
|
57
|
+
await checkCleanRestore(databaseUrl);
|
|
106
58
|
}
|
|
107
|
-
}
|
|
108
59
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.log(chalk.green('✅ Storage Objects restaurados com sucesso!'));
|
|
117
|
-
} else {
|
|
118
|
-
console.log(chalk.yellow('⚠️ Restauração dos Storage Objects falhou:'), storageResult.error);
|
|
60
|
+
// Executar restauração
|
|
61
|
+
await performRestore(selectedBackup.path, databaseUrl);
|
|
62
|
+
|
|
63
|
+
// Verificação pós-restore
|
|
64
|
+
if (config.restore.verifyAfterRestore) {
|
|
65
|
+
console.log(chalk.blue('\n🔍 Executando verificação pós-restore...'));
|
|
66
|
+
console.log(chalk.yellow('💡 Execute manualmente: npx smoonb check'));
|
|
119
67
|
}
|
|
68
|
+
|
|
69
|
+
console.log(chalk.green('\n🎉 Restauração concluída com sucesso!'));
|
|
70
|
+
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red(`❌ Erro na restauração: ${error.message}`));
|
|
73
|
+
process.exit(1);
|
|
120
74
|
}
|
|
75
|
+
});
|
|
121
76
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
77
|
+
// Listar backups disponíveis
|
|
78
|
+
async function listAvailableBackups(backupsDir) {
|
|
79
|
+
if (!fs.existsSync(backupsDir)) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const items = fs.readdirSync(backupsDir, { withFileTypes: true });
|
|
84
|
+
const backups = [];
|
|
85
|
+
|
|
86
|
+
for (const item of items) {
|
|
87
|
+
if (item.isDirectory() && item.name.startsWith('backup-')) {
|
|
88
|
+
const backupPath = path.join(backupsDir, item.name);
|
|
89
|
+
const manifestPath = path.join(backupPath, 'backup-manifest.json');
|
|
127
90
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
91
|
+
let manifest = null;
|
|
92
|
+
if (fs.existsSync(manifestPath)) {
|
|
93
|
+
try {
|
|
94
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn(chalk.yellow(`⚠️ Erro ao ler manifesto: ${item.name}`));
|
|
97
|
+
}
|
|
132
98
|
}
|
|
133
|
-
}
|
|
134
99
|
|
|
135
|
-
|
|
136
|
-
if (options.verify) {
|
|
137
|
-
console.log(chalk.blue.bold('\n🔍 Verificação pós-restore...'));
|
|
138
|
-
const checkResult = await postRestoreVerification(options.projectId);
|
|
100
|
+
const stats = fs.statSync(backupPath);
|
|
139
101
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
102
|
+
backups.push({
|
|
103
|
+
name: item.name,
|
|
104
|
+
path: backupPath,
|
|
105
|
+
created: manifest?.created_at || stats.birthtime.toISOString(),
|
|
106
|
+
projectId: manifest?.project_id || 'Desconhecido',
|
|
107
|
+
size: getDirectorySize(backupPath),
|
|
108
|
+
manifest: manifest
|
|
109
|
+
});
|
|
145
110
|
}
|
|
146
|
-
|
|
147
|
-
console.log(chalk.green.bold('\n🎉 RESTAURAÇÃO COMPLETA FINALIZADA!'));
|
|
148
|
-
console.log(chalk.blue('📁 Backup:'), backupPath);
|
|
149
|
-
console.log(chalk.blue('🆔 Project ID:'), options.projectId);
|
|
150
|
-
console.log(chalk.yellow('\n💡 Use "smoonb check" para verificar a integridade do projeto restaurado'));
|
|
151
|
-
|
|
152
|
-
} catch (error) {
|
|
153
|
-
console.error(chalk.red.bold('❌ Erro durante a restauração:'), error.message);
|
|
154
|
-
console.error(chalk.gray('Stack trace:'), error.stack);
|
|
155
|
-
process.exit(1);
|
|
156
111
|
}
|
|
112
|
+
|
|
113
|
+
// Ordenar por data de criação (mais recente primeiro)
|
|
114
|
+
return backups.sort((a, b) => new Date(b.created) - new Date(a.created));
|
|
157
115
|
}
|
|
158
116
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Construir URL de conexão
|
|
166
|
-
const dbUrl = process.env.DATABASE_URL || `postgresql://postgres:[password]@db.${projectId}.supabase.co:5432/postgres`;
|
|
167
|
-
const dbName = new URL(dbUrl).pathname.slice(1);
|
|
168
|
-
const baseUrl = dbUrl.replace(`/${dbName}`, '');
|
|
169
|
-
|
|
170
|
-
console.log(chalk.gray('🔥 Processo de Restauração Limpa iniciado...'));
|
|
117
|
+
// Calcular tamanho do diretório
|
|
118
|
+
function getDirectorySize(dirPath) {
|
|
119
|
+
let totalSize = 0;
|
|
120
|
+
|
|
121
|
+
function calculateSize(itemPath) {
|
|
122
|
+
const stats = fs.statSync(itemPath);
|
|
171
123
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
124
|
+
if (stats.isDirectory()) {
|
|
125
|
+
const items = fs.readdirSync(itemPath);
|
|
126
|
+
for (const item of items) {
|
|
127
|
+
calculateSize(path.join(itemPath, item));
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
totalSize += stats.size;
|
|
179
131
|
}
|
|
180
|
-
|
|
181
|
-
// 2. CRIAR banco novo e vazio
|
|
182
|
-
console.log(chalk.gray(' - Step 2/3: Criando banco vazio...'));
|
|
183
|
-
const createCmd = `createdb "${baseUrl}/${dbName}"`;
|
|
184
|
-
execSync(createCmd, { stdio: 'pipe' });
|
|
185
|
-
|
|
186
|
-
// 3. RESTAURAR backup no banco vazio
|
|
187
|
-
console.log(chalk.gray(' - Step 3/3: Restaurando backup...'));
|
|
188
|
-
const restoreCmd = `pg_restore -d "${dbUrl}" --clean --if-exists --single-transaction "${backupFile}"`;
|
|
189
|
-
execSync(restoreCmd, { stdio: 'pipe' });
|
|
190
|
-
|
|
191
|
-
console.log(chalk.green('✅ Restauração da database concluída com sucesso!'));
|
|
192
|
-
return { success: true };
|
|
193
|
-
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.log(chalk.yellow('⚠️ Restauração da database falhou (credenciais não configuradas)'));
|
|
196
|
-
console.log(chalk.gray(' - Configure DATABASE_URL ou use smoonb config --init'));
|
|
197
|
-
return { success: false, error: error.message };
|
|
198
132
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Restauração das Edge Functions via Supabase CLI
|
|
203
|
-
*/
|
|
204
|
-
async function restoreEdgeFunctions(functionsDir, projectId) {
|
|
133
|
+
|
|
205
134
|
try {
|
|
206
|
-
|
|
207
|
-
console.log(chalk.gray(' - Nenhuma Edge Function encontrada no backup'));
|
|
208
|
-
return { success: true };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
console.log(chalk.gray(' - Deploy das Edge Functions via Supabase CLI...'));
|
|
212
|
-
|
|
213
|
-
// Verificar se Supabase CLI está instalado
|
|
214
|
-
try {
|
|
215
|
-
execSync('supabase --version', { stdio: 'pipe' });
|
|
216
|
-
} catch (error) {
|
|
217
|
-
console.log(chalk.yellow('⚠️ Supabase CLI não encontrado'));
|
|
218
|
-
console.log(chalk.gray(' - Instale: npm install -g supabase'));
|
|
219
|
-
return { success: false, error: 'Supabase CLI não encontrado' };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Deploy functions via Supabase CLI
|
|
223
|
-
const deployCmd = `supabase functions deploy --project-ref ${projectId}`;
|
|
224
|
-
execSync(deployCmd, { stdio: 'pipe' });
|
|
225
|
-
|
|
226
|
-
return { success: true };
|
|
135
|
+
calculateSize(dirPath);
|
|
227
136
|
} catch (error) {
|
|
228
|
-
|
|
137
|
+
// Ignorar erros de acesso
|
|
229
138
|
}
|
|
139
|
+
|
|
140
|
+
return formatBytes(totalSize);
|
|
230
141
|
}
|
|
231
142
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
console.log(chalk.gray(' - Restaurando configurações de Auth...'));
|
|
243
|
-
|
|
244
|
-
// TODO: Implementar restauração real via Supabase API
|
|
245
|
-
const authConfig = JSON.parse(await fs.promises.readFile(authConfigPath, 'utf8'));
|
|
246
|
-
console.log(chalk.gray(' - Configurações carregadas:', Object.keys(authConfig).length, 'itens'));
|
|
247
|
-
|
|
248
|
-
return { success: true };
|
|
249
|
-
} catch (error) {
|
|
250
|
-
return { success: false, error: error.message };
|
|
251
|
-
}
|
|
143
|
+
// Formatar bytes em formato legível
|
|
144
|
+
function formatBytes(bytes) {
|
|
145
|
+
if (bytes === 0) return '0 Bytes';
|
|
146
|
+
|
|
147
|
+
const k = 1024;
|
|
148
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
149
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
150
|
+
|
|
151
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
252
152
|
}
|
|
253
153
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
console.log(chalk.gray(' - Restaurando Storage Objects...'));
|
|
154
|
+
// Seleção interativa do backup
|
|
155
|
+
async function selectBackup(backups) {
|
|
156
|
+
console.log(chalk.blue('\n📋 Backups disponíveis:'));
|
|
157
|
+
console.log(chalk.blue('═'.repeat(80)));
|
|
158
|
+
|
|
159
|
+
const choices = backups.map((backup, index) => {
|
|
160
|
+
const date = new Date(backup.created).toLocaleString('pt-BR');
|
|
161
|
+
const projectInfo = backup.projectId !== 'Desconhecido' ? ` (${backup.projectId})` : '';
|
|
265
162
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
163
|
+
return {
|
|
164
|
+
name: `${index + 1}. ${backup.name}${projectInfo}\n 📅 ${date} | 📦 ${backup.size}`,
|
|
165
|
+
value: backup,
|
|
166
|
+
short: backup.name
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const { selectedBackup } = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'list',
|
|
173
|
+
name: 'selectedBackup',
|
|
174
|
+
message: 'Selecione o backup para restaurar:',
|
|
175
|
+
choices: choices,
|
|
176
|
+
pageSize: 10
|
|
271
177
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return { success: false, error: error.message };
|
|
276
|
-
}
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
return selectedBackup;
|
|
277
181
|
}
|
|
278
182
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
*/
|
|
282
|
-
async function restoreRealtimeSettings(realtimeConfigPath, projectId) {
|
|
183
|
+
// Verificar se é possível fazer clean restore
|
|
184
|
+
async function checkCleanRestore(databaseUrl) {
|
|
283
185
|
try {
|
|
284
|
-
|
|
285
|
-
console.log(chalk.gray(' - Nenhuma configuração de Realtime encontrada no backup'));
|
|
286
|
-
return { success: true };
|
|
287
|
-
}
|
|
186
|
+
console.log(chalk.blue('🔍 Verificando se database está vazia...'));
|
|
288
187
|
|
|
289
|
-
|
|
188
|
+
// Verificar se existem tabelas no schema public
|
|
189
|
+
const checkQuery = `
|
|
190
|
+
SELECT COUNT(*) as table_count
|
|
191
|
+
FROM information_schema.tables
|
|
192
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE';
|
|
193
|
+
`;
|
|
290
194
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
195
|
+
const { stdout } = await runCommand(
|
|
196
|
+
`psql "${databaseUrl}" -t -c "${checkQuery}"`
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const tableCount = parseInt(stdout.trim());
|
|
200
|
+
|
|
201
|
+
if (tableCount > 0) {
|
|
202
|
+
console.error(chalk.red('❌ Database não está vazia!'));
|
|
203
|
+
console.log(chalk.yellow('💡 Para clean restore, a database deve estar vazia'));
|
|
204
|
+
console.log(chalk.yellow('💡 Opções:'));
|
|
205
|
+
console.log(chalk.yellow(' 1. Criar uma nova database'));
|
|
206
|
+
console.log(chalk.yellow(' 2. Desabilitar cleanRestore no .smoonbrc'));
|
|
207
|
+
console.log(chalk.yellow(' 3. Limpar manualmente a database'));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(chalk.green('✅ Database está vazia, prosseguindo com clean restore'));
|
|
294
212
|
|
|
295
|
-
return { success: true };
|
|
296
213
|
} catch (error) {
|
|
297
|
-
|
|
214
|
+
console.log(chalk.yellow(`⚠️ Não foi possível verificar database: ${error.message}`));
|
|
215
|
+
console.log(chalk.yellow('💡 Prosseguindo com restauração...'));
|
|
298
216
|
}
|
|
299
217
|
}
|
|
300
218
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// Verificações básicas
|
|
309
|
-
const checks = [
|
|
310
|
-
{ name: 'Database Connection', status: 'pending' },
|
|
311
|
-
{ name: 'Edge Functions', status: 'pending' },
|
|
312
|
-
{ name: 'Auth Providers', status: 'pending' },
|
|
313
|
-
{ name: 'Storage Buckets', status: 'pending' },
|
|
314
|
-
{ name: 'Realtime Settings', status: 'pending' }
|
|
315
|
-
];
|
|
219
|
+
// Executar restauração usando psql
|
|
220
|
+
async function performRestore(backupDir, databaseUrl) {
|
|
221
|
+
const sqlFiles = ['roles.sql', 'schema.sql', 'data.sql'];
|
|
222
|
+
|
|
223
|
+
for (const sqlFile of sqlFiles) {
|
|
224
|
+
const filePath = path.join(backupDir, sqlFile);
|
|
316
225
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
226
|
+
if (!fs.existsSync(filePath)) {
|
|
227
|
+
console.log(chalk.yellow(`⚠️ Arquivo ${sqlFile} não encontrado, pulando...`));
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
321
230
|
|
|
322
|
-
|
|
323
|
-
checks.forEach(check => {
|
|
324
|
-
const icon = check.status === 'ok' ? '✅' :
|
|
325
|
-
check.status === 'warning' ? '⚠️' : '❌';
|
|
326
|
-
console.log(chalk.gray(` ${icon} ${check.name}`));
|
|
327
|
-
});
|
|
231
|
+
console.log(chalk.blue(`📄 Executando ${sqlFile}...`));
|
|
328
232
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
233
|
+
try {
|
|
234
|
+
let command;
|
|
235
|
+
if (sqlFile === 'data.sql') {
|
|
236
|
+
// Para dados, usar single-transaction
|
|
237
|
+
command = `psql "${databaseUrl}" -v ON_ERROR_STOP=1 --single-transaction -f "${filePath}"`;
|
|
238
|
+
} else {
|
|
239
|
+
// Para roles e schema, usar ON_ERROR_STOP
|
|
240
|
+
command = `psql "${databaseUrl}" -v ON_ERROR_STOP=1 -f "${filePath}"`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { stdout, stderr } = await runCommand(command);
|
|
244
|
+
|
|
245
|
+
if (stderr && !stderr.includes('NOTICE')) {
|
|
246
|
+
console.log(chalk.yellow(`⚠️ Avisos em ${sqlFile}: ${stderr}`));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(chalk.green(`✅ ${sqlFile} executado com sucesso`));
|
|
250
|
+
|
|
251
|
+
} catch (error) {
|
|
252
|
+
throw new Error(`Falha ao executar ${sqlFile}: ${error.message}`);
|
|
253
|
+
}
|
|
332
254
|
}
|
|
333
255
|
}
|
|
334
256
|
|
|
335
|
-
module.exports = restoreCommand;
|
|
257
|
+
module.exports = restoreCommand;
|
package/src/index.js
CHANGED
|
@@ -10,7 +10,6 @@ const chalk = require('chalk');
|
|
|
10
10
|
// Exportar comandos
|
|
11
11
|
const backupCommand = require('./commands/backup');
|
|
12
12
|
const restoreCommand = require('./commands/restore');
|
|
13
|
-
const secretsCommand = require('./commands/secrets');
|
|
14
13
|
const functionsCommand = require('./commands/functions');
|
|
15
14
|
const checkCommand = require('./commands/check');
|
|
16
15
|
const configCommand = require('./commands/config');
|
|
@@ -91,33 +90,26 @@ function showQuickHelp() {
|
|
|
91
90
|
🚀 COMANDOS PRINCIPAIS:
|
|
92
91
|
|
|
93
92
|
📊 Backup completo:
|
|
94
|
-
smoonb backup # Usa
|
|
95
|
-
smoonb backup --project-id <id> # Especifica projectId
|
|
93
|
+
npx smoonb backup # Usa configuração do .smoonbrc
|
|
96
94
|
|
|
97
95
|
🔄 Restauração completa:
|
|
98
|
-
smoonb restore --backup-dir <dir>
|
|
99
|
-
smoonb restore --project-id <id> --backup-dir <dir> # Especifica projectId
|
|
100
|
-
|
|
101
|
-
🔐 Gerenciamento de secrets:
|
|
102
|
-
smoonb secrets export
|
|
103
|
-
smoonb secrets import
|
|
96
|
+
npx smoonb restore --backup-dir <dir> # Restaura backup usando psql
|
|
104
97
|
|
|
105
98
|
⚡ Edge Functions:
|
|
106
|
-
smoonb functions
|
|
107
|
-
smoonb functions
|
|
99
|
+
npx smoonb functions list
|
|
100
|
+
npx smoonb functions push
|
|
108
101
|
|
|
109
102
|
🔍 Verificação pós-restore:
|
|
110
|
-
smoonb check
|
|
111
|
-
smoonb check --project-id <id> # Especifica projectId
|
|
103
|
+
npx smoonb check # Verifica integridade do projeto
|
|
112
104
|
|
|
113
105
|
⚙️ Configuração:
|
|
114
|
-
smoonb config --init
|
|
115
|
-
smoonb config --show
|
|
106
|
+
npx smoonb config --init # Criar arquivo de configuração
|
|
107
|
+
npx smoonb config --show # Mostrar configuração atual
|
|
116
108
|
|
|
117
109
|
📋 CONFIGURAÇÃO AUTOMÁTICA:
|
|
118
|
-
smoonb config --init
|
|
110
|
+
npx smoonb config --init # Cria .smoonbrc com projectId, URLs, etc.
|
|
119
111
|
# Edite o arquivo com suas credenciais Supabase
|
|
120
|
-
smoonb backup
|
|
112
|
+
npx smoonb backup # Funciona automaticamente!
|
|
121
113
|
|
|
122
114
|
📝 EXEMPLO DE CONFIGURAÇÃO (.smoonbrc):
|
|
123
115
|
{
|
|
@@ -131,9 +123,9 @@ function showQuickHelp() {
|
|
|
131
123
|
}
|
|
132
124
|
|
|
133
125
|
🔧 COMO CONFIGURAR:
|
|
134
|
-
1. smoonb config --init
|
|
135
|
-
2. Edite
|
|
136
|
-
3. smoonb backup (funciona automaticamente!)
|
|
126
|
+
1. npx smoonb config --init
|
|
127
|
+
2. Edite .smoonbrc com suas credenciais
|
|
128
|
+
3. npx smoonb backup (funciona automaticamente!)
|
|
137
129
|
`));
|
|
138
130
|
}
|
|
139
131
|
|
|
@@ -270,7 +262,6 @@ module.exports = {
|
|
|
270
262
|
commands: {
|
|
271
263
|
backup: backupCommand,
|
|
272
264
|
restore: restoreCommand,
|
|
273
|
-
secrets: secretsCommand,
|
|
274
265
|
functions: functionsCommand,
|
|
275
266
|
check: checkCommand,
|
|
276
267
|
config: configCommand
|