@devquest/cli 1.0.8 → 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.
@@ -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(`\\n📌 Você está no Estágio ${stageOrder} deste desafio.`));
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 rigorosos de submissão do Estágio.'));
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! Empacotando build processada...'));
56
+ console.log(chalk_1.default.green('\n✅ Integração local aprovada! Preparando envio...'));
59
57
  try {
60
- // 1. Commit and push automated
61
- console.log(chalk_1.default.gray('Preparando entrega gerando commit automático...'));
62
- await git.add('.');
63
- const status = await git.status();
64
- if (status.staged.length > 0 || status.not_added.length > 0) {
65
- await git.commit('Feat: Submissão via devquest CLI');
66
- }
67
- console.log(chalk_1.default.gray('Conectando ao Git interno e enviando código...'));
68
- // Assumindo que o "origin" foi configurado no clone com o remoto da plataforma
69
- try {
70
- await git.push('origin', 'main');
71
- }
72
- catch (pushErr) {
73
- const remotes = await git.getRemotes(true);
74
- const originUrl = remotes.find(r => r.name === 'origin')?.refs.push || '';
75
- if (originUrl.startsWith('file://')) {
76
- // Ignora erros de push se origin for um local file (ambiente dev/mock)
77
- console.log(chalk_1.default.yellow('⚠️ Aviso Modulação Local: Push nativo do git ignorado pois o remote é apenas uma pasta file:// do Template local. Submetendo o payload diretamente...'));
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
- else {
80
- throw pushErr;
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
- console.log(chalk_1.default.green('🚀 Código enviado! Conectando à Sandbox para log em tempo real...'));
84
- // 2. Conexão WebSockets para logs da MicroVM Firecracker
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
- console.error(err.message);
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('\\n');
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('\\n🎉 Parabéns! Você completou este estágio.'));
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('\\n💥 Erro na execução: ' + payload.message));
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devquest/cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "DevQuest CLI - Construa seu próprio Redis, Docker, Git e mais",
5
5
  "main": "dist/index.js",
6
6
  "bin": {