smoonb 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/backup.js +159 -26
package/package.json
CHANGED
package/src/commands/backup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
const { ensureBin, runCommand } = require('../utils/cli');
|
|
4
5
|
const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
|
|
5
6
|
const { sha256 } = require('../utils/hash');
|
|
@@ -12,13 +13,12 @@ module.exports = async (options) => {
|
|
|
12
13
|
showBetaBanner();
|
|
13
14
|
|
|
14
15
|
try {
|
|
15
|
-
// Verificar se
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
18
|
-
console.error(chalk.red('❌
|
|
19
|
-
console.log(chalk.yellow('💡 Instale
|
|
20
|
-
console.log(chalk.yellow('
|
|
21
|
-
console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
|
|
16
|
+
// Verificar se pg_dump está disponível
|
|
17
|
+
const pgDumpPath = await findPgDumpPath();
|
|
18
|
+
if (!pgDumpPath) {
|
|
19
|
+
console.error(chalk.red('❌ pg_dump não encontrado'));
|
|
20
|
+
console.log(chalk.yellow('💡 Instale PostgreSQL:'));
|
|
21
|
+
console.log(chalk.yellow(' https://www.postgresql.org/download/'));
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -43,10 +43,20 @@ module.exports = async (options) => {
|
|
|
43
43
|
|
|
44
44
|
console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
|
|
45
45
|
console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
|
|
46
|
+
console.log(chalk.gray(`🔧 Usando pg_dump: ${pgDumpPath}`));
|
|
46
47
|
|
|
47
|
-
// 1. Backup da Database usando
|
|
48
|
+
// 1. Backup da Database usando pg_dump/pg_dumpall
|
|
48
49
|
console.log(chalk.blue('\n📊 1/3 - Backup da Database PostgreSQL...'));
|
|
49
|
-
await
|
|
50
|
+
const dbBackupResult = await backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath);
|
|
51
|
+
|
|
52
|
+
if (!dbBackupResult.success) {
|
|
53
|
+
console.error(chalk.red('❌ Falha crítica no backup da database'));
|
|
54
|
+
console.log(chalk.yellow('💡 Verifique:'));
|
|
55
|
+
console.log(chalk.yellow(' - Se DATABASE_URL está correta'));
|
|
56
|
+
console.log(chalk.yellow(' - Se as credenciais estão corretas'));
|
|
57
|
+
console.log(chalk.yellow(' - Se o banco está acessível'));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
50
60
|
|
|
51
61
|
// 2. Gerar inventário real
|
|
52
62
|
console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
|
|
@@ -57,10 +67,11 @@ module.exports = async (options) => {
|
|
|
57
67
|
await backupLocalFunctions(backupDir);
|
|
58
68
|
|
|
59
69
|
// Gerar manifesto do backup
|
|
60
|
-
await generateBackupManifest(config, backupDir);
|
|
70
|
+
await generateBackupManifest(config, backupDir, dbBackupResult.files);
|
|
61
71
|
|
|
62
72
|
console.log(chalk.green('\n🎉 Backup completo finalizado!'));
|
|
63
73
|
console.log(chalk.blue(`📁 Localização: ${backupDir}`));
|
|
74
|
+
console.log(chalk.green(`✅ Database: ${dbBackupResult.files.length} arquivos SQL gerados`));
|
|
64
75
|
|
|
65
76
|
} catch (error) {
|
|
66
77
|
console.error(chalk.red(`❌ Erro no backup: ${error.message}`));
|
|
@@ -68,30 +79,150 @@ module.exports = async (options) => {
|
|
|
68
79
|
}
|
|
69
80
|
};
|
|
70
81
|
|
|
71
|
-
//
|
|
72
|
-
async function
|
|
82
|
+
// Encontrar caminho do pg_dump automaticamente
|
|
83
|
+
async function findPgDumpPath() {
|
|
84
|
+
// Primeiro, tentar encontrar no PATH
|
|
85
|
+
const pgDumpPath = await ensureBin('pg_dump');
|
|
86
|
+
if (pgDumpPath) {
|
|
87
|
+
return pgDumpPath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// No Windows, tentar caminhos comuns
|
|
91
|
+
if (process.platform === 'win32') {
|
|
92
|
+
const possiblePaths = [
|
|
93
|
+
'C:\\Program Files\\PostgreSQL\\17\\bin\\pg_dump.exe',
|
|
94
|
+
'C:\\Program Files\\PostgreSQL\\16\\bin\\pg_dump.exe',
|
|
95
|
+
'C:\\Program Files\\PostgreSQL\\15\\bin\\pg_dump.exe',
|
|
96
|
+
'C:\\Program Files\\PostgreSQL\\14\\bin\\pg_dump.exe',
|
|
97
|
+
'C:\\Program Files\\PostgreSQL\\13\\bin\\pg_dump.exe'
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
for (const pgDumpPath of possiblePaths) {
|
|
101
|
+
if (fs.existsSync(pgDumpPath)) {
|
|
102
|
+
return pgDumpPath;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Backup da database usando pg_dump/pg_dumpall
|
|
111
|
+
async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
73
112
|
try {
|
|
113
|
+
// Parse da URL da database
|
|
114
|
+
const url = new URL(databaseUrl);
|
|
115
|
+
const host = url.hostname;
|
|
116
|
+
const port = url.port || '5432';
|
|
117
|
+
const username = url.username;
|
|
118
|
+
const password = url.password;
|
|
119
|
+
const database = url.pathname.slice(1);
|
|
120
|
+
|
|
121
|
+
console.log(chalk.gray(` - Host: ${host}:${port}`));
|
|
122
|
+
console.log(chalk.gray(` - Database: ${database}`));
|
|
123
|
+
console.log(chalk.gray(` - Username: ${username}`));
|
|
124
|
+
|
|
125
|
+
const files = [];
|
|
126
|
+
let success = true;
|
|
127
|
+
|
|
128
|
+
// 1. Backup dos roles usando pg_dumpall
|
|
74
129
|
console.log(chalk.blue(' - Exportando roles...'));
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
);
|
|
130
|
+
const rolesFile = path.join(backupDir, 'roles.sql');
|
|
131
|
+
const rolesCommand = `"${pgDumpPath.replace('pg_dump', 'pg_dumpall')}" --host=${host} --port=${port} --username=${username} --roles-only -f "${rolesFile}"`;
|
|
78
132
|
|
|
133
|
+
try {
|
|
134
|
+
await runCommand(rolesCommand, {
|
|
135
|
+
env: { ...process.env, PGPASSWORD: password }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (await validateSqlFile(rolesFile)) {
|
|
139
|
+
files.push('roles.sql');
|
|
140
|
+
console.log(chalk.green(' ✅ Roles exportados com sucesso'));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.yellow(' ⚠️ Arquivo roles.sql está vazio'));
|
|
143
|
+
success = false;
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.log(chalk.red(` ❌ Erro ao exportar roles: ${error.message}`));
|
|
147
|
+
success = false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 2. Backup do schema usando pg_dump
|
|
79
151
|
console.log(chalk.blue(' - Exportando schema...'));
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
);
|
|
152
|
+
const schemaFile = path.join(backupDir, 'schema.sql');
|
|
153
|
+
const schemaCommand = `"${pgDumpPath}" --host=${host} --port=${port} --username=${username} --schema-only -f "${schemaFile}" ${database}`;
|
|
83
154
|
|
|
155
|
+
try {
|
|
156
|
+
await runCommand(schemaCommand, {
|
|
157
|
+
env: { ...process.env, PGPASSWORD: password }
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (await validateSqlFile(schemaFile)) {
|
|
161
|
+
files.push('schema.sql');
|
|
162
|
+
console.log(chalk.green(' ✅ Schema exportado com sucesso'));
|
|
163
|
+
} else {
|
|
164
|
+
console.log(chalk.yellow(' ⚠️ Arquivo schema.sql está vazio'));
|
|
165
|
+
success = false;
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log(chalk.red(` ❌ Erro ao exportar schema: ${error.message}`));
|
|
169
|
+
success = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 3. Backup dos dados usando pg_dump
|
|
84
173
|
console.log(chalk.blue(' - Exportando dados...'));
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
174
|
+
const dataFile = path.join(backupDir, 'data.sql');
|
|
175
|
+
const dataCommand = `"${pgDumpPath}" --host=${host} --port=${port} --username=${username} --data-only --use-copy -f "${dataFile}" ${database}`;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await runCommand(dataCommand, {
|
|
179
|
+
env: { ...process.env, PGPASSWORD: password }
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (await validateSqlFile(dataFile)) {
|
|
183
|
+
files.push('data.sql');
|
|
184
|
+
console.log(chalk.green(' ✅ Dados exportados com sucesso'));
|
|
185
|
+
} else {
|
|
186
|
+
console.log(chalk.yellow(' ⚠️ Arquivo data.sql está vazio'));
|
|
187
|
+
success = false;
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.log(chalk.red(` ❌ Erro ao exportar dados: ${error.message}`));
|
|
191
|
+
success = false;
|
|
192
|
+
}
|
|
88
193
|
|
|
89
|
-
|
|
194
|
+
return { success, files };
|
|
90
195
|
} catch (error) {
|
|
91
196
|
throw new Error(`Falha no backup da database: ${error.message}`);
|
|
92
197
|
}
|
|
93
198
|
}
|
|
94
199
|
|
|
200
|
+
// Validar arquivo SQL (não vazio e com conteúdo válido)
|
|
201
|
+
async function validateSqlFile(filePath) {
|
|
202
|
+
try {
|
|
203
|
+
if (!fs.existsSync(filePath)) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const stats = fs.statSync(filePath);
|
|
208
|
+
if (stats.size === 0) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
213
|
+
|
|
214
|
+
// Verificar se contém conteúdo SQL válido
|
|
215
|
+
const sqlKeywords = ['CREATE', 'INSERT', 'COPY', 'ALTER', 'DROP', 'GRANT', 'REVOKE'];
|
|
216
|
+
const hasValidContent = sqlKeywords.some(keyword =>
|
|
217
|
+
content.toUpperCase().includes(keyword)
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return hasValidContent;
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
95
226
|
// Gerar inventário completo
|
|
96
227
|
async function generateInventory(config, backupDir) {
|
|
97
228
|
try {
|
|
@@ -118,7 +249,6 @@ async function backupLocalFunctions(backupDir) {
|
|
|
118
249
|
const localFunctionsPath = 'supabase/functions';
|
|
119
250
|
|
|
120
251
|
try {
|
|
121
|
-
const fs = require('fs');
|
|
122
252
|
if (fs.existsSync(localFunctionsPath)) {
|
|
123
253
|
const functionsBackupDir = path.join(backupDir, 'functions');
|
|
124
254
|
await copyDir(localFunctionsPath, functionsBackupDir);
|
|
@@ -132,7 +262,7 @@ async function backupLocalFunctions(backupDir) {
|
|
|
132
262
|
}
|
|
133
263
|
|
|
134
264
|
// Gerar manifesto do backup
|
|
135
|
-
async function generateBackupManifest(config, backupDir) {
|
|
265
|
+
async function generateBackupManifest(config, backupDir, sqlFiles) {
|
|
136
266
|
const manifest = {
|
|
137
267
|
created_at: new Date().toISOString(),
|
|
138
268
|
project_id: config.supabase.projectId,
|
|
@@ -144,11 +274,14 @@ async function generateBackupManifest(config, backupDir) {
|
|
|
144
274
|
data: 'data.sql'
|
|
145
275
|
},
|
|
146
276
|
hashes: {},
|
|
147
|
-
inventory: {}
|
|
277
|
+
inventory: {},
|
|
278
|
+
validation: {
|
|
279
|
+
sql_files_created: sqlFiles.length,
|
|
280
|
+
sql_files_valid: sqlFiles.length === 3
|
|
281
|
+
}
|
|
148
282
|
};
|
|
149
283
|
|
|
150
284
|
// Calcular hashes dos arquivos SQL
|
|
151
|
-
const fs = require('fs');
|
|
152
285
|
for (const [type, filename] of Object.entries(manifest.files)) {
|
|
153
286
|
const filePath = path.join(backupDir, filename);
|
|
154
287
|
if (fs.existsSync(filePath)) {
|