smoonb 0.0.45 → 0.0.47

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.
Files changed (26) hide show
  1. package/package.json +15 -2
  2. package/src/commands/backup.js +159 -67
  3. package/src/commands/restore.js +83 -7
  4. package/src/interactive/envMapper.js +104 -0
  5. package/src/utils/env.js +106 -0
  6. package/src/utils/envMap.js +25 -0
  7. package/src/utils/supabase.js +2 -2
  8. package/.smoonbrc +0 -29
  9. package/.smoonbrc.example +0 -28
  10. package/backups/backup-2025-10-17T19-18-58-539Z/auth-config.json +0 -7
  11. package/backups/backup-2025-10-17T19-18-58-539Z/backup-manifest.json +0 -19
  12. package/backups/backup-2025-10-17T19-18-58-539Z/functions/README.md +0 -4
  13. package/backups/backup-2025-10-17T19-18-58-539Z/realtime-config.json +0 -7
  14. package/backups/backup-2025-10-17T19-18-58-539Z/storage/storage-config.json +0 -6
  15. package/backups/backup-2025-10-17T19-52-20-211Z/auth-config.json +0 -7
  16. package/backups/backup-2025-10-17T19-52-20-211Z/backup-manifest.json +0 -19
  17. package/backups/backup-2025-10-17T19-52-20-211Z/database-2025-10-17T19-52-20-215Z.dump +0 -0
  18. package/backups/backup-2025-10-17T19-52-20-211Z/functions/README.md +0 -4
  19. package/backups/backup-2025-10-17T19-52-20-211Z/realtime-config.json +0 -7
  20. package/backups/backup-2025-10-17T19-52-20-211Z/storage/storage-config.json +0 -6
  21. package/backups/backup-2025-10-17T20-38-13-188Z/auth-config.json +0 -7
  22. package/backups/backup-2025-10-17T20-38-13-188Z/backup-manifest.json +0 -19
  23. package/backups/backup-2025-10-17T20-38-13-188Z/database-2025-10-17T20-38-13-194Z.dump +0 -0
  24. package/backups/backup-2025-10-17T20-38-13-188Z/functions/README.md +0 -4
  25. package/backups/backup-2025-10-17T20-38-13-188Z/realtime-config.json +0 -7
  26. package/backups/backup-2025-10-17T20-38-13-188Z/storage/storage-config.json +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.45",
3
+ "version": "0.0.47",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -12,7 +12,10 @@
12
12
  "test": "echo \"Error: no test specified\" && exit 1",
13
13
  "start": "node bin/smoonb.js",
14
14
  "preinstall": "node -e \"if(process.env.npm_config_global) { console.error('\\n❌ SMOONB NÃO DEVE SER INSTALADO GLOBALMENTE!\\n\\n📋 Para usar o smoonb, instale localmente no seu projeto:\\n npm install smoonb\\n\\n💡 Depois execute com:\\n npx smoonb backup\\n\\n🚫 Instalação global cancelada!\\n'); process.exit(1); }\"",
15
- "postinstall": "echo '\\n✅ smoonb instalado com sucesso!\\n💡 Execute: npx smoonb backup\\n📖 Documentação: https://github.com/almmello/smoonb\\n'"
15
+ "postinstall": "echo '\\n✅ smoonb instalado com sucesso!\\n💡 Execute: npx smoonb backup\\n📖 Documentação: https://github.com/almmello/smoonb\\n'",
16
+ "lint": "eslint . --ext .js",
17
+ "lint:fix": "eslint . --ext .js --fix",
18
+ "build": "npm run lint"
16
19
  },
17
20
  "keywords": [
18
21
  "supabase",
@@ -36,6 +39,9 @@
36
39
  "inquirer": "^8.2.7"
37
40
  },
38
41
  "type": "commonjs",
42
+ "devDependencies": {
43
+ "eslint": "^9.38.0"
44
+ },
39
45
  "repository": {
40
46
  "type": "git",
41
47
  "url": "git+https://github.com/almmello/smoonb.git"
@@ -44,4 +50,11 @@
44
50
  "url": "https://github.com/almmello/smoonb/issues"
45
51
  },
46
52
  "homepage": "https://github.com/almmello/smoonb#readme"
53
+ ,
54
+ "files": [
55
+ "bin/",
56
+ "src/",
57
+ "README.md",
58
+ "LICENSE.md"
59
+ ]
47
60
  }
@@ -9,6 +9,9 @@ const { readConfig, validateFor } = require('../utils/config');
9
9
  const { showBetaBanner } = require('../utils/banner');
10
10
  const { canPerformCompleteBackup, getDockerVersion } = require('../utils/docker');
11
11
  const { captureRealtimeSettings } = require('../utils/realtime-settings');
12
+ const { readEnvFile, writeEnvFile, backupEnvFile } = require('../utils/env');
13
+ const { saveEnvMap } = require('../utils/envMap');
14
+ const { mapEnvVariablesInteractively, askComponentsFlags } = require('../interactive/envMapper');
12
15
 
13
16
  const execAsync = promisify(exec);
14
17
 
@@ -17,16 +20,82 @@ module.exports = async (options) => {
17
20
  showBetaBanner();
18
21
 
19
22
  try {
20
- // Carregar e validar configuração
21
- const config = await readConfig();
23
+ // Consentimento para leitura e escrita do .env.local
24
+ console.log(chalk.yellow('⚠️ O smoonb irá ler e escrever o arquivo .env.local localmente.'));
25
+ console.log(chalk.yellow(' Um backup automático do .env.local será criado antes de qualquer alteração.'));
26
+ const consent = await require('inquirer').prompt([{ type: 'confirm', name: 'ok', message: 'Você consente em prosseguir (S/n):', default: true }]);
27
+ if (!consent.ok) {
28
+ console.log(chalk.red('🚫 Operação cancelada pelo usuário.'));
29
+ process.exit(1);
30
+ }
31
+
32
+ // Carregar configuração existente apenas para defaults de diretório
33
+ const config = await readConfig().catch(() => ({ backup: { outputDir: './backups' }, supabase: {} }));
22
34
  validateFor(config, 'backup');
23
35
 
24
36
  // Validação adicional para pré-requisitos obrigatórios
25
- if (!config.supabase.databaseUrl) {
37
+ // Pré-passo de ENV: criar diretório de backup com timestamp já no início
38
+ const now = new Date();
39
+ const year = now.getFullYear();
40
+ const month = String(now.getMonth() + 1).padStart(2, '0');
41
+ const day = String(now.getDate()).padStart(2, '0');
42
+ const hour = String(now.getHours()).padStart(2, '0');
43
+ const minute = String(now.getMinutes()).padStart(2, '0');
44
+ const second = String(now.getSeconds()).padStart(2, '0');
45
+
46
+ // Resolver diretório de saída (prioriza .env.local mapeado depois, por ora usa default)
47
+ const defaultOutput = options.output || config.backup?.outputDir || './backups';
48
+ const backupDir = path.join(defaultOutput, `backup-${year}-${month}-${day}-${hour}-${minute}-${second}`);
49
+ await ensureDir(backupDir);
50
+
51
+ // Backup e mapeamento do .env.local
52
+ const envPath = path.join(process.cwd(), '.env.local');
53
+ const envBackupPath = path.join(backupDir, 'env', '.env.local');
54
+ await ensureDir(path.dirname(envBackupPath));
55
+ await backupEnvFile(envPath, envBackupPath);
56
+ console.log(chalk.blue(`📁 Backup do .env.local: ${path.relative(process.cwd(), envBackupPath)}`));
57
+
58
+ const expectedKeys = [
59
+ 'NEXT_PUBLIC_SUPABASE_URL',
60
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
61
+ 'SUPABASE_SERVICE_ROLE_KEY',
62
+ 'SUPABASE_DB_URL',
63
+ 'SUPABASE_PROJECT_ID',
64
+ 'SUPABASE_ACCESS_TOKEN',
65
+ 'SMOONB_OUTPUT_DIR'
66
+ ];
67
+ const currentEnv = await readEnvFile(envPath);
68
+ const { finalEnv, dePara } = await mapEnvVariablesInteractively(currentEnv, expectedKeys);
69
+ await writeEnvFile(envPath, finalEnv);
70
+ await saveEnvMap(dePara, path.join(backupDir, 'env', 'env-map.json'));
71
+ console.log(chalk.green('✅ .env.local atualizado com sucesso. Nenhuma chave renomeada; valores sincronizados.'));
72
+
73
+ function getValue(expectedKey) {
74
+ const clientKey = Object.keys(dePara).find(k => dePara[k] === expectedKey);
75
+ return clientKey ? finalEnv[clientKey] : '';
76
+ }
77
+
78
+ // Recalcular outputDir a partir do ENV mapeado
79
+ const resolvedOutputDir = options.output || getValue('SMOONB_OUTPUT_DIR') || config.backup?.outputDir || './backups';
80
+
81
+ // Se mudou o outputDir, movemos o backupDir inicial para o novo local mantendo timestamp
82
+ const finalBackupDir = backupDir.startsWith(path.resolve(resolvedOutputDir))
83
+ ? backupDir
84
+ : path.join(resolvedOutputDir, path.basename(backupDir));
85
+ if (finalBackupDir !== backupDir) {
86
+ await ensureDir(resolvedOutputDir);
87
+ await fs.rename(backupDir, finalBackupDir);
88
+ }
89
+
90
+ const projectId = getValue('SUPABASE_PROJECT_ID');
91
+ const accessToken = getValue('SUPABASE_ACCESS_TOKEN');
92
+ const databaseUrl = getValue('SUPABASE_DB_URL');
93
+
94
+ if (!databaseUrl) {
26
95
  console.log(chalk.red('❌ DATABASE_URL NÃO CONFIGURADA'));
27
96
  console.log('');
28
97
  console.log(chalk.yellow('📋 Para fazer backup completo do Supabase, você precisa:'));
29
- console.log(chalk.yellow(' 1. Configurar databaseUrl no .smoonbrc'));
98
+ console.log(chalk.yellow(' 1. Configurar SUPABASE_DB_URL no .env.local'));
30
99
  console.log(chalk.yellow(' 2. Repetir o comando de backup'));
31
100
  console.log('');
32
101
  console.log(chalk.blue('💡 Exemplo de configuração:'));
@@ -36,12 +105,12 @@ module.exports = async (options) => {
36
105
  process.exit(1);
37
106
  }
38
107
 
39
- if (!config.supabase.accessToken) {
108
+ if (!accessToken) {
40
109
  console.log(chalk.red('❌ ACCESS_TOKEN NÃO CONFIGURADO'));
41
110
  console.log('');
42
111
  console.log(chalk.yellow('📋 Para fazer backup completo do Supabase, você precisa:'));
43
112
  console.log(chalk.yellow(' 1. Obter Personal Access Token do Supabase'));
44
- console.log(chalk.yellow(' 2. Configurar accessToken no .smoonbrc'));
113
+ console.log(chalk.yellow(' 2. Configurar SUPABASE_ACCESS_TOKEN no .env.local'));
45
114
  console.log(chalk.yellow(' 3. Repetir o comando de backup'));
46
115
  console.log('');
47
116
  console.log(chalk.blue('🔗 Como obter o token:'));
@@ -53,7 +122,7 @@ module.exports = async (options) => {
53
122
  process.exit(1);
54
123
  }
55
124
 
56
- console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
125
+ console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${projectId}`));
57
126
  console.log(chalk.gray(`🔍 Verificando dependências Docker...`));
58
127
 
59
128
  // Verificar se é possível fazer backup completo via Docker
@@ -65,7 +134,9 @@ module.exports = async (options) => {
65
134
  console.log('');
66
135
 
67
136
  // Proceder com backup completo via Docker
68
- return await performFullBackup(config, options);
137
+ // Flags de componentes (não afetam Database)
138
+ const flags = await askComponentsFlags();
139
+ return await performFullBackup({ projectId, accessToken, databaseUrl }, { ...options, flags, backupDir: finalBackupDir, outputDir: resolvedOutputDir });
69
140
  } else {
70
141
  // Mostrar mensagens educativas e encerrar elegantemente
71
142
  showDockerMessagesAndExit(backupCapability.reason);
@@ -78,28 +149,17 @@ module.exports = async (options) => {
78
149
  };
79
150
 
80
151
  // Função para backup completo via Docker
81
- async function performFullBackup(config, options) {
82
- // Resolver diretório de saída
83
- const outputDir = options.output || config.backup.outputDir;
84
-
85
- // Criar diretório de backup com timestamp humanizado
86
- const now = new Date();
87
- const year = now.getFullYear();
88
- const month = String(now.getMonth() + 1).padStart(2, '0');
89
- const day = String(now.getDate()).padStart(2, '0');
90
- const hour = String(now.getHours()).padStart(2, '0');
91
- const minute = String(now.getMinutes()).padStart(2, '0');
92
- const second = String(now.getSeconds()).padStart(2, '0');
93
-
94
- const backupDir = path.join(outputDir, `backup-${year}-${month}-${day}-${hour}-${minute}-${second}`);
95
- await ensureDir(backupDir);
152
+ async function performFullBackup(envCfg, options) {
153
+ const { projectId, accessToken, databaseUrl } = envCfg;
154
+ const outputDir = options.outputDir;
155
+ const backupDir = options.backupDir;
96
156
 
97
157
  console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
98
158
  console.log(chalk.gray(`🐳 Backup via Docker Desktop`));
99
159
 
100
160
  const manifest = {
101
161
  created_at: new Date().toISOString(),
102
- project_id: config.supabase.projectId,
162
+ project_id: projectId,
103
163
  smoonb_version: require('../../package.json').version,
104
164
  backup_type: 'pg_dumpall_docker_dashboard_compatible',
105
165
  docker_version: await getDockerVersion(),
@@ -109,12 +169,12 @@ async function performFullBackup(config, options) {
109
169
 
110
170
  // 1. Backup Database via pg_dumpall Docker (idêntico ao Dashboard)
111
171
  console.log(chalk.blue('\n📊 1/8 - Backup da Database PostgreSQL via pg_dumpall Docker...'));
112
- const databaseResult = await backupDatabase(config.supabase.projectId, backupDir);
172
+ const databaseResult = await backupDatabase(databaseUrl, backupDir);
113
173
  manifest.components.database = databaseResult;
114
174
 
115
175
  // 1.5. Backup Database Separado (SQL files para troubleshooting)
116
176
  console.log(chalk.blue('\n📊 1.5/8 - Backup da Database PostgreSQL (arquivos SQL separados)...'));
117
- const dbSeparatedResult = await backupDatabaseSeparated(config.supabase.projectId, backupDir);
177
+ const dbSeparatedResult = await backupDatabaseSeparated(databaseUrl, backupDir, accessToken);
118
178
  manifest.components.database_separated = {
119
179
  success: dbSeparatedResult.success,
120
180
  method: 'supabase-cli',
@@ -123,34 +183,42 @@ async function performFullBackup(config, options) {
123
183
  };
124
184
 
125
185
  // 2. Backup Edge Functions via Docker
126
- console.log(chalk.blue('\n⚡ 2/8 - Backup das Edge Functions via Docker...'));
127
- const functionsResult = await backupEdgeFunctionsWithDocker(config.supabase.projectId, config.supabase.accessToken, backupDir);
128
- manifest.components.edge_functions = functionsResult;
186
+ if (options.flags?.includeFunctions) {
187
+ console.log(chalk.blue('\n⚡ 2/8 - Backup das Edge Functions via Docker...'));
188
+ const functionsResult = await backupEdgeFunctionsWithDocker(projectId, accessToken, backupDir);
189
+ manifest.components.edge_functions = functionsResult;
190
+ }
129
191
 
130
192
  // 3. Backup Auth Settings via API
131
- console.log(chalk.blue('\n🔐 3/8 - Backup das Auth Settings via API...'));
132
- const authResult = await backupAuthSettings(config.supabase.projectId, config.supabase.accessToken, backupDir);
133
- manifest.components.auth_settings = authResult;
193
+ if (options.flags?.includeAuth) {
194
+ console.log(chalk.blue('\n🔐 3/8 - Backup das Auth Settings via API...'));
195
+ const authResult = await backupAuthSettings(projectId, accessToken, backupDir);
196
+ manifest.components.auth_settings = authResult;
197
+ }
134
198
 
135
199
  // 4. Backup Storage via API
136
- console.log(chalk.blue('\n📦 4/8 - Backup do Storage via API...'));
137
- const storageResult = await backupStorage(config.supabase.projectId, config.supabase.accessToken, backupDir);
138
- manifest.components.storage = storageResult;
200
+ if (options.flags?.includeStorage) {
201
+ console.log(chalk.blue('\n📦 4/8 - Backup do Storage via API...'));
202
+ const storageResult = await backupStorage(projectId, accessToken, backupDir);
203
+ manifest.components.storage = storageResult;
204
+ }
139
205
 
140
206
  // 5. Backup Custom Roles via SQL
141
207
  console.log(chalk.blue('\n👥 5/8 - Backup dos Custom Roles via SQL...'));
142
- const rolesResult = await backupCustomRoles(config.supabase.databaseUrl, backupDir);
208
+ const rolesResult = await backupCustomRoles(databaseUrl, backupDir, accessToken);
143
209
  manifest.components.custom_roles = rolesResult;
144
210
 
145
211
  // 6. Backup das Database Extensions and Settings via SQL
146
212
  console.log(chalk.blue('\n🔧 6/8 - Backup das Database Extensions and Settings via SQL...'));
147
- const databaseSettingsResult = await backupDatabaseSettings(config.supabase.projectId, backupDir);
213
+ const databaseSettingsResult = await backupDatabaseSettings(databaseUrl, projectId, backupDir);
148
214
  manifest.components.database_settings = databaseSettingsResult;
149
215
 
150
216
  // 7. Backup Realtime Settings via Captura Interativa
151
- console.log(chalk.blue('\n🔄 7/8 - Backup das Realtime Settings via Captura Interativa...'));
152
- const realtimeResult = await backupRealtimeSettings(config.supabase.projectId, backupDir, options.skipRealtime);
153
- manifest.components.realtime = realtimeResult;
217
+ if (options.flags?.includeRealtime) {
218
+ console.log(chalk.blue('\n🔄 7/8 - Backup das Realtime Settings via Captura Interativa...'));
219
+ const realtimeResult = await backupRealtimeSettings(projectId, backupDir, options.skipRealtime);
220
+ manifest.components.realtime = realtimeResult;
221
+ }
154
222
 
155
223
  // Salvar manifest
156
224
  await writeJson(path.join(backupDir, 'backup-manifest.json'), manifest);
@@ -160,20 +228,50 @@ async function performFullBackup(config, options) {
160
228
  console.log(chalk.green(`📊 Database: ${databaseResult.fileName} (${databaseResult.size} KB) - Idêntico ao Dashboard`));
161
229
  console.log(chalk.green(`📊 Database SQL: ${dbSeparatedResult.files?.length || 0} arquivos separados (${dbSeparatedResult.totalSizeKB} KB) - Para troubleshooting`));
162
230
  console.log(chalk.green(`🔧 Database Settings: ${databaseSettingsResult.fileName} (${databaseSettingsResult.size} KB) - Extensions e Configurações`));
163
- console.log(chalk.green(`⚡ Edge Functions: ${functionsResult.success_count || 0}/${functionsResult.functions_count || 0} functions baixadas via Docker`));
164
- console.log(chalk.green(`🔐 Auth Settings: ${authResult.success ? 'Exportadas via API' : 'Falharam'}`));
165
- console.log(chalk.green(`📦 Storage: ${storageResult.buckets?.length || 0} buckets verificados via API`));
231
+ if (options.flags?.includeFunctions && manifest.components.edge_functions) {
232
+ const functionsResult = manifest.components.edge_functions;
233
+ console.log(chalk.green(`⚡ Edge Functions: ${functionsResult.success_count || 0}/${functionsResult.functions_count || 0} functions baixadas via Docker`));
234
+ }
235
+ if (options.flags?.includeAuth && manifest.components.auth_settings) {
236
+ const authResult = manifest.components.auth_settings;
237
+ console.log(chalk.green(`🔐 Auth Settings: ${authResult.success ? 'Exportadas via API' : 'Falharam'}`));
238
+ }
239
+ if (options.flags?.includeStorage && manifest.components.storage) {
240
+ const storageResult = manifest.components.storage;
241
+ console.log(chalk.green(`📦 Storage: ${storageResult.buckets?.length || 0} buckets verificados via API`));
242
+ }
166
243
  console.log(chalk.green(`👥 Custom Roles: ${rolesResult.roles?.length || 0} roles exportados via SQL`));
167
244
  // Determinar mensagem correta baseada no método usado
168
- let realtimeMessage = 'Falharam';
169
- if (realtimeResult.success) {
170
- if (options.skipRealtime) {
171
- realtimeMessage = 'Configurações copiadas do backup anterior';
172
- } else {
173
- realtimeMessage = 'Configurações capturadas interativamente';
245
+ if (options.flags?.includeRealtime && manifest.components.realtime) {
246
+ const realtimeResult = manifest.components.realtime;
247
+ let realtimeMessage = 'Falharam';
248
+ if (realtimeResult.success) {
249
+ if (options.skipRealtime) {
250
+ realtimeMessage = 'Configurações copiadas do backup anterior';
251
+ } else {
252
+ realtimeMessage = 'Configurações capturadas interativamente';
253
+ }
174
254
  }
255
+ console.log(chalk.green(`🔄 Realtime: ${realtimeMessage}`));
175
256
  }
176
- console.log(chalk.green(`🔄 Realtime: ${realtimeMessage}`));
257
+
258
+ // report.json
259
+ await writeJson(path.join(backupDir, 'report.json'), {
260
+ process: 'backup',
261
+ created_at: manifest.created_at,
262
+ project_id: manifest.project_id,
263
+ assets: {
264
+ env: path.join(backupDir, 'env', '.env.local'),
265
+ env_map: path.join(backupDir, 'env', 'env-map.json'),
266
+ manifest: path.join(backupDir, 'backup-manifest.json')
267
+ },
268
+ components: {
269
+ includeFunctions: !!options.flags?.includeFunctions,
270
+ includeStorage: !!options.flags?.includeStorage,
271
+ includeAuth: !!options.flags?.includeAuth,
272
+ includeRealtime: !!options.flags?.includeRealtime
273
+ }
274
+ });
177
275
 
178
276
  return { success: true, backupDir, manifest };
179
277
  }
@@ -240,16 +338,14 @@ function showDockerMessagesAndExit(reason) {
240
338
  }
241
339
 
242
340
  // Backup da database usando pg_dumpall via Docker (idêntico ao Supabase Dashboard)
243
- async function backupDatabase(projectId, backupDir) {
341
+ async function backupDatabase(databaseUrl, backupDir) {
244
342
  try {
245
343
  console.log(chalk.gray(' - Criando backup completo via pg_dumpall...'));
246
344
 
247
345
  const { execSync } = require('child_process');
248
- const config = await readConfig();
249
346
 
250
347
  // Extrair credenciais da databaseUrl
251
- const dbUrl = config.supabase.databaseUrl;
252
- const urlMatch = dbUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
348
+ const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
253
349
 
254
350
  if (!urlMatch) {
255
351
  throw new Error('Database URL inválida');
@@ -309,14 +405,12 @@ async function backupDatabase(projectId, backupDir) {
309
405
  }
310
406
 
311
407
  // Backup da database usando arquivos SQL separados via Supabase CLI (para troubleshooting)
312
- async function backupDatabaseSeparated(projectId, backupDir) {
408
+ async function backupDatabaseSeparated(databaseUrl, backupDir, accessToken) {
313
409
  try {
314
410
  console.log(chalk.gray(' - Criando backups SQL separados via Supabase CLI...'));
315
411
 
316
412
  const { execSync } = require('child_process');
317
- const config = await readConfig();
318
-
319
- const dbUrl = config.supabase.databaseUrl;
413
+ const dbUrl = databaseUrl;
320
414
  const files = [];
321
415
  let totalSizeKB = 0;
322
416
 
@@ -325,7 +419,7 @@ async function backupDatabaseSeparated(projectId, backupDir) {
325
419
  const schemaFile = path.join(backupDir, 'schema.sql');
326
420
 
327
421
  try {
328
- execSync(`supabase db dump --db-url "${dbUrl}" -f "${schemaFile}"`, { stdio: 'pipe' });
422
+ execSync(`supabase db dump --db-url "${dbUrl}" -f "${schemaFile}"`, { stdio: 'pipe', env: { ...process.env, SUPABASE_ACCESS_TOKEN: accessToken || '' } });
329
423
  const stats = await fs.stat(schemaFile);
330
424
  const sizeKB = (stats.size / 1024).toFixed(1);
331
425
  files.push({ filename: 'schema.sql', sizeKB });
@@ -340,7 +434,7 @@ async function backupDatabaseSeparated(projectId, backupDir) {
340
434
  const dataFile = path.join(backupDir, 'data.sql');
341
435
 
342
436
  try {
343
- execSync(`supabase db dump --db-url "${dbUrl}" --data-only -f "${dataFile}"`, { stdio: 'pipe' });
437
+ execSync(`supabase db dump --db-url "${dbUrl}" --data-only -f "${dataFile}"`, { stdio: 'pipe', env: { ...process.env, SUPABASE_ACCESS_TOKEN: accessToken || '' } });
344
438
  const stats = await fs.stat(dataFile);
345
439
  const sizeKB = (stats.size / 1024).toFixed(1);
346
440
  files.push({ filename: 'data.sql', sizeKB });
@@ -355,7 +449,7 @@ async function backupDatabaseSeparated(projectId, backupDir) {
355
449
  const rolesFile = path.join(backupDir, 'roles.sql');
356
450
 
357
451
  try {
358
- execSync(`supabase db dump --db-url "${dbUrl}" --role-only -f "${rolesFile}"`, { stdio: 'pipe' });
452
+ execSync(`supabase db dump --db-url "${dbUrl}" --role-only -f "${rolesFile}"`, { stdio: 'pipe', env: { ...process.env, SUPABASE_ACCESS_TOKEN: accessToken || '' } });
359
453
  const stats = await fs.stat(rolesFile);
360
454
  const sizeKB = (stats.size / 1024).toFixed(1);
361
455
  files.push({ filename: 'roles.sql', sizeKB });
@@ -625,7 +719,7 @@ async function backupStorage(projectId, accessToken, backupDir) {
625
719
  }
626
720
 
627
721
  // Backup dos Custom Roles via Docker
628
- async function backupCustomRoles(databaseUrl, backupDir) {
722
+ async function backupCustomRoles(databaseUrl, backupDir, accessToken) {
629
723
  try {
630
724
  console.log(chalk.gray(' - Exportando Custom Roles via Docker...'));
631
725
 
@@ -633,7 +727,7 @@ async function backupCustomRoles(databaseUrl, backupDir) {
633
727
 
634
728
  try {
635
729
  // ✅ Usar Supabase CLI via Docker para roles
636
- await execAsync(`supabase db dump --db-url "${databaseUrl}" --role-only -f "${customRolesFile}"`);
730
+ await execAsync(`supabase db dump --db-url "${databaseUrl}" --role-only -f "${customRolesFile}"`, { env: { ...process.env, SUPABASE_ACCESS_TOKEN: accessToken || '' } });
637
731
 
638
732
  const stats = await fs.stat(customRolesFile);
639
733
  const sizeKB = (stats.size / 1024).toFixed(1);
@@ -652,16 +746,14 @@ async function backupCustomRoles(databaseUrl, backupDir) {
652
746
  }
653
747
 
654
748
  // Backup das Database Extensions and Settings via SQL
655
- async function backupDatabaseSettings(projectId, backupDir) {
749
+ async function backupDatabaseSettings(databaseUrl, projectId, backupDir) {
656
750
  try {
657
751
  console.log(chalk.gray(' - Capturando Database Extensions and Settings...'));
658
752
 
659
753
  const { execSync } = require('child_process');
660
- const config = await readConfig();
661
754
 
662
755
  // Extrair credenciais da databaseUrl
663
- const dbUrl = config.supabase.databaseUrl;
664
- const urlMatch = dbUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
756
+ const urlMatch = databaseUrl.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
665
757
 
666
758
  if (!urlMatch) {
667
759
  throw new Error('Database URL inválida');
@@ -4,18 +4,72 @@ const fs = require('fs');
4
4
  const { readConfig, getSourceProject, getTargetProject } = require('../utils/config');
5
5
  const { showBetaBanner } = require('../utils/banner');
6
6
  const inquirer = require('inquirer');
7
+ const { readEnvFile, writeEnvFile, backupEnvFile } = require('../utils/env');
8
+ const { saveEnvMap } = require('../utils/envMap');
9
+ const { mapEnvVariablesInteractively } = require('../interactive/envMapper');
7
10
 
8
11
  module.exports = async (options) => {
9
12
  showBetaBanner();
10
13
 
11
14
  try {
12
- const config = await readConfig();
13
- const targetProject = getTargetProject(config);
14
-
15
- console.log(chalk.blue(`📁 Buscando backups em: ${config.backup.outputDir || './backups'}`));
15
+ // Consentimento para leitura e escrita do .env.local
16
+ console.log(chalk.yellow('⚠️ O smoonb irá ler e escrever o arquivo .env.local localmente.'));
17
+ console.log(chalk.yellow(' Um backup automático do .env.local será criado antes de qualquer alteração.'));
18
+ const consent = await inquirer.prompt([{ type: 'confirm', name: 'ok', message: 'Você consente em prosseguir (S/n):', default: true }]);
19
+ if (!consent.ok) {
20
+ console.log(chalk.red('🚫 Operação cancelada pelo usuário.'));
21
+ process.exit(1);
22
+ }
23
+
24
+ // Preparar diretório de processo restore-YYYY-...
25
+ const rootBackupsDir = path.join(process.cwd(), 'backups');
26
+ const now = new Date();
27
+ const ts = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}-${String(now.getHours()).padStart(2,'0')}-${String(now.getMinutes()).padStart(2,'0')}-${String(now.getSeconds()).padStart(2,'0')}`;
28
+ const processDir = path.join(rootBackupsDir, `restore-${ts}`);
29
+ fs.mkdirSync(path.join(processDir, 'env'), { recursive: true });
30
+
31
+ // Backup do .env.local
32
+ const envPath = path.join(process.cwd(), '.env.local');
33
+ const envBackupPath = path.join(processDir, 'env', '.env.local');
34
+ await backupEnvFile(envPath, envBackupPath);
35
+ console.log(chalk.blue(`📁 Backup do .env.local: ${path.relative(process.cwd(), envBackupPath)}`));
36
+
37
+ // Leitura e mapeamento interativo
38
+ const currentEnv = await readEnvFile(envPath);
39
+ const expectedKeys = [
40
+ 'NEXT_PUBLIC_SUPABASE_URL',
41
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
42
+ 'SUPABASE_SERVICE_ROLE_KEY',
43
+ 'SUPABASE_DB_URL',
44
+ 'SUPABASE_PROJECT_ID',
45
+ 'SUPABASE_ACCESS_TOKEN',
46
+ 'SMOONB_OUTPUT_DIR'
47
+ ];
48
+ const { finalEnv, dePara } = await mapEnvVariablesInteractively(currentEnv, expectedKeys);
49
+ await writeEnvFile(envPath, finalEnv);
50
+ await saveEnvMap(dePara, path.join(processDir, 'env', 'env-map.json'));
51
+ console.log(chalk.green('✅ .env.local atualizado com sucesso. Nenhuma chave renomeada; valores sincronizados.'));
52
+
53
+ // Resolver valores esperados a partir do de-para
54
+ function getValue(expectedKey) {
55
+ const clientKey = Object.keys(dePara).find(k => dePara[k] === expectedKey);
56
+ return clientKey ? finalEnv[clientKey] : '';
57
+ }
58
+
59
+ // Construir targetProject a partir do .env.local mapeado
60
+ const targetProject = {
61
+ targetProjectId: getValue('SUPABASE_PROJECT_ID'),
62
+ targetUrl: getValue('NEXT_PUBLIC_SUPABASE_URL'),
63
+ targetAnonKey: getValue('NEXT_PUBLIC_SUPABASE_ANON_KEY'),
64
+ targetServiceKey: getValue('SUPABASE_SERVICE_ROLE_KEY'),
65
+ targetDatabaseUrl: getValue('SUPABASE_DB_URL'),
66
+ targetAccessToken: getValue('SUPABASE_ACCESS_TOKEN')
67
+ };
68
+
69
+ console.log(chalk.blue(`📁 Buscando backups em: ${getValue('SMOONB_OUTPUT_DIR') || './backups'}`));
16
70
 
17
71
  // 1. Listar backups válidos (.backup.gz)
18
- const validBackups = await listValidBackups(config.backup.outputDir || './backups');
72
+ const validBackups = await listValidBackups(getValue('SMOONB_OUTPUT_DIR') || './backups');
19
73
 
20
74
  if (validBackups.length === 0) {
21
75
  console.error(chalk.red('❌ Nenhum backup válido encontrado'));
@@ -81,6 +135,26 @@ module.exports = async (options) => {
81
135
  await restoreRealtimeSettings(selectedBackup.path, targetProject);
82
136
  }
83
137
 
138
+ // report.json de restauração
139
+ const report = {
140
+ process: 'restore',
141
+ created_at: new Date().toISOString(),
142
+ target_project_id: targetProject.targetProjectId,
143
+ assets: {
144
+ env: path.join(processDir, 'env', '.env.local'),
145
+ env_map: path.join(processDir, 'env', 'env-map.json')
146
+ },
147
+ components: components,
148
+ notes: [
149
+ 'supabase/functions limpo antes e depois do deploy (se Edge Functions selecionado)'
150
+ ]
151
+ };
152
+ try {
153
+ require('fs').writeFileSync(path.join(processDir, 'report.json'), JSON.stringify(report, null, 2));
154
+ } catch (e) {
155
+ // silencioso
156
+ }
157
+
84
158
  console.log(chalk.green('\n🎉 Restauração completa finalizada!'));
85
159
 
86
160
  } catch (error) {
@@ -461,7 +535,8 @@ async function restoreEdgeFunctions(backupPath, targetProject) {
461
535
  execSync(`supabase link --project-ref ${targetProject.targetProjectId}`, {
462
536
  stdio: 'pipe',
463
537
  encoding: 'utf8',
464
- timeout: 10000
538
+ timeout: 10000,
539
+ env: { ...process.env, SUPABASE_ACCESS_TOKEN: targetProject.targetAccessToken || '' }
465
540
  });
466
541
  } catch (linkError) {
467
542
  console.log(chalk.yellow(' ⚠️ Link pode já existir, continuando...'));
@@ -476,7 +551,8 @@ async function restoreEdgeFunctions(backupPath, targetProject) {
476
551
  cwd: process.cwd(),
477
552
  stdio: 'pipe',
478
553
  encoding: 'utf8',
479
- timeout: 120000
554
+ timeout: 120000,
555
+ env: { ...process.env, SUPABASE_ACCESS_TOKEN: targetProject.targetAccessToken || '' }
480
556
  });
481
557
 
482
558
  console.log(chalk.green(` ✅ ${funcName} deployada com sucesso!`));
@@ -0,0 +1,104 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+
4
+ async function mapEnvVariablesInteractively(env, expectedKeys) {
5
+ const finalEnv = { ...env };
6
+ const dePara = {};
7
+
8
+ const allKeys = Object.keys(env);
9
+
10
+ for (const expected of expectedKeys) {
11
+ console.log(chalk.blue(`\n🔧 Mapeando variável: ${expected}`));
12
+
13
+ let clientKey = undefined;
14
+
15
+ // 3) Se existir chave exatamente igual, pular seleção e ir direto para confirmação
16
+ if (Object.prototype.hasOwnProperty.call(finalEnv, expected)) {
17
+ clientKey = expected;
18
+ } else {
19
+ // 2) Remover o caractere '?' do início da pergunta definindo prefix: ''
20
+ // 4) Opção explícita para adicionar nova chave
21
+ const choices = [
22
+ ...allKeys.map((k, idx) => ({ name: `${idx + 1}. ${k}`, value: k })),
23
+ new inquirer.Separator(),
24
+ { name: 'Adicione uma nova chave para mim', value: '__ADD_NEW__' }
25
+ ];
26
+
27
+ const { chosen } = await inquirer.prompt([{
28
+ type: 'list',
29
+ name: 'chosen',
30
+ message: `Selecione a chave correspondente para: ${expected}`,
31
+ choices,
32
+ loop: false,
33
+ prefix: ''
34
+ }]);
35
+
36
+ clientKey = chosen;
37
+ if (chosen === '__ADD_NEW__') {
38
+ clientKey = expected;
39
+ if (Object.prototype.hasOwnProperty.call(finalEnv, clientKey)) {
40
+ // Evitar colisão: gerar sufixo incremental
41
+ let i = 2;
42
+ while (Object.prototype.hasOwnProperty.call(finalEnv, `${clientKey}_${i}`)) i++;
43
+ clientKey = `${clientKey}_${i}`;
44
+ }
45
+ finalEnv[clientKey] = '';
46
+ }
47
+ }
48
+
49
+ const currentValue = finalEnv[clientKey] ?? '';
50
+ const { isCorrect } = await inquirer.prompt([{
51
+ type: 'confirm',
52
+ name: 'isCorrect',
53
+ message: `Valor atual: ${currentValue || '(vazio)'} Este é o valor correto do projeto alvo? (S/n):`,
54
+ default: true,
55
+ prefix: ''
56
+ }]);
57
+
58
+ let valueToWrite = currentValue;
59
+ if (!isCorrect) {
60
+ const { newValue } = await inquirer.prompt([{
61
+ type: 'input',
62
+ name: 'newValue',
63
+ message: `Cole o novo valor para ${clientKey}:`,
64
+ prefix: ''
65
+ }]);
66
+ valueToWrite = newValue || '';
67
+ }
68
+
69
+ if (!valueToWrite) {
70
+ const { newValueRequired } = await inquirer.prompt([{
71
+ type: 'input',
72
+ name: 'newValueRequired',
73
+ message: `Valor obrigatório. Informe valor para ${clientKey}:`,
74
+ prefix: ''
75
+ }]);
76
+ valueToWrite = newValueRequired || '';
77
+ }
78
+
79
+ finalEnv[clientKey] = valueToWrite;
80
+ if (dePara[clientKey] && dePara[clientKey] !== expected) {
81
+ throw new Error(`Duplicidade de mapeamento detectada para ${clientKey}`);
82
+ }
83
+ dePara[clientKey] = expected;
84
+ }
85
+
86
+ return { finalEnv, dePara };
87
+ }
88
+
89
+ async function askComponentsFlags() {
90
+ const answers = await inquirer.prompt([
91
+ { type: 'confirm', name: 'includeFunctions', message: 'Deseja incluir Edge Functions (S/n):', default: true },
92
+ { type: 'confirm', name: 'includeStorage', message: 'Deseja incluir Storage (s/N):', default: false },
93
+ { type: 'confirm', name: 'includeAuth', message: 'Deseja incluir Auth (s/N):', default: false },
94
+ { type: 'confirm', name: 'includeRealtime', message: 'Deseja incluir Realtime (s/N):', default: false }
95
+ ]);
96
+ return answers;
97
+ }
98
+
99
+ module.exports = {
100
+ mapEnvVariablesInteractively,
101
+ askComponentsFlags
102
+ };
103
+
104
+
@@ -0,0 +1,106 @@
1
+ const fs = require('fs');
2
+ const fsp = require('fs').promises;
3
+ const path = require('path');
4
+
5
+ function parseEnvContent(content) {
6
+ const lines = content.replace(/\r\n/g, '\n').split('\n');
7
+ const entries = {};
8
+ for (const line of lines) {
9
+ const trimmed = line.trim();
10
+ if (!trimmed || trimmed.startsWith('#')) continue;
11
+ const eqIndex = line.indexOf('=');
12
+ if (eqIndex === -1) continue;
13
+ const key = line.slice(0, eqIndex).trim();
14
+ let value = line.slice(eqIndex + 1);
15
+ // Remove optional quotes
16
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
17
+ value = value.slice(1, -1);
18
+ }
19
+ entries[key] = value;
20
+ }
21
+ return entries;
22
+ }
23
+
24
+ function stringifyEnv(entries, existingContent) {
25
+ // Best-effort: keep existing comments and order; update or append keys
26
+ const existingLines = (existingContent || '').replace(/\r\n/g, '\n').split('\n');
27
+ const seen = new Set();
28
+ const resultLines = [];
29
+
30
+ for (let line of existingLines) {
31
+ const trimmed = line.trim();
32
+ if (!trimmed || trimmed.startsWith('#') || !trimmed.includes('=')) {
33
+ resultLines.push(line);
34
+ continue;
35
+ }
36
+ const eqIndex = line.indexOf('=');
37
+ const key = line.slice(0, eqIndex).trim();
38
+ if (Object.prototype.hasOwnProperty.call(entries, key)) {
39
+ const rawValue = entries[key] ?? '';
40
+ const needsQuote = /\s|[#]/.test(rawValue);
41
+ const safeValue = needsQuote ? `"${rawValue.replace(/"/g, '\\"')}"` : rawValue;
42
+ resultLines.push(`${key}=${safeValue}`);
43
+ seen.add(key);
44
+ } else {
45
+ resultLines.push(line);
46
+ }
47
+ }
48
+
49
+ for (const [key, value] of Object.entries(entries)) {
50
+ if (seen.has(key)) continue;
51
+ const rawValue = value ?? '';
52
+ const needsQuote = /\s|[#]/.test(rawValue);
53
+ const safeValue = needsQuote ? `"${rawValue.replace(/"/g, '\\"')}"` : rawValue;
54
+ resultLines.push(`${key}=${safeValue}`);
55
+ }
56
+
57
+ // Ensure trailing newline
58
+ let out = resultLines.join('\n');
59
+ if (!out.endsWith('\n')) out += '\n';
60
+ return out;
61
+ }
62
+
63
+ async function readEnvFile(filePath) {
64
+ try {
65
+ const content = await fsp.readFile(filePath, 'utf8');
66
+ return parseEnvContent(content);
67
+ } catch (e) {
68
+ if (e.code === 'ENOENT') return {};
69
+ throw e;
70
+ }
71
+ }
72
+
73
+ async function writeEnvFile(filePath, entries, options = {}) {
74
+ const dir = path.dirname(filePath);
75
+ await fsp.mkdir(dir, { recursive: true });
76
+ let existing = '';
77
+ try { existing = await fsp.readFile(filePath, 'utf8'); } catch {}
78
+ const content = stringifyEnv(entries, existing);
79
+ await fsp.writeFile(filePath, content, 'utf8');
80
+ }
81
+
82
+ async function backupEnvFile(srcPath, destPath) {
83
+ await fsp.mkdir(path.dirname(destPath), { recursive: true });
84
+ try {
85
+ await fsp.copyFile(srcPath, destPath);
86
+ } catch (e) {
87
+ if (e.code === 'ENOENT') {
88
+ await fsp.writeFile(destPath, '', 'utf8');
89
+ return;
90
+ }
91
+ throw e;
92
+ }
93
+ }
94
+
95
+ function listEnvKeys(env) {
96
+ return Object.keys(env).sort();
97
+ }
98
+
99
+ module.exports = {
100
+ readEnvFile,
101
+ writeEnvFile,
102
+ backupEnvFile,
103
+ listEnvKeys
104
+ };
105
+
106
+
@@ -0,0 +1,25 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+
4
+ async function loadEnvMap(filePath) {
5
+ if (!filePath) return {};
6
+ try {
7
+ const content = await fs.readFile(filePath, 'utf8');
8
+ return JSON.parse(content);
9
+ } catch (e) {
10
+ return {};
11
+ }
12
+ }
13
+
14
+ async function saveEnvMap(map, destPath) {
15
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
16
+ const json = JSON.stringify(map || {}, null, 2);
17
+ await fs.writeFile(destPath, json, 'utf8');
18
+ }
19
+
20
+ module.exports = {
21
+ loadEnvMap,
22
+ saveEnvMap
23
+ };
24
+
25
+
@@ -281,8 +281,8 @@ async function getProjectInfo(projectId) {
281
281
  * Listar tabelas da database
282
282
  */
283
283
  async function listTables() {
284
+ const client = getSupabaseClient();
284
285
  try {
285
- const client = getSupabaseClient();
286
286
 
287
287
  // Usar RPC para listar tabelas
288
288
  const { data, error } = await client.rpc('get_tables');
@@ -315,8 +315,8 @@ async function listTables() {
315
315
  * Listar extensões instaladas
316
316
  */
317
317
  async function listExtensions() {
318
+ const client = getSupabaseClient();
318
319
  try {
319
- const client = getSupabaseClient();
320
320
 
321
321
  // Usar RPC para listar extensões
322
322
  const { data, error } = await client.rpc('get_extensions');
package/.smoonbrc DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "supabase": {
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "url": "https://dfgdfgdfgdfgdfgdfg.supabase.co",
5
- "serviceKey": "sdfsdfsdfsdfsdfgyjyuiuyjyujuyjyujuyjyujuy",
6
- "anonKey": "uyjyujyujyhmnbmbghjghjghghjghjghjghj",
7
- "databaseUrl": "postgresql://postgres:ghjghjghjghjghjghjghj@db.sdfsdfsdfsdfsdfsdfsdfsdf.supabase.co:5432/postgres",
8
- "accessToken": "your-personal-access-token-here"
9
- },
10
- "backup": {
11
- "includeFunctions": true,
12
- "includeStorage": true,
13
- "includeAuth": true,
14
- "includeRealtime": true,
15
- "outputDir": "./backups",
16
- "pgDumpPath": "C:\\Program Files\\PostgreSQL\\17\\bin\\pg_dump.exe"
17
- },
18
- "restore": {
19
- "verifyAfterRestore": true,
20
- "targetProject": {
21
- "targetProjectId": "target-project-id-here",
22
- "targetUrl": "https://target-project.supabase.co",
23
- "targetServiceKey": "target-service-key",
24
- "targetAnonKey": "target-anon-key",
25
- "targetDatabaseUrl": "postgresql://postgres:[password]@db.target-project.supabase.co:5432/postgres",
26
- "targetAccessToken": "target-access-token"
27
- }
28
- }
29
- }
package/.smoonbrc.example DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "supabase": {
3
- "projectId": "your-project-id-here",
4
- "url": "https://your-project.supabase.co",
5
- "serviceKey": "your-service-key-here",
6
- "anonKey": "your-anon-key-here",
7
- "databaseUrl": "postgresql://postgres:[password]@db.your-project.supabase.co:5432/postgres"
8
- },
9
- "backup": {
10
- "includeFunctions": true,
11
- "includeStorage": true,
12
- "includeAuth": true,
13
- "includeRealtime": true,
14
- "outputDir": "./backups",
15
- "pgDumpPath": "C:\\Program Files\\PostgreSQL\\17\\bin\\pg_dump.exe"
16
- },
17
- "restore": {
18
- "verifyAfterRestore": true,
19
- "targetProject": {
20
- "targetProjectId": "target-project-id-here",
21
- "targetUrl": "https://target-project.supabase.co",
22
- "targetServiceKey": "target-service-key",
23
- "targetAnonKey": "target-anon-key",
24
- "targetDatabaseUrl": "postgresql://postgres:[password]@db.target-project.supabase.co:5432/postgres",
25
- "targetAccessToken": "target-access-token"
26
- }
27
- }
28
- }
@@ -1,7 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:18:58.553Z",
3
- "projectId": "itrnlqsdfsdfsdf",
4
- "providers": [],
5
- "policies": [],
6
- "settings": {}
7
- }
@@ -1,19 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:18:58.559Z",
3
- "projectId": "itrnlqsdfsdfsdf",
4
- "version": "0.1.0-beta",
5
- "components": {
6
- "database": false,
7
- "functions": true,
8
- "auth": true,
9
- "storage": true,
10
- "realtime": true
11
- },
12
- "files": {
13
- "database": null,
14
- "functions": "functions/",
15
- "auth": "auth-config.json",
16
- "storage": "storage/",
17
- "realtime": "realtime-config.json"
18
- }
19
- }
@@ -1,4 +0,0 @@
1
- # Edge Functions Backup
2
-
3
- Nenhuma Edge Function local foi encontrada.
4
- Use o Supabase CLI para fazer backup das functions remotas.
@@ -1,7 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:18:58.558Z",
3
- "projectId": "itrnlqsdfsdfsdf",
4
- "enabled": false,
5
- "channels": [],
6
- "settings": {}
7
- }
@@ -1,6 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:18:58.556Z",
3
- "projectId": "itrnlqsdfsdfsdf",
4
- "buckets": [],
5
- "objects": []
6
- }
@@ -1,7 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:52:20.446Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "providers": [],
5
- "policies": [],
6
- "settings": {}
7
- }
@@ -1,19 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:52:20.452Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "version": "0.1.0-beta",
5
- "components": {
6
- "database": false,
7
- "functions": true,
8
- "auth": true,
9
- "storage": true,
10
- "realtime": true
11
- },
12
- "files": {
13
- "database": null,
14
- "functions": "functions/",
15
- "auth": "auth-config.json",
16
- "storage": "storage/",
17
- "realtime": "realtime-config.json"
18
- }
19
- }
@@ -1,4 +0,0 @@
1
- # Edge Functions Backup
2
-
3
- Nenhuma Edge Function local foi encontrada.
4
- Use o Supabase CLI para fazer backup das functions remotas.
@@ -1,7 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:52:20.450Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "enabled": false,
5
- "channels": [],
6
- "settings": {}
7
- }
@@ -1,6 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T19:52:20.449Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "buckets": [],
5
- "objects": []
6
- }
@@ -1,7 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T20:38:13.574Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "providers": [],
5
- "policies": [],
6
- "settings": {}
7
- }
@@ -1,19 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T20:38:13.584Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "version": "0.1.0-beta",
5
- "components": {
6
- "database": false,
7
- "functions": true,
8
- "auth": true,
9
- "storage": true,
10
- "realtime": true
11
- },
12
- "files": {
13
- "database": null,
14
- "functions": "functions/",
15
- "auth": "auth-config.json",
16
- "storage": "storage/",
17
- "realtime": "realtime-config.json"
18
- }
19
- }
@@ -1,4 +0,0 @@
1
- # Edge Functions Backup
2
-
3
- Nenhuma Edge Function local foi encontrada.
4
- Use o Supabase CLI para fazer backup das functions remotas.
@@ -1,7 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T20:38:13.582Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "enabled": false,
5
- "channels": [],
6
- "settings": {}
7
- }
@@ -1,6 +0,0 @@
1
- {
2
- "timestamp": "2025-10-17T20:38:13.578Z",
3
- "projectId": "xvfgdgdfgdfgdfgdfg",
4
- "buckets": [],
5
- "objects": []
6
- }