smoonb 0.0.23 → 0.0.24

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/bin/smoonb.js CHANGED
@@ -70,6 +70,7 @@ program
70
70
  .command('backup')
71
71
  .description('Fazer backup completo do projeto Supabase usando Supabase CLI')
72
72
  .option('-o, --output <dir>', 'Diretório de saída do backup')
73
+ .option('--skip-realtime', 'Pular captura interativa de Realtime Settings')
73
74
  .action(commands.backup);
74
75
 
75
76
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -8,6 +8,7 @@ const { sha256 } = require('../utils/hash');
8
8
  const { readConfig, validateFor } = require('../utils/config');
9
9
  const { showBetaBanner } = require('../utils/banner');
10
10
  const { canPerformCompleteBackup, getDockerVersion } = require('../utils/docker');
11
+ const { captureRealtimeSettings } = require('../utils/realtime-settings');
11
12
 
12
13
  const execAsync = promisify(exec);
13
14
 
@@ -135,9 +136,9 @@ async function performFullBackup(config, options) {
135
136
  const rolesResult = await backupCustomRoles(config.supabase.databaseUrl, backupDir);
136
137
  manifest.components.custom_roles = rolesResult;
137
138
 
138
- // 6. Backup Realtime Settings via SQL
139
- console.log(chalk.blue('\n🔄 6/6 - Backup das Realtime Settings via SQL...'));
140
- const realtimeResult = await backupRealtimeSettings(config.supabase.databaseUrl, backupDir);
139
+ // 6. Backup Realtime Settings via Captura Interativa
140
+ console.log(chalk.blue('\n🔄 6/6 - Backup das Realtime Settings via Captura Interativa...'));
141
+ const realtimeResult = await backupRealtimeSettings(config.supabase.projectId, backupDir, options.skipRealtime);
141
142
  manifest.components.realtime = realtimeResult;
142
143
 
143
144
  // Salvar manifest
@@ -551,37 +552,21 @@ async function backupCustomRoles(databaseUrl, backupDir) {
551
552
  }
552
553
  }
553
554
 
554
- // Backup das Realtime Settings via Management API (não via SQL)
555
- async function backupRealtimeSettings(databaseUrl, backupDir) {
555
+ // Backup das Realtime Settings via Captura Interativa
556
+ async function backupRealtimeSettings(projectId, backupDir, skipInteractive = false) {
556
557
  try {
557
- console.log(chalk.gray(' - Exportando Realtime Settings via Management API...'));
558
+ console.log(chalk.gray(' - Capturando Realtime Settings interativamente...'));
558
559
 
559
- const realtimeFile = path.join(backupDir, 'realtime-settings.json');
560
+ const result = await captureRealtimeSettings(projectId, backupDir, skipInteractive);
560
561
 
561
- // Usar Management API para Realtime Settings
562
- // Nota: Supabase CLI não tem comando específico para Realtime
563
- // Vamos criar um arquivo placeholder com informações sobre Realtime
564
-
565
- const realtimeContent = {
566
- project_id: databaseUrl.split('@')[1]?.split('.')[0] || 'unknown',
567
- timestamp: new Date().toISOString(),
568
- note: 'Realtime settings are managed via Supabase Dashboard',
569
- message: 'Para configurar Realtime, acesse o Dashboard do Supabase',
570
- url: 'https://supabase.com/dashboard/project/[PROJECT_ID]/settings/api',
571
- documentation: 'https://supabase.com/docs/guides/realtime'
572
- };
573
-
574
- await writeJson(realtimeFile, realtimeContent);
575
-
576
- const stats = fs.statSync(realtimeFile);
562
+ const stats = fs.statSync(path.join(backupDir, 'realtime-settings.json'));
577
563
  const sizeKB = (stats.size / 1024).toFixed(1);
578
564
 
579
- console.log(chalk.green(` ✅ Realtime Settings documentados: ${sizeKB} KB`));
580
- console.log(chalk.gray(` ℹ️ Realtime é gerenciado via Dashboard do Supabase`));
565
+ console.log(chalk.green(` ✅ Realtime Settings capturadas: ${sizeKB} KB`));
581
566
 
582
- return { success: true };
567
+ return { success: true, settings: result };
583
568
  } catch (error) {
584
- console.log(chalk.yellow(` ⚠️ Erro ao documentar Realtime Settings: ${error.message}`));
569
+ console.log(chalk.yellow(` ⚠️ Erro ao capturar Realtime Settings: ${error.message}`));
585
570
  return { success: false };
586
571
  }
587
572
  }
@@ -0,0 +1,205 @@
1
+ const readline = require('readline');
2
+ const fs = require('fs').promises;
3
+ const path = require('path');
4
+
5
+ /**
6
+ * Captura configurações de Realtime Settings interativamente
7
+ * @param {string} projectId - ID do projeto Supabase
8
+ * @param {string} backupDir - Diretório do backup atual
9
+ * @param {boolean} skipInteractive - Se deve pular a etapa interativa
10
+ * @returns {Promise<Object>} Configurações capturadas
11
+ */
12
+ async function captureRealtimeSettings(projectId, backupDir, skipInteractive = false) {
13
+ const settingsFile = path.join(backupDir, 'realtime-settings.json');
14
+ const dashboardUrl = `https://supabase.com/dashboard/project/${projectId}/realtime/settings`;
15
+
16
+ // Tentar ler configurações de backup anterior
17
+ const previousSettings = await getPreviousRealtimeSettings(backupDir);
18
+
19
+ if (skipInteractive && previousSettings) {
20
+ console.log('📋 Usando configurações de Realtime Settings do backup anterior...');
21
+ await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
22
+ return previousSettings;
23
+ }
24
+
25
+ if (previousSettings && !skipInteractive) {
26
+ const shouldReuse = await askToReusePreviousSettings();
27
+ if (shouldReuse) {
28
+ console.log('📋 Reutilizando configurações de Realtime Settings do backup anterior...');
29
+ await fs.writeFile(settingsFile, JSON.stringify(previousSettings, null, 2));
30
+ return previousSettings;
31
+ }
32
+ }
33
+
34
+ // Capturar configurações interativamente
35
+ console.log('\n🔧 Configurações de Realtime Settings');
36
+ console.log('═'.repeat(50));
37
+ console.log(`📱 Acesse: ${dashboardUrl}`);
38
+ console.log('📝 Anote os valores dos 4 parâmetros abaixo:\n');
39
+
40
+ const settings = await captureSettingsInteractively(projectId, previousSettings);
41
+
42
+ // Salvar configurações
43
+ await fs.writeFile(settingsFile, JSON.stringify(settings, null, 2));
44
+ console.log('\n✅ Configurações de Realtime Settings salvas!');
45
+
46
+ return settings;
47
+ }
48
+
49
+ /**
50
+ * Busca configurações de backup anterior
51
+ * @param {string} backupDir - Diretório do backup atual
52
+ * @returns {Promise<Object|null>} Configurações anteriores ou null
53
+ */
54
+ async function getPreviousRealtimeSettings(backupDir) {
55
+ try {
56
+ // Buscar em backups anteriores
57
+ const backupsDir = path.dirname(backupDir);
58
+ const entries = await fs.readdir(backupsDir, { withFileTypes: true });
59
+ const backupDirs = entries
60
+ .filter(entry => entry.isDirectory() && entry.name.startsWith('backup-'))
61
+ .map(entry => entry.name)
62
+ .sort()
63
+ .reverse(); // Mais recente primeiro
64
+
65
+ for (const backupName of backupDirs) {
66
+ const settingsPath = path.join(backupsDir, backupName, 'realtime-settings.json');
67
+ try {
68
+ const content = await fs.readFile(settingsPath, 'utf8');
69
+ const settings = JSON.parse(content);
70
+ if (settings.realtime_settings && settings.realtime_settings.settings) {
71
+ return settings;
72
+ }
73
+ } catch (error) {
74
+ // Continuar para próximo backup
75
+ continue;
76
+ }
77
+ }
78
+
79
+ return null;
80
+ } catch (error) {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Pergunta se deve reutilizar configurações anteriores
87
+ * @returns {Promise<boolean>} true se deve reutilizar
88
+ */
89
+ async function askToReusePreviousSettings() {
90
+ const rl = readline.createInterface({
91
+ input: process.stdin,
92
+ output: process.stdout
93
+ });
94
+
95
+ return new Promise((resolve) => {
96
+ rl.question('🔄 Foi identificada uma gravação anterior de Realtime Settings.\n Deseja reutilizar as configurações anteriores? (S/n): ', (answer) => {
97
+ rl.close();
98
+ const shouldReuse = !answer.toLowerCase().startsWith('n');
99
+ resolve(shouldReuse);
100
+ });
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Captura configurações interativamente via perguntas
106
+ * @param {string} projectId - ID do projeto Supabase
107
+ * @param {Object} previousSettings - Configurações anteriores para usar como padrão
108
+ * @returns {Promise<Object>} Configurações capturadas
109
+ */
110
+ async function captureSettingsInteractively(projectId, previousSettings) {
111
+ const rl = readline.createInterface({
112
+ input: process.stdin,
113
+ output: process.stdout
114
+ });
115
+
116
+ const askQuestion = (question, defaultValue) => {
117
+ return new Promise((resolve) => {
118
+ rl.question(`${question} [${defaultValue}]: `, (answer) => {
119
+ resolve(answer.trim() || defaultValue);
120
+ });
121
+ });
122
+ };
123
+
124
+ try {
125
+ console.log('📋 Responda as perguntas abaixo (pressione Enter para usar o valor padrão):\n');
126
+
127
+ // Valores padrão baseados na imagem ou configurações anteriores
128
+ const defaults = previousSettings?.realtime_settings?.settings || {
129
+ allow_public_access: { value: true },
130
+ database_connection_pool_size: { value: 2 },
131
+ max_concurrent_clients: { value: 200 },
132
+ max_events_per_second: { value: 100 }
133
+ };
134
+
135
+ const allowPublicAccess = await askQuestion(
136
+ '1. Allow public access (true/false):',
137
+ defaults.allow_public_access.value
138
+ );
139
+
140
+ const poolSize = await askQuestion(
141
+ '2. Database connection pool size:',
142
+ defaults.database_connection_pool_size.value
143
+ );
144
+
145
+ const maxClients = await askQuestion(
146
+ '3. Max concurrent clients:',
147
+ defaults.max_concurrent_clients.value
148
+ );
149
+
150
+ const maxEvents = await askQuestion(
151
+ '4. Max events per second:',
152
+ defaults.max_events_per_second.value
153
+ );
154
+
155
+ const settings = {
156
+ realtime_settings: {
157
+ note: "Configurações de Realtime Settings capturadas interativamente",
158
+ dashboard_url: `https://supabase.com/dashboard/project/${projectId}/realtime/settings`,
159
+ captured_at: new Date().toISOString(),
160
+ settings: {
161
+ allow_public_access: {
162
+ label: "Allow public access",
163
+ description: "If disabled, only private channels will be allowed",
164
+ value: allowPublicAccess === 'true'
165
+ },
166
+ database_connection_pool_size: {
167
+ label: "Database connection pool size",
168
+ description: "Realtime Authorization uses this database pool to check client access",
169
+ value: parseInt(poolSize)
170
+ },
171
+ max_concurrent_clients: {
172
+ label: "Max concurrent clients",
173
+ description: "Sets maximum number of concurrent clients that can connect to your Realtime service",
174
+ value: parseInt(maxClients)
175
+ },
176
+ max_events_per_second: {
177
+ label: "Max events per second",
178
+ description: "Sets maximum number of events per second that can be sent to your Realtime service",
179
+ value: parseInt(maxEvents)
180
+ }
181
+ },
182
+ restore_instructions: {
183
+ url: `https://supabase.com/dashboard/project/${projectId}/realtime/settings`,
184
+ steps: [
185
+ "1. Acesse a URL acima",
186
+ "2. Configure 'Allow public access' conforme o valor em settings.allow_public_access.value",
187
+ "3. Configure 'Database connection pool size' conforme o valor em settings.database_connection_pool_size.value",
188
+ "4. Configure 'Max concurrent clients' conforme o valor em settings.max_concurrent_clients.value",
189
+ "5. Configure 'Max events per second' conforme o valor em settings.max_events_per_second.value",
190
+ "6. Clique em 'Save changes'"
191
+ ]
192
+ }
193
+ }
194
+ };
195
+
196
+ return settings;
197
+
198
+ } finally {
199
+ rl.close();
200
+ }
201
+ }
202
+
203
+ module.exports = {
204
+ captureRealtimeSettings
205
+ };