mozhost-cli 1.0.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 ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const chalk = require('chalk');
5
+ const authCommands = require('../src/commands/auth');
6
+ const containerCommands = require('../src/commands/containers');
7
+ const deployCommands = require('../src/commands/deploy');
8
+ const packageJson = require('../package.json');
9
+
10
+ program
11
+ .name('mozhost')
12
+ .description('CLI for MozHost - Hospedagem de Bots e APIs')
13
+ .version(packageJson.version);
14
+
15
+ // Auth commands
16
+ program
17
+ .command('auth')
18
+ .description('Autenticar na MozHost')
19
+ .action(authCommands.login);
20
+
21
+ program
22
+ .command('logout')
23
+ .description('Deslogar da MozHost')
24
+ .action(authCommands.logout);
25
+
26
+ program
27
+ .command('whoami')
28
+ .description('Ver usuário atual')
29
+ .action(authCommands.whoami);
30
+
31
+ // Container commands
32
+ program
33
+ .command('containers')
34
+ .alias('ls')
35
+ .description('Listar containers')
36
+ .action(containerCommands.list);
37
+
38
+ program
39
+ .command('create')
40
+ .description('Criar novo container')
41
+ .requiredOption('-n, --name <name>', 'Nome do container')
42
+ .requiredOption('-t, --type <type>', 'Tipo (nodejs, python, php)')
43
+ .action(containerCommands.create);
44
+
45
+ program
46
+ .command('start <container>')
47
+ .description('Iniciar container')
48
+ .action(containerCommands.start);
49
+
50
+ program
51
+ .command('stop <container>')
52
+ .description('Parar container')
53
+ .action(containerCommands.stop);
54
+
55
+ program
56
+ .command('restart <container>')
57
+ .description('Reiniciar container')
58
+ .action(containerCommands.restart);
59
+
60
+ program
61
+ .command('delete <container>')
62
+ .alias('rm')
63
+ .description('Deletar container')
64
+ .action(containerCommands.deleteContainer);
65
+
66
+ program
67
+ .command('logs <container>')
68
+ .description('Ver logs do container')
69
+ .option('-n, --lines <number>', 'Número de linhas', '100')
70
+ .action(containerCommands.logs);
71
+
72
+ program
73
+ .command('info <container>')
74
+ .description('Ver informações do container')
75
+ .action(containerCommands.info);
76
+
77
+ program
78
+ .command('url <container>')
79
+ .description('Ver URL do container')
80
+ .action(containerCommands.url);
81
+
82
+ // Deploy commands
83
+ program
84
+ .command('init')
85
+ .description('Inicializar projeto para deploy')
86
+ .action(deployCommands.init);
87
+
88
+ program
89
+ .command('deploy [container]')
90
+ .description('Fazer deploy do projeto atual')
91
+ .option('-d, --directory <path>', 'Diretório do projeto', '.')
92
+ .action(deployCommands.deploy);
93
+
94
+ program
95
+ .command('link <container>')
96
+ .description('Vincular diretório atual a um container')
97
+ .action(deployCommands.link);
98
+
99
+ // Parse arguments
100
+ program.parse(process.argv);
101
+
102
+ // Show help if no command
103
+ if (!process.argv.slice(2).length) {
104
+ console.log(chalk.cyan.bold('\n🚀 MozHost CLI\n'));
105
+ program.outputHelp();
106
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "mozhost-cli",
3
+ "version": "1.0.0",
4
+ "description": "Command-line interface for MozHost container platform",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "mozhost": "./bin/mozhost.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "mozhost",
14
+ "cli",
15
+ "containers",
16
+ "docker",
17
+ "deployment"
18
+ ],
19
+ "author": "Eliobros Tech",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "axios": "^1.6.0",
23
+ "commander": "^11.1.0",
24
+ "chalk": "^4.1.2",
25
+ "ora": "^5.4.1",
26
+ "inquirer": "^8.2.5",
27
+ "form-data": "^4.0.0",
28
+ "archiver": "^6.0.1",
29
+ "fs-extra": "^11.1.1",
30
+ "dotenv": "^16.3.1"
31
+ },
32
+ "engines": {
33
+ "node": ">=14.0.0"
34
+ }
35
+ }
@@ -0,0 +1,122 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const ora = require('ora');
4
+ const api = require('../utils/api');
5
+ const config = require('../utils/config');
6
+
7
+ async function login() {
8
+ console.log(chalk.cyan.bold('\n🔐 MozHost - Login\n'));
9
+
10
+ try {
11
+ const answers = await inquirer.prompt([
12
+ {
13
+ type: 'input',
14
+ name: 'login',
15
+ message: 'Username ou Email:',
16
+ validate: input => input.length > 0 || 'Campo obrigatório'
17
+ },
18
+ {
19
+ type: 'password',
20
+ name: 'password',
21
+ message: 'Password:',
22
+ mask: '*',
23
+ validate: input => input.length > 0 || 'Campo obrigatório'
24
+ }
25
+ ]);
26
+
27
+ const spinner = ora('Autenticando...').start();
28
+
29
+ const response = await api.login(answers);
30
+
31
+ await config.setToken(response.token);
32
+ await config.setUser(response.user);
33
+
34
+ spinner.succeed(chalk.green('✅ Login realizado com sucesso!'));
35
+
36
+ console.log(chalk.gray('\nInformações da conta:'));
37
+ console.log(chalk.white(` Username: ${response.user.username}`));
38
+ console.log(chalk.white(` Email: ${response.user.email}`));
39
+ console.log(chalk.white(` Plano: ${response.user.plan}`));
40
+ console.log(chalk.white(` Containers: ${response.user.maxContainers}`));
41
+ console.log(chalk.yellow(` Coins: ${response.user.coins || 0}`));
42
+
43
+ } catch (error) {
44
+ console.error(chalk.red('\n❌ Erro no login:'));
45
+
46
+ if (error.response) {
47
+ console.error(chalk.red(` ${error.response.data.error || error.response.data.message}`));
48
+ } else if (error.request) {
49
+ console.error(chalk.red(' Não foi possível conectar à API'));
50
+ } else {
51
+ console.error(chalk.red(` ${error.message}`));
52
+ }
53
+
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ async function logout() {
59
+ try {
60
+ const user = await config.getUser();
61
+
62
+ if (!user) {
63
+ console.log(chalk.yellow('⚠️ Você não está logado'));
64
+ return;
65
+ }
66
+
67
+ await config.clearToken();
68
+ console.log(chalk.green('✅ Logout realizado com sucesso!'));
69
+
70
+ } catch (error) {
71
+ console.error(chalk.red('❌ Erro ao fazer logout:'), error.message);
72
+ process.exit(1);
73
+ }
74
+ }
75
+
76
+ async function whoami() {
77
+ try {
78
+ const token = await config.getToken();
79
+
80
+ if (!token) {
81
+ console.log(chalk.yellow('⚠️ Você não está logado'));
82
+ console.log(chalk.gray(' Use: mozhost auth'));
83
+ return;
84
+ }
85
+
86
+ const spinner = ora('Verificando credenciais...').start();
87
+
88
+ const response = await api.verify();
89
+
90
+ spinner.stop();
91
+
92
+ console.log(chalk.cyan.bold('\n👤 Usuário Atual\n'));
93
+ console.log(chalk.white(` Username: ${response.user.username}`));
94
+ console.log(chalk.white(` Email: ${response.user.email}`));
95
+ console.log(chalk.white(` Plano: ${response.user.plan}`));
96
+ console.log(chalk.white(` Max Containers: ${response.user.maxContainers}`));
97
+ console.log(chalk.white(` Max RAM: ${response.user.maxRamMb}MB`));
98
+ console.log(chalk.white(` Max Storage: ${response.user.maxStorageMb}MB`));
99
+ console.log(chalk.yellow(` Coins: ${response.user.coins || 0}`));
100
+ console.log(chalk.gray(`\n Email Verificado: ${response.user.emailVerified ? '✓' : '✗'}`));
101
+ console.log(chalk.gray(` WhatsApp Verificado: ${response.user.whatsappVerified ? '✓' : '✗'}`));
102
+ console.log(chalk.gray(` SMS Verificado: ${response.user.smsVerified ? '✓' : '✗'}`));
103
+
104
+ } catch (error) {
105
+ console.error(chalk.red('\n❌ Erro ao verificar usuário:'));
106
+
107
+ if (error.response?.status === 401) {
108
+ console.error(chalk.red(' Token inválido ou expirado'));
109
+ console.log(chalk.gray(' Use: mozhost auth'));
110
+ } else {
111
+ console.error(chalk.red(` ${error.message}`));
112
+ }
113
+
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ module.exports = {
119
+ login,
120
+ logout,
121
+ whoami
122
+ };
@@ -0,0 +1,269 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const inquirer = require('inquirer');
4
+ const api = require('../utils/api');
5
+
6
+ async function list() {
7
+ try {
8
+ const spinner = ora('Carregando containers...').start();
9
+
10
+ const response = await api.listContainers();
11
+
12
+ spinner.stop();
13
+
14
+ if (response.containers.length === 0) {
15
+ console.log(chalk.yellow('\n⚠️ Nenhum container encontrado'));
16
+ console.log(chalk.gray(' Use: mozhost create -n <nome> -t <tipo>'));
17
+ return;
18
+ }
19
+
20
+ console.log(chalk.cyan.bold(`\n📦 Containers (${response.total})\n`));
21
+
22
+ response.containers.forEach(container => {
23
+ const statusColor = container.status === 'running' ? chalk.green : chalk.gray;
24
+ const statusIcon = container.status === 'running' ? '●' : '○';
25
+
26
+ console.log(`${statusColor(statusIcon)} ${chalk.white.bold(container.name)} ${chalk.gray(`(${container.id.slice(0, 8)})`)}`);
27
+ console.log(` ${chalk.gray('Tipo:')} ${container.type}`);
28
+ console.log(` ${chalk.gray('Status:')} ${statusColor(container.status)}`);
29
+ console.log(` ${chalk.gray('URL:')} ${chalk.cyan(`https://${container.domain}`)}`);
30
+ console.log(` ${chalk.gray('Porta:')} ${container.port}`);
31
+ console.log(` ${chalk.gray('RAM:')} ${container.memory_limit_mb}MB`);
32
+ console.log(` ${chalk.gray('Storage:')} ${container.storage_used_mb || 0}MB\n`);
33
+ });
34
+
35
+ if (response.coins !== undefined) {
36
+ console.log(chalk.yellow(`💰 Coins disponíveis: ${response.coins}\n`));
37
+ }
38
+
39
+ if (response.storageAlerts?.length > 0) {
40
+ console.log(chalk.red('⚠️ Alertas de armazenamento:'));
41
+ response.storageAlerts.forEach(alert => {
42
+ console.log(chalk.red(` ${alert.name}: ${alert.usedMB}MB / ${alert.maxMB}MB (>90%)`));
43
+ });
44
+ console.log();
45
+ }
46
+
47
+ } catch (error) {
48
+ console.error(chalk.red('\n❌ Erro ao listar containers:'));
49
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ async function create(options) {
55
+ try {
56
+ const { name, type } = options;
57
+
58
+ if (!['nodejs', 'python', 'php'].includes(type)) {
59
+ console.error(chalk.red('❌ Tipo inválido. Use: nodejs, python ou php'));
60
+ process.exit(1);
61
+ }
62
+
63
+ console.log(chalk.cyan.bold('\n🔨 Criando container...\n'));
64
+ console.log(chalk.gray(` Nome: ${name}`));
65
+ console.log(chalk.gray(` Tipo: ${type}\n`));
66
+
67
+ const spinner = ora('Criando container...').start();
68
+
69
+ const response = await api.createContainer({ name, type });
70
+
71
+ spinner.succeed(chalk.green('✅ Container criado com sucesso!'));
72
+
73
+ console.log(chalk.gray('\nInformações do container:'));
74
+ console.log(chalk.white(` ID: ${response.container.id}`));
75
+ console.log(chalk.white(` Nome: ${response.container.name}`));
76
+ console.log(chalk.white(` Tipo: ${response.container.type}`));
77
+ console.log(chalk.white(` Status: ${response.container.status}`));
78
+ console.log(chalk.cyan(` URL: https://${response.container.domain}`));
79
+ console.log(chalk.white(` Porta: ${response.container.port}`));
80
+
81
+ console.log(chalk.gray('\nPróximos passos:'));
82
+ console.log(chalk.white(` 1. mozhost start ${response.container.id}`));
83
+ console.log(chalk.white(` 2. mozhost deploy ${response.container.id}`));
84
+
85
+ } catch (error) {
86
+ console.error(chalk.red('\n❌ Erro ao criar container:'));
87
+
88
+ if (error.response?.data) {
89
+ console.error(chalk.red(` ${error.response.data.error || error.response.data.message}`));
90
+
91
+ if (error.response.status === 403) {
92
+ console.log(chalk.yellow('\n💡 Dica: Você atingiu o limite de containers do seu plano'));
93
+ }
94
+
95
+ if (error.response.status === 402) {
96
+ console.log(chalk.yellow('\n💡 Dica: Você não tem coins suficientes'));
97
+ }
98
+ } else {
99
+ console.error(chalk.red(` ${error.message}`));
100
+ }
101
+
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ async function start(containerId) {
107
+ try {
108
+ const spinner = ora('Iniciando container...').start();
109
+
110
+ const response = await api.startContainer(containerId);
111
+
112
+ spinner.succeed(chalk.green(`✅ Container ${response.container.name} iniciado!`));
113
+
114
+ const info = await api.getContainer(containerId);
115
+ console.log(chalk.cyan(`\n🌐 URL: https://${info.container.domain}`));
116
+
117
+ } catch (error) {
118
+ console.error(chalk.red('\n❌ Erro ao iniciar container:'));
119
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
120
+ process.exit(1);
121
+ }
122
+ }
123
+
124
+ async function stop(containerId) {
125
+ try {
126
+ const spinner = ora('Parando container...').start();
127
+
128
+ const response = await api.stopContainer(containerId);
129
+
130
+ spinner.succeed(chalk.green(`✅ Container ${response.container.name} parado!`));
131
+
132
+ } catch (error) {
133
+ console.error(chalk.red('\n❌ Erro ao parar container:'));
134
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
135
+ process.exit(1);
136
+ }
137
+ }
138
+
139
+ async function restart(containerId) {
140
+ try {
141
+ const spinner = ora('Reiniciando container...').start();
142
+
143
+ const response = await api.restartContainer(containerId);
144
+
145
+ spinner.succeed(chalk.green(`✅ Container ${response.container.name} reiniciado!`));
146
+
147
+ } catch (error) {
148
+ console.error(chalk.red('\n❌ Erro ao reiniciar container:'));
149
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
150
+ process.exit(1);
151
+ }
152
+ }
153
+
154
+ async function deleteContainer(containerId) {
155
+ try {
156
+ const confirm = await inquirer.prompt([
157
+ {
158
+ type: 'confirm',
159
+ name: 'confirmed',
160
+ message: chalk.red('Tem certeza que deseja deletar este container?'),
161
+ default: false
162
+ }
163
+ ]);
164
+
165
+ if (!confirm.confirmed) {
166
+ console.log(chalk.yellow('Operação cancelada'));
167
+ return;
168
+ }
169
+
170
+ const spinner = ora('Deletando container...').start();
171
+
172
+ await api.deleteContainer(containerId);
173
+
174
+ spinner.succeed(chalk.green('✅ Container deletado com sucesso!'));
175
+
176
+ } catch (error) {
177
+ console.error(chalk.red('\n❌ Erro ao deletar container:'));
178
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
179
+ process.exit(1);
180
+ }
181
+ }
182
+
183
+ async function logs(containerId, options) {
184
+ try {
185
+ const spinner = ora('Carregando logs...').start();
186
+
187
+ const response = await api.getContainerLogs(containerId, options.lines);
188
+
189
+ spinner.stop();
190
+
191
+ if (!response.logs || response.logs.length === 0) {
192
+ console.log(chalk.yellow('\n⚠️ Nenhum log encontrado'));
193
+ return;
194
+ }
195
+
196
+ console.log(chalk.cyan.bold('\n📝 Logs do Container\n'));
197
+ response.logs.forEach(line => {
198
+ console.log(chalk.gray(line));
199
+ });
200
+
201
+ } catch (error) {
202
+ console.error(chalk.red('\n❌ Erro ao buscar logs:'));
203
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
204
+ process.exit(1);
205
+ }
206
+ }
207
+
208
+ async function info(containerId) {
209
+ try {
210
+ const spinner = ora('Carregando informações...').start();
211
+
212
+ const response = await api.getContainer(containerId);
213
+
214
+ spinner.stop();
215
+
216
+ const container = response.container;
217
+
218
+ console.log(chalk.cyan.bold('\n📦 Informações do Container\n'));
219
+ console.log(chalk.white(` ID: ${container.id}`));
220
+ console.log(chalk.white(` Nome: ${container.name}`));
221
+ console.log(chalk.white(` Tipo: ${container.type}`));
222
+
223
+ const statusColor = container.status === 'running' ? chalk.green : chalk.gray;
224
+ console.log(` Status: ${statusColor(container.status)}`);
225
+
226
+ console.log(chalk.cyan(` URL: https://${container.domain}`));
227
+ console.log(chalk.white(` Porta: ${container.port}`));
228
+ console.log(chalk.white(` CPU Limit: ${container.cpu_limit}`));
229
+ console.log(chalk.white(` RAM Limit: ${container.memory_limit_mb}MB`));
230
+ console.log(chalk.white(` Storage Usado: ${container.storage_used_mb || 0}MB`));
231
+ console.log(chalk.white(` Auto Restart: ${container.auto_restart ? 'Sim' : 'Não'}`));
232
+ console.log(chalk.gray(` Criado em: ${new Date(container.created_at).toLocaleString('pt-BR')}`));
233
+ console.log(chalk.gray(` Atualizado em: ${new Date(container.updated_at).toLocaleString('pt-BR')}`));
234
+
235
+ if (response.stats) {
236
+ console.log(chalk.cyan.bold('\n📊 Estatísticas em Tempo Real\n'));
237
+ console.log(chalk.white(` CPU: ${response.stats.cpu.toFixed(2)}%`));
238
+ console.log(chalk.white(` Memória: ${(response.stats.memory.used / 1024 / 1024).toFixed(2)}MB / ${(response.stats.memory.limit / 1024 / 1024).toFixed(2)}MB (${response.stats.memory.percent.toFixed(2)}%)`));
239
+ }
240
+
241
+ } catch (error) {
242
+ console.error(chalk.red('\n❌ Erro ao buscar informações:'));
243
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
244
+ process.exit(1);
245
+ }
246
+ }
247
+
248
+ async function url(containerId) {
249
+ try {
250
+ const response = await api.getContainer(containerId);
251
+ console.log(chalk.cyan(`https://${response.container.domain}`));
252
+ } catch (error) {
253
+ console.error(chalk.red('❌ Erro ao buscar URL:'));
254
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
255
+ process.exit(1);
256
+ }
257
+ }
258
+
259
+ module.exports = {
260
+ list,
261
+ create,
262
+ start,
263
+ stop,
264
+ restart,
265
+ deleteContainer,
266
+ logs,
267
+ info,
268
+ url
269
+ };
@@ -0,0 +1,303 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const inquirer = require('inquirer');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const archiver = require('archiver');
7
+ const FormData = require('form-data');
8
+ const api = require('../utils/api');
9
+ const config = require('../utils/config');
10
+
11
+ // Arquivos/pastas ignorados por padrão
12
+ const DEFAULT_IGNORE = [
13
+ 'node_modules',
14
+ '.git',
15
+ '.env',
16
+ '.DS_Store',
17
+ 'dist',
18
+ 'build',
19
+ '.mozhost.json',
20
+ '*.log',
21
+ '.vscode',
22
+ '.idea',
23
+ '__pycache__',
24
+ '*.pyc',
25
+ 'venv',
26
+ 'env'
27
+ ];
28
+
29
+ async function init() {
30
+ try {
31
+ console.log(chalk.cyan.bold('\n🚀 Inicializar Deploy MozHost\n'));
32
+
33
+ // Verificar se já existe configuração
34
+ const existingConfig = await config.getProjectConfig();
35
+ if (existingConfig) {
36
+ console.log(chalk.yellow('⚠️ Projeto já inicializado'));
37
+ console.log(chalk.gray(` Container vinculado: ${existingConfig.name} (${existingConfig.container})`));
38
+
39
+ const { override } = await inquirer.prompt([
40
+ {
41
+ type: 'confirm',
42
+ name: 'override',
43
+ message: 'Deseja reconfigurar?',
44
+ default: false
45
+ }
46
+ ]);
47
+
48
+ if (!override) {
49
+ return;
50
+ }
51
+ }
52
+
53
+ // Listar containers disponíveis
54
+ const spinner = ora('Carregando containers...').start();
55
+ const response = await api.listContainers();
56
+ spinner.stop();
57
+
58
+ if (response.containers.length === 0) {
59
+ console.log(chalk.red('\n❌ Nenhum container encontrado'));
60
+ console.log(chalk.gray(' Crie um container primeiro: mozhost create -n <nome> -t <tipo>'));
61
+ return;
62
+ }
63
+
64
+ // Selecionar container
65
+ const { containerId } = await inquirer.prompt([
66
+ {
67
+ type: 'list',
68
+ name: 'containerId',
69
+ message: 'Selecione o container:',
70
+ choices: response.containers.map(c => ({
71
+ name: `${c.name} (${c.type}) - ${c.status}`,
72
+ value: c.id
73
+ }))
74
+ }
75
+ ]);
76
+
77
+ const selectedContainer = response.containers.find(c => c.id === containerId);
78
+
79
+ // Criar arquivo .mozhostignore se não existir
80
+ const ignorePath = path.join(process.cwd(), '.mozhostignore');
81
+ if (!fs.existsSync(ignorePath)) {
82
+ await fs.writeFile(ignorePath, DEFAULT_IGNORE.join('\n'));
83
+ console.log(chalk.green('✅ Arquivo .mozhostignore criado'));
84
+ }
85
+
86
+ // Salvar configuração
87
+ await config.linkContainer(containerId, selectedContainer.name);
88
+
89
+ console.log(chalk.green('\n✅ Projeto inicializado com sucesso!'));
90
+ console.log(chalk.gray(` Container: ${selectedContainer.name}`));
91
+ console.log(chalk.cyan(` URL: https://${selectedContainer.domain}`));
92
+ console.log(chalk.gray('\nPróximos passos:'));
93
+ console.log(chalk.white(' mozhost deploy'));
94
+
95
+ } catch (error) {
96
+ console.error(chalk.red('\n❌ Erro ao inicializar:'));
97
+ console.error(chalk.red(` ${error.message}`));
98
+ process.exit(1);
99
+ }
100
+ }
101
+
102
+ async function link(containerId) {
103
+ try {
104
+ console.log(chalk.cyan.bold('\n🔗 Vincular Container\n'));
105
+
106
+ const spinner = ora('Buscando container...').start();
107
+
108
+ const response = await api.getContainer(containerId);
109
+ const container = response.container;
110
+
111
+ spinner.stop();
112
+
113
+ await config.linkContainer(container.id, container.name);
114
+
115
+ console.log(chalk.green('✅ Container vinculado com sucesso!'));
116
+ console.log(chalk.gray(` Container: ${container.name}`));
117
+ console.log(chalk.cyan(` URL: https://${container.domain}`));
118
+
119
+ } catch (error) {
120
+ console.error(chalk.red('\n❌ Erro ao vincular container:'));
121
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
122
+ process.exit(1);
123
+ }
124
+ }
125
+
126
+ async function deploy(containerId, options) {
127
+ try {
128
+ const projectDir = path.resolve(options.directory);
129
+
130
+ if (!fs.existsSync(projectDir)) {
131
+ console.error(chalk.red(`❌ Diretório não encontrado: ${projectDir}`));
132
+ process.exit(1);
133
+ }
134
+
135
+ // Determinar container ID
136
+ let targetContainerId = containerId;
137
+
138
+ if (!targetContainerId) {
139
+ targetContainerId = await config.getLinkedContainer();
140
+
141
+ if (!targetContainerId) {
142
+ console.error(chalk.red('❌ Nenhum container especificado'));
143
+ console.log(chalk.gray(' Use: mozhost deploy <container-id>'));
144
+ console.log(chalk.gray(' Ou: mozhost init (para vincular)'));
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ console.log(chalk.cyan.bold('\n🚀 Deploy MozHost\n'));
150
+
151
+ // Buscar informações do container
152
+ const spinner = ora('Verificando container...').start();
153
+ const containerInfo = await api.getContainer(targetContainerId);
154
+ spinner.text = 'Empacotando arquivos...';
155
+
156
+ // Ler arquivos ignorados
157
+ const ignorePatterns = await getIgnorePatterns(projectDir);
158
+
159
+ // Coletar arquivos
160
+ const files = await collectFiles(projectDir, ignorePatterns);
161
+
162
+ if (files.length === 0) {
163
+ spinner.fail(chalk.red('Nenhum arquivo encontrado para deploy'));
164
+ process.exit(1);
165
+ }
166
+
167
+ spinner.text = `Fazendo upload de ${files.length} arquivos...`;
168
+
169
+ // Fazer upload dos arquivos
170
+ await uploadFiles(targetContainerId, projectDir, files, spinner);
171
+
172
+ spinner.succeed(chalk.green('✅ Deploy concluído com sucesso!'));
173
+
174
+ console.log(chalk.gray(`\n📦 Arquivos enviados: ${files.length}`));
175
+ console.log(chalk.cyan(`🌐 URL: https://${containerInfo.container.domain}`));
176
+
177
+ // Perguntar se quer iniciar o container
178
+ if (containerInfo.container.status !== 'running') {
179
+ const { startNow } = await inquirer.prompt([
180
+ {
181
+ type: 'confirm',
182
+ name: 'startNow',
183
+ message: 'Container parado. Deseja iniciar agora?',
184
+ default: true
185
+ }
186
+ ]);
187
+
188
+ if (startNow) {
189
+ const startSpinner = ora('Iniciando container...').start();
190
+ await api.startContainer(targetContainerId);
191
+ startSpinner.succeed(chalk.green('✅ Container iniciado!'));
192
+ }
193
+ } else {
194
+ const { restart } = await inquirer.prompt([
195
+ {
196
+ type: 'confirm',
197
+ name: 'restart',
198
+ message: 'Deseja reiniciar o container para aplicar as mudanças?',
199
+ default: true
200
+ }
201
+ ]);
202
+
203
+ if (restart) {
204
+ const restartSpinner = ora('Reiniciando container...').start();
205
+ await api.restartContainer(targetContainerId);
206
+ restartSpinner.succeed(chalk.green('✅ Container reiniciado!'));
207
+ }
208
+ }
209
+
210
+ console.log(chalk.gray('\n💡 Dica: Use "mozhost logs <container>" para ver os logs\n'));
211
+
212
+ } catch (error) {
213
+ console.error(chalk.red('\n❌ Erro no deploy:'));
214
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
215
+ process.exit(1);
216
+ }
217
+ }
218
+
219
+ async function getIgnorePatterns(projectDir) {
220
+ const ignorePath = path.join(projectDir, '.mozhostignore');
221
+
222
+ if (fs.existsSync(ignorePath)) {
223
+ const content = await fs.readFile(ignorePath, 'utf-8');
224
+ return [...DEFAULT_IGNORE, ...content.split('\n').filter(line => line.trim() && !line.startsWith('#'))];
225
+ }
226
+
227
+ return DEFAULT_IGNORE;
228
+ }
229
+
230
+ async function collectFiles(dir, ignorePatterns, baseDir = dir) {
231
+ const files = [];
232
+ const items = await fs.readdir(dir);
233
+
234
+ for (const item of items) {
235
+ const fullPath = path.join(dir, item);
236
+ const relativePath = path.relative(baseDir, fullPath);
237
+
238
+ // Verificar se deve ignorar
239
+ if (shouldIgnore(relativePath, ignorePatterns)) {
240
+ continue;
241
+ }
242
+
243
+ const stat = await fs.stat(fullPath);
244
+
245
+ if (stat.isDirectory()) {
246
+ const subFiles = await collectFiles(fullPath, ignorePatterns, baseDir);
247
+ files.push(...subFiles);
248
+ } else if (stat.isFile()) {
249
+ files.push(relativePath);
250
+ }
251
+ }
252
+
253
+ return files;
254
+ }
255
+
256
+ function shouldIgnore(filePath, patterns) {
257
+ const normalized = filePath.replace(/\\/g, '/');
258
+
259
+ return patterns.some(pattern => {
260
+ // Pattern exato
261
+ if (normalized === pattern || normalized.startsWith(pattern + '/')) {
262
+ return true;
263
+ }
264
+
265
+ // Wildcard simples
266
+ if (pattern.includes('*')) {
267
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
268
+ return regex.test(normalized);
269
+ }
270
+
271
+ return false;
272
+ });
273
+ }
274
+
275
+ async function uploadFiles(containerId, projectDir, files, spinner) {
276
+ const client = await api.getClient();
277
+ const baseURL = await config.getApiUrl();
278
+
279
+ // Enviar em lotes para evitar timeout
280
+ const BATCH_SIZE = 20;
281
+
282
+ for (let i = 0; i < files.length; i += BATCH_SIZE) {
283
+ const batch = files.slice(i, i + BATCH_SIZE);
284
+
285
+ spinner.text = `Upload: ${Math.min(i + BATCH_SIZE, files.length)}/${files.length} arquivos...`;
286
+
287
+ for (const file of batch) {
288
+ const fullPath = path.join(projectDir, file);
289
+ const content = await fs.readFile(fullPath, 'utf-8');
290
+
291
+ await client.post(`${baseURL}/api/files/${containerId}`, {
292
+ path: file,
293
+ content: content
294
+ });
295
+ }
296
+ }
297
+ }
298
+
299
+ module.exports = {
300
+ init,
301
+ link,
302
+ deploy
303
+ };
@@ -0,0 +1,154 @@
1
+ const axios = require('axios');
2
+ const config = require('./config');
3
+ const chalk = require('chalk');
4
+
5
+ class ApiClient {
6
+ async getClient() {
7
+ const baseURL = await config.getApiUrl();
8
+ const token = await config.getToken();
9
+
10
+ const client = axios.create({
11
+ baseURL,
12
+ timeout: 30000,
13
+ headers: {
14
+ 'Content-Type': 'application/json'
15
+ }
16
+ });
17
+
18
+ // Add auth token if available
19
+ if (token) {
20
+ client.defaults.headers.common['Authorization'] = `Bearer ${token}`;
21
+ }
22
+
23
+ // Response interceptor for error handling
24
+ client.interceptors.response.use(
25
+ response => response,
26
+ async error => {
27
+ if (error.response?.status === 401) {
28
+ console.error(chalk.red('❌ Não autenticado. Use: mozhost auth'));
29
+ await config.clearToken();
30
+ process.exit(1);
31
+ }
32
+ throw error;
33
+ }
34
+ );
35
+
36
+ return client;
37
+ }
38
+
39
+ // Auth
40
+ async login(credentials) {
41
+ const client = await this.getClient();
42
+ const response = await client.post('/api/auth/login', credentials);
43
+ return response.data;
44
+ }
45
+
46
+ async verify() {
47
+ const client = await this.getClient();
48
+ const response = await client.get('/api/auth/verify');
49
+ return response.data;
50
+ }
51
+
52
+ // Containers
53
+ async listContainers() {
54
+ const client = await this.getClient();
55
+ const response = await client.get('/api/containers');
56
+ return response.data;
57
+ }
58
+
59
+ async getContainer(containerId) {
60
+ const client = await this.getClient();
61
+ const response = await client.get(`/api/containers/${containerId}`);
62
+ return response.data;
63
+ }
64
+
65
+ async createContainer(data) {
66
+ const client = await this.getClient();
67
+ const response = await client.post('/api/containers', data);
68
+ return response.data;
69
+ }
70
+
71
+ async startContainer(containerId) {
72
+ const client = await this.getClient();
73
+ const response = await client.post(`/api/containers/${containerId}/start`);
74
+ return response.data;
75
+ }
76
+
77
+ async stopContainer(containerId) {
78
+ const client = await this.getClient();
79
+ const response = await client.post(`/api/containers/${containerId}/stop`);
80
+ return response.data;
81
+ }
82
+
83
+ async restartContainer(containerId) {
84
+ const client = await this.getClient();
85
+ const response = await client.post(`/api/containers/${containerId}/restart`);
86
+ return response.data;
87
+ }
88
+
89
+ async deleteContainer(containerId) {
90
+ const client = await this.getClient();
91
+ const response = await client.delete(`/api/containers/${containerId}`);
92
+ return response.data;
93
+ }
94
+
95
+ async getContainerLogs(containerId, tail = 100) {
96
+ const client = await this.getClient();
97
+ const response = await client.get(`/api/containers/${containerId}/logs`, {
98
+ params: { tail }
99
+ });
100
+ return response.data;
101
+ }
102
+
103
+ // Files
104
+ async listFiles(containerId, dirPath = '/') {
105
+ const client = await this.getClient();
106
+ const response = await client.get(`/api/files/${containerId}`, {
107
+ params: { path: dirPath }
108
+ });
109
+ return response.data;
110
+ }
111
+
112
+ async uploadFile(containerId, filePath, content) {
113
+ const client = await this.getClient();
114
+ const response = await client.post(`/api/files/${containerId}`, {
115
+ path: filePath,
116
+ content
117
+ });
118
+ return response.data;
119
+ }
120
+
121
+ async uploadFiles(containerId, files) {
122
+ const client = await this.getClient();
123
+ const FormData = require('form-data');
124
+ const form = new FormData();
125
+
126
+ for (const file of files) {
127
+ form.append('files', file.content, {
128
+ filename: file.path,
129
+ filepath: file.path
130
+ });
131
+ }
132
+
133
+ const response = await client.post(
134
+ `/api/files/${containerId}/upload`,
135
+ form,
136
+ {
137
+ headers: form.getHeaders(),
138
+ maxContentLength: Infinity,
139
+ maxBodyLength: Infinity
140
+ }
141
+ );
142
+ return response.data;
143
+ }
144
+
145
+ async deleteFile(containerId, filePath) {
146
+ const client = await this.getClient();
147
+ const response = await client.delete(`/api/files/${containerId}`, {
148
+ data: { path: filePath }
149
+ });
150
+ return response.data;
151
+ }
152
+ }
153
+
154
+ module.exports = new ApiClient();
@@ -0,0 +1,114 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.mozhost');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+ const PROJECT_CONFIG = '.mozhost.json';
8
+
9
+ class Config {
10
+ constructor() {
11
+ this.ensureConfigDir();
12
+ }
13
+
14
+ ensureConfigDir() {
15
+ if (!fs.existsSync(CONFIG_DIR)) {
16
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
17
+ }
18
+ }
19
+
20
+ // Configuração global (token, API URL, etc)
21
+ async getGlobalConfig() {
22
+ try {
23
+ if (fs.existsSync(CONFIG_FILE)) {
24
+ return await fs.readJson(CONFIG_FILE);
25
+ }
26
+ return {};
27
+ } catch (error) {
28
+ return {};
29
+ }
30
+ }
31
+
32
+ async setGlobalConfig(config) {
33
+ await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
34
+ }
35
+
36
+ async updateGlobalConfig(updates) {
37
+ const current = await this.getGlobalConfig();
38
+ await this.setGlobalConfig({ ...current, ...updates });
39
+ }
40
+
41
+ async getToken() {
42
+ const config = await this.getGlobalConfig();
43
+ return config.token || null;
44
+ }
45
+
46
+ async setToken(token) {
47
+ await this.updateGlobalConfig({ token });
48
+ }
49
+
50
+ async clearToken() {
51
+ const config = await this.getGlobalConfig();
52
+ delete config.token;
53
+ delete config.user;
54
+ await this.setGlobalConfig(config);
55
+ }
56
+
57
+ async getApiUrl() {
58
+ const config = await this.getGlobalConfig();
59
+ return config.apiUrl || process.env.MOZHOST_API_URL || 'https://api.mozhost.topaziocoin.online';
60
+ }
61
+
62
+ async setApiUrl(apiUrl) {
63
+ await this.updateGlobalConfig({ apiUrl });
64
+ }
65
+
66
+ async getUser() {
67
+ const config = await this.getGlobalConfig();
68
+ return config.user || null;
69
+ }
70
+
71
+ async setUser(user) {
72
+ await this.updateGlobalConfig({ user });
73
+ }
74
+
75
+ // Configuração do projeto local
76
+ async getProjectConfig() {
77
+ try {
78
+ const projectConfigPath = path.join(process.cwd(), PROJECT_CONFIG);
79
+ if (fs.existsSync(projectConfigPath)) {
80
+ return await fs.readJson(projectConfigPath);
81
+ }
82
+ return null;
83
+ } catch (error) {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ async setProjectConfig(config) {
89
+ const projectConfigPath = path.join(process.cwd(), PROJECT_CONFIG);
90
+ await fs.writeJson(projectConfigPath, config, { spaces: 2 });
91
+ }
92
+
93
+ async getLinkedContainer() {
94
+ const projectConfig = await this.getProjectConfig();
95
+ return projectConfig?.container || null;
96
+ }
97
+
98
+ async linkContainer(containerId, containerName) {
99
+ await this.setProjectConfig({
100
+ container: containerId,
101
+ name: containerName,
102
+ linkedAt: new Date().toISOString()
103
+ });
104
+ }
105
+
106
+ async unlinkContainer() {
107
+ const projectConfigPath = path.join(process.cwd(), PROJECT_CONFIG);
108
+ if (fs.existsSync(projectConfigPath)) {
109
+ await fs.remove(projectConfigPath);
110
+ }
111
+ }
112
+ }
113
+
114
+ module.exports = new Config();