smoonb 0.0.11 → 0.0.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,5 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
  const path = require('path');
3
+ const fs = require('fs');
3
4
  const { ensureBin, runCommand } = require('../utils/cli');
4
5
  const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
5
6
  const { sha256 } = require('../utils/hash');
@@ -12,13 +13,12 @@ module.exports = async (options) => {
12
13
  showBetaBanner();
13
14
 
14
15
  try {
15
- // Verificar se Supabase CLI está disponível
16
- const supabasePath = await ensureBin('supabase');
17
- if (!supabasePath) {
18
- console.error(chalk.red('❌ Supabase CLI não encontrado'));
19
- console.log(chalk.yellow('💡 Instale o Supabase CLI:'));
20
- console.log(chalk.yellow(' npm install -g supabase'));
21
- console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
16
+ // Verificar se pg_dump está disponível
17
+ const pgDumpPath = await findPgDumpPath();
18
+ if (!pgDumpPath) {
19
+ console.error(chalk.red('❌ pg_dump não encontrado'));
20
+ console.log(chalk.yellow('💡 Instale PostgreSQL:'));
21
+ console.log(chalk.yellow(' https://www.postgresql.org/download/'));
22
22
  process.exit(1);
23
23
  }
24
24
 
@@ -43,10 +43,20 @@ module.exports = async (options) => {
43
43
 
44
44
  console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
45
45
  console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
46
+ console.log(chalk.gray(`🔧 Usando pg_dump: ${pgDumpPath}`));
46
47
 
47
- // 1. Backup da Database usando Supabase CLI
48
+ // 1. Backup da Database usando pg_dump/pg_dumpall
48
49
  console.log(chalk.blue('\n📊 1/3 - Backup da Database PostgreSQL...'));
49
- await backupDatabaseWithSupabaseCLI(databaseUrl, backupDir);
50
+ const dbBackupResult = await backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath);
51
+
52
+ if (!dbBackupResult.success) {
53
+ console.error(chalk.red('❌ Falha crítica no backup da database'));
54
+ console.log(chalk.yellow('💡 Verifique:'));
55
+ console.log(chalk.yellow(' - Se DATABASE_URL está correta'));
56
+ console.log(chalk.yellow(' - Se as credenciais estão corretas'));
57
+ console.log(chalk.yellow(' - Se o banco está acessível'));
58
+ process.exit(1);
59
+ }
50
60
 
51
61
  // 2. Gerar inventário real
52
62
  console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
@@ -57,10 +67,11 @@ module.exports = async (options) => {
57
67
  await backupLocalFunctions(backupDir);
58
68
 
59
69
  // Gerar manifesto do backup
60
- await generateBackupManifest(config, backupDir);
70
+ await generateBackupManifest(config, backupDir, dbBackupResult.files);
61
71
 
62
72
  console.log(chalk.green('\n🎉 Backup completo finalizado!'));
63
73
  console.log(chalk.blue(`📁 Localização: ${backupDir}`));
74
+ console.log(chalk.green(`✅ Database: ${dbBackupResult.files.length} arquivos SQL gerados`));
64
75
 
65
76
  } catch (error) {
66
77
  console.error(chalk.red(`❌ Erro no backup: ${error.message}`));
@@ -68,30 +79,150 @@ module.exports = async (options) => {
68
79
  }
69
80
  };
70
81
 
71
- // Backup da database usando Supabase CLI
72
- async function backupDatabaseWithSupabaseCLI(databaseUrl, backupDir) {
82
+ // Encontrar caminho do pg_dump automaticamente
83
+ async function findPgDumpPath() {
84
+ // Primeiro, tentar encontrar no PATH
85
+ const pgDumpPath = await ensureBin('pg_dump');
86
+ if (pgDumpPath) {
87
+ return pgDumpPath;
88
+ }
89
+
90
+ // No Windows, tentar caminhos comuns
91
+ if (process.platform === 'win32') {
92
+ const possiblePaths = [
93
+ 'C:\\Program Files\\PostgreSQL\\17\\bin\\pg_dump.exe',
94
+ 'C:\\Program Files\\PostgreSQL\\16\\bin\\pg_dump.exe',
95
+ 'C:\\Program Files\\PostgreSQL\\15\\bin\\pg_dump.exe',
96
+ 'C:\\Program Files\\PostgreSQL\\14\\bin\\pg_dump.exe',
97
+ 'C:\\Program Files\\PostgreSQL\\13\\bin\\pg_dump.exe'
98
+ ];
99
+
100
+ for (const pgDumpPath of possiblePaths) {
101
+ if (fs.existsSync(pgDumpPath)) {
102
+ return pgDumpPath;
103
+ }
104
+ }
105
+ }
106
+
107
+ return null;
108
+ }
109
+
110
+ // Backup da database usando pg_dump/pg_dumpall
111
+ async function backupDatabaseWithPgDump(databaseUrl, backupDir, pgDumpPath) {
73
112
  try {
113
+ // Parse da URL da database
114
+ const url = new URL(databaseUrl);
115
+ const host = url.hostname;
116
+ const port = url.port || '5432';
117
+ const username = url.username;
118
+ const password = url.password;
119
+ const database = url.pathname.slice(1);
120
+
121
+ console.log(chalk.gray(` - Host: ${host}:${port}`));
122
+ console.log(chalk.gray(` - Database: ${database}`));
123
+ console.log(chalk.gray(` - Username: ${username}`));
124
+
125
+ const files = [];
126
+ let success = true;
127
+
128
+ // 1. Backup dos roles usando pg_dumpall
74
129
  console.log(chalk.blue(' - Exportando roles...'));
75
- const { stdout: rolesOutput } = await runCommand(
76
- `supabase db dump --db-url "${databaseUrl}" -f roles.sql --role-only`
77
- );
130
+ const rolesFile = path.join(backupDir, 'roles.sql');
131
+ const rolesCommand = `"${pgDumpPath.replace('pg_dump', 'pg_dumpall')}" --host=${host} --port=${port} --username=${username} --roles-only -f "${rolesFile}"`;
78
132
 
133
+ try {
134
+ await runCommand(rolesCommand, {
135
+ env: { ...process.env, PGPASSWORD: password }
136
+ });
137
+
138
+ if (await validateSqlFile(rolesFile)) {
139
+ files.push('roles.sql');
140
+ console.log(chalk.green(' ✅ Roles exportados com sucesso'));
141
+ } else {
142
+ console.log(chalk.yellow(' ⚠️ Arquivo roles.sql está vazio'));
143
+ success = false;
144
+ }
145
+ } catch (error) {
146
+ console.log(chalk.red(` ❌ Erro ao exportar roles: ${error.message}`));
147
+ success = false;
148
+ }
149
+
150
+ // 2. Backup do schema usando pg_dump
79
151
  console.log(chalk.blue(' - Exportando schema...'));
80
- const { stdout: schemaOutput } = await runCommand(
81
- `supabase db dump --db-url "${databaseUrl}" -f schema.sql`
82
- );
152
+ const schemaFile = path.join(backupDir, 'schema.sql');
153
+ const schemaCommand = `"${pgDumpPath}" --host=${host} --port=${port} --username=${username} --schema-only -f "${schemaFile}" ${database}`;
83
154
 
155
+ try {
156
+ await runCommand(schemaCommand, {
157
+ env: { ...process.env, PGPASSWORD: password }
158
+ });
159
+
160
+ if (await validateSqlFile(schemaFile)) {
161
+ files.push('schema.sql');
162
+ console.log(chalk.green(' ✅ Schema exportado com sucesso'));
163
+ } else {
164
+ console.log(chalk.yellow(' ⚠️ Arquivo schema.sql está vazio'));
165
+ success = false;
166
+ }
167
+ } catch (error) {
168
+ console.log(chalk.red(` ❌ Erro ao exportar schema: ${error.message}`));
169
+ success = false;
170
+ }
171
+
172
+ // 3. Backup dos dados usando pg_dump
84
173
  console.log(chalk.blue(' - Exportando dados...'));
85
- const { stdout: dataOutput } = await runCommand(
86
- `supabase db dump --db-url "${databaseUrl}" -f data.sql --use-copy --data-only`
87
- );
174
+ const dataFile = path.join(backupDir, 'data.sql');
175
+ const dataCommand = `"${pgDumpPath}" --host=${host} --port=${port} --username=${username} --data-only --use-copy -f "${dataFile}" ${database}`;
176
+
177
+ try {
178
+ await runCommand(dataCommand, {
179
+ env: { ...process.env, PGPASSWORD: password }
180
+ });
181
+
182
+ if (await validateSqlFile(dataFile)) {
183
+ files.push('data.sql');
184
+ console.log(chalk.green(' ✅ Dados exportados com sucesso'));
185
+ } else {
186
+ console.log(chalk.yellow(' ⚠️ Arquivo data.sql está vazio'));
187
+ success = false;
188
+ }
189
+ } catch (error) {
190
+ console.log(chalk.red(` ❌ Erro ao exportar dados: ${error.message}`));
191
+ success = false;
192
+ }
88
193
 
89
- console.log(chalk.green('✅ Database exportada com sucesso'));
194
+ return { success, files };
90
195
  } catch (error) {
91
196
  throw new Error(`Falha no backup da database: ${error.message}`);
92
197
  }
93
198
  }
94
199
 
200
+ // Validar arquivo SQL (não vazio e com conteúdo válido)
201
+ async function validateSqlFile(filePath) {
202
+ try {
203
+ if (!fs.existsSync(filePath)) {
204
+ return false;
205
+ }
206
+
207
+ const stats = fs.statSync(filePath);
208
+ if (stats.size === 0) {
209
+ return false;
210
+ }
211
+
212
+ const content = fs.readFileSync(filePath, 'utf8');
213
+
214
+ // Verificar se contém conteúdo SQL válido
215
+ const sqlKeywords = ['CREATE', 'INSERT', 'COPY', 'ALTER', 'DROP', 'GRANT', 'REVOKE'];
216
+ const hasValidContent = sqlKeywords.some(keyword =>
217
+ content.toUpperCase().includes(keyword)
218
+ );
219
+
220
+ return hasValidContent;
221
+ } catch (error) {
222
+ return false;
223
+ }
224
+ }
225
+
95
226
  // Gerar inventário completo
96
227
  async function generateInventory(config, backupDir) {
97
228
  try {
@@ -118,7 +249,6 @@ async function backupLocalFunctions(backupDir) {
118
249
  const localFunctionsPath = 'supabase/functions';
119
250
 
120
251
  try {
121
- const fs = require('fs');
122
252
  if (fs.existsSync(localFunctionsPath)) {
123
253
  const functionsBackupDir = path.join(backupDir, 'functions');
124
254
  await copyDir(localFunctionsPath, functionsBackupDir);
@@ -132,7 +262,7 @@ async function backupLocalFunctions(backupDir) {
132
262
  }
133
263
 
134
264
  // Gerar manifesto do backup
135
- async function generateBackupManifest(config, backupDir) {
265
+ async function generateBackupManifest(config, backupDir, sqlFiles) {
136
266
  const manifest = {
137
267
  created_at: new Date().toISOString(),
138
268
  project_id: config.supabase.projectId,
@@ -144,11 +274,14 @@ async function generateBackupManifest(config, backupDir) {
144
274
  data: 'data.sql'
145
275
  },
146
276
  hashes: {},
147
- inventory: {}
277
+ inventory: {},
278
+ validation: {
279
+ sql_files_created: sqlFiles.length,
280
+ sql_files_valid: sqlFiles.length === 3
281
+ }
148
282
  };
149
283
 
150
284
  // Calcular hashes dos arquivos SQL
151
- const fs = require('fs');
152
285
  for (const [type, filename] of Object.entries(manifest.files)) {
153
286
  const filePath = path.join(backupDir, filename);
154
287
  if (fs.existsSync(filePath)) {