mozhost-cli 2.0.1 → 2.1.0

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/bin/mozhost.js CHANGED
@@ -5,6 +5,7 @@ const chalk = require('chalk');
5
5
  const authCommands = require('../src/commands/auth');
6
6
  const containerCommands = require('../src/commands/containers');
7
7
  const deployCommands = require('../src/commands/deploy');
8
+ const databaseCommands = require('../src/commands/databases'); // 👈 NOVO
8
9
  const packageJson = require('../package.json');
9
10
 
10
11
  program
@@ -12,7 +13,9 @@ program
12
13
  .description('CLI for MozHost - Hospedagem de Bots e APIs')
13
14
  .version(packageJson.version);
14
15
 
15
- // Auth commands
16
+ // ============================================
17
+ // AUTH COMMANDS
18
+ // ============================================
16
19
  program
17
20
  .command('auth')
18
21
  .description('Autenticar na MozHost')
@@ -28,7 +31,9 @@ program
28
31
  .description('Ver usuário atual')
29
32
  .action(authCommands.whoami);
30
33
 
31
- // Container commands
34
+ // ============================================
35
+ // CONTAINER COMMANDS
36
+ // ============================================
32
37
  program
33
38
  .command('containers')
34
39
  .alias('ls')
@@ -79,7 +84,48 @@ program
79
84
  .description('Ver URL do container')
80
85
  .action(containerCommands.url);
81
86
 
82
- // Deploy commands
87
+ // ============================================
88
+ // DATABASE COMMANDS 👈 NOVO
89
+ // ============================================
90
+ program
91
+ .command('db:list')
92
+ .alias('db:ls')
93
+ .description('Listar databases')
94
+ .action(databaseCommands.list);
95
+
96
+ program
97
+ .command('db:create')
98
+ .description('Criar novo database')
99
+ .requiredOption('-n, --name <name>', 'Nome do database')
100
+ .requiredOption('-t, --type <type>', 'Tipo (mysql, mariadb, postgres, mongodb, redis)')
101
+ .option('-c, --container <container>', 'Container para vincular (opcional)')
102
+ .action(databaseCommands.create);
103
+
104
+ program
105
+ .command('db:info <database>')
106
+ .description('Ver informações detalhadas do database')
107
+ .action(databaseCommands.info);
108
+
109
+ program
110
+ .command('db:credentials <database>')
111
+ .alias('db:creds')
112
+ .description('Ver credenciais do database')
113
+ .action(databaseCommands.credentials);
114
+
115
+ program
116
+ .command('db:link <database> <container>')
117
+ .description('Vincular database a um container')
118
+ .action(databaseCommands.link);
119
+
120
+ program
121
+ .command('db:delete <database>')
122
+ .alias('db:rm')
123
+ .description('Deletar database')
124
+ .action(databaseCommands.deleteDatabase);
125
+
126
+ // ============================================
127
+ // DEPLOY COMMANDS
128
+ // ============================================
83
129
  program
84
130
  .command('init')
85
131
  .description('Inicializar projeto para deploy')
@@ -96,11 +142,22 @@ program
96
142
  .description('Vincular diretório atual a um container')
97
143
  .action(deployCommands.link);
98
144
 
99
- // Parse arguments
145
+ // ============================================
146
+ // PARSE & HELP
147
+ // ============================================
100
148
  program.parse(process.argv);
101
149
 
102
150
  // Show help if no command
103
151
  if (!process.argv.slice(2).length) {
104
152
  console.log(chalk.cyan.bold('\n🚀 MozHost CLI\n'));
105
153
  program.outputHelp();
154
+
155
+ // Mostrar exemplos úteis
156
+ console.log(chalk.gray('\nExemplos:'));
157
+ console.log(chalk.white(' mozhost auth # Autenticar'));
158
+ console.log(chalk.white(' mozhost ls # Listar containers'));
159
+ console.log(chalk.white(' mozhost create -n app -t nodejs # Criar container'));
160
+ console.log(chalk.white(' mozhost db:list # Listar databases'));
161
+ console.log(chalk.white(' mozhost db:create -n db -t mysql # Criar database'));
162
+ console.log();
106
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mozhost-cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Command-line interface for MozHost container platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,389 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const inquirer = require('inquirer');
4
+ const api = require('../utils/api');
5
+
6
+ // ============================================
7
+ // HELPER FUNCTION - Resolver Nome ou ID
8
+ // ============================================
9
+ async function resolveDatabase(identifier) {
10
+ try {
11
+ // Tenta buscar diretamente por ID
12
+ const response = await api.getDatabase(identifier);
13
+ return response.database;
14
+ } catch (error) {
15
+ // Se não encontrar (404), busca na lista por nome
16
+ if (error.response?.status === 404 || error.response?.data?.error?.includes('not found')) {
17
+ const listResponse = await api.listDatabases();
18
+
19
+ // Procura por nome, ID completo ou ID parcial
20
+ const found = listResponse.databases.find(d =>
21
+ d.name === identifier ||
22
+ d.id === identifier ||
23
+ d.id.startsWith(identifier)
24
+ );
25
+
26
+ if (!found) {
27
+ // Procura databases similares para sugerir
28
+ const similar = listResponse.databases.filter(d =>
29
+ d.name.toLowerCase().includes(identifier.toLowerCase()) ||
30
+ d.id.includes(identifier)
31
+ );
32
+
33
+ if (similar.length > 0) {
34
+ console.log(chalk.yellow('\n💡 Você quis dizer:'));
35
+ similar.forEach(d => {
36
+ console.log(chalk.white(` • ${d.name} ${chalk.gray(`(${d.id.slice(0, 8)}) - ${d.type}`)}`));
37
+ });
38
+ } else {
39
+ console.log(chalk.gray('\n💡 Use: mozhost db:list para ver todos os databases'));
40
+ }
41
+
42
+ throw new Error(`Database "${identifier}" não encontrado`);
43
+ }
44
+
45
+ return found;
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ // ============================================
52
+ // LIST - Listar todos os databases
53
+ // ============================================
54
+ async function list() {
55
+ try {
56
+ const spinner = ora('Carregando databases...').start();
57
+
58
+ const response = await api.listDatabases();
59
+
60
+ spinner.stop();
61
+
62
+ if (response.databases.length === 0) {
63
+ console.log(chalk.yellow('\n⚠️ Nenhum database encontrado'));
64
+ console.log(chalk.gray(' Use: mozhost db:create --name <nome> --type <tipo>'));
65
+ return;
66
+ }
67
+
68
+ console.log(chalk.cyan.bold(`\n💾 Databases (${response.databases.length})\n`));
69
+
70
+ response.databases.forEach(database => {
71
+ const statusColor = database.status === 'running' ? chalk.green : chalk.gray;
72
+ const statusIcon = database.status === 'running' ? '●' : '○';
73
+
74
+ // Ícones por tipo de database
75
+ const typeIcons = {
76
+ mysql: '🐬',
77
+ mariadb: '🦭',
78
+ postgres: '🐘',
79
+ mongodb: '🍃',
80
+ redis: '🔴'
81
+ };
82
+ const typeIcon = typeIcons[database.type] || '💾';
83
+
84
+ console.log(`${statusColor(statusIcon)} ${typeIcon} ${chalk.white.bold(database.name)} ${chalk.gray(`(${database.id.slice(0, 8)})`)}`);
85
+ console.log(` ${chalk.gray('Tipo:')} ${database.type}`);
86
+ console.log(` ${chalk.gray('Status:')} ${statusColor(database.status)}`);
87
+ console.log(` ${chalk.gray('Host:')} ${chalk.cyan(database.host)}`);
88
+ console.log(` ${chalk.gray('Porta:')} ${database.port}`);
89
+ console.log(` ${chalk.gray('Database:')} ${database.database_name}`);
90
+
91
+ if (database.containers && database.containers.length > 0) {
92
+ console.log(` ${chalk.gray('Containers:')} ${database.containers.join(', ')}`);
93
+ }
94
+ console.log();
95
+ });
96
+
97
+ if (response.total_cost !== undefined) {
98
+ console.log(chalk.yellow(`💰 Custo total: ${response.total_cost} coins/dia\n`));
99
+ }
100
+
101
+ } catch (error) {
102
+ console.error(chalk.red('\n❌ Erro ao listar databases:'));
103
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ // ============================================
109
+ // CREATE - Criar novo database
110
+ // ============================================
111
+ async function create(options) {
112
+ try {
113
+ const { name, type, container } = options;
114
+
115
+ if (!name || !type) {
116
+ console.error(chalk.red('❌ Nome e tipo são obrigatórios'));
117
+ console.log(chalk.gray(' Use: mozhost db:create --name <nome> --type <tipo>'));
118
+ process.exit(1);
119
+ }
120
+
121
+ const validTypes = ['mysql', 'mariadb', 'postgres', 'mongodb', 'redis'];
122
+ if (!validTypes.includes(type)) {
123
+ console.error(chalk.red('❌ Tipo inválido'));
124
+ console.log(chalk.gray(' Tipos válidos: mysql, mariadb, postgres, mongodb, redis'));
125
+ process.exit(1);
126
+ }
127
+
128
+ console.log(chalk.cyan.bold('\n🔨 Criando database...\n'));
129
+ console.log(chalk.gray(` Nome: ${name}`));
130
+ console.log(chalk.gray(` Tipo: ${type}`));
131
+ if (container) {
132
+ console.log(chalk.gray(` Container: ${container}`));
133
+ }
134
+ console.log();
135
+
136
+ const spinner = ora('Criando database...').start();
137
+
138
+ const response = await api.createDatabase({ name, type, container });
139
+
140
+ spinner.succeed(chalk.green('✅ Database criado com sucesso!'));
141
+
142
+ const db = response.database;
143
+
144
+ console.log(chalk.gray('\n📋 Informações do Database:'));
145
+ console.log(chalk.white(` ID: ${db.id}`));
146
+ console.log(chalk.white(` Nome: ${db.name}`));
147
+ console.log(chalk.white(` Tipo: ${db.type}`));
148
+ console.log(chalk.white(` Status: ${db.status}`));
149
+ console.log(chalk.cyan(` Host: ${db.host}`));
150
+ console.log(chalk.white(` Porta: ${db.port}`));
151
+ console.log(chalk.white(` Database: ${db.database_name}`));
152
+ console.log(chalk.white(` Username: ${db.username}`));
153
+ console.log(chalk.yellow(` Password: ${db.password}`));
154
+ console.log(chalk.yellow(` 💰 Custo: ${db.cost} coins/dia`));
155
+
156
+ console.log(chalk.gray('\n🔗 Connection String:'));
157
+ console.log(chalk.cyan(` ${db.connection_string}`));
158
+
159
+ console.log(chalk.gray('\n💡 Dicas:'));
160
+ console.log(chalk.white(` • Guarde a senha em local seguro`));
161
+ console.log(chalk.white(` • Use: mozhost db:credentials ${db.name} para ver novamente`));
162
+ if (!container) {
163
+ console.log(chalk.white(` • Use: mozhost db:link ${db.name} <container> para vincular`));
164
+ }
165
+
166
+ } catch (error) {
167
+ console.error(chalk.red('\n❌ Erro ao criar database:'));
168
+
169
+ if (error.response?.data) {
170
+ console.error(chalk.red(` ${error.response.data.error || error.response.data.message}`));
171
+
172
+ if (error.response.status === 400 && error.response.data.error?.includes('Insufficient coins')) {
173
+ console.log(chalk.yellow('\n💡 Dica: Você não tem coins suficientes (necessário: 5 coins)'));
174
+ }
175
+ } else {
176
+ console.error(chalk.red(` ${error.message}`));
177
+ }
178
+
179
+ process.exit(1);
180
+ }
181
+ }
182
+
183
+ // ============================================
184
+ // INFO - Informações detalhadas do database
185
+ // ============================================
186
+ async function info(identifier) {
187
+ try {
188
+ const spinner = ora('Resolvendo database...').start();
189
+ const database = await resolveDatabase(identifier);
190
+
191
+ spinner.text = `Carregando informações de ${database.name}...`;
192
+
193
+ // Busca detalhes completos
194
+ const response = await api.getDatabase(database.id);
195
+ spinner.stop();
196
+
197
+ const db = response.database;
198
+
199
+ // Ícones por tipo
200
+ const typeIcons = {
201
+ mysql: '🐬',
202
+ mariadb: '🦭',
203
+ postgres: '🐘',
204
+ mongodb: '🍃',
205
+ redis: '🔴'
206
+ };
207
+ const typeIcon = typeIcons[db.type] || '💾';
208
+
209
+ console.log(chalk.cyan.bold(`\n${typeIcon} Informações do Database\n`));
210
+ console.log(chalk.white(` ID: ${db.id}`));
211
+ console.log(chalk.white(` Nome: ${db.name}`));
212
+ console.log(chalk.white(` Tipo: ${db.type}`));
213
+
214
+ const statusColor = db.status === 'running' ? chalk.green : chalk.gray;
215
+ console.log(` Status: ${statusColor(db.status)}`);
216
+
217
+ console.log(chalk.cyan(` Host: ${db.host}`));
218
+ console.log(chalk.white(` Porta: ${db.port}`));
219
+ console.log(chalk.white(` Database: ${db.database_name}`));
220
+ console.log(chalk.white(` Username: ${db.username}`));
221
+ console.log(chalk.yellow(` Password: ${db.password}`));
222
+ console.log(chalk.yellow(` 💰 Custo: ${db.cost} coins/dia`));
223
+
224
+ if (db.containers && db.containers.length > 0) {
225
+ console.log(chalk.gray('\n📦 Containers vinculados:'));
226
+ db.containers.forEach(c => {
227
+ console.log(chalk.white(` • ${c.name} ${chalk.gray(`(${c.id})`)}`));
228
+ });
229
+ }
230
+
231
+ console.log(chalk.gray(`\n📅 Criado em: ${new Date(db.created_at).toLocaleString('pt-BR')}`));
232
+ console.log(chalk.gray(` Atualizado em: ${new Date(db.updated_at).toLocaleString('pt-BR')}`));
233
+
234
+ console.log(chalk.gray('\n🔗 Connection String:'));
235
+ console.log(chalk.cyan(` ${db.connection_string}`));
236
+
237
+ } catch (error) {
238
+ console.error(chalk.red('\n❌ Erro ao buscar informações:'));
239
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
240
+ process.exit(1);
241
+ }
242
+ }
243
+
244
+ // ============================================
245
+ // CREDENTIALS - Ver credenciais do database
246
+ // ============================================
247
+ async function credentials(identifier) {
248
+ try {
249
+ const spinner = ora('Carregando credenciais...').start();
250
+ const database = await resolveDatabase(identifier);
251
+
252
+ const response = await api.getDatabase(database.id);
253
+ spinner.stop();
254
+
255
+ const db = response.database;
256
+
257
+ console.log(chalk.cyan.bold(`\n🔑 Credenciais de ${db.name}\n`));
258
+ console.log(chalk.white(` Host: ${chalk.cyan(db.host)}`));
259
+ console.log(chalk.white(` Porta: ${chalk.cyan(db.port)}`));
260
+ console.log(chalk.white(` Database: ${chalk.cyan(db.database_name)}`));
261
+ console.log(chalk.white(` Username: ${chalk.cyan(db.username)}`));
262
+ console.log(chalk.white(` Password: ${chalk.yellow(db.password)}`));
263
+
264
+ console.log(chalk.gray('\n🔗 Connection String:'));
265
+ console.log(chalk.cyan(` ${db.connection_string}`));
266
+
267
+ console.log(chalk.gray('\n💡 Copie e cole em suas variáveis de ambiente'));
268
+
269
+ } catch (error) {
270
+ console.error(chalk.red('\n❌ Erro ao buscar credenciais:'));
271
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
272
+ process.exit(1);
273
+ }
274
+ }
275
+
276
+ // ============================================
277
+ // LINK - Vincular database a container
278
+ // ============================================
279
+ async function link(databaseIdentifier, containerIdentifier) {
280
+ try {
281
+ if (!containerIdentifier) {
282
+ console.error(chalk.red('❌ Container não especificado'));
283
+ console.log(chalk.gray(' Use: mozhost db:link <database> <container>'));
284
+ process.exit(1);
285
+ }
286
+
287
+ const spinner = ora('Resolvendo database...').start();
288
+ const database = await resolveDatabase(databaseIdentifier);
289
+
290
+ spinner.text = 'Resolvendo container...';
291
+ // Reusar a função de containers (você precisará exportá-la ou criar similar)
292
+ let containerId;
293
+ try {
294
+ const containerResponse = await api.getContainer(containerIdentifier);
295
+ containerId = containerResponse.container.id;
296
+ } catch (error) {
297
+ if (error.response?.status === 404) {
298
+ const listResponse = await api.listContainers();
299
+ const found = listResponse.containers.find(c =>
300
+ c.name === containerIdentifier ||
301
+ c.id === containerIdentifier ||
302
+ c.id.startsWith(containerIdentifier)
303
+ );
304
+ if (!found) {
305
+ throw new Error(`Container "${containerIdentifier}" não encontrado`);
306
+ }
307
+ containerId = found.id;
308
+ } else {
309
+ throw error;
310
+ }
311
+ }
312
+
313
+ spinner.text = 'Vinculando database ao container...';
314
+ await api.linkDatabase(database.id, containerId);
315
+
316
+ spinner.succeed(chalk.green(`✅ Database ${database.name} vinculado com sucesso!`));
317
+
318
+ console.log(chalk.gray('\n💡 O container agora pode acessar este database'));
319
+
320
+ } catch (error) {
321
+ console.error(chalk.red('\n❌ Erro ao vincular database:'));
322
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
323
+ process.exit(1);
324
+ }
325
+ }
326
+
327
+ // ============================================
328
+ // DELETE - Deletar database
329
+ // ============================================
330
+ async function deleteDatabase(identifier) {
331
+ try {
332
+ const spinner = ora('Resolvendo database...').start();
333
+ const database = await resolveDatabase(identifier);
334
+ spinner.stop();
335
+
336
+ console.log(chalk.yellow('\n⚠️ ATENÇÃO: Esta ação é irreversível!'));
337
+ console.log(chalk.gray(' Todos os dados serão perdidos permanentemente.\n'));
338
+
339
+ const confirm = await inquirer.prompt([
340
+ {
341
+ type: 'confirm',
342
+ name: 'confirmed',
343
+ message: chalk.red(`Tem certeza que deseja deletar "${database.name}"?`),
344
+ default: false
345
+ }
346
+ ]);
347
+
348
+ if (!confirm.confirmed) {
349
+ console.log(chalk.yellow('Operação cancelada'));
350
+ return;
351
+ }
352
+
353
+ // Confirmação dupla para databases
354
+ const doubleConfirm = await inquirer.prompt([
355
+ {
356
+ type: 'input',
357
+ name: 'typed',
358
+ message: chalk.red(`Digite o nome do database "${database.name}" para confirmar:`),
359
+ }
360
+ ]);
361
+
362
+ if (doubleConfirm.typed !== database.name) {
363
+ console.log(chalk.red('❌ Nome incorreto. Operação cancelada.'));
364
+ return;
365
+ }
366
+
367
+ const deleteSpinner = ora(`Deletando ${database.name}...`).start();
368
+ await api.deleteDatabase(database.id);
369
+
370
+ deleteSpinner.succeed(chalk.green(`✅ Database ${database.name} deletado com sucesso!`));
371
+
372
+ } catch (error) {
373
+ console.error(chalk.red('\n❌ Erro ao deletar database:'));
374
+ console.error(chalk.red(` ${error.message || error.response?.data?.error}`));
375
+ process.exit(1);
376
+ }
377
+ }
378
+
379
+ // ============================================
380
+ // EXPORTS
381
+ // ============================================
382
+ module.exports = {
383
+ list,
384
+ create,
385
+ info,
386
+ credentials,
387
+ link,
388
+ deleteDatabase
389
+ };
package/src/utils/api.js CHANGED
@@ -170,4 +170,37 @@ class ApiClient {
170
170
  }
171
171
  }
172
172
 
173
+ // Databases
174
+ async listDatabases() {
175
+ const client = await this.getClient();
176
+ const response = await client.get('/api/databases');
177
+ return response.data;
178
+ }
179
+
180
+ async getDatabase(databaseId) {
181
+ const client = await this.getClient();
182
+ const response = await client.get(`/api/databases/${databaseId}`);
183
+ return response.data;
184
+ }
185
+
186
+ async createDatabase(data) {
187
+ const client = await this.getClient();
188
+ const response = await client.post('/api/databases', data);
189
+ return response.data;
190
+ }
191
+
192
+ async deleteDatabase(databaseId) {
193
+ const client = await this.getClient();
194
+ const response = await client.delete(`/api/databases/${databaseId}`);
195
+ return response.data;
196
+ }
197
+
198
+ async linkDatabase(databaseId, containerId) {
199
+ const client = await this.getClient();
200
+ const response = await client.post(`/api/databases/${databaseId}/link`, {
201
+ container_id: containerId
202
+ });
203
+ return response.data;
204
+ }
205
+
173
206
  module.exports = new ApiClient();