smoonb 0.0.74 → 0.0.76

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/commands/backup/index.js +62 -65
  3. package/src/commands/backup/steps/00-docker-validation.js +6 -4
  4. package/src/commands/backup/steps/01-database.js +8 -4
  5. package/src/commands/backup/steps/02-database-separated.js +11 -8
  6. package/src/commands/backup/steps/03-database-settings.js +8 -4
  7. package/src/commands/backup/steps/04-auth-settings.js +6 -3
  8. package/src/commands/backup/steps/05-realtime-settings.js +5 -2
  9. package/src/commands/backup/steps/06-storage.js +29 -26
  10. package/src/commands/backup/steps/07-custom-roles.js +6 -3
  11. package/src/commands/backup/steps/08-edge-functions.js +15 -11
  12. package/src/commands/backup/steps/09-supabase-temp.js +9 -6
  13. package/src/commands/backup/steps/10-migrations.js +14 -10
  14. package/src/commands/check.js +5 -3
  15. package/src/commands/restore/index.js +51 -46
  16. package/src/commands/restore/steps/00-backup-selection.js +6 -4
  17. package/src/commands/restore/steps/01-components-selection.js +30 -28
  18. package/src/commands/restore/steps/03-database.js +21 -17
  19. package/src/commands/restore/steps/04-edge-functions.js +16 -13
  20. package/src/commands/restore/steps/05-auth-settings.js +10 -7
  21. package/src/commands/restore/steps/06-storage.js +50 -42
  22. package/src/commands/restore/steps/07-database-settings.js +10 -7
  23. package/src/commands/restore/steps/08-realtime-settings.js +10 -7
  24. package/src/commands/restore/utils.js +15 -13
  25. package/src/i18n/locales/en.json +427 -1
  26. package/src/i18n/locales/pt-BR.json +426 -1
  27. package/src/interactive/envMapper.js +30 -25
  28. package/src/utils/realtime-settings.js +15 -9
  29. package/src/utils/supabaseLink.js +11 -10
@@ -25,16 +25,19 @@ const step08RealtimeSettings = require('./steps/08-realtime-settings');
25
25
  * Função auxiliar para importar arquivo de backup e storage (reutiliza lógica do comando import)
26
26
  */
27
27
  async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
28
+ const { t } = require('../../i18n');
29
+ const getT = global.smoonbI18n?.t || t;
30
+
28
31
  // Validar arquivo de backup
29
32
  try {
30
33
  await fsPromises.access(sourceFile);
31
34
  } catch {
32
- throw new Error(`Arquivo de backup não encontrado: ${sourceFile}`);
35
+ throw new Error(getT('restore.import.fileNotFound', { path: sourceFile }));
33
36
  }
34
37
 
35
38
  // Verificar se é um arquivo .backup.gz ou .backup
36
39
  if (!sourceFile.endsWith('.backup.gz') && !sourceFile.endsWith('.backup')) {
37
- throw new Error('Arquivo de backup deve ser .backup.gz ou .backup');
40
+ throw new Error(getT('restore.import.invalidFormat'));
38
41
  }
39
42
 
40
43
  // Validar arquivo de storage se fornecido
@@ -42,12 +45,12 @@ async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
42
45
  try {
43
46
  await fsPromises.access(sourceStorageFile);
44
47
  } catch {
45
- throw new Error(`Arquivo de storage não encontrado: ${sourceStorageFile}`);
48
+ throw new Error(getT('restore.import.storageNotFound', { path: sourceStorageFile }));
46
49
  }
47
50
 
48
51
  // Verificar se é um arquivo .storage.zip
49
52
  if (!sourceStorageFile.endsWith('.storage.zip')) {
50
- throw new Error('Arquivo de storage deve ser .storage.zip');
53
+ throw new Error(getT('restore.import.storageInvalidFormat'));
51
54
  }
52
55
  }
53
56
 
@@ -57,7 +60,7 @@ async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
57
60
  const match = fileName.match(/db_cluster-(\d{2})-(\d{2})-(\d{4})@(\d{2})-(\d{2})-(\d{2})\.backup(\.gz)?/);
58
61
 
59
62
  if (!match) {
60
- throw new Error('Nome do arquivo de backup não está no formato esperado do Dashboard. Formato esperado: db_cluster-DD-MM-YYYY@HH-MM-SS.backup.gz');
63
+ throw new Error(getT('restore.import.invalidFileName'));
61
64
  }
62
65
 
63
66
  const [, day, month, year, hour, minute, second] = match;
@@ -68,7 +71,7 @@ async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
68
71
 
69
72
  // Criar diretório de backup
70
73
  await ensureDir(backupDir);
71
- console.log(chalk.blue(`📁 Importando backup para: ${backupDirName}`));
74
+ console.log(chalk.blue(`📁 ${getT('restore.import.importing', { name: backupDirName })}`));
72
75
 
73
76
  // Copiar arquivo de backup para o diretório de backup
74
77
  const destFile = path.join(backupDir, fileName);
@@ -78,7 +81,7 @@ async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
78
81
  const stats = await fsPromises.stat(destFile);
79
82
  const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
80
83
 
81
- console.log(chalk.green(`✅ Arquivo de backup importado: ${fileName} (${sizeMB} MB)`));
84
+ console.log(chalk.green(`✅ ${getT('restore.import.backupImported', { fileName, size: sizeMB })}`));
82
85
 
83
86
  // Copiar arquivo de storage se fornecido
84
87
  if (sourceStorageFile) {
@@ -89,7 +92,7 @@ async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
89
92
  const storageStats = await fsPromises.stat(destStorageFile);
90
93
  const storageSizeMB = (storageStats.size / (1024 * 1024)).toFixed(2);
91
94
 
92
- console.log(chalk.green(`✅ Arquivo de storage importado: ${storageFileName} (${storageSizeMB} MB)`));
95
+ console.log(chalk.green(`✅ ${getT('restore.import.storageImported', { fileName: storageFileName, size: storageSizeMB })}`));
93
96
  }
94
97
 
95
98
  return backupDir;
@@ -141,10 +144,10 @@ module.exports = async (options) => {
141
144
  try {
142
145
  await fsPromises.access(envPath);
143
146
  await backupEnvFile(envPath, envBackupPath);
144
- console.log(chalk.blue(`📁 Backup do .env.local: ${path.relative(process.cwd(), envBackupPath)}`));
147
+ console.log(chalk.blue(`📁 ${getT('restore.import.envBackup', { path: path.relative(process.cwd(), envBackupPath) })}`));
145
148
  } catch {
146
149
  // Arquivo não existe, não fazer backup
147
- console.log(chalk.yellow('⚠️ Arquivo .env.local não encontrado. Será criado durante o mapeamento.'));
150
+ console.log(chalk.yellow(getT('restore.import.envNotFound')));
148
151
  }
149
152
 
150
153
  // Leitura e mapeamento interativo
@@ -161,7 +164,7 @@ module.exports = async (options) => {
161
164
  const { finalEnv, dePara } = await mapEnvVariablesInteractively(currentEnv, expectedKeys);
162
165
  await writeEnvFile(envPath, finalEnv);
163
166
  await saveEnvMap(dePara, path.join(processDir, 'env', 'env-map.json'));
164
- console.log(chalk.green('.env.local atualizado com sucesso. Nenhuma chave renomeada; valores sincronizados.'));
167
+ console.log(chalk.green(`✅ ${getT('restore.index.envUpdated')}`));
165
168
 
166
169
  // Resolver valores esperados a partir do de-para
167
170
  function getValue(expectedKey) {
@@ -187,7 +190,7 @@ module.exports = async (options) => {
187
190
  const sourceFile = path.resolve(options.file);
188
191
  const sourceStorageFile = options.storage ? path.resolve(options.storage) : null;
189
192
 
190
- console.log(chalk.blue(`📁 Importando arquivo de backup...`));
193
+ console.log(chalk.blue(`📁 ${getT('restore.import.importingFile')}`));
191
194
  const importedBackupDir = await importBackupFile(sourceFile, sourceStorageFile, outputDir);
192
195
 
193
196
  // Listar backups válidos para encontrar o backup importado
@@ -195,20 +198,20 @@ module.exports = async (options) => {
195
198
  selectedBackup = validBackups.find(b => b.path === importedBackupDir);
196
199
 
197
200
  if (!selectedBackup) {
198
- throw new Error('Não foi possível encontrar o backup importado');
201
+ throw new Error(getT('restore.import.cannotFind'));
199
202
  }
200
203
 
201
- console.log(chalk.green(`✅ Backup importado e selecionado automaticamente: ${path.basename(selectedBackup.path)}`));
204
+ console.log(chalk.green(`✅ ${getT('restore.import.importedSelected', { name: path.basename(selectedBackup.path) })}`));
202
205
  } else {
203
206
  // Fluxo normal: listar e selecionar backup interativamente
204
- console.log(chalk.blue(`📁 Buscando backups em: ${outputDir}`));
207
+ console.log(chalk.blue(`📁 ${getT('restore.index.searchingBackups', { path: outputDir })}`));
205
208
 
206
209
  // 1. Listar backups válidos (.backup.gz)
207
210
  const validBackups = await listValidBackups(outputDir);
208
211
 
209
212
  if (validBackups.length === 0) {
210
- console.error(chalk.red('❌ Nenhum backup válido encontrado'));
211
- console.log(chalk.yellow('💡 Execute primeiro: npx smoonb backup'));
213
+ console.error(chalk.red(`❌ ${getT('restore.index.noBackupsFound')}`));
214
+ console.log(chalk.yellow(`💡 ${getT('restore.index.runBackupFirst')}`));
212
215
  process.exit(1);
213
216
  }
214
217
 
@@ -221,33 +224,33 @@ module.exports = async (options) => {
221
224
 
222
225
  // Validar que pelo menos um componente foi selecionado
223
226
  if (!Object.values(components).some(Boolean)) {
224
- console.error(chalk.red('\n❌ Nenhum componente selecionado para restauração!'));
227
+ console.error(chalk.red(getT('restore.import.noComponents')));
225
228
  process.exit(1);
226
229
  }
227
230
 
228
231
  // 4. Mostrar resumo detalhado
229
- console.log(chalk.cyan('\n📋 RESUMO DA RESTAURAÇÃO:\n'));
230
- console.log(chalk.white(` 📁 Backup selecionado: ${path.basename(selectedBackup.path)}`));
231
- console.log(chalk.white(` 🎯 Projeto destino: ${targetProject.targetProjectId || '(não configurado)'}`));
232
- console.log(chalk.white(` 📊 Database: ${components.database ? 'Sim' : 'Não'}`));
233
- console.log(chalk.white(` ⚡ Edge Functions: ${components.edgeFunctions ? 'Sim' : 'Não'}`));
234
- console.log(chalk.white(` 🔐 Auth Settings: ${components.authSettings ? 'Sim' : 'Não'}`));
235
- console.log(chalk.white(` 📦 Storage: ${components.storage ? 'Sim' : 'Não'}`));
236
- console.log(chalk.white(` 🔧 Database Settings: ${components.databaseSettings ? 'Sim' : 'Não'}`));
237
- console.log(chalk.white(` 🔄 Realtime Settings: ${components.realtimeSettings ? 'Sim' : 'Não'}\n`));
232
+ console.log(chalk.cyan(`\n📋 ${getT('restore.index.summaryTitle')}\n`));
233
+ console.log(chalk.white(` 📁 ${getT('restore.index.selectedBackup', { name: path.basename(selectedBackup.path) })}`));
234
+ console.log(chalk.white(` 🎯 ${getT('restore.index.targetProject', { projectId: targetProject.targetProjectId || getT('restore.index.notConfigured') })}`));
235
+ console.log(chalk.white(` 📊 ${getT('restore.index.database', { value: components.database ? getT('restore.index.yes') : getT('restore.index.no') })}`));
236
+ console.log(chalk.white(` ⚡ ${getT('restore.index.edgeFunctions', { value: components.edgeFunctions ? getT('restore.index.yes') : getT('restore.index.no') })}`));
237
+ console.log(chalk.white(` 🔐 ${getT('restore.index.authSettings', { value: components.authSettings ? getT('restore.index.yes') : getT('restore.index.no') })}`));
238
+ console.log(chalk.white(` 📦 ${getT('restore.index.storage', { value: components.storage ? getT('restore.index.yes') : getT('restore.index.no') })}`));
239
+ console.log(chalk.white(` 🔧 ${getT('restore.index.databaseSettings', { value: components.databaseSettings ? getT('restore.index.yes') : getT('restore.index.no') })}`));
240
+ console.log(chalk.white(` 🔄 ${getT('restore.index.realtimeSettings', { value: components.realtimeSettings ? getT('restore.index.yes') : getT('restore.index.no') })}\n`));
238
241
 
239
242
  // Mostrar resumo técnico adicional
240
243
  showRestoreSummary(selectedBackup, components, targetProject);
241
244
 
242
245
  // 5. Confirmar execução
243
- const finalOk = await confirm('Deseja iniciar a restauração com estas configurações?', true);
246
+ const finalOk = await confirm(getT('restore.index.confirmRestore'), true);
244
247
  if (!finalOk) {
245
- console.log(chalk.yellow('🚫 Restauração cancelada.'));
248
+ console.log(chalk.yellow(`🚫 ${getT('restore.index.restoreCancelled')}`));
246
249
  process.exit(0);
247
250
  }
248
251
 
249
252
  // 6. Executar restauração
250
- console.log(chalk.blue('\n🚀 Iniciando restauração...'));
253
+ console.log(chalk.blue(`\n🚀 ${getT('restore.index.startingRestore')}`));
251
254
 
252
255
  // Contar etapas totais para numeração dinâmica
253
256
  let stepNumber = 0;
@@ -264,7 +267,7 @@ module.exports = async (options) => {
264
267
  // 6.1 Database (se selecionado)
265
268
  if (components.database) {
266
269
  stepNumber++;
267
- console.log(chalk.blue(`\n📊 ${stepNumber}/${totalSteps} - Restaurando Database...`));
270
+ console.log(chalk.blue(`\n📊 ${stepNumber}/${totalSteps} - ${getT('restore.index.restoringDatabase')}`));
268
271
  await step03Database({
269
272
  backupFilePath: path.join(selectedBackup.path, selectedBackup.backupFile),
270
273
  targetDatabaseUrl: targetProject.targetDatabaseUrl
@@ -275,7 +278,7 @@ module.exports = async (options) => {
275
278
  // 6.2 Edge Functions (se selecionado)
276
279
  if (components.edgeFunctions) {
277
280
  stepNumber++;
278
- console.log(chalk.blue(`\n⚡ ${stepNumber}/${totalSteps} - Restaurando Edge Functions...`));
281
+ console.log(chalk.blue(`\n⚡ ${stepNumber}/${totalSteps} - ${getT('restore.index.restoringEdgeFunctions')}`));
279
282
  const edgeFunctionsResult = await step04EdgeFunctions({
280
283
  backupPath: selectedBackup.path,
281
284
  targetProject
@@ -286,7 +289,7 @@ module.exports = async (options) => {
286
289
  // 6.3 Auth Settings (se selecionado)
287
290
  if (components.authSettings) {
288
291
  stepNumber++;
289
- console.log(chalk.blue(`\n🔐 ${stepNumber}/${totalSteps} - Restaurando Auth Settings...`));
292
+ console.log(chalk.blue(`\n🔐 ${stepNumber}/${totalSteps} - ${getT('restore.index.restoringAuthSettings')}`));
290
293
  await step05AuthSettings({
291
294
  backupPath: selectedBackup.path,
292
295
  targetProject
@@ -297,7 +300,7 @@ module.exports = async (options) => {
297
300
  // 6.4 Storage Buckets (se selecionado)
298
301
  if (components.storage) {
299
302
  stepNumber++;
300
- console.log(chalk.blue(`\n📦 ${stepNumber}/${totalSteps} - Restaurando Storage Buckets...`));
303
+ console.log(chalk.blue(`\n📦 ${stepNumber}/${totalSteps} - ${getT('restore.index.restoringStorageBuckets')}`));
301
304
  const storageResult = await step06Storage({
302
305
  backupPath: selectedBackup.path,
303
306
  targetProject
@@ -308,7 +311,7 @@ module.exports = async (options) => {
308
311
  // 6.5 Database Settings (se selecionado)
309
312
  if (components.databaseSettings) {
310
313
  stepNumber++;
311
- console.log(chalk.blue(`\n🔧 ${stepNumber}/${totalSteps} - Restaurando Database Settings...`));
314
+ console.log(chalk.blue(`\n🔧 ${stepNumber}/${totalSteps} - ${getT('restore.index.restoringDatabaseSettings')}`));
312
315
  await step07DatabaseSettings({
313
316
  backupPath: selectedBackup.path,
314
317
  targetProject
@@ -319,7 +322,7 @@ module.exports = async (options) => {
319
322
  // 6.6 Realtime Settings (se selecionado)
320
323
  if (components.realtimeSettings) {
321
324
  stepNumber++;
322
- console.log(chalk.blue(`\n🔄 ${stepNumber}/${totalSteps} - Restaurando Realtime Settings...`));
325
+ console.log(chalk.blue(`\n🔄 ${stepNumber}/${totalSteps} - ${getT('restore.index.restoringRealtimeSettings')}`));
323
326
  await step08RealtimeSettings({
324
327
  backupPath: selectedBackup.path,
325
328
  targetProject
@@ -349,21 +352,21 @@ module.exports = async (options) => {
349
352
  }
350
353
 
351
354
  // Exibir resumo final
352
- console.log(chalk.green('\n🎉 RESTAURAÇÃO COMPLETA FINALIZADA!'));
353
- console.log(chalk.blue(`🎯 Projeto destino: ${targetProject.targetProjectId || '(não configurado)'}`));
355
+ console.log(chalk.green(`\n🎉 ${getT('restore.index.restoreComplete')}`));
356
+ console.log(chalk.blue(`🎯 ${getT('restore.index.targetProjectFinal', { projectId: targetProject.targetProjectId || getT('restore.index.notConfigured') })}`));
354
357
 
355
358
  if (restoreResults.database) {
356
- console.log(chalk.green(`📊 Database: Restaurada com sucesso via Docker`));
359
+ console.log(chalk.green(`📊 ${getT('restore.index.databaseRestored')}`));
357
360
  }
358
361
 
359
362
  if (restoreResults.edgeFunctions) {
360
363
  const funcCount = restoreResults.edgeFunctions.functions_count || 0;
361
364
  const successCount = restoreResults.edgeFunctions.success_count || 0;
362
- console.log(chalk.green(`⚡ Edge Functions: ${successCount}/${funcCount} functions restauradas`));
365
+ console.log(chalk.green(`⚡ ${getT('restore.index.edgeFunctionsRestored', { success: successCount, total: funcCount })}`));
363
366
  }
364
367
 
365
368
  if (restoreResults.authSettings) {
366
- console.log(chalk.green(`🔐 Auth Settings: Configurações exibidas para configuração manual`));
369
+ console.log(chalk.green(`🔐 ${getT('restore.index.authSettingsRestored')}`));
367
370
  }
368
371
 
369
372
  if (restoreResults.storage) {
@@ -372,22 +375,24 @@ module.exports = async (options) => {
372
375
  const totalFiles = restoreResults.storage.total_files || 0;
373
376
 
374
377
  if (filesRestored) {
375
- console.log(chalk.green(`📦 Storage: ${bucketCount} bucket(s) restaurado(s), ${totalFiles} arquivo(s) enviado(s)`));
378
+ console.log(chalk.green(`📦 ${getT('restore.index.storageRestored', { buckets: bucketCount, files: totalFiles })}`));
376
379
  } else {
377
- console.log(chalk.green(`📦 Storage: ${bucketCount} bucket(s) encontrado(s) - apenas metadados (arquivo .storage.zip não encontrado)`));
380
+ console.log(chalk.green(`📦 ${getT('restore.index.storageMetadataOnly', { buckets: bucketCount })}`));
378
381
  }
379
382
  }
380
383
 
381
384
  if (restoreResults.databaseSettings) {
382
- console.log(chalk.green(`🔧 Database Settings: Extensões e configurações restauradas via SQL`));
385
+ console.log(chalk.green(`🔧 ${getT('restore.index.databaseSettingsRestored')}`));
383
386
  }
384
387
 
385
388
  if (restoreResults.realtimeSettings) {
386
- console.log(chalk.green(`🔄 Realtime Settings: Configurações exibidas para configuração manual`));
389
+ console.log(chalk.green(`🔄 ${getT('restore.index.realtimeSettingsRestored')}`));
387
390
  }
388
391
 
389
392
  } catch (error) {
390
- console.error(chalk.red(`❌ Erro na restauração: ${error.message}`));
393
+ const { t } = require('../../i18n');
394
+ const getT = global.smoonbI18n?.t || t;
395
+ console.error(chalk.red(`❌ ${getT('restore.index.error', { message: error.message })}`));
391
396
  process.exit(1);
392
397
  }
393
398
  };
@@ -1,12 +1,14 @@
1
1
  const chalk = require('chalk');
2
2
  const readline = require('readline');
3
+ const { t } = require('../../../i18n');
3
4
 
4
5
  /**
5
6
  * Etapa 0: Seleção interativa de backup
6
7
  */
7
8
  module.exports = async (backups) => {
8
- console.log(chalk.blue('\n📋 Backups disponíveis:'));
9
- console.log(chalk.blue(''.repeat(80)));
9
+ const getT = global.smoonbI18n?.t || t;
10
+ console.log(chalk.blue(`\n📋 ${getT('restore.steps.backupSelection.title')}`));
11
+ console.log(chalk.blue(getT('restore.steps.backupSelection.separator')));
10
12
 
11
13
  backups.forEach((backup, index) => {
12
14
  const date = new Date(backup.created).toLocaleString('pt-BR');
@@ -24,13 +26,13 @@ module.exports = async (backups) => {
24
26
 
25
27
  const question = (query) => new Promise(resolve => rl.question(query, resolve));
26
28
 
27
- const choice = await question(`\nDigite o número do backup para restaurar (1-${backups.length}): `);
29
+ const choice = await question(`\n${getT('restore.steps.backupSelection.input', { min: 1, max: backups.length })} `);
28
30
  rl.close();
29
31
 
30
32
  const backupIndex = parseInt(choice) - 1;
31
33
 
32
34
  if (backupIndex < 0 || backupIndex >= backups.length) {
33
- throw new Error('Número inválido');
35
+ throw new Error(getT('restore.steps.backupSelection.invalid'));
34
36
  }
35
37
 
36
38
  return backups[backupIndex];
@@ -2,31 +2,33 @@ const path = require('path');
2
2
  const fs = require('fs');
3
3
  const chalk = require('chalk');
4
4
  const { confirm } = require('../../../utils/prompt');
5
+ const { t } = require('../../../i18n');
5
6
 
6
7
  /**
7
8
  * Etapa 1: Perguntar quais componentes restaurar
8
9
  */
9
10
  module.exports = async (backupPath) => {
11
+ const getT = global.smoonbI18n?.t || t;
10
12
  // Database (sempre disponível)
11
- const restoreDatabase = await confirm('Deseja restaurar Database', true);
13
+ const restoreDatabase = await confirm(getT('restore.steps.components.database.include'), true);
12
14
 
13
15
  // Edge Functions
14
16
  const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
15
17
  let restoreEdgeFunctions = false;
16
18
  if (fs.existsSync(edgeFunctionsDir) && fs.readdirSync(edgeFunctionsDir).length > 0) {
17
- console.log(chalk.cyan('\n⚡ Edge Functions:'));
18
- console.log(chalk.white(' As Edge Functions serão copiadas para supabase/functions e implantadas no projeto destino.'));
19
- console.log(chalk.white(' A pasta supabase/functions será limpa antes do processo.\n'));
20
- restoreEdgeFunctions = await confirm('Deseja restaurar Edge Functions', true);
19
+ console.log(chalk.cyan(`\n⚡ ${getT('restore.steps.components.edgeFunctions.title')}`));
20
+ console.log(chalk.white(` ${getT('restore.steps.components.edgeFunctions.description1')}`));
21
+ console.log(chalk.white(` ${getT('restore.steps.components.edgeFunctions.description2')}\n`));
22
+ restoreEdgeFunctions = await confirm(getT('restore.steps.components.edgeFunctions.include'), true);
21
23
  }
22
-
24
+
23
25
  // Auth Settings
24
26
  let restoreAuthSettings = false;
25
27
  if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
26
- console.log(chalk.cyan('\n🔐 Auth Settings:'));
27
- console.log(chalk.white(' As configurações de Auth serão exibidas para configuração manual no Dashboard.'));
28
- console.log(chalk.white(' Algumas configurações não podem ser aplicadas automaticamente por questões de segurança.\n'));
29
- restoreAuthSettings = await confirm('Deseja ver as configurações de Auth Settings', true);
28
+ console.log(chalk.cyan(`\n🔐 ${getT('restore.steps.components.auth.title')}`));
29
+ console.log(chalk.white(` ${getT('restore.steps.components.auth.description1')}`));
30
+ console.log(chalk.white(` ${getT('restore.steps.components.auth.description2')}\n`));
31
+ restoreAuthSettings = await confirm(getT('restore.steps.components.auth.include'), true);
30
32
  }
31
33
 
32
34
  // Storage Buckets
@@ -49,37 +51,37 @@ module.exports = async (backupPath) => {
49
51
  const hasStorageFiles = hasStorageDir && fs.readdirSync(storageDir).length > 0;
50
52
 
51
53
  if (storageZipFiles.length > 0 || hasStorageFiles) {
52
- console.log(chalk.cyan('\n📦 Storage:'));
54
+ console.log(chalk.cyan(`\n📦 ${getT('restore.steps.components.storage.title')}`));
53
55
  if (storageZipFiles.length > 0) {
54
- console.log(chalk.white(` Arquivo .storage.zip encontrado: ${storageZipFiles[0]}`));
55
- console.log(chalk.white(' Os buckets e arquivos serão restaurados automaticamente no projeto destino.'));
56
- console.log(chalk.white(' O arquivo ZIP será extraído, buckets criados e arquivos enviados via API.\n'));
56
+ console.log(chalk.white(` ${getT('restore.steps.components.storage.zipFound', { fileName: storageZipFiles[0] })}`));
57
+ console.log(chalk.white(` ${getT('restore.steps.components.storage.withZip.description1')}`));
58
+ console.log(chalk.white(` ${getT('restore.steps.components.storage.withZip.description2')}\n`));
57
59
  } else {
58
- console.log(chalk.white(' Apenas metadados dos buckets encontrados (pasta storage).'));
59
- console.log(chalk.white(' Para restaurar os arquivos, é necessário o arquivo .storage.zip do Dashboard.'));
60
- console.log(chalk.white(' Apenas informações dos buckets serão exibidas.\n'));
60
+ console.log(chalk.white(` ${getT('restore.steps.components.storage.metadataOnly.description1')}`));
61
+ console.log(chalk.white(` ${getT('restore.steps.components.storage.metadataOnly.description2')}`));
62
+ console.log(chalk.white(` ${getT('restore.steps.components.storage.metadataOnly.description3')}\n`));
61
63
  }
62
- restoreStorage = await confirm('Deseja restaurar Storage Buckets', true);
64
+ restoreStorage = await confirm(getT('restore.steps.components.storage.include'), true);
63
65
  }
64
-
66
+
65
67
  // Database Extensions and Settings
66
68
  const dbSettingsFiles = fs.readdirSync(backupPath)
67
69
  .filter(file => file.startsWith('database-settings-') && file.endsWith('.json'));
68
70
  let restoreDatabaseSettings = false;
69
71
  if (dbSettingsFiles.length > 0) {
70
- console.log(chalk.cyan('\n🔧 Database Extensions and Settings:'));
71
- console.log(chalk.white(' As extensões e configurações do banco de dados serão restauradas via SQL.'));
72
- console.log(chalk.white(' Isso inclui extensões PostgreSQL e configurações específicas do projeto.\n'));
73
- restoreDatabaseSettings = await confirm('Deseja restaurar Database Extensions and Settings', true);
72
+ console.log(chalk.cyan(`\n🔧 ${getT('restore.steps.components.databaseSettings.title')}`));
73
+ console.log(chalk.white(` ${getT('restore.steps.components.databaseSettings.description1')}`));
74
+ console.log(chalk.white(` ${getT('restore.steps.components.databaseSettings.description2')}\n`));
75
+ restoreDatabaseSettings = await confirm(getT('restore.steps.components.databaseSettings.include'), true);
74
76
  }
75
-
77
+
76
78
  // Realtime Settings
77
79
  let restoreRealtimeSettings = false;
78
80
  if (fs.existsSync(path.join(backupPath, 'realtime-settings.json'))) {
79
- console.log(chalk.cyan('\n🔄 Realtime Settings:'));
80
- console.log(chalk.white(' As configurações de Realtime serão exibidas para configuração manual no Dashboard.'));
81
- console.log(chalk.white(' Algumas configurações precisam ser aplicadas manualmente.\n'));
82
- restoreRealtimeSettings = await confirm('Deseja ver as configurações de Realtime Settings', true);
81
+ console.log(chalk.cyan(`\n🔄 ${getT('restore.steps.components.realtime.title')}`));
82
+ console.log(chalk.white(` ${getT('restore.steps.components.realtime.description1')}`));
83
+ console.log(chalk.white(` ${getT('restore.steps.components.realtime.description2')}\n`));
84
+ restoreRealtimeSettings = await confirm(getT('restore.steps.components.realtime.include'), true);
83
85
  }
84
86
 
85
87
  return {
@@ -1,20 +1,22 @@
1
1
  const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const { execSync } = require('child_process');
4
+ const { t } = require('../../../i18n');
4
5
 
5
6
  /**
6
7
  * Etapa 3: Restaurar Database via psql
7
8
  */
8
9
  module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
9
10
  try {
11
+ const getT = global.smoonbI18n?.t || t;
10
12
  const backupDirAbs = path.resolve(path.dirname(backupFilePath));
11
13
  const fileName = path.basename(backupFilePath);
12
14
  let uncompressedFile = fileName;
13
15
 
14
16
  // Verificar se é arquivo .backup.gz (compactado) ou .backup (descompactado)
15
17
  if (fileName.endsWith('.backup.gz')) {
16
- console.log(chalk.white(' - Arquivo .backup.gz detectado'));
17
- console.log(chalk.white(' - Extraindo arquivo .gz...'));
18
+ console.log(chalk.white(` - ${getT('restore.steps.database.detectedGz')}`));
19
+ console.log(chalk.white(` - ${getT('restore.steps.database.extractingGz')}`));
18
20
 
19
21
  const unzipCmd = [
20
22
  'docker run --rm',
@@ -24,10 +26,10 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
24
26
 
25
27
  execSync(unzipCmd, { stdio: 'pipe' });
26
28
  uncompressedFile = fileName.replace('.gz', '');
27
- console.log(chalk.white(' - Arquivo descompactado: ' + uncompressedFile));
29
+ console.log(chalk.white(` - ${getT('restore.steps.database.uncompressed', { file: uncompressedFile })}`));
28
30
  } else if (fileName.endsWith('.backup')) {
29
- console.log(chalk.white(' - Arquivo .backup detectado (já descompactado)'));
30
- console.log(chalk.white(' - Prosseguindo com restauração direta'));
31
+ console.log(chalk.white(` - ${getT('restore.steps.database.detectedBackup')}`));
32
+ console.log(chalk.white(` - ${getT('restore.steps.database.proceeding')}`));
31
33
  } else {
32
34
  throw new Error(`Formato de arquivo inválido. Esperado .backup.gz ou .backup, recebido: ${fileName}`);
33
35
  }
@@ -36,7 +38,8 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
36
38
  const urlMatch = targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
37
39
 
38
40
  if (!urlMatch) {
39
- throw new Error('Database URL inválida. Formato esperado: postgresql://user:password@host/database');
41
+ const getT = global.smoonbI18n?.t || t;
42
+ throw new Error(getT('error.databaseUrlInvalid'));
40
43
  }
41
44
 
42
45
  // Comando psql conforme documentação oficial Supabase
@@ -49,29 +52,30 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
49
52
  `-f /host/${uncompressedFile}`
50
53
  ].join(' ');
51
54
 
52
- console.log(chalk.cyan(' - Executando psql via Docker...'));
53
- console.log(chalk.cyan(' ℹ️ Seguindo documentação oficial Supabase'));
54
- console.log(chalk.yellow(' ⚠️ AVISO: Erros como "object already exists" são ESPERADOS'));
55
- console.log(chalk.yellow(' ⚠️ Isto acontece porque o backup contém CREATE para todos os schemas'));
56
- console.log(chalk.yellow(' ⚠️ Supabase já tem auth e storage criados, então esses erros são normais'));
55
+ console.log(chalk.cyan(` - ${getT('restore.steps.database.executing')}`));
56
+ console.log(chalk.cyan(` ℹ️ ${getT('restore.steps.database.followingDocs')}`));
57
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.warning')}`));
58
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.warningReason1')}`));
59
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.warningReason2')}`));
57
60
 
58
61
  // Executar comando de restauração
59
62
  execSync(restoreCmd, { stdio: 'inherit', encoding: 'utf8' });
60
63
 
61
- console.log(chalk.green('Database restaurada com sucesso!'));
62
- console.log(chalk.gray(' ℹ️ Erros "already exists" são normais e não afetam a restauração'));
64
+ console.log(chalk.green(`${getT('restore.steps.database.success')}`));
65
+ console.log(chalk.gray(` ℹ️ ${getT('restore.steps.database.normalErrors')}`));
63
66
 
64
67
  } catch (error) {
65
68
  // Erros esperados conforme documentação oficial Supabase
69
+ const getT = global.smoonbI18n?.t || t;
66
70
  if (error.message.includes('already exists') ||
67
71
  error.message.includes('constraint') ||
68
72
  error.message.includes('duplicate') ||
69
73
  error.stdout?.includes('already exists')) {
70
- console.log(chalk.yellow(' ⚠️ Erros esperados encontrados (conforme documentação Supabase)'));
71
- console.log(chalk.green('Database restaurada com sucesso!'));
72
- console.log(chalk.gray(' ℹ️ Erros são ignorados pois são comandos de CREATE que já existem'));
74
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.expectedErrors')}`));
75
+ console.log(chalk.green(`${getT('restore.steps.database.success')}`));
76
+ console.log(chalk.gray(` ℹ️ ${getT('restore.steps.database.errorsIgnored')}`));
73
77
  } else {
74
- console.error(chalk.red(` ❌ Erro inesperado na restauração: ${error.message}`));
78
+ console.error(chalk.red(` ❌ ${getT('restore.steps.database.error', { message: error.message })}`));
75
79
  throw error;
76
80
  }
77
81
  }
@@ -3,16 +3,18 @@ const path = require('path');
3
3
  const fs = require('fs').promises;
4
4
  const { execSync } = require('child_process');
5
5
  const { copyDirectoryRecursive } = require('../utils');
6
+ const { t } = require('../../../i18n');
6
7
 
7
8
  /**
8
9
  * Etapa 4: Restaurar Edge Functions via supabase functions deploy
9
10
  */
10
11
  module.exports = async ({ backupPath, targetProject }) => {
11
12
  try {
13
+ const getT = global.smoonbI18n?.t || t;
12
14
  const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
13
15
 
14
16
  if (!await fs.access(edgeFunctionsDir).then(() => true).catch(() => false)) {
15
- console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
17
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.notFound')}`));
16
18
  return { success: false, functions_count: 0, success_count: 0 };
17
19
  }
18
20
 
@@ -28,11 +30,11 @@ module.exports = async ({ backupPath, targetProject }) => {
28
30
  }
29
31
 
30
32
  if (functions.length === 0) {
31
- console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
33
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.notFound')}`));
32
34
  return { success: false, functions_count: 0, success_count: 0 };
33
35
  }
34
36
 
35
- console.log(chalk.white(` - Encontradas ${functions.length} Edge Function(s)`));
37
+ console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.found', { count: functions.length })}`));
36
38
 
37
39
  // COPIAR Edge Functions de backups/backup-XXX/edge-functions para supabase/functions
38
40
  const supabaseFunctionsDir = path.join(process.cwd(), 'supabase', 'functions');
@@ -41,7 +43,7 @@ module.exports = async ({ backupPath, targetProject }) => {
41
43
  await fs.mkdir(supabaseFunctionsDir, { recursive: true });
42
44
 
43
45
  // Limpar supabase/functions antes de copiar (necessário para garantir ambiente limpo)
44
- console.log(chalk.cyan(' - Limpando supabase/functions antes de copiar...'));
46
+ console.log(chalk.cyan(` - ${getT('restore.steps.edgeFunctions.cleaningBefore')}`));
45
47
  try {
46
48
  await fs.rm(supabaseFunctionsDir, { recursive: true, force: true });
47
49
  await fs.mkdir(supabaseFunctionsDir, { recursive: true });
@@ -54,13 +56,13 @@ module.exports = async ({ backupPath, targetProject }) => {
54
56
  const backupFuncPath = path.join(edgeFunctionsDir, funcName);
55
57
  const targetFuncPath = path.join(supabaseFunctionsDir, funcName);
56
58
 
57
- console.log(chalk.white(` - Copiando ${funcName} para supabase/functions...`));
59
+ console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.copying', { funcName })}`));
58
60
 
59
61
  // Copiar recursivamente
60
62
  await copyDirectoryRecursive(backupFuncPath, targetFuncPath);
61
63
  }
62
64
 
63
- console.log(chalk.white(` - Linkando com projeto ${targetProject.targetProjectId}...`));
65
+ console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.linking', { projectId: targetProject.targetProjectId })}`));
64
66
 
65
67
  // Linkar com o projeto destino
66
68
  try {
@@ -71,13 +73,13 @@ module.exports = async ({ backupPath, targetProject }) => {
71
73
  env: { ...process.env, SUPABASE_ACCESS_TOKEN: targetProject.targetAccessToken || '' }
72
74
  });
73
75
  } catch {
74
- console.log(chalk.yellow(' ⚠️ Link pode já existir, continuando...'));
76
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.linkMayExist')}`));
75
77
  }
76
78
 
77
79
  // Deploy das Edge Functions
78
80
  let successCount = 0;
79
81
  for (const funcName of functions) {
80
- console.log(chalk.white(` - Deployando ${funcName}...`));
82
+ console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.deploying', { funcName })}`));
81
83
 
82
84
  try {
83
85
  execSync(`supabase functions deploy ${funcName}`, {
@@ -88,22 +90,22 @@ module.exports = async ({ backupPath, targetProject }) => {
88
90
  env: { ...process.env, SUPABASE_ACCESS_TOKEN: targetProject.targetAccessToken || '' }
89
91
  });
90
92
 
91
- console.log(chalk.green(` ✅ ${funcName} deployada com sucesso!`));
93
+ console.log(chalk.green(` ✅ ${getT('restore.steps.edgeFunctions.deployed', { funcName })}`));
92
94
  successCount++;
93
95
  } catch (deployError) {
94
- console.log(chalk.yellow(` ⚠️ ${funcName} - deploy falhou: ${deployError.message}`));
96
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.deployFailed', { funcName, message: deployError.message })}`));
95
97
  }
96
98
  }
97
99
 
98
100
  // Limpar supabase/functions após deploy (arquivos temporários não são mais necessários)
99
- console.log(chalk.cyan(' - Limpando supabase/functions após deploy...'));
101
+ console.log(chalk.cyan(` - ${getT('restore.steps.edgeFunctions.cleaningAfter')}`));
100
102
  try {
101
103
  await fs.rm(supabaseFunctionsDir, { recursive: true, force: true });
102
104
  } catch {
103
105
  // Ignorar erro de limpeza
104
106
  }
105
107
 
106
- console.log(chalk.green('Edge Functions restauradas com sucesso!'));
108
+ console.log(chalk.green(`${getT('restore.steps.edgeFunctions.success')}`));
107
109
 
108
110
  return {
109
111
  success: true,
@@ -112,7 +114,8 @@ module.exports = async ({ backupPath, targetProject }) => {
112
114
  };
113
115
 
114
116
  } catch (error) {
115
- console.error(chalk.red(` ❌ Erro ao restaurar Edge Functions: ${error.message}`));
117
+ const getT = global.smoonbI18n?.t || t;
118
+ console.error(chalk.red(` ❌ ${getT('restore.steps.edgeFunctions.error', { message: error.message })}`));
116
119
  throw error;
117
120
  }
118
121
  };