smoonb 0.0.74 → 0.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/backup/index.js +62 -65
- package/src/commands/backup/steps/00-docker-validation.js +6 -4
- package/src/commands/backup/steps/01-database.js +8 -4
- package/src/commands/backup/steps/02-database-separated.js +11 -8
- package/src/commands/backup/steps/03-database-settings.js +8 -4
- package/src/commands/backup/steps/04-auth-settings.js +6 -3
- package/src/commands/backup/steps/05-realtime-settings.js +5 -2
- package/src/commands/backup/steps/06-storage.js +29 -26
- package/src/commands/backup/steps/07-custom-roles.js +6 -3
- package/src/commands/backup/steps/08-edge-functions.js +15 -11
- package/src/commands/backup/steps/09-supabase-temp.js +9 -6
- package/src/commands/backup/steps/10-migrations.js +14 -10
- package/src/commands/check.js +5 -3
- package/src/commands/restore/index.js +51 -46
- package/src/commands/restore/steps/00-backup-selection.js +6 -4
- package/src/commands/restore/steps/01-components-selection.js +30 -28
- package/src/commands/restore/steps/03-database.js +21 -17
- package/src/commands/restore/steps/04-edge-functions.js +16 -13
- package/src/commands/restore/steps/05-auth-settings.js +10 -7
- package/src/commands/restore/steps/06-storage.js +50 -42
- package/src/commands/restore/steps/07-database-settings.js +10 -7
- package/src/commands/restore/steps/08-realtime-settings.js +10 -7
- package/src/commands/restore/utils.js +15 -13
- package/src/i18n/locales/en.json +427 -1
- package/src/i18n/locales/pt-BR.json +426 -1
- package/src/interactive/envMapper.js +30 -25
- package/src/utils/realtime-settings.js +15 -9
- package/src/utils/supabaseLink.js +11 -10
|
@@ -25,16 +25,19 @@ const step08RealtimeSettings = require('./steps/08-realtime-settings');
|
|
|
25
25
|
* Função auxiliar para importar arquivo de backup e storage (reutiliza lógica do comando import)
|
|
26
26
|
*/
|
|
27
27
|
async function importBackupFile(sourceFile, sourceStorageFile, outputDir) {
|
|
28
|
+
const { t } = require('../../i18n');
|
|
29
|
+
const getT = global.smoonbI18n?.t || t;
|
|
30
|
+
|
|
28
31
|
// Validar arquivo de backup
|
|
29
32
|
try {
|
|
30
33
|
await fsPromises.access(sourceFile);
|
|
31
34
|
} catch {
|
|
32
|
-
throw new Error(
|
|
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
|
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const readline = require('readline');
|
|
3
|
+
const { t } = require('../../../i18n');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Etapa 0: Seleção interativa de backup
|
|
6
7
|
*/
|
|
7
8
|
module.exports = async (backups) => {
|
|
8
|
-
|
|
9
|
-
console.log(chalk.blue('
|
|
9
|
+
const getT = global.smoonbI18n?.t || t;
|
|
10
|
+
console.log(chalk.blue(`\n📋 ${getT('restore.steps.backupSelection.title')}`));
|
|
11
|
+
console.log(chalk.blue(getT('restore.steps.backupSelection.separator')));
|
|
10
12
|
|
|
11
13
|
backups.forEach((backup, index) => {
|
|
12
14
|
const date = new Date(backup.created).toLocaleString('pt-BR');
|
|
@@ -24,13 +26,13 @@ module.exports = async (backups) => {
|
|
|
24
26
|
|
|
25
27
|
const question = (query) => new Promise(resolve => rl.question(query, resolve));
|
|
26
28
|
|
|
27
|
-
const choice = await question(`\
|
|
29
|
+
const choice = await question(`\n${getT('restore.steps.backupSelection.input', { min: 1, max: backups.length })} `);
|
|
28
30
|
rl.close();
|
|
29
31
|
|
|
30
32
|
const backupIndex = parseInt(choice) - 1;
|
|
31
33
|
|
|
32
34
|
if (backupIndex < 0 || backupIndex >= backups.length) {
|
|
33
|
-
throw new Error('
|
|
35
|
+
throw new Error(getT('restore.steps.backupSelection.invalid'));
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
return backups[backupIndex];
|
|
@@ -2,31 +2,33 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { confirm } = require('../../../utils/prompt');
|
|
5
|
+
const { t } = require('../../../i18n');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Etapa 1: Perguntar quais componentes restaurar
|
|
8
9
|
*/
|
|
9
10
|
module.exports = async (backupPath) => {
|
|
11
|
+
const getT = global.smoonbI18n?.t || t;
|
|
10
12
|
// Database (sempre disponível)
|
|
11
|
-
const restoreDatabase = await confirm('
|
|
13
|
+
const restoreDatabase = await confirm(getT('restore.steps.components.database.include'), true);
|
|
12
14
|
|
|
13
15
|
// Edge Functions
|
|
14
16
|
const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
|
|
15
17
|
let restoreEdgeFunctions = false;
|
|
16
18
|
if (fs.existsSync(edgeFunctionsDir) && fs.readdirSync(edgeFunctionsDir).length > 0) {
|
|
17
|
-
console.log(chalk.cyan(
|
|
18
|
-
console.log(chalk.white('
|
|
19
|
-
console.log(chalk.white(
|
|
20
|
-
restoreEdgeFunctions = await confirm('
|
|
19
|
+
console.log(chalk.cyan(`\n⚡ ${getT('restore.steps.components.edgeFunctions.title')}`));
|
|
20
|
+
console.log(chalk.white(` ${getT('restore.steps.components.edgeFunctions.description1')}`));
|
|
21
|
+
console.log(chalk.white(` ${getT('restore.steps.components.edgeFunctions.description2')}\n`));
|
|
22
|
+
restoreEdgeFunctions = await confirm(getT('restore.steps.components.edgeFunctions.include'), true);
|
|
21
23
|
}
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
// Auth Settings
|
|
24
26
|
let restoreAuthSettings = false;
|
|
25
27
|
if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
|
|
26
|
-
console.log(chalk.cyan(
|
|
27
|
-
console.log(chalk.white('
|
|
28
|
-
console.log(chalk.white(
|
|
29
|
-
restoreAuthSettings = await confirm('
|
|
28
|
+
console.log(chalk.cyan(`\n🔐 ${getT('restore.steps.components.auth.title')}`));
|
|
29
|
+
console.log(chalk.white(` ${getT('restore.steps.components.auth.description1')}`));
|
|
30
|
+
console.log(chalk.white(` ${getT('restore.steps.components.auth.description2')}\n`));
|
|
31
|
+
restoreAuthSettings = await confirm(getT('restore.steps.components.auth.include'), true);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
// Storage Buckets
|
|
@@ -49,37 +51,37 @@ module.exports = async (backupPath) => {
|
|
|
49
51
|
const hasStorageFiles = hasStorageDir && fs.readdirSync(storageDir).length > 0;
|
|
50
52
|
|
|
51
53
|
if (storageZipFiles.length > 0 || hasStorageFiles) {
|
|
52
|
-
console.log(chalk.cyan(
|
|
54
|
+
console.log(chalk.cyan(`\n📦 ${getT('restore.steps.components.storage.title')}`));
|
|
53
55
|
if (storageZipFiles.length > 0) {
|
|
54
|
-
console.log(chalk.white(`
|
|
55
|
-
console.log(chalk.white('
|
|
56
|
-
console.log(chalk.white(
|
|
56
|
+
console.log(chalk.white(` ${getT('restore.steps.components.storage.zipFound', { fileName: storageZipFiles[0] })}`));
|
|
57
|
+
console.log(chalk.white(` ${getT('restore.steps.components.storage.withZip.description1')}`));
|
|
58
|
+
console.log(chalk.white(` ${getT('restore.steps.components.storage.withZip.description2')}\n`));
|
|
57
59
|
} else {
|
|
58
|
-
console.log(chalk.white(
|
|
59
|
-
console.log(chalk.white('
|
|
60
|
-
console.log(chalk.white(
|
|
60
|
+
console.log(chalk.white(` ${getT('restore.steps.components.storage.metadataOnly.description1')}`));
|
|
61
|
+
console.log(chalk.white(` ${getT('restore.steps.components.storage.metadataOnly.description2')}`));
|
|
62
|
+
console.log(chalk.white(` ${getT('restore.steps.components.storage.metadataOnly.description3')}\n`));
|
|
61
63
|
}
|
|
62
|
-
restoreStorage = await confirm('
|
|
64
|
+
restoreStorage = await confirm(getT('restore.steps.components.storage.include'), true);
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
|
|
65
67
|
// Database Extensions and Settings
|
|
66
68
|
const dbSettingsFiles = fs.readdirSync(backupPath)
|
|
67
69
|
.filter(file => file.startsWith('database-settings-') && file.endsWith('.json'));
|
|
68
70
|
let restoreDatabaseSettings = false;
|
|
69
71
|
if (dbSettingsFiles.length > 0) {
|
|
70
|
-
console.log(chalk.cyan(
|
|
71
|
-
console.log(chalk.white('
|
|
72
|
-
console.log(chalk.white(
|
|
73
|
-
restoreDatabaseSettings = await confirm('
|
|
72
|
+
console.log(chalk.cyan(`\n🔧 ${getT('restore.steps.components.databaseSettings.title')}`));
|
|
73
|
+
console.log(chalk.white(` ${getT('restore.steps.components.databaseSettings.description1')}`));
|
|
74
|
+
console.log(chalk.white(` ${getT('restore.steps.components.databaseSettings.description2')}\n`));
|
|
75
|
+
restoreDatabaseSettings = await confirm(getT('restore.steps.components.databaseSettings.include'), true);
|
|
74
76
|
}
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
// Realtime Settings
|
|
77
79
|
let restoreRealtimeSettings = false;
|
|
78
80
|
if (fs.existsSync(path.join(backupPath, 'realtime-settings.json'))) {
|
|
79
|
-
console.log(chalk.cyan(
|
|
80
|
-
console.log(chalk.white('
|
|
81
|
-
console.log(chalk.white(
|
|
82
|
-
restoreRealtimeSettings = await confirm('
|
|
81
|
+
console.log(chalk.cyan(`\n🔄 ${getT('restore.steps.components.realtime.title')}`));
|
|
82
|
+
console.log(chalk.white(` ${getT('restore.steps.components.realtime.description1')}`));
|
|
83
|
+
console.log(chalk.white(` ${getT('restore.steps.components.realtime.description2')}\n`));
|
|
84
|
+
restoreRealtimeSettings = await confirm(getT('restore.steps.components.realtime.include'), true);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
return {
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
|
+
const { t } = require('../../../i18n');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Etapa 3: Restaurar Database via psql
|
|
7
8
|
*/
|
|
8
9
|
module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
9
10
|
try {
|
|
11
|
+
const getT = global.smoonbI18n?.t || t;
|
|
10
12
|
const backupDirAbs = path.resolve(path.dirname(backupFilePath));
|
|
11
13
|
const fileName = path.basename(backupFilePath);
|
|
12
14
|
let uncompressedFile = fileName;
|
|
13
15
|
|
|
14
16
|
// Verificar se é arquivo .backup.gz (compactado) ou .backup (descompactado)
|
|
15
17
|
if (fileName.endsWith('.backup.gz')) {
|
|
16
|
-
console.log(chalk.white(
|
|
17
|
-
console.log(chalk.white(
|
|
18
|
+
console.log(chalk.white(` - ${getT('restore.steps.database.detectedGz')}`));
|
|
19
|
+
console.log(chalk.white(` - ${getT('restore.steps.database.extractingGz')}`));
|
|
18
20
|
|
|
19
21
|
const unzipCmd = [
|
|
20
22
|
'docker run --rm',
|
|
@@ -24,10 +26,10 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
24
26
|
|
|
25
27
|
execSync(unzipCmd, { stdio: 'pipe' });
|
|
26
28
|
uncompressedFile = fileName.replace('.gz', '');
|
|
27
|
-
console.log(chalk.white(
|
|
29
|
+
console.log(chalk.white(` - ${getT('restore.steps.database.uncompressed', { file: uncompressedFile })}`));
|
|
28
30
|
} else if (fileName.endsWith('.backup')) {
|
|
29
|
-
console.log(chalk.white(
|
|
30
|
-
console.log(chalk.white(
|
|
31
|
+
console.log(chalk.white(` - ${getT('restore.steps.database.detectedBackup')}`));
|
|
32
|
+
console.log(chalk.white(` - ${getT('restore.steps.database.proceeding')}`));
|
|
31
33
|
} else {
|
|
32
34
|
throw new Error(`Formato de arquivo inválido. Esperado .backup.gz ou .backup, recebido: ${fileName}`);
|
|
33
35
|
}
|
|
@@ -36,7 +38,8 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
36
38
|
const urlMatch = targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
|
|
37
39
|
|
|
38
40
|
if (!urlMatch) {
|
|
39
|
-
|
|
41
|
+
const getT = global.smoonbI18n?.t || t;
|
|
42
|
+
throw new Error(getT('error.databaseUrlInvalid'));
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
// Comando psql conforme documentação oficial Supabase
|
|
@@ -49,29 +52,30 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
49
52
|
`-f /host/${uncompressedFile}`
|
|
50
53
|
].join(' ');
|
|
51
54
|
|
|
52
|
-
console.log(chalk.cyan(
|
|
53
|
-
console.log(chalk.cyan(
|
|
54
|
-
console.log(chalk.yellow(
|
|
55
|
-
console.log(chalk.yellow(
|
|
56
|
-
console.log(chalk.yellow(
|
|
55
|
+
console.log(chalk.cyan(` - ${getT('restore.steps.database.executing')}`));
|
|
56
|
+
console.log(chalk.cyan(` ℹ️ ${getT('restore.steps.database.followingDocs')}`));
|
|
57
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.warning')}`));
|
|
58
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.warningReason1')}`));
|
|
59
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.warningReason2')}`));
|
|
57
60
|
|
|
58
61
|
// Executar comando de restauração
|
|
59
62
|
execSync(restoreCmd, { stdio: 'inherit', encoding: 'utf8' });
|
|
60
63
|
|
|
61
|
-
console.log(chalk.green(
|
|
62
|
-
console.log(chalk.gray(
|
|
64
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.database.success')}`));
|
|
65
|
+
console.log(chalk.gray(` ℹ️ ${getT('restore.steps.database.normalErrors')}`));
|
|
63
66
|
|
|
64
67
|
} catch (error) {
|
|
65
68
|
// Erros esperados conforme documentação oficial Supabase
|
|
69
|
+
const getT = global.smoonbI18n?.t || t;
|
|
66
70
|
if (error.message.includes('already exists') ||
|
|
67
71
|
error.message.includes('constraint') ||
|
|
68
72
|
error.message.includes('duplicate') ||
|
|
69
73
|
error.stdout?.includes('already exists')) {
|
|
70
|
-
console.log(chalk.yellow(
|
|
71
|
-
console.log(chalk.green(
|
|
72
|
-
console.log(chalk.gray(
|
|
74
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.expectedErrors')}`));
|
|
75
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.database.success')}`));
|
|
76
|
+
console.log(chalk.gray(` ℹ️ ${getT('restore.steps.database.errorsIgnored')}`));
|
|
73
77
|
} else {
|
|
74
|
-
console.error(chalk.red(` ❌
|
|
78
|
+
console.error(chalk.red(` ❌ ${getT('restore.steps.database.error', { message: error.message })}`));
|
|
75
79
|
throw error;
|
|
76
80
|
}
|
|
77
81
|
}
|
|
@@ -3,16 +3,18 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs').promises;
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const { copyDirectoryRecursive } = require('../utils');
|
|
6
|
+
const { t } = require('../../../i18n');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Etapa 4: Restaurar Edge Functions via supabase functions deploy
|
|
9
10
|
*/
|
|
10
11
|
module.exports = async ({ backupPath, targetProject }) => {
|
|
11
12
|
try {
|
|
13
|
+
const getT = global.smoonbI18n?.t || t;
|
|
12
14
|
const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
|
|
13
15
|
|
|
14
16
|
if (!await fs.access(edgeFunctionsDir).then(() => true).catch(() => false)) {
|
|
15
|
-
console.log(chalk.yellow(
|
|
17
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.notFound')}`));
|
|
16
18
|
return { success: false, functions_count: 0, success_count: 0 };
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -28,11 +30,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
if (functions.length === 0) {
|
|
31
|
-
console.log(chalk.yellow(
|
|
33
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.notFound')}`));
|
|
32
34
|
return { success: false, functions_count: 0, success_count: 0 };
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
console.log(chalk.white(` -
|
|
37
|
+
console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.found', { count: functions.length })}`));
|
|
36
38
|
|
|
37
39
|
// COPIAR Edge Functions de backups/backup-XXX/edge-functions para supabase/functions
|
|
38
40
|
const supabaseFunctionsDir = path.join(process.cwd(), 'supabase', 'functions');
|
|
@@ -41,7 +43,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
41
43
|
await fs.mkdir(supabaseFunctionsDir, { recursive: true });
|
|
42
44
|
|
|
43
45
|
// Limpar supabase/functions antes de copiar (necessário para garantir ambiente limpo)
|
|
44
|
-
console.log(chalk.cyan(
|
|
46
|
+
console.log(chalk.cyan(` - ${getT('restore.steps.edgeFunctions.cleaningBefore')}`));
|
|
45
47
|
try {
|
|
46
48
|
await fs.rm(supabaseFunctionsDir, { recursive: true, force: true });
|
|
47
49
|
await fs.mkdir(supabaseFunctionsDir, { recursive: true });
|
|
@@ -54,13 +56,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
54
56
|
const backupFuncPath = path.join(edgeFunctionsDir, funcName);
|
|
55
57
|
const targetFuncPath = path.join(supabaseFunctionsDir, funcName);
|
|
56
58
|
|
|
57
|
-
console.log(chalk.white(` -
|
|
59
|
+
console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.copying', { funcName })}`));
|
|
58
60
|
|
|
59
61
|
// Copiar recursivamente
|
|
60
62
|
await copyDirectoryRecursive(backupFuncPath, targetFuncPath);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
console.log(chalk.white(` -
|
|
65
|
+
console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.linking', { projectId: targetProject.targetProjectId })}`));
|
|
64
66
|
|
|
65
67
|
// Linkar com o projeto destino
|
|
66
68
|
try {
|
|
@@ -71,13 +73,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
71
73
|
env: { ...process.env, SUPABASE_ACCESS_TOKEN: targetProject.targetAccessToken || '' }
|
|
72
74
|
});
|
|
73
75
|
} catch {
|
|
74
|
-
console.log(chalk.yellow(
|
|
76
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.linkMayExist')}`));
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
// Deploy das Edge Functions
|
|
78
80
|
let successCount = 0;
|
|
79
81
|
for (const funcName of functions) {
|
|
80
|
-
console.log(chalk.white(` -
|
|
82
|
+
console.log(chalk.white(` - ${getT('restore.steps.edgeFunctions.deploying', { funcName })}`));
|
|
81
83
|
|
|
82
84
|
try {
|
|
83
85
|
execSync(`supabase functions deploy ${funcName}`, {
|
|
@@ -88,22 +90,22 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
88
90
|
env: { ...process.env, SUPABASE_ACCESS_TOKEN: targetProject.targetAccessToken || '' }
|
|
89
91
|
});
|
|
90
92
|
|
|
91
|
-
console.log(chalk.green(` ✅ ${
|
|
93
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.edgeFunctions.deployed', { funcName })}`));
|
|
92
94
|
successCount++;
|
|
93
95
|
} catch (deployError) {
|
|
94
|
-
console.log(chalk.yellow(` ⚠️ ${
|
|
96
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.edgeFunctions.deployFailed', { funcName, message: deployError.message })}`));
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
// Limpar supabase/functions após deploy (arquivos temporários não são mais necessários)
|
|
99
|
-
console.log(chalk.cyan(
|
|
101
|
+
console.log(chalk.cyan(` - ${getT('restore.steps.edgeFunctions.cleaningAfter')}`));
|
|
100
102
|
try {
|
|
101
103
|
await fs.rm(supabaseFunctionsDir, { recursive: true, force: true });
|
|
102
104
|
} catch {
|
|
103
105
|
// Ignorar erro de limpeza
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
console.log(chalk.green(
|
|
108
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.edgeFunctions.success')}`));
|
|
107
109
|
|
|
108
110
|
return {
|
|
109
111
|
success: true,
|
|
@@ -112,7 +114,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
112
114
|
};
|
|
113
115
|
|
|
114
116
|
} catch (error) {
|
|
115
|
-
|
|
117
|
+
const getT = global.smoonbI18n?.t || t;
|
|
118
|
+
console.error(chalk.red(` ❌ ${getT('restore.steps.edgeFunctions.error', { message: error.message })}`));
|
|
116
119
|
throw error;
|
|
117
120
|
}
|
|
118
121
|
};
|