@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.
@@ -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, precisamos extrair a pasta específica
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 um novo repo git se o usuário quiser (opcional, mas bom pra começar limpo)
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. Tente rodar 'npm install' manualmente.`));
86
+ console.log(chalk_1.default.yellow(`\n⚠️ Aviso: Não foi possível instalar as dependências automaticamente.`));
95
87
  }
96
88
  }
97
- console.log(chalk_1.default.green(`✅ Desafio clonado com sucesso em ./${targetFolderName}`));
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.`);
@@ -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.6",
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": {