smoonb 0.0.8 → 0.0.9

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/README.md CHANGED
@@ -107,16 +107,18 @@ backups/backup-2024-01-15T10-30-45-123Z/
107
107
  └── [código das Edge Functions locais]
108
108
  ```
109
109
 
110
- ### Restauração
110
+ ### Restauração Interativa
111
111
  ```bash
112
- npx smoonb restore --backup-dir backups/backup-2024-01-15T10-30-45-123Z
112
+ npx smoonb restore
113
113
  ```
114
114
 
115
- **Processo:**
116
- 1. Verifica se database está vazia (clean restore)
117
- 2. Executa `roles.sql` (roles e permissões)
118
- 3. Executa `schema.sql` (estrutura das tabelas)
119
- 4. Executa `data.sql` (dados com COPY)
115
+ **Processo interativo:**
116
+ 1. Lista todos os backups disponíveis
117
+ 2. Permite seleção numerada do backup desejado
118
+ 3. Verifica se database está vazia (clean restore)
119
+ 4. Executa `roles.sql` (roles e permissões)
120
+ 5. Executa `schema.sql` (estrutura das tabelas)
121
+ 6. Executa `data.sql` (dados com COPY)
120
122
 
121
123
  ### Verificação Pós-Restore
122
124
  ```bash
@@ -145,7 +147,7 @@ npx smoonb functions push
145
147
  | Comando | Descrição |
146
148
  |---------|-----------|
147
149
  | `npx smoonb backup` | Backup completo usando Supabase CLI |
148
- | `npx smoonb restore` | Restauração usando psql |
150
+ | `npx smoonb restore` | Restauração interativa usando psql |
149
151
  | `npx smoonb check` | Verificação de integridade |
150
152
  | `npx smoonb functions` | Gerenciar Edge Functions |
151
153
  | `npx smoonb config` | Configurar credenciais |
@@ -179,8 +181,8 @@ npx smoonb backup
179
181
  # 3. Configurar .smoonbrc com credenciais do novo projeto
180
182
  npx smoonb config --init
181
183
 
182
- # 4. Restaurar backup
183
- npx smoonb restore --backup-dir backups/backup-YYYY-MM-DDTHH-MM-SS-sssZ
184
+ # 4. Restaurar backup (modo interativo)
185
+ npx smoonb restore
184
186
 
185
187
  # 5. Verificar integridade
186
188
  npx smoonb check
package/bin/smoonb.js CHANGED
@@ -74,8 +74,7 @@ program
74
74
 
75
75
  program
76
76
  .command('restore')
77
- .description('Restaurar backup completo usando psql')
78
- .option('-b, --backup-dir <dir>', 'Diretório do backup a ser restaurado')
77
+ .description('Restaurar backup completo usando psql (modo interativo)')
79
78
  .option('--db-url <url>', 'URL da database de destino (override)')
80
79
  .action(commands.restore);
81
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -26,10 +26,10 @@
26
26
  "node": ">=16.0.0"
27
27
  },
28
28
  "dependencies": {
29
- "commander": "^11.1.0",
30
29
  "@supabase/supabase-js": "^2.38.0",
31
30
  "chalk": "^4.1.2",
32
- "inquirer": "^8.2.6"
31
+ "commander": "^11.1.0",
32
+ "inquirer": "^8.2.7"
33
33
  },
34
34
  "type": "commonjs",
35
35
  "repository": {
@@ -3,7 +3,8 @@ const chalk = require('chalk');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
5
  const { ensureBin, runCommand } = require('../utils/cli');
6
- const { readConfig, validateFor, writeJson } = require('../utils/config');
6
+ const { readConfig, validateFor } = require('../utils/config');
7
+ const { writeJson } = require('../utils/fsx');
7
8
  const { IntrospectionService } = require('../services/introspect');
8
9
  const { showBetaBanner } = require('../index');
9
10
 
@@ -2,13 +2,13 @@ const { Command } = require('commander');
2
2
  const chalk = require('chalk');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
+ const inquirer = require('inquirer');
5
6
  const { ensureBin, runCommand } = require('../utils/cli');
6
7
  const { readConfig, validateFor } = require('../utils/config');
7
8
  const { showBetaBanner } = require('../index');
8
9
 
9
10
  const restoreCommand = new Command('restore')
10
- .description('Restaurar backup do projeto Supabase usando psql')
11
- .option('-b, --backup-dir <dir>', 'Diretório do backup')
11
+ .description('Restaurar backup do projeto Supabase usando psql (modo interativo)')
12
12
  .option('--db-url <url>', 'URL da database de destino (override)')
13
13
  .action(async (options) => {
14
14
  showBetaBanner();
@@ -35,31 +35,21 @@ const restoreCommand = new Command('restore')
35
35
  process.exit(1);
36
36
  }
37
37
 
38
- // Resolver diretório do backup
39
- let backupDir = options.backupDir;
40
- if (!backupDir) {
41
- // Procurar backup mais recente
42
- const backupsDir = config.backup.outputDir || './backups';
43
- if (fs.existsSync(backupsDir)) {
44
- const backups = fs.readdirSync(backupsDir)
45
- .filter(dir => dir.startsWith('backup-'))
46
- .sort()
47
- .reverse();
48
-
49
- if (backups.length > 0) {
50
- backupDir = path.join(backupsDir, backups[0]);
51
- console.log(chalk.blue(`📁 Usando backup mais recente: ${backups[0]}`));
52
- }
53
- }
54
- }
38
+ console.log(chalk.blue(`🔍 Procurando backups em: ${config.backup.outputDir || './backups'}`));
55
39
 
56
- if (!backupDir || !fs.existsSync(backupDir)) {
57
- console.error(chalk.red('❌ Diretório de backup não encontrado'));
58
- console.log(chalk.yellow('💡 Use: npx smoonb restore --backup-dir <caminho>'));
40
+ // Listar backups disponíveis
41
+ const backups = await listAvailableBackups(config.backup.outputDir || './backups');
42
+
43
+ if (backups.length === 0) {
44
+ console.error(chalk.red('❌ Nenhum backup encontrado'));
45
+ console.log(chalk.yellow('💡 Execute primeiro: npx smoonb backup'));
59
46
  process.exit(1);
60
47
  }
61
48
 
62
- console.log(chalk.blue(`🚀 Iniciando restauração do backup: ${path.basename(backupDir)}`));
49
+ // Seleção interativa do backup
50
+ const selectedBackup = await selectBackup(backups);
51
+
52
+ console.log(chalk.blue(`🚀 Iniciando restauração do backup: ${selectedBackup.name}`));
63
53
  console.log(chalk.blue(`🎯 Database destino: ${databaseUrl.replace(/:[^:]*@/, ':***@')}`));
64
54
 
65
55
  // Verificar se é clean restore
@@ -68,13 +58,11 @@ const restoreCommand = new Command('restore')
68
58
  }
69
59
 
70
60
  // Executar restauração
71
- await performRestore(backupDir, databaseUrl);
61
+ await performRestore(selectedBackup.path, databaseUrl);
72
62
 
73
63
  // Verificação pós-restore
74
64
  if (config.restore.verifyAfterRestore) {
75
65
  console.log(chalk.blue('\n🔍 Executando verificação pós-restore...'));
76
- // TODO: Implementar verificação automática
77
- console.log(chalk.yellow('⚠️ Verificação automática não implementada ainda'));
78
66
  console.log(chalk.yellow('💡 Execute manualmente: npx smoonb check'));
79
67
  }
80
68
 
@@ -86,6 +74,112 @@ const restoreCommand = new Command('restore')
86
74
  }
87
75
  });
88
76
 
77
+ // Listar backups disponíveis
78
+ async function listAvailableBackups(backupsDir) {
79
+ if (!fs.existsSync(backupsDir)) {
80
+ return [];
81
+ }
82
+
83
+ const items = fs.readdirSync(backupsDir, { withFileTypes: true });
84
+ const backups = [];
85
+
86
+ for (const item of items) {
87
+ if (item.isDirectory() && item.name.startsWith('backup-')) {
88
+ const backupPath = path.join(backupsDir, item.name);
89
+ const manifestPath = path.join(backupPath, 'backup-manifest.json');
90
+
91
+ let manifest = null;
92
+ if (fs.existsSync(manifestPath)) {
93
+ try {
94
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
95
+ } catch (error) {
96
+ console.warn(chalk.yellow(`⚠️ Erro ao ler manifesto: ${item.name}`));
97
+ }
98
+ }
99
+
100
+ const stats = fs.statSync(backupPath);
101
+
102
+ backups.push({
103
+ name: item.name,
104
+ path: backupPath,
105
+ created: manifest?.created_at || stats.birthtime.toISOString(),
106
+ projectId: manifest?.project_id || 'Desconhecido',
107
+ size: getDirectorySize(backupPath),
108
+ manifest: manifest
109
+ });
110
+ }
111
+ }
112
+
113
+ // Ordenar por data de criação (mais recente primeiro)
114
+ return backups.sort((a, b) => new Date(b.created) - new Date(a.created));
115
+ }
116
+
117
+ // Calcular tamanho do diretório
118
+ function getDirectorySize(dirPath) {
119
+ let totalSize = 0;
120
+
121
+ function calculateSize(itemPath) {
122
+ const stats = fs.statSync(itemPath);
123
+
124
+ if (stats.isDirectory()) {
125
+ const items = fs.readdirSync(itemPath);
126
+ for (const item of items) {
127
+ calculateSize(path.join(itemPath, item));
128
+ }
129
+ } else {
130
+ totalSize += stats.size;
131
+ }
132
+ }
133
+
134
+ try {
135
+ calculateSize(dirPath);
136
+ } catch (error) {
137
+ // Ignorar erros de acesso
138
+ }
139
+
140
+ return formatBytes(totalSize);
141
+ }
142
+
143
+ // Formatar bytes em formato legível
144
+ function formatBytes(bytes) {
145
+ if (bytes === 0) return '0 Bytes';
146
+
147
+ const k = 1024;
148
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
149
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
150
+
151
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
152
+ }
153
+
154
+ // Seleção interativa do backup
155
+ async function selectBackup(backups) {
156
+ console.log(chalk.blue('\n📋 Backups disponíveis:'));
157
+ console.log(chalk.blue('═'.repeat(80)));
158
+
159
+ const choices = backups.map((backup, index) => {
160
+ const date = new Date(backup.created).toLocaleString('pt-BR');
161
+ const projectInfo = backup.projectId !== 'Desconhecido' ? ` (${backup.projectId})` : '';
162
+
163
+ return {
164
+ name: `${index + 1}. ${backup.name}${projectInfo}\n 📅 ${date} | 📦 ${backup.size}`,
165
+ value: backup,
166
+ short: backup.name
167
+ };
168
+ });
169
+
170
+ const { selectedBackup } = await inquirer.prompt([
171
+ {
172
+ type: 'list',
173
+ name: 'selectedBackup',
174
+ message: 'Selecione o backup para restaurar:',
175
+ choices: choices,
176
+ pageSize: 10
177
+ }
178
+ ]);
179
+
180
+ return selectedBackup;
181
+ }
182
+
89
183
  // Verificar se é possível fazer clean restore
90
184
  async function checkCleanRestore(databaseUrl) {
91
185
  try {
@@ -1,5 +1,5 @@
1
1
  const { createClient } = require('@supabase/supabase-js');
2
- const { runCommand } = require('./cli');
2
+ const { runCommand } = require('../utils/cli');
3
3
 
4
4
  /**
5
5
  * Serviço de introspecção do banco de dados Supabase