smoonb 0.0.54 → 0.0.56
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 +14 -12
- package/src/commands/backup/steps/00-docker-validation.js +2 -2
- package/src/commands/backup/steps/01-database.js +2 -2
- package/src/commands/backup/steps/02-database-separated.js +4 -4
- package/src/commands/backup/steps/03-database-settings.js +2 -2
- package/src/commands/backup/steps/04-auth-settings.js +1 -1
- package/src/commands/backup/steps/05-realtime-settings.js +1 -1
- package/src/commands/backup/steps/06-storage.js +4 -4
- package/src/commands/backup/steps/07-custom-roles.js +1 -1
- package/src/commands/backup/steps/08-edge-functions.js +57 -17
- package/src/commands/backup/steps/09-supabase-temp.js +3 -3
- package/src/commands/backup/steps/10-migrations.js +4 -4
- package/src/commands/restore/steps/03-database.js +7 -7
- package/src/commands/restore/steps/04-edge-functions.js +8 -8
- package/src/commands/restore/steps/05-auth-settings.js +2 -2
- package/src/commands/restore/steps/06-storage.js +6 -6
- package/src/commands/restore/steps/07-database-settings.js +2 -2
- package/src/commands/restore/steps/08-realtime-settings.js +2 -2
- package/src/utils/supabaseLink.js +2 -2
package/package.json
CHANGED
|
@@ -139,22 +139,22 @@ module.exports = async (options) => {
|
|
|
139
139
|
|
|
140
140
|
// Mostrar resumo e pedir confirmação final
|
|
141
141
|
console.log(chalk.cyan('\n📋 RESUMO DAS CONFIGURAÇÕES:\n'));
|
|
142
|
-
console.log(chalk.
|
|
142
|
+
console.log(chalk.white(` ✅ Edge Functions: ${flags.includeFunctions ? 'Sim' : 'Não'}`));
|
|
143
143
|
if (flags.includeFunctions) {
|
|
144
|
-
console.log(chalk.
|
|
144
|
+
console.log(chalk.white(` 🗑️ Limpar após backup: ${flags.cleanFunctions ? 'Sim' : 'Não'}`));
|
|
145
145
|
}
|
|
146
|
-
console.log(chalk.
|
|
146
|
+
console.log(chalk.white(` ✅ Supabase .temp: ${flags.includeTemp ? 'Sim' : 'Não'}`));
|
|
147
147
|
if (flags.includeTemp) {
|
|
148
|
-
console.log(chalk.
|
|
148
|
+
console.log(chalk.white(` 🗑️ Apagar após backup: ${flags.cleanTemp ? 'Sim' : 'Não'}`));
|
|
149
149
|
}
|
|
150
|
-
console.log(chalk.
|
|
150
|
+
console.log(chalk.white(` ✅ Migrations: ${flags.includeMigrations ? 'Sim' : 'Não'}`));
|
|
151
151
|
if (flags.includeMigrations) {
|
|
152
|
-
console.log(chalk.
|
|
152
|
+
console.log(chalk.white(` 🗑️ Apagar após backup: ${flags.cleanMigrations ? 'Sim' : 'Não'}`));
|
|
153
153
|
}
|
|
154
|
-
console.log(chalk.
|
|
155
|
-
console.log(chalk.
|
|
156
|
-
console.log(chalk.
|
|
157
|
-
console.log(chalk.
|
|
154
|
+
console.log(chalk.white(` ✅ Storage: ${flags.includeStorage ? 'Sim' : 'Não'}`));
|
|
155
|
+
console.log(chalk.white(` ✅ Auth: ${flags.includeAuth ? 'Sim' : 'Não'}`));
|
|
156
|
+
console.log(chalk.white(` ✅ Realtime: ${flags.includeRealtime ? 'Sim' : 'Não'}`));
|
|
157
|
+
console.log(chalk.white(` 📁 Diretório de backup: ${finalBackupDir}\n`));
|
|
158
158
|
|
|
159
159
|
const finalOk = await confirm('Deseja iniciar o backup com estas configurações?', true);
|
|
160
160
|
|
|
@@ -193,11 +193,13 @@ module.exports = async (options) => {
|
|
|
193
193
|
|
|
194
194
|
// Executar todas as etapas na ordem
|
|
195
195
|
console.log(chalk.blue(`📁 Diretório: ${finalBackupDir}`));
|
|
196
|
-
console.log(chalk.
|
|
196
|
+
console.log(chalk.white(`🐳 Backup via Docker Desktop`));
|
|
197
197
|
|
|
198
198
|
// Contar etapas totais para numeração
|
|
199
|
+
// Etapas fixas: Database, Database Separado, Database Settings, Custom Roles (4)
|
|
200
|
+
// Etapas condicionais: Auth, Realtime, Storage, Functions, Temp, Migrations
|
|
199
201
|
let stepNumber = 0;
|
|
200
|
-
const totalSteps =
|
|
202
|
+
const totalSteps = 4 + (flags?.includeAuth ? 1 : 0) + (flags?.includeRealtime ? 1 : 0) + (flags?.includeStorage ? 1 : 0) + (flags?.includeFunctions ? 1 : 0) + (flags?.includeTemp ? 1 : 0) + (flags?.includeMigrations ? 1 : 0);
|
|
201
203
|
|
|
202
204
|
// 1. Backup Database via pg_dumpall Docker
|
|
203
205
|
stepNumber++;
|
|
@@ -8,7 +8,7 @@ const { showDockerMessagesAndExit } = require('../utils');
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = async () => {
|
|
10
10
|
console.log(chalk.blue('\n🐳 Validação Docker...'));
|
|
11
|
-
console.log(chalk.
|
|
11
|
+
console.log(chalk.cyan('🔍 Verificando dependências Docker...'));
|
|
12
12
|
|
|
13
13
|
const backupCapability = await canPerformCompleteBackup();
|
|
14
14
|
|
|
@@ -17,7 +17,7 @@ module.exports = async () => {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
console.log(chalk.green('✅ Docker Desktop detectado e funcionando'));
|
|
20
|
-
console.log(chalk.
|
|
20
|
+
console.log(chalk.white(`🐳 Versão: ${backupCapability.dockerStatus.version}`));
|
|
21
21
|
|
|
22
22
|
return { success: true };
|
|
23
23
|
};
|
|
@@ -8,7 +8,7 @@ const { execSync } = require('child_process');
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = async ({ databaseUrl, backupDir }) => {
|
|
10
10
|
try {
|
|
11
|
-
console.log(chalk.
|
|
11
|
+
console.log(chalk.white(' - Criando backup completo via pg_dumpall...'));
|
|
12
12
|
|
|
13
13
|
// Extrair credenciais da databaseUrl
|
|
14
14
|
const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
|
|
@@ -45,7 +45,7 @@ module.exports = async ({ databaseUrl, backupDir }) => {
|
|
|
45
45
|
`-f /host/${fileName}`
|
|
46
46
|
].join(' ');
|
|
47
47
|
|
|
48
|
-
console.log(chalk.
|
|
48
|
+
console.log(chalk.white(' - Executando pg_dumpall via Docker...'));
|
|
49
49
|
execSync(dockerCmd, { stdio: 'pipe' });
|
|
50
50
|
|
|
51
51
|
// Compactar igual ao Supabase Dashboard
|
|
@@ -8,14 +8,14 @@ const { execSync } = require('child_process');
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
|
|
10
10
|
try {
|
|
11
|
-
console.log(chalk.
|
|
11
|
+
console.log(chalk.white(' - Criando backups SQL separados via Supabase CLI...'));
|
|
12
12
|
|
|
13
13
|
const dbUrl = databaseUrl;
|
|
14
14
|
const files = [];
|
|
15
15
|
let totalSizeKB = 0;
|
|
16
16
|
|
|
17
17
|
// 1. Backup do Schema
|
|
18
|
-
console.log(chalk.
|
|
18
|
+
console.log(chalk.white(' - Exportando schema...'));
|
|
19
19
|
const schemaFile = path.join(backupDir, 'schema.sql');
|
|
20
20
|
|
|
21
21
|
try {
|
|
@@ -33,7 +33,7 @@ module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// 2. Backup dos Dados
|
|
36
|
-
console.log(chalk.
|
|
36
|
+
console.log(chalk.white(' - Exportando dados...'));
|
|
37
37
|
const dataFile = path.join(backupDir, 'data.sql');
|
|
38
38
|
|
|
39
39
|
try {
|
|
@@ -51,7 +51,7 @@ module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// 3. Backup dos Roles
|
|
54
|
-
console.log(chalk.
|
|
54
|
+
console.log(chalk.white(' - Exportando roles...'));
|
|
55
55
|
const rolesFile = path.join(backupDir, 'roles.sql');
|
|
56
56
|
|
|
57
57
|
try {
|
|
@@ -8,7 +8,7 @@ const { execSync } = require('child_process');
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = async ({ databaseUrl, projectId, backupDir }) => {
|
|
10
10
|
try {
|
|
11
|
-
console.log(chalk.
|
|
11
|
+
console.log(chalk.white(' - Capturando Database Extensions and Settings...'));
|
|
12
12
|
|
|
13
13
|
// Extrair credenciais da databaseUrl
|
|
14
14
|
const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
|
|
@@ -125,7 +125,7 @@ AND EXISTS (
|
|
|
125
125
|
'-A' // Unaligned output
|
|
126
126
|
].join(' ');
|
|
127
127
|
|
|
128
|
-
console.log(chalk.
|
|
128
|
+
console.log(chalk.white(' - Executando queries de configurações via Docker...'));
|
|
129
129
|
const output = execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
130
130
|
|
|
131
131
|
// Processar output e criar JSON estruturado
|
|
@@ -7,7 +7,7 @@ const { writeJson } = require('../../../utils/fsx');
|
|
|
7
7
|
*/
|
|
8
8
|
module.exports = async ({ projectId, accessToken, backupDir }) => {
|
|
9
9
|
try {
|
|
10
|
-
console.log(chalk.
|
|
10
|
+
console.log(chalk.white(' - Exportando configurações de Auth via Management API...'));
|
|
11
11
|
|
|
12
12
|
// Usar fetch direto para Management API com Personal Access Token
|
|
13
13
|
const authResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/config/auth`, {
|
|
@@ -8,7 +8,7 @@ const { captureRealtimeSettings } = require('../../../utils/realtime-settings');
|
|
|
8
8
|
*/
|
|
9
9
|
module.exports = async ({ projectId, backupDir, options }) => {
|
|
10
10
|
try {
|
|
11
|
-
console.log(chalk.
|
|
11
|
+
console.log(chalk.white(' - Capturando Realtime Settings interativamente...'));
|
|
12
12
|
|
|
13
13
|
const result = await captureRealtimeSettings(projectId, backupDir, options?.skipRealtime);
|
|
14
14
|
|
|
@@ -10,7 +10,7 @@ module.exports = async ({ projectId, accessToken, backupDir }) => {
|
|
|
10
10
|
const storageDir = path.join(backupDir, 'storage');
|
|
11
11
|
await ensureDir(storageDir);
|
|
12
12
|
|
|
13
|
-
console.log(chalk.
|
|
13
|
+
console.log(chalk.white(' - Listando buckets de Storage via Management API...'));
|
|
14
14
|
|
|
15
15
|
// Usar fetch direto para Management API com Personal Access Token
|
|
16
16
|
const storageResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/storage/buckets`, {
|
|
@@ -28,20 +28,20 @@ module.exports = async ({ projectId, accessToken, backupDir }) => {
|
|
|
28
28
|
const buckets = await storageResponse.json();
|
|
29
29
|
|
|
30
30
|
if (!buckets || buckets.length === 0) {
|
|
31
|
-
console.log(chalk.
|
|
31
|
+
console.log(chalk.white(' - Nenhum bucket encontrado'));
|
|
32
32
|
await writeJson(path.join(storageDir, 'README.md'), {
|
|
33
33
|
message: 'Nenhum bucket de Storage encontrado neste projeto'
|
|
34
34
|
});
|
|
35
35
|
return { success: true, buckets: [] };
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
console.log(chalk.
|
|
38
|
+
console.log(chalk.white(` - Encontrados ${buckets.length} buckets`));
|
|
39
39
|
|
|
40
40
|
const processedBuckets = [];
|
|
41
41
|
|
|
42
42
|
for (const bucket of buckets || []) {
|
|
43
43
|
try {
|
|
44
|
-
console.log(chalk.
|
|
44
|
+
console.log(chalk.white(` - Processando bucket: ${bucket.name}`));
|
|
45
45
|
|
|
46
46
|
// Listar objetos do bucket via Management API com Personal Access Token
|
|
47
47
|
const objectsResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/storage/buckets/${bucket.name}/objects`, {
|
|
@@ -11,7 +11,7 @@ const execAsync = promisify(exec);
|
|
|
11
11
|
*/
|
|
12
12
|
module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
|
|
13
13
|
try {
|
|
14
|
-
console.log(chalk.
|
|
14
|
+
console.log(chalk.white(' - Exportando Custom Roles via Docker...'));
|
|
15
15
|
|
|
16
16
|
const customRolesFile = path.join(backupDir, 'custom-roles.sql');
|
|
17
17
|
|
|
@@ -18,12 +18,45 @@ module.exports = async (context) => {
|
|
|
18
18
|
const dbPassword = extractPasswordFromDbUrl(databaseUrl);
|
|
19
19
|
await ensureCleanLink(projectId, accessToken, dbPassword);
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Preparar diretório supabase/functions (criar se não existir, mas não limpar ainda)
|
|
22
22
|
const supabaseFunctionsDir = path.join(process.cwd(), 'supabase', 'functions');
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
// Verificar flag de limpeza antes do backup
|
|
25
|
+
const shouldCleanAfter = context?.cleanupFlags?.cleanFunctions || false;
|
|
26
|
+
|
|
27
|
+
// Registrar funções que já existiam ANTES do processo (para preservar se necessário)
|
|
28
|
+
let existingFunctionsBefore = [];
|
|
29
|
+
try {
|
|
30
|
+
const existingItems = await fs.readdir(supabaseFunctionsDir);
|
|
31
|
+
for (const item of existingItems) {
|
|
32
|
+
const itemPath = path.join(supabaseFunctionsDir, item);
|
|
33
|
+
const stats = await fs.stat(itemPath);
|
|
34
|
+
if (stats.isDirectory()) {
|
|
35
|
+
existingFunctionsBefore.push(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Diretório não existe, tudo bem
|
|
40
|
+
existingFunctionsBefore = [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Se o usuário escolheu limpar APÓS, podemos limpar ANTES também para garantir ambiente limpo
|
|
44
|
+
// Mas se escolheu NÃO limpar, preservamos o que já existe
|
|
45
|
+
if (shouldCleanAfter) {
|
|
46
|
+
// Limpar antes se o usuário escolheu limpar após (garante ambiente limpo)
|
|
47
|
+
await cleanDir(supabaseFunctionsDir);
|
|
48
|
+
console.log(chalk.white(' - Pasta supabase/functions limpa antes do backup.'));
|
|
49
|
+
} else {
|
|
50
|
+
// Apenas garantir que o diretório existe
|
|
51
|
+
await fs.mkdir(supabaseFunctionsDir, { recursive: true });
|
|
52
|
+
if (existingFunctionsBefore.length > 0) {
|
|
53
|
+
console.log(chalk.white(` - Preservando ${existingFunctionsBefore.length} função(ões) existente(s) na pasta supabase/functions.`));
|
|
54
|
+
} else {
|
|
55
|
+
console.log(chalk.white(' - Pasta supabase/functions preparada (será preservada após backup).'));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
25
58
|
|
|
26
|
-
console.log(chalk.
|
|
59
|
+
console.log(chalk.white(' - Listando Edge Functions via Management API...'));
|
|
27
60
|
|
|
28
61
|
// Usar fetch direto para Management API com Personal Access Token
|
|
29
62
|
const functionsResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/functions`, {
|
|
@@ -41,14 +74,14 @@ module.exports = async (context) => {
|
|
|
41
74
|
const functions = await functionsResponse.json();
|
|
42
75
|
|
|
43
76
|
if (!functions || functions.length === 0) {
|
|
44
|
-
console.log(chalk.
|
|
77
|
+
console.log(chalk.white(' - Nenhuma Edge Function encontrada'));
|
|
45
78
|
await writeJson(path.join(functionsDir, 'README.md'), {
|
|
46
79
|
message: 'Nenhuma Edge Function encontrada neste projeto'
|
|
47
80
|
});
|
|
48
81
|
return { success: true, reason: 'no_functions', functions: [] };
|
|
49
82
|
}
|
|
50
83
|
|
|
51
|
-
console.log(chalk.
|
|
84
|
+
console.log(chalk.white(` - Encontradas ${functions.length} Edge Function(s)`));
|
|
52
85
|
|
|
53
86
|
const downloadedFunctions = [];
|
|
54
87
|
let successCount = 0;
|
|
@@ -58,7 +91,7 @@ module.exports = async (context) => {
|
|
|
58
91
|
// Nota: O CLI ignora o cwd e sempre baixa para supabase/functions
|
|
59
92
|
for (const func of functions) {
|
|
60
93
|
try {
|
|
61
|
-
console.log(chalk.
|
|
94
|
+
console.log(chalk.white(` - Baixando: ${func.name}...`));
|
|
62
95
|
|
|
63
96
|
// Criar diretório da função NO BACKUP
|
|
64
97
|
const functionTargetDir = path.join(functionsDir, func.name);
|
|
@@ -99,12 +132,16 @@ module.exports = async (context) => {
|
|
|
99
132
|
console.log(chalk.yellow(` ⚠️ Nenhum arquivo encontrado em ${tempDownloadDir}`));
|
|
100
133
|
}
|
|
101
134
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
135
|
+
// Limpar função baixada temporariamente APENAS se o usuário escolheu limpar após
|
|
136
|
+
// Se não escolheu, preservar (pode ser que já existisse antes)
|
|
137
|
+
if (shouldCleanAfter) {
|
|
138
|
+
try {
|
|
139
|
+
await fs.rm(tempDownloadDir, { recursive: true, force: true });
|
|
140
|
+
} catch {
|
|
141
|
+
// Ignorar erro de limpeza
|
|
142
|
+
}
|
|
107
143
|
}
|
|
144
|
+
// Se shouldCleanAfter = false, manter a função baixada (e qualquer função que já existia)
|
|
108
145
|
|
|
109
146
|
console.log(chalk.green(` ✅ ${func.name} baixada com sucesso`));
|
|
110
147
|
successCount++;
|
|
@@ -126,12 +163,15 @@ module.exports = async (context) => {
|
|
|
126
163
|
console.log(chalk.green(` ✅ Sucessos: ${successCount}`));
|
|
127
164
|
console.log(chalk.green(` ❌ Erros: ${errorCount}`));
|
|
128
165
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (shouldClean) {
|
|
166
|
+
// Limpar pasta supabase/functions APÓS o backup apenas se o usuário escolheu
|
|
167
|
+
// Nota: shouldCleanAfter já foi definido acima
|
|
168
|
+
if (shouldCleanAfter) {
|
|
133
169
|
await cleanDir(supabaseFunctionsDir);
|
|
134
|
-
console.log(chalk.
|
|
170
|
+
console.log(chalk.white(' - supabase/functions limpo após o backup.'));
|
|
171
|
+
} else {
|
|
172
|
+
// Preservar tudo: tanto as funções que já existiam quanto as que foram baixadas
|
|
173
|
+
// As funções baixadas não foram removidas individualmente (linha acima foi ajustada)
|
|
174
|
+
console.log(chalk.white(' - supabase/functions preservada conforme solicitado.'));
|
|
135
175
|
}
|
|
136
176
|
|
|
137
177
|
return {
|
|
@@ -14,10 +14,10 @@ module.exports = async (context) => {
|
|
|
14
14
|
|
|
15
15
|
const fileCount = await copyDirSafe(tempDir, backupTempDir);
|
|
16
16
|
|
|
17
|
-
console.log(chalk.
|
|
17
|
+
console.log(chalk.white(` - Copiando supabase/.temp → backups/backup-${path.basename(backupDir)}/supabase-temp (${fileCount} arquivos)...`));
|
|
18
18
|
|
|
19
19
|
if (fileCount === 0) {
|
|
20
|
-
console.log(chalk.
|
|
20
|
+
console.log(chalk.white(' - Nenhum arquivo encontrado em supabase/.temp'));
|
|
21
21
|
} else {
|
|
22
22
|
console.log(chalk.green(` ✅ ${fileCount} arquivo(s) copiado(s)`));
|
|
23
23
|
}
|
|
@@ -27,7 +27,7 @@ module.exports = async (context) => {
|
|
|
27
27
|
|
|
28
28
|
if (shouldClean) {
|
|
29
29
|
await cleanDir(tempDir);
|
|
30
|
-
console.log(chalk.
|
|
30
|
+
console.log(chalk.white(' - supabase/.temp apagado.'));
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return {
|
|
@@ -17,10 +17,10 @@ module.exports = async (context) => {
|
|
|
17
17
|
// Limpar migrations local (opcional, mas recomendado para garantir servidor como fonte da verdade)
|
|
18
18
|
const migrationsDir = path.join(process.cwd(), 'supabase', 'migrations');
|
|
19
19
|
await cleanDir(migrationsDir);
|
|
20
|
-
console.log(chalk.
|
|
20
|
+
console.log(chalk.white(' - Limpando supabase/migrations...'));
|
|
21
21
|
|
|
22
22
|
// Baixar todas as migrations do servidor usando migration fetch
|
|
23
|
-
console.log(chalk.
|
|
23
|
+
console.log(chalk.white(' - Baixando todas as migrations do servidor usando migration fetch...'));
|
|
24
24
|
|
|
25
25
|
const env = {
|
|
26
26
|
...process.env,
|
|
@@ -43,12 +43,12 @@ module.exports = async (context) => {
|
|
|
43
43
|
|
|
44
44
|
// Contar arquivos baixados
|
|
45
45
|
const fileCount = await countFiles(migrationsDir);
|
|
46
|
-
console.log(chalk.
|
|
46
|
+
console.log(chalk.white(` - Arquivos baixados: ${fileCount} migrations`));
|
|
47
47
|
|
|
48
48
|
// Copiar migrations para o backup
|
|
49
49
|
const backupMigrationsDir = path.join(backupDir, 'migrations');
|
|
50
50
|
const copiedCount = await copyDirSafe(migrationsDir, backupMigrationsDir);
|
|
51
|
-
console.log(chalk.
|
|
51
|
+
console.log(chalk.white(` - Copiando supabase/migrations → backups/backup-${path.basename(backupDir)}/migrations (${copiedCount} arquivos)...`));
|
|
52
52
|
|
|
53
53
|
if (copiedCount > 0) {
|
|
54
54
|
console.log(chalk.green(` ✅ ${copiedCount} migration(s) copiada(s)`));
|
|
@@ -13,8 +13,8 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
13
13
|
|
|
14
14
|
// Verificar se é arquivo .backup.gz (compactado) ou .backup (descompactado)
|
|
15
15
|
if (fileName.endsWith('.backup.gz')) {
|
|
16
|
-
console.log(chalk.
|
|
17
|
-
console.log(chalk.
|
|
16
|
+
console.log(chalk.white(' - Arquivo .backup.gz detectado'));
|
|
17
|
+
console.log(chalk.white(' - Extraindo arquivo .gz...'));
|
|
18
18
|
|
|
19
19
|
const unzipCmd = [
|
|
20
20
|
'docker run --rm',
|
|
@@ -24,10 +24,10 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
24
24
|
|
|
25
25
|
execSync(unzipCmd, { stdio: 'pipe' });
|
|
26
26
|
uncompressedFile = fileName.replace('.gz', '');
|
|
27
|
-
console.log(chalk.
|
|
27
|
+
console.log(chalk.white(' - Arquivo descompactado: ' + uncompressedFile));
|
|
28
28
|
} else if (fileName.endsWith('.backup')) {
|
|
29
|
-
console.log(chalk.
|
|
30
|
-
console.log(chalk.
|
|
29
|
+
console.log(chalk.white(' - Arquivo .backup detectado (já descompactado)'));
|
|
30
|
+
console.log(chalk.white(' - Prosseguindo com restauração direta'));
|
|
31
31
|
} else {
|
|
32
32
|
throw new Error(`Formato de arquivo inválido. Esperado .backup.gz ou .backup, recebido: ${fileName}`);
|
|
33
33
|
}
|
|
@@ -49,8 +49,8 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
49
49
|
`-f /host/${uncompressedFile}`
|
|
50
50
|
].join(' ');
|
|
51
51
|
|
|
52
|
-
console.log(chalk.
|
|
53
|
-
console.log(chalk.
|
|
52
|
+
console.log(chalk.cyan(' - Executando psql via Docker...'));
|
|
53
|
+
console.log(chalk.cyan(' ℹ️ Seguindo documentação oficial Supabase'));
|
|
54
54
|
console.log(chalk.yellow(' ⚠️ AVISO: Erros como "object already exists" são ESPERADOS'));
|
|
55
55
|
console.log(chalk.yellow(' ⚠️ Isto acontece porque o backup contém CREATE para todos os schemas'));
|
|
56
56
|
console.log(chalk.yellow(' ⚠️ Supabase já tem auth e storage criados, então esses erros são normais'));
|
|
@@ -32,7 +32,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
32
32
|
return { success: false, functions_count: 0, success_count: 0 };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
console.log(chalk.
|
|
35
|
+
console.log(chalk.white(` - Encontradas ${functions.length} Edge Function(s)`));
|
|
36
36
|
|
|
37
37
|
// COPIAR Edge Functions de backups/backup-XXX/edge-functions para supabase/functions
|
|
38
38
|
const supabaseFunctionsDir = path.join(process.cwd(), 'supabase', 'functions');
|
|
@@ -40,8 +40,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
40
40
|
// Criar diretório supabase/functions se não existir
|
|
41
41
|
await fs.mkdir(supabaseFunctionsDir, { recursive: true });
|
|
42
42
|
|
|
43
|
-
// Limpar supabase/functions antes de copiar
|
|
44
|
-
console.log(chalk.
|
|
43
|
+
// Limpar supabase/functions antes de copiar (necessário para garantir ambiente limpo)
|
|
44
|
+
console.log(chalk.cyan(' - Limpando supabase/functions antes de copiar...'));
|
|
45
45
|
try {
|
|
46
46
|
await fs.rm(supabaseFunctionsDir, { recursive: true, force: true });
|
|
47
47
|
await fs.mkdir(supabaseFunctionsDir, { recursive: true });
|
|
@@ -54,13 +54,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
54
54
|
const backupFuncPath = path.join(edgeFunctionsDir, funcName);
|
|
55
55
|
const targetFuncPath = path.join(supabaseFunctionsDir, funcName);
|
|
56
56
|
|
|
57
|
-
console.log(chalk.
|
|
57
|
+
console.log(chalk.white(` - Copiando ${funcName} para supabase/functions...`));
|
|
58
58
|
|
|
59
59
|
// Copiar recursivamente
|
|
60
60
|
await copyDirectoryRecursive(backupFuncPath, targetFuncPath);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
console.log(chalk.
|
|
63
|
+
console.log(chalk.white(` - Linkando com projeto ${targetProject.targetProjectId}...`));
|
|
64
64
|
|
|
65
65
|
// Linkar com o projeto destino
|
|
66
66
|
try {
|
|
@@ -77,7 +77,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
77
77
|
// Deploy das Edge Functions
|
|
78
78
|
let successCount = 0;
|
|
79
79
|
for (const funcName of functions) {
|
|
80
|
-
console.log(chalk.
|
|
80
|
+
console.log(chalk.white(` - Deployando ${funcName}...`));
|
|
81
81
|
|
|
82
82
|
try {
|
|
83
83
|
execSync(`supabase functions deploy ${funcName}`, {
|
|
@@ -95,8 +95,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// Limpar supabase/functions após deploy
|
|
99
|
-
console.log(chalk.
|
|
98
|
+
// Limpar supabase/functions após deploy (arquivos temporários não são mais necessários)
|
|
99
|
+
console.log(chalk.cyan(' - Limpando supabase/functions após deploy...'));
|
|
100
100
|
try {
|
|
101
101
|
await fs.rm(supabaseFunctionsDir, { recursive: true, force: true });
|
|
102
102
|
} catch {
|
|
@@ -25,11 +25,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
25
25
|
|
|
26
26
|
if (authSettings.settings?.auth_url_config) {
|
|
27
27
|
Object.entries(authSettings.settings.auth_url_config).forEach(([key, value]) => {
|
|
28
|
-
console.log(chalk.
|
|
28
|
+
console.log(chalk.white(` - ${key}: ${value}`));
|
|
29
29
|
});
|
|
30
30
|
} else if (authSettings.auth_url_config) {
|
|
31
31
|
Object.entries(authSettings.auth_url_config).forEach(([key, value]) => {
|
|
32
|
-
console.log(chalk.
|
|
32
|
+
console.log(chalk.white(` - ${key}: ${value}`));
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -26,23 +26,23 @@ module.exports = async ({ backupPath }) => {
|
|
|
26
26
|
const buckets = manifest?.components?.storage?.buckets || [];
|
|
27
27
|
|
|
28
28
|
if (buckets.length === 0) {
|
|
29
|
-
console.log(chalk.
|
|
29
|
+
console.log(chalk.white(' ℹ️ Nenhum bucket para restaurar'));
|
|
30
30
|
return { success: false, buckets_count: 0 };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
console.log(chalk.green(`\n ✅ ${buckets.length} bucket(s) encontrado(s) no backup`));
|
|
34
34
|
buckets.forEach(bucket => {
|
|
35
|
-
console.log(chalk.
|
|
35
|
+
console.log(chalk.white(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
const colabUrl = 'https://colab.research.google.com/github/PLyn/supabase-storage-migrate/blob/main/Supabase_Storage_migration.ipynb';
|
|
39
39
|
|
|
40
40
|
console.log(chalk.yellow('\n ⚠️ Migração de objetos de Storage requer processo manual'));
|
|
41
41
|
console.log(chalk.cyan(` ℹ️ Use o script do Google Colab: ${colabUrl}`));
|
|
42
|
-
console.log(chalk.
|
|
43
|
-
console.log(chalk.
|
|
44
|
-
console.log(chalk.
|
|
45
|
-
console.log(chalk.
|
|
42
|
+
console.log(chalk.white('\n 📋 Instruções:'));
|
|
43
|
+
console.log(chalk.white(' 1. Execute o script no Google Colab'));
|
|
44
|
+
console.log(chalk.white(' 2. Configure as credenciais dos projetos (origem e destino)'));
|
|
45
|
+
console.log(chalk.white(' 3. Execute a migração'));
|
|
46
46
|
|
|
47
47
|
await inquirer.prompt([{
|
|
48
48
|
type: 'input',
|
|
@@ -22,11 +22,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
22
22
|
const extensions = dbSettings.extensions || [];
|
|
23
23
|
|
|
24
24
|
if (extensions.length > 0) {
|
|
25
|
-
console.log(chalk.
|
|
25
|
+
console.log(chalk.white(` - Habilitando ${extensions.length} extension(s)...`));
|
|
26
26
|
|
|
27
27
|
for (const ext of extensions) {
|
|
28
28
|
const extName = typeof ext === 'string' ? ext : ext.name;
|
|
29
|
-
console.log(chalk.
|
|
29
|
+
console.log(chalk.white(` - ${extName}`));
|
|
30
30
|
|
|
31
31
|
const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${extName};`;
|
|
32
32
|
|
|
@@ -25,9 +25,9 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
25
25
|
|
|
26
26
|
if (realtimeSettings.realtime_settings?.settings) {
|
|
27
27
|
Object.values(realtimeSettings.realtime_settings.settings).forEach((setting) => {
|
|
28
|
-
console.log(chalk.
|
|
28
|
+
console.log(chalk.white(` - ${setting.label}: ${setting.value}`));
|
|
29
29
|
if (setting.description) {
|
|
30
|
-
console.log(chalk.
|
|
30
|
+
console.log(chalk.white(` ${setting.description}`));
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
}
|
|
@@ -28,7 +28,7 @@ async function ensureCleanLink(projectRef, accessToken, dbPassword) {
|
|
|
28
28
|
const tempDir = path.join(process.cwd(), 'supabase', '.temp');
|
|
29
29
|
|
|
30
30
|
// Remover supabase/.temp completamente
|
|
31
|
-
console.log(chalk.
|
|
31
|
+
console.log(chalk.white(` - Zerando vínculo e linkando projeto: ${projectRef}...`));
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
34
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
@@ -66,7 +66,7 @@ async function ensureCleanLink(projectRef, accessToken, dbPassword) {
|
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
console.log(chalk.
|
|
69
|
+
console.log(chalk.white(` - Validação: linked-ref = ${linkedRefTrimmed} (esperado = ${projectRef})`));
|
|
70
70
|
} catch (error) {
|
|
71
71
|
if (error.message.includes('Validação falhou')) {
|
|
72
72
|
throw error;
|