smoonb 0.0.1

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.
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Comando de restauração completa do projeto Supabase
3
+ * Implementação técnica real com processo DROP→CREATE→RESTORE
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const { execSync } = require('child_process');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Restauração completa do projeto Supabase
13
+ * Processo seguro: DROP → CREATE → RESTORE (nunca restore sobre banco existente)
14
+ */
15
+ async function restoreCommand(options) {
16
+ console.log(chalk.red.bold('🚀 smoonb v0.0.1 - EXPERIMENTAL VERSION'));
17
+ console.log(chalk.red.bold('⚠️ VERSÃO EXPERIMENTAL - NUNCA TESTADA EM PRODUÇÃO!'));
18
+ console.log(chalk.red.bold('🚨 USE POR SUA CONTA E RISCO - Pode causar perda de dados!'));
19
+ console.log(chalk.red.bold('❌ NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados!\n'));
20
+
21
+ console.log(chalk.cyan.bold('🔄 Iniciando restauração COMPLETA do projeto Supabase...\n'));
22
+
23
+ try {
24
+ // Validar opções
25
+ if (!options.projectId) {
26
+ console.error(chalk.red.bold('❌ Erro: Project ID é obrigatório'));
27
+ console.log(chalk.yellow('💡 Use: smoonb restore --project-id <seu-project-id> --backup-dir <diretorio-backup>'));
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!options.backupDir) {
32
+ console.error(chalk.red.bold('❌ Erro: Diretório de backup é obrigatório'));
33
+ console.log(chalk.yellow('💡 Use: smoonb restore --project-id <seu-project-id> --backup-dir <diretorio-backup>'));
34
+ process.exit(1);
35
+ }
36
+
37
+ // Verificar se o diretório de backup existe
38
+ const backupPath = path.resolve(options.backupDir);
39
+ try {
40
+ await fs.promises.access(backupPath);
41
+ } catch (error) {
42
+ console.error(chalk.red.bold('❌ Erro: Diretório de backup não encontrado:'), backupPath);
43
+ process.exit(1);
44
+ }
45
+
46
+ // Ler manifesto do backup
47
+ const manifestPath = path.join(backupPath, 'backup-manifest.json');
48
+ let manifest = null;
49
+ try {
50
+ const manifestContent = await fs.promises.readFile(manifestPath, 'utf8');
51
+ manifest = JSON.parse(manifestContent);
52
+ console.log(chalk.green('✅ Manifesto do backup encontrado'));
53
+ console.log(chalk.gray(' - Projeto origem:'), manifest.projectId);
54
+ console.log(chalk.gray(' - Data do backup:'), manifest.timestamp);
55
+ } catch (error) {
56
+ console.log(chalk.yellow('⚠️ Manifesto do backup não encontrado, continuando...'));
57
+ }
58
+
59
+ console.log(chalk.green('✅ Diretório de backup encontrado:'), backupPath);
60
+
61
+ // 1. RESTAURAÇÃO DA DATABASE (processo seguro)
62
+ if (manifest?.files?.database) {
63
+ console.log(chalk.blue.bold('\n📊 1/5 - Restauração da Database PostgreSQL...'));
64
+ const dbBackupFile = path.join(backupPath, manifest.files.database);
65
+ const dbResult = await restoreDatabase(dbBackupFile, options.projectId);
66
+
67
+ if (dbResult.success) {
68
+ console.log(chalk.green('✅ Database restaurada com sucesso!'));
69
+ } else {
70
+ console.log(chalk.yellow('⚠️ Restauração da database falhou:'), dbResult.error);
71
+ }
72
+ }
73
+
74
+ // 2. RESTAURAÇÃO DAS EDGE FUNCTIONS
75
+ if (manifest?.files?.functions) {
76
+ console.log(chalk.blue.bold('\n⚡ 2/5 - Restauração das Edge Functions...'));
77
+ const functionsDir = path.join(backupPath, 'functions');
78
+ const functionsResult = await restoreEdgeFunctions(functionsDir, options.projectId);
79
+
80
+ if (functionsResult.success) {
81
+ console.log(chalk.green('✅ Edge Functions restauradas com sucesso!'));
82
+ } else {
83
+ console.log(chalk.yellow('⚠️ Restauração das Edge Functions falhou:'), functionsResult.error);
84
+ }
85
+ }
86
+
87
+ // 3. RESTAURAÇÃO DAS CONFIGURAÇÕES DE AUTH
88
+ if (manifest?.files?.auth) {
89
+ console.log(chalk.blue.bold('\n🔐 3/5 - Restauração das configurações de Auth...'));
90
+ const authConfigPath = path.join(backupPath, 'auth-config.json');
91
+ const authResult = await restoreAuthSettings(authConfigPath, options.projectId);
92
+
93
+ if (authResult.success) {
94
+ console.log(chalk.green('✅ Auth settings restauradas com sucesso!'));
95
+ } else {
96
+ console.log(chalk.yellow('⚠️ Restauração das configurações de Auth falhou:'), authResult.error);
97
+ }
98
+ }
99
+
100
+ // 4. RESTAURAÇÃO DOS STORAGE OBJECTS
101
+ if (manifest?.files?.storage) {
102
+ console.log(chalk.blue.bold('\n📁 4/5 - Restauração dos Storage Objects...'));
103
+ const storageDir = path.join(backupPath, 'storage');
104
+ const storageResult = await restoreStorageObjects(storageDir, options.projectId);
105
+
106
+ if (storageResult.success) {
107
+ console.log(chalk.green('✅ Storage Objects restaurados com sucesso!'));
108
+ } else {
109
+ console.log(chalk.yellow('⚠️ Restauração dos Storage Objects falhou:'), storageResult.error);
110
+ }
111
+ }
112
+
113
+ // 5. RESTAURAÇÃO DAS CONFIGURAÇÕES DE REALTIME
114
+ if (manifest?.files?.realtime) {
115
+ console.log(chalk.blue.bold('\n🔄 5/5 - Restauração das configurações de Realtime...'));
116
+ const realtimeConfigPath = path.join(backupPath, 'realtime-config.json');
117
+ const realtimeResult = await restoreRealtimeSettings(realtimeConfigPath, options.projectId);
118
+
119
+ if (realtimeResult.success) {
120
+ console.log(chalk.green('✅ Realtime settings restauradas com sucesso!'));
121
+ } else {
122
+ console.log(chalk.yellow('⚠️ Restauração das configurações de Realtime falhou:'), realtimeResult.error);
123
+ }
124
+ }
125
+
126
+ // Verificação pós-restore (se solicitada)
127
+ if (options.verify) {
128
+ console.log(chalk.blue.bold('\n🔍 Verificação pós-restore...'));
129
+ const checkResult = await postRestoreVerification(options.projectId);
130
+
131
+ if (checkResult.success) {
132
+ console.log(chalk.green('✅ Verificação concluída com sucesso!'));
133
+ } else {
134
+ console.log(chalk.yellow('⚠️ Verificação encontrou problemas:'), checkResult.issues);
135
+ }
136
+ }
137
+
138
+ console.log(chalk.green.bold('\n🎉 RESTAURAÇÃO COMPLETA FINALIZADA!'));
139
+ console.log(chalk.blue('📁 Backup:'), backupPath);
140
+ console.log(chalk.blue('🆔 Project ID:'), options.projectId);
141
+ console.log(chalk.yellow('\n💡 Use "smoonb check" para verificar a integridade do projeto restaurado'));
142
+
143
+ } catch (error) {
144
+ console.error(chalk.red.bold('❌ Erro durante a restauração:'), error.message);
145
+ console.error(chalk.gray('Stack trace:'), error.stack);
146
+ process.exit(1);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Restauração da database PostgreSQL
152
+ * Processo seguro: DROP → CREATE → RESTORE
153
+ */
154
+ async function restoreDatabase(backupFile, projectId) {
155
+ try {
156
+ // Construir URL de conexão
157
+ const dbUrl = process.env.DATABASE_URL || `postgresql://postgres:[password]@db.${projectId}.supabase.co:5432/postgres`;
158
+ const dbName = new URL(dbUrl).pathname.slice(1);
159
+ const baseUrl = dbUrl.replace(`/${dbName}`, '');
160
+
161
+ console.log(chalk.gray('🔥 Processo de Restauração Limpa iniciado...'));
162
+
163
+ // 1. DROPAR banco existente (força desconexão)
164
+ console.log(chalk.gray(' - Step 1/3: Removendo banco antigo...'));
165
+ try {
166
+ const dropCmd = `dropdb "${dbUrl}" --if-exists -f`;
167
+ execSync(dropCmd, { stdio: 'pipe' }); // Oculta erro se não existir
168
+ } catch (error) {
169
+ console.log(chalk.gray(' (Banco não existia, continuando...)'));
170
+ }
171
+
172
+ // 2. CRIAR banco novo e vazio
173
+ console.log(chalk.gray(' - Step 2/3: Criando banco vazio...'));
174
+ const createCmd = `createdb "${baseUrl}/${dbName}"`;
175
+ execSync(createCmd, { stdio: 'pipe' });
176
+
177
+ // 3. RESTAURAR backup no banco vazio
178
+ console.log(chalk.gray(' - Step 3/3: Restaurando backup...'));
179
+ const restoreCmd = `pg_restore -d "${dbUrl}" --clean --if-exists --single-transaction "${backupFile}"`;
180
+ execSync(restoreCmd, { stdio: 'pipe' });
181
+
182
+ console.log(chalk.green('✅ Restauração da database concluída com sucesso!'));
183
+ return { success: true };
184
+
185
+ } catch (error) {
186
+ console.log(chalk.yellow('⚠️ Restauração da database falhou (credenciais não configuradas)'));
187
+ console.log(chalk.gray(' - Configure DATABASE_URL ou use smoonb config --init'));
188
+ return { success: false, error: error.message };
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Restauração das Edge Functions via Supabase CLI
194
+ */
195
+ async function restoreEdgeFunctions(functionsDir, projectId) {
196
+ try {
197
+ if (!fs.existsSync(functionsDir)) {
198
+ console.log(chalk.gray(' - Nenhuma Edge Function encontrada no backup'));
199
+ return { success: true };
200
+ }
201
+
202
+ console.log(chalk.gray(' - Deploy das Edge Functions via Supabase CLI...'));
203
+
204
+ // Verificar se Supabase CLI está instalado
205
+ try {
206
+ execSync('supabase --version', { stdio: 'pipe' });
207
+ } catch (error) {
208
+ console.log(chalk.yellow('⚠️ Supabase CLI não encontrado'));
209
+ console.log(chalk.gray(' - Instale: npm install -g supabase'));
210
+ return { success: false, error: 'Supabase CLI não encontrado' };
211
+ }
212
+
213
+ // Deploy functions via Supabase CLI
214
+ const deployCmd = `supabase functions deploy --project-ref ${projectId}`;
215
+ execSync(deployCmd, { stdio: 'pipe' });
216
+
217
+ return { success: true };
218
+ } catch (error) {
219
+ return { success: false, error: error.message };
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Restauração das configurações de Auth
225
+ */
226
+ async function restoreAuthSettings(authConfigPath, projectId) {
227
+ try {
228
+ if (!fs.existsSync(authConfigPath)) {
229
+ console.log(chalk.gray(' - Nenhuma configuração de Auth encontrada no backup'));
230
+ return { success: true };
231
+ }
232
+
233
+ console.log(chalk.gray(' - Restaurando configurações de Auth...'));
234
+
235
+ // TODO: Implementar restauração real via Supabase API
236
+ const authConfig = JSON.parse(await fs.promises.readFile(authConfigPath, 'utf8'));
237
+ console.log(chalk.gray(' - Configurações carregadas:', Object.keys(authConfig).length, 'itens'));
238
+
239
+ return { success: true };
240
+ } catch (error) {
241
+ return { success: false, error: error.message };
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Restauração dos Storage Objects
247
+ */
248
+ async function restoreStorageObjects(storageDir, projectId) {
249
+ try {
250
+ if (!fs.existsSync(storageDir)) {
251
+ console.log(chalk.gray(' - Nenhum Storage Object encontrado no backup'));
252
+ return { success: true };
253
+ }
254
+
255
+ console.log(chalk.gray(' - Restaurando Storage Objects...'));
256
+
257
+ // TODO: Implementar restauração real via Supabase API
258
+ const storageConfigPath = path.join(storageDir, 'storage-config.json');
259
+ if (fs.existsSync(storageConfigPath)) {
260
+ const storageConfig = JSON.parse(await fs.promises.readFile(storageConfigPath, 'utf8'));
261
+ console.log(chalk.gray(' - Configurações carregadas:', Object.keys(storageConfig).length, 'itens'));
262
+ }
263
+
264
+ return { success: true };
265
+ } catch (error) {
266
+ return { success: false, error: error.message };
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Restauração das configurações de Realtime
272
+ */
273
+ async function restoreRealtimeSettings(realtimeConfigPath, projectId) {
274
+ try {
275
+ if (!fs.existsSync(realtimeConfigPath)) {
276
+ console.log(chalk.gray(' - Nenhuma configuração de Realtime encontrada no backup'));
277
+ return { success: true };
278
+ }
279
+
280
+ console.log(chalk.gray(' - Restaurando configurações de Realtime...'));
281
+
282
+ // TODO: Implementar restauração real via Supabase API
283
+ const realtimeConfig = JSON.parse(await fs.promises.readFile(realtimeConfigPath, 'utf8'));
284
+ console.log(chalk.gray(' - Configurações carregadas:', Object.keys(realtimeConfig).length, 'itens'));
285
+
286
+ return { success: true };
287
+ } catch (error) {
288
+ return { success: false, error: error.message };
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Verificação pós-restore
294
+ */
295
+ async function postRestoreVerification(projectId) {
296
+ try {
297
+ console.log(chalk.gray(' - Executando verificações básicas...'));
298
+
299
+ // Verificações básicas
300
+ const checks = [
301
+ { name: 'Database Connection', status: 'pending' },
302
+ { name: 'Edge Functions', status: 'pending' },
303
+ { name: 'Auth Providers', status: 'pending' },
304
+ { name: 'Storage Buckets', status: 'pending' },
305
+ { name: 'Realtime Settings', status: 'pending' }
306
+ ];
307
+
308
+ // Simular verificações (TODO: implementar verificações reais)
309
+ checks.forEach(check => {
310
+ check.status = 'ok';
311
+ });
312
+
313
+ // Mostrar resultado
314
+ checks.forEach(check => {
315
+ const icon = check.status === 'ok' ? '✅' :
316
+ check.status === 'warning' ? '⚠️' : '❌';
317
+ console.log(chalk.gray(` ${icon} ${check.name}`));
318
+ });
319
+
320
+ return { success: true };
321
+ } catch (error) {
322
+ return { success: false, issues: [error.message] };
323
+ }
324
+ }
325
+
326
+ module.exports = restoreCommand;
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Comando de gerenciamento de secrets do Supabase
3
+ * Export/import sem commitar no git
4
+ */
5
+
6
+ const chalk = require('chalk');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ /**
11
+ * Gerenciamento de secrets do Supabase
12
+ * Resolve o problema: secrets sensíveis não devem ser commitados
13
+ */
14
+ async function secretsCommand(options) {
15
+ console.log(chalk.red.bold('🚀 smoonb v0.0.1 - EXPERIMENTAL VERSION'));
16
+ console.log(chalk.red.bold('⚠️ VERSÃO EXPERIMENTAL - NUNCA TESTADA EM PRODUÇÃO!'));
17
+ console.log(chalk.red.bold('🚨 USE POR SUA CONTA E RISCO - Pode causar perda de dados!'));
18
+ console.log(chalk.red.bold('❌ NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados!\n'));
19
+
20
+ console.log(chalk.cyan.bold('🔐 Gerenciamento de secrets do Supabase...\n'));
21
+
22
+ try {
23
+ const args = process.argv.slice(3); // Remover 'smoonb', 'secrets'
24
+
25
+ if (args.length === 0) {
26
+ showSecretsHelp();
27
+ return;
28
+ }
29
+
30
+ const action = args[0];
31
+
32
+ switch (action) {
33
+ case 'export':
34
+ await exportSecrets(options);
35
+ break;
36
+ case 'import':
37
+ await importSecrets(options);
38
+ break;
39
+ case 'list':
40
+ await listSecrets(options);
41
+ break;
42
+ case 'validate':
43
+ await validateSecrets(options);
44
+ break;
45
+ default:
46
+ console.error(chalk.red.bold('❌ Ação não reconhecida:'), action);
47
+ showSecretsHelp();
48
+ process.exit(1);
49
+ }
50
+
51
+ } catch (error) {
52
+ console.error(chalk.red.bold('❌ Erro durante o gerenciamento de secrets:'), error.message);
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Exportar secrets para arquivo temporário (gitignored)
59
+ */
60
+ async function exportSecrets(options) {
61
+ console.log(chalk.blue.bold('📤 Exportando secrets...\n'));
62
+
63
+ try {
64
+ // Procurar arquivos de configuração com secrets
65
+ const envFiles = [
66
+ '.env',
67
+ '.env.local',
68
+ '.env.production',
69
+ '.env.development'
70
+ ];
71
+
72
+ const secrets = {};
73
+ let foundSecrets = false;
74
+
75
+ for (const envFile of envFiles) {
76
+ if (fs.existsSync(envFile)) {
77
+ console.log(chalk.gray(` - Lendo ${envFile}...`));
78
+
79
+ const envContent = await fs.promises.readFile(envFile, 'utf8');
80
+ const lines = envContent.split('\n');
81
+
82
+ for (const line of lines) {
83
+ const trimmedLine = line.trim();
84
+ if (trimmedLine && !trimmedLine.startsWith('#')) {
85
+ const [key, ...valueParts] = trimmedLine.split('=');
86
+ if (key && valueParts.length > 0) {
87
+ const value = valueParts.join('=');
88
+ secrets[key.trim()] = value.trim();
89
+ foundSecrets = true;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ if (!foundSecrets) {
97
+ console.log(chalk.yellow('⚠️ Nenhum secret encontrado nos arquivos .env'));
98
+ console.log(chalk.gray(' - Verifique se existem arquivos .env no projeto'));
99
+ return;
100
+ }
101
+
102
+ // Criar arquivo de secrets temporário (gitignored)
103
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
104
+ const secretsFile = `smoonb-secrets-${timestamp}.env`;
105
+
106
+ let secretsContent = `# smoonb Secrets Export - ${new Date().toISOString()}\n`;
107
+ secretsContent += `# ⚠️ NÃO COMMITE ESTE ARQUIVO! ⚠️\n`;
108
+ secretsContent += `# Este arquivo contém secrets sensíveis\n\n`;
109
+
110
+ for (const [key, value] of Object.entries(secrets)) {
111
+ secretsContent += `${key}=${value}\n`;
112
+ }
113
+
114
+ await fs.promises.writeFile(secretsFile, secretsContent);
115
+
116
+ console.log(chalk.green('✅ Secrets exportados com sucesso!'));
117
+ console.log(chalk.blue('📁 Arquivo:'), secretsFile);
118
+ console.log(chalk.blue('🔢 Secrets encontrados:'), Object.keys(secrets).length);
119
+ console.log(chalk.yellow('\n⚠️ IMPORTANTE: NÃO commite este arquivo!'));
120
+ console.log(chalk.yellow('💡 Use "smoonb secrets import" para importar em outro projeto'));
121
+
122
+ // Adicionar ao .gitignore se não estiver lá
123
+ await ensureGitignore(secretsFile);
124
+
125
+ } catch (error) {
126
+ console.error(chalk.red.bold('❌ Erro durante export de secrets:'), error.message);
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Importar secrets de arquivo temporário
133
+ */
134
+ async function importSecrets(options) {
135
+ console.log(chalk.blue.bold('📥 Importando secrets...\n'));
136
+
137
+ try {
138
+ // Procurar arquivo de secrets mais recente
139
+ const secretsFiles = fs.readdirSync('.')
140
+ .filter(file => file.startsWith('smoonb-secrets-') && file.endsWith('.env'))
141
+ .sort()
142
+ .reverse();
143
+
144
+ if (secretsFiles.length === 0) {
145
+ console.error(chalk.red.bold('❌ Nenhum arquivo de secrets encontrado'));
146
+ console.log(chalk.yellow('💡 Use "smoonb secrets export" primeiro'));
147
+ process.exit(1);
148
+ }
149
+
150
+ const secretsFile = secretsFiles[0];
151
+ console.log(chalk.gray(` - Usando arquivo: ${secretsFile}`));
152
+
153
+ // Ler arquivo de secrets
154
+ const secretsContent = await fs.promises.readFile(secretsFile, 'utf8');
155
+ const lines = secretsContent.split('\n');
156
+
157
+ const secrets = {};
158
+ for (const line of lines) {
159
+ const trimmedLine = line.trim();
160
+ if (trimmedLine && !trimmedLine.startsWith('#')) {
161
+ const [key, ...valueParts] = trimmedLine.split('=');
162
+ if (key && valueParts.length > 0) {
163
+ const value = valueParts.join('=');
164
+ secrets[key.trim()] = value.trim();
165
+ }
166
+ }
167
+ }
168
+
169
+ if (Object.keys(secrets).length === 0) {
170
+ console.log(chalk.yellow('⚠️ Nenhum secret válido encontrado no arquivo'));
171
+ return;
172
+ }
173
+
174
+ // Criar/atualizar .env.local
175
+ const envLocalPath = '.env.local';
176
+ let envLocalContent = `# smoonb Import - ${new Date().toISOString()}\n`;
177
+ envLocalContent += `# Secrets importados automaticamente\n\n`;
178
+
179
+ for (const [key, value] of Object.entries(secrets)) {
180
+ envLocalContent += `${key}=${value}\n`;
181
+ }
182
+
183
+ await fs.promises.writeFile(envLocalPath, envLocalContent);
184
+
185
+ console.log(chalk.green('✅ Secrets importados com sucesso!'));
186
+ console.log(chalk.blue('📁 Arquivo:'), envLocalPath);
187
+ console.log(chalk.blue('🔢 Secrets importados:'), Object.keys(secrets).length);
188
+ console.log(chalk.yellow('\n💡 Os secrets foram salvos em .env.local (gitignored)'));
189
+
190
+ } catch (error) {
191
+ console.error(chalk.red.bold('❌ Erro durante import de secrets:'), error.message);
192
+ throw error;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Listar secrets encontrados (sem mostrar valores)
198
+ */
199
+ async function listSecrets(options) {
200
+ console.log(chalk.blue.bold('📋 Listando secrets encontrados...\n'));
201
+
202
+ try {
203
+ const envFiles = [
204
+ '.env',
205
+ '.env.local',
206
+ '.env.production',
207
+ '.env.development'
208
+ ];
209
+
210
+ const allSecrets = new Set();
211
+ let foundFiles = 0;
212
+
213
+ for (const envFile of envFiles) {
214
+ if (fs.existsSync(envFile)) {
215
+ foundFiles++;
216
+ console.log(chalk.gray(` - ${envFile}:`));
217
+
218
+ const envContent = await fs.promises.readFile(envFile, 'utf8');
219
+ const lines = envContent.split('\n');
220
+
221
+ for (const line of lines) {
222
+ const trimmedLine = line.trim();
223
+ if (trimmedLine && !trimmedLine.startsWith('#')) {
224
+ const [key] = trimmedLine.split('=');
225
+ if (key) {
226
+ allSecrets.add(key.trim());
227
+ console.log(chalk.gray(` • ${key.trim()}`));
228
+ }
229
+ }
230
+ }
231
+ console.log();
232
+ }
233
+ }
234
+
235
+ if (foundFiles === 0) {
236
+ console.log(chalk.yellow('⚠️ Nenhum arquivo .env encontrado'));
237
+ return;
238
+ }
239
+
240
+ console.log(chalk.green('✅ Resumo:'));
241
+ console.log(chalk.blue('📁 Arquivos analisados:'), foundFiles);
242
+ console.log(chalk.blue('🔢 Secrets únicos encontrados:'), allSecrets.size);
243
+
244
+ } catch (error) {
245
+ console.error(chalk.red.bold('❌ Erro durante listagem de secrets:'), error.message);
246
+ throw error;
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Validar secrets (verificar se estão configurados)
252
+ */
253
+ async function validateSecrets(options) {
254
+ console.log(chalk.blue.bold('🔍 Validando secrets...\n'));
255
+
256
+ try {
257
+ const requiredSecrets = [
258
+ 'SUPABASE_URL',
259
+ 'SUPABASE_ANON_KEY',
260
+ 'SUPABASE_SERVICE_ROLE_KEY'
261
+ ];
262
+
263
+ const envFiles = [
264
+ '.env',
265
+ '.env.local',
266
+ '.env.production',
267
+ '.env.development'
268
+ ];
269
+
270
+ const foundSecrets = new Set();
271
+ let foundFiles = 0;
272
+
273
+ // Coletar todos os secrets
274
+ for (const envFile of envFiles) {
275
+ if (fs.existsSync(envFile)) {
276
+ foundFiles++;
277
+ const envContent = await fs.promises.readFile(envFile, 'utf8');
278
+ const lines = envContent.split('\n');
279
+
280
+ for (const line of lines) {
281
+ const trimmedLine = line.trim();
282
+ if (trimmedLine && !trimmedLine.startsWith('#')) {
283
+ const [key] = trimmedLine.split('=');
284
+ if (key) {
285
+ foundSecrets.add(key.trim());
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ // Validar secrets obrigatórios
293
+ console.log(chalk.gray(' - Verificando secrets obrigatórios:'));
294
+ let allValid = true;
295
+
296
+ for (const secret of requiredSecrets) {
297
+ if (foundSecrets.has(secret)) {
298
+ console.log(chalk.green(` ✅ ${secret}`));
299
+ } else {
300
+ console.log(chalk.red(` ❌ ${secret} - NÃO ENCONTRADO`));
301
+ allValid = false;
302
+ }
303
+ }
304
+
305
+ console.log(chalk.green('\n✅ Validação concluída!'));
306
+ console.log(chalk.blue('📁 Arquivos analisados:'), foundFiles);
307
+ console.log(chalk.blue('🔢 Secrets encontrados:'), foundSecrets.size);
308
+
309
+ if (allValid) {
310
+ console.log(chalk.green('🎉 Todos os secrets obrigatórios estão configurados!'));
311
+ } else {
312
+ console.log(chalk.yellow('⚠️ Alguns secrets obrigatórios estão faltando'));
313
+ console.log(chalk.yellow('💡 Use "smoonb config --init" para configurar'));
314
+ }
315
+
316
+ } catch (error) {
317
+ console.error(chalk.red.bold('❌ Erro durante validação de secrets:'), error.message);
318
+ throw error;
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Mostrar ajuda do comando secrets
324
+ */
325
+ function showSecretsHelp() {
326
+ console.log(chalk.cyan.bold('🔐 Comandos de Secrets disponíveis:\n'));
327
+ console.log(chalk.cyan(' export'), chalk.gray(' - Exportar secrets para arquivo temporário'));
328
+ console.log(chalk.cyan(' import'), chalk.gray(' - Importar secrets de arquivo temporário'));
329
+ console.log(chalk.cyan(' list'), chalk.gray(' - Listar secrets encontrados (sem valores)'));
330
+ console.log(chalk.cyan(' validate'), chalk.gray(' - Validar secrets obrigatórios'));
331
+ console.log(chalk.yellow('\n💡 Exemplos:'));
332
+ console.log(chalk.gray(' smoonb secrets export'));
333
+ console.log(chalk.gray(' smoonb secrets import'));
334
+ console.log(chalk.gray(' smoonb secrets list'));
335
+ console.log(chalk.gray(' smoonb secrets validate'));
336
+ }
337
+
338
+ /**
339
+ * Garantir que arquivo está no .gitignore
340
+ */
341
+ async function ensureGitignore(filename) {
342
+ try {
343
+ const gitignorePath = '.gitignore';
344
+ let gitignoreContent = '';
345
+
346
+ if (fs.existsSync(gitignorePath)) {
347
+ gitignoreContent = await fs.promises.readFile(gitignorePath, 'utf8');
348
+ }
349
+
350
+ // Verificar se já está no .gitignore
351
+ if (!gitignoreContent.includes(filename)) {
352
+ gitignoreContent += `\n# smoonb secrets files\n${filename}\n`;
353
+ await fs.promises.writeFile(gitignorePath, gitignoreContent);
354
+ console.log(chalk.gray(` - Arquivo ${filename} adicionado ao .gitignore`));
355
+ }
356
+ } catch (error) {
357
+ console.log(chalk.yellow('⚠️ Não foi possível atualizar .gitignore:'), error.message);
358
+ }
359
+ }
360
+
361
+ module.exports = secretsCommand;