smoonb 0.0.40 → 0.0.44
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/bin/smoonb.js +1 -1
- package/package.json +1 -1
- package/src/commands/restore.js +330 -74
package/bin/smoonb.js
CHANGED
package/package.json
CHANGED
package/src/commands/restore.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { readConfig, getSourceProject, getTargetProject } = require('../utils/config');
|
|
5
5
|
const { showBetaBanner } = require('../utils/banner');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
6
7
|
|
|
7
8
|
module.exports = async (options) => {
|
|
8
9
|
showBetaBanner();
|
|
@@ -28,6 +29,12 @@ module.exports = async (options) => {
|
|
|
28
29
|
// 3. Perguntar quais componentes restaurar
|
|
29
30
|
const components = await askRestoreComponents(selectedBackup.path);
|
|
30
31
|
|
|
32
|
+
// Validar que pelo menos um componente foi selecionado
|
|
33
|
+
if (!Object.values(components).some(Boolean)) {
|
|
34
|
+
console.error(chalk.red('\n❌ Nenhum componente selecionado para restauração!'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
// 4. Mostrar resumo
|
|
32
39
|
showRestoreSummary(selectedBackup, components, targetProject);
|
|
33
40
|
|
|
@@ -41,27 +48,29 @@ module.exports = async (options) => {
|
|
|
41
48
|
// 6. Executar restauração
|
|
42
49
|
console.log(chalk.blue('\n🚀 Iniciando restauração...'));
|
|
43
50
|
|
|
44
|
-
// 6.1 Database
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
// 6.1 Database (se selecionado)
|
|
52
|
+
if (components.database) {
|
|
53
|
+
await restoreDatabaseGz(
|
|
54
|
+
path.join(selectedBackup.path, selectedBackup.backupFile),
|
|
55
|
+
targetProject.targetDatabaseUrl
|
|
56
|
+
);
|
|
57
|
+
}
|
|
49
58
|
|
|
50
59
|
// 6.2 Edge Functions (se selecionado)
|
|
51
60
|
if (components.edgeFunctions) {
|
|
52
61
|
await restoreEdgeFunctions(selectedBackup.path, targetProject);
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
// 6.3
|
|
56
|
-
if (components.storage) {
|
|
57
|
-
await restoreStorageBuckets(selectedBackup.path, targetProject);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 6.4 Auth Settings (se selecionado)
|
|
64
|
+
// 6.3 Auth Settings (se selecionado)
|
|
61
65
|
if (components.authSettings) {
|
|
62
66
|
await restoreAuthSettings(selectedBackup.path, targetProject);
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
// 6.4 Storage Buckets (se selecionado)
|
|
70
|
+
if (components.storage) {
|
|
71
|
+
await restoreStorageBuckets(selectedBackup.path, targetProject);
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
// 6.5 Database Settings (se selecionado)
|
|
66
75
|
if (components.databaseSettings) {
|
|
67
76
|
await restoreDatabaseSettings(selectedBackup.path, targetProject);
|
|
@@ -80,7 +89,7 @@ module.exports = async (options) => {
|
|
|
80
89
|
}
|
|
81
90
|
};
|
|
82
91
|
|
|
83
|
-
// Listar backups válidos (
|
|
92
|
+
// Listar backups válidos (aceita .backup.gz e .backup)
|
|
84
93
|
async function listValidBackups(backupsDir) {
|
|
85
94
|
if (!fs.existsSync(backupsDir)) {
|
|
86
95
|
return [];
|
|
@@ -93,7 +102,10 @@ async function listValidBackups(backupsDir) {
|
|
|
93
102
|
if (item.isDirectory() && item.name.startsWith('backup-')) {
|
|
94
103
|
const backupPath = path.join(backupsDir, item.name);
|
|
95
104
|
const files = fs.readdirSync(backupPath);
|
|
96
|
-
|
|
105
|
+
// Aceitar tanto .backup.gz quanto .backup
|
|
106
|
+
const backupFile = files.find(file =>
|
|
107
|
+
file.endsWith('.backup.gz') || file.endsWith('.backup')
|
|
108
|
+
);
|
|
97
109
|
|
|
98
110
|
if (backupFile) {
|
|
99
111
|
const manifestPath = path.join(backupPath, 'backup-manifest.json');
|
|
@@ -169,59 +181,80 @@ async function selectBackupInteractive(backups) {
|
|
|
169
181
|
|
|
170
182
|
// Perguntar quais componentes restaurar
|
|
171
183
|
async function askRestoreComponents(backupPath) {
|
|
172
|
-
const
|
|
173
|
-
edgeFunctions: true,
|
|
174
|
-
storage: false,
|
|
175
|
-
authSettings: false,
|
|
176
|
-
databaseSettings: false,
|
|
177
|
-
realtimeSettings: false
|
|
178
|
-
};
|
|
184
|
+
const questions = [];
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
// Database
|
|
187
|
+
questions.push({
|
|
188
|
+
type: 'confirm',
|
|
189
|
+
name: 'restoreDatabase',
|
|
190
|
+
message: 'Deseja restaurar Database?',
|
|
191
|
+
default: true
|
|
183
192
|
});
|
|
184
193
|
|
|
185
|
-
const question = (query) => new Promise(resolve => readline.question(query, resolve));
|
|
186
|
-
|
|
187
|
-
console.log(chalk.blue('\n📦 Selecione os componentes para restaurar:'));
|
|
188
|
-
|
|
189
194
|
// Edge Functions
|
|
190
195
|
const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
|
|
191
196
|
if (fs.existsSync(edgeFunctionsDir) && fs.readdirSync(edgeFunctionsDir).length > 0) {
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
questions.push({
|
|
198
|
+
type: 'confirm',
|
|
199
|
+
name: 'restoreEdgeFunctions',
|
|
200
|
+
message: 'Deseja restaurar Edge Functions?',
|
|
201
|
+
default: true
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Auth Settings
|
|
206
|
+
if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
|
|
207
|
+
questions.push({
|
|
208
|
+
type: 'confirm',
|
|
209
|
+
name: 'restoreAuthSettings',
|
|
210
|
+
message: 'Deseja restaurar Auth Settings (interativo)?',
|
|
211
|
+
default: true
|
|
212
|
+
});
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
// Storage Buckets
|
|
197
216
|
const storageDir = path.join(backupPath, 'storage');
|
|
198
217
|
if (fs.existsSync(storageDir) && fs.readdirSync(storageDir).length > 0) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const authChoice = await question('Deseja restaurar Auth Settings? (s/N): ');
|
|
206
|
-
components.authSettings = authChoice.toLowerCase() === 's';
|
|
218
|
+
questions.push({
|
|
219
|
+
type: 'confirm',
|
|
220
|
+
name: 'restoreStorage',
|
|
221
|
+
message: 'Deseja ver informações de Storage Buckets?',
|
|
222
|
+
default: false
|
|
223
|
+
});
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
// Database Settings
|
|
226
|
+
// Database Extensions and Settings
|
|
210
227
|
const dbSettingsFiles = fs.readdirSync(backupPath)
|
|
211
228
|
.filter(file => file.startsWith('database-settings-') && file.endsWith('.json'));
|
|
212
229
|
if (dbSettingsFiles.length > 0) {
|
|
213
|
-
|
|
214
|
-
|
|
230
|
+
questions.push({
|
|
231
|
+
type: 'confirm',
|
|
232
|
+
name: 'restoreDatabaseSettings',
|
|
233
|
+
message: 'Deseja restaurar Database Extensions and Settings?',
|
|
234
|
+
default: false
|
|
235
|
+
});
|
|
215
236
|
}
|
|
216
237
|
|
|
217
238
|
// Realtime Settings
|
|
218
239
|
if (fs.existsSync(path.join(backupPath, 'realtime-settings.json'))) {
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
questions.push({
|
|
241
|
+
type: 'confirm',
|
|
242
|
+
name: 'restoreRealtimeSettings',
|
|
243
|
+
message: 'Deseja restaurar Realtime Settings (interativo)?',
|
|
244
|
+
default: true
|
|
245
|
+
});
|
|
221
246
|
}
|
|
222
247
|
|
|
223
|
-
|
|
224
|
-
|
|
248
|
+
const answers = await inquirer.prompt(questions);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
database: answers.restoreDatabase,
|
|
252
|
+
edgeFunctions: answers.restoreEdgeFunctions || false,
|
|
253
|
+
storage: answers.restoreStorage || false,
|
|
254
|
+
authSettings: answers.restoreAuthSettings || false,
|
|
255
|
+
databaseSettings: answers.restoreDatabaseSettings || false,
|
|
256
|
+
realtimeSettings: answers.restoreRealtimeSettings || false
|
|
257
|
+
};
|
|
225
258
|
}
|
|
226
259
|
|
|
227
260
|
// Mostrar resumo da restauração
|
|
@@ -235,7 +268,9 @@ function showRestoreSummary(backup, components, targetProject) {
|
|
|
235
268
|
console.log(chalk.cyan('Componentes que serão restaurados:'));
|
|
236
269
|
console.log('');
|
|
237
270
|
|
|
238
|
-
|
|
271
|
+
if (components.database) {
|
|
272
|
+
console.log('✅ Database (psql -f via Docker)');
|
|
273
|
+
}
|
|
239
274
|
|
|
240
275
|
if (components.edgeFunctions) {
|
|
241
276
|
const edgeFunctionsDir = path.join(backup.path, 'edge-functions');
|
|
@@ -246,20 +281,20 @@ function showRestoreSummary(backup, components, targetProject) {
|
|
|
246
281
|
functions.forEach(func => console.log(` - ${func}`));
|
|
247
282
|
}
|
|
248
283
|
|
|
249
|
-
if (components.
|
|
250
|
-
console.log('
|
|
284
|
+
if (components.authSettings) {
|
|
285
|
+
console.log('🔐 Auth Settings: Exibir URL e valores para configuração manual');
|
|
251
286
|
}
|
|
252
287
|
|
|
253
|
-
if (components.
|
|
254
|
-
console.log('
|
|
288
|
+
if (components.storage) {
|
|
289
|
+
console.log('📦 Storage Buckets: Exibir informações e instruções do Google Colab');
|
|
255
290
|
}
|
|
256
291
|
|
|
257
292
|
if (components.databaseSettings) {
|
|
258
|
-
console.log('🔧 Database Settings: Restaurar
|
|
293
|
+
console.log('🔧 Database Extensions and Settings: Restaurar via SQL');
|
|
259
294
|
}
|
|
260
295
|
|
|
261
296
|
if (components.realtimeSettings) {
|
|
262
|
-
console.log('🔄 Realtime Settings:
|
|
297
|
+
console.log('🔄 Realtime Settings: Exibir URL e valores para configuração manual');
|
|
263
298
|
}
|
|
264
299
|
|
|
265
300
|
console.log('');
|
|
@@ -281,9 +316,9 @@ async function confirmExecution() {
|
|
|
281
316
|
}
|
|
282
317
|
|
|
283
318
|
// Restaurar Database via psql (conforme documentação oficial Supabase: https://supabase.com/docs/guides/platform/migrating-within-supabase/dashboard-restore)
|
|
319
|
+
// Aceita tanto arquivos .backup.gz quanto .backup já descompactados
|
|
284
320
|
async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
|
|
285
321
|
console.log(chalk.blue('📊 Restaurando Database...'));
|
|
286
|
-
console.log(chalk.gray(' - Descompactando backup (se necessário)...'));
|
|
287
322
|
|
|
288
323
|
try {
|
|
289
324
|
const { execSync } = require('child_process');
|
|
@@ -292,9 +327,11 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
|
|
|
292
327
|
const fileName = path.basename(backupFilePath);
|
|
293
328
|
let uncompressedFile = fileName;
|
|
294
329
|
|
|
295
|
-
//
|
|
296
|
-
if (fileName.endsWith('.gz')) {
|
|
330
|
+
// Verificar se é arquivo .backup.gz (compactado) ou .backup (descompactado)
|
|
331
|
+
if (fileName.endsWith('.backup.gz')) {
|
|
332
|
+
console.log(chalk.gray(' - Arquivo .backup.gz detectado'));
|
|
297
333
|
console.log(chalk.gray(' - Extraindo arquivo .gz...'));
|
|
334
|
+
|
|
298
335
|
const unzipCmd = [
|
|
299
336
|
'docker run --rm',
|
|
300
337
|
`-v "${backupDirAbs}:/host"`,
|
|
@@ -304,6 +341,11 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
|
|
|
304
341
|
execSync(unzipCmd, { stdio: 'pipe' });
|
|
305
342
|
uncompressedFile = fileName.replace('.gz', '');
|
|
306
343
|
console.log(chalk.gray(' - Arquivo descompactado: ' + uncompressedFile));
|
|
344
|
+
} else if (fileName.endsWith('.backup')) {
|
|
345
|
+
console.log(chalk.gray(' - Arquivo .backup detectado (já descompactado)'));
|
|
346
|
+
console.log(chalk.gray(' - Prosseguindo com restauração direta'));
|
|
347
|
+
} else {
|
|
348
|
+
throw new Error(`Formato de arquivo inválido. Esperado .backup.gz ou .backup, recebido: ${fileName}`);
|
|
307
349
|
}
|
|
308
350
|
|
|
309
351
|
// Extrair credenciais da URL de conexão
|
|
@@ -354,37 +396,251 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
|
|
|
354
396
|
}
|
|
355
397
|
}
|
|
356
398
|
|
|
357
|
-
// Restaurar Edge Functions
|
|
399
|
+
// Restaurar Edge Functions via supabase functions deploy
|
|
358
400
|
async function restoreEdgeFunctions(backupPath, targetProject) {
|
|
359
|
-
console.log(chalk.blue('⚡ Restaurando Edge Functions...'));
|
|
360
|
-
|
|
361
|
-
|
|
401
|
+
console.log(chalk.blue('\n⚡ Restaurando Edge Functions...'));
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const { execSync } = require('child_process');
|
|
405
|
+
const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
|
|
406
|
+
|
|
407
|
+
if (!fs.existsSync(edgeFunctionsDir)) {
|
|
408
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const functions = fs.readdirSync(edgeFunctionsDir).filter(item =>
|
|
413
|
+
fs.statSync(path.join(edgeFunctionsDir, item)).isDirectory()
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (functions.length === 0) {
|
|
417
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
|
|
422
|
+
|
|
423
|
+
// Link com projeto target
|
|
424
|
+
console.log(chalk.gray(` - Linkando com projeto ${targetProject.targetProjectId}...`));
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
execSync(`supabase link --project-ref ${targetProject.targetProjectId}`, {
|
|
428
|
+
stdio: 'pipe',
|
|
429
|
+
encoding: 'utf8'
|
|
430
|
+
});
|
|
431
|
+
} catch (linkError) {
|
|
432
|
+
console.log(chalk.yellow(` ⚠️ Link pode já existir, continuando...`));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Deploy de cada função
|
|
436
|
+
for (const funcName of functions) {
|
|
437
|
+
console.log(chalk.gray(` - Deployando ${funcName}...`));
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
const functionPath = path.join(edgeFunctionsDir, funcName);
|
|
441
|
+
|
|
442
|
+
execSync(`supabase functions deploy ${funcName}`, {
|
|
443
|
+
cwd: functionPath,
|
|
444
|
+
stdio: 'pipe',
|
|
445
|
+
encoding: 'utf8'
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
console.log(chalk.green(` ✅ ${funcName} deployada com sucesso!`));
|
|
449
|
+
} catch (deployError) {
|
|
450
|
+
console.log(chalk.yellow(` ⚠️ ${funcName} - deploy falhou: ${deployError.message}`));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error(chalk.red(` ❌ Erro ao restaurar Edge Functions: ${error.message}`));
|
|
456
|
+
}
|
|
362
457
|
}
|
|
363
458
|
|
|
364
|
-
// Restaurar Storage Buckets (
|
|
459
|
+
// Restaurar Storage Buckets (interativo - exibir informações)
|
|
365
460
|
async function restoreStorageBuckets(backupPath, targetProject) {
|
|
366
|
-
console.log(chalk.blue('📦 Restaurando Storage Buckets...'));
|
|
367
|
-
|
|
368
|
-
|
|
461
|
+
console.log(chalk.blue('\n📦 Restaurando Storage Buckets...'));
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const storageDir = path.join(backupPath, 'storage');
|
|
465
|
+
|
|
466
|
+
if (!fs.existsSync(storageDir)) {
|
|
467
|
+
console.log(chalk.yellow(' ⚠️ Nenhum bucket de Storage encontrado no backup'));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const manifestPath = path.join(backupPath, 'backup-manifest.json');
|
|
472
|
+
let manifest = null;
|
|
473
|
+
|
|
474
|
+
if (fs.existsSync(manifestPath)) {
|
|
475
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const buckets = manifest?.components?.storage?.buckets || [];
|
|
479
|
+
|
|
480
|
+
if (buckets.length === 0) {
|
|
481
|
+
console.log(chalk.gray(' ℹ️ Nenhum bucket para restaurar'));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.log(chalk.green(`\n ✅ ${buckets.length} bucket(s) encontrado(s) no backup`));
|
|
486
|
+
buckets.forEach(bucket => {
|
|
487
|
+
console.log(chalk.gray(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const colabUrl = 'https://colab.research.google.com/github/PLyn/supabase-storage-migrate/blob/main/Supabase_Storage_migration.ipynb';
|
|
491
|
+
|
|
492
|
+
console.log(chalk.yellow('\n ⚠️ Migração de objetos de Storage requer processo manual'));
|
|
493
|
+
console.log(chalk.cyan(` ℹ️ Use o script do Google Colab: ${colabUrl}`));
|
|
494
|
+
console.log(chalk.gray('\n 📋 Instruções:'));
|
|
495
|
+
console.log(chalk.gray(' 1. Execute o script no Google Colab'));
|
|
496
|
+
console.log(chalk.gray(' 2. Configure as credenciais dos projetos (origem e destino)'));
|
|
497
|
+
console.log(chalk.gray(' 3. Execute a migração'));
|
|
498
|
+
|
|
499
|
+
await inquirer.prompt([{
|
|
500
|
+
type: 'input',
|
|
501
|
+
name: 'continue',
|
|
502
|
+
message: 'Pressione Enter para continuar'
|
|
503
|
+
}]);
|
|
504
|
+
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error(chalk.red(` ❌ Erro ao processar Storage: ${error.message}`));
|
|
507
|
+
}
|
|
369
508
|
}
|
|
370
509
|
|
|
371
|
-
// Restaurar Auth Settings (
|
|
510
|
+
// Restaurar Auth Settings (interativo - exibir URL e valores)
|
|
372
511
|
async function restoreAuthSettings(backupPath, targetProject) {
|
|
373
|
-
console.log(chalk.blue('🔐 Restaurando Auth Settings...'));
|
|
374
|
-
|
|
375
|
-
|
|
512
|
+
console.log(chalk.blue('\n🔐 Restaurando Auth Settings...'));
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
const authSettingsPath = path.join(backupPath, 'auth-settings.json');
|
|
516
|
+
|
|
517
|
+
if (!fs.existsSync(authSettingsPath)) {
|
|
518
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Auth encontrada no backup'));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const authSettings = JSON.parse(fs.readFileSync(authSettingsPath, 'utf8'));
|
|
523
|
+
const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/auth/url-config`;
|
|
524
|
+
|
|
525
|
+
console.log(chalk.green('\n ✅ URL para configuração manual:'));
|
|
526
|
+
console.log(chalk.cyan(` ${dashboardUrl}`));
|
|
527
|
+
console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
|
|
528
|
+
|
|
529
|
+
if (authSettings.auth_url_config) {
|
|
530
|
+
Object.entries(authSettings.auth_url_config).forEach(([key, value]) => {
|
|
531
|
+
console.log(chalk.gray(` - ${key}: ${value}`));
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
|
|
536
|
+
|
|
537
|
+
await inquirer.prompt([{
|
|
538
|
+
type: 'input',
|
|
539
|
+
name: 'continue',
|
|
540
|
+
message: 'Pressione Enter para continuar'
|
|
541
|
+
}]);
|
|
542
|
+
|
|
543
|
+
console.log(chalk.green(' ✅ Auth Settings processados'));
|
|
544
|
+
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.error(chalk.red(` ❌ Erro ao processar Auth Settings: ${error.message}`));
|
|
547
|
+
}
|
|
376
548
|
}
|
|
377
549
|
|
|
378
|
-
// Restaurar Database Settings (
|
|
550
|
+
// Restaurar Database Settings (via SQL)
|
|
379
551
|
async function restoreDatabaseSettings(backupPath, targetProject) {
|
|
380
|
-
console.log(chalk.blue('🔧 Restaurando Database Settings...'));
|
|
381
|
-
|
|
382
|
-
|
|
552
|
+
console.log(chalk.blue('\n🔧 Restaurando Database Settings...'));
|
|
553
|
+
|
|
554
|
+
try {
|
|
555
|
+
const files = fs.readdirSync(backupPath);
|
|
556
|
+
const dbSettingsFile = files.find(f => f.startsWith('database-settings-') && f.endsWith('.json'));
|
|
557
|
+
|
|
558
|
+
if (!dbSettingsFile) {
|
|
559
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Database encontrada no backup'));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const dbSettings = JSON.parse(fs.readFileSync(path.join(backupPath, dbSettingsFile), 'utf8'));
|
|
564
|
+
const { execSync } = require('child_process');
|
|
565
|
+
|
|
566
|
+
if (dbSettings.extensions && dbSettings.extensions.length > 0) {
|
|
567
|
+
console.log(chalk.gray(` - Habilitando ${dbSettings.extensions.length} extension(s)...`));
|
|
568
|
+
|
|
569
|
+
for (const ext of dbSettings.extensions) {
|
|
570
|
+
console.log(chalk.gray(` - ${ext}`));
|
|
571
|
+
|
|
572
|
+
const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${ext};`;
|
|
573
|
+
|
|
574
|
+
const urlMatch = targetProject.targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
|
|
575
|
+
|
|
576
|
+
if (!urlMatch) {
|
|
577
|
+
console.log(chalk.yellow(` ⚠️ URL inválida para ${ext}`));
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const dockerCmd = [
|
|
582
|
+
'docker run --rm',
|
|
583
|
+
'--network host',
|
|
584
|
+
`-e PGPASSWORD="${encodeURIComponent(urlMatch[2])}"`,
|
|
585
|
+
'postgres:17 psql',
|
|
586
|
+
`-d "${targetProject.targetDatabaseUrl}"`,
|
|
587
|
+
`-c "${sqlCommand}"`
|
|
588
|
+
].join(' ');
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
592
|
+
} catch (sqlError) {
|
|
593
|
+
console.log(chalk.yellow(` ⚠️ ${ext} - extension já existe ou não pode ser habilitada`));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
console.log(chalk.green(' ✅ Database Settings restaurados com sucesso!'));
|
|
599
|
+
|
|
600
|
+
} catch (error) {
|
|
601
|
+
console.error(chalk.red(` ❌ Erro ao restaurar Database Settings: ${error.message}`));
|
|
602
|
+
}
|
|
383
603
|
}
|
|
384
604
|
|
|
385
|
-
// Restaurar Realtime Settings (
|
|
605
|
+
// Restaurar Realtime Settings (interativo - exibir URL e valores)
|
|
386
606
|
async function restoreRealtimeSettings(backupPath, targetProject) {
|
|
387
|
-
console.log(chalk.blue('🔄 Restaurando Realtime Settings...'));
|
|
388
|
-
|
|
389
|
-
|
|
607
|
+
console.log(chalk.blue('\n🔄 Restaurando Realtime Settings...'));
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
const realtimeSettingsPath = path.join(backupPath, 'realtime-settings.json');
|
|
611
|
+
|
|
612
|
+
if (!fs.existsSync(realtimeSettingsPath)) {
|
|
613
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Realtime encontrada no backup'));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const realtimeSettings = JSON.parse(fs.readFileSync(realtimeSettingsPath, 'utf8'));
|
|
618
|
+
const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/realtime/settings`;
|
|
619
|
+
|
|
620
|
+
console.log(chalk.green('\n ✅ URL para configuração manual:'));
|
|
621
|
+
console.log(chalk.cyan(` ${dashboardUrl}`));
|
|
622
|
+
console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
|
|
623
|
+
|
|
624
|
+
if (realtimeSettings.realtime_settings?.settings) {
|
|
625
|
+
Object.entries(realtimeSettings.realtime_settings.settings).forEach(([key, setting]) => {
|
|
626
|
+
console.log(chalk.gray(` - ${setting.label}: ${setting.value}`));
|
|
627
|
+
if (setting.description) {
|
|
628
|
+
console.log(chalk.gray(` ${setting.description}`));
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
|
|
634
|
+
|
|
635
|
+
await inquirer.prompt([{
|
|
636
|
+
type: 'input',
|
|
637
|
+
name: 'continue',
|
|
638
|
+
message: 'Pressione Enter para continuar'
|
|
639
|
+
}]);
|
|
640
|
+
|
|
641
|
+
console.log(chalk.green(' ✅ Realtime Settings processados'));
|
|
642
|
+
|
|
643
|
+
} catch (error) {
|
|
644
|
+
console.error(chalk.red(` ❌ Erro ao processar Realtime Settings: ${error.message}`));
|
|
645
|
+
}
|
|
390
646
|
}
|