smoonb 0.0.16 → 0.0.18

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,43 +1,43 @@
1
- {
2
- "name": "smoonb",
3
- "version": "0.0.16",
4
- "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
- "main": "index.js",
6
- "bin": {
7
- "smoonb": "bin/smoonb.js"
8
- },
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1",
11
- "start": "node bin/smoonb.js"
12
- },
13
- "keywords": [
14
- "supabase",
15
- "backup",
16
- "migration",
17
- "postgresql",
18
- "edge-functions",
19
- "database",
20
- "cli",
21
- "tool"
22
- ],
23
- "author": "Goalmoon Tecnologia LTDA <https://goalmoon.com>",
24
- "license": "SEE LICENSE IN LICENSE.md",
25
- "engines": {
26
- "node": ">=16.0.0"
27
- },
28
- "dependencies": {
29
- "@supabase/supabase-js": "^2.38.0",
30
- "chalk": "^4.1.2",
31
- "commander": "^11.1.0",
32
- "inquirer": "^8.2.7"
33
- },
34
- "type": "commonjs",
35
- "repository": {
36
- "type": "git",
37
- "url": "git+https://github.com/almmello/smoonb.git"
38
- },
39
- "bugs": {
40
- "url": "https://github.com/almmello/smoonb/issues"
41
- },
42
- "homepage": "https://github.com/almmello/smoonb#readme"
43
- }
1
+ {
2
+ "name": "smoonb",
3
+ "version": "0.0.18",
4
+ "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "smoonb": "bin/smoonb.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "start": "node bin/smoonb.js"
12
+ },
13
+ "keywords": [
14
+ "supabase",
15
+ "backup",
16
+ "migration",
17
+ "postgresql",
18
+ "edge-functions",
19
+ "database",
20
+ "cli",
21
+ "tool"
22
+ ],
23
+ "author": "Goalmoon Tecnologia LTDA <https://goalmoon.com>",
24
+ "license": "SEE LICENSE IN LICENSE.md",
25
+ "engines": {
26
+ "node": ">=16.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@supabase/supabase-js": "^2.38.0",
30
+ "chalk": "^4.1.2",
31
+ "commander": "^11.1.0",
32
+ "inquirer": "^8.2.7"
33
+ },
34
+ "type": "commonjs",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/almmello/smoonb.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/almmello/smoonb/issues"
41
+ },
42
+ "homepage": "https://github.com/almmello/smoonb#readme"
43
+ }
@@ -6,6 +6,7 @@ const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
6
6
  const { sha256 } = require('../utils/hash');
7
7
  const { readConfig, validateFor } = require('../utils/config');
8
8
  const { showBetaBanner } = require('../utils/banner');
9
+ const { detectDockerDependencies } = require('../utils/docker');
9
10
  const { createClient } = require('@supabase/supabase-js');
10
11
 
11
12
  // Exportar FUNÇÃO em vez de objeto Command
@@ -98,7 +99,14 @@ module.exports = async (options) => {
98
99
  console.log(chalk.green('\n🎉 BACKUP COMPLETO FINALIZADO!'));
99
100
  console.log(chalk.blue(`📁 Localização: ${backupDir}`));
100
101
  console.log(chalk.green(`✅ Database: ${dbBackupResult.files.length} arquivos SQL gerados`));
101
- console.log(chalk.green(`✅ Edge Functions: ${edgeFunctionsResult.functions.length} functions baixadas`));
102
+ if (edgeFunctionsResult.success) {
103
+ console.log(chalk.green(`✅ Edge Functions: ${edgeFunctionsResult.successCount}/${edgeFunctionsResult.functionsCount} functions baixadas`));
104
+ } else {
105
+ console.log(chalk.yellow(`⚠️ Edge Functions: ${edgeFunctionsResult.reason === 'docker_not_installed' ? 'Docker não instalado' :
106
+ edgeFunctionsResult.reason === 'docker_not_running' ? 'Docker não está rodando' :
107
+ edgeFunctionsResult.reason === 'supabase_cli_not_found' ? 'Supabase CLI não encontrado' :
108
+ 'Erro no backup'}`));
109
+ }
102
110
  console.log(chalk.green(`✅ Auth Settings: ${authSettingsResult.success ? 'Exportadas' : 'Falharam'}`));
103
111
  console.log(chalk.green(`✅ Storage: ${storageResult.buckets.length} buckets verificados`));
104
112
  console.log(chalk.green(`✅ Custom Roles: ${customRolesResult.roles.length} roles exportados`));
@@ -109,8 +117,10 @@ module.exports = async (options) => {
109
117
  for (const file of dbBackupResult.files) {
110
118
  console.log(chalk.gray(` - ${file.filename}: ${file.sizeKB} KB`));
111
119
  }
112
- if (edgeFunctionsResult.functions.length > 0) {
113
- console.log(chalk.gray(` - Edge Functions: ${edgeFunctionsResult.functions.length} functions`));
120
+ if (edgeFunctionsResult.success && edgeFunctionsResult.functions.length > 0) {
121
+ console.log(chalk.gray(` - Edge Functions: ${edgeFunctionsResult.successCount}/${edgeFunctionsResult.functionsCount} functions`));
122
+ } else if (!edgeFunctionsResult.success) {
123
+ console.log(chalk.gray(` - Edge Functions: Pulado (${edgeFunctionsResult.reason})`));
114
124
  }
115
125
 
116
126
  } catch (error) {
@@ -253,26 +263,79 @@ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
253
263
  }
254
264
  }
255
265
 
256
- // Backup das Edge Functions via Supabase API
266
+ // Backup das Edge Functions com detecção inteligente do Docker
257
267
  async function backupEdgeFunctions(config, backupDir) {
258
268
  try {
269
+ console.log('🔍 Verificando dependências para backup de Edge Functions...');
270
+
271
+ // 1. Verificar se Docker está instalado e rodando
272
+ const dockerStatus = await detectDockerDependencies();
273
+
274
+ if (!dockerStatus.dockerInstalled) {
275
+ console.log('⚠️ DOCKER DESKTOP NÃO ENCONTRADO');
276
+ console.log('');
277
+ console.log('📋 Para fazer backup das Edge Functions, você precisa:');
278
+ console.log(' 1. Instalar Docker Desktop');
279
+ console.log(' 2. Executar Docker Desktop');
280
+ console.log(' 3. Repetir o comando de backup');
281
+ console.log('');
282
+ console.log('🔗 Download: https://docs.docker.com/desktop/install/');
283
+ console.log('');
284
+ console.log('⏭️ Pulando backup de Edge Functions...');
285
+ console.log('✅ Continuando com outros componentes do backup...');
286
+ return { success: false, reason: 'docker_not_installed', functions: [] };
287
+ }
288
+
289
+ if (!dockerStatus.dockerRunning) {
290
+ console.log('⚠️ DOCKER DESKTOP NÃO ESTÁ EXECUTANDO');
291
+ console.log('');
292
+ console.log('📋 Para fazer backup das Edge Functions, você precisa:');
293
+ console.log(' 1. Abrir Docker Desktop');
294
+ console.log(' 2. Aguardar inicialização completa');
295
+ console.log(' 3. Repetir o comando de backup');
296
+ console.log('');
297
+ console.log('💡 Dica: Docker Desktop deve estar rodando em segundo plano');
298
+ console.log('');
299
+ console.log('⏭️ Pulando backup de Edge Functions...');
300
+ console.log('✅ Continuando com outros componentes do backup...');
301
+ return { success: false, reason: 'docker_not_running', functions: [] };
302
+ }
303
+
304
+ if (!dockerStatus.supabaseCLI) {
305
+ console.log('⚠️ SUPABASE CLI NÃO ENCONTRADO');
306
+ console.log('');
307
+ console.log('📋 Para fazer backup das Edge Functions, você precisa:');
308
+ console.log(' 1. Instalar Supabase CLI');
309
+ console.log(' 2. Repetir o comando de backup');
310
+ console.log('');
311
+ console.log('🔗 Instalação: npm install -g supabase');
312
+ console.log('');
313
+ console.log('⏭️ Pulando backup de Edge Functions...');
314
+ console.log('✅ Continuando com outros componentes do backup...');
315
+ return { success: false, reason: 'supabase_cli_not_found', functions: [] };
316
+ }
317
+
318
+ // 3. Docker está OK, proceder com backup
319
+ console.log('✅ Docker Desktop detectado e funcionando');
320
+ console.log('✅ Supabase CLI detectado');
321
+ console.log('📥 Iniciando backup das Edge Functions...');
322
+
259
323
  const functionsDir = path.join(backupDir, 'edge-functions');
260
324
  await ensureDir(functionsDir);
261
325
 
262
326
  console.log(chalk.gray(' - Listando Edge Functions via Management API...'));
263
327
 
264
- // ✅ Usar fetch direto para Management API
328
+ // ✅ Usar fetch direto para Management API com Personal Access Token
265
329
  const functionsResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/functions`, {
266
330
  headers: {
267
- 'Authorization': `Bearer ${config.supabase.serviceKey}`,
268
- 'apikey': config.supabase.serviceKey,
331
+ 'Authorization': `Bearer ${config.supabase.accessToken}`,
269
332
  'Content-Type': 'application/json'
270
333
  }
271
334
  });
272
335
 
273
336
  if (!functionsResponse.ok) {
274
337
  console.log(chalk.yellow(` ⚠️ Erro ao listar Edge Functions: ${functionsResponse.status} ${functionsResponse.statusText}`));
275
- return { success: false, functions: [] };
338
+ return { success: false, reason: 'api_error', functions: [] };
276
339
  }
277
340
 
278
341
  const functions = await functionsResponse.json();
@@ -282,79 +345,68 @@ async function backupEdgeFunctions(config, backupDir) {
282
345
  await writeJson(path.join(functionsDir, 'README.md'), {
283
346
  message: 'Nenhuma Edge Function encontrada neste projeto'
284
347
  });
285
- return { success: true, functions: [] };
348
+ return { success: true, reason: 'no_functions', functions: [] };
286
349
  }
287
350
 
288
- console.log(chalk.gray(` - Encontradas ${functions.length} Edge Functions`));
351
+ console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
289
352
 
290
353
  const downloadedFunctions = [];
354
+ let successCount = 0;
355
+ let errorCount = 0;
291
356
 
292
- // ✅ Baixar todas as functions encontradas
357
+ // ✅ Baixar cada Edge Function usando Supabase CLI
293
358
  for (const func of functions) {
294
359
  try {
295
- console.log(chalk.gray(` - Baixando: ${func.name}`));
360
+ console.log(chalk.gray(` - Baixando: ${func.name}...`));
296
361
 
297
- // Baixar código da function via Management API
298
- const functionResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/functions/${func.name}`, {
299
- headers: {
300
- 'Authorization': `Bearer ${config.supabase.serviceKey}`,
301
- 'apikey': config.supabase.serviceKey,
302
- 'Content-Type': 'application/json'
303
- }
362
+ // Usar comando oficial do Supabase CLI
363
+ await runCommand(`supabase functions download ${func.name}`, {
364
+ cwd: process.cwd(),
365
+ timeout: 60000 // 60 segundos timeout
304
366
  });
305
367
 
306
- if (!functionResponse.ok) {
307
- console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${functionResponse.status} ${functionResponse.statusText}`));
308
- continue;
309
- }
310
-
311
- const functionData = await functionResponse.json();
312
-
313
- // ✅ Salvar arquivos dinamicamente
314
- const funcDir = path.join(functionsDir, func.name);
315
- await ensureDir(funcDir);
316
-
317
- // ✅ Salvar cada arquivo da function
318
- if (functionData && functionData.files) {
319
- for (const file of functionData.files) {
320
- const fileName = path.basename(file.name);
321
- const filePath = path.join(funcDir, fileName);
322
- await fs.promises.writeFile(filePath, file.content);
323
- }
324
- } else if (functionData && functionData.code) {
325
- // Fallback para estrutura simples
326
- const indexPath = path.join(funcDir, 'index.ts');
327
- await fs.promises.writeFile(indexPath, functionData.code);
368
+ // Mover arquivos baixados para o diretório de backup
369
+ const sourceDir = path.join(process.cwd(), 'supabase', 'functions', func.name);
370
+ const targetDir = path.join(functionsDir, func.name);
371
+
372
+ if (fs.existsSync(sourceDir)) {
373
+ await copyDir(sourceDir, targetDir);
374
+ console.log(chalk.green(` ✅ ${func.name} baixada com sucesso`));
375
+ successCount++;
328
376
 
329
- if (functionData.deno_config) {
330
- const denoPath = path.join(funcDir, 'deno.json');
331
- await writeJson(denoPath, functionData.deno_config);
332
- }
377
+ downloadedFunctions.push({
378
+ name: func.name,
379
+ slug: func.name,
380
+ version: func.version || 'unknown',
381
+ files: fs.existsSync(targetDir) ? fs.readdirSync(targetDir) : []
382
+ });
333
383
  } else {
334
- // Criar arquivo placeholder se não houver código
335
- const indexPath = path.join(funcDir, 'index.ts');
336
- await fs.promises.writeFile(indexPath, `// Edge Function: ${func.name}\n// Code not available via API\n`);
384
+ throw new Error('Diretório não encontrado após download');
337
385
  }
338
-
339
- downloadedFunctions.push({
340
- name: func.name,
341
- slug: func.name,
342
- version: func.version || 'unknown',
343
- files: fs.existsSync(funcDir) ? fs.readdirSync(funcDir) : []
344
- });
345
-
346
- console.log(chalk.green(` ✅ ${func.name} baixada`));
386
+
347
387
  } catch (error) {
348
388
  console.log(chalk.yellow(` ⚠️ Erro ao baixar ${func.name}: ${error.message}`));
389
+ errorCount++;
349
390
  }
350
391
  }
351
-
352
- console.log(chalk.green(`✅ Edge Functions backupadas: ${downloadedFunctions.length} functions`));
353
- return { success: true, functions: downloadedFunctions };
392
+
393
+ console.log(chalk.green(`📊 Backup de Edge Functions concluído:`));
394
+ console.log(chalk.green(` ✅ Sucessos: ${successCount}`));
395
+ console.log(chalk.green(` ❌ Erros: ${errorCount}`));
396
+
397
+ return {
398
+ success: true,
399
+ reason: 'success',
400
+ functions: downloadedFunctions,
401
+ functionsCount: functions.length,
402
+ successCount,
403
+ errorCount
404
+ };
354
405
 
355
406
  } catch (error) {
356
- console.log(chalk.yellow(` ⚠️ Erro no backup das Edge Functions: ${error.message}`));
357
- return { success: false, functions: [] };
407
+ console.log(chalk.yellow(` ⚠️ Erro durante backup de Edge Functions: ${error.message}`));
408
+ console.log('⏭️ Continuando com outros componentes...');
409
+ return { success: false, reason: 'download_error', error: error.message, functions: [] };
358
410
  }
359
411
  }
360
412
 
@@ -363,11 +415,10 @@ async function backupAuthSettings(config, backupDir) {
363
415
  try {
364
416
  console.log(chalk.gray(' - Exportando configurações de Auth via Management API...'));
365
417
 
366
- // ✅ Usar fetch direto para Management API
367
- const authResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/auth/settings`, {
418
+ // ✅ Usar fetch direto para Management API com Personal Access Token
419
+ const authResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/config/auth`, {
368
420
  headers: {
369
- 'Authorization': `Bearer ${config.supabase.serviceKey}`,
370
- 'apikey': config.supabase.serviceKey,
421
+ 'Authorization': `Bearer ${config.supabase.accessToken}`,
371
422
  'Content-Type': 'application/json'
372
423
  }
373
424
  });
@@ -404,11 +455,10 @@ async function backupStorage(config, backupDir) {
404
455
 
405
456
  console.log(chalk.gray(' - Listando buckets de Storage via Management API...'));
406
457
 
407
- // ✅ Usar fetch direto para Management API
458
+ // ✅ Usar fetch direto para Management API com Personal Access Token
408
459
  const storageResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/storage/buckets`, {
409
460
  headers: {
410
- 'Authorization': `Bearer ${config.supabase.serviceKey}`,
411
- 'apikey': config.supabase.serviceKey,
461
+ 'Authorization': `Bearer ${config.supabase.accessToken}`,
412
462
  'Content-Type': 'application/json'
413
463
  }
414
464
  });
@@ -434,11 +484,10 @@ async function backupStorage(config, backupDir) {
434
484
  try {
435
485
  console.log(chalk.gray(` - Processando bucket: ${bucket.name}`));
436
486
 
437
- // ✅ Listar objetos do bucket via Management API
487
+ // ✅ Listar objetos do bucket via Management API com Personal Access Token
438
488
  const objectsResponse = await fetch(`https://api.supabase.com/v1/projects/${config.supabase.projectId}/storage/buckets/${bucket.name}/objects`, {
439
489
  headers: {
440
- 'Authorization': `Bearer ${config.supabase.serviceKey}`,
441
- 'apikey': config.supabase.serviceKey,
490
+ 'Authorization': `Bearer ${config.supabase.accessToken}`,
442
491
  'Content-Type': 'application/json'
443
492
  }
444
493
  });
@@ -625,8 +674,12 @@ async function generateCompleteBackupManifest(config, backupDir, results) {
625
674
  },
626
675
  edge_functions: {
627
676
  success: results.edgeFunctions.success,
628
- functions_count: results.edgeFunctions.functions.length,
629
- functions: results.edgeFunctions.functions.map(f => f.name)
677
+ reason: results.edgeFunctions.reason || null,
678
+ functions_count: results.edgeFunctions.functionsCount || 0,
679
+ success_count: results.edgeFunctions.successCount || 0,
680
+ error_count: results.edgeFunctions.errorCount || 0,
681
+ functions: results.edgeFunctions.functions.map(f => f.name),
682
+ timestamp: new Date().toISOString()
630
683
  },
631
684
  auth_settings: {
632
685
  success: results.authSettings.success
@@ -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
  }
@@ -0,0 +1,71 @@
1
+ const { exec } = require('child_process');
2
+ const { promisify } = require('util');
3
+
4
+ const execAsync = promisify(exec);
5
+
6
+ /**
7
+ * Detecta se Docker Desktop está instalado no sistema
8
+ * @returns {Promise<{installed: boolean, running: boolean}>}
9
+ */
10
+ async function detectDockerInstallation() {
11
+ try {
12
+ // Tentar executar docker --version
13
+ await execAsync('docker --version');
14
+ return { installed: true, running: false }; // Instalado mas não sabemos se está rodando
15
+ } catch (error) {
16
+ return { installed: false, running: false };
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Verifica se Docker está rodando e acessível
22
+ * @returns {Promise<boolean>}
23
+ */
24
+ async function detectDockerRunning() {
25
+ try {
26
+ // Tentar executar docker ps
27
+ await execAsync('docker ps');
28
+ return true;
29
+ } catch (error) {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Verifica se Supabase CLI está disponível
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ async function detectSupabaseCLI() {
39
+ try {
40
+ // Tentar executar supabase --version
41
+ await execAsync('supabase --version');
42
+ return true;
43
+ } catch (error) {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Função principal para detectar todas as dependências do Docker
50
+ * @returns {Promise<{dockerInstalled: boolean, dockerRunning: boolean, supabaseCLI: boolean}>}
51
+ */
52
+ async function detectDockerDependencies() {
53
+ console.log('🔍 Verificando dependências para backup de Edge Functions...');
54
+
55
+ const dockerInstalled = await detectDockerInstallation();
56
+ const dockerRunning = dockerInstalled.installed ? await detectDockerRunning() : false;
57
+ const supabaseCLI = await detectSupabaseCLI();
58
+
59
+ return {
60
+ dockerInstalled: dockerInstalled.installed,
61
+ dockerRunning,
62
+ supabaseCLI
63
+ };
64
+ }
65
+
66
+ module.exports = {
67
+ detectDockerInstallation,
68
+ detectDockerRunning,
69
+ detectSupabaseCLI,
70
+ detectDockerDependencies
71
+ };