smoonb 0.0.15 → 0.0.17
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.js +88 -43
- package/src/commands/config.js +8 -1
- package/src/index.js +11 -3
- package/src/utils/config.js +2 -2
package/package.json
CHANGED
package/src/commands/backup.js
CHANGED
|
@@ -256,20 +256,26 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
|
|
|
256
256
|
// Backup das Edge Functions via Supabase API
|
|
257
257
|
async function backupEdgeFunctions(config, backupDir) {
|
|
258
258
|
try {
|
|
259
|
-
const supabase = createClient(config.supabase.url, config.supabase.serviceKey);
|
|
260
259
|
const functionsDir = path.join(backupDir, 'edge-functions');
|
|
261
260
|
await ensureDir(functionsDir);
|
|
262
261
|
|
|
263
|
-
console.log(chalk.gray(' - Listando Edge Functions...'));
|
|
262
|
+
console.log(chalk.gray(' - Listando Edge Functions via Management API...'));
|
|
264
263
|
|
|
265
|
-
// ✅
|
|
266
|
-
const
|
|
264
|
+
// ✅ Usar fetch direto para Management API com Personal Access Token
|
|
265
|
+
const functionsResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/functions`, {
|
|
266
|
+
headers: {
|
|
267
|
+
'Authorization': `Bearer ${config.supabase.accessToken}`,
|
|
268
|
+
'Content-Type': 'application/json'
|
|
269
|
+
}
|
|
270
|
+
});
|
|
267
271
|
|
|
268
|
-
if (
|
|
269
|
-
console.log(chalk.yellow(` ⚠️ Erro ao listar Edge Functions: ${
|
|
272
|
+
if (!functionsResponse.ok) {
|
|
273
|
+
console.log(chalk.yellow(` ⚠️ Erro ao listar Edge Functions: ${functionsResponse.status} ${functionsResponse.statusText}`));
|
|
270
274
|
return { success: false, functions: [] };
|
|
271
275
|
}
|
|
272
276
|
|
|
277
|
+
const functions = await functionsResponse.json();
|
|
278
|
+
|
|
273
279
|
if (!functions || functions.length === 0) {
|
|
274
280
|
console.log(chalk.gray(' - Nenhuma Edge Function encontrada'));
|
|
275
281
|
await writeJson(path.join(functionsDir, 'README.md'), {
|
|
@@ -287,14 +293,21 @@ async function backupEdgeFunctions(config, backupDir) {
|
|
|
287
293
|
try {
|
|
288
294
|
console.log(chalk.gray(` - Baixando: ${func.name}`));
|
|
289
295
|
|
|
290
|
-
// ✅ Baixar código da function
|
|
291
|
-
const
|
|
296
|
+
// ✅ Baixar código da function via Management API com Personal Access Token
|
|
297
|
+
const functionResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/functions/${func.name}`, {
|
|
298
|
+
headers: {
|
|
299
|
+
'Authorization': `Bearer ${config.supabase.accessToken}`,
|
|
300
|
+
'Content-Type': 'application/json'
|
|
301
|
+
}
|
|
302
|
+
});
|
|
292
303
|
|
|
293
|
-
if (
|
|
294
|
-
console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${
|
|
304
|
+
if (!functionResponse.ok) {
|
|
305
|
+
console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${functionResponse.status} ${functionResponse.statusText}`));
|
|
295
306
|
continue;
|
|
296
307
|
}
|
|
297
308
|
|
|
309
|
+
const functionData = await functionResponse.json();
|
|
310
|
+
|
|
298
311
|
// ✅ Salvar arquivos dinamicamente
|
|
299
312
|
const funcDir = path.join(functionsDir, func.name);
|
|
300
313
|
await ensureDir(funcDir);
|
|
@@ -307,7 +320,7 @@ async function backupEdgeFunctions(config, backupDir) {
|
|
|
307
320
|
await fs.promises.writeFile(filePath, file.content);
|
|
308
321
|
}
|
|
309
322
|
} else if (functionData && functionData.code) {
|
|
310
|
-
// Fallback para
|
|
323
|
+
// Fallback para estrutura simples
|
|
311
324
|
const indexPath = path.join(funcDir, 'index.ts');
|
|
312
325
|
await fs.promises.writeFile(indexPath, functionData.code);
|
|
313
326
|
|
|
@@ -315,11 +328,15 @@ async function backupEdgeFunctions(config, backupDir) {
|
|
|
315
328
|
const denoPath = path.join(funcDir, 'deno.json');
|
|
316
329
|
await writeJson(denoPath, functionData.deno_config);
|
|
317
330
|
}
|
|
331
|
+
} else {
|
|
332
|
+
// Criar arquivo placeholder se não houver código
|
|
333
|
+
const indexPath = path.join(funcDir, 'index.ts');
|
|
334
|
+
await fs.promises.writeFile(indexPath, `// Edge Function: ${func.name}\n// Code not available via API\n`);
|
|
318
335
|
}
|
|
319
336
|
|
|
320
337
|
downloadedFunctions.push({
|
|
321
338
|
name: func.name,
|
|
322
|
-
slug: func.
|
|
339
|
+
slug: func.name,
|
|
323
340
|
version: func.version || 'unknown',
|
|
324
341
|
files: fs.existsSync(funcDir) ? fs.readdirSync(funcDir) : []
|
|
325
342
|
});
|
|
@@ -332,8 +349,9 @@ async function backupEdgeFunctions(config, backupDir) {
|
|
|
332
349
|
|
|
333
350
|
console.log(chalk.green(`✅ Edge Functions backupadas: ${downloadedFunctions.length} functions`));
|
|
334
351
|
return { success: true, functions: downloadedFunctions };
|
|
352
|
+
|
|
335
353
|
} catch (error) {
|
|
336
|
-
console.log(chalk.yellow(
|
|
354
|
+
console.log(chalk.yellow(` ⚠️ Erro no backup das Edge Functions: ${error.message}`));
|
|
337
355
|
return { success: false, functions: [] };
|
|
338
356
|
}
|
|
339
357
|
}
|
|
@@ -341,31 +359,36 @@ async function backupEdgeFunctions(config, backupDir) {
|
|
|
341
359
|
// Backup das Auth Settings via Management API
|
|
342
360
|
async function backupAuthSettings(config, backupDir) {
|
|
343
361
|
try {
|
|
344
|
-
console.log(chalk.gray(' - Exportando configurações de Auth...'));
|
|
362
|
+
console.log(chalk.gray(' - Exportando configurações de Auth via Management API...'));
|
|
345
363
|
|
|
346
|
-
// Usar Management API
|
|
347
|
-
const
|
|
364
|
+
// ✅ Usar fetch direto para Management API com Personal Access Token
|
|
365
|
+
const authResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/config/auth`, {
|
|
366
|
+
headers: {
|
|
367
|
+
'Authorization': `Bearer ${config.supabase.accessToken}`,
|
|
368
|
+
'Content-Type': 'application/json'
|
|
369
|
+
}
|
|
370
|
+
});
|
|
348
371
|
|
|
349
|
-
|
|
372
|
+
if (!authResponse.ok) {
|
|
373
|
+
console.log(chalk.yellow(` ⚠️ Erro ao obter Auth Settings: ${authResponse.status} ${authResponse.statusText}`));
|
|
374
|
+
return { success: false };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const authSettings = await authResponse.json();
|
|
378
|
+
|
|
379
|
+
// Salvar configurações de Auth
|
|
380
|
+
const authSettingsPath = path.join(backupDir, 'auth-settings.json');
|
|
381
|
+
await writeJson(authSettingsPath, {
|
|
350
382
|
project_id: config.supabase.projectId,
|
|
351
383
|
timestamp: new Date().toISOString(),
|
|
352
|
-
settings:
|
|
353
|
-
|
|
354
|
-
site_url: config.supabase.url,
|
|
355
|
-
jwt_secret: 'REDACTED', // Não expor secret
|
|
356
|
-
smtp_settings: null,
|
|
357
|
-
rate_limits: null,
|
|
358
|
-
email_templates: null
|
|
359
|
-
},
|
|
360
|
-
note: 'Configurações completas requerem acesso ao Management API'
|
|
361
|
-
};
|
|
384
|
+
settings: authSettings
|
|
385
|
+
});
|
|
362
386
|
|
|
363
|
-
|
|
364
|
-
console.log(chalk.green(' ✅ Auth Settings exportadas'));
|
|
365
|
-
|
|
387
|
+
console.log(chalk.green(`✅ Auth Settings exportadas: ${path.basename(authSettingsPath)}`));
|
|
366
388
|
return { success: true };
|
|
389
|
+
|
|
367
390
|
} catch (error) {
|
|
368
|
-
console.log(chalk.yellow(`
|
|
391
|
+
console.log(chalk.yellow(` ⚠️ Erro no backup das Auth Settings: ${error.message}`));
|
|
369
392
|
return { success: false };
|
|
370
393
|
}
|
|
371
394
|
}
|
|
@@ -373,30 +396,52 @@ async function backupAuthSettings(config, backupDir) {
|
|
|
373
396
|
// Backup do Storage via Supabase API
|
|
374
397
|
async function backupStorage(config, backupDir) {
|
|
375
398
|
try {
|
|
376
|
-
const supabase = createClient(config.supabase.url, config.supabase.serviceKey);
|
|
377
399
|
const storageDir = path.join(backupDir, 'storage');
|
|
378
400
|
await ensureDir(storageDir);
|
|
379
401
|
|
|
380
|
-
console.log(chalk.gray(' - Listando buckets de Storage...'));
|
|
402
|
+
console.log(chalk.gray(' - Listando buckets de Storage via Management API...'));
|
|
381
403
|
|
|
382
|
-
//
|
|
383
|
-
const
|
|
404
|
+
// ✅ Usar fetch direto para Management API com Personal Access Token
|
|
405
|
+
const storageResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/storage/buckets`, {
|
|
406
|
+
headers: {
|
|
407
|
+
'Authorization': `Bearer ${config.supabase.accessToken}`,
|
|
408
|
+
'Content-Type': 'application/json'
|
|
409
|
+
}
|
|
410
|
+
});
|
|
384
411
|
|
|
385
|
-
if (
|
|
386
|
-
console.log(chalk.yellow(` ⚠️ Erro ao listar buckets: ${
|
|
412
|
+
if (!storageResponse.ok) {
|
|
413
|
+
console.log(chalk.yellow(` ⚠️ Erro ao listar buckets: ${storageResponse.status} ${storageResponse.statusText}`));
|
|
387
414
|
return { success: false, buckets: [] };
|
|
388
415
|
}
|
|
389
416
|
|
|
390
|
-
const
|
|
417
|
+
const buckets = await storageResponse.json();
|
|
418
|
+
|
|
419
|
+
if (!buckets || buckets.length === 0) {
|
|
420
|
+
console.log(chalk.gray(' - Nenhum bucket encontrado'));
|
|
421
|
+
await writeJson(path.join(storageDir, 'README.md'), {
|
|
422
|
+
message: 'Nenhum bucket de Storage encontrado neste projeto'
|
|
423
|
+
});
|
|
424
|
+
return { success: true, buckets: [] };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
console.log(chalk.gray(` - Encontrados ${buckets.length} buckets`));
|
|
391
428
|
|
|
392
429
|
for (const bucket of buckets || []) {
|
|
393
430
|
try {
|
|
394
431
|
console.log(chalk.gray(` - Processando bucket: ${bucket.name}`));
|
|
395
432
|
|
|
396
|
-
// Listar objetos do bucket
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
433
|
+
// ✅ Listar objetos do bucket via Management API com Personal Access Token
|
|
434
|
+
const objectsResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/storage/buckets/${bucket.name}/objects`, {
|
|
435
|
+
headers: {
|
|
436
|
+
'Authorization': `Bearer ${config.supabase.accessToken}`,
|
|
437
|
+
'Content-Type': 'application/json'
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
let objects = [];
|
|
442
|
+
if (objectsResponse.ok) {
|
|
443
|
+
objects = await objectsResponse.json();
|
|
444
|
+
}
|
|
400
445
|
|
|
401
446
|
const bucketInfo = {
|
|
402
447
|
id: bucket.id,
|
|
@@ -422,7 +467,7 @@ async function backupStorage(config, backupDir) {
|
|
|
422
467
|
}
|
|
423
468
|
}
|
|
424
469
|
|
|
425
|
-
|
|
470
|
+
console.log(chalk.green(`✅ Storage backupado: ${processedBuckets.length} buckets`));
|
|
426
471
|
} catch (error) {
|
|
427
472
|
console.log(chalk.yellow(`⚠️ Erro no backup do Storage: ${error.message}`));
|
|
428
473
|
return { success: false, buckets: [] };
|
package/src/commands/config.js
CHANGED
|
@@ -39,7 +39,8 @@ async function initializeConfig(configPath) {
|
|
|
39
39
|
url: 'https://your-project-id.supabase.co',
|
|
40
40
|
serviceKey: 'your-service-key-here',
|
|
41
41
|
anonKey: 'your-anon-key-here',
|
|
42
|
-
databaseUrl: 'postgresql://postgres:[password]@db.your-project-id.supabase.co:5432/postgres'
|
|
42
|
+
databaseUrl: 'postgresql://postgres:[password]@db.your-project-id.supabase.co:5432/postgres',
|
|
43
|
+
accessToken: 'your-personal-access-token-here'
|
|
43
44
|
},
|
|
44
45
|
backup: {
|
|
45
46
|
includeFunctions: true,
|
|
@@ -106,6 +107,12 @@ async function showConfig(configPath) {
|
|
|
106
107
|
} else {
|
|
107
108
|
console.log(chalk.yellow(' - Database URL: Não configurada'));
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
if (config.supabase?.accessToken && config.supabase.accessToken !== 'your-personal-access-token-here') {
|
|
112
|
+
console.log(chalk.gray(' - Access Token: Configurado'));
|
|
113
|
+
} else {
|
|
114
|
+
console.log(chalk.yellow(' - Access Token: Não configurado (obrigatório para Management API)'));
|
|
115
|
+
}
|
|
109
116
|
|
|
110
117
|
console.log(chalk.blue('\n📊 Configurações de backup:'));
|
|
111
118
|
console.log(chalk.gray(` - Output Dir: ${config.backup?.outputDir || './backups'}`));
|
package/src/index.js
CHANGED
|
@@ -88,12 +88,20 @@ function showQuickHelp() {
|
|
|
88
88
|
"supabase": {
|
|
89
89
|
"projectId": "abc123def456",
|
|
90
90
|
"url": "https://abc123def456.supabase.co",
|
|
91
|
-
"serviceKey": "
|
|
92
|
-
"anonKey": "
|
|
93
|
-
"databaseUrl": "postgresql://postgres:[senha]@db.abc123def456.supabase.co:5432/postgres"
|
|
91
|
+
"serviceKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkXVCJ9...",
|
|
92
|
+
"anonKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkXVCJ9...",
|
|
93
|
+
"databaseUrl": "postgresql://postgres:[senha]@db.abc123def456.supabase.co:5432/postgres",
|
|
94
|
+
"accessToken": "sbp_1234567890abcdef1234567890abcdef"
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
🔑 PERSONAL ACCESS TOKEN (OBRIGATÓRIO):
|
|
99
|
+
Para Management API (Edge Functions, Auth Settings, Storage):
|
|
100
|
+
1. Acesse: https://supabase.com/dashboard/account/tokens
|
|
101
|
+
2. Clique em "Generate new token"
|
|
102
|
+
3. Copie o token (formato: sbp_...)
|
|
103
|
+
4. Adicione ao .smoonbrc como "accessToken"
|
|
104
|
+
|
|
97
105
|
🔧 COMO CONFIGURAR:
|
|
98
106
|
1. npx smoonb config --init
|
|
99
107
|
2. Edite .smoonbrc com suas credenciais
|
package/src/utils/config.js
CHANGED
|
@@ -85,8 +85,8 @@ function validateFor(config, action) {
|
|
|
85
85
|
if (!config.supabase?.url) {
|
|
86
86
|
errors.push('supabase.url é obrigatório');
|
|
87
87
|
}
|
|
88
|
-
if (!config.supabase?.
|
|
89
|
-
errors.push('supabase.
|
|
88
|
+
if (!config.supabase?.accessToken) {
|
|
89
|
+
errors.push('supabase.accessToken é obrigatório para Management API');
|
|
90
90
|
}
|
|
91
91
|
break;
|
|
92
92
|
}
|