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
@@ -2,6 +2,7 @@ const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const inquirer = require('inquirer');
5
+ const { t } = require('../../../i18n');
5
6
 
6
7
  /**
7
8
  * Etapa 5: Restaurar Auth Settings (interativo - exibir URL e valores)
@@ -9,19 +10,20 @@ const inquirer = require('inquirer');
9
10
  module.exports = async ({ backupPath, targetProject }) => {
10
11
 
11
12
  try {
13
+ const getT = global.smoonbI18n?.t || t;
12
14
  const authSettingsPath = path.join(backupPath, 'auth-settings.json');
13
15
 
14
16
  if (!fs.existsSync(authSettingsPath)) {
15
- console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Auth encontrada no backup'));
17
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.auth.notFound')}`));
16
18
  return;
17
19
  }
18
20
 
19
21
  const authSettings = JSON.parse(fs.readFileSync(authSettingsPath, 'utf8'));
20
22
  const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/auth/url-config`;
21
23
 
22
- console.log(chalk.green('\n ✅ URL para configuração manual:'));
24
+ console.log(chalk.green(`\n ✅ ${getT('restore.steps.auth.urlTitle')}`));
23
25
  console.log(chalk.cyan(` ${dashboardUrl}`));
24
- console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
26
+ console.log(chalk.yellow(`\n 📋 ${getT('restore.steps.auth.configureTitle')}`));
25
27
 
26
28
  if (authSettings.settings?.auth_url_config) {
27
29
  Object.entries(authSettings.settings.auth_url_config).forEach(([key, value]) => {
@@ -33,18 +35,19 @@ module.exports = async ({ backupPath, targetProject }) => {
33
35
  });
34
36
  }
35
37
 
36
- console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
38
+ console.log(chalk.yellow(`\n ⚠️ ${getT('restore.steps.auth.pressEnter')}`));
37
39
 
38
40
  await inquirer.prompt([{
39
41
  type: 'input',
40
42
  name: 'continue',
41
- message: 'Pressione Enter para continuar'
43
+ message: getT('restore.steps.auth.pressEnter')
42
44
  }]);
43
45
 
44
- console.log(chalk.green('Auth Settings processados'));
46
+ console.log(chalk.green(`${getT('restore.steps.auth.success')}`));
45
47
 
46
48
  } catch (error) {
47
- console.error(chalk.red(` ❌ Erro ao processar Auth Settings: ${error.message}`));
49
+ const getT = global.smoonbI18n?.t || t;
50
+ console.error(chalk.red(` ❌ ${getT('restore.steps.auth.error', { message: error.message })}`));
48
51
  }
49
52
  };
50
53
 
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const fs = require('fs').promises;
4
4
  const AdmZip = require('adm-zip');
5
5
  const { createClient } = require('@supabase/supabase-js');
6
+ const { t } = require('../../../i18n');
6
7
 
7
8
  /**
8
9
  * Etapa 6: Restaurar Storage Buckets e arquivos
@@ -12,6 +13,7 @@ const { createClient } = require('@supabase/supabase-js');
12
13
  module.exports = async ({ backupPath, targetProject }) => {
13
14
 
14
15
  try {
16
+ const getT = global.smoonbI18n?.t || t;
15
17
  // 1. Verificar se existe arquivo .storage.zip na pasta do backup
16
18
  const storageZipFiles = await fs.readdir(backupPath).then(files =>
17
19
  files.filter(f => f.endsWith('.storage.zip'))
@@ -79,21 +81,22 @@ module.exports = async ({ backupPath, targetProject }) => {
79
81
  const buckets = manifest?.components?.storage?.buckets || [];
80
82
 
81
83
  if (buckets.length === 0) {
82
- console.log(chalk.yellow(' ⚠️ Nenhum bucket de Storage encontrado no backup'));
84
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.noBuckets')}`));
83
85
  return { success: false, buckets_count: 0 };
84
86
  }
85
87
 
86
- console.log(chalk.yellow('\n ⚠️ Arquivo .storage.zip não encontrado'));
87
- console.log(chalk.white(' ℹ️ Apenas metadados dos buckets foram encontrados'));
88
- console.log(chalk.white(' ℹ️ Para restaurar os arquivos, é necessário o arquivo .storage.zip do Dashboard'));
89
- console.log(chalk.green(`\n ✅ ${buckets.length} bucket(s) encontrado(s) no backup (apenas metadados)`));
88
+ console.log(chalk.yellow(`\n ⚠️ ${getT('restore.steps.storage.zipNotFound')}`));
89
+ console.log(chalk.white(` ℹ️ ${getT('restore.steps.storage.metadataOnly')}`));
90
+ console.log(chalk.white(` ℹ️ ${getT('restore.steps.storage.zipRequired')}`));
91
+ console.log(chalk.green(`\n ✅ ${getT('restore.steps.storage.bucketsFoundMetadata', { count: buckets.length })}`));
90
92
  buckets.forEach(bucket => {
91
- console.log(chalk.white(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
93
+ const visibility = bucket.public ? getT('restore.steps.storage.public') : getT('restore.steps.storage.private');
94
+ console.log(chalk.white(` - ${getT('restore.steps.storage.bucketInfo', { name: bucket.name, visibility })}`));
92
95
  });
93
96
 
94
97
  return { success: true, buckets_count: buckets.length, files_restored: false };
95
98
  } catch {
96
- console.log(chalk.yellow(' ⚠️ Nenhum bucket de Storage encontrado no backup'));
99
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.noBucketsInBackup')}`));
97
100
  return { success: false, buckets_count: 0 };
98
101
  }
99
102
  }
@@ -101,26 +104,27 @@ module.exports = async ({ backupPath, targetProject }) => {
101
104
  // 3. Selecionar o primeiro arquivo .storage.zip encontrado
102
105
  const storageZipFile = path.join(backupPath, storageZipFiles[0]);
103
106
  const storageZipBaseName = path.basename(storageZipFiles[0], '.storage.zip');
104
- console.log(chalk.white(` - Arquivo de storage encontrado: ${storageZipFiles[0]}`));
107
+ console.log(chalk.white(` - ${getT('restore.steps.storage.fileFound', { fileName: storageZipFiles[0] })}`));
105
108
 
106
109
  // 4. Validar credenciais do projeto destino
107
110
  if (!targetProject.targetProjectId || !targetProject.targetAccessToken) {
108
- throw new Error('Credenciais do projeto destino não configuradas. É necessário SUPABASE_PROJECT_ID e SUPABASE_ACCESS_TOKEN');
111
+ const getT = global.smoonbI18n?.t || t;
112
+ throw new Error(getT('error.storageCredentialsNotConfigured'));
109
113
  }
110
114
 
111
115
  if (!targetProject.targetUrl || !targetProject.targetServiceKey) {
112
- throw new Error('Credenciais do Supabase não configuradas. É necessário NEXT_PUBLIC_SUPABASE_URL e SUPABASE_SERVICE_ROLE_KEY');
116
+ throw new Error(getT('error.supabaseCredentialsNotConfigured'));
113
117
  }
114
118
 
115
119
  // 4.1 Obter project ID do projeto origem e validar substituição
116
120
  if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
117
- console.log(chalk.cyan(` 🔄 Substituindo Project ID: ${sourceProjectId} ${targetProject.targetProjectId}`));
121
+ console.log(chalk.cyan(` 🔄 ${getT('restore.steps.storage.replacingProjectId', { source: sourceProjectId, target: targetProject.targetProjectId })}`));
118
122
  } else if (!sourceProjectId) {
119
- console.log(chalk.yellow(' ⚠️ Project ID do projeto origem não encontrado no manifest. Continuando sem substituição...'));
123
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.projectIdNotFound')}`));
120
124
  }
121
125
 
122
126
  // 5. Extrair arquivo ZIP
123
- console.log(chalk.white(' - Extraindo arquivo .storage.zip...'));
127
+ console.log(chalk.white(` - ${getT('restore.steps.storage.extracting')}`));
124
128
  const extractDir = path.join(backupPath, 'storage_extracted');
125
129
 
126
130
  try {
@@ -131,14 +135,14 @@ module.exports = async ({ backupPath, targetProject }) => {
131
135
 
132
136
  const zip = new AdmZip(storageZipFile);
133
137
  zip.extractAllTo(extractDir, true);
134
- console.log(chalk.green('Arquivo extraído com sucesso'));
138
+ console.log(chalk.green(`${getT('restore.steps.storage.extracted')}`));
135
139
 
136
140
  // 5.1 Substituir Project ID antigo pelo novo nos diretórios e arquivos extraídos
137
141
  // O Supabase pode incluir o project ID nos caminhos dentro do ZIP
138
142
  if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
139
- console.log(chalk.white(' - Substituindo referências ao Project ID antigo nos arquivos extraídos...'));
143
+ console.log(chalk.white(` - ${getT('restore.steps.storage.replacing')}`));
140
144
  await replaceProjectIdInExtractedFiles(extractDir, sourceProjectId, targetProject.targetProjectId);
141
- console.log(chalk.green('Substituição de Project ID concluída'));
145
+ console.log(chalk.green(`${getT('restore.steps.storage.replaced')}`));
142
146
  }
143
147
 
144
148
  // 6. Ler estrutura de diretórios extraídos
@@ -179,8 +183,8 @@ module.exports = async ({ backupPath, targetProject }) => {
179
183
  if (matchesZipFileName) reasons.push('nome do arquivo ZIP');
180
184
  if (matchesProjectIdPattern) reasons.push('formato de Project ID');
181
185
  const reasonText = reasons.length ? ` (${reasons.join(', ')})` : '';
182
- console.log(chalk.white(` - Detectada pasta raiz com Project ID: ${firstItem}${reasonText}`));
183
- console.log(chalk.white(` - Buscando buckets nas subpastas...`));
186
+ console.log(chalk.white(` - ${getT('restore.steps.storage.detectedProjectId', { projectId: firstItem, reason: reasonText })}`));
187
+ console.log(chalk.white(` - ${getT('restore.steps.storage.searchingBuckets')}`));
184
188
  }
185
189
  }
186
190
  }
@@ -204,8 +208,8 @@ module.exports = async ({ backupPath, targetProject }) => {
204
208
 
205
209
  // Se não encontrou buckets nas subpastas, avisar e retornar erro
206
210
  if (bucketDirs.length === 0) {
207
- console.log(chalk.red(` ❌ Erro: Nenhum bucket encontrado nas subpastas de ${rootDir}`));
208
- console.log(chalk.red(` ❌ A pasta raiz é um Project ID e não deve ser tratada como bucket`));
211
+ console.log(chalk.red(` ❌ ${getT('restore.steps.storage.noBucketsInSubfolders', { rootDir })}`));
212
+ console.log(chalk.red(` ❌ ${getT('restore.steps.storage.rootIsProjectId')}`));
209
213
  return { success: false, buckets_count: 0 };
210
214
  }
211
215
  } else {
@@ -224,11 +228,11 @@ module.exports = async ({ backupPath, targetProject }) => {
224
228
  }
225
229
 
226
230
  if (bucketDirs.length === 0) {
227
- console.log(chalk.yellow(' ⚠️ Nenhum bucket encontrado no arquivo .storage.zip'));
231
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.noBucketsInZip')}`));
228
232
  return { success: false, buckets_count: 0 };
229
233
  }
230
234
 
231
- console.log(chalk.white(` - Encontrados ${bucketDirs.length} bucket(s) no arquivo ZIP`));
235
+ console.log(chalk.white(` - ${getT('restore.steps.storage.bucketsFoundInZip', { count: bucketDirs.length })}`));
232
236
 
233
237
  // 7. Criar cliente Supabase para o projeto destino
234
238
  const supabase = createClient(targetProject.targetUrl, targetProject.targetServiceKey);
@@ -242,7 +246,7 @@ module.exports = async ({ backupPath, targetProject }) => {
242
246
  const bucketPath = bucketInfo.path;
243
247
 
244
248
  try {
245
- console.log(chalk.white(`\n - Processando bucket: ${bucketName}`));
249
+ console.log(chalk.white(`\n - ${getT('restore.steps.storage.processingBucket', { bucketName })}`));
246
250
 
247
251
  // 8.1 Obter metadados do bucket do backup (se disponíveis)
248
252
  const bucketMeta = bucketMetadata[bucketName] || {
@@ -255,7 +259,7 @@ module.exports = async ({ backupPath, targetProject }) => {
255
259
  const { data: existingBuckets, error: listError } = await supabase.storage.listBuckets();
256
260
 
257
261
  if (listError) {
258
- throw new Error(`Erro ao listar buckets: ${listError.message}`);
262
+ throw new Error(getT('restore.steps.edgeFunctions.listBucketsError', { message: listError.message }));
259
263
  }
260
264
 
261
265
  const existingBucket = existingBuckets?.find(b => b.name === bucketName);
@@ -263,8 +267,11 @@ module.exports = async ({ backupPath, targetProject }) => {
263
267
 
264
268
  if (!bucketExists) {
265
269
  // Criar bucket via Management API com configurações do backup
266
- console.log(chalk.white(` - Criando bucket ${bucketName}...`));
267
- console.log(chalk.white(` Configurações: ${bucketMeta.public ? 'público' : 'privado'}, limite: ${bucketMeta.file_size_limit || 'sem limite'}, tipos: ${bucketMeta.allowed_mime_types?.join(', ') || 'todos'}`));
270
+ console.log(chalk.white(` - ${getT('restore.steps.storage.creatingBucket', { bucketName })}`));
271
+ const visibility = bucketMeta.public ? getT('restore.steps.storage.public') : getT('restore.steps.storage.private');
272
+ const limit = bucketMeta.file_size_limit || getT('restore.steps.storage.noLimit');
273
+ const types = bucketMeta.allowed_mime_types?.join(', ') || getT('restore.steps.storage.allTypes');
274
+ console.log(chalk.white(` ${getT('restore.steps.storage.bucketConfig', { visibility, limit, types })}`));
268
275
 
269
276
  const createResponse = await fetch(
270
277
  `https://api.supabase.com/v1/projects/${targetProject.targetProjectId}/storage/buckets`,
@@ -285,13 +292,13 @@ module.exports = async ({ backupPath, targetProject }) => {
285
292
 
286
293
  if (!createResponse.ok) {
287
294
  const errorText = await createResponse.text();
288
- throw new Error(`Erro ao criar bucket: ${createResponse.status} ${errorText}`);
295
+ throw new Error(getT('restore.steps.edgeFunctions.createBucketError', { status: createResponse.status, errorText }));
289
296
  }
290
297
 
291
- console.log(chalk.green(` ✅ Bucket ${bucketName} criado com configurações do backup`));
298
+ console.log(chalk.green(` ✅ ${getT('restore.steps.storage.bucketCreated', { bucketName })}`));
292
299
  } else {
293
300
  // Bucket já existe - verificar e atualizar configurações se necessário
294
- console.log(chalk.white(` - Bucket ${bucketName} já existe`));
301
+ console.log(chalk.white(` - ${getT('restore.steps.storage.bucketExists', { bucketName })}`));
295
302
 
296
303
  const needsUpdate =
297
304
  existingBucket.public !== bucketMeta.public ||
@@ -299,7 +306,7 @@ module.exports = async ({ backupPath, targetProject }) => {
299
306
  JSON.stringify(existingBucket.allowed_mime_types || []) !== JSON.stringify(bucketMeta.allowed_mime_types || []);
300
307
 
301
308
  if (needsUpdate) {
302
- console.log(chalk.white(` - Atualizando configurações do bucket para corresponder ao backup...`));
309
+ console.log(chalk.white(` - ${getT('restore.steps.storage.updating')}`));
303
310
 
304
311
  const updateResponse = await fetch(
305
312
  `https://api.supabase.com/v1/projects/${targetProject.targetProjectId}/storage/buckets/${bucketName}`,
@@ -319,12 +326,12 @@ module.exports = async ({ backupPath, targetProject }) => {
319
326
 
320
327
  if (!updateResponse.ok) {
321
328
  const errorText = await updateResponse.text();
322
- console.log(chalk.yellow(` ⚠️ Não foi possível atualizar configurações: ${errorText}`));
329
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.updateError', { errorText })}`));
323
330
  } else {
324
- console.log(chalk.green(` ✅ Configurações do bucket atualizadas`));
331
+ console.log(chalk.green(` ✅ ${getT('restore.steps.storage.bucketUpdated')}`));
325
332
  }
326
333
  } else {
327
- console.log(chalk.white(` - Configurações do bucket já estão corretas`));
334
+ console.log(chalk.white(` - ${getT('restore.steps.storage.bucketConfigCorrect')}`));
328
335
  }
329
336
  }
330
337
 
@@ -356,7 +363,7 @@ module.exports = async ({ backupPath, targetProject }) => {
356
363
  }
357
364
 
358
365
  const filesToUpload = await getAllFiles(bucketPath);
359
- console.log(chalk.white(` - Encontrados ${filesToUpload.length} arquivo(s) para upload`));
366
+ console.log(chalk.white(` - ${getT('restore.steps.storage.filesFoundForUpload', { count: filesToUpload.length })}`));
360
367
 
361
368
  // 8.4 Fazer upload de cada arquivo
362
369
  // Criar mapa de metadados dos objetos do backup (se disponível)
@@ -427,25 +434,25 @@ module.exports = async ({ backupPath, targetProject }) => {
427
434
  .upload(storagePath, fileContent, uploadOptions);
428
435
 
429
436
  if (uploadError) {
430
- console.log(chalk.yellow(` ⚠️ Erro ao fazer upload de ${storagePath}: ${uploadError.message}`));
437
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.uploadError', { path: storagePath, message: uploadError.message })}`));
431
438
  } else {
432
439
  filesUploaded++;
433
440
  if (filesToUpload.length <= 10 || filesUploaded % Math.ceil(filesToUpload.length / 10) === 0) {
434
- const metaInfo = objectMeta ? ' (metadados preservados)' : '';
435
- console.log(chalk.white(` - Upload: ${storagePath}${metaInfo}`));
441
+ const metaInfo = objectMeta ? ` ${getT('restore.steps.storage.metadataPreserved')}` : '';
442
+ console.log(chalk.white(` - ${getT('restore.steps.storage.uploadProgress', { path: storagePath, metaInfo })}`));
436
443
  }
437
444
  }
438
445
  } catch (fileError) {
439
- console.log(chalk.yellow(` ⚠️ Erro ao processar arquivo ${file.storagePath}: ${fileError.message}`));
446
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.fileError', { path: file.storagePath, message: fileError.message })}`));
440
447
  }
441
448
  }
442
449
 
443
- console.log(chalk.green(` ✅ Bucket ${bucketName}: ${filesUploaded}/${filesToUpload.length} arquivo(s) enviado(s)`));
450
+ console.log(chalk.green(` ✅ ${getT('restore.steps.storage.bucketDone', { bucketName, uploaded: filesUploaded, total: filesToUpload.length })}`));
444
451
  successCount++;
445
452
  totalFilesUploaded += filesUploaded;
446
453
 
447
454
  } catch (bucketError) {
448
- console.log(chalk.red(` ❌ Erro ao processar bucket ${bucketName}: ${bucketError.message}`));
455
+ console.log(chalk.red(` ❌ ${getT('restore.steps.storage.bucketError', { bucketName, message: bucketError.message })}`));
449
456
  }
450
457
  }
451
458
 
@@ -456,7 +463,7 @@ module.exports = async ({ backupPath, targetProject }) => {
456
463
  // Ignorar erro de limpeza
457
464
  }
458
465
 
459
- console.log(chalk.green(`\n ✅ Restauração de Storage concluída: ${successCount}/${bucketDirs.length} bucket(s) processado(s), ${totalFilesUploaded} arquivo(s) enviado(s)`));
466
+ console.log(chalk.green(`\n ✅ ${getT('restore.steps.storage.restoreComplete', { success: successCount, total: bucketDirs.length, files: totalFilesUploaded })}`));
460
467
 
461
468
  return {
462
469
  success: true,
@@ -466,7 +473,8 @@ module.exports = async ({ backupPath, targetProject }) => {
466
473
  };
467
474
 
468
475
  } catch (error) {
469
- console.error(chalk.red(` ❌ Erro ao processar Storage: ${error.message}`));
476
+ const getT = global.smoonbI18n?.t || t;
477
+ console.error(chalk.red(` ❌ ${getT('restore.steps.storage.error', { message: error.message })}`));
470
478
  throw error;
471
479
  }
472
480
  };
@@ -2,17 +2,19 @@ const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const { execSync } = require('child_process');
5
+ const { t } = require('../../../i18n');
5
6
 
6
7
  /**
7
8
  * Etapa 7: Restaurar Database Settings (via SQL)
8
9
  */
9
10
  module.exports = async ({ backupPath, targetProject }) => {
10
11
  try {
12
+ const getT = global.smoonbI18n?.t || t;
11
13
  const files = fs.readdirSync(backupPath);
12
14
  const dbSettingsFile = files.find(f => f.startsWith('database-settings-') && f.endsWith('.json'));
13
15
 
14
16
  if (!dbSettingsFile) {
15
- console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Database encontrada no backup'));
17
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.databaseSettings.notFound')}`));
16
18
  return { success: false };
17
19
  }
18
20
 
@@ -22,18 +24,18 @@ module.exports = async ({ backupPath, targetProject }) => {
22
24
  const extensions = dbSettings.extensions || [];
23
25
 
24
26
  if (extensions.length > 0) {
25
- console.log(chalk.white(` - Habilitando ${extensions.length} extension(s)...`));
27
+ console.log(chalk.white(` - ${getT('restore.steps.databaseSettings.enabling', { count: extensions.length })}`));
26
28
 
27
29
  for (const ext of extensions) {
28
30
  const extName = typeof ext === 'string' ? ext : ext.name;
29
- console.log(chalk.white(` - ${extName}`));
31
+ console.log(chalk.white(` ${getT('restore.steps.databaseSettings.extension', { extName })}`));
30
32
 
31
33
  const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${extName};`;
32
34
 
33
35
  const urlMatch = targetProject.targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
34
36
 
35
37
  if (!urlMatch) {
36
- console.log(chalk.yellow(` ⚠️ URL inválida para ${extName}`));
38
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.databaseSettings.invalidUrl', { extName })}`));
37
39
  continue;
38
40
  }
39
41
 
@@ -49,17 +51,18 @@ module.exports = async ({ backupPath, targetProject }) => {
49
51
  try {
50
52
  execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
51
53
  } catch {
52
- console.log(chalk.yellow(` ⚠️ ${extName} - extension já existe ou não pode ser habilitada`));
54
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.databaseSettings.extensionExists', { extName })}`));
53
55
  }
54
56
  }
55
57
  }
56
58
 
57
- console.log(chalk.green('Database Settings restaurados com sucesso!'));
59
+ console.log(chalk.green(`${getT('restore.steps.databaseSettings.success')}`));
58
60
 
59
61
  return { success: true, extensions_count: extensions.length };
60
62
 
61
63
  } catch (error) {
62
- console.error(chalk.red(` ❌ Erro ao restaurar Database Settings: ${error.message}`));
64
+ const getT = global.smoonbI18n?.t || t;
65
+ console.error(chalk.red(` ❌ ${getT('restore.steps.databaseSettings.error', { message: error.message })}`));
63
66
  throw error;
64
67
  }
65
68
  };
@@ -2,6 +2,7 @@ const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const inquirer = require('inquirer');
5
+ const { t } = require('../../../i18n');
5
6
 
6
7
  /**
7
8
  * Etapa 8: Restaurar Realtime Settings (interativo - exibir URL e valores)
@@ -9,19 +10,20 @@ const inquirer = require('inquirer');
9
10
  module.exports = async ({ backupPath, targetProject }) => {
10
11
 
11
12
  try {
13
+ const getT = global.smoonbI18n?.t || t;
12
14
  const realtimeSettingsPath = path.join(backupPath, 'realtime-settings.json');
13
15
 
14
16
  if (!fs.existsSync(realtimeSettingsPath)) {
15
- console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Realtime encontrada no backup'));
17
+ console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.realtime.notFound')}`));
16
18
  return;
17
19
  }
18
20
 
19
21
  const realtimeSettings = JSON.parse(fs.readFileSync(realtimeSettingsPath, 'utf8'));
20
22
  const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/realtime/settings`;
21
23
 
22
- console.log(chalk.green('\n ✅ URL para configuração manual:'));
24
+ console.log(chalk.green(`\n ✅ ${getT('restore.steps.realtime.urlTitle')}`));
23
25
  console.log(chalk.cyan(` ${dashboardUrl}`));
24
- console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
26
+ console.log(chalk.yellow(`\n 📋 ${getT('restore.steps.realtime.configureTitle')}`));
25
27
 
26
28
  if (realtimeSettings.realtime_settings?.settings) {
27
29
  Object.values(realtimeSettings.realtime_settings.settings).forEach((setting) => {
@@ -32,18 +34,19 @@ module.exports = async ({ backupPath, targetProject }) => {
32
34
  });
33
35
  }
34
36
 
35
- console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
37
+ console.log(chalk.yellow(`\n ⚠️ ${getT('restore.steps.realtime.pressEnter')}`));
36
38
 
37
39
  await inquirer.prompt([{
38
40
  type: 'input',
39
41
  name: 'continue',
40
- message: 'Pressione Enter para continuar'
42
+ message: getT('restore.steps.realtime.pressEnter')
41
43
  }]);
42
44
 
43
- console.log(chalk.green('Realtime Settings processados'));
45
+ console.log(chalk.green(`${getT('restore.steps.realtime.success')}`));
44
46
 
45
47
  } catch (error) {
46
- console.error(chalk.red(` ❌ Erro ao processar Realtime Settings: ${error.message}`));
48
+ const getT = global.smoonbI18n?.t || t;
49
+ console.error(chalk.red(` ❌ ${getT('restore.steps.realtime.error', { message: error.message })}`));
47
50
  }
48
51
  };
49
52
 
@@ -1,6 +1,7 @@
1
1
  const chalk = require('chalk');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
+ const { t } = require('../../i18n');
4
5
 
5
6
  /**
6
7
  * Listar backups válidos (aceita .backup.gz e .backup)
@@ -67,17 +68,18 @@ function formatBytes(bytes) {
67
68
  * Mostrar resumo da restauração
68
69
  */
69
70
  function showRestoreSummary(backup, components, targetProject) {
70
- console.log(chalk.blue('\n📋 Resumo da Restauração:'));
71
- console.log(chalk.blue(''.repeat(80)));
72
- console.log(chalk.cyan(`📦 Backup: ${backup.name}`));
73
- console.log(chalk.cyan(`📤 Projeto Origem: ${backup.projectId}`));
74
- console.log(chalk.cyan(`📥 Projeto Destino: ${targetProject.targetProjectId}`));
71
+ const getT = global.smoonbI18n?.t || t;
72
+ console.log(chalk.blue(`\n📋 ${getT('restore.utils.summaryTitle')}`));
73
+ console.log(chalk.blue(getT('restore.utils.summarySeparator')));
74
+ console.log(chalk.cyan(`📦 ${getT('restore.utils.backup', { name: backup.name })}`));
75
+ console.log(chalk.cyan(`📤 ${getT('restore.utils.sourceProject', { projectId: backup.projectId })}`));
76
+ console.log(chalk.cyan(`📥 ${getT('restore.utils.targetProject', { projectId: targetProject.targetProjectId })}`));
75
77
  console.log('');
76
- console.log(chalk.cyan('Componentes que serão restaurados:'));
78
+ console.log(chalk.cyan(getT('restore.utils.componentsTitle')));
77
79
  console.log('');
78
80
 
79
81
  if (components.database) {
80
- console.log('✅ Database (psql -f via Docker)');
82
+ console.log(`✅ ${getT('restore.utils.database')}`);
81
83
  }
82
84
 
83
85
  if (components.edgeFunctions) {
@@ -85,30 +87,30 @@ function showRestoreSummary(backup, components, targetProject) {
85
87
  const functions = fs.readdirSync(edgeFunctionsDir).filter(item =>
86
88
  fs.statSync(path.join(edgeFunctionsDir, item)).isDirectory()
87
89
  );
88
- console.log(`⚡ Edge Functions: ${functions.length} function(s)`);
90
+ console.log(`⚡ ${getT('restore.utils.edgeFunctions', { count: functions.length })}`);
89
91
  functions.forEach(func => console.log(` - ${func}`));
90
92
  }
91
93
 
92
94
  if (components.authSettings) {
93
- console.log('🔐 Auth Settings: Exibir URL e valores para configuração manual');
95
+ console.log(`🔐 ${getT('restore.utils.authSettings')}`);
94
96
  }
95
97
 
96
98
  if (components.storage) {
97
99
  const backupPath = backup.path;
98
100
  const storageZipFiles = fs.readdirSync(backupPath).filter(f => f.endsWith('.storage.zip'));
99
101
  if (storageZipFiles.length > 0) {
100
- console.log('📦 Storage Buckets: Restauração automática de buckets e arquivos via API');
102
+ console.log(`📦 ${getT('restore.utils.storageBuckets')}`);
101
103
  } else {
102
- console.log('📦 Storage Buckets: Exibir informações (apenas metadados - arquivo .storage.zip não encontrado)');
104
+ console.log(`📦 ${getT('restore.utils.storageMetadataOnly')}`);
103
105
  }
104
106
  }
105
107
 
106
108
  if (components.databaseSettings) {
107
- console.log('🔧 Database Extensions and Settings: Restaurar via SQL');
109
+ console.log(`🔧 ${getT('restore.utils.databaseSettings')}`);
108
110
  }
109
111
 
110
112
  if (components.realtimeSettings) {
111
- console.log('🔄 Realtime Settings: Exibir URL e valores para configuração manual');
113
+ console.log(`🔄 ${getT('restore.utils.realtimeSettings')}`);
112
114
  }
113
115
 
114
116
  console.log('');