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 +22 -0
- package/README.md +84 -48
- package/package.json +1 -1
- package/src/commands/backup/index.js +27 -17
- package/src/commands/backup/steps/00-docker-validation.js +1 -1
- package/src/commands/backup/steps/01-database.js +2 -2
- package/src/commands/backup/steps/02-database-separated.js +4 -4
- package/src/commands/backup/steps/03-database-settings.js +2 -2
- package/src/commands/backup/steps/04-auth-settings.js +1 -1
- package/src/commands/backup/steps/05-realtime-settings.js +1 -1
- package/src/commands/backup/steps/06-storage.js +4 -4
- package/src/commands/backup/steps/07-custom-roles.js +1 -1
- package/src/commands/backup/steps/08-edge-functions.js +9 -9
- package/src/commands/backup/steps/09-supabase-temp.js +3 -3
- package/src/commands/backup/steps/10-migrations.js +4 -4
- package/src/commands/restore/index.js +12 -0
- package/src/commands/restore/steps/03-database.js +5 -5
- package/src/commands/restore/steps/04-edge-functions.js +4 -4
- package/src/commands/restore/steps/05-auth-settings.js +2 -2
- package/src/commands/restore/steps/06-storage.js +6 -6
- package/src/commands/restore/steps/07-database-settings.js +2 -2
- package/src/commands/restore/steps/08-realtime-settings.js +2 -2
- package/src/index.js +9 -34
- package/src/utils/banner.js +20 -24
- package/src/utils/realtime-settings.js +47 -11
- package/src/utils/supabaseLink.js +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
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** (
|
|
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=
|
|
100
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
|
101
|
-
SUPABASE_SERVICE_ROLE_KEY=
|
|
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:[
|
|
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=
|
|
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/
|
|
166
|
-
- 📊 2/
|
|
167
|
-
- 🔧 3/
|
|
168
|
-
- 🔐 4/
|
|
169
|
-
- 🔄 5/
|
|
170
|
-
- 📦 6/
|
|
171
|
-
- 👥 7/
|
|
172
|
-
- ⚡ 8/
|
|
173
|
-
- 📁 9/
|
|
174
|
-
- 📋 10/
|
|
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
|
-
##
|
|
487
|
+
## 💼 Modelo de Acesso e Assinatura
|
|
475
488
|
|
|
476
|
-
|
|
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
|
-
|
|
491
|
+
Até que a validação esteja ativa, a ferramenta pode ser utilizada sem login.
|
|
480
492
|
|
|
481
|
-
|
|
493
|
+
Saiba mais em [Pricing](https://smoonb.com/pricing) e [FAQ Comercial](https://smoonb.com/faq).
|
|
482
494
|
|
|
483
|
-
|
|
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
|
-
##
|
|
499
|
+
## 🔒 Privacidade e LGPD (resumo)
|
|
486
500
|
|
|
487
|
-
|
|
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
|
-
##
|
|
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://
|
|
533
|
+
**Website:** https://smoonb.com
|
|
498
534
|
**GitHub:** https://github.com/almmello/smoonb
|
package/package.json
CHANGED
|
@@ -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
|
-
//
|
|
46
|
-
const
|
|
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 ||
|
|
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') ||
|
|
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.
|
|
152
|
+
console.log(chalk.white(` ✅ Edge Functions: ${flags.includeFunctions ? 'Sim' : 'Não'}`));
|
|
143
153
|
if (flags.includeFunctions) {
|
|
144
|
-
console.log(chalk.
|
|
154
|
+
console.log(chalk.white(` 🗑️ Limpar após backup: ${flags.cleanFunctions ? 'Sim' : 'Não'}`));
|
|
145
155
|
}
|
|
146
|
-
console.log(chalk.
|
|
156
|
+
console.log(chalk.white(` ✅ Supabase .temp: ${flags.includeTemp ? 'Sim' : 'Não'}`));
|
|
147
157
|
if (flags.includeTemp) {
|
|
148
|
-
console.log(chalk.
|
|
158
|
+
console.log(chalk.white(` 🗑️ Apagar após backup: ${flags.cleanTemp ? 'Sim' : 'Não'}`));
|
|
149
159
|
}
|
|
150
|
-
console.log(chalk.
|
|
160
|
+
console.log(chalk.white(` ✅ Migrations: ${flags.includeMigrations ? 'Sim' : 'Não'}`));
|
|
151
161
|
if (flags.includeMigrations) {
|
|
152
|
-
console.log(chalk.
|
|
162
|
+
console.log(chalk.white(` 🗑️ Apagar após backup: ${flags.cleanMigrations ? 'Sim' : 'Não'}`));
|
|
153
163
|
}
|
|
154
|
-
console.log(chalk.
|
|
155
|
-
console.log(chalk.
|
|
156
|
-
console.log(chalk.
|
|
157
|
-
console.log(chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
53
|
+
console.log(chalk.white(` - Preservando ${existingFunctionsBefore.length} função(ões) existente(s) na pasta supabase/functions.`));
|
|
54
54
|
} else {
|
|
55
|
-
console.log(chalk.
|
|
55
|
+
console.log(chalk.white(' - Pasta supabase/functions preparada (será preservada após backup).'));
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
console.log(chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
17
|
-
console.log(chalk.
|
|
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.
|
|
27
|
+
console.log(chalk.white(' - Arquivo descompactado: ' + uncompressedFile));
|
|
28
28
|
} else if (fileName.endsWith('.backup')) {
|
|
29
|
-
console.log(chalk.
|
|
30
|
-
console.log(chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
43
|
-
console.log(chalk.
|
|
44
|
-
console.log(chalk.
|
|
45
|
-
console.log(chalk.
|
|
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.
|
|
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.
|
|
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.
|
|
28
|
+
console.log(chalk.white(` - ${setting.label}: ${setting.value}`));
|
|
29
29
|
if (setting.description) {
|
|
30
|
-
console.log(chalk.
|
|
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 #
|
|
66
|
+
npx smoonb backup # Backup completo interativo usando Docker
|
|
66
67
|
|
|
67
68
|
🔄 Restauração completa:
|
|
68
|
-
npx smoonb restore
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 .
|
|
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: ${
|
|
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(' -
|
|
188
|
+
console.log(chalk.gray(' - Configure o arquivo .env.local na raiz do projeto'));
|
|
214
189
|
}
|
|
215
190
|
|
|
216
191
|
if (hasCredentials) {
|
package/src/utils/banner.js
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Banner
|
|
4
|
+
* Banner principal do smoonb
|
|
5
5
|
*/
|
|
6
6
|
function showBetaBanner() {
|
|
7
|
-
console.log(chalk.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
'
|
|
154
|
+
'4. Max concurrent clients:',
|
|
147
155
|
defaults.max_concurrent_clients.value
|
|
148
156
|
);
|
|
149
157
|
|
|
150
158
|
const maxEvents = await askQuestion(
|
|
151
|
-
'
|
|
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 '
|
|
187
|
-
"3. Configure '
|
|
188
|
-
"4. Configure '
|
|
189
|
-
"5. Configure 'Max
|
|
190
|
-
"6.
|
|
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.
|
|
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.
|
|
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;
|