@devquest/cli 1.0.6 → 1.0.9
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/dist/commands/clone.js
CHANGED
|
@@ -47,15 +47,13 @@ exports.cloneCommand = new commander_1.Command('clone')
|
|
|
47
47
|
await git.clone(finalUrl, destPath);
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
|
-
// Fallback para o repositório central de templates
|
|
51
50
|
finalUrl = `https://github.com/KalmonJ/devquest-templates.git`;
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
|
-
// Em produção, sempre usamos o repositório central devquest-templates
|
|
55
53
|
else {
|
|
56
54
|
finalUrl = `https://github.com/KalmonJ/devquest-templates.git`;
|
|
57
55
|
}
|
|
58
|
-
// Se o URL final for o repositório central,
|
|
56
|
+
// Se o URL final for o repositório central, extrair pasta específica
|
|
59
57
|
if (finalUrl.includes('devquest-templates.git')) {
|
|
60
58
|
console.log(chalk_1.default.gray(`Clonando template de devquest-templates...`));
|
|
61
59
|
const tempDir = path_1.default.join(process.cwd(), `.devquest-temp-${id}`);
|
|
@@ -63,22 +61,17 @@ exports.cloneCommand = new commander_1.Command('clone')
|
|
|
63
61
|
await git.clone(finalUrl, tempDir, ['--depth', '1']);
|
|
64
62
|
const templateSourcePath = path_1.default.join(tempDir, id);
|
|
65
63
|
if (!fs_1.default.existsSync(templateSourcePath)) {
|
|
66
|
-
// Cleanup antes do erro
|
|
67
64
|
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
68
65
|
throw new Error(`Template '${id}' não encontrado no repositório de templates.`);
|
|
69
66
|
}
|
|
70
|
-
// Move a pasta do template para o destino final
|
|
71
|
-
// No Node < 16.7.0 fs.cpSync não existe, mas como o usuário usa node v24, podemos usar
|
|
72
67
|
fs_1.default.cpSync(templateSourcePath, destPath, { recursive: true });
|
|
73
|
-
// Remove a pasta temporária
|
|
74
68
|
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
75
|
-
// Inicializa
|
|
69
|
+
// Inicializa o novo repo para o usuário
|
|
76
70
|
const newGit = (0, simple_git_1.default)(destPath);
|
|
77
71
|
await newGit.init();
|
|
78
72
|
console.log(chalk_1.default.cyan(`🌐 Template extraído com sucesso do repositório central.`));
|
|
79
73
|
}
|
|
80
74
|
else if (!env_1.IS_DEV || !cloneUrl.startsWith('file://')) {
|
|
81
|
-
// Fallback genérico para clone direto (caso o URL seja outro)
|
|
82
75
|
const git = (0, simple_git_1.default)();
|
|
83
76
|
await git.clone(finalUrl, destPath);
|
|
84
77
|
}
|
|
@@ -86,15 +79,24 @@ exports.cloneCommand = new commander_1.Command('clone')
|
|
|
86
79
|
if (fs_1.default.existsSync(path_1.default.join(destPath, 'package.json'))) {
|
|
87
80
|
console.log(chalk_1.default.gray(`\n📦 Instalando dependências...`));
|
|
88
81
|
try {
|
|
89
|
-
// Usamos stdio: 'inherit' para mostrar o progresso para o usuário
|
|
90
82
|
(0, child_process_1.execSync)('npm install', { cwd: destPath, stdio: 'inherit' });
|
|
91
83
|
console.log(chalk_1.default.green(`✅ Dependências instaladas com sucesso.`));
|
|
92
84
|
}
|
|
93
85
|
catch (e) {
|
|
94
|
-
console.log(chalk_1.default.yellow(`\n⚠️ Aviso: Não foi possível instalar as dependências automaticamente
|
|
86
|
+
console.log(chalk_1.default.yellow(`\n⚠️ Aviso: Não foi possível instalar as dependências automaticamente.`));
|
|
95
87
|
}
|
|
96
88
|
}
|
|
97
|
-
|
|
89
|
+
// 4. Primeiro commit para limpar a visão do VS Code (Slate limpo)
|
|
90
|
+
try {
|
|
91
|
+
const finalGit = (0, simple_git_1.default)(destPath);
|
|
92
|
+
await finalGit.add('.');
|
|
93
|
+
await finalGit.commit('feat: initial boilerplate');
|
|
94
|
+
console.log(chalk_1.default.cyan(`✅ Projeto inicializado com commit de histórico.`));
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
// Silencioso se o git global não estiver configurado
|
|
98
|
+
}
|
|
99
|
+
console.log(chalk_1.default.green(`\n✅ Desafio clonado com sucesso em ./${targetFolderName}`));
|
|
98
100
|
console.log(chalk_1.default.yellow(`\nPróximos passos:`));
|
|
99
101
|
console.log(` cd ${targetFolderName}`);
|
|
100
102
|
console.log(` Leia o README.md e comece a desenvolver.`);
|
package/dist/commands/submit.js
CHANGED
|
@@ -21,11 +21,8 @@ exports.submitCommand = new commander_1.Command('submit')
|
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
const currentDir = process.cwd();
|
|
24
|
-
if (!fs_1.default.existsSync(path_1.default.join(currentDir, '.git'))) {
|
|
25
|
-
console.log(chalk_1.default.red('Não encontrei um repositório git no diretório atual. Execute esse comando na raiz do desafio.'));
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
24
|
const git = (0, simple_git_1.default)(currentDir);
|
|
25
|
+
const hasGit = fs_1.default.existsSync(path_1.default.join(currentDir, '.git'));
|
|
29
26
|
const packageJsonPath = path_1.default.join(currentDir, 'package.json');
|
|
30
27
|
let challengeId = 'unknown';
|
|
31
28
|
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
@@ -47,51 +44,81 @@ exports.submitCommand = new commander_1.Command('submit')
|
|
|
47
44
|
catch (e) {
|
|
48
45
|
console.log(chalk_1.default.yellow('⚠️ Não foi possível determinar o estágio na API. Assumindo Estágio 1.'));
|
|
49
46
|
}
|
|
50
|
-
console.log(chalk_1.default.blue(
|
|
47
|
+
console.log(chalk_1.default.blue(`\n📌 Você está no Estágio ${stageOrder} deste desafio.`));
|
|
48
|
+
// 1. RODAR TESTES LOCAIS REAIS (Tester.ts)
|
|
51
49
|
const { DevQuestTester } = require('../utils/tester');
|
|
52
50
|
const tester = new DevQuestTester(currentDir, challengeId, stageOrder);
|
|
53
51
|
const passed = await tester.execute();
|
|
54
52
|
if (!passed) {
|
|
55
|
-
console.log(chalk_1.default.red('\n🚫 Execução interrompida pela falha dos testes
|
|
53
|
+
console.log(chalk_1.default.red('\n🚫 Execução local interrompida pela falha dos testes. Corrija o código antes de submeter.'));
|
|
56
54
|
process.exit(1);
|
|
57
55
|
}
|
|
58
|
-
console.log(chalk_1.default.green('\n✅ Integração aprovada!
|
|
56
|
+
console.log(chalk_1.default.green('\n✅ Integração local aprovada! Preparando envio...'));
|
|
59
57
|
try {
|
|
60
|
-
//
|
|
61
|
-
console.log(chalk_1.default.gray('
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
// 2. Coletar arquivos para envio (Opção B - via API POST)
|
|
59
|
+
console.log(chalk_1.default.gray('Empacotando código-fonte para submissão remota...'));
|
|
60
|
+
const files = {};
|
|
61
|
+
const srcDir = path_1.default.join(currentDir, 'src');
|
|
62
|
+
// Helper para ler arquivos recursivamente
|
|
63
|
+
const collectFiles = (dir, baseDir) => {
|
|
64
|
+
const items = fs_1.default.readdirSync(dir);
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
const fullPath = path_1.default.join(dir, item);
|
|
67
|
+
const relativePath = path_1.default.relative(baseDir, fullPath);
|
|
68
|
+
if (fs_1.default.statSync(fullPath).isDirectory()) {
|
|
69
|
+
if (item !== 'node_modules' && item !== '.git' && item !== 'dist') {
|
|
70
|
+
collectFiles(fullPath, baseDir);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Apenas arquivos de texto/código (simplificado)
|
|
75
|
+
files[relativePath] = fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
76
|
+
}
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
};
|
|
79
|
+
if (fs_1.default.existsSync(srcDir))
|
|
80
|
+
collectFiles(srcDir, currentDir);
|
|
81
|
+
if (fs_1.default.existsSync(packageJsonPath))
|
|
82
|
+
files['package.json'] = fs_1.default.readFileSync(packageJsonPath, 'utf-8');
|
|
83
|
+
const tsConfigPath = path_1.default.join(currentDir, 'tsconfig.json');
|
|
84
|
+
if (fs_1.default.existsSync(tsConfigPath))
|
|
85
|
+
files['tsconfig.json'] = fs_1.default.readFileSync(tsConfigPath, 'utf-8');
|
|
86
|
+
// 3. Commit local pra manter o histórico do usuário (se houver git)
|
|
87
|
+
if (hasGit) {
|
|
88
|
+
await git.add('.');
|
|
89
|
+
const status = await git.status();
|
|
90
|
+
if (status.staged.length > 0) {
|
|
91
|
+
await git.commit(`feat: submissão estágio ${stageOrder}`);
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
// 4. Envio via API POST
|
|
95
|
+
console.log(chalk_1.default.gray('Enviando payload para o servidor DevQuest...'));
|
|
96
|
+
const subResponse = await axios_1.default.post(`${env_1.API_BASE}/challenges/${challengeId}/submit`, {
|
|
97
|
+
files,
|
|
98
|
+
stageOrder
|
|
99
|
+
}, {
|
|
100
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
101
|
+
});
|
|
102
|
+
if (!subResponse.data.success) {
|
|
103
|
+
throw new Error(subResponse.data.error || 'Erro desconhecido na API');
|
|
104
|
+
}
|
|
105
|
+
console.log(chalk_1.default.green(`🚀 Código recebido! (Submissão: ${subResponse.data.submissionId})`));
|
|
106
|
+
console.log(chalk_1.default.cyan('📡 Conectando à Sandbox remota para avaliação final...'));
|
|
107
|
+
// 5. SSE Logs para simular execução
|
|
85
108
|
startStreamingLogs(token, currentDir, challengeId, stageOrder);
|
|
86
109
|
}
|
|
87
110
|
catch (err) {
|
|
88
111
|
console.error(chalk_1.default.red('\n❌ Falha ao enviar a submissão.'));
|
|
89
|
-
|
|
112
|
+
if (err.response) {
|
|
113
|
+
console.error(chalk_1.default.red(`API: ${err.response.data.error || err.response.statusText}`));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.error(chalk_1.default.red(err.message));
|
|
117
|
+
}
|
|
90
118
|
}
|
|
91
119
|
});
|
|
92
120
|
async function startStreamingLogs(token, challengeDir, challengeId, stageOrder) {
|
|
93
121
|
const url = `${env_1.SSE_BASE}?token=${encodeURIComponent(token)}&challengeId=${encodeURIComponent(challengeId)}&stageOrder=${stageOrder}`;
|
|
94
|
-
console.log(chalk_1.default.cyan('📡 Conectando à Sandbox segurizada (MicroVM Firecracker)...'));
|
|
95
122
|
try {
|
|
96
123
|
const response = await fetch(url);
|
|
97
124
|
if (!response.body) {
|
|
@@ -105,7 +132,7 @@ async function startStreamingLogs(token, challengeDir, challengeId, stageOrder)
|
|
|
105
132
|
if (done)
|
|
106
133
|
break;
|
|
107
134
|
const chunk = decoder.decode(value, { stream: true });
|
|
108
|
-
const lines = chunk.split('
|
|
135
|
+
const lines = chunk.split('\n');
|
|
109
136
|
for (const line of lines) {
|
|
110
137
|
if (line.startsWith('data: ')) {
|
|
111
138
|
const dataStr = line.slice(6);
|
|
@@ -122,20 +149,17 @@ async function startStreamingLogs(token, challengeDir, challengeId, stageOrder)
|
|
|
122
149
|
console.log(chalk_1.default.red('❌ ' + payload.message));
|
|
123
150
|
break;
|
|
124
151
|
case 'FINISHED':
|
|
125
|
-
console.log(chalk_1.default.green.bold('
|
|
152
|
+
console.log(chalk_1.default.green.bold('\n🎉 Parabéns! Você completou este estágio.'));
|
|
126
153
|
process.exit(0);
|
|
127
154
|
case 'ERROR':
|
|
128
|
-
console.log(chalk_1.default.red.bold('
|
|
155
|
+
console.log(chalk_1.default.red.bold('\n💥 Erro na execução: ' + payload.message));
|
|
129
156
|
process.exit(1);
|
|
130
157
|
}
|
|
131
158
|
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
// ignores bad json fragments
|
|
134
|
-
}
|
|
159
|
+
catch (e) { }
|
|
135
160
|
}
|
|
136
161
|
}
|
|
137
162
|
}
|
|
138
|
-
console.log(chalk_1.default.gray('Conexão encerrada pelo servidor.'));
|
|
139
163
|
}
|
|
140
164
|
catch (err) {
|
|
141
165
|
console.error(chalk_1.default.red('Erro na escuta da MicroVM: ' + err.message));
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runStage = runStage;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
/**
|
|
12
|
+
* Suite de testes para o desafio Build Your Own Formatter
|
|
13
|
+
* Estágio 1: Scanner / Lexer
|
|
14
|
+
*/
|
|
15
|
+
async function runStage(stage, cwd) {
|
|
16
|
+
console.log(chalk_1.default.blue(`\n[Test Suite] Avaliando Estágio ${stage} para build-your-own-formatter...`));
|
|
17
|
+
if (stage === 1) {
|
|
18
|
+
return await testStage1(cwd);
|
|
19
|
+
}
|
|
20
|
+
console.log(chalk_1.default.yellow(`[!] Nenhum teste definido para o estágio ${stage}.`));
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
async function testStage1(cwd) {
|
|
24
|
+
console.log(chalk_1.default.gray(`[Lexer Test] Validando saída do scanner para tokens básicos...`));
|
|
25
|
+
// Cria um arquivo de teste temporário para o formatador ler
|
|
26
|
+
const testFilePath = path_1.default.join(cwd, 'test_input.ts');
|
|
27
|
+
const testContent = `const x = 10;`;
|
|
28
|
+
fs_1.default.writeFileSync(testFilePath, testContent);
|
|
29
|
+
try {
|
|
30
|
+
// Executa o código do usuário: node dist/index.js test_input.ts
|
|
31
|
+
// Assumindo que o usuário usa a estrutura padrão: ts-node ou dist/index.js
|
|
32
|
+
const entryPoint = fs_1.default.existsSync(path_1.default.join(cwd, 'dist/index.js'))
|
|
33
|
+
? 'dist/index.js'
|
|
34
|
+
: 'src/index.ts';
|
|
35
|
+
const cmd = entryPoint.endsWith('.ts') ? 'npx ts-node' : 'node';
|
|
36
|
+
const result = (0, child_process_1.spawnSync)(`${cmd} ${entryPoint} ${testFilePath}`, {
|
|
37
|
+
shell: true,
|
|
38
|
+
cwd,
|
|
39
|
+
encoding: 'utf-8'
|
|
40
|
+
});
|
|
41
|
+
if (result.status !== 0) {
|
|
42
|
+
console.log(chalk_1.default.red(`❌ Erro ao executar o código: ${result.stderr}`));
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const output = result.stdout;
|
|
46
|
+
console.log(chalk_1.default.gray(`Saída do usuário:\n${output}`));
|
|
47
|
+
// Validação mock para o estágio 1: o usuário deve imprimir algo relacionado a tokens
|
|
48
|
+
// Na plataforma real, esperaríamos JSON estruturado, aqui buscamos palavras-chave
|
|
49
|
+
const lowerOutput = output.toLowerCase();
|
|
50
|
+
const hasTokens = lowerOutput.includes('token') || lowerOutput.includes('const') || lowerOutput.includes('x');
|
|
51
|
+
if (hasTokens) {
|
|
52
|
+
console.log(chalk_1.default.green(`✅ Scanner identificou os tokens básicos com sucesso.`));
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(chalk_1.default.red(`❌ Scanner não produziu a saída esperada (tokens ou logs de depuração).`));
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.log(chalk_1.default.red(`❌ Falha técnica no teste: ${e.message}`));
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (fs_1.default.existsSync(testFilePath))
|
|
66
|
+
fs_1.default.unlinkSync(testFilePath);
|
|
67
|
+
}
|
|
68
|
+
}
|