mozhost-cli 2.4.0 → 2.6.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 +41 -2
- package/package.json +7 -6
- package/src/commands/containers.js +174 -23
- package/src/commands/git.js +314 -0
- package/src/utils/api.js +49 -0
package/bin/mozhost.js
CHANGED
|
@@ -8,6 +8,7 @@ const deployCommands = require('../src/commands/deploy');
|
|
|
8
8
|
const domainCommands = require('../src/commands/domains');
|
|
9
9
|
const databaseCommands = require('../src/commands/databases'); // 👈 NOVO
|
|
10
10
|
const terminalCommands = require('../src/commands/terminal');
|
|
11
|
+
const gitCommands = require('../src/commands/git');
|
|
11
12
|
const packageJson = require('../package.json');
|
|
12
13
|
|
|
13
14
|
program
|
|
@@ -45,8 +46,8 @@ program
|
|
|
45
46
|
program
|
|
46
47
|
.command('create')
|
|
47
48
|
.description('Criar novo container')
|
|
48
|
-
.
|
|
49
|
-
.
|
|
49
|
+
.option('-n, --name <name>', 'Nome do container')
|
|
50
|
+
.option('-t, --type <type>', 'Tipo: bot-baileys, bot-whatsapp-web-js, nodejs, python, php')
|
|
50
51
|
.action(containerCommands.create);
|
|
51
52
|
|
|
52
53
|
program
|
|
@@ -186,6 +187,40 @@ program
|
|
|
186
187
|
.action(terminalCommands.exec);
|
|
187
188
|
|
|
188
189
|
|
|
190
|
+
// ============================================
|
|
191
|
+
// GIT / GITHUB COMMANDS
|
|
192
|
+
// ============================================
|
|
193
|
+
program
|
|
194
|
+
.command('git:auth')
|
|
195
|
+
.description('Conectar conta GitHub')
|
|
196
|
+
.action(gitCommands.auth);
|
|
197
|
+
|
|
198
|
+
program
|
|
199
|
+
.command('git:status')
|
|
200
|
+
.description('Ver status da conexão GitHub')
|
|
201
|
+
.action(gitCommands.status);
|
|
202
|
+
|
|
203
|
+
program
|
|
204
|
+
.command('git:repos')
|
|
205
|
+
.alias('git:ls')
|
|
206
|
+
.description('Listar repositórios GitHub')
|
|
207
|
+
.action(gitCommands.repos);
|
|
208
|
+
|
|
209
|
+
program
|
|
210
|
+
.command('git:connect [container]')
|
|
211
|
+
.description('Conectar repositório a um container')
|
|
212
|
+
.action(gitCommands.connect);
|
|
213
|
+
|
|
214
|
+
program
|
|
215
|
+
.command('git:deploys <container>')
|
|
216
|
+
.description('Ver histórico de deploys via Git')
|
|
217
|
+
.action(gitCommands.deploys);
|
|
218
|
+
|
|
219
|
+
program
|
|
220
|
+
.command('git:disconnect')
|
|
221
|
+
.description('Desconectar conta GitHub')
|
|
222
|
+
.action(gitCommands.disconnect);
|
|
223
|
+
|
|
189
224
|
// ============================================
|
|
190
225
|
// PARSE & HELP
|
|
191
226
|
// ============================================
|
|
@@ -206,5 +241,9 @@ if (!process.argv.slice(2).length) {
|
|
|
206
241
|
console.log(chalk.white(' mozhost domain:list # Listar domínios'));
|
|
207
242
|
console.log(chalk.white(' mozhost domain:add bot exemplo.com # Adicionar domínio'));
|
|
208
243
|
console.log(chalk.white(' mozhost domain:verify exemplo.com # Verificar domínio'));
|
|
244
|
+
console.log(chalk.white(' mozhost git:auth # Conectar GitHub'));
|
|
245
|
+
console.log(chalk.white(' mozhost git:repos # Listar repositórios'));
|
|
246
|
+
console.log(chalk.white(' mozhost git:connect # Conectar repo a container'));
|
|
247
|
+
console.log(chalk.white(' mozhost git:deploys <container> # Histórico de deploys'));
|
|
209
248
|
console.log();
|
|
210
249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mozhost-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Command-line interface for MozHost container platform",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
"author": "Eliobros Tech",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"archiver": "^6.0.1",
|
|
22
23
|
"axios": "^1.6.0",
|
|
23
|
-
"commander": "^11.1.0",
|
|
24
24
|
"chalk": "^4.1.2",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"commander": "^11.1.0",
|
|
26
|
+
"dotenv": "^16.3.1",
|
|
27
27
|
"form-data": "^4.0.0",
|
|
28
|
-
"archiver": "^6.0.1",
|
|
29
28
|
"fs-extra": "^11.1.1",
|
|
30
|
-
"
|
|
29
|
+
"inquirer": "^8.2.5",
|
|
30
|
+
"ora": "^5.4.1",
|
|
31
|
+
"ws": "^8.18.3"
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": ">=14.0.0"
|
|
@@ -15,21 +15,21 @@ async function resolveContainer(identifier) {
|
|
|
15
15
|
// Se não encontrar (404), busca na lista por nome
|
|
16
16
|
if (error.response?.status === 404 || error.response?.data?.error?.includes('not found')) {
|
|
17
17
|
const listResponse = await api.listContainers();
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
// Procura por nome, ID completo ou ID parcial
|
|
20
|
-
const found = listResponse.containers.find(c =>
|
|
21
|
-
c.name === identifier ||
|
|
20
|
+
const found = listResponse.containers.find(c =>
|
|
21
|
+
c.name === identifier ||
|
|
22
22
|
c.id === identifier ||
|
|
23
23
|
c.id.startsWith(identifier)
|
|
24
24
|
);
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
if (!found) {
|
|
27
27
|
// Procura containers similares para sugerir
|
|
28
|
-
const similar = listResponse.containers.filter(c =>
|
|
28
|
+
const similar = listResponse.containers.filter(c =>
|
|
29
29
|
c.name.toLowerCase().includes(identifier.toLowerCase()) ||
|
|
30
30
|
c.id.includes(identifier)
|
|
31
31
|
);
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
if (similar.length > 0) {
|
|
34
34
|
console.log(chalk.yellow('\n💡 Você quis dizer:'));
|
|
35
35
|
similar.forEach(c => {
|
|
@@ -38,10 +38,10 @@ async function resolveContainer(identifier) {
|
|
|
38
38
|
} else {
|
|
39
39
|
console.log(chalk.gray('\n💡 Use: mozhost ls para ver todos os containers'));
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
throw new Error(`Container "${identifier}" não encontrado`);
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
return found;
|
|
46
46
|
}
|
|
47
47
|
throw error;
|
|
@@ -61,7 +61,7 @@ async function list() {
|
|
|
61
61
|
|
|
62
62
|
if (response.containers.length === 0) {
|
|
63
63
|
console.log(chalk.yellow('\n⚠️ Nenhum container encontrado'));
|
|
64
|
-
console.log(chalk.gray(' Use: mozhost create
|
|
64
|
+
console.log(chalk.gray(' Use: mozhost create'));
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -100,24 +100,159 @@ async function list() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// ============================================
|
|
103
|
-
// CREATE - Criar novo container
|
|
103
|
+
// CREATE - Criar novo container (INTERATIVO)
|
|
104
104
|
// ============================================
|
|
105
105
|
async function create(options) {
|
|
106
106
|
try {
|
|
107
|
-
|
|
107
|
+
let name = options.name;
|
|
108
|
+
let type = options.type;
|
|
109
|
+
let prefix = '!'; // Prefix padrão para bots
|
|
110
|
+
|
|
111
|
+
// ========================================
|
|
112
|
+
// MODO INTERATIVO (se não passar opções)
|
|
113
|
+
// ========================================
|
|
114
|
+
if (!name || !type) {
|
|
115
|
+
console.log(chalk.cyan.bold('\n🔨 Criar Novo Container\n'));
|
|
116
|
+
|
|
117
|
+
const answers = await inquirer.prompt([
|
|
118
|
+
// 1. Nome do container
|
|
119
|
+
{
|
|
120
|
+
type: 'input',
|
|
121
|
+
name: 'name',
|
|
122
|
+
message: 'Nome do container:',
|
|
123
|
+
when: !name,
|
|
124
|
+
validate: (input) => {
|
|
125
|
+
if (!input || input.trim().length === 0) {
|
|
126
|
+
return 'Nome não pode ser vazio';
|
|
127
|
+
}
|
|
128
|
+
if (!/^[a-z0-9-]+$/.test(input)) {
|
|
129
|
+
return 'Use apenas letras minúsculas, números e hífens';
|
|
130
|
+
}
|
|
131
|
+
if (input.length < 3) {
|
|
132
|
+
return 'Nome deve ter pelo menos 3 caracteres';
|
|
133
|
+
}
|
|
134
|
+
if (input.length > 30) {
|
|
135
|
+
return 'Nome não pode ter mais de 30 caracteres';
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// 2. Categoria (Bot ou API)
|
|
142
|
+
{
|
|
143
|
+
type: 'list',
|
|
144
|
+
name: 'category',
|
|
145
|
+
message: 'O que você deseja criar?',
|
|
146
|
+
when: !type,
|
|
147
|
+
choices: [
|
|
148
|
+
{ name: '🤖 Bot WhatsApp', value: 'bot' },
|
|
149
|
+
{ name: '🌐 API / Aplicação Web', value: 'api' }
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// 3. Biblioteca do Bot (se escolheu Bot)
|
|
154
|
+
{
|
|
155
|
+
type: 'list',
|
|
156
|
+
name: 'botLibrary',
|
|
157
|
+
message: 'Escolha a biblioteca:',
|
|
158
|
+
when: (answers) => answers.category === 'bot',
|
|
159
|
+
choices: [
|
|
160
|
+
{ name: 'Baileys (recomendado, mais estável)', value: 'baileys' },
|
|
161
|
+
{ name: 'whatsapp-web.js (compatível com navegador)', value: 'whatsapp-web-js' }
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// 4. Prefix do Bot (se escolheu Bot)
|
|
166
|
+
{
|
|
167
|
+
type: 'input',
|
|
168
|
+
name: 'botPrefix',
|
|
169
|
+
message: 'Prefix dos comandos do bot:',
|
|
170
|
+
default: '!',
|
|
171
|
+
when: (answers) => answers.category === 'bot',
|
|
172
|
+
validate: (input) => {
|
|
173
|
+
if (!input || input.trim().length === 0) {
|
|
174
|
+
return 'Prefix não pode ser vazio';
|
|
175
|
+
}
|
|
176
|
+
if (input.length > 3) {
|
|
177
|
+
return 'Prefix deve ter no máximo 3 caracteres';
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// 5. Linguagem da API (se escolheu API)
|
|
184
|
+
{
|
|
185
|
+
type: 'list',
|
|
186
|
+
name: 'apiLanguage',
|
|
187
|
+
message: 'Escolha a linguagem:',
|
|
188
|
+
when: (answers) => answers.category === 'api',
|
|
189
|
+
choices: [
|
|
190
|
+
{ name: 'Node.js (JavaScript/TypeScript)', value: 'nodejs' },
|
|
191
|
+
{ name: 'Python (Flask/Django/FastAPI)', value: 'python' },
|
|
192
|
+
{ name: 'PHP (Laravel/Slim)', value: 'php' }
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
]);
|
|
108
196
|
|
|
109
|
-
|
|
110
|
-
|
|
197
|
+
// Atribuir valores
|
|
198
|
+
name = answers.name || name;
|
|
199
|
+
|
|
200
|
+
// Montar o tipo baseado nas escolhas
|
|
201
|
+
if (answers.category === 'bot') {
|
|
202
|
+
type = `bot-${answers.botLibrary}`;
|
|
203
|
+
prefix = answers.botPrefix || '!';
|
|
204
|
+
} else if (answers.category === 'api') {
|
|
205
|
+
type = answers.apiLanguage;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ========================================
|
|
210
|
+
// VALIDAÇÃO FINAL
|
|
211
|
+
// ========================================
|
|
212
|
+
const validTypes = ['nodejs', 'python', 'php', 'bot-baileys', 'bot-whatsapp-web-js'];
|
|
213
|
+
|
|
214
|
+
if (!validTypes.includes(type)) {
|
|
215
|
+
console.error(chalk.red('\n❌ Tipo inválido.'));
|
|
216
|
+
console.error(chalk.yellow('\n Bots:'));
|
|
217
|
+
console.error(chalk.white(' • bot-baileys'));
|
|
218
|
+
console.error(chalk.white(' • bot-whatsapp-web-js'));
|
|
219
|
+
console.error(chalk.yellow('\n APIs:'));
|
|
220
|
+
console.error(chalk.white(' • nodejs'));
|
|
221
|
+
console.error(chalk.white(' • python'));
|
|
222
|
+
console.error(chalk.white(' • php\n'));
|
|
111
223
|
process.exit(1);
|
|
112
224
|
}
|
|
113
225
|
|
|
226
|
+
// Detectar se é bot
|
|
227
|
+
const isBot = type.startsWith('bot-');
|
|
228
|
+
|
|
229
|
+
// ========================================
|
|
230
|
+
// CRIAR CONTAINER
|
|
231
|
+
// ========================================
|
|
114
232
|
console.log(chalk.cyan.bold('\n🔨 Criando container...\n'));
|
|
115
233
|
console.log(chalk.gray(` Nome: ${name}`));
|
|
116
|
-
console.log(chalk.gray(` Tipo: ${type}
|
|
234
|
+
console.log(chalk.gray(` Tipo: ${type}`));
|
|
235
|
+
if (isBot) {
|
|
236
|
+
console.log(chalk.gray(` Prefix: ${prefix}`));
|
|
237
|
+
}
|
|
238
|
+
console.log();
|
|
117
239
|
|
|
118
240
|
const spinner = ora('Criando container...').start();
|
|
119
241
|
|
|
120
|
-
|
|
242
|
+
// Payload para criar container
|
|
243
|
+
const payload = {
|
|
244
|
+
name,
|
|
245
|
+
type
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Se for bot, adicionar variáveis de ambiente
|
|
249
|
+
if (isBot) {
|
|
250
|
+
payload.env = {
|
|
251
|
+
PREFIX: prefix
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const response = await api.createContainer(payload);
|
|
121
256
|
|
|
122
257
|
spinner.succeed(chalk.green('✅ Container criado com sucesso!'));
|
|
123
258
|
|
|
@@ -129,9 +264,25 @@ async function create(options) {
|
|
|
129
264
|
console.log(chalk.cyan(` URL: https://${response.container.domain}`));
|
|
130
265
|
console.log(chalk.white(` Porta: ${response.container.port}`));
|
|
131
266
|
|
|
267
|
+
// ========================================
|
|
268
|
+
// PRÓXIMOS PASSOS (diferente para Bot vs API)
|
|
269
|
+
// ========================================
|
|
132
270
|
console.log(chalk.gray('\nPróximos passos:'));
|
|
133
|
-
|
|
134
|
-
|
|
271
|
+
|
|
272
|
+
if (isBot) {
|
|
273
|
+
console.log(chalk.white(` 1. mozhost start ${response.container.name}`));
|
|
274
|
+
console.log(chalk.white(` 2. Aguarde ~10 segundos para o bot inicializar`));
|
|
275
|
+
console.log(chalk.white(` 3. Acesse o painel web para escanear o QR Code:`));
|
|
276
|
+
console.log(chalk.cyan(` https://mozhost.topaziocoin.online/containers/${response.container.id}`));
|
|
277
|
+
console.log(chalk.gray(`\n 💡 Comandos do bot usarão o prefix: ${prefix}`));
|
|
278
|
+
console.log(chalk.gray(` Exemplo: ${prefix}ping`));
|
|
279
|
+
} else {
|
|
280
|
+
console.log(chalk.white(` 1. mozhost start ${response.container.name}`));
|
|
281
|
+
console.log(chalk.white(` 2. mozhost deploy ${response.container.name}`));
|
|
282
|
+
console.log(chalk.gray(`\n 💡 Ou use 'mozhost init' para inicializar um projeto local`));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log();
|
|
135
286
|
|
|
136
287
|
} catch (error) {
|
|
137
288
|
console.error(chalk.red('\n❌ Erro ao criar container:'));
|
|
@@ -161,7 +312,7 @@ async function start(identifier) {
|
|
|
161
312
|
try {
|
|
162
313
|
const spinner = ora('Resolvendo container...').start();
|
|
163
314
|
const container = await resolveContainer(identifier);
|
|
164
|
-
|
|
315
|
+
|
|
165
316
|
spinner.text = `Iniciando ${container.name}...`;
|
|
166
317
|
const response = await api.startContainer(container.id);
|
|
167
318
|
|
|
@@ -183,7 +334,7 @@ async function stop(identifier) {
|
|
|
183
334
|
try {
|
|
184
335
|
const spinner = ora('Resolvendo container...').start();
|
|
185
336
|
const container = await resolveContainer(identifier);
|
|
186
|
-
|
|
337
|
+
|
|
187
338
|
spinner.text = `Parando ${container.name}...`;
|
|
188
339
|
await api.stopContainer(container.id);
|
|
189
340
|
|
|
@@ -203,7 +354,7 @@ async function restart(identifier) {
|
|
|
203
354
|
try {
|
|
204
355
|
const spinner = ora('Resolvendo container...').start();
|
|
205
356
|
const container = await resolveContainer(identifier);
|
|
206
|
-
|
|
357
|
+
|
|
207
358
|
spinner.text = `Reiniciando ${container.name}...`;
|
|
208
359
|
await api.restartContainer(container.id);
|
|
209
360
|
|
|
@@ -258,7 +409,7 @@ async function logs(identifier, options) {
|
|
|
258
409
|
try {
|
|
259
410
|
const spinner = ora('Resolvendo container...').start();
|
|
260
411
|
const container = await resolveContainer(identifier);
|
|
261
|
-
|
|
412
|
+
|
|
262
413
|
spinner.text = `Carregando logs de ${container.name}...`;
|
|
263
414
|
const response = await api.getContainerLogs(container.id, options.lines);
|
|
264
415
|
|
|
@@ -288,9 +439,9 @@ async function info(identifier) {
|
|
|
288
439
|
try {
|
|
289
440
|
const spinner = ora('Resolvendo container...').start();
|
|
290
441
|
const container = await resolveContainer(identifier);
|
|
291
|
-
|
|
442
|
+
|
|
292
443
|
spinner.text = `Carregando informações de ${container.name}...`;
|
|
293
|
-
|
|
444
|
+
|
|
294
445
|
// Busca detalhes completos (com stats se disponível)
|
|
295
446
|
const response = await api.getContainer(container.id);
|
|
296
447
|
spinner.stop();
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// git:auth - Autenticar com GitHub via Device Flow
|
|
8
|
+
// ============================================
|
|
9
|
+
async function auth() {
|
|
10
|
+
try {
|
|
11
|
+
console.log(chalk.cyan.bold('\n🔑 Conectar GitHub\n'));
|
|
12
|
+
|
|
13
|
+
const spinner = ora('Iniciando autenticação...').start();
|
|
14
|
+
const response = await api.githubDeviceStart();
|
|
15
|
+
spinner.stop();
|
|
16
|
+
|
|
17
|
+
console.log(chalk.yellow.bold(`\n📋 Código: ${response.user_code}\n`));
|
|
18
|
+
console.log(chalk.white(`1. Acesse: ${chalk.cyan.underline(response.verification_uri)}`));
|
|
19
|
+
console.log(chalk.white(`2. Cole o código: ${chalk.yellow.bold(response.user_code)}`));
|
|
20
|
+
console.log(chalk.white('3. Autorize o acesso\n'));
|
|
21
|
+
|
|
22
|
+
const pollSpinner = ora('Aguardando autorização no navegador...').start();
|
|
23
|
+
|
|
24
|
+
const interval = (response.interval || 5) * 1000;
|
|
25
|
+
const maxAttempts = Math.floor((response.expires_in || 900) / (response.interval || 5));
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
28
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const pollResult = await api.githubDevicePoll(response.device_code);
|
|
32
|
+
|
|
33
|
+
if (pollResult.status === 'connected') {
|
|
34
|
+
pollSpinner.succeed(chalk.green(`✅ GitHub conectado como @${pollResult.github_username}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (pollResult.status === 'slow_down') {
|
|
39
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.response?.status === 400) {
|
|
43
|
+
pollSpinner.fail(chalk.red('❌ Código expirado. Tente novamente.'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pollSpinner.fail(chalk.red('❌ Tempo esgotado. Tente novamente.'));
|
|
50
|
+
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(chalk.red('\n❌ Erro ao autenticar:'));
|
|
53
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================
|
|
59
|
+
// git:status - Ver status da conexão GitHub
|
|
60
|
+
// ============================================
|
|
61
|
+
async function status() {
|
|
62
|
+
try {
|
|
63
|
+
const spinner = ora('Verificando conexão GitHub...').start();
|
|
64
|
+
const response = await api.githubStatus();
|
|
65
|
+
spinner.stop();
|
|
66
|
+
|
|
67
|
+
if (response.connected) {
|
|
68
|
+
console.log(chalk.green(`\n✅ GitHub conectado como @${response.github_username}\n`));
|
|
69
|
+
} else {
|
|
70
|
+
console.log(chalk.yellow('\n⚠️ GitHub não conectado'));
|
|
71
|
+
console.log(chalk.gray(' Use: mozhost git:auth\n'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(chalk.red('\n❌ Erro ao verificar status:'));
|
|
76
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============================================
|
|
82
|
+
// git:repos - Listar repositórios do GitHub
|
|
83
|
+
// ============================================
|
|
84
|
+
async function repos() {
|
|
85
|
+
try {
|
|
86
|
+
const spinner = ora('Carregando repositórios...').start();
|
|
87
|
+
const response = await api.githubRepos();
|
|
88
|
+
spinner.stop();
|
|
89
|
+
|
|
90
|
+
if (!response.repos || response.repos.length === 0) {
|
|
91
|
+
console.log(chalk.yellow('\n⚠️ Nenhum repositório encontrado\n'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(chalk.cyan.bold(`\n📦 Seus Repositórios (${response.repos.length})\n`));
|
|
96
|
+
|
|
97
|
+
for (const repo of response.repos) {
|
|
98
|
+
const visibility = repo.private ? chalk.red('🔒 privado') : chalk.green('🌍 público');
|
|
99
|
+
console.log(chalk.white(` ${chalk.bold(repo.full_name)} ${visibility}`));
|
|
100
|
+
console.log(chalk.gray(` Branch: ${repo.default_branch} | ${repo.url}`));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log();
|
|
104
|
+
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error.response?.status === 404) {
|
|
107
|
+
console.log(chalk.yellow('\n⚠️ GitHub não conectado'));
|
|
108
|
+
console.log(chalk.gray(' Use: mozhost git:auth\n'));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.error(chalk.red('\n❌ Erro ao listar repositórios:'));
|
|
112
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================
|
|
118
|
+
// git:connect <container> - Conectar repo a container
|
|
119
|
+
// ============================================
|
|
120
|
+
async function connect(containerId) {
|
|
121
|
+
try {
|
|
122
|
+
console.log(chalk.cyan.bold('\n🔗 Conectar Repositório ao Container\n'));
|
|
123
|
+
|
|
124
|
+
// Se não passou container, listar para selecionar
|
|
125
|
+
if (!containerId) {
|
|
126
|
+
const listSpinner = ora('Carregando containers...').start();
|
|
127
|
+
const containersRes = await api.listContainers();
|
|
128
|
+
listSpinner.stop();
|
|
129
|
+
|
|
130
|
+
if (!containersRes.containers || containersRes.containers.length === 0) {
|
|
131
|
+
console.log(chalk.red('\n❌ Nenhum container encontrado'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const { selectedContainer } = await inquirer.prompt([
|
|
136
|
+
{
|
|
137
|
+
type: 'list',
|
|
138
|
+
name: 'selectedContainer',
|
|
139
|
+
message: 'Selecione o container:',
|
|
140
|
+
choices: containersRes.containers.map(c => ({
|
|
141
|
+
name: `${c.name} (${c.type}) - ${c.status}`,
|
|
142
|
+
value: c.id
|
|
143
|
+
}))
|
|
144
|
+
}
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
containerId = selectedContainer;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Listar repositórios
|
|
151
|
+
const repoSpinner = ora('Carregando repositórios...').start();
|
|
152
|
+
const reposRes = await api.githubRepos();
|
|
153
|
+
repoSpinner.stop();
|
|
154
|
+
|
|
155
|
+
if (!reposRes.repos || reposRes.repos.length === 0) {
|
|
156
|
+
console.log(chalk.red('\n❌ Nenhum repositório encontrado'));
|
|
157
|
+
console.log(chalk.gray(' Verifique se o GitHub está conectado: mozhost git:status'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Selecionar repositório
|
|
162
|
+
const { selectedRepo } = await inquirer.prompt([
|
|
163
|
+
{
|
|
164
|
+
type: 'list',
|
|
165
|
+
name: 'selectedRepo',
|
|
166
|
+
message: 'Selecione o repositório:',
|
|
167
|
+
choices: reposRes.repos.map(r => ({
|
|
168
|
+
name: `${r.full_name} ${r.private ? '🔒' : '🌍'} (${r.default_branch})`,
|
|
169
|
+
value: r
|
|
170
|
+
}))
|
|
171
|
+
}
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
// Listar branches do repositório
|
|
175
|
+
const [owner, repo] = selectedRepo.full_name.split('/');
|
|
176
|
+
const branchSpinner = ora('Carregando branches...').start();
|
|
177
|
+
const branchesRes = await api.githubBranches(owner, repo);
|
|
178
|
+
branchSpinner.stop();
|
|
179
|
+
|
|
180
|
+
let branch = selectedRepo.default_branch;
|
|
181
|
+
|
|
182
|
+
if (branchesRes.branches && branchesRes.branches.length > 1) {
|
|
183
|
+
const { selectedBranch } = await inquirer.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: 'list',
|
|
186
|
+
name: 'selectedBranch',
|
|
187
|
+
message: 'Selecione a branch:',
|
|
188
|
+
choices: branchesRes.branches.map(b => ({
|
|
189
|
+
name: `${b.name} ${b.name === selectedRepo.default_branch ? chalk.gray('(default)') : ''}`,
|
|
190
|
+
value: b.name
|
|
191
|
+
}))
|
|
192
|
+
}
|
|
193
|
+
]);
|
|
194
|
+
branch = selectedBranch;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Confirmar
|
|
198
|
+
const { confirm } = await inquirer.prompt([
|
|
199
|
+
{
|
|
200
|
+
type: 'confirm',
|
|
201
|
+
name: 'confirm',
|
|
202
|
+
message: `Conectar ${chalk.cyan(selectedRepo.full_name)}@${chalk.yellow(branch)} ao container?`,
|
|
203
|
+
default: true
|
|
204
|
+
}
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
if (!confirm) {
|
|
208
|
+
console.log(chalk.gray('\nOperação cancelada.'));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Conectar
|
|
213
|
+
const connectSpinner = ora('Conectando repositório e clonando código...').start();
|
|
214
|
+
const result = await api.githubConnect({
|
|
215
|
+
container_id: containerId,
|
|
216
|
+
repo_url: selectedRepo.url,
|
|
217
|
+
repo_name: selectedRepo.full_name,
|
|
218
|
+
branch
|
|
219
|
+
});
|
|
220
|
+
connectSpinner.succeed(chalk.green('✅ Repositório conectado com sucesso!'));
|
|
221
|
+
|
|
222
|
+
console.log(chalk.gray(`\n Repo: ${selectedRepo.full_name}`));
|
|
223
|
+
console.log(chalk.gray(` Branch: ${branch}`));
|
|
224
|
+
console.log(chalk.gray(` Webhook: ${result.webhook_registered ? '✅ Ativo' : '⚠️ Não registrado'}`));
|
|
225
|
+
console.log(chalk.cyan('\n💡 Agora cada push nessa branch fará deploy automático!\n'));
|
|
226
|
+
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error.response?.status === 404) {
|
|
229
|
+
console.log(chalk.yellow('\n⚠️ GitHub não conectado'));
|
|
230
|
+
console.log(chalk.gray(' Use: mozhost git:auth\n'));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
console.error(chalk.red('\n❌ Erro ao conectar repositório:'));
|
|
234
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================
|
|
240
|
+
// git:deploys <container> - Ver histórico de deploys
|
|
241
|
+
// ============================================
|
|
242
|
+
async function deploys(containerId) {
|
|
243
|
+
try {
|
|
244
|
+
const spinner = ora('Carregando histórico de deploys...').start();
|
|
245
|
+
const response = await api.githubDeploys(containerId);
|
|
246
|
+
spinner.stop();
|
|
247
|
+
|
|
248
|
+
if (!response.deploys || response.deploys.length === 0) {
|
|
249
|
+
console.log(chalk.yellow('\n⚠️ Nenhum deploy encontrado para este container\n'));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log(chalk.cyan.bold(`\n📋 Histórico de Deploys (${response.deploys.length})\n`));
|
|
254
|
+
|
|
255
|
+
for (const deploy of response.deploys) {
|
|
256
|
+
const statusIcon = deploy.status === 'success' ? chalk.green('✅')
|
|
257
|
+
: deploy.status === 'failed' ? chalk.red('❌')
|
|
258
|
+
: chalk.yellow('⏳');
|
|
259
|
+
|
|
260
|
+
const sha = deploy.commit_sha ? deploy.commit_sha.substring(0, 7) : '-------';
|
|
261
|
+
const date = new Date(deploy.created_at).toLocaleString('pt-BR');
|
|
262
|
+
|
|
263
|
+
console.log(` ${statusIcon} ${chalk.gray(sha)} ${chalk.white(deploy.commit_message || 'Sem mensagem')}`);
|
|
264
|
+
console.log(chalk.gray(` ${date} | via ${deploy.triggered_by}`));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
console.log();
|
|
268
|
+
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(chalk.red('\n❌ Erro ao buscar deploys:'));
|
|
271
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================
|
|
277
|
+
// git:disconnect - Desconectar GitHub
|
|
278
|
+
// ============================================
|
|
279
|
+
async function disconnect() {
|
|
280
|
+
try {
|
|
281
|
+
const { confirm } = await inquirer.prompt([
|
|
282
|
+
{
|
|
283
|
+
type: 'confirm',
|
|
284
|
+
name: 'confirm',
|
|
285
|
+
message: chalk.red('Tem certeza que deseja desconectar o GitHub? Todos os webhooks serão removidos.'),
|
|
286
|
+
default: false
|
|
287
|
+
}
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
if (!confirm) {
|
|
291
|
+
console.log(chalk.gray('\nOperação cancelada.'));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const spinner = ora('Desconectando GitHub...').start();
|
|
296
|
+
await api.githubDisconnect();
|
|
297
|
+
spinner.succeed(chalk.green('✅ GitHub desconectado com sucesso!'));
|
|
298
|
+
console.log();
|
|
299
|
+
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error(chalk.red('\n❌ Erro ao desconectar:'));
|
|
302
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
auth,
|
|
309
|
+
status,
|
|
310
|
+
repos,
|
|
311
|
+
connect,
|
|
312
|
+
deploys,
|
|
313
|
+
disconnect
|
|
314
|
+
};
|
package/src/utils/api.js
CHANGED
|
@@ -232,6 +232,55 @@ class ApiClient {
|
|
|
232
232
|
return response.data;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
// GitHub
|
|
236
|
+
async githubDeviceStart() {
|
|
237
|
+
const client = await this.getClient();
|
|
238
|
+
const response = await client.post('/api/github/device/start');
|
|
239
|
+
return response.data;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async githubDevicePoll(deviceCode) {
|
|
243
|
+
const client = await this.getClient();
|
|
244
|
+
const response = await client.post('/api/github/device/poll', { device_code: deviceCode });
|
|
245
|
+
return response.data;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async githubStatus() {
|
|
249
|
+
const client = await this.getClient();
|
|
250
|
+
const response = await client.get('/api/github/status');
|
|
251
|
+
return response.data;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async githubRepos() {
|
|
255
|
+
const client = await this.getClient();
|
|
256
|
+
const response = await client.get('/api/github/repos');
|
|
257
|
+
return response.data;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async githubBranches(owner, repo) {
|
|
261
|
+
const client = await this.getClient();
|
|
262
|
+
const response = await client.get(`/api/github/repos/${owner}/${repo}/branches`);
|
|
263
|
+
return response.data;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async githubConnect(data) {
|
|
267
|
+
const client = await this.getClient();
|
|
268
|
+
const response = await client.post('/api/github/connect', data);
|
|
269
|
+
return response.data;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async githubDeploys(containerId) {
|
|
273
|
+
const client = await this.getClient();
|
|
274
|
+
const response = await client.get(`/api/github/deploys/${containerId}`);
|
|
275
|
+
return response.data;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async githubDisconnect() {
|
|
279
|
+
const client = await this.getClient();
|
|
280
|
+
const response = await client.delete('/api/github/disconnect');
|
|
281
|
+
return response.data;
|
|
282
|
+
}
|
|
283
|
+
|
|
235
284
|
// Terminal
|
|
236
285
|
async executeCommand(containerId, command) {
|
|
237
286
|
const client = await this.getClient();
|