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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -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
- // ✅ Descobrir dinamicamente quantas functions existem
266
- const { data: functions, error } = await supabase.functions.list();
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 (error) {
269
- console.log(chalk.yellow(` ⚠️ Erro ao listar Edge Functions: ${error.message}`));
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 { data: functionData, error: functionError } = await supabase.functions.get(func.slug);
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 (functionError) {
294
- console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${functionError.message}`));
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 API antiga
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.slug,
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(`⚠️ Erro no backup das Edge Functions: ${error.message}`));
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 para obter configurações de Auth
347
- const authSettingsPath = path.join(backupDir, 'auth-settings.json');
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
- const authSettings = {
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
- // Configurações básicas que podemos obter
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
- await writeJson(authSettingsPath, authSettings);
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(` ⚠️ Erro ao exportar Auth Settings: ${error.message}`));
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
- // Listar buckets
383
- const { data: buckets, error } = await supabase.storage.listBuckets();
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 (error) {
386
- console.log(chalk.yellow(` ⚠️ Erro ao listar buckets: ${error.message}`));
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 processedBuckets = [];
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 { data: objects, error: objectsError } = await supabase.storage
398
- .from(bucket.name)
399
- .list('', { limit: 1000 });
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
- return { success: true, buckets: processedBuckets };
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: [] };
@@ -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": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
92
- "anonKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
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
@@ -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?.serviceKey) {
89
- errors.push('supabase.serviceKey é obrigatório');
88
+ if (!config.supabase?.accessToken) {
89
+ errors.push('supabase.accessToken é obrigatório para Management API');
90
90
  }
91
91
  break;
92
92
  }