smoonb 0.0.75 → 0.0.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/backup/steps/01-database.js +2 -1
- package/src/commands/backup/steps/03-database-settings.js +2 -1
- package/src/commands/backup/steps/06-storage.js +1 -1
- package/src/commands/backup/steps/08-edge-functions.js +1 -1
- package/src/commands/backup/steps/10-migrations.js +1 -1
- package/src/commands/check.js +5 -3
- package/src/commands/restore/index.js +51 -46
- package/src/commands/restore/steps/00-backup-selection.js +2 -2
- package/src/commands/restore/steps/03-database.js +2 -1
- package/src/commands/restore/steps/05-auth-settings.js +2 -1
- package/src/commands/restore/steps/06-storage.js +3 -2
- package/src/commands/restore/steps/08-realtime-settings.js +2 -1
- package/src/commands/restore/utils.js +15 -13
- package/src/i18n/locales/en.json +102 -3
- package/src/i18n/locales/pt-BR.json +100 -1
- package/src/utils/realtime-settings.js +15 -9
- package/src/utils/supabaseLink.js +11 -10
package/package.json
CHANGED
|
@@ -16,7 +16,8 @@ module.exports = async ({ databaseUrl, backupDir }) => {
|
|
|
16
16
|
const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
|
|
17
17
|
|
|
18
18
|
if (!urlMatch) {
|
|
19
|
-
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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
|
|
9
|
+
* Etapa 10: Migrations Backup (NOVA ETAPA INDEPENDENTE)
|
|
10
10
|
*/
|
|
11
11
|
module.exports = async (context) => {
|
|
12
12
|
const { projectId, accessToken, databaseUrl, backupDir } = context;
|
package/src/commands/check.js
CHANGED
|
@@ -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: '
|
|
103
|
+
return { status: 'ok', message: getT('check.connectionSuccess') };
|
|
103
104
|
} else {
|
|
104
|
-
return { status: 'error', message: '
|
|
105
|
+
return { status: 'error', message: getT('check.unexpectedResponse') };
|
|
105
106
|
}
|
|
106
107
|
} catch (error) {
|
|
107
|
-
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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('
|
|
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(`📁
|
|
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(`✅
|
|
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(`✅
|
|
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(`📁
|
|
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('
|
|
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('
|
|
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(`📁
|
|
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('
|
|
201
|
+
throw new Error(getT('restore.import.cannotFind'));
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
console.log(chalk.green(`✅
|
|
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(`📁
|
|
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(
|
|
211
|
-
console.log(chalk.yellow(
|
|
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('
|
|
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(
|
|
230
|
-
console.log(chalk.white(` 📁
|
|
231
|
-
console.log(chalk.white(` 🎯
|
|
232
|
-
console.log(chalk.white(` 📊
|
|
233
|
-
console.log(chalk.white(` ⚡
|
|
234
|
-
console.log(chalk.white(` 🔐
|
|
235
|
-
console.log(chalk.white(` 📦
|
|
236
|
-
console.log(chalk.white(` 🔧
|
|
237
|
-
console.log(chalk.white(` 🔄
|
|
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('
|
|
246
|
+
const finalOk = await confirm(getT('restore.index.confirmRestore'), true);
|
|
244
247
|
if (!finalOk) {
|
|
245
|
-
console.log(chalk.yellow('
|
|
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(
|
|
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} -
|
|
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} -
|
|
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} -
|
|
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} -
|
|
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} -
|
|
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} -
|
|
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(
|
|
353
|
-
console.log(chalk.blue(`🎯
|
|
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(`📊
|
|
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(`⚡
|
|
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(`🔐
|
|
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(`📦
|
|
378
|
+
console.log(chalk.green(`📦 ${getT('restore.index.storageRestored', { buckets: bucketCount, files: totalFiles })}`));
|
|
376
379
|
} else {
|
|
377
|
-
console.log(chalk.green(`📦
|
|
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(`🔧
|
|
385
|
+
console.log(chalk.green(`🔧 ${getT('restore.index.databaseSettingsRestored')}`));
|
|
383
386
|
}
|
|
384
387
|
|
|
385
388
|
if (restoreResults.realtimeSettings) {
|
|
386
|
-
console.log(chalk.green(`🔄
|
|
389
|
+
console.log(chalk.green(`🔄 ${getT('restore.index.realtimeSettingsRestored')}`));
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
} catch (error) {
|
|
390
|
-
|
|
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(`\
|
|
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('
|
|
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
|
-
|
|
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,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
40
40
|
await inquirer.prompt([{
|
|
41
41
|
type: 'input',
|
|
42
42
|
name: 'continue',
|
|
43
|
-
message: '
|
|
43
|
+
message: getT('restore.steps.auth.pressEnter'),
|
|
44
|
+
prefix: ''
|
|
44
45
|
}]);
|
|
45
46
|
|
|
46
47
|
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
|
-
|
|
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('
|
|
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,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
39
39
|
await inquirer.prompt([{
|
|
40
40
|
type: 'input',
|
|
41
41
|
name: 'continue',
|
|
42
|
-
message: '
|
|
42
|
+
message: getT('restore.steps.realtime.pressEnter'),
|
|
43
|
+
prefix: ''
|
|
43
44
|
}]);
|
|
44
45
|
|
|
45
46
|
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
|
-
|
|
71
|
-
console.log(chalk.blue('
|
|
72
|
-
console.log(chalk.
|
|
73
|
-
console.log(chalk.cyan(
|
|
74
|
-
console.log(chalk.cyan(
|
|
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('
|
|
78
|
+
console.log(chalk.cyan(getT('restore.utils.componentsTitle')));
|
|
77
79
|
console.log('');
|
|
78
80
|
|
|
79
81
|
if (components.database) {
|
|
80
|
-
console.log(
|
|
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(`⚡
|
|
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(
|
|
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(
|
|
102
|
+
console.log(`📦 ${getT('restore.utils.storageBuckets')}`);
|
|
101
103
|
} else {
|
|
102
|
-
console.log(
|
|
104
|
+
console.log(`📦 ${getT('restore.utils.storageMetadataOnly')}`);
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
if (components.databaseSettings) {
|
|
107
|
-
console.log(
|
|
109
|
+
console.log(`🔧 ${getT('restore.utils.databaseSettings')}`);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
if (components.realtimeSettings) {
|
|
111
|
-
console.log(
|
|
113
|
+
console.log(`🔄 ${getT('restore.utils.realtimeSettings')}`);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
console.log('');
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -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": "
|
|
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": "
|
|
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(
|
|
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(
|
|
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(
|
|
36
|
-
console.log('
|
|
37
|
-
console.log(`📱
|
|
38
|
-
console.log(
|
|
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(
|
|
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('
|
|
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(
|
|
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('
|
|
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(` -
|
|
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(
|
|
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(` -
|
|
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(
|
|
75
|
+
throw new Error(getT('utils.supabaseLink.cannotValidate', { message: error.message }));
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
|