smoonb 0.0.55 → 0.0.57

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) Goalmoon Tecnologia Ltda.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,32 +1,43 @@
1
- # smoonb
1
+ # Supa Moonbase (smoonb)
2
2
 
3
3
  **Complete Supabase backup and migration tool**
4
4
 
5
- ## ⚠️ EXPERIMENTAL VERSION - NÃO TESTADA - USE POR SUA CONTA E RISCO
5
+ A primeira ferramenta CLI completa para backup e migração de projetos Supabase. Resolve o problema de backup incompleto das ferramentas existentes.
6
6
 
7
- **🚨 AVISO IMPORTANTE:**
8
- - Este software **NUNCA** foi testado em produção
9
- - **USE POR SUA CONTA E RISCO** - Pode causar perda irreparável de dados
10
- - **NÃO NOS RESPONSABILIZAMOS** por qualquer perda de dados
11
- - **NENHUM SUPORTE** é oferecido nesta fase - apenas aceitamos contribuições
7
+ > **Nota sobre acesso comercial:** o Supa Moonbase passará a exigir validação de conta antes de executar operações (login + verificação de assinatura) em fase futura. Nesta versão, não há autenticação implementada — este README apenas apresenta a base legal/comercial. O uso operacional será regido pelos [Termos de Serviço](https://smoonb.com/terms) e pela [Política de Privacidade](https://smoonb.com/privacy).
12
8
 
13
- **Desenvolvido por:** Goalmoon Tecnologia LTDA (https://goalmoon.com)
9
+ **Desenvolvido por:** Goalmoon Tecnologia LTDA
10
+ **Website:** https://smoonb.com
11
+ **GitHub:** https://github.com/almmello/smoonb
14
12
 
15
13
  ## 🎯 Objetivo
16
14
 
17
- O **smoonb** resolve o problema das ferramentas existentes que fazem backup apenas da database PostgreSQL, ignorando componentes críticos do Supabase:
15
+ O **smoonb** resolve o problema das ferramentas existentes que fazem backup apenas da database PostgreSQL, ignorando componentes críticos do Supabase.
16
+
17
+ ## 📦 Componentes de Backup
18
18
 
19
- - **Database PostgreSQL** (backup completo via `pg_dumpall`, idêntico ao Dashboard)
20
- - ✅ **Database SQL Separado** (schema, data, roles em arquivos separados para troubleshooting)
19
+ O smoonb faz backup completo de todos os componentes do seu projeto Supabase:
20
+
21
+ - ✅ **Database PostgreSQL** (backup completo via `pg_dumpall` + SQL separados, idêntico ao Dashboard)
21
22
  - ✅ **Database Extensions and Settings** (extensões PostgreSQL e configurações)
23
+ - ✅ **Custom Roles** (roles personalizados do PostgreSQL)
22
24
  - ✅ **Edge Functions** (download automático do servidor)
23
25
  - ✅ **Auth Settings** (configurações de autenticação via Management API)
24
26
  - ✅ **Storage Buckets** (metadados e configurações via Management API)
25
- - ✅ **Realtime Settings** (publicações e configurações capturadas interativamente)
26
- - ✅ **Custom Roles** (roles personalizados do PostgreSQL)
27
+ - ✅ **Realtime Settings** (7 parâmetros capturados interativamente)
27
28
  - ✅ **Supabase .temp** (arquivos temporários do Supabase CLI)
28
29
  - ✅ **Migrations** (todas as migrations do projeto via `supabase migration fetch`)
29
30
 
31
+ ## ⚠️ Termo de Uso e Aviso de Risco
32
+
33
+ Ao usar o smoonb, você reconhece e concorda que o smoonb é fornecido "NO ESTADO EM QUE SE ENCONTRA" ("AS IS") e "CONFORME DISPONIBILIDADE", sem garantias de qualquer natureza—expressas, implícitas ou legais—incluindo, sem limitação, garantias de comercialização, adequação a um fim específico e não violação, na máxima extensão permitida pela lei aplicável.
34
+
35
+ Operações de backup e restauração envolvem riscos, os ambientes variam amplamente e não é possível prever ou validar todas as configurações dos usuários. Você é o único responsável por validar seu ambiente, manter cópias independentes e verificar os resultados antes de utilizá-los em produção.
36
+
37
+ **Limitação de responsabilidade (PT-BR)** — Na máxima extensão permitida por lei, a Goalmoon, seus contribuidores e licenciadores não serão responsáveis por danos indiretos, incidentais, especiais, consequentes, exemplares ou punitivos (incluindo perda de dados, interrupção de negócios ou lucros cessantes) decorrentes do uso, incapacidade de uso, das operações de backup/restauração realizadas com, ou dos resultados gerados pelo smoonb.
38
+
39
+ **Observação para consumidores no Brasil (PT-BR)** — Para consumidores brasileiros, este aviso não afasta direitos irrenunciáveis previstos no Código de Defesa do Consumidor (CDC); qualquer limitação aqui prevista só se aplica nos limites da lei e não impede a indenização obrigatória quando cabível.
40
+
30
41
  ## 🚀 Instalação
31
42
 
32
43
  **⚠️ IMPORTANTE: Instale APENAS localmente no projeto!**
@@ -96,18 +107,18 @@ touch .env.local
96
107
 
97
108
  ```env
98
109
  # URLs e Chaves do Supabase
99
- NEXT_PUBLIC_SUPABASE_URL=https://seu-project-id.supabase.co
100
- NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
101
- SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
110
+ NEXT_PUBLIC_SUPABASE_URL=[sua-supabase-url]
111
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=[sua-anon-key]
112
+ SUPABASE_SERVICE_ROLE_KEY=e[sua-service-role]
102
113
 
103
114
  # Database Connection
104
- SUPABASE_DB_URL=postgresql://postgres:[senha]@db.seu-project-id.supabase.co:5432/postgres
115
+ SUPABASE_DB_URL=postgresql://postgres:[sua-database-password]@db.[seu-project-id].supabase.co:5432/postgres
105
116
 
106
117
  # Identificação do Projeto
107
- SUPABASE_PROJECT_ID=seu-project-id
118
+ SUPABASE_PROJECT_ID=[seu-project-id]
108
119
 
109
120
  # Personal Access Token (OBRIGATÓRIO para Management API)
110
- SUPABASE_ACCESS_TOKEN=sbp_1234567890abcdef1234567890abcdef
121
+ SUPABASE_ACCESS_TOKEN=[seu-access-token]]
111
122
 
112
123
  # Diretório de Backups (opcional, padrão: ./backups)
113
124
  SMOONB_OUTPUT_DIR=./backups
@@ -131,10 +142,6 @@ Valor atual: https://abc123.supabase.co
131
142
  Este é o valor correto do projeto alvo? (S/n): S
132
143
  ```
133
144
 
134
- ### Método Legado: `.smoonbrc` (DEPRECADO)
135
-
136
- ⚠️ **Nota:** O arquivo `.smoonbrc` ainda é suportado para compatibilidade, mas é **recomendado migrar para `.env.local`**.
137
-
138
145
  ```bash
139
146
  npx smoonb config --init
140
147
  ```
@@ -157,21 +164,21 @@ npx smoonb backup
157
164
  - ⚡ Edge Functions (explicação sobre reset de link e download)
158
165
  - 📦 Storage (explicação sobre metadados)
159
166
  - 🔐 Auth Settings (explicação sobre configurações)
160
- - 🔄 Realtime Settings (explicação sobre captura interativa)
167
+ - 🔄 Realtime Settings (explicação sobre captura interativa de 7 parâmetros)
161
168
  - 🗑️ Opções de limpeza (functions, .temp, migrations após backup)
162
169
  6. **Resumo de Configurações** - Mostra tudo que será feito
163
170
  7. **Confirmação Final** - Confirma antes de iniciar
164
171
  8. **Execução das Etapas:**
165
- - 📊 1/11 - Backup Database via `pg_dumpall` (Docker)
166
- - 📊 2/11 - Backup Database SQL separado (schema, data, roles)
167
- - 🔧 3/11 - Backup Database Extensions and Settings
168
- - 🔐 4/11 - Backup Auth Settings (se selecionado)
169
- - 🔄 5/11 - Backup Realtime Settings (se selecionado)
170
- - 📦 6/11 - Backup Storage (se selecionado)
171
- - 👥 7/11 - Backup Custom Roles
172
- - ⚡ 8/11 - Backup Edge Functions (se selecionado)
173
- - 📁 9/11 - Backup Supabase .temp
174
- - 📋 10/11 - Backup Migrations
172
+ - 📊 1/10 - Backup Database via `pg_dumpall` (Docker)
173
+ - 📊 2/10 - Backup Database SQL separado (schema, data, roles)
174
+ - 🔧 3/10 - Backup Database Extensions and Settings
175
+ - 🔐 4/10 - Backup Auth Settings (se selecionado)
176
+ - 🔄 5/10 - Backup Realtime Settings (se selecionado) - 7 parâmetros capturados interativamente
177
+ - 📦 6/10 - Backup Storage (se selecionado)
178
+ - 👥 7/10 - Backup Custom Roles
179
+ - ⚡ 8/10 - Backup Edge Functions (se selecionado)
180
+ - 📁 9/10 - Backup Supabase .temp (se selecionado)
181
+ - 📋 10/10 - Backup Migrations (se selecionado)
175
182
 
176
183
  **Resultado:**
177
184
  ```
@@ -259,8 +266,6 @@ npx smoonb functions push
259
266
  | `npx smoonb backup` | Backup completo interativo usando Docker |
260
267
  | `npx smoonb restore` | Restauração interativa usando psql (Docker) |
261
268
  | `npx smoonb check` | Verificação de integridade pós-restore |
262
- | `npx smoonb functions` | Gerenciar Edge Functions |
263
- | `npx smoonb config` | Configurar credenciais (legado) |
264
269
 
265
270
  ## 🏗️ Arquitetura Técnica
266
271
 
@@ -328,6 +333,14 @@ restore/
328
333
  #### Auth, Storage, Realtime
329
334
  - **Management API**: Usa Personal Access Token
330
335
  - **JSON Export**: Configurações exportadas como JSON
336
+ - **Realtime Settings**: Captura interativa de 7 parâmetros:
337
+ 1. Enable Realtime service
338
+ 2. Allow public access
339
+ 3. Database connection pool size
340
+ 4. Max concurrent clients
341
+ 5. Max events per second
342
+ 6. Max presence events per second
343
+ 7. Max payload size in KB
331
344
  - **Manual para alguns**: Alguns settings precisam ser aplicados manualmente por segurança
332
345
 
333
346
  ### Restore Strategy
@@ -471,28 +484,51 @@ Se houver problemas:
471
484
  - **Sem Dados Sensíveis**: Nenhum dado sensível é enviado para fora do seu ambiente
472
485
  - **Docker Isolado**: Operações de database via Docker (isolamento)
473
486
 
474
- ## 📝 Licença
487
+ ## 💼 Modelo de Acesso e Assinatura
475
488
 
476
- **Versões 0.x.x**: Uso gratuito (experimental)
477
- **Versões 1.0.0+**: Licença comercial (anúncio com 90 dias de antecedência)
489
+ O código do Supa Moonbase é disponibilizado sob licença MIT (ver `LICENSE`). Em fase futura, a execução do CLI será vinculada a uma assinatura por conta, permitindo uso associado a uma conta válida. A validação de conta ocorrerá antes de qualquer operação sensível (ex.: backup e restore).
478
490
 
479
- Veja [LICENSE.md](LICENSE.md) para mais detalhes.
491
+ Até que a validação esteja ativa, a ferramenta pode ser utilizada sem login.
480
492
 
481
- ## 🤝 Contribuição
493
+ Saiba mais em [Pricing](https://smoonb.com/pricing) e [FAQ Comercial](https://smoonb.com/faq).
482
494
 
483
- Contribuições são bem-vindas! Este é um projeto experimental e precisamos de feedback da comunidade.
495
+ ## 🎁 Grandfathering (conceito)
496
+
497
+ Contas criadas durante o período inicial de disponibilização comercial poderão manter condições de acesso diferenciadas enquanto permanecerem ativas. O objetivo é reconhecer os primeiros usuários. Detalhes específicos constarão nos [Termos de Serviço](https://smoonb.com/terms) e no [Pricing](https://smoonb.com/pricing).
484
498
 
485
- ## 📚 Versão
499
+ ## 🔒 Privacidade e LGPD (resumo)
486
500
 
487
- **Versão Atual:** 0.0.48
501
+ O Supa Moonbase adota o princípio de minimização de dados. Quando a validação de conta estiver ativa, trataremos apenas informações estritamente necessárias para controle de acesso e faturamento (por exemplo, identificador de conta e contato). Os propósitos, bases legais e direitos do titular serão descritos na [Política de Privacidade](https://smoonb.com/privacy).
488
502
 
489
- ## Apoie o Projeto
503
+ ## 📋 Termos de Serviço e Uso de Marca
504
+
505
+ A licença de código (MIT) não substitui os Termos de Serviço que regerão o acesso operacional por conta e a validação de assinatura.
506
+
507
+ "Supa Moonbase" e elementos de identidade visual são marcas da Goalmoon Tecnologia Ltda.; o uso de marca e assets de branding é restrito, conforme os [Termos de Serviço](https://smoonb.com/terms).
508
+
509
+ ## ❓ FAQ Comercial
510
+
511
+ **Por que assinatura se o código é MIT?**
512
+
513
+ > O código permanece aberto para auditoria e contribuições. O acesso operacional será condicionado à validação de conta, conforme Termos de Serviço.
514
+
515
+ **O que significa grandfathering?**
516
+
517
+ > Contas do período inicial poderão manter condições diferenciadas enquanto ativas; detalhes estarão nos Termos.
518
+
519
+
520
+
521
+ ## 📝 Licença
522
+
523
+ O código do Supa Moonbase é disponibilizado sob licença MIT. Veja [LICENSE](LICENSE) para o texto completo da licença.
524
+
525
+ ## 🤝 Contribuição
526
+
527
+ Contribuições são bem-vindas! Este é um projeto experimental e precisamos de feedback da comunidade.
490
528
 
491
- Se este projeto for útil para você, considere comprar um café:
492
- [Compre um café](https://pag.ae/7Yj8QjQjQ)
493
529
 
494
530
  ---
495
531
 
496
532
  **Desenvolvido por:** Goalmoon Tecnologia LTDA
497
- **Website:** https://goalmoon.com
533
+ **Website:** https://smoonb.com
498
534
  **GitHub:** https://github.com/almmello/smoonb
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -2,7 +2,6 @@ const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const fs = require('fs').promises;
4
4
  const { ensureDir, writeJson } = require('../../utils/fsx');
5
- const { readConfig, validateFor } = require('../../utils/config');
6
5
  const { showBetaBanner } = require('../../utils/banner');
7
6
  const { getDockerVersion } = require('../../utils/docker');
8
7
  const { readEnvFile, writeEnvFile, backupEnvFile } = require('../../utils/env');
@@ -28,6 +27,18 @@ module.exports = async (options) => {
28
27
  showBetaBanner();
29
28
 
30
29
  try {
30
+ // Termo de uso e aviso de risco
31
+ console.log(chalk.yellow.bold('\n⚠️ TERMO DE USO E AVISO DE RISCO\n'));
32
+ console.log(chalk.white('Ao prosseguir, você reconhece e concorda que o smoonb é fornecido "NO ESTADO EM QUE SE ENCONTRA" ("AS IS") e "CONFORME DISPONIBILIDADE", sem garantias de qualquer natureza—expressas, implícitas ou legais—incluindo, sem limitação, garantias de comercialização, adequação a um fim específico e não violação, na máxima extensão permitida pela lei aplicável. Operações de backup e restauração envolvem riscos, os ambientes variam amplamente e não é possível prever ou validar todas as configurações dos usuários. Você é o único responsável por validar seu ambiente, manter cópias independentes e verificar os resultados antes de utilizá-los em produção. O smoonb é construído com repositórios públicos, auditáveis e software livre, para auxiliar pessoas a simplificar seus fluxos, sem com isso criar qualquer garantia, promessa de suporte ou compromisso de nível de serviço.\n'));
33
+ console.log(chalk.white('Limitação de responsabilidade (PT-BR) — Na máxima extensão permitida por lei, a Goalmoon, seus contribuidores e licenciadores não serão responsáveis por danos indiretos, incidentais, especiais, consequentes, exemplares ou punitivos (incluindo perda de dados, interrupção de negócios ou lucros cessantes) decorrentes do uso, incapacidade de uso, das operações de backup/restauração realizadas com, ou dos resultados gerados pelo smoonb. Em qualquer hipótese, a responsabilidade total por todas as reivindicações relacionadas ao smoonb não excederá o valor pago por você pelo smoonb nos 12 meses anteriores ao evento. Nada neste aviso exclui ou limita responsabilidades onde tais limites sejam proibidos por lei, incluindo (conforme aplicável) dolo ou culpa grave.\n'));
34
+ console.log(chalk.white('Observação para consumidores no Brasil (PT-BR) — Para consumidores brasileiros, este aviso não afasta direitos irrenunciáveis previstos no Código de Defesa do Consumidor (CDC); qualquer limitação aqui prevista só se aplica nos limites da lei e não impede a indenização obrigatória quando cabível.\n'));
35
+
36
+ const termsAccepted = await confirm('Você aceita os Termos de Uso e o Aviso de Risco de Backup?', true);
37
+ if (!termsAccepted) {
38
+ console.log(chalk.red('🚫 Operação cancelada pelo usuário.'));
39
+ process.exit(1);
40
+ }
41
+
31
42
  // Executar validação Docker ANTES de tudo
32
43
  await step00DockerValidation();
33
44
 
@@ -42,9 +53,8 @@ module.exports = async (options) => {
42
53
  process.exit(1);
43
54
  }
44
55
 
45
- // Carregar configuração existente apenas para defaults de diretório
46
- const config = await readConfig().catch(() => ({ backup: { outputDir: './backups' }, supabase: {} }));
47
- validateFor(config, 'backup');
56
+ // Diretório de backup padrão
57
+ const defaultOutputDir = './backups';
48
58
 
49
59
  // Pré-passo de ENV: criar diretório de backup com timestamp já no início
50
60
  const now = new Date();
@@ -56,7 +66,7 @@ module.exports = async (options) => {
56
66
  const second = String(now.getSeconds()).padStart(2, '0');
57
67
 
58
68
  // Resolver diretório de saída
59
- const defaultOutput = options.output || config.backup?.outputDir || './backups';
69
+ const defaultOutput = options.output || defaultOutputDir;
60
70
  const backupDir = path.join(defaultOutput, `backup-${year}-${month}-${day}-${hour}-${minute}-${second}`);
61
71
  await ensureDir(backupDir);
62
72
 
@@ -88,7 +98,7 @@ module.exports = async (options) => {
88
98
  }
89
99
 
90
100
  // Recalcular outputDir a partir do ENV mapeado
91
- const resolvedOutputDir = options.output || getValue('SMOONB_OUTPUT_DIR') || config.backup?.outputDir || './backups';
101
+ const resolvedOutputDir = options.output || getValue('SMOONB_OUTPUT_DIR') || defaultOutputDir;
92
102
 
93
103
  // Se mudou o outputDir, movemos o backupDir inicial para o novo local mantendo timestamp
94
104
  const finalBackupDir = backupDir.startsWith(path.resolve(resolvedOutputDir))
@@ -139,22 +149,22 @@ module.exports = async (options) => {
139
149
 
140
150
  // Mostrar resumo e pedir confirmação final
141
151
  console.log(chalk.cyan('\n📋 RESUMO DAS CONFIGURAÇÕES:\n'));
142
- console.log(chalk.gray(` ✅ Edge Functions: ${flags.includeFunctions ? 'Sim' : 'Não'}`));
152
+ console.log(chalk.white(` ✅ Edge Functions: ${flags.includeFunctions ? 'Sim' : 'Não'}`));
143
153
  if (flags.includeFunctions) {
144
- console.log(chalk.gray(` 🗑️ Limpar após backup: ${flags.cleanFunctions ? 'Sim' : 'Não'}`));
154
+ console.log(chalk.white(` 🗑️ Limpar após backup: ${flags.cleanFunctions ? 'Sim' : 'Não'}`));
145
155
  }
146
- console.log(chalk.gray(` ✅ Supabase .temp: ${flags.includeTemp ? 'Sim' : 'Não'}`));
156
+ console.log(chalk.white(` ✅ Supabase .temp: ${flags.includeTemp ? 'Sim' : 'Não'}`));
147
157
  if (flags.includeTemp) {
148
- console.log(chalk.gray(` 🗑️ Apagar após backup: ${flags.cleanTemp ? 'Sim' : 'Não'}`));
158
+ console.log(chalk.white(` 🗑️ Apagar após backup: ${flags.cleanTemp ? 'Sim' : 'Não'}`));
149
159
  }
150
- console.log(chalk.gray(` ✅ Migrations: ${flags.includeMigrations ? 'Sim' : 'Não'}`));
160
+ console.log(chalk.white(` ✅ Migrations: ${flags.includeMigrations ? 'Sim' : 'Não'}`));
151
161
  if (flags.includeMigrations) {
152
- console.log(chalk.gray(` 🗑️ Apagar após backup: ${flags.cleanMigrations ? 'Sim' : 'Não'}`));
162
+ console.log(chalk.white(` 🗑️ Apagar após backup: ${flags.cleanMigrations ? 'Sim' : 'Não'}`));
153
163
  }
154
- console.log(chalk.gray(` ✅ Storage: ${flags.includeStorage ? 'Sim' : 'Não'}`));
155
- console.log(chalk.gray(` ✅ Auth: ${flags.includeAuth ? 'Sim' : 'Não'}`));
156
- console.log(chalk.gray(` ✅ Realtime: ${flags.includeRealtime ? 'Sim' : 'Não'}`));
157
- console.log(chalk.gray(` 📁 Diretório de backup: ${finalBackupDir}\n`));
164
+ console.log(chalk.white(` ✅ Storage: ${flags.includeStorage ? 'Sim' : 'Não'}`));
165
+ console.log(chalk.white(` ✅ Auth: ${flags.includeAuth ? 'Sim' : 'Não'}`));
166
+ console.log(chalk.white(` ✅ Realtime: ${flags.includeRealtime ? 'Sim' : 'Não'}`));
167
+ console.log(chalk.white(` 📁 Diretório de backup: ${finalBackupDir}\n`));
158
168
 
159
169
  const finalOk = await confirm('Deseja iniciar o backup com estas configurações?', true);
160
170
 
@@ -193,7 +203,7 @@ module.exports = async (options) => {
193
203
 
194
204
  // Executar todas as etapas na ordem
195
205
  console.log(chalk.blue(`📁 Diretório: ${finalBackupDir}`));
196
- console.log(chalk.gray(`🐳 Backup via Docker Desktop`));
206
+ console.log(chalk.white(`🐳 Backup via Docker Desktop`));
197
207
 
198
208
  // Contar etapas totais para numeração
199
209
  // Etapas fixas: Database, Database Separado, Database Settings, Custom Roles (4)
@@ -17,7 +17,7 @@ module.exports = async () => {
17
17
  }
18
18
 
19
19
  console.log(chalk.green('✅ Docker Desktop detectado e funcionando'));
20
- console.log(chalk.gray(`🐳 Versão: ${backupCapability.dockerStatus.version}`));
20
+ console.log(chalk.white(`🐳 Versão: ${backupCapability.dockerStatus.version}`));
21
21
 
22
22
  return { success: true };
23
23
  };
@@ -8,7 +8,7 @@ const { execSync } = require('child_process');
8
8
  */
9
9
  module.exports = async ({ databaseUrl, backupDir }) => {
10
10
  try {
11
- console.log(chalk.gray(' - Criando backup completo via pg_dumpall...'));
11
+ console.log(chalk.white(' - Criando backup completo via pg_dumpall...'));
12
12
 
13
13
  // Extrair credenciais da databaseUrl
14
14
  const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
@@ -45,7 +45,7 @@ module.exports = async ({ databaseUrl, backupDir }) => {
45
45
  `-f /host/${fileName}`
46
46
  ].join(' ');
47
47
 
48
- console.log(chalk.gray(' - Executando pg_dumpall via Docker...'));
48
+ console.log(chalk.white(' - Executando pg_dumpall via Docker...'));
49
49
  execSync(dockerCmd, { stdio: 'pipe' });
50
50
 
51
51
  // Compactar igual ao Supabase Dashboard
@@ -8,14 +8,14 @@ const { execSync } = require('child_process');
8
8
  */
9
9
  module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
10
10
  try {
11
- console.log(chalk.gray(' - Criando backups SQL separados via Supabase CLI...'));
11
+ console.log(chalk.white(' - Criando backups SQL separados via Supabase CLI...'));
12
12
 
13
13
  const dbUrl = databaseUrl;
14
14
  const files = [];
15
15
  let totalSizeKB = 0;
16
16
 
17
17
  // 1. Backup do Schema
18
- console.log(chalk.gray(' - Exportando schema...'));
18
+ console.log(chalk.white(' - Exportando schema...'));
19
19
  const schemaFile = path.join(backupDir, 'schema.sql');
20
20
 
21
21
  try {
@@ -33,7 +33,7 @@ module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
33
33
  }
34
34
 
35
35
  // 2. Backup dos Dados
36
- console.log(chalk.gray(' - Exportando dados...'));
36
+ console.log(chalk.white(' - Exportando dados...'));
37
37
  const dataFile = path.join(backupDir, 'data.sql');
38
38
 
39
39
  try {
@@ -51,7 +51,7 @@ module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
51
51
  }
52
52
 
53
53
  // 3. Backup dos Roles
54
- console.log(chalk.gray(' - Exportando roles...'));
54
+ console.log(chalk.white(' - Exportando roles...'));
55
55
  const rolesFile = path.join(backupDir, 'roles.sql');
56
56
 
57
57
  try {
@@ -8,7 +8,7 @@ const { execSync } = require('child_process');
8
8
  */
9
9
  module.exports = async ({ databaseUrl, projectId, backupDir }) => {
10
10
  try {
11
- console.log(chalk.gray(' - Capturando Database Extensions and Settings...'));
11
+ console.log(chalk.white(' - Capturando Database Extensions and Settings...'));
12
12
 
13
13
  // Extrair credenciais da databaseUrl
14
14
  const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
@@ -125,7 +125,7 @@ AND EXISTS (
125
125
  '-A' // Unaligned output
126
126
  ].join(' ');
127
127
 
128
- console.log(chalk.gray(' - Executando queries de configurações via Docker...'));
128
+ console.log(chalk.white(' - Executando queries de configurações via Docker...'));
129
129
  const output = execSync(dockerCmd, { stdio: 'pipe', encoding: 'utf8' });
130
130
 
131
131
  // Processar output e criar JSON estruturado
@@ -7,7 +7,7 @@ const { writeJson } = require('../../../utils/fsx');
7
7
  */
8
8
  module.exports = async ({ projectId, accessToken, backupDir }) => {
9
9
  try {
10
- console.log(chalk.gray(' - Exportando configurações de Auth via Management API...'));
10
+ console.log(chalk.white(' - Exportando configurações de Auth via Management API...'));
11
11
 
12
12
  // Usar fetch direto para Management API com Personal Access Token
13
13
  const authResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/config/auth`, {
@@ -8,7 +8,7 @@ const { captureRealtimeSettings } = require('../../../utils/realtime-settings');
8
8
  */
9
9
  module.exports = async ({ projectId, backupDir, options }) => {
10
10
  try {
11
- console.log(chalk.gray(' - Capturando Realtime Settings interativamente...'));
11
+ console.log(chalk.white(' - Capturando Realtime Settings interativamente...'));
12
12
 
13
13
  const result = await captureRealtimeSettings(projectId, backupDir, options?.skipRealtime);
14
14
 
@@ -10,7 +10,7 @@ module.exports = async ({ projectId, accessToken, backupDir }) => {
10
10
  const storageDir = path.join(backupDir, 'storage');
11
11
  await ensureDir(storageDir);
12
12
 
13
- console.log(chalk.gray(' - Listando buckets de Storage via Management API...'));
13
+ console.log(chalk.white(' - Listando buckets de Storage via Management API...'));
14
14
 
15
15
  // Usar fetch direto para Management API com Personal Access Token
16
16
  const storageResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/storage/buckets`, {
@@ -28,20 +28,20 @@ module.exports = async ({ projectId, accessToken, backupDir }) => {
28
28
  const buckets = await storageResponse.json();
29
29
 
30
30
  if (!buckets || buckets.length === 0) {
31
- console.log(chalk.gray(' - Nenhum bucket encontrado'));
31
+ console.log(chalk.white(' - Nenhum bucket encontrado'));
32
32
  await writeJson(path.join(storageDir, 'README.md'), {
33
33
  message: 'Nenhum bucket de Storage encontrado neste projeto'
34
34
  });
35
35
  return { success: true, buckets: [] };
36
36
  }
37
37
 
38
- console.log(chalk.gray(` - Encontrados ${buckets.length} buckets`));
38
+ console.log(chalk.white(` - Encontrados ${buckets.length} buckets`));
39
39
 
40
40
  const processedBuckets = [];
41
41
 
42
42
  for (const bucket of buckets || []) {
43
43
  try {
44
- console.log(chalk.gray(` - Processando bucket: ${bucket.name}`));
44
+ console.log(chalk.white(` - Processando bucket: ${bucket.name}`));
45
45
 
46
46
  // Listar objetos do bucket via Management API com Personal Access Token
47
47
  const objectsResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/storage/buckets/${bucket.name}/objects`, {
@@ -11,7 +11,7 @@ const execAsync = promisify(exec);
11
11
  */
12
12
  module.exports = async ({ databaseUrl, backupDir, accessToken }) => {
13
13
  try {
14
- console.log(chalk.gray(' - Exportando Custom Roles via Docker...'));
14
+ console.log(chalk.white(' - Exportando Custom Roles via Docker...'));
15
15
 
16
16
  const customRolesFile = path.join(backupDir, 'custom-roles.sql');
17
17
 
@@ -45,18 +45,18 @@ module.exports = async (context) => {
45
45
  if (shouldCleanAfter) {
46
46
  // Limpar antes se o usuário escolheu limpar após (garante ambiente limpo)
47
47
  await cleanDir(supabaseFunctionsDir);
48
- console.log(chalk.gray(' - Pasta supabase/functions limpa antes do backup.'));
48
+ console.log(chalk.white(' - Pasta supabase/functions limpa antes do backup.'));
49
49
  } else {
50
50
  // Apenas garantir que o diretório existe
51
51
  await fs.mkdir(supabaseFunctionsDir, { recursive: true });
52
52
  if (existingFunctionsBefore.length > 0) {
53
- console.log(chalk.gray(` - Preservando ${existingFunctionsBefore.length} função(ões) existente(s) na pasta supabase/functions.`));
53
+ console.log(chalk.white(` - Preservando ${existingFunctionsBefore.length} função(ões) existente(s) na pasta supabase/functions.`));
54
54
  } else {
55
- console.log(chalk.gray(' - Pasta supabase/functions preparada (será preservada após backup).'));
55
+ console.log(chalk.white(' - Pasta supabase/functions preparada (será preservada após backup).'));
56
56
  }
57
57
  }
58
58
 
59
- console.log(chalk.gray(' - Listando Edge Functions via Management API...'));
59
+ console.log(chalk.white(' - Listando Edge Functions via Management API...'));
60
60
 
61
61
  // Usar fetch direto para Management API com Personal Access Token
62
62
  const functionsResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/functions`, {
@@ -74,14 +74,14 @@ module.exports = async (context) => {
74
74
  const functions = await functionsResponse.json();
75
75
 
76
76
  if (!functions || functions.length === 0) {
77
- console.log(chalk.gray(' - Nenhuma Edge Function encontrada'));
77
+ console.log(chalk.white(' - Nenhuma Edge Function encontrada'));
78
78
  await writeJson(path.join(functionsDir, 'README.md'), {
79
79
  message: 'Nenhuma Edge Function encontrada neste projeto'
80
80
  });
81
81
  return { success: true, reason: 'no_functions', functions: [] };
82
82
  }
83
83
 
84
- console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
84
+ console.log(chalk.white(` - Encontradas ${functions.length} Edge Function(s)`));
85
85
 
86
86
  const downloadedFunctions = [];
87
87
  let successCount = 0;
@@ -91,7 +91,7 @@ module.exports = async (context) => {
91
91
  // Nota: O CLI ignora o cwd e sempre baixa para supabase/functions
92
92
  for (const func of functions) {
93
93
  try {
94
- console.log(chalk.gray(` - Baixando: ${func.name}...`));
94
+ console.log(chalk.white(` - Baixando: ${func.name}...`));
95
95
 
96
96
  // Criar diretório da função NO BACKUP
97
97
  const functionTargetDir = path.join(functionsDir, func.name);
@@ -167,11 +167,11 @@ module.exports = async (context) => {
167
167
  // Nota: shouldCleanAfter já foi definido acima
168
168
  if (shouldCleanAfter) {
169
169
  await cleanDir(supabaseFunctionsDir);
170
- console.log(chalk.gray(' - supabase/functions limpo após o backup.'));
170
+ console.log(chalk.white(' - supabase/functions limpo após o backup.'));
171
171
  } else {
172
172
  // Preservar tudo: tanto as funções que já existiam quanto as que foram baixadas
173
173
  // As funções baixadas não foram removidas individualmente (linha acima foi ajustada)
174
- console.log(chalk.gray(' - supabase/functions preservada conforme solicitado.'));
174
+ console.log(chalk.white(' - supabase/functions preservada conforme solicitado.'));
175
175
  }
176
176
 
177
177
  return {
@@ -14,10 +14,10 @@ module.exports = async (context) => {
14
14
 
15
15
  const fileCount = await copyDirSafe(tempDir, backupTempDir);
16
16
 
17
- console.log(chalk.gray(` - Copiando supabase/.temp → backups/backup-${path.basename(backupDir)}/supabase-temp (${fileCount} arquivos)...`));
17
+ console.log(chalk.white(` - Copiando supabase/.temp → backups/backup-${path.basename(backupDir)}/supabase-temp (${fileCount} arquivos)...`));
18
18
 
19
19
  if (fileCount === 0) {
20
- console.log(chalk.gray(' - Nenhum arquivo encontrado em supabase/.temp'));
20
+ console.log(chalk.white(' - Nenhum arquivo encontrado em supabase/.temp'));
21
21
  } else {
22
22
  console.log(chalk.green(` ✅ ${fileCount} arquivo(s) copiado(s)`));
23
23
  }
@@ -27,7 +27,7 @@ module.exports = async (context) => {
27
27
 
28
28
  if (shouldClean) {
29
29
  await cleanDir(tempDir);
30
- console.log(chalk.gray(' - supabase/.temp apagado.'));
30
+ console.log(chalk.white(' - supabase/.temp apagado.'));
31
31
  }
32
32
 
33
33
  return {
@@ -17,10 +17,10 @@ module.exports = async (context) => {
17
17
  // Limpar migrations local (opcional, mas recomendado para garantir servidor como fonte da verdade)
18
18
  const migrationsDir = path.join(process.cwd(), 'supabase', 'migrations');
19
19
  await cleanDir(migrationsDir);
20
- console.log(chalk.gray(' - Limpando supabase/migrations...'));
20
+ console.log(chalk.white(' - Limpando supabase/migrations...'));
21
21
 
22
22
  // Baixar todas as migrations do servidor usando migration fetch
23
- console.log(chalk.gray(' - Baixando todas as migrations do servidor usando migration fetch...'));
23
+ console.log(chalk.white(' - Baixando todas as migrations do servidor usando migration fetch...'));
24
24
 
25
25
  const env = {
26
26
  ...process.env,
@@ -43,12 +43,12 @@ module.exports = async (context) => {
43
43
 
44
44
  // Contar arquivos baixados
45
45
  const fileCount = await countFiles(migrationsDir);
46
- console.log(chalk.gray(` - Arquivos baixados: ${fileCount} migrations`));
46
+ console.log(chalk.white(` - Arquivos baixados: ${fileCount} migrations`));
47
47
 
48
48
  // Copiar migrations para o backup
49
49
  const backupMigrationsDir = path.join(backupDir, 'migrations');
50
50
  const copiedCount = await copyDirSafe(migrationsDir, backupMigrationsDir);
51
- console.log(chalk.gray(` - Copiando supabase/migrations → backups/backup-${path.basename(backupDir)}/migrations (${copiedCount} arquivos)...`));
51
+ console.log(chalk.white(` - Copiando supabase/migrations → backups/backup-${path.basename(backupDir)}/migrations (${copiedCount} arquivos)...`));
52
52
 
53
53
  if (copiedCount > 0) {
54
54
  console.log(chalk.green(` ✅ ${copiedCount} migration(s) copiada(s)`));
@@ -23,6 +23,18 @@ module.exports = async (_options) => {
23
23
  showBetaBanner();
24
24
 
25
25
  try {
26
+ // Termo de uso e aviso de risco
27
+ console.log(chalk.yellow.bold('\n⚠️ TERMO DE USO E AVISO DE RISCO\n'));
28
+ console.log(chalk.white('Ao prosseguir, você reconhece e concorda que o smoonb é fornecido "NO ESTADO EM QUE SE ENCONTRA" ("AS IS") e "CONFORME DISPONIBILIDADE", sem garantias de qualquer natureza—expressas, implícitas ou legais—incluindo, sem limitação, garantias de comercialização, adequação a um fim específico e não violação, na máxima extensão permitida pela lei aplicável. Operações de backup e restauração envolvem riscos, os ambientes variam amplamente e não é possível prever ou validar todas as configurações dos usuários. Você é o único responsável por validar seu ambiente, manter cópias independentes e verificar os resultados antes de utilizá-los em produção. O smoonb é construído com repositórios públicos, auditáveis e software livre, para auxiliar pessoas a simplificar seus fluxos, sem com isso criar qualquer garantia, promessa de suporte ou compromisso de nível de serviço.\n'));
29
+ console.log(chalk.white('Limitação de responsabilidade (PT-BR) — Na máxima extensão permitida por lei, a Goalmoon, seus contribuidores e licenciadores não serão responsáveis por danos indiretos, incidentais, especiais, consequentes, exemplares ou punitivos (incluindo perda de dados, interrupção de negócios ou lucros cessantes) decorrentes do uso, incapacidade de uso, das operações de backup/restauração realizadas com, ou dos resultados gerados pelo smoonb. Em qualquer hipótese, a responsabilidade total por todas as reivindicações relacionadas ao smoonb não excederá o valor pago por você pelo smoonb nos 12 meses anteriores ao evento. Nada neste aviso exclui ou limita responsabilidades onde tais limites sejam proibidos por lei, incluindo (conforme aplicável) dolo ou culpa grave.\n'));
30
+ console.log(chalk.white('Observação para consumidores no Brasil (PT-BR) — Para consumidores brasileiros, este aviso não afasta direitos irrenunciáveis previstos no Código de Defesa do Consumidor (CDC); qualquer limitação aqui prevista só se aplica nos limites da lei e não impede a indenização obrigatória quando cabível.\n'));
31
+
32
+ const termsAccepted = await confirm('Você aceita os Termos de Uso e o Aviso de Risco de Restauração?', true);
33
+ if (!termsAccepted) {
34
+ console.log(chalk.red('🚫 Operação cancelada pelo usuário.'));
35
+ process.exit(1);
36
+ }
37
+
26
38
  // Executar validação Docker ANTES de tudo
27
39
  await step00DockerValidation();
28
40
 
@@ -13,8 +13,8 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
13
13
 
14
14
  // Verificar se é arquivo .backup.gz (compactado) ou .backup (descompactado)
15
15
  if (fileName.endsWith('.backup.gz')) {
16
- console.log(chalk.gray(' - Arquivo .backup.gz detectado'));
17
- console.log(chalk.gray(' - Extraindo arquivo .gz...'));
16
+ console.log(chalk.white(' - Arquivo .backup.gz detectado'));
17
+ console.log(chalk.white(' - Extraindo arquivo .gz...'));
18
18
 
19
19
  const unzipCmd = [
20
20
  'docker run --rm',
@@ -24,10 +24,10 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
24
24
 
25
25
  execSync(unzipCmd, { stdio: 'pipe' });
26
26
  uncompressedFile = fileName.replace('.gz', '');
27
- console.log(chalk.gray(' - Arquivo descompactado: ' + uncompressedFile));
27
+ console.log(chalk.white(' - Arquivo descompactado: ' + uncompressedFile));
28
28
  } else if (fileName.endsWith('.backup')) {
29
- console.log(chalk.gray(' - Arquivo .backup detectado (já descompactado)'));
30
- console.log(chalk.gray(' - Prosseguindo com restauração direta'));
29
+ console.log(chalk.white(' - Arquivo .backup detectado (já descompactado)'));
30
+ console.log(chalk.white(' - Prosseguindo com restauração direta'));
31
31
  } else {
32
32
  throw new Error(`Formato de arquivo inválido. Esperado .backup.gz ou .backup, recebido: ${fileName}`);
33
33
  }
@@ -32,7 +32,7 @@ module.exports = async ({ backupPath, targetProject }) => {
32
32
  return { success: false, functions_count: 0, success_count: 0 };
33
33
  }
34
34
 
35
- console.log(chalk.gray(` - Encontradas ${functions.length} Edge Function(s)`));
35
+ console.log(chalk.white(` - Encontradas ${functions.length} Edge Function(s)`));
36
36
 
37
37
  // COPIAR Edge Functions de backups/backup-XXX/edge-functions para supabase/functions
38
38
  const supabaseFunctionsDir = path.join(process.cwd(), 'supabase', 'functions');
@@ -54,13 +54,13 @@ module.exports = async ({ backupPath, targetProject }) => {
54
54
  const backupFuncPath = path.join(edgeFunctionsDir, funcName);
55
55
  const targetFuncPath = path.join(supabaseFunctionsDir, funcName);
56
56
 
57
- console.log(chalk.gray(` - Copiando ${funcName} para supabase/functions...`));
57
+ console.log(chalk.white(` - Copiando ${funcName} para supabase/functions...`));
58
58
 
59
59
  // Copiar recursivamente
60
60
  await copyDirectoryRecursive(backupFuncPath, targetFuncPath);
61
61
  }
62
62
 
63
- console.log(chalk.gray(` - Linkando com projeto ${targetProject.targetProjectId}...`));
63
+ console.log(chalk.white(` - Linkando com projeto ${targetProject.targetProjectId}...`));
64
64
 
65
65
  // Linkar com o projeto destino
66
66
  try {
@@ -77,7 +77,7 @@ module.exports = async ({ backupPath, targetProject }) => {
77
77
  // Deploy das Edge Functions
78
78
  let successCount = 0;
79
79
  for (const funcName of functions) {
80
- console.log(chalk.gray(` - Deployando ${funcName}...`));
80
+ console.log(chalk.white(` - Deployando ${funcName}...`));
81
81
 
82
82
  try {
83
83
  execSync(`supabase functions deploy ${funcName}`, {
@@ -25,11 +25,11 @@ module.exports = async ({ backupPath, targetProject }) => {
25
25
 
26
26
  if (authSettings.settings?.auth_url_config) {
27
27
  Object.entries(authSettings.settings.auth_url_config).forEach(([key, value]) => {
28
- console.log(chalk.gray(` - ${key}: ${value}`));
28
+ console.log(chalk.white(` - ${key}: ${value}`));
29
29
  });
30
30
  } else if (authSettings.auth_url_config) {
31
31
  Object.entries(authSettings.auth_url_config).forEach(([key, value]) => {
32
- console.log(chalk.gray(` - ${key}: ${value}`));
32
+ console.log(chalk.white(` - ${key}: ${value}`));
33
33
  });
34
34
  }
35
35
 
@@ -26,23 +26,23 @@ module.exports = async ({ backupPath }) => {
26
26
  const buckets = manifest?.components?.storage?.buckets || [];
27
27
 
28
28
  if (buckets.length === 0) {
29
- console.log(chalk.gray(' ℹ️ Nenhum bucket para restaurar'));
29
+ console.log(chalk.white(' ℹ️ Nenhum bucket para restaurar'));
30
30
  return { success: false, buckets_count: 0 };
31
31
  }
32
32
 
33
33
  console.log(chalk.green(`\n ✅ ${buckets.length} bucket(s) encontrado(s) no backup`));
34
34
  buckets.forEach(bucket => {
35
- console.log(chalk.gray(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
35
+ console.log(chalk.white(` - ${bucket.name} (${bucket.public ? 'público' : 'privado'})`));
36
36
  });
37
37
 
38
38
  const colabUrl = 'https://colab.research.google.com/github/PLyn/supabase-storage-migrate/blob/main/Supabase_Storage_migration.ipynb';
39
39
 
40
40
  console.log(chalk.yellow('\n ⚠️ Migração de objetos de Storage requer processo manual'));
41
41
  console.log(chalk.cyan(` ℹ️ Use o script do Google Colab: ${colabUrl}`));
42
- console.log(chalk.gray('\n 📋 Instruções:'));
43
- console.log(chalk.gray(' 1. Execute o script no Google Colab'));
44
- console.log(chalk.gray(' 2. Configure as credenciais dos projetos (origem e destino)'));
45
- console.log(chalk.gray(' 3. Execute a migração'));
42
+ console.log(chalk.white('\n 📋 Instruções:'));
43
+ console.log(chalk.white(' 1. Execute o script no Google Colab'));
44
+ console.log(chalk.white(' 2. Configure as credenciais dos projetos (origem e destino)'));
45
+ console.log(chalk.white(' 3. Execute a migração'));
46
46
 
47
47
  await inquirer.prompt([{
48
48
  type: 'input',
@@ -22,11 +22,11 @@ module.exports = async ({ backupPath, targetProject }) => {
22
22
  const extensions = dbSettings.extensions || [];
23
23
 
24
24
  if (extensions.length > 0) {
25
- console.log(chalk.gray(` - Habilitando ${extensions.length} extension(s)...`));
25
+ console.log(chalk.white(` - Habilitando ${extensions.length} extension(s)...`));
26
26
 
27
27
  for (const ext of extensions) {
28
28
  const extName = typeof ext === 'string' ? ext : ext.name;
29
- console.log(chalk.gray(` - ${extName}`));
29
+ console.log(chalk.white(` - ${extName}`));
30
30
 
31
31
  const sqlCommand = `CREATE EXTENSION IF NOT EXISTS ${extName};`;
32
32
 
@@ -25,9 +25,9 @@ module.exports = async ({ backupPath, targetProject }) => {
25
25
 
26
26
  if (realtimeSettings.realtime_settings?.settings) {
27
27
  Object.values(realtimeSettings.realtime_settings.settings).forEach((setting) => {
28
- console.log(chalk.gray(` - ${setting.label}: ${setting.value}`));
28
+ console.log(chalk.white(` - ${setting.label}: ${setting.value}`));
29
29
  if (setting.description) {
30
- console.log(chalk.gray(` ${setting.description}`));
30
+ console.log(chalk.white(` ${setting.description}`));
31
31
  }
32
32
  });
33
33
  }
package/src/index.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const chalk = require('chalk');
9
+ const path = require('path');
9
10
  const { showBetaBanner } = require('./utils/banner');
10
11
 
11
12
  // Exportar comandos
@@ -62,50 +63,24 @@ function showQuickHelp() {
62
63
  🚀 COMANDOS PRINCIPAIS:
63
64
 
64
65
  📊 Backup completo:
65
- npx smoonb backup # Usa configuração do .smoonbrc
66
+ npx smoonb backup # Backup completo interativo usando Docker
66
67
 
67
68
  🔄 Restauração completa:
68
- npx smoonb restore --backup-dir <dir> # Restaura backup usando psql
69
-
70
- ⚡ Edge Functions:
71
- npx smoonb functions list
72
- npx smoonb functions push
69
+ npx smoonb restore # Restauração interativa usando psql (Docker)
73
70
 
74
71
  🔍 Verificação pós-restore:
75
72
  npx smoonb check # Verifica integridade do projeto
76
73
 
77
- ⚙️ Configuração:
78
- npx smoonb config --init # Criar arquivo de configuração
79
- npx smoonb config --show # Mostrar configuração atual
80
-
81
- 📋 CONFIGURAÇÃO AUTOMÁTICA:
82
- npx smoonb config --init # Cria .smoonbrc com projectId, URLs, etc.
83
- # Edite o arquivo com suas credenciais Supabase
84
- npx smoonb backup # Funciona automaticamente!
85
-
86
- 📝 EXEMPLO DE CONFIGURAÇÃO (.smoonbrc):
87
- {
88
- "supabase": {
89
- "projectId": "abc123def456",
90
- "url": "https://abc123def456.supabase.co",
91
- "serviceKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkXVCJ9...",
92
- "anonKey": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkXVCJ9...",
93
- "databaseUrl": "postgresql://postgres:[senha]@db.abc123def456.supabase.co:5432/postgres",
94
- "accessToken": "sbp_1234567890abcdef1234567890abcdef"
95
- }
96
- }
74
+ 📋 CONFIGURAÇÃO:
75
+ Configure o arquivo .env.local na raiz do projeto com suas credenciais Supabase.
76
+ O smoonb irá mapear as variáveis interativamente na primeira execução.
97
77
 
98
78
  🔑 PERSONAL ACCESS TOKEN (OBRIGATÓRIO):
99
79
  Para Management API (Edge Functions, Auth Settings, Storage):
100
80
  1. Acesse: https://supabase.com/dashboard/account/tokens
101
81
  2. Clique em "Generate new token"
102
82
  3. Copie o token (formato: sbp_...)
103
- 4. Adicione ao .smoonbrc como "accessToken"
104
-
105
- 🔧 COMO CONFIGURAR:
106
- 1. npx smoonb config --init
107
- 2. Edite .smoonbrc com suas credenciais
108
- 3. npx smoonb backup (funciona automaticamente!)
83
+ 4. Adicione ao .env.local como SUPABASE_ACCESS_TOKEN
109
84
  `));
110
85
  }
111
86
 
@@ -195,7 +170,7 @@ function checkCurrentConfig() {
195
170
 
196
171
  if (config) {
197
172
  console.log(chalk.green('✅ Arquivo de configuração encontrado'));
198
- console.log(chalk.gray(` - Localização: ${require('os').homedir()}/.smoonbrc`));
173
+ console.log(chalk.gray(` - Localização: ${path.join(process.cwd(), '.env.local')}`));
199
174
 
200
175
  if (config.supabase?.url) {
201
176
  console.log(chalk.gray(` - Supabase URL: ${config.supabase.url}`));
@@ -210,7 +185,7 @@ function checkCurrentConfig() {
210
185
  }
211
186
  } else {
212
187
  console.log(chalk.yellow('⚠️ Arquivo de configuração não encontrado'));
213
- console.log(chalk.gray(' - Use: smoonb config --init'));
188
+ console.log(chalk.gray(' - Configure o arquivo .env.local na raiz do projeto'));
214
189
  }
215
190
 
216
191
  if (hasCredentials) {
@@ -1,32 +1,28 @@
1
1
  const chalk = require('chalk');
2
2
 
3
3
  /**
4
- * Banner da versão experimental
4
+ * Banner principal do smoonb
5
5
  */
6
6
  function showBetaBanner() {
7
- console.log(chalk.red.bold(`
8
- ╔══════════════════════════════════════════════════════════════╗
9
- ║ 🚀 smoonb ║
10
- ║ ║
11
- ║ ⚠️ EXPERIMENTAL VERSION - NÃO TESTADA! ║
12
- ║ ║
13
- 🚨 AVISO: Este software NUNCA foi testado em produção! ║
14
- ⚠️ USE POR SUA CONTA E RISCO - Pode causar perda de dados ║
15
- NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados ║
16
- ║ ║
17
- A primeira ferramenta CLI completa para backup e migração ║
18
- de projetos Supabase. Resolve o problema de backup ║
19
- incompleto das ferramentas existentes.
20
- ║ ║
21
- ✅ Database PostgreSQL + Edge Functions + Auth Settings ║
22
- Storage Objects + Realtime Settings + Metadados ║
23
- ║ ║
24
- 🏢 Desenvolvido por: Goalmoon Tecnologia LTDA ║
25
- 🌐 Website: https://goalmoon.com
26
- ║ 📖 Documentação: https://github.com/almmello/smoonb ║
27
- ║ 🐛 Issues: https://github.com/almmello/smoonb/issues ║
28
- ╚══════════════════════════════════════════════════════════════╝
29
- `));
7
+ console.log(chalk.cyan.bold('\n🚀 Supa Moonbase (smoonb)\n'));
8
+ console.log(chalk.white('A primeira ferramenta CLI completa para backup e migração de projetos Supabase.'));
9
+ console.log(chalk.white('Resolve o problema de backup incompleto das ferramentas existentes.\n'));
10
+
11
+ console.log(chalk.cyan.bold('📦 Componentes de Backup:\n'));
12
+ console.log(chalk.white(' ✅ Database PostgreSQL (pg_dumpall + SQL separados)'));
13
+ console.log(chalk.white(' ✅ Database Extensions and Settings'));
14
+ console.log(chalk.white(' Custom Roles'));
15
+ console.log(chalk.white(' ✅ Edge Functions'));
16
+ console.log(chalk.white(' ✅ Auth Settings'));
17
+ console.log(chalk.white(' ✅ Storage Buckets'));
18
+ console.log(chalk.white(' ✅ Realtime Settings'));
19
+ console.log(chalk.white(' ✅ Supabase .temp'));
20
+ console.log(chalk.white(' ✅ Migrations\n'));
21
+
22
+ console.log(chalk.white('🏢 Desenvolvido por: Goalmoon Tecnologia LTDA'));
23
+ console.log(chalk.cyan('🌐 Website: https://smoonb.com'));
24
+ console.log(chalk.gray('📖 Documentação: https://github.com/almmello/smoonb'));
25
+ console.log(chalk.gray('🐛 Issues: https://github.com/almmello/smoonb/issues\n'));
30
26
  }
31
27
 
32
28
  module.exports = { showBetaBanner };
@@ -35,7 +35,7 @@ async function captureRealtimeSettings(projectId, backupDir, skipInteractive = f
35
35
  console.log('\n🔧 Configurações de Realtime Settings');
36
36
  console.log('═'.repeat(50));
37
37
  console.log(`📱 Acesse: ${dashboardUrl}`);
38
- console.log('📝 Anote os valores dos 4 parâmetros abaixo:\n');
38
+ console.log('📝 Anote os valores dos 7 parâmetros abaixo:\n');
39
39
 
40
40
  const settings = await captureSettingsInteractively(projectId, previousSettings);
41
41
 
@@ -126,38 +126,61 @@ async function captureSettingsInteractively(projectId, previousSettings) {
126
126
 
127
127
  // Valores padrão baseados na imagem ou configurações anteriores
128
128
  const defaults = previousSettings?.realtime_settings?.settings || {
129
+ enable_realtime_service: { value: true },
129
130
  allow_public_access: { value: true },
130
131
  database_connection_pool_size: { value: 2 },
131
132
  max_concurrent_clients: { value: 200 },
132
- max_events_per_second: { value: 100 }
133
+ max_events_per_second: { value: 100 },
134
+ max_presence_events_per_second: { value: 100 },
135
+ max_payload_size_kb: { value: 100 }
133
136
  };
134
137
 
138
+ const enableRealtime = await askQuestion(
139
+ '1. Enable Realtime service (true/false):',
140
+ defaults.enable_realtime_service?.value ?? true
141
+ );
142
+
135
143
  const allowPublicAccess = await askQuestion(
136
- '1. Allow public access (true/false):',
144
+ '2. Allow public access (true/false):',
137
145
  defaults.allow_public_access.value
138
146
  );
139
147
 
140
148
  const poolSize = await askQuestion(
141
- '2. Database connection pool size:',
149
+ '3. Database connection pool size:',
142
150
  defaults.database_connection_pool_size.value
143
151
  );
144
152
 
145
153
  const maxClients = await askQuestion(
146
- '3. Max concurrent clients:',
154
+ '4. Max concurrent clients:',
147
155
  defaults.max_concurrent_clients.value
148
156
  );
149
157
 
150
158
  const maxEvents = await askQuestion(
151
- '4. Max events per second:',
159
+ '5. Max events per second:',
152
160
  defaults.max_events_per_second.value
153
161
  );
154
162
 
163
+ const maxPresenceEvents = await askQuestion(
164
+ '6. Max presence events per second:',
165
+ defaults.max_presence_events_per_second?.value ?? 100
166
+ );
167
+
168
+ const maxPayloadSize = await askQuestion(
169
+ '7. Max payload size in KB:',
170
+ defaults.max_payload_size_kb?.value ?? 100
171
+ );
172
+
155
173
  const settings = {
156
174
  realtime_settings: {
157
175
  note: "Configurações de Realtime Settings capturadas interativamente",
158
176
  dashboard_url: `https://supabase.com/dashboard/project/${projectId}/realtime/settings`,
159
177
  captured_at: new Date().toISOString(),
160
178
  settings: {
179
+ enable_realtime_service: {
180
+ label: "Enable Realtime service",
181
+ description: "If disabled, no clients will be able to connect and new connections will be rejected",
182
+ value: enableRealtime === 'true' || enableRealtime === true
183
+ },
161
184
  allow_public_access: {
162
185
  label: "Allow public access",
163
186
  description: "If disabled, only private channels will be allowed",
@@ -177,17 +200,30 @@ async function captureSettingsInteractively(projectId, previousSettings) {
177
200
  label: "Max events per second",
178
201
  description: "Sets maximum number of events per second that can be sent to your Realtime service",
179
202
  value: parseInt(maxEvents)
203
+ },
204
+ max_presence_events_per_second: {
205
+ label: "Max presence events per second",
206
+ description: "Sets maximum number of presence events per second",
207
+ value: parseInt(maxPresenceEvents)
208
+ },
209
+ max_payload_size_kb: {
210
+ label: "Max payload size in KB",
211
+ description: "Sets maximum payload size in KB",
212
+ value: parseInt(maxPayloadSize)
180
213
  }
181
214
  },
182
215
  restore_instructions: {
183
216
  url: `https://supabase.com/dashboard/project/${projectId}/realtime/settings`,
184
217
  steps: [
185
218
  "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'"
219
+ "2. Configure 'Enable Realtime service' conforme o valor em settings.enable_realtime_service.value",
220
+ "3. Configure 'Allow public access' conforme o valor em settings.allow_public_access.value",
221
+ "4. Configure 'Database connection pool size' conforme o valor em settings.database_connection_pool_size.value",
222
+ "5. Configure 'Max concurrent clients' conforme o valor em settings.max_concurrent_clients.value",
223
+ "6. Configure 'Max events per second' conforme o valor em settings.max_events_per_second.value",
224
+ "7. Configure 'Max presence events per second' conforme o valor em settings.max_presence_events_per_second.value",
225
+ "8. Configure 'Max payload size in KB' conforme o valor em settings.max_payload_size_kb.value",
226
+ "9. Clique em 'Save changes'"
191
227
  ]
192
228
  }
193
229
  }
@@ -28,7 +28,7 @@ async function ensureCleanLink(projectRef, accessToken, dbPassword) {
28
28
  const tempDir = path.join(process.cwd(), 'supabase', '.temp');
29
29
 
30
30
  // Remover supabase/.temp completamente
31
- console.log(chalk.gray(` - Zerando vínculo e linkando projeto: ${projectRef}...`));
31
+ console.log(chalk.white(` - Zerando vínculo e linkando projeto: ${projectRef}...`));
32
32
 
33
33
  try {
34
34
  await fs.rm(tempDir, { recursive: true, force: true });
@@ -66,7 +66,7 @@ async function ensureCleanLink(projectRef, accessToken, dbPassword) {
66
66
  );
67
67
  }
68
68
 
69
- console.log(chalk.gray(` - Validação: linked-ref = ${linkedRefTrimmed} (esperado = ${projectRef})`));
69
+ console.log(chalk.white(` - Validação: linked-ref = ${linkedRefTrimmed} (esperado = ${projectRef})`));
70
70
  } catch (error) {
71
71
  if (error.message.includes('Validação falhou')) {
72
72
  throw error;