smoonb 0.0.13 → 0.0.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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 timestamp = new Date().toISOString().replace(/[:.]/g, '-');
40
- const backupDir = path.join(outputDir, `backup-${timestamp}`);
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 usando APENAS pg_dump/pg_dumpall
48
- console.log(chalk.blue('\n📊 1/2 - Backup da Database PostgreSQL...'));
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 locais (se existirem)
61
- console.log(chalk.blue('\n⚡ 2/2 - Backup das Edge Functions locais...'));
62
- await backupLocalFunctions(backupDir);
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
- // Gerar manifesto do backup
65
- await generateBackupManifest(config, backupDir, dbBackupResult.files);
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
- console.log(chalk.green('\n🎉 Backup completo finalizado!'));
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
- const filePath = path.join(backupDir, file.filename);
75
- const stats = fs.statSync(filePath);
76
- const sizeKB = (stats.size / 1024).toFixed(1);
77
- console.log(chalk.gray(` - ${file.filename}: ${sizeKB} KB`));
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 APENAS pg_dump/pg_dumpall
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 (COMANDO VALIDADO)
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 (COMANDO VALIDADO)
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 (COMANDO VALIDADO)
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,283 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
217
253
  }
218
254
  }
219
255
 
220
- // Validar arquivo SQL (não vazio e com conteúdo válido)
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
+ // ✅ Descobrir dinamicamente quantas functions existem
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
+ if (!functions || functions.length === 0) {
274
+ console.log(chalk.gray(' - Nenhuma Edge Function encontrada'));
275
+ await writeJson(path.join(functionsDir, 'README.md'), {
276
+ message: 'Nenhuma Edge Function encontrada neste projeto'
277
+ });
278
+ return { success: true, functions: [] };
279
+ }
280
+
281
+ console.log(chalk.gray(` - Encontradas ${functions.length} Edge Functions`));
282
+
283
+ const downloadedFunctions = [];
284
+
285
+ // ✅ Baixar todas as functions encontradas
286
+ for (const func of functions) {
287
+ try {
288
+ console.log(chalk.gray(` - Baixando: ${func.name}`));
289
+
290
+ // ✅ Baixar código da function
291
+ const { data: functionData, error: functionError } = await supabase.functions.get(func.slug);
292
+
293
+ if (functionError) {
294
+ console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${functionError.message}`));
295
+ continue;
296
+ }
297
+
298
+ // ✅ Salvar arquivos dinamicamente
299
+ const funcDir = path.join(functionsDir, func.name);
300
+ await ensureDir(funcDir);
301
+
302
+ // ✅ Salvar cada arquivo da function
303
+ if (functionData && functionData.files) {
304
+ for (const file of functionData.files) {
305
+ const fileName = path.basename(file.name);
306
+ const filePath = path.join(funcDir, fileName);
307
+ await fs.promises.writeFile(filePath, file.content);
308
+ }
309
+ } else if (functionData && functionData.code) {
310
+ // Fallback para API antiga
311
+ const indexPath = path.join(funcDir, 'index.ts');
312
+ await fs.promises.writeFile(indexPath, functionData.code);
313
+
314
+ if (functionData.deno_config) {
315
+ const denoPath = path.join(funcDir, 'deno.json');
316
+ await writeJson(denoPath, functionData.deno_config);
317
+ }
318
+ }
319
+
320
+ downloadedFunctions.push({
321
+ name: func.name,
322
+ slug: func.slug,
323
+ version: func.version || 'unknown',
324
+ files: fs.existsSync(funcDir) ? fs.readdirSync(funcDir) : []
325
+ });
326
+
327
+ console.log(chalk.green(` ✅ ${func.name} baixada`));
328
+ } catch (error) {
329
+ console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${error.message}`));
330
+ }
331
+ }
332
+
333
+ console.log(chalk.green(`✅ Edge Functions backupadas: ${downloadedFunctions.length} functions`));
334
+ return { success: true, functions: downloadedFunctions };
335
+ } catch (error) {
336
+ console.log(chalk.yellow(`⚠️ Erro no backup das Edge Functions: ${error.message}`));
337
+ return { success: false, functions: [] };
338
+ }
339
+ }
340
+
341
+ // Backup das Auth Settings via Management API
342
+ async function backupAuthSettings(config, backupDir) {
343
+ try {
344
+ console.log(chalk.gray(' - Exportando configurações de Auth...'));
345
+
346
+ // Usar Management API para obter configurações de Auth
347
+ const authSettingsPath = path.join(backupDir, 'auth-settings.json');
348
+
349
+ const authSettings = {
350
+ project_id: config.supabase.projectId,
351
+ timestamp: new Date().toISOString(),
352
+ settings: {
353
+ // Configurações básicas que podemos obter
354
+ site_url: config.supabase.url,
355
+ jwt_secret: 'REDACTED', // Não expor secret
356
+ smtp_settings: null,
357
+ rate_limits: null,
358
+ email_templates: null
359
+ },
360
+ note: 'Configurações completas requerem acesso ao Management API'
361
+ };
362
+
363
+ await writeJson(authSettingsPath, authSettings);
364
+ console.log(chalk.green(' ✅ Auth Settings exportadas'));
365
+
366
+ return { success: true };
367
+ } catch (error) {
368
+ console.log(chalk.yellow(` ⚠️ Erro ao exportar Auth Settings: ${error.message}`));
369
+ return { success: false };
370
+ }
371
+ }
372
+
373
+ // Backup do Storage via Supabase API
374
+ async function backupStorage(config, backupDir) {
375
+ try {
376
+ const supabase = createClient(config.supabase.url, config.supabase.serviceKey);
377
+ const storageDir = path.join(backupDir, 'storage');
378
+ await ensureDir(storageDir);
379
+
380
+ console.log(chalk.gray(' - Listando buckets de Storage...'));
381
+
382
+ // Listar buckets
383
+ const { data: buckets, error } = await supabase.storage.listBuckets();
384
+
385
+ if (error) {
386
+ console.log(chalk.yellow(` ⚠️ Erro ao listar buckets: ${error.message}`));
387
+ return { success: false, buckets: [] };
388
+ }
389
+
390
+ const processedBuckets = [];
391
+
392
+ for (const bucket of buckets || []) {
393
+ try {
394
+ console.log(chalk.gray(` - Processando bucket: ${bucket.name}`));
395
+
396
+ // Listar objetos do bucket
397
+ const { data: objects, error: objectsError } = await supabase.storage
398
+ .from(bucket.name)
399
+ .list('', { limit: 1000 });
400
+
401
+ const bucketInfo = {
402
+ id: bucket.id,
403
+ name: bucket.name,
404
+ public: bucket.public,
405
+ file_size_limit: bucket.file_size_limit,
406
+ allowed_mime_types: bucket.allowed_mime_types,
407
+ objects: objects || []
408
+ };
409
+
410
+ // Salvar informações do bucket
411
+ const bucketPath = path.join(storageDir, `${bucket.name}.json`);
412
+ await writeJson(bucketPath, bucketInfo);
413
+
414
+ processedBuckets.push({
415
+ name: bucket.name,
416
+ objectCount: objects?.length || 0
417
+ });
418
+
419
+ console.log(chalk.green(` ✅ Bucket ${bucket.name}: ${objects?.length || 0} objetos`));
420
+ } catch (error) {
421
+ console.log(chalk.yellow(` ⚠️ Erro ao processar bucket ${bucket.name}: ${error.message}`));
422
+ }
423
+ }
424
+
425
+ return { success: true, buckets: processedBuckets };
426
+ } catch (error) {
427
+ console.log(chalk.yellow(`⚠️ Erro no backup do Storage: ${error.message}`));
428
+ return { success: false, buckets: [] };
429
+ }
430
+ }
431
+
432
+ // Backup dos Custom Roles via SQL
433
+ async function backupCustomRoles(databaseUrl, backupDir) {
434
+ try {
435
+ console.log(chalk.gray(' - Exportando Custom Roles...'));
436
+
437
+ const customRolesFile = path.join(backupDir, 'custom-roles.sql');
438
+
439
+ // Query para obter roles customizados com senhas
440
+ const customRolesQuery = `
441
+ -- Custom Roles Backup
442
+ -- Roles customizados com senhas
443
+
444
+ SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolconnlimit, rolpassword
445
+ FROM pg_roles
446
+ WHERE rolname NOT IN ('postgres', 'supabase_admin', 'supabase_auth_admin', 'supabase_storage_admin', 'supabase_read_only_user', 'authenticator', 'anon', 'authenticated', 'service_role')
447
+ ORDER BY rolname;
448
+ `;
449
+
450
+ // Executar query e salvar resultado
451
+ const { stdout } = await runCommand(
452
+ `psql "${databaseUrl}" -t -c "${customRolesQuery}"`
453
+ );
454
+
455
+ const rolesContent = `-- Custom Roles Backup
456
+ -- Generated at: ${new Date().toISOString()}
457
+
458
+ ${customRolesQuery}
459
+
460
+ -- Results:
461
+ ${stdout}
462
+ `;
463
+
464
+ await fs.promises.writeFile(customRolesFile, rolesContent);
465
+
466
+ const stats = fs.statSync(customRolesFile);
467
+ const sizeKB = (stats.size / 1024).toFixed(1);
468
+
469
+ console.log(chalk.green(` ✅ Custom Roles exportados: ${sizeKB} KB`));
470
+
471
+ return { success: true, roles: [{ filename: 'custom-roles.sql', sizeKB }] };
472
+ } catch (error) {
473
+ console.log(chalk.yellow(` ⚠️ Erro ao exportar Custom Roles: ${error.message}`));
474
+ return { success: false, roles: [] };
475
+ }
476
+ }
477
+
478
+ // Backup das Realtime Settings via SQL
479
+ async function backupRealtimeSettings(databaseUrl, backupDir) {
480
+ try {
481
+ console.log(chalk.gray(' - Exportando Realtime Settings...'));
482
+
483
+ const realtimeFile = path.join(backupDir, 'realtime-settings.sql');
484
+
485
+ // Query para obter configurações de Realtime
486
+ const realtimeQuery = `
487
+ -- Realtime Settings Backup
488
+ -- Publicações e configurações de Realtime
489
+
490
+ -- Publicações
491
+ SELECT pubname, puballtables, pubinsert, pubupdate, pubdelete, pubtruncate
492
+ FROM pg_publication
493
+ ORDER BY pubname;
494
+
495
+ -- Tabelas publicadas
496
+ SELECT p.pubname, c.relname as table_name, n.nspname as schema_name
497
+ FROM pg_publication_tables pt
498
+ JOIN pg_publication p ON p.oid = pt.ptpubid
499
+ JOIN pg_class c ON c.oid = pt.ptrelid
500
+ JOIN pg_namespace n ON n.oid = c.relnamespace
501
+ ORDER BY p.pubname, n.nspname, c.relname;
502
+ `;
503
+
504
+ // Executar query e salvar resultado
505
+ const { stdout } = await runCommand(
506
+ `psql "${databaseUrl}" -t -c "${realtimeQuery}"`
507
+ );
508
+
509
+ const realtimeContent = `-- Realtime Settings Backup
510
+ -- Generated at: ${new Date().toISOString()}
511
+
512
+ ${realtimeQuery}
513
+
514
+ -- Results:
515
+ ${stdout}
516
+ `;
517
+
518
+ await fs.promises.writeFile(realtimeFile, realtimeContent);
519
+
520
+ const stats = fs.statSync(realtimeFile);
521
+ const sizeKB = (stats.size / 1024).toFixed(1);
522
+
523
+ console.log(chalk.green(` ✅ Realtime Settings exportados: ${sizeKB} KB`));
524
+
525
+ return { success: true };
526
+ } catch (error) {
527
+ console.log(chalk.yellow(` ⚠️ Erro ao exportar Realtime Settings: ${error.message}`));
528
+ return { success: false };
529
+ }
530
+ }
531
+
532
+ // Validar arquivo SQL
221
533
  async function validateSqlFile(filePath) {
222
534
  try {
223
535
  if (!fs.existsSync(filePath)) {
@@ -233,7 +545,6 @@ async function validateSqlFile(filePath) {
233
545
 
234
546
  const content = fs.readFileSync(filePath, 'utf8');
235
547
 
236
- // Verificar se contém conteúdo SQL válido
237
548
  const sqlKeywords = ['CREATE', 'INSERT', 'COPY', 'ALTER', 'DROP', 'GRANT', 'REVOKE'];
238
549
  const hasValidContent = sqlKeywords.some(keyword =>
239
550
  content.toUpperCase().includes(keyword)
@@ -249,48 +560,64 @@ async function validateSqlFile(filePath) {
249
560
  }
250
561
  }
251
562
 
252
- // Backup das Edge Functions locais (se existirem)
253
- async function backupLocalFunctions(backupDir) {
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) {
563
+ // Gerar manifesto do backup completo
564
+ async function generateCompleteBackupManifest(config, backupDir, results) {
271
565
  const manifest = {
272
566
  created_at: new Date().toISOString(),
273
567
  project_id: config.supabase.projectId,
274
568
  smoonb_version: require('../../package.json').version,
275
- backup_type: 'postgresql_native',
569
+ backup_type: 'complete_supabase',
570
+ components: {
571
+ database: {
572
+ success: results.database.success,
573
+ files: results.database.files.length,
574
+ total_size_kb: results.database.files.reduce((total, file) => total + parseFloat(file.sizeKB), 0).toFixed(1)
575
+ },
576
+ edge_functions: {
577
+ success: results.edgeFunctions.success,
578
+ functions_count: results.edgeFunctions.functions.length,
579
+ functions: results.edgeFunctions.functions.map(f => f.name)
580
+ },
581
+ auth_settings: {
582
+ success: results.authSettings.success
583
+ },
584
+ storage: {
585
+ success: results.storage.success,
586
+ buckets_count: results.storage.buckets.length,
587
+ buckets: results.storage.buckets.map(b => b.name)
588
+ },
589
+ custom_roles: {
590
+ success: results.customRoles.success,
591
+ roles_count: results.customRoles.roles.length
592
+ },
593
+ realtime: {
594
+ success: results.realtime.success
595
+ }
596
+ },
276
597
  files: {
277
598
  roles: 'roles.sql',
278
599
  schema: 'schema.sql',
279
- data: 'data.sql'
600
+ data: 'data.sql',
601
+ custom_roles: 'custom-roles.sql',
602
+ realtime_settings: 'realtime-settings.sql',
603
+ auth_settings: 'auth-settings.json',
604
+ edge_functions: 'edge-functions/',
605
+ storage: 'storage/'
280
606
  },
281
607
  hashes: {},
282
608
  validation: {
283
- sql_files_created: sqlFiles.length,
284
- sql_files_valid: sqlFiles.length === 3,
285
- total_size_kb: sqlFiles.reduce((total, file) => total + parseFloat(file.sizeKB), 0).toFixed(1)
609
+ all_components_backed_up: Object.values(results).every(r => r.success),
610
+ total_files: results.database.files.length + 4, // +4 for custom files
611
+ backup_complete: true
286
612
  }
287
613
  };
288
614
 
289
- // Calcular hashes dos arquivos SQL
290
- for (const [type, filename] of Object.entries(manifest.files)) {
615
+ // Calcular hashes dos arquivos principais
616
+ const mainFiles = ['roles.sql', 'schema.sql', 'data.sql', 'custom-roles.sql', 'realtime-settings.sql'];
617
+ for (const filename of mainFiles) {
291
618
  const filePath = path.join(backupDir, filename);
292
619
  if (fs.existsSync(filePath)) {
293
- manifest.hashes[type] = await sha256(filePath);
620
+ manifest.hashes[filename] = await sha256(filePath);
294
621
  }
295
622
  }
296
623