smoonb 0.0.75 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.75",
3
+ "version": "0.0.76",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -16,7 +16,8 @@ module.exports = async ({ databaseUrl, backupDir }) => {
16
16
  const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
17
17
 
18
18
  if (!urlMatch) {
19
- throw new Error('Database URL inválida');
19
+ const getT = global.smoonbI18n?.t || t;
20
+ throw new Error(getT('error.databaseUrlInvalidSimple'));
20
21
  }
21
22
 
22
23
  const [, username, password, host, port] = urlMatch;
@@ -16,7 +16,8 @@ module.exports = async ({ databaseUrl, projectId, backupDir }) => {
16
16
  const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
17
17
 
18
18
  if (!urlMatch) {
19
- throw new Error('Database URL inválida');
19
+ const getT = global.smoonbI18n?.t || t;
20
+ throw new Error(getT('error.databaseUrlInvalidSimple'));
20
21
  }
21
22
 
22
23
  const [, username, password, host, port, database] = urlMatch;
@@ -37,7 +37,7 @@ module.exports = async ({ projectId, accessToken, backupDir, supabaseUrl, supaba
37
37
  if (!buckets || buckets.length === 0) {
38
38
  console.log(chalk.white(` - ${getT('backup.steps.storage.noBuckets')}`));
39
39
  await writeJson(path.join(storageDir, 'README.md'), {
40
- message: 'Nenhum bucket de Storage encontrado neste projeto'
40
+ message: getT('backup.steps.storage.noBucketsMessage')
41
41
  });
42
42
  return { success: true, buckets: [] };
43
43
  }
@@ -79,7 +79,7 @@ module.exports = async (context) => {
79
79
  if (!functions || functions.length === 0) {
80
80
  console.log(chalk.white(` - ${getT('backup.steps.functions.noneFound')}`));
81
81
  await writeJson(path.join(functionsDir, 'README.md'), {
82
- message: 'Nenhuma Edge Function encontrada neste projeto'
82
+ message: getT('backup.steps.functions.noFunctionsMessage')
83
83
  });
84
84
  return { success: true, reason: 'no_functions', functions: [] };
85
85
  }
@@ -6,7 +6,7 @@ const { cleanDir, countFiles, copyDirSafe } = require('../../../utils/fsExtra');
6
6
  const { t } = require('../../../i18n');
7
7
 
8
8
  /**
9
- * Etapa 10: Backup Migrations (NOVA ETAPA INDEPENDENTE)
9
+ * Etapa 10: Migrations Backup (NOVA ETAPA INDEPENDENTE)
10
10
  */
11
11
  module.exports = async (context) => {
12
12
  const { projectId, accessToken, databaseUrl, backupDir } = context;
@@ -98,13 +98,15 @@ async function checkDatabaseConnection(databaseUrl) {
98
98
  `psql "${databaseUrl}" -t -c "SELECT 1 as test_connection;"`
99
99
  );
100
100
 
101
+ const getT = global.smoonbI18n?.t || t;
101
102
  if (stdout.trim() === '1') {
102
- return { status: 'ok', message: 'Conexão estabelecida com sucesso' };
103
+ return { status: 'ok', message: getT('check.connectionSuccess') };
103
104
  } else {
104
- return { status: 'error', message: 'Resposta inesperada da database' };
105
+ return { status: 'error', message: getT('check.unexpectedResponse') };
105
106
  }
106
107
  } catch (error) {
107
- return { status: 'error', message: `Falha na conexão: ${error.message}` };
108
+ const getT = global.smoonbI18n?.t || t;
109
+ return { status: 'error', message: getT('check.connectionError', { message: error.message }) };
108
110
  }
109
111
  }
110
112
 
@@ -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
  };
@@ -26,13 +26,13 @@ module.exports = async (backups) => {
26
26
 
27
27
  const question = (query) => new Promise(resolve => rl.question(query, resolve));
28
28
 
29
- 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 })} `);
30
30
  rl.close();
31
31
 
32
32
  const backupIndex = parseInt(choice) - 1;
33
33
 
34
34
  if (backupIndex < 0 || backupIndex >= backups.length) {
35
- throw new Error('Número inválido');
35
+ throw new Error(getT('restore.steps.backupSelection.invalid'));
36
36
  }
37
37
 
38
38
  return backups[backupIndex];
@@ -38,7 +38,8 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
38
38
  const urlMatch = targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
39
39
 
40
40
  if (!urlMatch) {
41
- 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'));
42
43
  }
43
44
 
44
45
  // Comando psql conforme documentação oficial Supabase
@@ -40,7 +40,7 @@ module.exports = async ({ backupPath, targetProject }) => {
40
40
  await inquirer.prompt([{
41
41
  type: 'input',
42
42
  name: 'continue',
43
- message: 'Pressione Enter para continuar'
43
+ message: getT('restore.steps.auth.pressEnter')
44
44
  }]);
45
45
 
46
46
  console.log(chalk.green(` ✅ ${getT('restore.steps.auth.success')}`));
@@ -108,11 +108,12 @@ module.exports = async ({ backupPath, targetProject }) => {
108
108
 
109
109
  // 4. Validar credenciais do projeto destino
110
110
  if (!targetProject.targetProjectId || !targetProject.targetAccessToken) {
111
- 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'));
112
113
  }
113
114
 
114
115
  if (!targetProject.targetUrl || !targetProject.targetServiceKey) {
115
- 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'));
116
117
  }
117
118
 
118
119
  // 4.1 Obter project ID do projeto origem e validar substituição
@@ -39,7 +39,7 @@ module.exports = async ({ backupPath, targetProject }) => {
39
39
  await inquirer.prompt([{
40
40
  type: 'input',
41
41
  name: 'continue',
42
- message: 'Pressione Enter para continuar'
42
+ message: getT('restore.steps.realtime.pressEnter')
43
43
  }]);
44
44
 
45
45
  console.log(chalk.green(` ✅ ${getT('restore.steps.realtime.success')}`));
@@ -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('');
@@ -364,7 +364,7 @@
364
364
  "backup.steps.migrations.validation": "Validation: linked-ref = {linkedRef} (expected = {expected})",
365
365
  "backup.steps.migrations.cleaning": "Cleaning supabase/migrations...",
366
366
  "backup.steps.migrations.downloading": "Downloading all migrations from server using migration fetch...",
367
- "backup.steps.migrations.downloaded": "Files downloaded: {count} migrations",
367
+ "backup.steps.migrations.downloaded": "Downloaded files: {count} migrations",
368
368
  "backup.steps.migrations.copying": "Copying supabase/migrations → {path} ({count} files)...",
369
369
 
370
370
  "backup.complete.title": "FULL BACKUP COMPLETED VIA DOCKER!",
@@ -437,7 +437,7 @@
437
437
  "backup.steps.migrations.downloading": "Downloading all migrations from server using migration fetch...",
438
438
  "backup.steps.migrations.fetchError": "Error executing migration fetch: {message}",
439
439
  "backup.steps.migrations.fetchTip": "Verify that the project is correctly linked and the token is valid.",
440
- "backup.steps.migrations.downloaded": "Files downloaded: {count} migrations",
440
+ "backup.steps.migrations.downloaded": "Downloaded files: {count} migrations",
441
441
  "backup.steps.migrations.copying": "Copying supabase/migrations → {path} ({count} files)...",
442
442
  "backup.steps.migrations.copied": "{count} migration(s) copied",
443
443
  "backup.steps.migrations.cleaned": "supabase/migrations deleted.",
@@ -573,6 +573,105 @@
573
573
  "restore.steps.realtime.success": "Realtime Settings processed",
574
574
 
575
575
  "restore.steps.backupSelection.title": "Available backups:",
576
- "restore.steps.backupSelection.separator": "═══════════════════════════════════════════════════════════════════════════════"
576
+ "restore.steps.backupSelection.separator": "═══════════════════════════════════════════════════════════════════════════════",
577
+ "restore.steps.backupSelection.input": "Enter the backup number to restore ({min}-{max}):",
578
+ "restore.steps.backupSelection.invalid": "Invalid number",
579
+
580
+ "utils.realtime.previousFound": "A previous Realtime Settings recording was found.",
581
+ "utils.realtime.reuse": "Do you want to reuse the previous configurations?",
582
+ "utils.realtime.reusing": "Reusing Realtime Settings configurations from previous backup...",
583
+ "utils.realtime.configTitle": "Realtime Settings Configuration",
584
+ "utils.realtime.separator": "════════════════════════════════════════════════════════════════",
585
+ "utils.realtime.access": "Access: {url}",
586
+ "utils.realtime.note": "Note the values of the 7 parameters below:",
587
+ "utils.realtime.questions": "Answer the questions below (press Enter to use default value):",
588
+ "utils.realtime.saved": "Realtime Settings configurations saved!",
589
+ "utils.realtime.copying": "Copying Realtime Settings from previous backup...",
590
+
591
+ "utils.supabaseLink.linking": "Resetting link and linking project: {projectId}...",
592
+ "utils.supabaseLink.validation": "Validation: linked-ref = {linkedRef} (expected = {expected})",
593
+ "utils.supabaseLink.validationFailed": "Validation failed: linked-ref = {linkedRef} (expected = {expected}). The linked project does not match the expected project.",
594
+ "utils.supabaseLink.cannotValidate": "Could not validate link: {message}",
595
+ "utils.supabaseLink.linkFailed": "Failed to link project {projectId}: {message}",
596
+
597
+ "restore.index.envUpdated": ".env.local updated successfully. No keys renamed; values synchronized.",
598
+ "restore.index.searchingBackups": "Searching for backups in: {path}",
599
+ "restore.index.noBackupsFound": "No valid backups found",
600
+ "restore.index.runBackupFirst": "Run first: npx smoonb backup",
601
+ "restore.index.summaryTitle": "RESTORATION SUMMARY:",
602
+ "restore.index.selectedBackup": "Selected backup: {name}",
603
+ "restore.index.targetProject": "Target project: {projectId}",
604
+ "restore.index.database": "Database: {value}",
605
+ "restore.index.edgeFunctions": "Edge Functions: {value}",
606
+ "restore.index.authSettings": "Auth Settings: {value}",
607
+ "restore.index.storage": "Storage: {value}",
608
+ "restore.index.databaseSettings": "Database Settings: {value}",
609
+ "restore.index.realtimeSettings": "Realtime Settings: {value}",
610
+ "restore.index.yes": "Yes",
611
+ "restore.index.no": "No",
612
+ "restore.index.notConfigured": "(not configured)",
613
+ "restore.index.confirmRestore": "Do you want to start the restoration with these configurations?",
614
+ "restore.index.restoreCancelled": "Restoration cancelled.",
615
+ "restore.index.startingRestore": "Starting restoration...",
616
+ "restore.index.restoringDatabase": "Restoring Database...",
617
+ "restore.index.restoringEdgeFunctions": "Restoring Edge Functions...",
618
+ "restore.index.restoringAuthSettings": "Restoring Auth Settings...",
619
+ "restore.index.restoringStorageBuckets": "Restoring Storage Buckets...",
620
+ "restore.index.restoringDatabaseSettings": "Restoring Database Settings...",
621
+ "restore.index.restoringRealtimeSettings": "Restoring Realtime Settings...",
622
+ "restore.index.restoreComplete": "RESTORATION COMPLETE FINISHED!",
623
+ "restore.index.targetProjectFinal": "Target project: {projectId}",
624
+ "restore.index.databaseRestored": "Database: Restored successfully via Docker",
625
+ "restore.index.edgeFunctionsRestored": "Edge Functions: {success}/{total} functions restored",
626
+ "restore.index.authSettingsRestored": "Auth Settings: Settings displayed for manual configuration",
627
+ "restore.index.storageRestored": "Storage: {buckets} bucket(s) restored, {files} file(s) uploaded",
628
+ "restore.index.storageMetadataOnly": "Storage: {buckets} bucket(s) found - metadata only (.storage.zip file not found)",
629
+ "restore.index.databaseSettingsRestored": "Database Settings: Extensions and settings restored via SQL",
630
+ "restore.index.realtimeSettingsRestored": "Realtime Settings: Settings displayed for manual configuration",
631
+ "restore.index.error": "Error during restoration: {message}",
632
+
633
+ "restore.utils.componentsTitle": "Components that will be restored:",
634
+ "restore.utils.database": "Database (psql -f via Docker)",
635
+ "restore.utils.edgeFunctions": "Edge Functions: {count} function(s)",
636
+ "restore.utils.authSettings": "Auth Settings: Display URL and values for manual configuration",
637
+ "restore.utils.storageBuckets": "Storage Buckets: Automatic restoration of buckets and files via API",
638
+ "restore.utils.storageMetadataOnly": "Storage Buckets: Display information (metadata only - .storage.zip file not found)",
639
+ "restore.utils.databaseSettings": "Database Extensions and Settings: Restore via SQL",
640
+ "restore.utils.realtimeSettings": "Realtime Settings: Display URL and values for manual configuration",
641
+ "restore.utils.summaryTitle": "Restoration Summary:",
642
+ "restore.utils.summarySeparator": "═══════════════════════════════════════════════════════════════════════════════",
643
+ "restore.utils.backup": "Backup: {name}",
644
+ "restore.utils.sourceProject": "Source Project: {projectId}",
645
+ "restore.utils.targetProject": "Target Project: {projectId}",
646
+
647
+ "restore.steps.auth.pressEnter": "Press Enter to continue",
648
+ "restore.steps.realtime.pressEnter": "Press Enter to continue",
649
+
650
+ "restore.import.fileNotFound": "Backup file not found: {path}",
651
+ "restore.import.invalidFormat": "Backup file must be .backup.gz or .backup",
652
+ "restore.import.storageNotFound": "Storage file not found: {path}",
653
+ "restore.import.storageInvalidFormat": "Storage file must be .storage.zip",
654
+ "restore.import.invalidFileName": "Backup file name is not in the expected Dashboard format. Expected format: db_cluster-DD-MM-YYYY@HH-MM-SS.backup.gz",
655
+ "restore.import.importing": "Importing backup to: {name}",
656
+ "restore.import.backupImported": "Backup file imported: {fileName} ({size} MB)",
657
+ "restore.import.storageImported": "Storage file imported: {fileName} ({size} MB)",
658
+ "restore.import.importingFile": "Importing backup file...",
659
+ "restore.import.importedSelected": "Backup imported and automatically selected: {name}",
660
+ "restore.import.cannotFind": "Could not find the imported backup",
661
+ "restore.import.envBackup": "Backup of .env.local: {path}",
662
+ "restore.import.envNotFound": "⚠️ .env.local file not found. It will be created during mapping.",
663
+ "restore.import.noComponents": "\n❌ No components selected for restoration!",
664
+
665
+ "backup.steps.functions.noFunctionsMessage": "No Edge Functions found in this project",
666
+ "backup.steps.storage.noBucketsMessage": "No Storage buckets found in this project",
667
+
668
+ "check.connectionSuccess": "Connection established successfully",
669
+ "check.unexpectedResponse": "Unexpected response from database",
670
+ "check.connectionError": "Connection failed: {message}",
671
+
672
+ "error.databaseUrlInvalid": "Invalid Database URL. Expected format: postgresql://user:password@host/database",
673
+ "error.databaseUrlInvalidSimple": "Invalid Database URL",
674
+ "error.storageCredentialsNotConfigured": "Target project credentials not configured. SUPABASE_PROJECT_ID and SUPABASE_ACCESS_TOKEN are required",
675
+ "error.supabaseCredentialsNotConfigured": "Supabase credentials not configured. NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are required"
577
676
  }
578
677
 
@@ -572,6 +572,105 @@
572
572
  "restore.steps.realtime.success": "Realtime Settings processados",
573
573
 
574
574
  "restore.steps.backupSelection.title": "Backups disponíveis:",
575
- "restore.steps.backupSelection.separator": "═══════════════════════════════════════════════════════════════════════════════"
575
+ "restore.steps.backupSelection.separator": "═══════════════════════════════════════════════════════════════════════════════",
576
+ "restore.steps.backupSelection.input": "Digite o número do backup para restaurar ({min}-{max}):",
577
+ "restore.steps.backupSelection.invalid": "Número inválido",
578
+
579
+ "utils.realtime.previousFound": "Foi identificada uma gravação anterior de Realtime Settings.",
580
+ "utils.realtime.reuse": "Deseja reutilizar as configurações anteriores?",
581
+ "utils.realtime.reusing": "Reutilizando configurações de Realtime Settings do backup anterior...",
582
+ "utils.realtime.configTitle": "Configurações de Realtime Settings",
583
+ "utils.realtime.separator": "════════════════════════════════════════════════════════════════",
584
+ "utils.realtime.access": "Acesse: {url}",
585
+ "utils.realtime.note": "Anote os valores dos 7 parâmetros abaixo:",
586
+ "utils.realtime.questions": "Responda as perguntas abaixo (pressione Enter para usar o valor padrão):",
587
+ "utils.realtime.saved": "Configurações de Realtime Settings salvas!",
588
+ "utils.realtime.copying": "Copiando Realtime Settings do backup anterior...",
589
+
590
+ "utils.supabaseLink.linking": "Zerando vínculo e linkando projeto: {projectId}...",
591
+ "utils.supabaseLink.validation": "Validação: linked-ref = {linkedRef} (esperado = {expected})",
592
+ "utils.supabaseLink.validationFailed": "Validação falhou: linked-ref = {linkedRef} (esperado = {expected}). O projeto linkado não corresponde ao projeto esperado.",
593
+ "utils.supabaseLink.cannotValidate": "Não foi possível validar o vínculo: {message}",
594
+ "utils.supabaseLink.linkFailed": "Falha ao linkar projeto {projectId}: {message}",
595
+
596
+ "restore.index.envUpdated": ".env.local atualizado com sucesso. Nenhuma chave renomeada; valores sincronizados.",
597
+ "restore.index.searchingBackups": "Buscando backups em: {path}",
598
+ "restore.index.noBackupsFound": "Nenhum backup válido encontrado",
599
+ "restore.index.runBackupFirst": "Execute primeiro: npx smoonb backup",
600
+ "restore.index.summaryTitle": "RESUMO DA RESTAURAÇÃO:",
601
+ "restore.index.selectedBackup": "Backup selecionado: {name}",
602
+ "restore.index.targetProject": "Projeto destino: {projectId}",
603
+ "restore.index.database": "Database: {value}",
604
+ "restore.index.edgeFunctions": "Edge Functions: {value}",
605
+ "restore.index.authSettings": "Auth Settings: {value}",
606
+ "restore.index.storage": "Storage: {value}",
607
+ "restore.index.databaseSettings": "Database Settings: {value}",
608
+ "restore.index.realtimeSettings": "Realtime Settings: {value}",
609
+ "restore.index.yes": "Sim",
610
+ "restore.index.no": "Não",
611
+ "restore.index.notConfigured": "(não configurado)",
612
+ "restore.index.confirmRestore": "Deseja iniciar a restauração com estas configurações?",
613
+ "restore.index.restoreCancelled": "Restauração cancelada.",
614
+ "restore.index.startingRestore": "Iniciando restauração...",
615
+ "restore.index.restoringDatabase": "Restaurando Database...",
616
+ "restore.index.restoringEdgeFunctions": "Restaurando Edge Functions...",
617
+ "restore.index.restoringAuthSettings": "Restaurando Auth Settings...",
618
+ "restore.index.restoringStorageBuckets": "Restaurando Storage Buckets...",
619
+ "restore.index.restoringDatabaseSettings": "Restaurando Database Settings...",
620
+ "restore.index.restoringRealtimeSettings": "Restaurando Realtime Settings...",
621
+ "restore.index.restoreComplete": "RESTAURAÇÃO COMPLETA FINALIZADA!",
622
+ "restore.index.targetProjectFinal": "Projeto destino: {projectId}",
623
+ "restore.index.databaseRestored": "Database: Restaurada com sucesso via Docker",
624
+ "restore.index.edgeFunctionsRestored": "Edge Functions: {success}/{total} functions restauradas",
625
+ "restore.index.authSettingsRestored": "Auth Settings: Configurações exibidas para configuração manual",
626
+ "restore.index.storageRestored": "Storage: {buckets} bucket(s) restaurado(s), {files} arquivo(s) enviado(s)",
627
+ "restore.index.storageMetadataOnly": "Storage: {buckets} bucket(s) encontrado(s) - apenas metadados (arquivo .storage.zip não encontrado)",
628
+ "restore.index.databaseSettingsRestored": "Database Settings: Extensões e configurações restauradas via SQL",
629
+ "restore.index.realtimeSettingsRestored": "Realtime Settings: Configurações exibidas para configuração manual",
630
+ "restore.index.error": "Erro na restauração: {message}",
631
+
632
+ "restore.utils.componentsTitle": "Componentes que serão restaurados:",
633
+ "restore.utils.database": "Database (psql -f via Docker)",
634
+ "restore.utils.edgeFunctions": "Edge Functions: {count} function(s)",
635
+ "restore.utils.authSettings": "Auth Settings: Exibir URL e valores para configuração manual",
636
+ "restore.utils.storageBuckets": "Storage Buckets: Restauração automática de buckets e arquivos via API",
637
+ "restore.utils.storageMetadataOnly": "Storage Buckets: Exibir informações (apenas metadados - arquivo .storage.zip não encontrado)",
638
+ "restore.utils.databaseSettings": "Database Extensions and Settings: Restaurar via SQL",
639
+ "restore.utils.realtimeSettings": "Realtime Settings: Exibir URL e valores para configuração manual",
640
+ "restore.utils.summaryTitle": "Resumo da Restauração:",
641
+ "restore.utils.summarySeparator": "═══════════════════════════════════════════════════════════════════════════════",
642
+ "restore.utils.backup": "Backup: {name}",
643
+ "restore.utils.sourceProject": "Projeto Origem: {projectId}",
644
+ "restore.utils.targetProject": "Projeto Destino: {projectId}",
645
+
646
+ "restore.steps.auth.pressEnter": "Pressione Enter para continuar",
647
+ "restore.steps.realtime.pressEnter": "Pressione Enter para continuar",
648
+
649
+ "restore.import.fileNotFound": "Arquivo de backup não encontrado: {path}",
650
+ "restore.import.invalidFormat": "Arquivo de backup deve ser .backup.gz ou .backup",
651
+ "restore.import.storageNotFound": "Arquivo de storage não encontrado: {path}",
652
+ "restore.import.storageInvalidFormat": "Arquivo de storage deve ser .storage.zip",
653
+ "restore.import.invalidFileName": "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",
654
+ "restore.import.importing": "Importando backup para: {name}",
655
+ "restore.import.backupImported": "Arquivo de backup importado: {fileName} ({size} MB)",
656
+ "restore.import.storageImported": "Arquivo de storage importado: {fileName} ({size} MB)",
657
+ "restore.import.importingFile": "Importando arquivo de backup...",
658
+ "restore.import.importedSelected": "Backup importado e selecionado automaticamente: {name}",
659
+ "restore.import.cannotFind": "Não foi possível encontrar o backup importado",
660
+ "restore.import.envBackup": "Backup do .env.local: {path}",
661
+ "restore.import.envNotFound": "⚠️ Arquivo .env.local não encontrado. Será criado durante o mapeamento.",
662
+ "restore.import.noComponents": "\n❌ Nenhum componente selecionado para restauração!",
663
+
664
+ "backup.steps.functions.noFunctionsMessage": "Nenhuma Edge Function encontrada neste projeto",
665
+ "backup.steps.storage.noBucketsMessage": "Nenhum bucket de Storage encontrado neste projeto",
666
+
667
+ "check.connectionSuccess": "Conexão estabelecida com sucesso",
668
+ "check.unexpectedResponse": "Resposta inesperada da database",
669
+ "check.connectionError": "Falha na conexão: {message}",
670
+
671
+ "error.databaseUrlInvalid": "Database URL inválida. Formato esperado: postgresql://user:password@host/database",
672
+ "error.databaseUrlInvalidSimple": "Database URL inválida",
673
+ "error.storageCredentialsNotConfigured": "Credenciais do projeto destino não configuradas. É necessário SUPABASE_PROJECT_ID e SUPABASE_ACCESS_TOKEN",
674
+ "error.supabaseCredentialsNotConfigured": "Credenciais do Supabase não configuradas. É necessário NEXT_PUBLIC_SUPABASE_URL e SUPABASE_SERVICE_ROLE_KEY"
576
675
  }
577
676
 
@@ -1,6 +1,7 @@
1
1
  const readline = require('readline');
2
2
  const fs = require('fs').promises;
3
3
  const path = require('path');
4
+ const { t } = require('../i18n');
4
5
 
5
6
  /**
6
7
  * Captura configurações de Realtime Settings interativamente
@@ -16,8 +17,10 @@ async function captureRealtimeSettings(projectId, backupDir, skipInteractive = f
16
17
  // Tentar ler configurações de backup anterior
17
18
  const previousSettings = await getPreviousRealtimeSettings(backupDir);
18
19
 
20
+ const getT = global.smoonbI18n?.t || t;
21
+
19
22
  if (skipInteractive && previousSettings) {
20
- console.log('📋 Copiando Realtime Settings do backup anterior...');
23
+ console.log(`📋 ${getT('utils.realtime.copying')}`);
21
24
  await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
22
25
  return previousSettings;
23
26
  }
@@ -25,23 +28,23 @@ async function captureRealtimeSettings(projectId, backupDir, skipInteractive = f
25
28
  if (previousSettings && !skipInteractive) {
26
29
  const shouldReuse = await askToReusePreviousSettings();
27
30
  if (shouldReuse) {
28
- console.log('📋 Reutilizando configurações de Realtime Settings do backup anterior...');
31
+ console.log(`📋 ${getT('utils.realtime.reusing')}`);
29
32
  await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
30
33
  return previousSettings;
31
34
  }
32
35
  }
33
36
 
34
37
  // Capturar configurações interativamente
35
- console.log('\n🔧 Configurações de Realtime Settings');
36
- console.log(''.repeat(50));
37
- console.log(`📱 Acesse: ${dashboardUrl}`);
38
- console.log('📝 Anote os valores dos 7 parâmetros abaixo:\n');
38
+ console.log(`\n🔧 ${getT('utils.realtime.configTitle')}`);
39
+ console.log(getT('utils.realtime.separator'));
40
+ console.log(`📱 ${getT('utils.realtime.access', { url: dashboardUrl })}`);
41
+ console.log(`📝 ${getT('utils.realtime.note')}\n`);
39
42
 
40
43
  const settings = await captureSettingsInteractively(projectId, previousSettings);
41
44
 
42
45
  // Salvar configurações
43
46
  await fs.writeFile(settingsFile, JSON.stringify(settings, null, 2));
44
- console.log('\n✅ Configurações de Realtime Settings salvas!');
47
+ console.log(`\n✅ ${getT('utils.realtime.saved')}`);
45
48
 
46
49
  return settings;
47
50
  }
@@ -87,13 +90,14 @@ async function getPreviousRealtimeSettings(backupDir) {
87
90
  * @returns {Promise<boolean>} true se deve reutilizar
88
91
  */
89
92
  async function askToReusePreviousSettings() {
93
+ const getT = global.smoonbI18n?.t || t;
90
94
  const rl = readline.createInterface({
91
95
  input: process.stdin,
92
96
  output: process.stdout
93
97
  });
94
98
 
95
99
  return new Promise((resolve) => {
96
- rl.question('🔄 Foi identificada uma gravação anterior de Realtime Settings.\n Deseja reutilizar as configurações anteriores? (S/n): ', (answer) => {
100
+ rl.question(`🔄 ${getT('utils.realtime.previousFound')}\n ${getT('utils.realtime.reuse')} (S/n): `, (answer) => {
97
101
  rl.close();
98
102
  const shouldReuse = !answer.toLowerCase().startsWith('n');
99
103
  resolve(shouldReuse);
@@ -121,8 +125,10 @@ async function captureSettingsInteractively(projectId, previousSettings) {
121
125
  });
122
126
  };
123
127
 
128
+ const getT = global.smoonbI18n?.t || t;
129
+
124
130
  try {
125
- console.log('📋 Responda as perguntas abaixo (pressione Enter para usar o valor padrão):\n');
131
+ console.log(`📋 ${getT('utils.realtime.questions')}\n`);
126
132
 
127
133
  // Valores padrão baseados na imagem ou configurações anteriores
128
134
  const defaults = previousSettings?.realtime_settings?.settings || {
@@ -2,6 +2,7 @@ const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const fs = require('fs').promises;
4
4
  const { execSync } = require('child_process');
5
+ const { t } = require('../i18n');
5
6
 
6
7
  /**
7
8
  * Extrai a senha da URL de conexão PostgreSQL
@@ -9,9 +10,11 @@ const { execSync } = require('child_process');
9
10
  * @returns {string} - Senha extraída
10
11
  */
11
12
  function extractPasswordFromDbUrl(dbUrl) {
13
+ const { t } = require('../i18n');
14
+ const getT = global.smoonbI18n?.t || t;
12
15
  const urlMatch = dbUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
13
16
  if (!urlMatch) {
14
- throw new Error('Database URL inválida');
17
+ throw new Error(getT('error.databaseUrlInvalidSimple'));
15
18
  }
16
19
  const [, , password] = urlMatch;
17
20
  return password;
@@ -25,10 +28,11 @@ function extractPasswordFromDbUrl(dbUrl) {
25
28
  * @returns {Promise<void>}
26
29
  */
27
30
  async function ensureCleanLink(projectRef, accessToken, dbPassword) {
31
+ const getT = global.smoonbI18n?.t || t;
28
32
  const tempDir = path.join(process.cwd(), 'supabase', '.temp');
29
33
 
30
34
  // Remover supabase/.temp completamente
31
- console.log(chalk.white(` - Zerando vínculo e linkando projeto: ${projectRef}...`));
35
+ console.log(chalk.white(` - ${getT('utils.supabaseLink.linking', { projectId: projectRef })}`));
32
36
 
33
37
  try {
34
38
  await fs.rm(tempDir, { recursive: true, force: true });
@@ -50,7 +54,7 @@ async function ensureCleanLink(projectRef, accessToken, dbPassword) {
50
54
  env
51
55
  });
52
56
  } catch (error) {
53
- throw new Error(`Falha ao linkar projeto ${projectRef}: ${error.message}`);
57
+ throw new Error(getT('utils.supabaseLink.linkFailed', { projectId: projectRef, message: error.message }));
54
58
  }
55
59
 
56
60
  // Validar: ler supabase/.temp/project-ref e verificar se == projectRef
@@ -60,18 +64,15 @@ async function ensureCleanLink(projectRef, accessToken, dbPassword) {
60
64
  const linkedRefTrimmed = linkedRef.trim();
61
65
 
62
66
  if (linkedRefTrimmed !== projectRef) {
63
- throw new Error(
64
- `Validação falhou: linked-ref = ${linkedRefTrimmed} (esperado = ${projectRef}). ` +
65
- `O projeto linkado não corresponde ao projeto esperado.`
66
- );
67
+ throw new Error(getT('utils.supabaseLink.validationFailed', { linkedRef: linkedRefTrimmed, expected: projectRef }));
67
68
  }
68
69
 
69
- console.log(chalk.white(` - Validação: linked-ref = ${linkedRefTrimmed} (esperado = ${projectRef})`));
70
+ console.log(chalk.white(` - ${getT('utils.supabaseLink.validation', { linkedRef: linkedRefTrimmed, expected: projectRef })}`));
70
71
  } catch (error) {
71
- if (error.message.includes('Validação falhou')) {
72
+ if (error.message.includes('Validation failed') || error.message.includes('Validação falhou')) {
72
73
  throw error;
73
74
  }
74
- throw new Error(`Não foi possível validar o vínculo: ${error.message}`);
75
+ throw new Error(getT('utils.supabaseLink.cannotValidate', { message: error.message }));
75
76
  }
76
77
  }
77
78