smoonb 0.0.65 → 0.0.67
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
CHANGED
|
@@ -31,10 +31,24 @@ module.exports = async (backupPath) => {
|
|
|
31
31
|
|
|
32
32
|
// Storage Buckets
|
|
33
33
|
const storageDir = path.join(backupPath, 'storage');
|
|
34
|
-
|
|
34
|
+
let storageZipFiles = [];
|
|
35
|
+
|
|
36
|
+
// Verificar se o diretório existe e listar arquivos
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(backupPath)) {
|
|
39
|
+
const files = fs.readdirSync(backupPath);
|
|
40
|
+
storageZipFiles = files.filter(f => f.endsWith('.storage.zip'));
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Ignorar erro ao ler diretório
|
|
44
|
+
storageZipFiles = [];
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
let restoreStorage = false;
|
|
48
|
+
const hasStorageDir = fs.existsSync(storageDir);
|
|
49
|
+
const hasStorageFiles = hasStorageDir && fs.readdirSync(storageDir).length > 0;
|
|
36
50
|
|
|
37
|
-
if (storageZipFiles.length > 0 ||
|
|
51
|
+
if (storageZipFiles.length > 0 || hasStorageFiles) {
|
|
38
52
|
console.log(chalk.cyan('\n📦 Storage:'));
|
|
39
53
|
if (storageZipFiles.length > 0) {
|
|
40
54
|
console.log(chalk.white(` Arquivo .storage.zip encontrado: ${storageZipFiles[0]}`));
|
|
@@ -22,11 +22,15 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
22
22
|
const manifestPath = path.join(backupPath, 'backup-manifest.json');
|
|
23
23
|
const bucketMetadata = {};
|
|
24
24
|
let manifest = null;
|
|
25
|
+
let sourceProjectId = null;
|
|
25
26
|
|
|
26
27
|
try {
|
|
27
28
|
const manifestContent = await fs.readFile(manifestPath, 'utf8');
|
|
28
29
|
manifest = JSON.parse(manifestContent);
|
|
29
30
|
|
|
31
|
+
// Obter project ID do projeto origem do manifest
|
|
32
|
+
sourceProjectId = manifest?.project_id || null;
|
|
33
|
+
|
|
30
34
|
// Carregar metadados dos buckets do manifest
|
|
31
35
|
const buckets = manifest?.components?.storage?.buckets || [];
|
|
32
36
|
for (const bucket of buckets) {
|
|
@@ -107,6 +111,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
107
111
|
throw new Error('Credenciais do Supabase não configuradas. É necessário NEXT_PUBLIC_SUPABASE_URL e SUPABASE_SERVICE_ROLE_KEY');
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
// 4.1 Obter project ID do projeto origem e validar substituição
|
|
115
|
+
if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
|
|
116
|
+
console.log(chalk.cyan(` 🔄 Substituindo Project ID: ${sourceProjectId} → ${targetProject.targetProjectId}`));
|
|
117
|
+
} else if (!sourceProjectId) {
|
|
118
|
+
console.log(chalk.yellow(' ⚠️ Project ID do projeto origem não encontrado no manifest. Continuando sem substituição...'));
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
// 5. Extrair arquivo ZIP
|
|
111
122
|
console.log(chalk.white(' - Extraindo arquivo .storage.zip...'));
|
|
112
123
|
const extractDir = path.join(backupPath, 'storage_extracted');
|
|
@@ -121,20 +132,85 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
121
132
|
zip.extractAllTo(extractDir, true);
|
|
122
133
|
console.log(chalk.green(' ✅ Arquivo extraído com sucesso'));
|
|
123
134
|
|
|
135
|
+
// 5.1 Substituir Project ID antigo pelo novo nos diretórios e arquivos extraídos
|
|
136
|
+
// O Supabase pode incluir o project ID nos caminhos dentro do ZIP
|
|
137
|
+
if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
|
|
138
|
+
console.log(chalk.white(' - Substituindo referências ao Project ID antigo nos arquivos extraídos...'));
|
|
139
|
+
await replaceProjectIdInExtractedFiles(extractDir, sourceProjectId, targetProject.targetProjectId);
|
|
140
|
+
console.log(chalk.green(' ✅ Substituição de Project ID concluída'));
|
|
141
|
+
}
|
|
142
|
+
|
|
124
143
|
// 6. Ler estrutura de diretórios extraídos
|
|
125
|
-
// O formato do .storage.zip do Supabase
|
|
126
|
-
// bucket-name/
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// file2.png
|
|
144
|
+
// O formato do .storage.zip do Supabase pode ter duas estruturas:
|
|
145
|
+
// Estrutura 1 (direta): bucket-name/file1.jpg
|
|
146
|
+
// Estrutura 2 (com Project ID): project-id/bucket-name/file1.jpg
|
|
147
|
+
// Após a substituição do Project ID, a estrutura 2 fica: project-id-novo/bucket-name/file1.jpg
|
|
130
148
|
const extractedContents = await fs.readdir(extractDir);
|
|
131
149
|
const bucketDirs = [];
|
|
132
150
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
// Verificar se a pasta raiz é o Project ID do destino (após substituição)
|
|
152
|
+
// Se for, as subpastas são os buckets reais
|
|
153
|
+
let rootDir = null;
|
|
154
|
+
if (extractedContents.length === 1) {
|
|
155
|
+
const firstItem = extractedContents[0];
|
|
156
|
+
const firstItemPath = path.join(extractDir, firstItem);
|
|
157
|
+
const firstItemStats = await fs.stat(firstItemPath);
|
|
158
|
+
|
|
159
|
+
if (firstItemStats.isDirectory()) {
|
|
160
|
+
// Verificar se o nome da pasta raiz corresponde ao Project ID do destino
|
|
161
|
+
// Isso pode acontecer se a pasta raiz original era o Project ID antigo
|
|
162
|
+
// e foi renomeada para o Project ID novo pela função replaceProjectIdInExtractedFiles
|
|
163
|
+
if (firstItem === targetProject.targetProjectId) {
|
|
164
|
+
// Verificar se contém subpastas (buckets reais)
|
|
165
|
+
const subContents = await fs.readdir(firstItemPath);
|
|
166
|
+
const hasSubDirs = subContents.some(item => {
|
|
167
|
+
const itemPath = path.join(firstItemPath, item);
|
|
168
|
+
try {
|
|
169
|
+
const stats = fs.statSync(itemPath);
|
|
170
|
+
return stats.isDirectory();
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (hasSubDirs) {
|
|
177
|
+
// A pasta raiz é um wrapper do Project ID - buscar buckets nas subpastas
|
|
178
|
+
rootDir = firstItem;
|
|
179
|
+
console.log(chalk.white(` - Detectada pasta raiz com Project ID do destino: ${firstItem}`));
|
|
180
|
+
console.log(chalk.white(` - Buscando buckets nas subpastas...`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (rootDir) {
|
|
187
|
+
// Estrutura com Project ID: project-id/bucket-name/...
|
|
188
|
+
// Listar subpastas dentro da pasta do Project ID - essas são os buckets reais
|
|
189
|
+
const projectIdPath = path.join(extractDir, rootDir);
|
|
190
|
+
const subContents = await fs.readdir(projectIdPath);
|
|
191
|
+
|
|
192
|
+
for (const item of subContents) {
|
|
193
|
+
const itemPath = path.join(projectIdPath, item);
|
|
194
|
+
const stats = await fs.stat(itemPath);
|
|
195
|
+
if (stats.isDirectory()) {
|
|
196
|
+
bucketDirs.push({
|
|
197
|
+
name: item,
|
|
198
|
+
path: itemPath
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
// Estrutura direta: bucket-name/...
|
|
204
|
+
// As pastas raiz são os buckets
|
|
205
|
+
for (const item of extractedContents) {
|
|
206
|
+
const itemPath = path.join(extractDir, item);
|
|
207
|
+
const stats = await fs.stat(itemPath);
|
|
208
|
+
if (stats.isDirectory()) {
|
|
209
|
+
bucketDirs.push({
|
|
210
|
+
name: item,
|
|
211
|
+
path: itemPath
|
|
212
|
+
});
|
|
213
|
+
}
|
|
138
214
|
}
|
|
139
215
|
}
|
|
140
216
|
|
|
@@ -152,12 +228,13 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
152
228
|
let successCount = 0;
|
|
153
229
|
let totalFilesUploaded = 0;
|
|
154
230
|
|
|
155
|
-
for (const
|
|
231
|
+
for (const bucketInfo of bucketDirs) {
|
|
232
|
+
const bucketName = bucketInfo.name;
|
|
233
|
+
const bucketPath = bucketInfo.path;
|
|
234
|
+
|
|
156
235
|
try {
|
|
157
236
|
console.log(chalk.white(`\n - Processando bucket: ${bucketName}`));
|
|
158
237
|
|
|
159
|
-
const bucketPath = path.join(extractDir, bucketName);
|
|
160
|
-
|
|
161
238
|
// 8.1 Obter metadados do bucket do backup (se disponíveis)
|
|
162
239
|
const bucketMeta = bucketMetadata[bucketName] || {
|
|
163
240
|
public: false,
|
|
@@ -249,7 +326,11 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
249
326
|
|
|
250
327
|
for (const entry of entries) {
|
|
251
328
|
const fullPath = path.join(dir, entry.name);
|
|
252
|
-
|
|
329
|
+
// Substituir project ID antigo pelo novo no caminho relativo
|
|
330
|
+
let relativePath = path.join(basePath, entry.name).replace(/\\/g, '/');
|
|
331
|
+
if (sourceProjectId && sourceProjectId !== targetProject.targetProjectId) {
|
|
332
|
+
relativePath = relativePath.replace(new RegExp(sourceProjectId, 'g'), targetProject.targetProjectId);
|
|
333
|
+
}
|
|
253
334
|
|
|
254
335
|
if (entry.isDirectory()) {
|
|
255
336
|
const subFiles = await getAllFiles(fullPath, relativePath);
|
|
@@ -381,6 +462,75 @@ module.exports = async ({ backupPath, targetProject }) => {
|
|
|
381
462
|
}
|
|
382
463
|
};
|
|
383
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Substitui o Project ID antigo pelo novo em arquivos extraídos
|
|
467
|
+
* Processa recursivamente todos os arquivos e diretórios
|
|
468
|
+
* IMPORTANTE: Processa primeiro os filhos, depois renomeia o diretório atual
|
|
469
|
+
* para evitar problemas com caminhos que mudam durante o processamento
|
|
470
|
+
*/
|
|
471
|
+
async function replaceProjectIdInExtractedFiles(dir, oldProjectId, newProjectId) {
|
|
472
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
473
|
+
|
|
474
|
+
// Primeiro, processar recursivamente todos os filhos (arquivos e subdiretórios)
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
const entryPath = path.join(dir, entry.name);
|
|
477
|
+
|
|
478
|
+
if (entry.isDirectory()) {
|
|
479
|
+
// Processar recursivamente o subdiretório ANTES de renomeá-lo
|
|
480
|
+
await replaceProjectIdInExtractedFiles(entryPath, oldProjectId, newProjectId);
|
|
481
|
+
} else {
|
|
482
|
+
// Processar arquivos: substituir project ID no conteúdo (se for texto) e no nome
|
|
483
|
+
// Renomear arquivo se contiver o project ID antigo
|
|
484
|
+
if (entry.name.includes(oldProjectId)) {
|
|
485
|
+
const newName = entry.name.replace(new RegExp(oldProjectId, 'g'), newProjectId);
|
|
486
|
+
if (newName !== entry.name) {
|
|
487
|
+
const newPath = path.join(dir, newName);
|
|
488
|
+
try {
|
|
489
|
+
await fs.rename(entryPath, newPath);
|
|
490
|
+
} catch {
|
|
491
|
+
// Ignorar erros de renomeação (pode já ter sido renomeado)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Substituir project ID no conteúdo de arquivos de texto
|
|
497
|
+
// Verificar extensões comuns de arquivos de texto
|
|
498
|
+
const textExtensions = ['.json', '.txt', '.md', '.html', '.css', '.js', '.xml', '.yaml', '.yml'];
|
|
499
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
500
|
+
|
|
501
|
+
if (textExtensions.includes(ext)) {
|
|
502
|
+
try {
|
|
503
|
+
const content = await fs.readFile(entryPath, 'utf8');
|
|
504
|
+
if (content.includes(oldProjectId)) {
|
|
505
|
+
const newContent = content.replace(new RegExp(oldProjectId, 'g'), newProjectId);
|
|
506
|
+
await fs.writeFile(entryPath, newContent, 'utf8');
|
|
507
|
+
}
|
|
508
|
+
} catch {
|
|
509
|
+
// Ignorar erros ao processar arquivos (pode ser binário ou sem permissão)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Depois de processar todos os filhos, renomear o diretório atual se necessário
|
|
516
|
+
// Ler novamente os diretórios para pegar os nomes atualizados
|
|
517
|
+
const updatedEntries = await fs.readdir(dir, { withFileTypes: true });
|
|
518
|
+
for (const entry of updatedEntries) {
|
|
519
|
+
if (entry.isDirectory() && entry.name.includes(oldProjectId)) {
|
|
520
|
+
const entryPath = path.join(dir, entry.name);
|
|
521
|
+
const newName = entry.name.replace(new RegExp(oldProjectId, 'g'), newProjectId);
|
|
522
|
+
if (newName !== entry.name) {
|
|
523
|
+
const newPath = path.join(dir, newName);
|
|
524
|
+
try {
|
|
525
|
+
await fs.rename(entryPath, newPath);
|
|
526
|
+
} catch {
|
|
527
|
+
// Ignorar erros de renomeação (pode já ter sido renomeado)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
384
534
|
/**
|
|
385
535
|
* Determina o content-type baseado na extensão do arquivo
|
|
386
536
|
*/
|