smoonb 0.0.61 → 0.0.62

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.61",
3
+ "version": "0.0.62",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -77,11 +77,11 @@ module.exports = async (options) => {
77
77
  console.log(chalk.blue(`📁 Backup do .env.local: ${path.relative(process.cwd(), envBackupPath)}`));
78
78
 
79
79
  const expectedKeys = [
80
+ 'SUPABASE_PROJECT_ID',
80
81
  'NEXT_PUBLIC_SUPABASE_URL',
81
82
  'NEXT_PUBLIC_SUPABASE_ANON_KEY',
82
83
  'SUPABASE_SERVICE_ROLE_KEY',
83
84
  'SUPABASE_DB_URL',
84
- 'SUPABASE_PROJECT_ID',
85
85
  'SUPABASE_ACCESS_TOKEN',
86
86
  'SMOONB_OUTPUT_DIR'
87
87
  ];
@@ -4,6 +4,7 @@ const fs = require('fs').promises;
4
4
  const { ensureDir } = require('../utils/fsx');
5
5
  const { readEnvFile } = require('../utils/env');
6
6
  const { showBetaBanner } = require('../utils/banner');
7
+ const { confirm } = require('../utils/prompt');
7
8
 
8
9
  /**
9
10
  * Comando para importar arquivo .backup.gz e opcionalmente .storage.zip do Dashboard do Supabase
@@ -12,6 +13,18 @@ module.exports = async (options) => {
12
13
  showBetaBanner();
13
14
 
14
15
  try {
16
+ // Termo de uso e aviso de risco
17
+ console.log(chalk.yellow.bold('\n⚠️ TERMO DE USO E AVISO DE RISCO\n'));
18
+ console.log(chalk.white('Ao prosseguir, você reconhece e concorda que o Supa Moonbase (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 Supa Moonbase (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'));
19
+ 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 Supa Moonbase (smoonb). Em qualquer hipótese, a responsabilidade total por todas as reivindicações relacionadas ao Supa Moonbase (smoonb) não excederá o valor pago por você pelo Supa Moonbase (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'));
20
+ 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'));
21
+
22
+ const termsAccepted = await confirm('Você aceita os Termos de Uso e o Aviso de Risco de Importação?', true);
23
+ if (!termsAccepted) {
24
+ console.log(chalk.red('🚫 Operação cancelada pelo usuário.'));
25
+ process.exit(1);
26
+ }
27
+
15
28
  // Validar que o arquivo de backup foi fornecido
16
29
  if (!options.file) {
17
30
  console.error(chalk.red('❌ Arquivo de backup não fornecido'));
@@ -65,11 +65,11 @@ module.exports = async (_options) => {
65
65
  // Leitura e mapeamento interativo
66
66
  const currentEnv = await readEnvFile(envPath);
67
67
  const expectedKeys = [
68
+ 'SUPABASE_PROJECT_ID',
68
69
  'NEXT_PUBLIC_SUPABASE_URL',
69
70
  'NEXT_PUBLIC_SUPABASE_ANON_KEY',
70
71
  'SUPABASE_SERVICE_ROLE_KEY',
71
72
  'SUPABASE_DB_URL',
72
- 'SUPABASE_PROJECT_ID',
73
73
  'SUPABASE_ACCESS_TOKEN',
74
74
  'SMOONB_OUTPUT_DIR'
75
75
  ];
@@ -7,18 +7,92 @@ async function mapEnvVariablesInteractively(env, expectedKeys) {
7
7
  const dePara = {};
8
8
 
9
9
  const allKeys = Object.keys(env);
10
+ let projectId = null;
11
+
12
+ // Função auxiliar para obter Project ID já mapeado
13
+ function getProjectId() {
14
+ if (projectId) return projectId;
15
+ // Tentar encontrar Project ID já mapeado
16
+ const projectIdKey = Object.keys(dePara).find(k => dePara[k] === 'SUPABASE_PROJECT_ID');
17
+ if (projectIdKey && finalEnv[projectIdKey]) {
18
+ projectId = finalEnv[projectIdKey];
19
+ return projectId;
20
+ }
21
+ // Tentar encontrar no env original
22
+ const originalKey = Object.keys(env).find(k => k === 'SUPABASE_PROJECT_ID' || env[k]?.match(/^[a-z]{20}$/));
23
+ if (originalKey && env[originalKey]) {
24
+ projectId = env[originalKey];
25
+ return projectId;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ // Função para obter instruções e links específicos para cada variável
31
+ function getVariableInstructions(expected, currentProjectId) {
32
+ const instructions = {
33
+ 'SUPABASE_PROJECT_ID': {
34
+ notFound: 'Não foi encontrada uma entrada para a variável SUPABASE_PROJECT_ID.',
35
+ help: 'Encontre em: Dashboard -> Project Settings -> General -> General Settings -> Project ID',
36
+ link: null
37
+ },
38
+ 'NEXT_PUBLIC_SUPABASE_URL': {
39
+ notFound: 'Não foi encontrada uma entrada para a variável NEXT_PUBLIC_SUPABASE_URL.',
40
+ help: currentProjectId
41
+ ? `Acesse: https://supabase.com/dashboard/project/${currentProjectId}/settings/api`
42
+ : 'Acesse: Dashboard -> Project Settings -> API -> Project URL',
43
+ link: currentProjectId ? `https://supabase.com/dashboard/project/${currentProjectId}/settings/api` : null
44
+ },
45
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY': {
46
+ notFound: 'Não foi encontrada uma entrada para a variável NEXT_PUBLIC_SUPABASE_ANON_KEY.',
47
+ help: currentProjectId
48
+ ? `Acesse: https://supabase.com/dashboard/project/${currentProjectId}/settings/api-keys`
49
+ : 'Acesse: Dashboard -> Project Settings -> API -> API Keys -> anon public',
50
+ link: currentProjectId ? `https://supabase.com/dashboard/project/${currentProjectId}/settings/api-keys` : null
51
+ },
52
+ 'SUPABASE_SERVICE_ROLE_KEY': {
53
+ notFound: 'Não foi encontrada uma entrada para a variável SUPABASE_SERVICE_ROLE_KEY.',
54
+ help: currentProjectId
55
+ ? `Acesse: https://supabase.com/dashboard/project/${currentProjectId}/settings/api-keys`
56
+ : 'Acesse: Dashboard -> Project Settings -> API -> API Keys -> service_role secret',
57
+ link: currentProjectId ? `https://supabase.com/dashboard/project/${currentProjectId}/settings/api-keys` : null
58
+ },
59
+ 'SUPABASE_DB_URL': {
60
+ notFound: 'Não foi encontrada uma entrada para a variável SUPABASE_DB_URL.',
61
+ help: currentProjectId
62
+ ? `Formato: postgresql://postgres:[DATABASE_PASSWORD]@db.${currentProjectId}.supabase.co:5432/postgres\nPara resetar senha: https://supabase.com/dashboard/project/${currentProjectId}/database/settings`
63
+ : 'Formato: postgresql://postgres:[DATABASE_PASSWORD]@db.[PROJECT_ID].supabase.co:5432/postgres\nPara resetar senha: Dashboard -> Project Settings -> Database -> Database Settings',
64
+ link: currentProjectId ? `https://supabase.com/dashboard/project/${currentProjectId}/database/settings` : null
65
+ },
66
+ 'SUPABASE_ACCESS_TOKEN': {
67
+ notFound: 'Não foi encontrada uma entrada para a variável SUPABASE_ACCESS_TOKEN.',
68
+ help: 'Acesse: https://supabase.com/dashboard/account/tokens',
69
+ link: 'https://supabase.com/dashboard/account/tokens'
70
+ },
71
+ 'SMOONB_OUTPUT_DIR': {
72
+ notFound: 'Não foi encontrada uma entrada para a variável SMOONB_OUTPUT_DIR.',
73
+ help: 'Diretório padrão para armazenar backups',
74
+ link: null,
75
+ default: './backups'
76
+ }
77
+ };
78
+
79
+ return instructions[expected] || {
80
+ notFound: `Não foi encontrada uma entrada para a variável ${expected}.`,
81
+ help: '',
82
+ link: null
83
+ };
84
+ }
10
85
 
11
86
  for (const expected of expectedKeys) {
12
87
  console.log(chalk.blue(`\n🔧 Mapeando variável: ${expected}`));
13
88
 
14
89
  let clientKey = undefined;
15
90
 
16
- // 3) Se existir chave exatamente igual, pular seleção e ir direto para confirmação
91
+ // Se existir chave exatamente igual, pular seleção e ir direto para confirmação
17
92
  if (Object.prototype.hasOwnProperty.call(finalEnv, expected)) {
18
93
  clientKey = expected;
19
94
  } else {
20
- // 2) Remover o caractere '?' do início da pergunta definindo prefix: ''
21
- // 4) Opção explícita para adicionar nova chave
95
+ // Opção explícita para adicionar nova chave
22
96
  const choices = [
23
97
  ...allKeys.map((k, idx) => ({ name: `${idx + 1}. ${k}`, value: k })),
24
98
  new inquirer.Separator(),
@@ -48,30 +122,71 @@ async function mapEnvVariablesInteractively(env, expectedKeys) {
48
122
  }
49
123
 
50
124
  const currentValue = finalEnv[clientKey] ?? '';
51
- const isCorrect = await confirm(`Valor atual: ${currentValue || '(vazio)'}\nEste é o valor correto do projeto alvo?`, true);
125
+ const currentProjectId = getProjectId();
126
+ const instructions = getVariableInstructions(expected, currentProjectId);
52
127
 
53
- let valueToWrite = currentValue;
54
- if (!isCorrect) {
128
+ // Se não tem valor, mostrar mensagem específica
129
+ if (!currentValue) {
130
+ console.log(chalk.yellow(instructions.notFound));
131
+ if (instructions.help) {
132
+ console.log(chalk.white(instructions.help));
133
+ }
134
+ if (instructions.link) {
135
+ console.log(chalk.cyan(`🔗 ${instructions.link}`));
136
+ }
137
+ } else {
138
+ // Se tem valor, perguntar se está correto
139
+ const isCorrect = await confirm(`Valor atual: ${currentValue}\nEste é o valor correto do projeto alvo?`, true);
140
+ if (isCorrect) {
141
+ finalEnv[clientKey] = currentValue;
142
+ if (expected === 'SUPABASE_PROJECT_ID') {
143
+ projectId = currentValue;
144
+ }
145
+ if (dePara[clientKey] && dePara[clientKey] !== expected) {
146
+ throw new Error(`Duplicidade de mapeamento detectada para ${clientKey}`);
147
+ }
148
+ dePara[clientKey] = expected;
149
+ continue;
150
+ }
151
+ }
152
+
153
+ // Solicitar novo valor
154
+ let valueToWrite = '';
155
+ if (expected === 'SMOONB_OUTPUT_DIR') {
156
+ // Para SMOONB_OUTPUT_DIR, pré-preencher com ./backups
55
157
  const { newValue } = await inquirer.prompt([{
56
158
  type: 'input',
57
159
  name: 'newValue',
58
- message: `Cole o novo valor para ${clientKey}:`,
160
+ message: `Confirme o novo valor para ${expected}:`,
161
+ default: instructions.default || './backups',
162
+ prefix: ''
163
+ }]);
164
+ valueToWrite = newValue || instructions.default || './backups';
165
+ } else {
166
+ const { newValue } = await inquirer.prompt([{
167
+ type: 'input',
168
+ name: 'newValue',
169
+ message: `Cole o novo valor para ${expected}:`,
59
170
  prefix: ''
60
171
  }]);
61
172
  valueToWrite = newValue || '';
62
173
  }
63
174
 
64
- if (!valueToWrite) {
175
+ // Validar valor obrigatório (exceto SMOONB_OUTPUT_DIR que tem default)
176
+ if (!valueToWrite && expected !== 'SMOONB_OUTPUT_DIR') {
65
177
  const { newValueRequired } = await inquirer.prompt([{
66
178
  type: 'input',
67
179
  name: 'newValueRequired',
68
- message: `Valor obrigatório. Informe valor para ${clientKey}:`,
180
+ message: `Valor obrigatório. Informe valor para ${expected}:`,
69
181
  prefix: ''
70
182
  }]);
71
183
  valueToWrite = newValueRequired || '';
72
184
  }
73
185
 
74
- finalEnv[clientKey] = valueToWrite;
186
+ finalEnv[clientKey] = valueToWrite || (expected === 'SMOONB_OUTPUT_DIR' ? './backups' : '');
187
+ if (expected === 'SUPABASE_PROJECT_ID') {
188
+ projectId = valueToWrite;
189
+ }
75
190
  if (dePara[clientKey] && dePara[clientKey] !== expected) {
76
191
  throw new Error(`Duplicidade de mapeamento detectada para ${clientKey}`);
77
192
  }