banco-sync-cli 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,79 @@
1
+ # banco-sync-cli
2
+
3
+ `banco-sync-cli` é um utilitário CLI em Node.js projetado para manter seus arquivos locais de migração/criação SQL em perfeita sincronia com o estado atual de um banco de dados PostgreSQL. Ele utiliza a API do Gemini (`gemini-2.5-flash`) para analisar as diferenças de estrutura e atualizar os arquivos em tempo real, exibindo um diff colorido no terminal (no estilo `git diff`) antes de salvar as alterações.
4
+
5
+ ---
6
+
7
+ ## Recursos
8
+
9
+ - **Suporte Exclusivo a PostgreSQL**: Coleta metadados de tabelas, colunas, tipos de dados, nulidade, chaves primárias e valores padrões.
10
+ - **Integração com Gemini API**: Usa o modelo `gemini-2.5-flash` com saídas em JSON estruturado para garantir modificações de esquema precisas, preservando comentários, formatação e estilo dos arquivos originais.
11
+ - **Diff Visual Colorida**: Exibe modificações em verde (adições) e vermelho (remoções) com trechos de contexto antes de aplicar as mudanças.
12
+ - **Armazenamento Seguro**: Armazena as credenciais e chaves de API na pasta Home do usuário (`~/.banco-sync-cli/config.json`) indexadas pelo caminho absoluto do projeto, prevenindo vazamentos acidentais em repositórios Git.
13
+
14
+ ---
15
+
16
+ ## Instalação e Execução
17
+
18
+ Como a ferramenta foi estruturada para ser executada via `npx`, você pode executá-la diretamente no diretório do projeto:
19
+
20
+ ```bash
21
+ # Executar a CLI a partir do diretório raiz
22
+ npx . <comando>
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Comandos
28
+
29
+ ### 1. `login`
30
+
31
+ Configura ou atualiza as credenciais do banco de dados PostgreSQL, a chave de API do Gemini e a pasta de migrações SQL.
32
+
33
+ Você pode executar o login de forma **interativa** (respondendo às perguntas na tela):
34
+
35
+ ```bash
36
+ npx . login
37
+ ```
38
+
39
+ Ou de forma **não-interativa** passando parâmetros por linha de comando. Se todos os parâmetros obrigatórios forem fornecidos, os prompts interativos serão ignorados:
40
+
41
+ ```bash
42
+ npx . login \
43
+ --host "meu-ip-ou-host" \
44
+ --port 5432 \
45
+ --user "meu_usuario" \
46
+ --password "minha_senha" \
47
+ --database "meu_banco" \
48
+ --schema "public" \
49
+ --gemini-api-key "MINHA_API_KEY_DO_GEMINI" \
50
+ --sql-folder "caminho/da/pasta/migrations"
51
+ ```
52
+
53
+ #### Opções do Comando `login`:
54
+ - `--host <host>`: IP ou Host de conexão do PostgreSQL.
55
+ - `--port <port>`: Porta de conexão (Padrão: `5432`).
56
+ - `--user <user>`: Usuário do banco (Padrão: `postgres`).
57
+ - `--password <password>`: Senha de acesso ao banco.
58
+ - `--database <database>`: Nome do banco de dados PostgreSQL.
59
+ - `--schema <schema>`: Schema do banco a ser lido (Padrão: `public`).
60
+ - `--ssl`: Adiciona esta flag caso a conexão precise de criptografia SSL (desabilitada por padrão).
61
+ - `--gemini-api-key <key>`: Chave de acesso à API do Gemini.
62
+ - `--sql-folder <folder>`: Caminho relativo ou absoluto da pasta que contém seus arquivos `.sql`.
63
+
64
+ ---
65
+
66
+ ### 2. `sync`
67
+
68
+ Conecta-se ao banco de dados configurado, extrai a estrutura do banco e compara os arquivos SQL definidos.
69
+
70
+ ```bash
71
+ npx . sync
72
+ ```
73
+
74
+ #### Fluxo do Comando `sync`:
75
+ 1. **Seleção de arquivos:** Pergunta se você deseja sincronizar **todos** os arquivos da pasta configurada ou apenas um **específico**.
76
+ 2. **Coleta de Esquema:** Conecta ao PostgreSQL e lê a estrutura atual.
77
+ 3. **Análise do Gemini:** Compara o arquivo `.sql` com o esquema do banco de dados. O Gemini analisa e atualiza o conteúdo do arquivo mantendo apenas a sintaxe que já existia (por exemplo, adicionando uma nova coluna a uma tabela existente).
78
+ 4. **Exibição do Diff:** Mostra um preview visual no terminal das alterações propostas (linhas adicionadas e removidas).
79
+ 5. **Confirmação:** Solicita sua autorização final (`Y/n`) para escrever as atualizações diretamente no arquivo local.
package/bin/index.js CHANGED
@@ -16,137 +16,189 @@ const program = new Command();
16
16
 
17
17
  program
18
18
  .name('banco-sync-cli')
19
- .description('CLI tool to sync SQL files with database schemas using Gemini API')
20
- .version('1.0.0');
19
+ .description('Ferramenta CLI para sincronizar arquivos SQL com a estrutura do banco usando a API do Gemini')
20
+ .version('1.0.0', '-V, --version', 'Exibe a versão atual')
21
+ .helpOption('-h, --help', 'Exibe a ajuda do comando')
22
+ .addHelpCommand('help [comando]', 'Exibe a ajuda do comando');
21
23
 
22
24
  // Login Command
23
25
  program
24
26
  .command('login')
25
- .description('Configure database credentials, Gemini API key, and SQL folder')
26
- .action(async () => {
27
- console.log(chalk.bold.cyan('\n--- Configure banco-sync-cli ---\n'));
27
+ .description('Configura as credenciais do banco PostgreSQL, a chave de API do Gemini e a pasta de arquivos SQL')
28
+ .option('--host <host>', 'Host/IP do banco de dados')
29
+ .option('--port <port>', 'Porta do banco de dados')
30
+ .option('--user <user>', 'Usuário do banco de dados')
31
+ .option('--password <password>', 'Senha do banco de dados')
32
+ .option('--database <database>', 'Nome do banco de dados')
33
+ .option('--schema <schema>', 'Schema do banco de dados (padrão: public)')
34
+ .option('--ssl', 'Habilitar conexão SSL (rejectUnauthorized=false)')
35
+ .option('--gemini-api-key <key>', 'Chave de API do Gemini')
36
+ .option('--sql-folder <folder>', 'Caminho da pasta com os arquivos SQL')
37
+ .action(async (options) => {
38
+ console.log(chalk.bold.cyan('\n--- Configurar banco-sync-cli (PostgreSQL) ---\n'));
28
39
 
29
40
  const currentConfig = getConfig() || {};
30
41
  const currentDb = currentConfig.dbConfig || {};
31
42
 
32
- try {
33
- const answers = await inquirer.prompt([
34
- {
35
- type: 'list',
36
- name: 'dbType',
37
- message: 'Select database type:',
38
- choices: ['postgres', 'mysql'],
39
- default: currentConfig.dbType || 'postgres'
40
- },
41
- {
42
- type: 'input',
43
- name: 'host',
44
- message: 'Database host:',
45
- default: answers => {
46
- return currentDb.host || 'localhost';
47
- }
48
- },
49
- {
50
- type: 'input',
51
- name: 'port',
52
- message: 'Database port:',
53
- default: (answers) => {
54
- if (currentDb.port) return currentDb.port.toString();
55
- return answers.dbType === 'postgres' ? '5432' : '3306';
56
- }
57
- },
58
- {
59
- type: 'input',
60
- name: 'user',
61
- message: 'Database user:',
62
- default: (answers) => {
63
- if (currentDb.user) return currentDb.user;
64
- return answers.dbType === 'postgres' ? 'postgres' : 'root';
65
- }
66
- },
67
- {
68
- type: 'password',
69
- name: 'password',
70
- message: 'Database password:',
71
- mask: '*',
72
- default: currentDb.password || ''
73
- },
74
- {
75
- type: 'input',
76
- name: 'database',
77
- message: 'Database name:',
78
- default: currentDb.database || '',
79
- validate: input => input.trim() !== '' ? true : 'Database name is required'
80
- },
81
- {
82
- type: 'confirm',
83
- name: 'ssl',
84
- message: 'Enable SSL (rejectUnauthorized=false)?',
85
- default: currentDb.ssl || false,
86
- when: (answers) => answers.dbType === 'postgres'
87
- },
88
- {
89
- type: 'password',
90
- name: 'geminiApiKey',
91
- message: 'Gemini API Key:',
92
- mask: '*',
93
- default: currentConfig.geminiApiKey || '',
94
- validate: input => input.trim() !== '' ? true : 'Gemini API Key is required'
95
- },
96
- {
97
- type: 'input',
98
- name: 'sqlFolder',
99
- message: 'Path to the folder with SQL files:',
100
- default: currentConfig.sqlFolder || './',
101
- validate: input => {
102
- const resolved = path.resolve(process.cwd(), input);
103
- if (!fs.existsSync(resolved)) {
104
- return `Directory does not exist: ${resolved}`;
105
- }
106
- if (!fs.statSync(resolved).isDirectory()) {
107
- return `Path is not a directory: ${resolved}`;
108
- }
109
- return true;
110
- }
111
- }
112
- ]);
43
+ const host = options.host || currentDb.host;
44
+ const port = options.port || currentDb.port;
45
+ const user = options.user || currentDb.user;
46
+ const password = options.password || currentDb.password;
47
+ const database = options.database || currentDb.database;
48
+ const schema = options.schema || currentDb.schema;
49
+ const ssl = options.ssl !== undefined ? options.ssl : currentDb.ssl;
50
+ const geminiApiKey = options.geminiApiKey || currentConfig.geminiApiKey;
51
+ const sqlFolder = options.sqlFolder || currentConfig.sqlFolder;
52
+
53
+ const hasRequiredOptions =
54
+ host &&
55
+ port &&
56
+ user &&
57
+ password !== undefined &&
58
+ database &&
59
+ geminiApiKey &&
60
+ sqlFolder;
113
61
 
114
- const projectConfig = {
115
- dbType: answers.dbType,
62
+ let projectConfig;
63
+
64
+ if (hasRequiredOptions) {
65
+ console.log(chalk.gray('Configuração fornecida via opções de linha de comando. Ignorando prompts interativos.'));
66
+ const resolved = path.resolve(process.cwd(), sqlFolder);
67
+ if (!fs.existsSync(resolved)) {
68
+ console.error(chalk.red(`\nO diretório não existe: ${resolved}`));
69
+ process.exit(1);
70
+ }
71
+
72
+ projectConfig = {
73
+ dbType: 'postgres',
116
74
  dbConfig: {
117
- host: answers.host,
118
- port: parseInt(answers.port, 10),
119
- user: answers.user,
120
- password: answers.password,
121
- database: answers.database,
122
- ssl: answers.ssl || false
75
+ host,
76
+ port: parseInt(port, 10),
77
+ user,
78
+ password,
79
+ database,
80
+ schema: schema || 'public',
81
+ ssl: !!ssl
123
82
  },
124
- geminiApiKey: answers.geminiApiKey,
125
- sqlFolder: answers.sqlFolder
83
+ geminiApiKey,
84
+ sqlFolder
126
85
  };
86
+ } else {
87
+ try {
88
+ const answers = await inquirer.prompt([
89
+ {
90
+ type: 'input',
91
+ name: 'host',
92
+ message: 'Host do banco de dados:',
93
+ default: () => host || 'localhost'
94
+ },
95
+ {
96
+ type: 'input',
97
+ name: 'port',
98
+ message: 'Porta do banco de dados:',
99
+ default: () => port?.toString() || '5432'
100
+ },
101
+ {
102
+ type: 'input',
103
+ name: 'user',
104
+ message: 'Usuário do banco de dados:',
105
+ default: () => user || 'postgres'
106
+ },
107
+ {
108
+ type: 'password',
109
+ name: 'password',
110
+ message: 'Senha do banco de dados:',
111
+ mask: '*',
112
+ default: password || ''
113
+ },
114
+ {
115
+ type: 'input',
116
+ name: 'database',
117
+ message: 'Nome do banco de dados:',
118
+ default: database || '',
119
+ validate: input => input.trim() !== '' ? true : 'O nome do banco de dados é obrigatório'
120
+ },
121
+ {
122
+ type: 'input',
123
+ name: 'schema',
124
+ message: 'Schema do banco de dados:',
125
+ default: schema || 'public'
126
+ },
127
+ {
128
+ type: 'confirm',
129
+ name: 'ssl',
130
+ message: 'Habilitar SSL (rejectUnauthorized=false)?',
131
+ default: ssl || false
132
+ },
133
+ {
134
+ type: 'password',
135
+ name: 'geminiApiKey',
136
+ message: 'Chave de API do Gemini:',
137
+ mask: '*',
138
+ default: geminiApiKey || '',
139
+ validate: input => input.trim() !== '' ? true : 'A chave de API do Gemini é obrigatória'
140
+ },
141
+ {
142
+ type: 'input',
143
+ name: 'sqlFolder',
144
+ message: 'Caminho da pasta com os arquivos SQL:',
145
+ default: sqlFolder || './',
146
+ validate: input => {
147
+ const resolved = path.resolve(process.cwd(), input);
148
+ if (!fs.existsSync(resolved)) {
149
+ return `O diretório não existe: ${resolved}`;
150
+ }
151
+ if (!fs.statSync(resolved).isDirectory()) {
152
+ return `O caminho não é um diretório: ${resolved}`;
153
+ }
154
+ return true;
155
+ }
156
+ }
157
+ ]);
158
+
159
+ projectConfig = {
160
+ dbType: 'postgres',
161
+ dbConfig: {
162
+ host: answers.host,
163
+ port: parseInt(answers.port, 10),
164
+ user: answers.user,
165
+ password: answers.password,
166
+ database: answers.database,
167
+ schema: answers.schema || 'public',
168
+ ssl: answers.ssl || false
169
+ },
170
+ geminiApiKey: answers.geminiApiKey,
171
+ sqlFolder: answers.sqlFolder
172
+ };
173
+ } catch (error) {
174
+ console.error(chalk.red('\nFalha nos prompts de configuração:'), error.message);
175
+ return;
176
+ }
177
+ }
127
178
 
179
+ try {
128
180
  saveConfig(projectConfig);
129
- console.log(chalk.bold.green('\nConfiguration saved successfully!\n'));
181
+ console.log(chalk.bold.green('\nConfiguração salva com sucesso!\n'));
130
182
  } catch (error) {
131
- console.error(chalk.red('\nConfiguration failed:'), error.message);
183
+ console.error(chalk.red('\nFalha ao salvar a configuração:'), error.message);
132
184
  }
133
185
  });
134
186
 
135
187
  // Sync Command
136
188
  program
137
189
  .command('sync')
138
- .description('Sync SQL file(s) with the current database schema')
190
+ .description('Sincroniza arquivo(s) SQL com a estrutura atual do banco de dados')
139
191
  .action(async () => {
140
192
  const config = getConfig();
141
193
  if (!config) {
142
- console.log(chalk.bold.red('\nNo configuration found for this folder.'));
143
- console.log(`Please run the login command first:\n ${chalk.cyan('npx banco-sync-cli login')}\n`);
194
+ console.log(chalk.bold.red('\nNenhuma configuração encontrada para esta pasta.'));
195
+ console.log(`Por favor, execute o comando de login primeiro:\n ${chalk.cyan('npx banco-sync-cli login')}\n`);
144
196
  process.exit(1);
145
197
  }
146
198
 
147
199
  const absoluteSqlFolder = path.resolve(process.cwd(), config.sqlFolder);
148
200
  if (!fs.existsSync(absoluteSqlFolder)) {
149
- console.error(chalk.red(`\nSQL folder not found at: ${absoluteSqlFolder}`));
201
+ console.error(chalk.red(`\nPasta de arquivos SQL não encontrada em: ${absoluteSqlFolder}`));
150
202
  process.exit(1);
151
203
  }
152
204
 
@@ -156,12 +208,12 @@ program
156
208
  sqlFiles = fs.readdirSync(absoluteSqlFolder)
157
209
  .filter(file => file.toLowerCase().endsWith('.sql'));
158
210
  } catch (err) {
159
- console.error(chalk.red(`\nError reading directory: ${err.message}`));
211
+ console.error(chalk.red(`\nErro ao ler o diretório: ${err.message}`));
160
212
  process.exit(1);
161
213
  }
162
214
 
163
215
  if (sqlFiles.length === 0) {
164
- console.log(chalk.yellow(`\nNo .sql files found in directory: ${absoluteSqlFolder}\n`));
216
+ console.log(chalk.yellow(`\nNenhum arquivo .sql encontrado na pasta: ${absoluteSqlFolder}\n`));
165
217
  process.exit(0);
166
218
  }
167
219
 
@@ -171,10 +223,10 @@ program
171
223
  {
172
224
  type: 'list',
173
225
  name: 'syncScope',
174
- message: 'What would you like to sync?',
226
+ message: 'O que você deseja sincronizar?',
175
227
  choices: [
176
- { name: 'All SQL files in the folder', value: 'all' },
177
- { name: 'Select a specific SQL file', value: 'single' }
228
+ { name: 'Todos os arquivos SQL da pasta', value: 'all' },
229
+ { name: 'Selecionar um arquivo SQL específico', value: 'single' }
178
230
  ]
179
231
  }
180
232
  ]);
@@ -187,7 +239,7 @@ program
187
239
  {
188
240
  type: 'list',
189
241
  name: 'selectedFile',
190
- message: 'Select the SQL file to sync:',
242
+ message: 'Selecione o arquivo SQL para sincronizar:',
191
243
  choices: sqlFiles
192
244
  }
193
245
  ]);
@@ -195,13 +247,13 @@ program
195
247
  }
196
248
 
197
249
  // 2. Connect to database and fetch schema
198
- const dbSpinner = ora('Connecting to database and fetching schema...').start();
250
+ const dbSpinner = ora('Conectando ao banco de dados e obtendo estrutura...').start();
199
251
  let schema;
200
252
  try {
201
253
  schema = await fetchSchema(config.dbType, config.dbConfig);
202
- dbSpinner.succeed('Database schema fetched successfully!');
254
+ dbSpinner.succeed('Estrutura do banco de dados obtida com sucesso!');
203
255
  } catch (err) {
204
- dbSpinner.fail(`Failed to fetch database schema: ${err.message}`);
256
+ dbSpinner.fail(`Falha ao obter estrutura do banco de dados: ${err.message}`);
205
257
  process.exit(1);
206
258
  }
207
259
 
@@ -210,7 +262,7 @@ program
210
262
  const filePath = path.join(absoluteSqlFolder, fileName);
211
263
  const originalContent = fs.readFileSync(filePath, 'utf-8');
212
264
 
213
- const geminiSpinner = ora(`Analyzing and comparing ${chalk.bold(fileName)} with Gemini...`).start();
265
+ const geminiSpinner = ora(`Analisando e comparando ${chalk.bold(fileName)} com o Gemini...`).start();
214
266
 
215
267
  try {
216
268
  const result = await syncSqlWithGemini(
@@ -220,7 +272,7 @@ program
220
272
  fileName
221
273
  );
222
274
 
223
- geminiSpinner.succeed(`Analysis completed for ${fileName}`);
275
+ geminiSpinner.succeed(`Análise concluída para ${fileName}`);
224
276
 
225
277
  const { updatedSql, changes } = result;
226
278
 
@@ -232,7 +284,7 @@ program
232
284
  }
233
285
 
234
286
  // Print changes summary
235
- console.log(chalk.bold('Summary of proposed changes:'));
287
+ console.log(chalk.bold('Resumo das alterações propostas:'));
236
288
  changes.forEach(change => console.log(` - ${change}`));
237
289
  console.log();
238
290
 
@@ -241,28 +293,28 @@ program
241
293
  {
242
294
  type: 'confirm',
243
295
  name: 'confirmSave',
244
- message: `Do you want to apply these changes to ${chalk.cyan(fileName)}?`,
296
+ message: `Deseja aplicar estas alterações no arquivo ${chalk.cyan(fileName)}?`,
245
297
  default: true
246
298
  }
247
299
  ]);
248
300
 
249
301
  if (confirmSave) {
250
302
  fs.writeFileSync(filePath, updatedSql, 'utf-8');
251
- console.log(chalk.green(`\nSaved updates to ${fileName} successfully!\n`));
303
+ console.log(chalk.green(`\nAlterações salvas com sucesso em ${fileName}!\n`));
252
304
  } else {
253
- console.log(chalk.yellow(`\nSkipped updates for ${fileName}.\n`));
305
+ console.log(chalk.yellow(`\nAlterações ignoradas para ${fileName}.\n`));
254
306
  }
255
307
 
256
308
  } catch (err) {
257
- geminiSpinner.fail(`Failed to sync ${fileName}: ${err.message}`);
309
+ geminiSpinner.fail(`Falha ao sincronizar ${fileName}: ${err.message}`);
258
310
  console.error(err);
259
311
  }
260
312
  }
261
313
 
262
- console.log(chalk.bold.green('Sync process finished!\n'));
314
+ console.log(chalk.bold.green('Processo de sincronização concluído!\n'));
263
315
 
264
316
  } catch (err) {
265
- console.error(chalk.red('\nSync command failed:'), err.message);
317
+ console.error(chalk.red('\nO comando de sincronização falhou:'), err.message);
266
318
  }
267
319
  });
268
320
 
package/lib/config.js CHANGED
@@ -24,7 +24,7 @@ function loadStore() {
24
24
  const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
25
25
  return JSON.parse(data);
26
26
  } catch (error) {
27
- console.error('Error reading configuration file:', error.message);
27
+ console.error('Erro ao ler o arquivo de configuração:', error.message);
28
28
  return {};
29
29
  }
30
30
  }
@@ -38,7 +38,7 @@ function saveStore(store) {
38
38
  try {
39
39
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(store, null, 2), 'utf-8');
40
40
  } catch (error) {
41
- console.error('Error writing configuration file:', error.message);
41
+ console.error('Erro ao gravar o arquivo de configuração:', error.message);
42
42
  }
43
43
  }
44
44
 
package/lib/db.js CHANGED
@@ -13,7 +13,7 @@ export async function fetchSchema(dbType, config) {
13
13
  } else if (dbType === 'mysql') {
14
14
  return fetchMysqlSchema(config);
15
15
  } else {
16
- throw new Error(`Unsupported database type: ${dbType}`);
16
+ throw new Error(`Tipo de banco de dados não suportado: ${dbType}`);
17
17
  }
18
18
  }
19
19
 
@@ -46,11 +46,11 @@ async function fetchPostgresSchema(config) {
46
46
  FROM
47
47
  information_schema.columns
48
48
  WHERE
49
- table_schema = 'public'
49
+ table_schema = $1
50
50
  ORDER BY
51
51
  table_name, ordinal_position;
52
52
  `;
53
- const colsResult = await client.query(columnsQuery);
53
+ const colsResult = await client.query(columnsQuery, [config.schema || 'public']);
54
54
 
55
55
  // 2. Get primary keys
56
56
  const pkQuery = `
@@ -63,10 +63,10 @@ async function fetchPostgresSchema(config) {
63
63
  ON tc.constraint_name = kcu.constraint_name
64
64
  AND tc.table_schema = kcu.table_schema
65
65
  WHERE
66
- tc.table_schema = 'public'
66
+ tc.table_schema = $1
67
67
  AND tc.constraint_type = 'PRIMARY KEY';
68
68
  `;
69
- const pkResult = await client.query(pkQuery);
69
+ const pkResult = await client.query(pkQuery, [config.schema || 'public']);
70
70
 
71
71
  // Create a set of primary keys for lookup "tableName.columnName"
72
72
  const primaryKeys = new Set(
package/lib/diff.js CHANGED
@@ -12,11 +12,11 @@ export function printDiff(filename, oldStr, newStr) {
12
12
  const patch = structuredPatch(filename, filename, oldStr, newStr, '', '', { context: 3 });
13
13
 
14
14
  if (patch.hunks.length === 0) {
15
- console.log(chalk.gray(`No changes detected for ${filename}.`));
15
+ console.log(chalk.gray(`Nenhuma alteração detectada para ${filename}.`));
16
16
  return false;
17
17
  }
18
18
 
19
- console.log(chalk.bold(`\nDiff for ${chalk.cyan(filename)}:`));
19
+ console.log(chalk.bold(`\nDiff para ${chalk.cyan(filename)}:`));
20
20
  console.log(chalk.gray('--------------------------------------------------'));
21
21
 
22
22
  for (const hunk of patch.hunks) {
package/lib/gemini.js CHANGED
@@ -13,36 +13,36 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
13
13
  export async function syncSqlWithGemini(apiKey, dbSchema, sqlContent, fileName) {
14
14
  const genAI = new GoogleGenerativeAI(apiKey);
15
15
 
16
- // Using gemini-1.5-flash as it is fast, highly accurate, and supports structured JSON outputs
17
16
  const model = genAI.getGenerativeModel({
18
- model: 'gemini-1.5-flash',
19
- systemInstruction: `You are an expert SQL engineer. Your task is to compare a provided SQL file containing database table definitions (such as schema setups, migrations, or DDL scripts) against the actual database schema (provided as a JSON representation of current tables, columns, types, defaults, and constraints).
17
+ model: 'gemini-2.5-flash',
18
+ systemInstruction: `Você é um engenheiro de SQL especialista. Sua tarefa é comparar um arquivo SQL fornecido contendo definições de tabelas de banco de dados (como configurações de esquema, migrações ou scripts DDL) com o esquema real do banco de dados (fornecido como uma representação JSON de tabelas, colunas, tipos, valores padrão e restrições atuais).
20
19
 
21
- You must modify the SQL file so that the definitions of any tables, columns, and properties in the file match the actual state of the database.
20
+ Você deve modificar o arquivo SQL para que as definições de quaisquer tabelas, colunas e propriedades no arquivo correspondam ao estado real do banco de dados.
22
21
 
23
- Rules:
24
- 1. ONLY modify structures (tables, columns, data types, constraints, defaults) that are already defined or referenced in the SQL file. Do not add completely new tables to the file that are not already present there, unless the SQL file is completely empty.
25
- 2. If a table defined in the SQL file has columns in the actual database schema that are missing in the SQL file, add those columns to the table definition in the SQL file.
26
- 3. If a column in the SQL file has a different data type, nullability, or default value than the database schema, update it to match the database schema.
27
- 4. Keep the original style, syntax, capitalization, formatting, comments, and structure of the SQL file as much as possible. Only change the column definitions or constraints that need updating.
28
- 5. Return the full updated SQL code in the JSON response under 'updatedSql'.
29
- 6. Do not include markdown code block formatting (like \`\`\`sql) inside the JSON string return values. Return raw text.`
22
+ Regras:
23
+ 1. Modifique APENAS as estruturas (tabelas, colunas, tipos de dados, restrições, valores padrão) que estão definidas ou referenciadas no arquivo SQL. Não adicione tabelas completamente novas ao arquivo que não estejam presentes lá, a menos que o arquivo SQL esteja completamente vazio.
24
+ 2. Se uma tabela definida no arquivo SQL possuir colunas no esquema real do banco de dados que estão ausentes no arquivo SQL, adicione essas colunas à definição da tabela no arquivo SQL.
25
+ 3. Se uma coluna no arquivo SQL tiver um tipo de dados, nulidade ou valor padrão diferente do esquema do banco de dados, atualize-a para corresponder ao esquema do banco de dados.
26
+ 4. Mantenha o estilo original, sintaxe, capitalização, formatação, comentários e estrutura do arquivo SQL o máximo possível. Altere apenas as definições de coluna ou restrições que precisam de atualização.
27
+ 5. Retorne o código SQL atualizado completo na resposta JSON sob a chave 'updatedSql'.
28
+ 6. Não inclua formatação de bloco de código markdown (como \`\`\`sql) dentro dos valores de string retornados no JSON. Retorne texto puro.
29
+ 7. O resumo das alterações ('changes') DEVE ser gerado em português do Brasil.`
30
30
  });
31
31
 
32
32
  const prompt = `
33
- SQL File Name: ${fileName}
33
+ Nome do Arquivo SQL: ${fileName}
34
34
 
35
- Current SQL File Content:
35
+ Conteúdo Atual do Arquivo SQL:
36
36
  ---
37
37
  ${sqlContent}
38
38
  ---
39
39
 
40
- Actual Database Schema (JSON):
40
+ Esquema Real do Banco de Dados (JSON):
41
41
  ---
42
42
  ${JSON.stringify(dbSchema, null, 2)}
43
43
  ---
44
44
 
45
- Compare the SQL file content with the actual database schema. Generate the updated SQL file contents to align it with the database schema, and list the changes made.
45
+ Compare o conteúdo do arquivo SQL com o esquema real do banco de dados. Gere o conteúdo atualizado do arquivo SQL para alinhá-lo com o esquema do banco de dados e liste as alterações feitas.
46
46
  `;
47
47
 
48
48
  const generationConfig = {
@@ -52,14 +52,14 @@ Compare the SQL file content with the actual database schema. Generate the updat
52
52
  properties: {
53
53
  updatedSql: {
54
54
  type: 'string',
55
- description: 'The complete, updated SQL file content with all style and formatting preserved but with structural adjustments to match the database schema.'
55
+ description: 'O conteúdo do arquivo SQL atualizado completo, mantendo todo o estilo e formatação originais, mas com os ajustes estruturais para corresponder ao esquema do banco de dados.'
56
56
  },
57
57
  changes: {
58
58
  type: 'array',
59
59
  items: {
60
60
  type: 'string'
61
61
  },
62
- description: 'A list of short sentences describing the modifications made (e.g., "Added age column to users table", "Changed email column type to varchar(255)").'
62
+ description: 'Uma lista de frases curtas em português descrevendo as modificações realizadas (ex: "Adicionada coluna idade à tabela usuarios", "Alterado tipo da coluna email para varchar(255)").'
63
63
  }
64
64
  },
65
65
  required: ['updatedSql', 'changes']
@@ -77,6 +77,6 @@ Compare the SQL file content with the actual database schema. Generate the updat
77
77
 
78
78
  return JSON.parse(responseText);
79
79
  } catch (error) {
80
- throw new Error(`Gemini API call failed: ${error.message}`);
80
+ throw new Error(`Falha na chamada da API do Gemini: ${error.message}`);
81
81
  }
82
82
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "banco-sync-cli",
3
- "version": "1.0.0",
4
- "description": "CLI tool to sync SQL files with database schemas using Gemini API",
3
+ "version": "1.0.1",
4
+ "description": "Ferramenta CLI para sincronizar arquivos SQL com a estrutura do banco usando a API do Gemini",
5
5
  "type": "module",
6
6
  "main": "bin/index.js",
7
7
  "bin": {