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 +106 -0
- package/package.json +35 -0
- package/src/commands/auth.js +122 -0
- package/src/commands/containers.js +269 -0
- package/src/commands/deploy.js +303 -0
- package/src/utils/api.js +154 -0
- package/src/utils/config.js +114 -0
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
|
+
};
|
package/src/utils/api.js
ADDED
|
@@ -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();
|