smoonb 0.0.13 → 0.0.14
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 +358 -49
package/package.json
CHANGED
package/src/commands/backup.js
CHANGED
|
@@ -6,6 +6,7 @@ const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
|
|
|
6
6
|
const { sha256 } = require('../utils/hash');
|
|
7
7
|
const { readConfig, validateFor } = require('../utils/config');
|
|
8
8
|
const { showBetaBanner } = require('../utils/banner');
|
|
9
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
9
10
|
|
|
10
11
|
// Exportar FUNÇÃO em vez de objeto Command
|
|
11
12
|
module.exports = async (options) => {
|
|
@@ -35,17 +36,24 @@ module.exports = async (options) => {
|
|
|
35
36
|
// Resolver diretório de saída
|
|
36
37
|
const outputDir = options.output || config.backup.outputDir;
|
|
37
38
|
|
|
38
|
-
// Criar diretório de backup com timestamp
|
|
39
|
-
const
|
|
40
|
-
const
|
|
39
|
+
// Criar diretório de backup com timestamp humanizado
|
|
40
|
+
const now = new Date();
|
|
41
|
+
const year = now.getFullYear();
|
|
42
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
43
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
44
|
+
const hour = String(now.getHours()).padStart(2, '0');
|
|
45
|
+
const minute = String(now.getMinutes()).padStart(2, '0');
|
|
46
|
+
const second = String(now.getSeconds()).padStart(2, '0');
|
|
47
|
+
|
|
48
|
+
const backupDir = path.join(outputDir, `backup-${year}-${month}-${day}-${hour}-${minute}-${second}`);
|
|
41
49
|
await ensureDir(backupDir);
|
|
42
50
|
|
|
43
|
-
console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
|
|
51
|
+
console.log(chalk.blue(`🚀 Iniciando backup COMPLETO do projeto: ${config.supabase.projectId}`));
|
|
44
52
|
console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
|
|
45
53
|
console.log(chalk.gray(`🔧 Usando pg_dump: ${pgDumpPath}`));
|
|
46
54
|
|
|
47
|
-
// 1. Backup da Database
|
|
48
|
-
console.log(chalk.blue('\n📊 1/
|
|
55
|
+
// 1. Backup da Database PostgreSQL (básico)
|
|
56
|
+
console.log(chalk.blue('\n📊 1/6 - Backup da Database PostgreSQL...'));
|
|
49
57
|
const dbBackupResult = await backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath);
|
|
50
58
|
|
|
51
59
|
if (!dbBackupResult.success) {
|
|
@@ -57,24 +65,52 @@ module.exports = async (options) => {
|
|
|
57
65
|
process.exit(1);
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
// 2. Backup das Edge Functions
|
|
61
|
-
console.log(chalk.blue('\n⚡ 2/
|
|
62
|
-
await
|
|
68
|
+
// 2. Backup das Edge Functions via Supabase API
|
|
69
|
+
console.log(chalk.blue('\n⚡ 2/6 - Backup das Edge Functions via API...'));
|
|
70
|
+
const edgeFunctionsResult = await backupEdgeFunctions(config, backupDir);
|
|
71
|
+
|
|
72
|
+
// 3. Backup das Auth Settings via Management API
|
|
73
|
+
console.log(chalk.blue('\n🔐 3/6 - Backup das Auth Settings via API...'));
|
|
74
|
+
const authSettingsResult = await backupAuthSettings(config, backupDir);
|
|
75
|
+
|
|
76
|
+
// 4. Backup do Storage via Supabase API
|
|
77
|
+
console.log(chalk.blue('\n📦 4/6 - Backup do Storage via API...'));
|
|
78
|
+
const storageResult = await backupStorage(config, backupDir);
|
|
79
|
+
|
|
80
|
+
// 5. Backup dos Custom Roles via SQL
|
|
81
|
+
console.log(chalk.blue('\n👥 5/6 - Backup dos Custom Roles via SQL...'));
|
|
82
|
+
const customRolesResult = await backupCustomRoles(databaseUrl, backupDir);
|
|
63
83
|
|
|
64
|
-
//
|
|
65
|
-
|
|
84
|
+
// 6. Backup das Realtime Settings via SQL
|
|
85
|
+
console.log(chalk.blue('\n🔄 6/6 - Backup das Realtime Settings via SQL...'));
|
|
86
|
+
const realtimeResult = await backupRealtimeSettings(databaseUrl, backupDir);
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
// Gerar manifesto do backup completo
|
|
89
|
+
await generateCompleteBackupManifest(config, backupDir, {
|
|
90
|
+
database: dbBackupResult,
|
|
91
|
+
edgeFunctions: edgeFunctionsResult,
|
|
92
|
+
authSettings: authSettingsResult,
|
|
93
|
+
storage: storageResult,
|
|
94
|
+
customRoles: customRolesResult,
|
|
95
|
+
realtime: realtimeResult
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO!'));
|
|
68
99
|
console.log(chalk.blue(`📁 Localização: ${backupDir}`));
|
|
69
100
|
console.log(chalk.green(`✅ Database: ${dbBackupResult.files.length} arquivos SQL gerados`));
|
|
101
|
+
console.log(chalk.green(`✅ Edge Functions: ${edgeFunctionsResult.functions.length} functions baixadas`));
|
|
102
|
+
console.log(chalk.green(`✅ Auth Settings: ${authSettingsResult.success ? 'Exportadas' : 'Falharam'}`));
|
|
103
|
+
console.log(chalk.green(`✅ Storage: ${storageResult.buckets.length} buckets verificados`));
|
|
104
|
+
console.log(chalk.green(`✅ Custom Roles: ${customRolesResult.roles.length} roles exportados`));
|
|
105
|
+
console.log(chalk.green(`✅ Realtime: ${realtimeResult.success ? 'Configurações exportadas' : 'Falharam'}`));
|
|
70
106
|
|
|
71
107
|
// Mostrar resumo dos arquivos
|
|
72
108
|
console.log(chalk.blue('\n📊 Resumo dos arquivos gerados:'));
|
|
73
109
|
for (const file of dbBackupResult.files) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.log(chalk.gray(` -
|
|
110
|
+
console.log(chalk.gray(` - ${file.filename}: ${file.sizeKB} KB`));
|
|
111
|
+
}
|
|
112
|
+
if (edgeFunctionsResult.functions.length > 0) {
|
|
113
|
+
console.log(chalk.gray(` - Edge Functions: ${edgeFunctionsResult.functions.length} functions`));
|
|
78
114
|
}
|
|
79
115
|
|
|
80
116
|
} catch (error) {
|
|
@@ -111,7 +147,7 @@ async function findPgDumpPath() {
|
|
|
111
147
|
return null;
|
|
112
148
|
}
|
|
113
149
|
|
|
114
|
-
// Backup da database usando
|
|
150
|
+
// Backup da database usando pg_dump/pg_dumpall
|
|
115
151
|
async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
116
152
|
try {
|
|
117
153
|
// Parse da URL da database
|
|
@@ -129,7 +165,7 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
|
129
165
|
const files = [];
|
|
130
166
|
let success = true;
|
|
131
167
|
|
|
132
|
-
// 1. Backup do schema usando pg_dump
|
|
168
|
+
// 1. Backup do schema usando pg_dump
|
|
133
169
|
console.log(chalk.blue(' - Exportando schema...'));
|
|
134
170
|
const schemaFile = path.join(backupDir, 'schema.sql');
|
|
135
171
|
const schemaCommand = `"${pgDumpPath}" "${databaseUrl}" --schema-only -f "${schemaFile}"`;
|
|
@@ -156,7 +192,7 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
|
156
192
|
success = false;
|
|
157
193
|
}
|
|
158
194
|
|
|
159
|
-
// 2. Backup dos dados usando pg_dump
|
|
195
|
+
// 2. Backup dos dados usando pg_dump
|
|
160
196
|
console.log(chalk.blue(' - Exportando dados...'));
|
|
161
197
|
const dataFile = path.join(backupDir, 'data.sql');
|
|
162
198
|
const dataCommand = `"${pgDumpPath}" "${databaseUrl}" --data-only -f "${dataFile}"`;
|
|
@@ -183,7 +219,7 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
|
183
219
|
success = false;
|
|
184
220
|
}
|
|
185
221
|
|
|
186
|
-
// 3. Backup dos roles usando pg_dumpall
|
|
222
|
+
// 3. Backup dos roles usando pg_dumpall
|
|
187
223
|
console.log(chalk.blue(' - Exportando roles...'));
|
|
188
224
|
const rolesFile = path.join(backupDir, 'roles.sql');
|
|
189
225
|
const pgDumpallPath = pgDumpPath.replace('pg_dump', 'pg_dumpall');
|
|
@@ -217,7 +253,265 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
|
217
253
|
}
|
|
218
254
|
}
|
|
219
255
|
|
|
220
|
-
//
|
|
256
|
+
// Backup das Edge Functions via Supabase API
|
|
257
|
+
async function backupEdgeFunctions(config, backupDir) {
|
|
258
|
+
try {
|
|
259
|
+
const supabase = createClient(config.supabase.url, config.supabase.serviceKey);
|
|
260
|
+
const functionsDir = path.join(backupDir, 'edge-functions');
|
|
261
|
+
await ensureDir(functionsDir);
|
|
262
|
+
|
|
263
|
+
console.log(chalk.gray(' - Listando Edge Functions...'));
|
|
264
|
+
|
|
265
|
+
// Listar Edge Functions via API
|
|
266
|
+
const { data: functions, error } = await supabase.functions.list();
|
|
267
|
+
|
|
268
|
+
if (error) {
|
|
269
|
+
console.log(chalk.yellow(` ⚠️ Erro ao listar Edge Functions: ${error.message}`));
|
|
270
|
+
return { success: false, functions: [] };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const downloadedFunctions = [];
|
|
274
|
+
|
|
275
|
+
for (const func of functions || []) {
|
|
276
|
+
try {
|
|
277
|
+
console.log(chalk.gray(` - Baixando function: ${func.name}`));
|
|
278
|
+
|
|
279
|
+
// Criar diretório para a function
|
|
280
|
+
const funcDir = path.join(functionsDir, func.name);
|
|
281
|
+
await ensureDir(funcDir);
|
|
282
|
+
|
|
283
|
+
// Baixar código da function via API
|
|
284
|
+
const { data: functionCode, error: codeError } = await supabase.functions.getEdgeFunction(func.name);
|
|
285
|
+
|
|
286
|
+
if (codeError) {
|
|
287
|
+
console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${codeError.message}`));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Salvar arquivos da function
|
|
292
|
+
if (functionCode) {
|
|
293
|
+
// Salvar index.ts
|
|
294
|
+
const indexPath = path.join(funcDir, 'index.ts');
|
|
295
|
+
await fs.promises.writeFile(indexPath, functionCode.code || '// Function code not available');
|
|
296
|
+
|
|
297
|
+
// Salvar deno.json se disponível
|
|
298
|
+
if (functionCode.deno_config) {
|
|
299
|
+
const denoPath = path.join(funcDir, 'deno.json');
|
|
300
|
+
await writeJson(denoPath, functionCode.deno_config);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
downloadedFunctions.push({
|
|
304
|
+
name: func.name,
|
|
305
|
+
version: func.version,
|
|
306
|
+
files: ['index.ts', 'deno.json'].filter(file => fs.existsSync(path.join(funcDir, file)))
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
console.log(chalk.green(` ✅ ${func.name} baixada`));
|
|
310
|
+
}
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.log(chalk.yellow(` ⚠️ Erro ao processar ${func.name}: ${error.message}`));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { success: true, functions: downloadedFunctions };
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.log(chalk.yellow(`⚠️ Erro no backup das Edge Functions: ${error.message}`));
|
|
319
|
+
return { success: false, functions: [] };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Backup das Auth Settings via Management API
|
|
324
|
+
async function backupAuthSettings(config, backupDir) {
|
|
325
|
+
try {
|
|
326
|
+
console.log(chalk.gray(' - Exportando configurações de Auth...'));
|
|
327
|
+
|
|
328
|
+
// Usar Management API para obter configurações de Auth
|
|
329
|
+
const authSettingsPath = path.join(backupDir, 'auth-settings.json');
|
|
330
|
+
|
|
331
|
+
const authSettings = {
|
|
332
|
+
project_id: config.supabase.projectId,
|
|
333
|
+
timestamp: new Date().toISOString(),
|
|
334
|
+
settings: {
|
|
335
|
+
// Configurações básicas que podemos obter
|
|
336
|
+
site_url: config.supabase.url,
|
|
337
|
+
jwt_secret: 'REDACTED', // Não expor secret
|
|
338
|
+
smtp_settings: null,
|
|
339
|
+
rate_limits: null,
|
|
340
|
+
email_templates: null
|
|
341
|
+
},
|
|
342
|
+
note: 'Configurações completas requerem acesso ao Management API'
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
await writeJson(authSettingsPath, authSettings);
|
|
346
|
+
console.log(chalk.green(' ✅ Auth Settings exportadas'));
|
|
347
|
+
|
|
348
|
+
return { success: true };
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.log(chalk.yellow(` ⚠️ Erro ao exportar Auth Settings: ${error.message}`));
|
|
351
|
+
return { success: false };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Backup do Storage via Supabase API
|
|
356
|
+
async function backupStorage(config, backupDir) {
|
|
357
|
+
try {
|
|
358
|
+
const supabase = createClient(config.supabase.url, config.supabase.serviceKey);
|
|
359
|
+
const storageDir = path.join(backupDir, 'storage');
|
|
360
|
+
await ensureDir(storageDir);
|
|
361
|
+
|
|
362
|
+
console.log(chalk.gray(' - Listando buckets de Storage...'));
|
|
363
|
+
|
|
364
|
+
// Listar buckets
|
|
365
|
+
const { data: buckets, error } = await supabase.storage.listBuckets();
|
|
366
|
+
|
|
367
|
+
if (error) {
|
|
368
|
+
console.log(chalk.yellow(` ⚠️ Erro ao listar buckets: ${error.message}`));
|
|
369
|
+
return { success: false, buckets: [] };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const processedBuckets = [];
|
|
373
|
+
|
|
374
|
+
for (const bucket of buckets || []) {
|
|
375
|
+
try {
|
|
376
|
+
console.log(chalk.gray(` - Processando bucket: ${bucket.name}`));
|
|
377
|
+
|
|
378
|
+
// Listar objetos do bucket
|
|
379
|
+
const { data: objects, error: objectsError } = await supabase.storage
|
|
380
|
+
.from(bucket.name)
|
|
381
|
+
.list('', { limit: 1000 });
|
|
382
|
+
|
|
383
|
+
const bucketInfo = {
|
|
384
|
+
id: bucket.id,
|
|
385
|
+
name: bucket.name,
|
|
386
|
+
public: bucket.public,
|
|
387
|
+
file_size_limit: bucket.file_size_limit,
|
|
388
|
+
allowed_mime_types: bucket.allowed_mime_types,
|
|
389
|
+
objects: objects || []
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Salvar informações do bucket
|
|
393
|
+
const bucketPath = path.join(storageDir, `${bucket.name}.json`);
|
|
394
|
+
await writeJson(bucketPath, bucketInfo);
|
|
395
|
+
|
|
396
|
+
processedBuckets.push({
|
|
397
|
+
name: bucket.name,
|
|
398
|
+
objectCount: objects?.length || 0
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
console.log(chalk.green(` ✅ Bucket ${bucket.name}: ${objects?.length || 0} objetos`));
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.log(chalk.yellow(` ⚠️ Erro ao processar bucket ${bucket.name}: ${error.message}`));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return { success: true, buckets: processedBuckets };
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.log(chalk.yellow(`⚠️ Erro no backup do Storage: ${error.message}`));
|
|
410
|
+
return { success: false, buckets: [] };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Backup dos Custom Roles via SQL
|
|
415
|
+
async function backupCustomRoles(databaseUrl, backupDir) {
|
|
416
|
+
try {
|
|
417
|
+
console.log(chalk.gray(' - Exportando Custom Roles...'));
|
|
418
|
+
|
|
419
|
+
const customRolesFile = path.join(backupDir, 'custom-roles.sql');
|
|
420
|
+
|
|
421
|
+
// Query para obter roles customizados com senhas
|
|
422
|
+
const customRolesQuery = `
|
|
423
|
+
-- Custom Roles Backup
|
|
424
|
+
-- Roles customizados com senhas
|
|
425
|
+
|
|
426
|
+
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolconnlimit, rolpassword
|
|
427
|
+
FROM pg_roles
|
|
428
|
+
WHERE rolname NOT IN ('postgres', 'supabase_admin', 'supabase_auth_admin', 'supabase_storage_admin', 'supabase_read_only_user', 'authenticator', 'anon', 'authenticated', 'service_role')
|
|
429
|
+
ORDER BY rolname;
|
|
430
|
+
`;
|
|
431
|
+
|
|
432
|
+
// Executar query e salvar resultado
|
|
433
|
+
const { stdout } = await runCommand(
|
|
434
|
+
`psql "${databaseUrl}" -t -c "${customRolesQuery}"`
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const rolesContent = `-- Custom Roles Backup
|
|
438
|
+
-- Generated at: ${new Date().toISOString()}
|
|
439
|
+
|
|
440
|
+
${customRolesQuery}
|
|
441
|
+
|
|
442
|
+
-- Results:
|
|
443
|
+
${stdout}
|
|
444
|
+
`;
|
|
445
|
+
|
|
446
|
+
await fs.promises.writeFile(customRolesFile, rolesContent);
|
|
447
|
+
|
|
448
|
+
const stats = fs.statSync(customRolesFile);
|
|
449
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
450
|
+
|
|
451
|
+
console.log(chalk.green(` ✅ Custom Roles exportados: ${sizeKB} KB`));
|
|
452
|
+
|
|
453
|
+
return { success: true, roles: [{ filename: 'custom-roles.sql', sizeKB }] };
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.log(chalk.yellow(` ⚠️ Erro ao exportar Custom Roles: ${error.message}`));
|
|
456
|
+
return { success: false, roles: [] };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Backup das Realtime Settings via SQL
|
|
461
|
+
async function backupRealtimeSettings(databaseUrl, backupDir) {
|
|
462
|
+
try {
|
|
463
|
+
console.log(chalk.gray(' - Exportando Realtime Settings...'));
|
|
464
|
+
|
|
465
|
+
const realtimeFile = path.join(backupDir, 'realtime-settings.sql');
|
|
466
|
+
|
|
467
|
+
// Query para obter configurações de Realtime
|
|
468
|
+
const realtimeQuery = `
|
|
469
|
+
-- Realtime Settings Backup
|
|
470
|
+
-- Publicações e configurações de Realtime
|
|
471
|
+
|
|
472
|
+
-- Publicações
|
|
473
|
+
SELECT pubname, puballtables, pubinsert, pubupdate, pubdelete, pubtruncate
|
|
474
|
+
FROM pg_publication
|
|
475
|
+
ORDER BY pubname;
|
|
476
|
+
|
|
477
|
+
-- Tabelas publicadas
|
|
478
|
+
SELECT p.pubname, c.relname as table_name, n.nspname as schema_name
|
|
479
|
+
FROM pg_publication_tables pt
|
|
480
|
+
JOIN pg_publication p ON p.oid = pt.ptpubid
|
|
481
|
+
JOIN pg_class c ON c.oid = pt.ptrelid
|
|
482
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
483
|
+
ORDER BY p.pubname, n.nspname, c.relname;
|
|
484
|
+
`;
|
|
485
|
+
|
|
486
|
+
// Executar query e salvar resultado
|
|
487
|
+
const { stdout } = await runCommand(
|
|
488
|
+
`psql "${databaseUrl}" -t -c "${realtimeQuery}"`
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const realtimeContent = `-- Realtime Settings Backup
|
|
492
|
+
-- Generated at: ${new Date().toISOString()}
|
|
493
|
+
|
|
494
|
+
${realtimeQuery}
|
|
495
|
+
|
|
496
|
+
-- Results:
|
|
497
|
+
${stdout}
|
|
498
|
+
`;
|
|
499
|
+
|
|
500
|
+
await fs.promises.writeFile(realtimeFile, realtimeContent);
|
|
501
|
+
|
|
502
|
+
const stats = fs.statSync(realtimeFile);
|
|
503
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
504
|
+
|
|
505
|
+
console.log(chalk.green(` ✅ Realtime Settings exportados: ${sizeKB} KB`));
|
|
506
|
+
|
|
507
|
+
return { success: true };
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.log(chalk.yellow(` ⚠️ Erro ao exportar Realtime Settings: ${error.message}`));
|
|
510
|
+
return { success: false };
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Validar arquivo SQL
|
|
221
515
|
async function validateSqlFile(filePath) {
|
|
222
516
|
try {
|
|
223
517
|
if (!fs.existsSync(filePath)) {
|
|
@@ -233,7 +527,6 @@ async function validateSqlFile(filePath) {
|
|
|
233
527
|
|
|
234
528
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
235
529
|
|
|
236
|
-
// Verificar se contém conteúdo SQL válido
|
|
237
530
|
const sqlKeywords = ['CREATE', 'INSERT', 'COPY', 'ALTER', 'DROP', 'GRANT', 'REVOKE'];
|
|
238
531
|
const hasValidContent = sqlKeywords.some(keyword =>
|
|
239
532
|
content.toUpperCase().includes(keyword)
|
|
@@ -249,48 +542,64 @@ async function validateSqlFile(filePath) {
|
|
|
249
542
|
}
|
|
250
543
|
}
|
|
251
544
|
|
|
252
|
-
//
|
|
253
|
-
async function
|
|
254
|
-
const localFunctionsPath = 'supabase/functions';
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
if (fs.existsSync(localFunctionsPath)) {
|
|
258
|
-
const functionsBackupDir = path.join(backupDir, 'functions');
|
|
259
|
-
await copyDir(localFunctionsPath, functionsBackupDir);
|
|
260
|
-
console.log(chalk.green('✅ Edge Functions locais copiadas'));
|
|
261
|
-
} else {
|
|
262
|
-
console.log(chalk.yellow('⚠️ Diretório supabase/functions não encontrado'));
|
|
263
|
-
}
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.log(chalk.yellow(`⚠️ Erro ao copiar Edge Functions: ${error.message}`));
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Gerar manifesto do backup
|
|
270
|
-
async function generateBackupManifest(config, backupDir, sqlFiles) {
|
|
545
|
+
// Gerar manifesto do backup completo
|
|
546
|
+
async function generateCompleteBackupManifest(config, backupDir, results) {
|
|
271
547
|
const manifest = {
|
|
272
548
|
created_at: new Date().toISOString(),
|
|
273
549
|
project_id: config.supabase.projectId,
|
|
274
550
|
smoonb_version: require('../../package.json').version,
|
|
275
|
-
backup_type: '
|
|
551
|
+
backup_type: 'complete_supabase',
|
|
552
|
+
components: {
|
|
553
|
+
database: {
|
|
554
|
+
success: results.database.success,
|
|
555
|
+
files: results.database.files.length,
|
|
556
|
+
total_size_kb: results.database.files.reduce((total, file) => total + parseFloat(file.sizeKB), 0).toFixed(1)
|
|
557
|
+
},
|
|
558
|
+
edge_functions: {
|
|
559
|
+
success: results.edgeFunctions.success,
|
|
560
|
+
functions_count: results.edgeFunctions.functions.length,
|
|
561
|
+
functions: results.edgeFunctions.functions.map(f => f.name)
|
|
562
|
+
},
|
|
563
|
+
auth_settings: {
|
|
564
|
+
success: results.authSettings.success
|
|
565
|
+
},
|
|
566
|
+
storage: {
|
|
567
|
+
success: results.storage.success,
|
|
568
|
+
buckets_count: results.storage.buckets.length,
|
|
569
|
+
buckets: results.storage.buckets.map(b => b.name)
|
|
570
|
+
},
|
|
571
|
+
custom_roles: {
|
|
572
|
+
success: results.customRoles.success,
|
|
573
|
+
roles_count: results.customRoles.roles.length
|
|
574
|
+
},
|
|
575
|
+
realtime: {
|
|
576
|
+
success: results.realtime.success
|
|
577
|
+
}
|
|
578
|
+
},
|
|
276
579
|
files: {
|
|
277
580
|
roles: 'roles.sql',
|
|
278
581
|
schema: 'schema.sql',
|
|
279
|
-
data: 'data.sql'
|
|
582
|
+
data: 'data.sql',
|
|
583
|
+
custom_roles: 'custom-roles.sql',
|
|
584
|
+
realtime_settings: 'realtime-settings.sql',
|
|
585
|
+
auth_settings: 'auth-settings.json',
|
|
586
|
+
edge_functions: 'edge-functions/',
|
|
587
|
+
storage: 'storage/'
|
|
280
588
|
},
|
|
281
589
|
hashes: {},
|
|
282
590
|
validation: {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
591
|
+
all_components_backed_up: Object.values(results).every(r => r.success),
|
|
592
|
+
total_files: results.database.files.length + 4, // +4 for custom files
|
|
593
|
+
backup_complete: true
|
|
286
594
|
}
|
|
287
595
|
};
|
|
288
596
|
|
|
289
|
-
// Calcular hashes dos arquivos
|
|
290
|
-
|
|
597
|
+
// Calcular hashes dos arquivos principais
|
|
598
|
+
const mainFiles = ['roles.sql', 'schema.sql', 'data.sql', 'custom-roles.sql', 'realtime-settings.sql'];
|
|
599
|
+
for (const filename of mainFiles) {
|
|
291
600
|
const filePath = path.join(backupDir, filename);
|
|
292
601
|
if (fs.existsSync(filePath)) {
|
|
293
|
-
manifest.hashes[
|
|
602
|
+
manifest.hashes[filename] = await sha256(filePath);
|
|
294
603
|
}
|
|
295
604
|
}
|
|
296
605
|
|