smoonb 0.0.38 → 0.0.42
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/backup.js +38 -5
- package/src/commands/restore.js +315 -69
package/bin/smoonb.js
CHANGED
package/package.json
CHANGED
package/src/commands/backup.js
CHANGED
|
@@ -414,25 +414,58 @@ async function backupEdgeFunctionsWithDocker(projectId, accessToken, backupDir)
|
|
|
414
414
|
let successCount = 0;
|
|
415
415
|
let errorCount = 0;
|
|
416
416
|
|
|
417
|
-
// ✅ Baixar cada Edge Function
|
|
417
|
+
// ✅ Baixar cada Edge Function via Supabase CLI
|
|
418
|
+
// Nota: O CLI ignora o cwd e sempre baixa para supabase/functions
|
|
418
419
|
for (const func of functions) {
|
|
419
420
|
try {
|
|
420
421
|
console.log(chalk.gray(` - Baixando: ${func.name}...`));
|
|
421
422
|
|
|
422
|
-
// Criar diretório da função
|
|
423
|
+
// Criar diretório da função NO BACKUP
|
|
423
424
|
const functionTargetDir = path.join(functionsDir, func.name);
|
|
424
425
|
await ensureDir(functionTargetDir);
|
|
425
426
|
|
|
426
|
-
//
|
|
427
|
+
// Diretório temporário onde o supabase CLI irá baixar (supabase/functions)
|
|
428
|
+
const tempDownloadDir = path.join(process.cwd(), 'supabase', 'functions', func.name);
|
|
429
|
+
|
|
430
|
+
// Baixar Edge Function via Supabase CLI (sempre vai para supabase/functions)
|
|
427
431
|
const { execSync } = require('child_process');
|
|
428
432
|
|
|
429
|
-
// Download DIRETO para o diretório de destino (sem intermediários)
|
|
430
433
|
execSync(`supabase functions download ${func.name}`, {
|
|
431
|
-
cwd: functionTargetDir,
|
|
432
434
|
timeout: 60000,
|
|
433
435
|
stdio: 'pipe'
|
|
434
436
|
});
|
|
435
437
|
|
|
438
|
+
// ✅ COPIAR arquivos de supabase/functions para o backup
|
|
439
|
+
try {
|
|
440
|
+
const stat = await fs.stat(tempDownloadDir);
|
|
441
|
+
if (stat.isDirectory()) {
|
|
442
|
+
const files = await fs.readdir(tempDownloadDir);
|
|
443
|
+
for (const file of files) {
|
|
444
|
+
const srcPath = path.join(tempDownloadDir, file);
|
|
445
|
+
const dstPath = path.join(functionTargetDir, file);
|
|
446
|
+
|
|
447
|
+
const fileStats = await fs.stat(srcPath);
|
|
448
|
+
if (fileStats.isDirectory()) {
|
|
449
|
+
// Copiar diretórios recursivamente
|
|
450
|
+
await fs.cp(srcPath, dstPath, { recursive: true });
|
|
451
|
+
} else {
|
|
452
|
+
// Copiar arquivos
|
|
453
|
+
await fs.copyFile(srcPath, dstPath);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
} catch (copyError) {
|
|
458
|
+
// Arquivos não foram baixados, continuar
|
|
459
|
+
console.log(chalk.yellow(` ⚠️ Nenhum arquivo encontrado em ${tempDownloadDir}`));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ✅ LIMPAR supabase/functions após copiar
|
|
463
|
+
try {
|
|
464
|
+
await fs.rm(tempDownloadDir, { recursive: true, force: true });
|
|
465
|
+
} catch (cleanError) {
|
|
466
|
+
// Ignorar erro de limpeza
|
|
467
|
+
}
|
|
468
|
+
|
|
436
469
|
console.log(chalk.green(` ✅ ${func.name} baixada com sucesso`));
|
|
437
470
|
successCount++;
|
|
438
471
|
|
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);
|
|
@@ -169,59 +178,80 @@ async function selectBackupInteractive(backups) {
|
|
|
169
178
|
|
|
170
179
|
// Perguntar quais componentes restaurar
|
|
171
180
|
async function askRestoreComponents(backupPath) {
|
|
172
|
-
const
|
|
173
|
-
edgeFunctions: true,
|
|
174
|
-
storage: false,
|
|
175
|
-
authSettings: false,
|
|
176
|
-
databaseSettings: false,
|
|
177
|
-
realtimeSettings: false
|
|
178
|
-
};
|
|
181
|
+
const questions = [];
|
|
179
182
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
// Database
|
|
184
|
+
questions.push({
|
|
185
|
+
type: 'confirm',
|
|
186
|
+
name: 'restoreDatabase',
|
|
187
|
+
message: 'Deseja restaurar Database?',
|
|
188
|
+
default: true
|
|
183
189
|
});
|
|
184
190
|
|
|
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
191
|
// Edge Functions
|
|
190
192
|
const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
|
|
191
193
|
if (fs.existsSync(edgeFunctionsDir) && fs.readdirSync(edgeFunctionsDir).length > 0) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
questions.push({
|
|
195
|
+
type: 'confirm',
|
|
196
|
+
name: 'restoreEdgeFunctions',
|
|
197
|
+
message: 'Deseja restaurar Edge Functions?',
|
|
198
|
+
default: true
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Auth Settings
|
|
203
|
+
if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
|
|
204
|
+
questions.push({
|
|
205
|
+
type: 'confirm',
|
|
206
|
+
name: 'restoreAuthSettings',
|
|
207
|
+
message: 'Deseja restaurar Auth Settings (interativo)?',
|
|
208
|
+
default: true
|
|
209
|
+
});
|
|
194
210
|
}
|
|
195
211
|
|
|
196
212
|
// Storage Buckets
|
|
197
213
|
const storageDir = path.join(backupPath, 'storage');
|
|
198
214
|
if (fs.existsSync(storageDir) && fs.readdirSync(storageDir).length > 0) {
|
|
199
|
-
|
|
200
|
-
|
|
215
|
+
questions.push({
|
|
216
|
+
type: 'confirm',
|
|
217
|
+
name: 'restoreStorage',
|
|
218
|
+
message: 'Deseja ver informações de Storage Buckets?',
|
|
219
|
+
default: false
|
|
220
|
+
});
|
|
201
221
|
}
|
|
202
222
|
|
|
203
|
-
//
|
|
204
|
-
if (fs.existsSync(path.join(backupPath, 'auth-settings.json'))) {
|
|
205
|
-
const authChoice = await question('Deseja restaurar Auth Settings? (s/N): ');
|
|
206
|
-
components.authSettings = authChoice.toLowerCase() === 's';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Database Settings
|
|
223
|
+
// Database Extensions and Settings
|
|
210
224
|
const dbSettingsFiles = fs.readdirSync(backupPath)
|
|
211
225
|
.filter(file => file.startsWith('database-settings-') && file.endsWith('.json'));
|
|
212
226
|
if (dbSettingsFiles.length > 0) {
|
|
213
|
-
|
|
214
|
-
|
|
227
|
+
questions.push({
|
|
228
|
+
type: 'confirm',
|
|
229
|
+
name: 'restoreDatabaseSettings',
|
|
230
|
+
message: 'Deseja restaurar Database Extensions and Settings?',
|
|
231
|
+
default: false
|
|
232
|
+
});
|
|
215
233
|
}
|
|
216
234
|
|
|
217
235
|
// Realtime Settings
|
|
218
236
|
if (fs.existsSync(path.join(backupPath, 'realtime-settings.json'))) {
|
|
219
|
-
|
|
220
|
-
|
|
237
|
+
questions.push({
|
|
238
|
+
type: 'confirm',
|
|
239
|
+
name: 'restoreRealtimeSettings',
|
|
240
|
+
message: 'Deseja restaurar Realtime Settings (interativo)?',
|
|
241
|
+
default: true
|
|
242
|
+
});
|
|
221
243
|
}
|
|
222
244
|
|
|
223
|
-
|
|
224
|
-
|
|
245
|
+
const answers = await inquirer.prompt(questions);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
database: answers.restoreDatabase,
|
|
249
|
+
edgeFunctions: answers.restoreEdgeFunctions || false,
|
|
250
|
+
storage: answers.restoreStorage || false,
|
|
251
|
+
authSettings: answers.restoreAuthSettings || false,
|
|
252
|
+
databaseSettings: answers.restoreDatabaseSettings || false,
|
|
253
|
+
realtimeSettings: answers.restoreRealtimeSettings || false
|
|
254
|
+
};
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
// Mostrar resumo da restauração
|
|
@@ -235,7 +265,9 @@ function showRestoreSummary(backup, components, targetProject) {
|
|
|
235
265
|
console.log(chalk.cyan('Componentes que serão restaurados:'));
|
|
236
266
|
console.log('');
|
|
237
267
|
|
|
238
|
-
|
|
268
|
+
if (components.database) {
|
|
269
|
+
console.log('✅ Database (psql -f via Docker)');
|
|
270
|
+
}
|
|
239
271
|
|
|
240
272
|
if (components.edgeFunctions) {
|
|
241
273
|
const edgeFunctionsDir = path.join(backup.path, 'edge-functions');
|
|
@@ -246,20 +278,20 @@ function showRestoreSummary(backup, components, targetProject) {
|
|
|
246
278
|
functions.forEach(func => console.log(` - ${func}`));
|
|
247
279
|
}
|
|
248
280
|
|
|
249
|
-
if (components.
|
|
250
|
-
console.log('
|
|
281
|
+
if (components.authSettings) {
|
|
282
|
+
console.log('🔐 Auth Settings: Exibir URL e valores para configuração manual');
|
|
251
283
|
}
|
|
252
284
|
|
|
253
|
-
if (components.
|
|
254
|
-
console.log('
|
|
285
|
+
if (components.storage) {
|
|
286
|
+
console.log('📦 Storage Buckets: Exibir informações e instruções do Google Colab');
|
|
255
287
|
}
|
|
256
288
|
|
|
257
289
|
if (components.databaseSettings) {
|
|
258
|
-
console.log('🔧 Database Settings: Restaurar
|
|
290
|
+
console.log('🔧 Database Extensions and Settings: Restaurar via SQL');
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
if (components.realtimeSettings) {
|
|
262
|
-
console.log('🔄 Realtime Settings:
|
|
294
|
+
console.log('🔄 Realtime Settings: Exibir URL e valores para configuração manual');
|
|
263
295
|
}
|
|
264
296
|
|
|
265
297
|
console.log('');
|
|
@@ -354,37 +386,251 @@ async function restoreDatabaseGz(backupFilePath, targetDatabaseUrl) {
|
|
|
354
386
|
}
|
|
355
387
|
}
|
|
356
388
|
|
|
357
|
-
// Restaurar Edge Functions
|
|
389
|
+
// Restaurar Edge Functions via supabase functions deploy
|
|
358
390
|
async function restoreEdgeFunctions(backupPath, targetProject) {
|
|
359
|
-
console.log(chalk.blue('⚡ Restaurando Edge Functions...'));
|
|
360
|
-
|
|
361
|
-
|
|
391
|
+
console.log(chalk.blue('\n⚡ Restaurando Edge Functions...'));
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
const { execSync } = require('child_process');
|
|
395
|
+
const edgeFunctionsDir = path.join(backupPath, 'edge-functions');
|
|
396
|
+
|
|
397
|
+
if (!fs.existsSync(edgeFunctionsDir)) {
|
|
398
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const functions = fs.readdirSync(edgeFunctionsDir).filter(item =>
|
|
403
|
+
fs.statSync(path.join(edgeFunctionsDir, item)).isDirectory()
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if (functions.length === 0) {
|
|
407
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma Edge Function encontrada no backup'));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
|
|
412
|
+
|
|
413
|
+
// Link com projeto target
|
|
414
|
+
console.log(chalk.gray(` - Linkando com projeto ${targetProject.targetProjectId}...`));
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
execSync(`supabase link --project-ref ${targetProject.targetProjectId}`, {
|
|
418
|
+
stdio: 'pipe',
|
|
419
|
+
encoding: 'utf8'
|
|
420
|
+
});
|
|
421
|
+
} catch (linkError) {
|
|
422
|
+
console.log(chalk.yellow(` ⚠️ Link pode já existir, continuando...`));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Deploy de cada função
|
|
426
|
+
for (const funcName of functions) {
|
|
427
|
+
console.log(chalk.gray(` - Deployando ${funcName}...`));
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
const functionPath = path.join(edgeFunctionsDir, funcName);
|
|
431
|
+
|
|
432
|
+
execSync(`supabase functions deploy ${funcName}`, {
|
|
433
|
+
cwd: functionPath,
|
|
434
|
+
stdio: 'pipe',
|
|
435
|
+
encoding: 'utf8'
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
console.log(chalk.green(` ✅ ${funcName} deployada com sucesso!`));
|
|
439
|
+
} catch (deployError) {
|
|
440
|
+
console.log(chalk.yellow(` ⚠️ ${funcName} - deploy falhou: ${deployError.message}`));
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error(chalk.red(` ❌ Erro ao restaurar Edge Functions: ${error.message}`));
|
|
446
|
+
}
|
|
362
447
|
}
|
|
363
448
|
|
|
364
|
-
// Restaurar Storage Buckets (
|
|
449
|
+
// Restaurar Storage Buckets (interativo - exibir informações)
|
|
365
450
|
async function restoreStorageBuckets(backupPath, targetProject) {
|
|
366
|
-
console.log(chalk.blue('📦 Restaurando Storage Buckets...'));
|
|
367
|
-
|
|
368
|
-
|
|
451
|
+
console.log(chalk.blue('\n📦 Restaurando Storage Buckets...'));
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
const storageDir = path.join(backupPath, 'storage');
|
|
455
|
+
|
|
456
|
+
if (!fs.existsSync(storageDir)) {
|
|
457
|
+
console.log(chalk.yellow(' ⚠️ Nenhum bucket de Storage encontrado no backup'));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const manifestPath = path.join(backupPath, 'backup-manifest.json');
|
|
462
|
+
let manifest = null;
|
|
463
|
+
|
|
464
|
+
if (fs.existsSync(manifestPath)) {
|
|
465
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const buckets = manifest?.components?.storage?.buckets || [];
|
|
469
|
+
|
|
470
|
+
if (buckets.length === 0) {
|
|
471
|
+
console.log(chalk.gray(' ℹ️ Nenhum bucket para restaurar'));
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
console.log(chalk.green(`\n ✅ ${buckets.length} bucket(s) encontrado(s) no backup`));
|
|
476
|
+
buckets.forEach(bucket => {
|
|
477
|
+
console.log(chalk.gray(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const colabUrl = 'https://colab.research.google.com/github/PLyn/supabase-storage-migrate/blob/main/Supabase_Storage_migration.ipynb';
|
|
481
|
+
|
|
482
|
+
console.log(chalk.yellow('\n ⚠️ Migração de objetos de Storage requer processo manual'));
|
|
483
|
+
console.log(chalk.cyan(` ℹ️ Use o script do Google Colab: ${colabUrl}`));
|
|
484
|
+
console.log(chalk.gray('\n 📋 Instruções:'));
|
|
485
|
+
console.log(chalk.gray(' 1. Execute o script no Google Colab'));
|
|
486
|
+
console.log(chalk.gray(' 2. Configure as credenciais dos projetos (origem e destino)'));
|
|
487
|
+
console.log(chalk.gray(' 3. Execute a migração'));
|
|
488
|
+
|
|
489
|
+
await inquirer.prompt([{
|
|
490
|
+
type: 'input',
|
|
491
|
+
name: 'continue',
|
|
492
|
+
message: 'Pressione Enter para continuar'
|
|
493
|
+
}]);
|
|
494
|
+
|
|
495
|
+
} catch (error) {
|
|
496
|
+
console.error(chalk.red(` ❌ Erro ao processar Storage: ${error.message}`));
|
|
497
|
+
}
|
|
369
498
|
}
|
|
370
499
|
|
|
371
|
-
// Restaurar Auth Settings (
|
|
500
|
+
// Restaurar Auth Settings (interativo - exibir URL e valores)
|
|
372
501
|
async function restoreAuthSettings(backupPath, targetProject) {
|
|
373
|
-
console.log(chalk.blue('🔐 Restaurando Auth Settings...'));
|
|
374
|
-
|
|
375
|
-
|
|
502
|
+
console.log(chalk.blue('\n🔐 Restaurando Auth Settings...'));
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const authSettingsPath = path.join(backupPath, 'auth-settings.json');
|
|
506
|
+
|
|
507
|
+
if (!fs.existsSync(authSettingsPath)) {
|
|
508
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Auth encontrada no backup'));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const authSettings = JSON.parse(fs.readFileSync(authSettingsPath, 'utf8'));
|
|
513
|
+
const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/auth/url-config`;
|
|
514
|
+
|
|
515
|
+
console.log(chalk.green('\n ✅ URL para configuração manual:'));
|
|
516
|
+
console.log(chalk.cyan(` ${dashboardUrl}`));
|
|
517
|
+
console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
|
|
518
|
+
|
|
519
|
+
if (authSettings.auth_url_config) {
|
|
520
|
+
Object.entries(authSettings.auth_url_config).forEach(([key, value]) => {
|
|
521
|
+
console.log(chalk.gray(` - ${key}: ${value}`));
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
|
|
526
|
+
|
|
527
|
+
await inquirer.prompt([{
|
|
528
|
+
type: 'input',
|
|
529
|
+
name: 'continue',
|
|
530
|
+
message: 'Pressione Enter para continuar'
|
|
531
|
+
}]);
|
|
532
|
+
|
|
533
|
+
console.log(chalk.green(' ✅ Auth Settings processados'));
|
|
534
|
+
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.error(chalk.red(` ❌ Erro ao processar Auth Settings: ${error.message}`));
|
|
537
|
+
}
|
|
376
538
|
}
|
|
377
539
|
|
|
378
|
-
// Restaurar Database Settings (
|
|
540
|
+
// Restaurar Database Settings (via SQL)
|
|
379
541
|
async function restoreDatabaseSettings(backupPath, targetProject) {
|
|
380
|
-
console.log(chalk.blue('🔧 Restaurando Database Settings...'));
|
|
381
|
-
|
|
382
|
-
|
|
542
|
+
console.log(chalk.blue('\n🔧 Restaurando Database Settings...'));
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
const files = fs.readdirSync(backupPath);
|
|
546
|
+
const dbSettingsFile = files.find(f => f.startsWith('database-settings-') && f.endsWith('.json'));
|
|
547
|
+
|
|
548
|
+
if (!dbSettingsFile) {
|
|
549
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Database encontrada no backup'));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const dbSettings = JSON.parse(fs.readFileSync(path.join(backupPath, dbSettingsFile), 'utf8'));
|
|
554
|
+
const { execSync } = require('child_process');
|
|
555
|
+
|
|
556
|
+
if (dbSettings.extensions && dbSettings.extensions.length > 0) {
|
|
557
|
+
console.log(chalk.gray(` - Habilitando ${dbSettings.extensions.length} extension(s)...`));
|
|
558
|
+
|
|
559
|
+
for (const ext of dbSettings.extensions) {
|
|
560
|
+
console.log(chalk.gray(` - ${ext}`));
|
|
561
|
+
|
|
562
|
+
const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${ext};`;
|
|
563
|
+
|
|
564
|
+
const urlMatch = targetProject.targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
|
|
565
|
+
|
|
566
|
+
if (!urlMatch) {
|
|
567
|
+
console.log(chalk.yellow(` ⚠️ URL inválida para ${ext}`));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const dockerCmd = [
|
|
572
|
+
'docker run --rm',
|
|
573
|
+
'--network host',
|
|
574
|
+
`-e PGPASSWORD="${encodeURIComponent(urlMatch[2])}"`,
|
|
575
|
+
'postgres:17 psql',
|
|
576
|
+
`-d "${targetProject.targetDatabaseUrl}"`,
|
|
577
|
+
`-c "${sqlCommand}"`
|
|
578
|
+
].join(' ');
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
582
|
+
} catch (sqlError) {
|
|
583
|
+
console.log(chalk.yellow(` ⚠️ ${ext} - extension já existe ou não pode ser habilitada`));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
console.log(chalk.green(' ✅ Database Settings restaurados com sucesso!'));
|
|
589
|
+
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error(chalk.red(` ❌ Erro ao restaurar Database Settings: ${error.message}`));
|
|
592
|
+
}
|
|
383
593
|
}
|
|
384
594
|
|
|
385
|
-
// Restaurar Realtime Settings (
|
|
595
|
+
// Restaurar Realtime Settings (interativo - exibir URL e valores)
|
|
386
596
|
async function restoreRealtimeSettings(backupPath, targetProject) {
|
|
387
|
-
console.log(chalk.blue('🔄 Restaurando Realtime Settings...'));
|
|
388
|
-
|
|
389
|
-
|
|
597
|
+
console.log(chalk.blue('\n🔄 Restaurando Realtime Settings...'));
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const realtimeSettingsPath = path.join(backupPath, 'realtime-settings.json');
|
|
601
|
+
|
|
602
|
+
if (!fs.existsSync(realtimeSettingsPath)) {
|
|
603
|
+
console.log(chalk.yellow(' ⚠️ Nenhuma configuração de Realtime encontrada no backup'));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const realtimeSettings = JSON.parse(fs.readFileSync(realtimeSettingsPath, 'utf8'));
|
|
608
|
+
const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/realtime/settings`;
|
|
609
|
+
|
|
610
|
+
console.log(chalk.green('\n ✅ URL para configuração manual:'));
|
|
611
|
+
console.log(chalk.cyan(` ${dashboardUrl}`));
|
|
612
|
+
console.log(chalk.yellow('\n 📋 Configure manualmente as seguintes opções:'));
|
|
613
|
+
|
|
614
|
+
if (realtimeSettings.realtime_settings?.settings) {
|
|
615
|
+
Object.entries(realtimeSettings.realtime_settings.settings).forEach(([key, setting]) => {
|
|
616
|
+
console.log(chalk.gray(` - ${setting.label}: ${setting.value}`));
|
|
617
|
+
if (setting.description) {
|
|
618
|
+
console.log(chalk.gray(` ${setting.description}`));
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
console.log(chalk.yellow('\n ⚠️ Após configurar, pressione Enter para continuar...'));
|
|
624
|
+
|
|
625
|
+
await inquirer.prompt([{
|
|
626
|
+
type: 'input',
|
|
627
|
+
name: 'continue',
|
|
628
|
+
message: 'Pressione Enter para continuar'
|
|
629
|
+
}]);
|
|
630
|
+
|
|
631
|
+
console.log(chalk.green(' ✅ Realtime Settings processados'));
|
|
632
|
+
|
|
633
|
+
} catch (error) {
|
|
634
|
+
console.error(chalk.red(` ❌ Erro ao processar Realtime Settings: ${error.message}`));
|
|
635
|
+
}
|
|
390
636
|
}
|