smoonb 0.0.73 → 0.0.75
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 +6 -3
- package/src/commands/backup/steps/02-database-separated.js +11 -8
- package/src/commands/backup/steps/03-database-settings.js +6 -3
- 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 +28 -25
- package/src/commands/backup/steps/07-custom-roles.js +6 -3
- package/src/commands/backup/steps/08-edge-functions.js +14 -10
- package/src/commands/backup/steps/09-supabase-temp.js +9 -6
- package/src/commands/backup/steps/10-migrations.js +13 -9
- package/src/commands/restore/steps/00-backup-selection.js +4 -2
- package/src/commands/restore/steps/01-components-selection.js +30 -28
- package/src/commands/restore/steps/03-database.js +19 -16
- package/src/commands/restore/steps/04-edge-functions.js +16 -13
- package/src/commands/restore/steps/05-auth-settings.js +9 -6
- package/src/commands/restore/steps/06-storage.js +47 -40
- package/src/commands/restore/steps/07-database-settings.js +10 -7
- package/src/commands/restore/steps/08-realtime-settings.js +9 -6
- package/src/i18n/locales/en.json +328 -1
- package/src/i18n/locales/pt-BR.json +327 -1
- package/src/interactive/envMapper.js +30 -25
|
@@ -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
|
};
|
|
@@ -2,6 +2,7 @@ const chalk = require('chalk');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
+
const { t } = require('../../../i18n');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Etapa 5: Restaurar Auth Settings (interativo - exibir URL e valores)
|
|
@@ -9,19 +10,20 @@ const inquirer = require('inquirer');
|
|
|
9
10
|
module.exports = async ({ backupPath, targetProject }) => {
|
|
10
11
|
|
|
11
12
|
try {
|
|
13
|
+
const getT = global.smoonbI18n?.t || t;
|
|
12
14
|
const authSettingsPath = path.join(backupPath, 'auth-settings.json');
|
|
13
15
|
|
|
14
16
|
if (!fs.existsSync(authSettingsPath)) {
|
|
15
|
-
console.log(chalk.yellow(
|
|
17
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.auth.notFound')}`));
|
|
16
18
|
return;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const authSettings = JSON.parse(fs.readFileSync(authSettingsPath, 'utf8'));
|
|
20
22
|
const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/auth/url-config`;
|
|
21
23
|
|
|
22
|
-
console.log(chalk.green(
|
|
24
|
+
console.log(chalk.green(`\n ✅ ${getT('restore.steps.auth.urlTitle')}`));
|
|
23
25
|
console.log(chalk.cyan(` ${dashboardUrl}`));
|
|
24
|
-
console.log(chalk.yellow(
|
|
26
|
+
console.log(chalk.yellow(`\n 📋 ${getT('restore.steps.auth.configureTitle')}`));
|
|
25
27
|
|
|
26
28
|
if (authSettings.settings?.auth_url_config) {
|
|
27
29
|
Object.entries(authSettings.settings.auth_url_config).forEach(([key, value]) => {
|
|
@@ -33,7 +35,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
console.log(chalk.yellow(
|
|
38
|
+
console.log(chalk.yellow(`\n ⚠️ ${getT('restore.steps.auth.pressEnter')}`));
|
|
37
39
|
|
|
38
40
|
await inquirer.prompt([{
|
|
39
41
|
type: 'input',
|
|
@@ -41,10 +43,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
41
43
|
message: 'Pressione Enter para continuar'
|
|
42
44
|
}]);
|
|
43
45
|
|
|
44
|
-
console.log(chalk.green(
|
|
46
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.auth.success')}`));
|
|
45
47
|
|
|
46
48
|
} catch (error) {
|
|
47
|
-
|
|
49
|
+
const getT = global.smoonbI18n?.t || t;
|
|
50
|
+
console.error(chalk.red(` ❌ ${getT('restore.steps.auth.error', { message: error.message })}`));
|
|
48
51
|
}
|
|
49
52
|
};
|
|
50
53
|
|
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const fs = require('fs').promises;
|
|
4
4
|
const AdmZip = require('adm-zip');
|
|
5
5
|
const { createClient } = require('@supabase/supabase-js');
|
|
6
|
+
const { t } = require('../../../i18n');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Etapa 6: Restaurar Storage Buckets e arquivos
|
|
@@ -12,6 +13,7 @@ const { createClient } = require('@supabase/supabase-js');
|
|
|
12
13
|
module.exports = async ({ backupPath, targetProject }) => {
|
|
13
14
|
|
|
14
15
|
try {
|
|
16
|
+
const getT = global.smoonbI18n?.t || t;
|
|
15
17
|
// 1. Verificar se existe arquivo .storage.zip na pasta do backup
|
|
16
18
|
const storageZipFiles = await fs.readdir(backupPath).then(files =>
|
|
17
19
|
files.filter(f => f.endsWith('.storage.zip'))
|
|
@@ -79,21 +81,22 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
79
81
|
const buckets = manifest?.components?.storage?.buckets || [];
|
|
80
82
|
|
|
81
83
|
if (buckets.length === 0) {
|
|
82
|
-
console.log(chalk.yellow(
|
|
84
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.noBuckets')}`));
|
|
83
85
|
return { success: false, buckets_count: 0 };
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
console.log(chalk.yellow(
|
|
87
|
-
console.log(chalk.white(
|
|
88
|
-
console.log(chalk.white(
|
|
89
|
-
console.log(chalk.green(`\n ✅ ${
|
|
88
|
+
console.log(chalk.yellow(`\n ⚠️ ${getT('restore.steps.storage.zipNotFound')}`));
|
|
89
|
+
console.log(chalk.white(` ℹ️ ${getT('restore.steps.storage.metadataOnly')}`));
|
|
90
|
+
console.log(chalk.white(` ℹ️ ${getT('restore.steps.storage.zipRequired')}`));
|
|
91
|
+
console.log(chalk.green(`\n ✅ ${getT('restore.steps.storage.bucketsFoundMetadata', { count: buckets.length })}`));
|
|
90
92
|
buckets.forEach(bucket => {
|
|
91
|
-
|
|
93
|
+
const visibility = bucket.public ? getT('restore.steps.storage.public') : getT('restore.steps.storage.private');
|
|
94
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.bucketInfo', { name: bucket.name, visibility })}`));
|
|
92
95
|
});
|
|
93
96
|
|
|
94
97
|
return { success: true, buckets_count: buckets.length, files_restored: false };
|
|
95
98
|
} catch {
|
|
96
|
-
console.log(chalk.yellow(
|
|
99
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.noBucketsInBackup')}`));
|
|
97
100
|
return { success: false, buckets_count: 0 };
|
|
98
101
|
}
|
|
99
102
|
}
|
|
@@ -101,7 +104,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
101
104
|
// 3. Selecionar o primeiro arquivo .storage.zip encontrado
|
|
102
105
|
const storageZipFile = path.join(backupPath, storageZipFiles[0]);
|
|
103
106
|
const storageZipBaseName = path.basename(storageZipFiles[0], '.storage.zip');
|
|
104
|
-
console.log(chalk.white(` -
|
|
107
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.fileFound', { fileName: storageZipFiles[0] })}`));
|
|
105
108
|
|
|
106
109
|
// 4. Validar credenciais do projeto destino
|
|
107
110
|
if (!targetProject.targetProjectId || !targetProject.targetAccessToken) {
|
|
@@ -114,13 +117,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
114
117
|
|
|
115
118
|
// 4.1 Obter project ID do projeto origem e validar substituição
|
|
116
119
|
if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
|
|
117
|
-
console.log(chalk.cyan(` 🔄
|
|
120
|
+
console.log(chalk.cyan(` 🔄 ${getT('restore.steps.storage.replacingProjectId', { source: sourceProjectId, target: targetProject.targetProjectId })}`));
|
|
118
121
|
} else if (!sourceProjectId) {
|
|
119
|
-
console.log(chalk.yellow(
|
|
122
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.projectIdNotFound')}`));
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// 5. Extrair arquivo ZIP
|
|
123
|
-
console.log(chalk.white(
|
|
126
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.extracting')}`));
|
|
124
127
|
const extractDir = path.join(backupPath, 'storage_extracted');
|
|
125
128
|
|
|
126
129
|
try {
|
|
@@ -131,14 +134,14 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
131
134
|
|
|
132
135
|
const zip = new AdmZip(storageZipFile);
|
|
133
136
|
zip.extractAllTo(extractDir, true);
|
|
134
|
-
console.log(chalk.green(
|
|
137
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.storage.extracted')}`));
|
|
135
138
|
|
|
136
139
|
// 5.1 Substituir Project ID antigo pelo novo nos diretórios e arquivos extraídos
|
|
137
140
|
// O Supabase pode incluir o project ID nos caminhos dentro do ZIP
|
|
138
141
|
if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
|
|
139
|
-
console.log(chalk.white(
|
|
142
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.replacing')}`));
|
|
140
143
|
await replaceProjectIdInExtractedFiles(extractDir, sourceProjectId, targetProject.targetProjectId);
|
|
141
|
-
console.log(chalk.green(
|
|
144
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.storage.replaced')}`));
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
// 6. Ler estrutura de diretórios extraídos
|
|
@@ -179,8 +182,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
179
182
|
if (matchesZipFileName) reasons.push('nome do arquivo ZIP');
|
|
180
183
|
if (matchesProjectIdPattern) reasons.push('formato de Project ID');
|
|
181
184
|
const reasonText = reasons.length ? ` (${reasons.join(', ')})` : '';
|
|
182
|
-
console.log(chalk.white(` -
|
|
183
|
-
console.log(chalk.white(` -
|
|
185
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.detectedProjectId', { projectId: firstItem, reason: reasonText })}`));
|
|
186
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.searchingBuckets')}`));
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
}
|
|
@@ -204,8 +207,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
204
207
|
|
|
205
208
|
// Se não encontrou buckets nas subpastas, avisar e retornar erro
|
|
206
209
|
if (bucketDirs.length === 0) {
|
|
207
|
-
console.log(chalk.red(` ❌
|
|
208
|
-
console.log(chalk.red(` ❌
|
|
210
|
+
console.log(chalk.red(` ❌ ${getT('restore.steps.storage.noBucketsInSubfolders', { rootDir })}`));
|
|
211
|
+
console.log(chalk.red(` ❌ ${getT('restore.steps.storage.rootIsProjectId')}`));
|
|
209
212
|
return { success: false, buckets_count: 0 };
|
|
210
213
|
}
|
|
211
214
|
} else {
|
|
@@ -224,11 +227,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
224
227
|
}
|
|
225
228
|
|
|
226
229
|
if (bucketDirs.length === 0) {
|
|
227
|
-
console.log(chalk.yellow(
|
|
230
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.noBucketsInZip')}`));
|
|
228
231
|
return { success: false, buckets_count: 0 };
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
console.log(chalk.white(` -
|
|
234
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.bucketsFoundInZip', { count: bucketDirs.length })}`));
|
|
232
235
|
|
|
233
236
|
// 7. Criar cliente Supabase para o projeto destino
|
|
234
237
|
const supabase = createClient(targetProject.targetUrl, targetProject.targetServiceKey);
|
|
@@ -242,7 +245,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
242
245
|
const bucketPath = bucketInfo.path;
|
|
243
246
|
|
|
244
247
|
try {
|
|
245
|
-
console.log(chalk.white(`\n -
|
|
248
|
+
console.log(chalk.white(`\n - ${getT('restore.steps.storage.processingBucket', { bucketName })}`));
|
|
246
249
|
|
|
247
250
|
// 8.1 Obter metadados do bucket do backup (se disponíveis)
|
|
248
251
|
const bucketMeta = bucketMetadata[bucketName] || {
|
|
@@ -255,7 +258,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
255
258
|
const { data: existingBuckets, error: listError } = await supabase.storage.listBuckets();
|
|
256
259
|
|
|
257
260
|
if (listError) {
|
|
258
|
-
throw new Error(
|
|
261
|
+
throw new Error(getT('restore.steps.edgeFunctions.listBucketsError', { message: listError.message }));
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
const existingBucket = existingBuckets?.find(b => b.name === bucketName);
|
|
@@ -263,8 +266,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
263
266
|
|
|
264
267
|
if (!bucketExists) {
|
|
265
268
|
// Criar bucket via Management API com configurações do backup
|
|
266
|
-
console.log(chalk.white(` -
|
|
267
|
-
|
|
269
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.creatingBucket', { bucketName })}`));
|
|
270
|
+
const visibility = bucketMeta.public ? getT('restore.steps.storage.public') : getT('restore.steps.storage.private');
|
|
271
|
+
const limit = bucketMeta.file_size_limit || getT('restore.steps.storage.noLimit');
|
|
272
|
+
const types = bucketMeta.allowed_mime_types?.join(', ') || getT('restore.steps.storage.allTypes');
|
|
273
|
+
console.log(chalk.white(` ${getT('restore.steps.storage.bucketConfig', { visibility, limit, types })}`));
|
|
268
274
|
|
|
269
275
|
const createResponse = await fetch(
|
|
270
276
|
`https://api.supabase.com/v1/projects/${targetProject.targetProjectId}/storage/buckets`,
|
|
@@ -285,13 +291,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
285
291
|
|
|
286
292
|
if (!createResponse.ok) {
|
|
287
293
|
const errorText = await createResponse.text();
|
|
288
|
-
throw new Error(
|
|
294
|
+
throw new Error(getT('restore.steps.edgeFunctions.createBucketError', { status: createResponse.status, errorText }));
|
|
289
295
|
}
|
|
290
296
|
|
|
291
|
-
console.log(chalk.green(` ✅
|
|
297
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.storage.bucketCreated', { bucketName })}`));
|
|
292
298
|
} else {
|
|
293
299
|
// Bucket já existe - verificar e atualizar configurações se necessário
|
|
294
|
-
console.log(chalk.white(` -
|
|
300
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.bucketExists', { bucketName })}`));
|
|
295
301
|
|
|
296
302
|
const needsUpdate =
|
|
297
303
|
existingBucket.public !== bucketMeta.public ||
|
|
@@ -299,7 +305,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
299
305
|
JSON.stringify(existingBucket.allowed_mime_types || []) !== JSON.stringify(bucketMeta.allowed_mime_types || []);
|
|
300
306
|
|
|
301
307
|
if (needsUpdate) {
|
|
302
|
-
console.log(chalk.white(` -
|
|
308
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.updating')}`));
|
|
303
309
|
|
|
304
310
|
const updateResponse = await fetch(
|
|
305
311
|
`https://api.supabase.com/v1/projects/${targetProject.targetProjectId}/storage/buckets/${bucketName}`,
|
|
@@ -319,12 +325,12 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
319
325
|
|
|
320
326
|
if (!updateResponse.ok) {
|
|
321
327
|
const errorText = await updateResponse.text();
|
|
322
|
-
console.log(chalk.yellow(` ⚠️
|
|
328
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.updateError', { errorText })}`));
|
|
323
329
|
} else {
|
|
324
|
-
console.log(chalk.green(` ✅
|
|
330
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.storage.bucketUpdated')}`));
|
|
325
331
|
}
|
|
326
332
|
} else {
|
|
327
|
-
console.log(chalk.white(` -
|
|
333
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.bucketConfigCorrect')}`));
|
|
328
334
|
}
|
|
329
335
|
}
|
|
330
336
|
|
|
@@ -356,7 +362,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
356
362
|
}
|
|
357
363
|
|
|
358
364
|
const filesToUpload = await getAllFiles(bucketPath);
|
|
359
|
-
console.log(chalk.white(` -
|
|
365
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.filesFoundForUpload', { count: filesToUpload.length })}`));
|
|
360
366
|
|
|
361
367
|
// 8.4 Fazer upload de cada arquivo
|
|
362
368
|
// Criar mapa de metadados dos objetos do backup (se disponível)
|
|
@@ -427,25 +433,25 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
427
433
|
.upload(storagePath, fileContent, uploadOptions);
|
|
428
434
|
|
|
429
435
|
if (uploadError) {
|
|
430
|
-
console.log(chalk.yellow(` ⚠️
|
|
436
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.uploadError', { path: storagePath, message: uploadError.message })}`));
|
|
431
437
|
} else {
|
|
432
438
|
filesUploaded++;
|
|
433
439
|
if (filesToUpload.length <= 10 || filesUploaded % Math.ceil(filesToUpload.length / 10) === 0) {
|
|
434
|
-
const metaInfo = objectMeta ?
|
|
435
|
-
console.log(chalk.white(` -
|
|
440
|
+
const metaInfo = objectMeta ? ` ${getT('restore.steps.storage.metadataPreserved')}` : '';
|
|
441
|
+
console.log(chalk.white(` - ${getT('restore.steps.storage.uploadProgress', { path: storagePath, metaInfo })}`));
|
|
436
442
|
}
|
|
437
443
|
}
|
|
438
444
|
} catch (fileError) {
|
|
439
|
-
console.log(chalk.yellow(` ⚠️
|
|
445
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.storage.fileError', { path: file.storagePath, message: fileError.message })}`));
|
|
440
446
|
}
|
|
441
447
|
}
|
|
442
448
|
|
|
443
|
-
console.log(chalk.green(` ✅
|
|
449
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.storage.bucketDone', { bucketName, uploaded: filesUploaded, total: filesToUpload.length })}`));
|
|
444
450
|
successCount++;
|
|
445
451
|
totalFilesUploaded += filesUploaded;
|
|
446
452
|
|
|
447
453
|
} catch (bucketError) {
|
|
448
|
-
console.log(chalk.red(` ❌
|
|
454
|
+
console.log(chalk.red(` ❌ ${getT('restore.steps.storage.bucketError', { bucketName, message: bucketError.message })}`));
|
|
449
455
|
}
|
|
450
456
|
}
|
|
451
457
|
|
|
@@ -456,7 +462,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
456
462
|
// Ignorar erro de limpeza
|
|
457
463
|
}
|
|
458
464
|
|
|
459
|
-
console.log(chalk.green(`\n ✅
|
|
465
|
+
console.log(chalk.green(`\n ✅ ${getT('restore.steps.storage.restoreComplete', { success: successCount, total: bucketDirs.length, files: totalFilesUploaded })}`));
|
|
460
466
|
|
|
461
467
|
return {
|
|
462
468
|
success: true,
|
|
@@ -466,7 +472,8 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
466
472
|
};
|
|
467
473
|
|
|
468
474
|
} catch (error) {
|
|
469
|
-
|
|
475
|
+
const getT = global.smoonbI18n?.t || t;
|
|
476
|
+
console.error(chalk.red(` ❌ ${getT('restore.steps.storage.error', { message: error.message })}`));
|
|
470
477
|
throw error;
|
|
471
478
|
}
|
|
472
479
|
};
|
|
@@ -2,17 +2,19 @@ const chalk = require('chalk');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
|
+
const { t } = require('../../../i18n');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Etapa 7: Restaurar Database Settings (via SQL)
|
|
8
9
|
*/
|
|
9
10
|
module.exports = async ({ backupPath, targetProject }) => {
|
|
10
11
|
try {
|
|
12
|
+
const getT = global.smoonbI18n?.t || t;
|
|
11
13
|
const files = fs.readdirSync(backupPath);
|
|
12
14
|
const dbSettingsFile = files.find(f => f.startsWith('database-settings-') && f.endsWith('.json'));
|
|
13
15
|
|
|
14
16
|
if (!dbSettingsFile) {
|
|
15
|
-
console.log(chalk.yellow(
|
|
17
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.databaseSettings.notFound')}`));
|
|
16
18
|
return { success: false };
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -22,18 +24,18 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
22
24
|
const extensions = dbSettings.extensions || [];
|
|
23
25
|
|
|
24
26
|
if (extensions.length > 0) {
|
|
25
|
-
console.log(chalk.white(` -
|
|
27
|
+
console.log(chalk.white(` - ${getT('restore.steps.databaseSettings.enabling', { count: extensions.length })}`));
|
|
26
28
|
|
|
27
29
|
for (const ext of extensions) {
|
|
28
30
|
const extName = typeof ext === 'string' ? ext : ext.name;
|
|
29
|
-
console.log(chalk.white(`
|
|
31
|
+
console.log(chalk.white(` ${getT('restore.steps.databaseSettings.extension', { extName })}`));
|
|
30
32
|
|
|
31
33
|
const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${extName};`;
|
|
32
34
|
|
|
33
35
|
const urlMatch = targetProject.targetDatabaseUrl.match(/postgresql:\/\/([^@:]+):([^@]+)@(.+)$/);
|
|
34
36
|
|
|
35
37
|
if (!urlMatch) {
|
|
36
|
-
console.log(chalk.yellow(` ⚠️
|
|
38
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.databaseSettings.invalidUrl', { extName })}`));
|
|
37
39
|
continue;
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -49,17 +51,18 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
49
51
|
try {
|
|
50
52
|
execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
51
53
|
} catch {
|
|
52
|
-
console.log(chalk.yellow(` ⚠️ ${
|
|
54
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.databaseSettings.extensionExists', { extName })}`));
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
console.log(chalk.green(
|
|
59
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.databaseSettings.success')}`));
|
|
58
60
|
|
|
59
61
|
return { success: true, extensions_count: extensions.length };
|
|
60
62
|
|
|
61
63
|
} catch (error) {
|
|
62
|
-
|
|
64
|
+
const getT = global.smoonbI18n?.t || t;
|
|
65
|
+
console.error(chalk.red(` ❌ ${getT('restore.steps.databaseSettings.error', { message: error.message })}`));
|
|
63
66
|
throw error;
|
|
64
67
|
}
|
|
65
68
|
};
|
|
@@ -2,6 +2,7 @@ const chalk = require('chalk');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
+
const { t } = require('../../../i18n');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Etapa 8: Restaurar Realtime Settings (interativo - exibir URL e valores)
|
|
@@ -9,19 +10,20 @@ const inquirer = require('inquirer');
|
|
|
9
10
|
module.exports = async ({ backupPath, targetProject }) => {
|
|
10
11
|
|
|
11
12
|
try {
|
|
13
|
+
const getT = global.smoonbI18n?.t || t;
|
|
12
14
|
const realtimeSettingsPath = path.join(backupPath, 'realtime-settings.json');
|
|
13
15
|
|
|
14
16
|
if (!fs.existsSync(realtimeSettingsPath)) {
|
|
15
|
-
console.log(chalk.yellow(
|
|
17
|
+
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.realtime.notFound')}`));
|
|
16
18
|
return;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const realtimeSettings = JSON.parse(fs.readFileSync(realtimeSettingsPath, 'utf8'));
|
|
20
22
|
const dashboardUrl = `https://supabase.com/dashboard/project/${targetProject.targetProjectId}/realtime/settings`;
|
|
21
23
|
|
|
22
|
-
console.log(chalk.green(
|
|
24
|
+
console.log(chalk.green(`\n ✅ ${getT('restore.steps.realtime.urlTitle')}`));
|
|
23
25
|
console.log(chalk.cyan(` ${dashboardUrl}`));
|
|
24
|
-
console.log(chalk.yellow(
|
|
26
|
+
console.log(chalk.yellow(`\n 📋 ${getT('restore.steps.realtime.configureTitle')}`));
|
|
25
27
|
|
|
26
28
|
if (realtimeSettings.realtime_settings?.settings) {
|
|
27
29
|
Object.values(realtimeSettings.realtime_settings.settings).forEach((setting) => {
|
|
@@ -32,7 +34,7 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
console.log(chalk.yellow(
|
|
37
|
+
console.log(chalk.yellow(`\n ⚠️ ${getT('restore.steps.realtime.pressEnter')}`));
|
|
36
38
|
|
|
37
39
|
await inquirer.prompt([{
|
|
38
40
|
type: 'input',
|
|
@@ -40,10 +42,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
40
42
|
message: 'Pressione Enter para continuar'
|
|
41
43
|
}]);
|
|
42
44
|
|
|
43
|
-
console.log(chalk.green(
|
|
45
|
+
console.log(chalk.green(` ✅ ${getT('restore.steps.realtime.success')}`));
|
|
44
46
|
|
|
45
47
|
} catch (error) {
|
|
46
|
-
|
|
48
|
+
const getT = global.smoonbI18n?.t || t;
|
|
49
|
+
console.error(chalk.red(` ❌ ${getT('restore.steps.realtime.error', { message: error.message })}`));
|
|
47
50
|
}
|
|
48
51
|
};
|
|
49
52
|
|