@devquest/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.
@@ -0,0 +1,97 @@
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.authCommand = void 0;
7
+ exports.getToken = getToken;
8
+ const commander_1 = require("commander");
9
+ const http_1 = __importDefault(require("http"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const child_process_1 = require("child_process");
14
+ const chalk_1 = __importDefault(require("chalk"));
15
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.devquest');
16
+ const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
17
+ exports.authCommand = new commander_1.Command('auth')
18
+ .description('Autenticação via navegador no CLI')
19
+ .action(async () => {
20
+ const PORT = 3838;
21
+ const DASHBOARD_URL = process.env.DEVQUEST_URL || 'http://localhost:3000';
22
+ console.log(chalk_1.default.blue('⏳ Iniciando autenticação...'));
23
+ console.log(chalk_1.default.gray(`Abriremos o seu navegador em breve para aprovação.`));
24
+ const server = http_1.default.createServer((req, res) => {
25
+ // Cors para permitir o front-end fazer o fetch na API localhost:3838
26
+ res.setHeader('Access-Control-Allow-Origin', '*');
27
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
28
+ if (req.method === 'OPTIONS') {
29
+ res.writeHead(200);
30
+ res.end();
31
+ return;
32
+ }
33
+ const url = new URL(req.url || '/', `http://localhost:${PORT}`);
34
+ if (url.pathname === '/callback') {
35
+ const token = url.searchParams.get('token');
36
+ if (token) {
37
+ try {
38
+ if (!fs_1.default.existsSync(CONFIG_DIR)) {
39
+ fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
40
+ }
41
+ const config = fs_1.default.existsSync(CONFIG_FILE) ? JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, 'utf-8')) : {};
42
+ config.token = token;
43
+ fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
44
+ res.writeHead(200, { 'Content-Type': 'application/json' });
45
+ res.end(JSON.stringify({ success: true }));
46
+ console.log(chalk_1.default.green('\n✅ Autenticado com sucesso! Seu token foi guardado localmente protegido.'));
47
+ console.log(chalk_1.default.yellow('Agora você pode clonar e submeter desafios!'));
48
+ console.log(chalk_1.default.gray('Use: devquest clone <id-do-curso>'));
49
+ server.close();
50
+ process.exit(0);
51
+ }
52
+ catch (err) {
53
+ console.error('\nErro ao salvar token de forma segura:', err);
54
+ res.writeHead(500);
55
+ res.end(JSON.stringify({ error: 'Erro interno' }));
56
+ server.close();
57
+ process.exit(1);
58
+ }
59
+ }
60
+ else {
61
+ res.writeHead(400);
62
+ res.end(JSON.stringify({ error: 'Token ausente' }));
63
+ }
64
+ }
65
+ else {
66
+ res.writeHead(404);
67
+ res.end('Not found');
68
+ }
69
+ });
70
+ server.listen(PORT, () => {
71
+ const cliAuthPath = encodeURIComponent(`/dashboard/cli-auth?port=${PORT}`);
72
+ const authUrl = `${DASHBOARD_URL}/login?callbackUrl=${cliAuthPath}`;
73
+ (0, child_process_1.exec)(`open "${authUrl}"`, (err) => {
74
+ if (err) {
75
+ console.log(chalk_1.default.yellow(`Não foi possível abrir o navegador automaticamente.`));
76
+ console.log(`Por favor, acesse manualmente: ${chalk_1.default.cyan(authUrl)}`);
77
+ }
78
+ });
79
+ });
80
+ // Timeout of 5 minutes
81
+ setTimeout(() => {
82
+ console.log(chalk_1.default.red('\n❌ Tempo de autenticação esgotado.'));
83
+ server.close();
84
+ process.exit(1);
85
+ }, 5 * 60 * 1000);
86
+ });
87
+ async function getToken() {
88
+ if (!fs_1.default.existsSync(CONFIG_FILE))
89
+ return null;
90
+ try {
91
+ const config = JSON.parse(fs_1.default.readFileSync(CONFIG_FILE, 'utf-8'));
92
+ return config.token || null;
93
+ }
94
+ catch (e) {
95
+ return null;
96
+ }
97
+ }
@@ -0,0 +1,63 @@
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.cloneCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const simple_git_1 = __importDefault(require("simple-git"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const axios_1 = __importDefault(require("axios"));
13
+ const auth_1 = require("./auth");
14
+ const API_BASE = process.env.DEVQUEST_API_URL || 'http://localhost:8787';
15
+ exports.cloneCommand = new commander_1.Command('clone')
16
+ .argument('<id>', 'ID do desafio (ex: build-your-own-redis)')
17
+ .description('Clona o repositório boilerplate personalizado configurando o remoto')
18
+ .action(async (id) => {
19
+ const token = await (0, auth_1.getToken)();
20
+ if (!token) {
21
+ console.log(chalk_1.default.red('Você não está autenticado. Use `devquest auth`.'));
22
+ process.exit(1);
23
+ }
24
+ console.log(chalk_1.default.blue(`🚀 Iniciando o desafio: ${id}`));
25
+ try {
26
+ // 1. Requisitar a URL do repositório para este usuário/desafio do Backend
27
+ // Isso criaria um "provisionamento" do repositório no backend
28
+ const response = await axios_1.default.post(`${API_BASE}/challenges/${id}/clone`, {}, {
29
+ headers: { Authorization: `Bearer ${token}` }
30
+ });
31
+ const { cloneUrl, targetDir } = response.data;
32
+ const destPath = path_1.default.resolve(process.cwd(), targetDir || id);
33
+ if (fs_1.default.existsSync(destPath)) {
34
+ console.log(chalk_1.default.red(`O diretório ${destPath} já existe.`));
35
+ process.exit(1);
36
+ }
37
+ let finalUrl = cloneUrl;
38
+ // Trata casos locais (para dev) onde o banco retorna 'file://../../templates/xyz'
39
+ if (cloneUrl.startsWith('file://../../templates/')) {
40
+ const repoRoot = path_1.default.resolve(__dirname, '../../../..'); // de dist/commands -> root turborepo
41
+ finalUrl = `file://${path_1.default.join(repoRoot, 'templates', targetDir || id)}`;
42
+ }
43
+ console.log(chalk_1.default.gray(`Clonando de ${finalUrl} para ${destPath}...`));
44
+ const git = (0, simple_git_1.default)();
45
+ await git.clone(finalUrl, destPath);
46
+ // 2. O boilerplate baixado deverá ter o origin já devidamente setado
47
+ // O remote será algo como http://git.plataforma.com/hash.git (servidor git interno)
48
+ console.log(chalk_1.default.green(`✅ Desafio clonado com sucesso em ./${targetDir || id}`));
49
+ console.log(chalk_1.default.yellow(`\nPróximos passos:`));
50
+ console.log(` cd ${targetDir || id}`);
51
+ console.log(` Leia o README.md e comece a desenvolver.`);
52
+ console.log(` Para submeter: devquest submit`);
53
+ }
54
+ catch (err) {
55
+ console.error(chalk_1.default.red('\n❌ Falha ao clonar desafio.'));
56
+ if (err.response) {
57
+ console.error(chalk_1.default.red(`Erro da API: ${err.response.data.message || err.response.statusText}`));
58
+ }
59
+ else {
60
+ console.error(chalk_1.default.red(err.message));
61
+ }
62
+ }
63
+ });
@@ -0,0 +1,144 @@
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.submitCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const simple_git_1 = __importDefault(require("simple-git"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const axios_1 = __importDefault(require("axios"));
13
+ const auth_1 = require("./auth");
14
+ const API_BASE = process.env.DEVQUEST_API_URL || 'http://localhost:8787';
15
+ const SSE_BASE = process.env.DEVQUEST_SSE_URL || 'http://localhost:8787/challenges/sse';
16
+ exports.submitCommand = new commander_1.Command('submit')
17
+ .description('Envia seu código para os testes automatizados da plataforma')
18
+ .action(async () => {
19
+ const token = await (0, auth_1.getToken)();
20
+ if (!token) {
21
+ console.log(chalk_1.default.red('Você não está autenticado. Use `devquest auth`.'));
22
+ process.exit(1);
23
+ }
24
+ const currentDir = process.cwd();
25
+ if (!fs_1.default.existsSync(path_1.default.join(currentDir, '.git'))) {
26
+ console.log(chalk_1.default.red('Não encontrei um repositório git no diretório atual. Execute esse comando na raiz do desafio.'));
27
+ process.exit(1);
28
+ }
29
+ const git = (0, simple_git_1.default)(currentDir);
30
+ const packageJsonPath = path_1.default.join(currentDir, 'package.json');
31
+ let challengeId = 'unknown';
32
+ if (fs_1.default.existsSync(packageJsonPath)) {
33
+ try {
34
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
35
+ challengeId = pkg.name;
36
+ }
37
+ catch (e) { }
38
+ }
39
+ let stageOrder = 1;
40
+ try {
41
+ const progRes = await axios_1.default.get(`${API_BASE}/challenges/${challengeId}/progress`, {
42
+ headers: { Authorization: `Bearer ${token}` }
43
+ });
44
+ if (progRes.data.stageOrder) {
45
+ stageOrder = progRes.data.stageOrder;
46
+ }
47
+ }
48
+ catch (e) {
49
+ console.log(chalk_1.default.yellow('⚠️ Não foi possível determinar o estágio na API. Assumindo Estágio 1.'));
50
+ }
51
+ console.log(chalk_1.default.blue(`\\n📌 Você está no Estágio ${stageOrder} deste desafio.`));
52
+ const { DevQuestTester } = require('../utils/tester');
53
+ const tester = new DevQuestTester(currentDir, challengeId, stageOrder);
54
+ const passed = await tester.execute();
55
+ if (!passed) {
56
+ console.log(chalk_1.default.red('\n🚫 Execução interrompida pela falha dos testes rigorosos de submissão do Estágio.'));
57
+ process.exit(1);
58
+ }
59
+ console.log(chalk_1.default.green('\n✅ Integração aprovada! Empacotando build processada...'));
60
+ try {
61
+ // 1. Commit and push automated
62
+ console.log(chalk_1.default.gray('Preparando entrega gerando commit automático...'));
63
+ await git.add('.');
64
+ const status = await git.status();
65
+ if (status.staged.length > 0 || status.not_added.length > 0) {
66
+ await git.commit('Feat: Submissão via devquest CLI');
67
+ }
68
+ console.log(chalk_1.default.gray('Conectando ao Git interno e enviando código...'));
69
+ // Assumindo que o "origin" foi configurado no clone com o remoto da plataforma
70
+ try {
71
+ await git.push('origin', 'main');
72
+ }
73
+ catch (pushErr) {
74
+ const remotes = await git.getRemotes(true);
75
+ const originUrl = remotes.find(r => r.name === 'origin')?.refs.push || '';
76
+ if (originUrl.startsWith('file://')) {
77
+ // Ignora erros de push se origin for um local file (ambiente dev/mock)
78
+ 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...'));
79
+ }
80
+ else {
81
+ throw pushErr;
82
+ }
83
+ }
84
+ console.log(chalk_1.default.green('🚀 Código enviado! Conectando à Sandbox para log em tempo real...'));
85
+ // 2. Conexão WebSockets para logs da MicroVM Firecracker
86
+ startStreamingLogs(token, currentDir, challengeId, stageOrder);
87
+ }
88
+ catch (err) {
89
+ console.error(chalk_1.default.red('\n❌ Falha ao enviar a submissão.'));
90
+ console.error(err.message);
91
+ }
92
+ });
93
+ async function startStreamingLogs(token, challengeDir, challengeId, stageOrder) {
94
+ const url = `${SSE_BASE}?token=${encodeURIComponent(token)}&challengeId=${encodeURIComponent(challengeId)}&stageOrder=${stageOrder}`;
95
+ console.log(chalk_1.default.cyan('📡 Conectando à Sandbox segurizada (MicroVM Firecracker)...'));
96
+ try {
97
+ const response = await fetch(url);
98
+ if (!response.body) {
99
+ console.error(chalk_1.default.red('Não foi possível obter os logs.'));
100
+ return;
101
+ }
102
+ const reader = response.body.getReader();
103
+ const decoder = new TextDecoder('utf-8');
104
+ while (true) {
105
+ const { done, value } = await reader.read();
106
+ if (done)
107
+ break;
108
+ const chunk = decoder.decode(value, { stream: true });
109
+ const lines = chunk.split('\\n');
110
+ for (const line of lines) {
111
+ if (line.startsWith('data: ')) {
112
+ const dataStr = line.slice(6);
113
+ try {
114
+ const payload = JSON.parse(dataStr);
115
+ switch (payload.type) {
116
+ case 'INFO':
117
+ console.log(chalk_1.default.blue(payload.message));
118
+ break;
119
+ case 'TEST_SUCCESS':
120
+ console.log(chalk_1.default.green('✅ ' + payload.message));
121
+ break;
122
+ case 'TEST_FAILED':
123
+ console.log(chalk_1.default.red('❌ ' + payload.message));
124
+ break;
125
+ case 'FINISHED':
126
+ console.log(chalk_1.default.green.bold('\\n🎉 Parabéns! Você completou este estágio.'));
127
+ process.exit(0);
128
+ case 'ERROR':
129
+ console.log(chalk_1.default.red.bold('\\n💥 Erro na execução: ' + payload.message));
130
+ process.exit(1);
131
+ }
132
+ }
133
+ catch (e) {
134
+ // ignores bad json fragments
135
+ }
136
+ }
137
+ }
138
+ }
139
+ console.log(chalk_1.default.gray('Conexão encerrada pelo servidor.'));
140
+ }
141
+ catch (err) {
142
+ console.error(chalk_1.default.red('Erro na escuta da MicroVM: ' + err.message));
143
+ }
144
+ }
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const auth_1 = require("./commands/auth");
6
+ const clone_1 = require("./commands/clone");
7
+ const submit_1 = require("./commands/submit");
8
+ const program = new commander_1.Command();
9
+ program
10
+ .name('devquest')
11
+ .description('DevQuest CLI - Construa seu próprio Redis, Docker, Git e mais')
12
+ .version('1.0.0');
13
+ program.addCommand(auth_1.authCommand);
14
+ program.addCommand(clone_1.cloneCommand);
15
+ program.addCommand(submit_1.submitCommand);
16
+ if (!process.argv.slice(2).length) {
17
+ program.outputHelp();
18
+ process.exit(0);
19
+ }
20
+ program.parse(process.argv);
@@ -0,0 +1,48 @@
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.DevQuestTester = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ class DevQuestTester {
10
+ cwd;
11
+ challengeId;
12
+ stageOrder;
13
+ constructor(cwd, challengeId, stageOrder) {
14
+ this.cwd = cwd;
15
+ this.challengeId = challengeId;
16
+ this.stageOrder = stageOrder;
17
+ }
18
+ async execute() {
19
+ console.log(chalk_1.default.cyan(`\n[DevQuest Tester] Iniciando avaliação padrão (Integration Tests)...`));
20
+ console.log(chalk_1.default.gray(`[DevQuest Tester] Diretório alvo: ${this.cwd}`));
21
+ try {
22
+ console.log(chalk_1.default.gray(`[DevQuest Tester] Emitindo TypeScript (Compilação)...`));
23
+ (0, child_process_1.execSync)('npx tsc', { cwd: this.cwd, stdio: 'ignore' });
24
+ }
25
+ catch (e) {
26
+ console.log(chalk_1.default.red('\\n❌ Falha de Compilação! Os tipos do TypeScript ou sintaxe estão incorretos.'));
27
+ return false;
28
+ }
29
+ let success = false;
30
+ try {
31
+ // Dynamic import
32
+ const testSuite = require(`./tests/${this.challengeId}`);
33
+ success = await testSuite.runStage(this.stageOrder, this.cwd);
34
+ }
35
+ catch (e) {
36
+ if (e.code === 'MODULE_NOT_FOUND') {
37
+ console.log(chalk_1.default.yellow(`[DevQuest Tester] Nenhuma suite de testes rigorosa definida para ${this.challengeId} no estágio ${this.stageOrder}. Passando...`));
38
+ success = true;
39
+ }
40
+ else {
41
+ console.log(chalk_1.default.red(`[!] Erro fatal ao carregar a suite de testes: ${e.message}`));
42
+ success = false;
43
+ }
44
+ }
45
+ return success;
46
+ }
47
+ }
48
+ exports.DevQuestTester = DevQuestTester;
@@ -0,0 +1,206 @@
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 path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const child_process_1 = require("child_process");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function runStage(stageOrder, cwd) {
12
+ if (stageOrder === 1) {
13
+ console.log(chalk_1.default.blue(`[Stage 1: AI Framework] Verificando encapsulamento e requests para provider...`));
14
+ const testFilePath = path_1.default.join(cwd, '.eval-stage1.ts');
15
+ const code = `
16
+ let userApp: any;
17
+ try { userApp = require('./src/index'); }
18
+ catch(e: any) { console.error('[!] Falha ao importar src/index.ts: ' + e.message); process.exit(1); }
19
+
20
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
21
+ if (!condition) {
22
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
23
+ console.error('📝 ' + failMsg);
24
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
25
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
26
+ console.error('');
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ const { callOpenAI } = userApp;
32
+ assert(typeof callOpenAI === 'function', 'A função callOpenAI não foi exportada no src/index.ts', 'function', typeof callOpenAI);
33
+
34
+ let fetchCallParams: any = null;
35
+ (global as any).fetch = async (url: string, options: any) => {
36
+ fetchCallParams = { url, options };
37
+ return {
38
+ json: async () => ({
39
+ id: "chatcmpl-mock",
40
+ choices: [{ message: { content: "DevQuest LLM Response" } }]
41
+ })
42
+ } as any;
43
+ };
44
+
45
+ async function testSuite() {
46
+ try {
47
+ const prompt = 'Qual a capital do Brasil?';
48
+ const result = await callOpenAI(prompt);
49
+
50
+ assert(fetchCallParams !== null, 'Seu código não utilizou a API global fetch()', 'Chamada em fetch(...)', 'Nenhum request de rede feito');
51
+ assert(fetchCallParams.url === 'https://api.openai.com/v1/chat/completions', 'A URL da OpenAI base está errada na sua request', 'https://api.openai.com/v1/chat/completions', fetchCallParams.url);
52
+
53
+ const auth = fetchCallParams.options?.headers?.Authorization;
54
+ assert(!!auth, 'Header Authoziration Bearer <token> faltando nas configurações.', 'Bearer <Key>', auth);
55
+
56
+ let parsedBody;
57
+ try { parsedBody = JSON.parse(fetchCallParams.options?.body || '{}'); } catch(e) {}
58
+ assert(parsedBody?.model === 'gpt-4o', 'O modelo no payload não é gpt-4o de acordo com as instruções', 'gpt-4o', parsedBody?.model);
59
+
60
+ const messages = parsedBody?.messages || [];
61
+ const lastMsg = messages[messages.length - 1];
62
+ assert(lastMsg?.content === prompt, 'Você não repassou o prompt dinamicamente.', prompt, lastMsg?.content);
63
+ assert(result === 'DevQuest LLM Response', 'Retorno da função incorreto.', 'DevQuest LLM Response', result);
64
+
65
+ console.log('PASS');
66
+ } catch(e: any) {
67
+ console.error(e.message);
68
+ process.exit(1);
69
+ }
70
+ }
71
+ testSuite();
72
+ `;
73
+ try {
74
+ fs_1.default.writeFileSync(testFilePath, code);
75
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage1.ts', { cwd, stdio: 'pipe' });
76
+ return stdout.toString().includes('PASS');
77
+ }
78
+ catch (err) {
79
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
80
+ return false;
81
+ }
82
+ finally {
83
+ if (fs_1.default.existsSync(testFilePath))
84
+ fs_1.default.unlinkSync(testFilePath);
85
+ }
86
+ }
87
+ if (stageOrder === 2) {
88
+ console.log(chalk_1.default.blue(`[Stage 2: Provider Abstraction] Testando unificação de adaptadores...`));
89
+ const testFilePath = path_1.default.join(cwd, '.eval-stage2.ts');
90
+ const code = `
91
+ let userApp: any;
92
+ try { userApp = require('./src/index'); }
93
+ catch(e: any) { console.error('[!] Erro de importação: ' + e.message); process.exit(1); }
94
+
95
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
96
+ if (!condition) {
97
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
98
+ console.error('📝 ' + failMsg);
99
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
100
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
101
+ console.error('');
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ const { openai } = userApp;
107
+ assert(typeof openai === 'function', 'Você deve exportar o helper openai() para criar o adaptador.', 'function', typeof openai);
108
+
109
+ async function testSuite() {
110
+ try {
111
+ const model = openai('gpt-4o');
112
+ assert(typeof model.doGenerate === 'function', 'O adaptador instanciado via openai() deve ter o método doGenerate().', 'function', typeof model.doGenerate);
113
+
114
+ console.log('PASS');
115
+ } catch(e: any) {
116
+ console.error(e.message);
117
+ process.exit(1);
118
+ }
119
+ }
120
+ testSuite();
121
+ `;
122
+ try {
123
+ fs_1.default.writeFileSync(testFilePath, code);
124
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage2.ts', { cwd, stdio: 'pipe' });
125
+ return stdout.toString().includes('PASS');
126
+ }
127
+ catch (err) {
128
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
129
+ return false;
130
+ }
131
+ finally {
132
+ if (fs_1.default.existsSync(testFilePath))
133
+ fs_1.default.unlinkSync(testFilePath);
134
+ }
135
+ }
136
+ if (stageOrder === 3) {
137
+ console.log(chalk_1.default.blue(`[Stage 3: Core generateText] Testando função unificada e normalização de retorno...`));
138
+ const testFilePath = path_1.default.join(cwd, '.eval-stage3.ts');
139
+ const code = `
140
+ let userApp: any;
141
+ try { userApp = require('./src/index'); }
142
+ catch(e: any) { console.error('[!] Erro de importação: ' + e.message); process.exit(1); }
143
+
144
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
145
+ if (!condition) {
146
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
147
+ console.error('📝 ' + failMsg);
148
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
149
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
150
+ console.error('');
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ const { generateText, openai } = userApp;
156
+ assert(typeof generateText === 'function', 'Você não exportou a função unificada generateText()', 'function', typeof generateText);
157
+
158
+ let fetchCallParams: any = null;
159
+ (global as any).fetch = async (url: string, options: any) => {
160
+ fetchCallParams = { url, options };
161
+ return {
162
+ json: async () => ({
163
+ choices: [{ message: { content: "Parsed Text" }, finish_reason: "stop" }],
164
+ usage: { prompt_tokens: 5, completion_tokens: 10 }
165
+ })
166
+ } as any;
167
+ };
168
+
169
+ async function testSuite() {
170
+ try {
171
+ const result = await generateText({
172
+ model: openai('gpt-4o'),
173
+ prompt: 'Explain the core function',
174
+ maxTokens: 50
175
+ });
176
+
177
+ assert(result.text === 'Parsed Text', 'Propriedade \\'text\\' ausente ou incorreta no retorno de generateText.', 'Parsed Text', result.text);
178
+ assert(result.usage !== undefined, 'Propriedade \\'usage\\' ausente no retorno.', '{ promptTokens, ... }', result.usage);
179
+ assert(result.usage.promptTokens === 5, 'Mapping incorreto de usage.promptTokens (OpenAI snake_case -> CamelCase).', 5, result.usage.promptTokens);
180
+ assert(result.finishReason === 'stop', 'Propriedade \\'finishReason\\' ausente ou incorreta no retorno.', 'stop', result.finishReason);
181
+
182
+ console.log('PASS');
183
+ } catch(e: any) {
184
+ console.error(e.message);
185
+ process.exit(1);
186
+ }
187
+ }
188
+ testSuite();
189
+ `;
190
+ try {
191
+ fs_1.default.writeFileSync(testFilePath, code);
192
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage3.ts', { cwd, stdio: 'pipe' });
193
+ return stdout.toString().includes('PASS');
194
+ }
195
+ catch (err) {
196
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
197
+ return false;
198
+ }
199
+ finally {
200
+ if (fs_1.default.existsSync(testFilePath))
201
+ fs_1.default.unlinkSync(testFilePath);
202
+ }
203
+ }
204
+ console.log(chalk_1.default.yellow(`⚠️ Nenhuma suite implementada ainda para Stage ${stageOrder} deste desafio. Bypass (Aprovado).`));
205
+ return true;
206
+ }
@@ -0,0 +1,66 @@
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 path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const child_process_1 = require("child_process");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function runStage(stageOrder, cwd) {
12
+ if (stageOrder === 1) {
13
+ console.log(chalk_1.default.blue(`[Stage 1: Compiler Lexer] Tokenizando subset de C...`));
14
+ const testFilePath = path_1.default.join(cwd, '.eval-stage1.ts');
15
+ const code = `
16
+ const { execSync } = require('child_process');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
21
+ if (!condition) {
22
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
23
+ console.error('📝 ' + failMsg);
24
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
25
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
26
+ console.error('');
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ async function testSuite() {
32
+ try {
33
+ const testFile = path.join('${cwd}', 'main.c');
34
+ fs.writeFileSync(testFile, 'int main() { return 42; }');
35
+
36
+ console.log('-> Executando seu comando: node src/index.js tokenize main.c');
37
+ const output = execSync('npx ts-node src/index.ts tokenize main.c', { cwd: '${cwd}', stdio: 'pipe' });
38
+
39
+ const content = output.toString().toUpperCase();
40
+ assert(content.includes('INT') && content.includes('RETURN') && content.includes('42'), 'O Lexer falhou em identificar os tokens básicos.', 'INT, RETURN, 42', content);
41
+
42
+ console.log('PASS');
43
+ if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
44
+ } catch(e: any) {
45
+ console.error(e.stderr?.toString() || e.message);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ testSuite();
50
+ `;
51
+ try {
52
+ fs_1.default.writeFileSync(testFilePath, code);
53
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage1.ts', { cwd, stdio: 'pipe' });
54
+ return stdout.toString().includes('PASS');
55
+ }
56
+ catch (err) {
57
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
58
+ return false;
59
+ }
60
+ finally {
61
+ if (fs_1.default.existsSync(testFilePath))
62
+ fs_1.default.unlinkSync(testFilePath);
63
+ }
64
+ }
65
+ return true;
66
+ }
@@ -0,0 +1,129 @@
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 path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const child_process_1 = require("child_process");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function runStage(stageOrder, cwd) {
12
+ if (stageOrder === 1) {
13
+ console.log(chalk_1.default.blue(`[Stage 1: Git Init] Verificando criação da estrutura .git/...`));
14
+ const testFilePath = path_1.default.join(cwd, '.eval-stage1.ts');
15
+ const code = `
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { execSync } = require('child_process');
19
+
20
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
21
+ if (!condition) {
22
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
23
+ console.error('📝 ' + failMsg);
24
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
25
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
26
+ console.error('');
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ async function testSuite() {
32
+ try {
33
+ const repoPath = path.join('${cwd}', 'test-repo-' + Date.now());
34
+ fs.mkdirSync(repoPath);
35
+
36
+ execSync('npx ts-node src/index.ts init', { cwd: repoPath, stdio: 'pipe' });
37
+
38
+ const gitPath = path.join(repoPath, '.git');
39
+ assert(fs.existsSync(gitPath), 'O diretório .git não foi criado.', 'EXISTE: .git/', 'NÃO ENCONTRADO');
40
+ assert(fs.existsSync(path.join(gitPath, 'objects')), 'Pasta .git/objects não encontrada.', 'folder', 'false');
41
+ assert(fs.existsSync(path.join(gitPath, 'refs')), 'Pasta .git/refs não encontrada.', 'folder', 'false');
42
+ assert(fs.existsSync(path.join(gitPath, 'HEAD')), 'Arquivo .git/HEAD não encontrado.', 'file', 'false');
43
+
44
+ console.log('PASS');
45
+ fs.rmSync(repoPath, { recursive: true, force: true });
46
+ } catch(e: any) {
47
+ console.error(e.stderr?.toString() || e.message);
48
+ process.exit(1);
49
+ }
50
+ }
51
+ testSuite();
52
+ `;
53
+ try {
54
+ fs_1.default.writeFileSync(testFilePath, code);
55
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage1.ts', { cwd, stdio: 'pipe' });
56
+ return stdout.toString().includes('PASS');
57
+ }
58
+ catch (err) {
59
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
60
+ return false;
61
+ }
62
+ finally {
63
+ if (fs_1.default.existsSync(testFilePath))
64
+ fs_1.default.unlinkSync(testFilePath);
65
+ }
66
+ }
67
+ if (stageOrder === 2) {
68
+ console.log(chalk_1.default.blue(`[Stage 2: Git Cat-File] Verificando leitura de blobs (zlib decompression)...`));
69
+ const testFilePath = path_1.default.join(cwd, '.eval-stage2.ts');
70
+ const code = `
71
+ const fs = require('fs');
72
+ const path = require('path');
73
+ const zlib = require('zlib');
74
+ const { execSync } = require('child_process');
75
+
76
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
77
+ if (!condition) {
78
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
79
+ console.error('📝 ' + failMsg);
80
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
81
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
82
+ console.error('');
83
+ process.exit(1);
84
+ }
85
+ }
86
+
87
+ async function testSuite() {
88
+ try {
89
+ const repoPath = path.join('${cwd}', 'test-repo-cat-' + Date.now());
90
+ fs.mkdirSync(repoPath, { recursive: true });
91
+
92
+ const objectsPath = path.join(repoPath, '.git', 'objects');
93
+ const hash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
94
+ const dir = hash.slice(0, 2);
95
+ const file = hash.slice(2);
96
+
97
+ fs.mkdirSync(path.join(objectsPath, dir), { recursive: true });
98
+ const content = Buffer.concat([Buffer.from("blob 5\\0"), Buffer.from("hello")]);
99
+ const compressed = zlib.deflateSync(content);
100
+ fs.writeFileSync(path.join(objectsPath, dir, file), compressed);
101
+
102
+ const output = execSync('npx ts-node src/index.ts cat-file -p ' + hash, { cwd: repoPath, stdio: 'pipe' });
103
+ assert(output.toString().trim() === 'hello', 'O comando cat-file não descompactou ou não imprimiu o conteúdo correto.', 'hello', output.toString().trim());
104
+
105
+ console.log('PASS');
106
+ fs.rmSync(repoPath, { recursive: true, force: true });
107
+ } catch(e: any) {
108
+ console.error(e.stderr?.toString() || e.message);
109
+ process.exit(1);
110
+ }
111
+ }
112
+ testSuite();
113
+ `;
114
+ try {
115
+ fs_1.default.writeFileSync(testFilePath, code);
116
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage2.ts', { cwd, stdio: 'pipe' });
117
+ return stdout.toString().includes('PASS');
118
+ }
119
+ catch (err) {
120
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
121
+ return false;
122
+ }
123
+ finally {
124
+ if (fs_1.default.existsSync(testFilePath))
125
+ fs_1.default.unlinkSync(testFilePath);
126
+ }
127
+ }
128
+ return true;
129
+ }
@@ -0,0 +1,49 @@
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 child_process_1 = require("child_process");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ async function runStage(stageOrder, cwd) {
10
+ if (stageOrder === 1) {
11
+ console.log(chalk_1.default.blue(`[Stage 1: HTTP Server] Rodando TCP Connection Tests...`));
12
+ return new Promise(async (resolve) => {
13
+ let child = null;
14
+ try {
15
+ console.log(chalk_1.default.gray(`Iniciando servidor do usário [node dist/index.js]...`));
16
+ child = (0, child_process_1.spawn)('node', ['dist/index.js'], { cwd });
17
+ await new Promise(r => setTimeout(r, 1500));
18
+ console.log(chalk_1.default.gray(`Disparando Requests em rotas conhecidas e desconhecidas...`));
19
+ let passed = true;
20
+ // TestCase 1: Invalid Route
21
+ try {
22
+ const res = await fetch('http://localhost:3000/rota-que-nao-existe');
23
+ // The first stage of DevQuest TCP requires returning 200 dynamically by default if implemented simply,
24
+ // but normally a baseline server just accepts socket connections.
25
+ if (res.status) {
26
+ console.log(chalk_1.default.green('✅ Conexão HTTP estabelecida com sucesso!'));
27
+ }
28
+ }
29
+ catch (e) {
30
+ console.log(chalk_1.default.red('❌ FALHA DE ASSERÇÃO\n📝 O servidor não aceitou a conexão TCP na porta 3000.\n🎯 Esperado : Conexão Open\n💥 Recebido : ECONNREFUSED'));
31
+ passed = false;
32
+ }
33
+ // Testcase 2: Valid Param
34
+ // HTTP framework challenge stage 1 generally involves simply binding a socket server.
35
+ // Once bound, it passes.
36
+ child.kill();
37
+ resolve(passed);
38
+ }
39
+ catch (e) {
40
+ if (child)
41
+ child.kill();
42
+ console.log(chalk_1.default.red('❌ Falha fatal ao executar o daemon local.'));
43
+ resolve(false);
44
+ }
45
+ });
46
+ }
47
+ console.log(chalk_1.default.yellow(`⚠️ Nenhuma suite implementada ainda para Stage ${stageOrder} deste desafio. Bypass (Aprovado).`));
48
+ return true;
49
+ }
@@ -0,0 +1,69 @@
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 path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const child_process_1 = require("child_process");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function runStage(stageOrder, cwd) {
12
+ if (stageOrder === 1) {
13
+ console.log(chalk_1.default.blue(`[Stage 1: Interpreter Entry Point] Verificando subcomandos (tokenize/parse/run)...`));
14
+ const testFilePath = path_1.default.join(cwd, '.eval-stage1.ts');
15
+ const code = `
16
+ const { execSync } = require('child_process');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
21
+ if (!condition) {
22
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
23
+ console.error('📝 ' + failMsg);
24
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
25
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
26
+ console.error('');
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ async function testSuite() {
32
+ try {
33
+ const testFile = path.join('${cwd}', 'test.lox');
34
+ fs.writeFileSync(testFile, 'print "hello";');
35
+
36
+ console.log('-> Testando subcomando: tokenize');
37
+ const outputTokenize = execSync('npx ts-node src/index.ts tokenize test.lox', { cwd: '${cwd}', stdio: 'pipe' });
38
+ assert(outputTokenize.toString().toLowerCase().includes('print') || outputTokenize.toString().toLowerCase().includes('hello'), 'O subcomando tokenize não funcionou.', 'Tokens: PRINT, STRING', outputTokenize.toString());
39
+
40
+ console.log('-> Testando subcomando: run');
41
+ const outputRun = execSync('npx ts-node src/index.ts run test.lox', { cwd: '${cwd}', stdio: 'pipe' });
42
+ // Even if it doesn't run yet, it should at least not crash if implemented properly.
43
+ // But we expect it to output 'hello' if implemented.
44
+
45
+ console.log('PASS');
46
+ if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
47
+ } catch(e: any) {
48
+ console.error(e.stderr?.toString() || e.message);
49
+ process.exit(1);
50
+ }
51
+ }
52
+ testSuite();
53
+ `;
54
+ try {
55
+ fs_1.default.writeFileSync(testFilePath, code);
56
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage1.ts', { cwd, stdio: 'pipe' });
57
+ return stdout.toString().includes('PASS');
58
+ }
59
+ catch (err) {
60
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
61
+ return false;
62
+ }
63
+ finally {
64
+ if (fs_1.default.existsSync(testFilePath))
65
+ fs_1.default.unlinkSync(testFilePath);
66
+ }
67
+ }
68
+ return true;
69
+ }
@@ -0,0 +1,149 @@
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 child_process_1 = require("child_process");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ async function runStage(stageOrder, cwd) {
10
+ if (stageOrder === 1) {
11
+ console.log(chalk_1.default.blue(`[Stage 1: Redis TCP Bind] Verificando servidor TCP escutando na porta 6379...`));
12
+ return new Promise(async (resolve) => {
13
+ let child = null;
14
+ try {
15
+ console.log(chalk_1.default.gray(`Iniciando servidor Redis do usuário (node src/index.js)...`));
16
+ // Use ts-node -T to run the index.ts directly if that's the setup, or check if dist exists.
17
+ // Assuming the Redis template follows the same pattern.
18
+ child = (0, child_process_1.spawn)('npx', ['ts-node', '-T', 'src/index.ts'], { cwd });
19
+ // Wait for bind
20
+ await new Promise(r => setTimeout(r, 2000));
21
+ let passed = false;
22
+ const net = require('net');
23
+ try {
24
+ const client = new net.Socket();
25
+ await new Promise((resolveSocket, rejectSocket) => {
26
+ client.connect(6379, '127.0.0.1', () => {
27
+ passed = true;
28
+ client.destroy();
29
+ resolveSocket();
30
+ });
31
+ client.on('error', (err) => {
32
+ rejectSocket(err);
33
+ });
34
+ setTimeout(() => rejectSocket(new Error('Timeout ao conectar')), 1000);
35
+ });
36
+ }
37
+ catch (e) {
38
+ console.error('\n❌ FALHA DE ASSERÇÃO');
39
+ console.error('📝 O servidor não aceitou conexão TCP na porta 6379.');
40
+ console.error('🎯 Esperado : Conexão Estabelecida (Port 6379)');
41
+ console.error('💥 Recebido : Erro de Conexão (ECONNREFUSED)');
42
+ passed = false;
43
+ }
44
+ if (passed) {
45
+ console.log(chalk_1.default.green('✅ Conexão TCP na porta 6379 detectada!'));
46
+ }
47
+ child.kill();
48
+ resolve(passed);
49
+ }
50
+ catch (e) {
51
+ if (child)
52
+ child.kill();
53
+ console.error(chalk_1.default.red(`[!] Erro fatal: ${e.message}`));
54
+ resolve(false);
55
+ }
56
+ });
57
+ }
58
+ if (stageOrder === 2) {
59
+ console.log(chalk_1.default.blue(`[Stage 2: Redis PONG Response] Verificando resposta +PONG\\r\\n...`));
60
+ return new Promise(async (resolve) => {
61
+ let child = null;
62
+ try {
63
+ child = (0, child_process_1.spawn)('npx', ['ts-node', '-T', 'src/index.ts'], { cwd });
64
+ await new Promise(r => setTimeout(r, 2000));
65
+ let passed = false;
66
+ const net = require('net');
67
+ try {
68
+ const client = new net.Socket();
69
+ await new Promise((resolveSocket, rejectSocket) => {
70
+ let receivedData = '';
71
+ client.connect(6379, '127.0.0.1', () => {
72
+ client.write('PING\\r\\n');
73
+ });
74
+ client.on('data', (data) => {
75
+ receivedData += data.toString();
76
+ if (receivedData.includes('+PONG\\r\\n')) {
77
+ passed = true;
78
+ client.destroy();
79
+ resolveSocket();
80
+ }
81
+ });
82
+ client.on('error', (err) => rejectSocket(err));
83
+ setTimeout(() => rejectSocket(new Error('Timeout ao receber +PONG')), 2000);
84
+ });
85
+ }
86
+ catch (e) {
87
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
88
+ console.error('📝 O servidor não respondeu com +PONG\\r\\n.');
89
+ console.error('🎯 Esperado : +PONG\\r\\n');
90
+ console.error('💥 Recebido : ' + (e.message || 'Sem resposta'));
91
+ passed = false;
92
+ }
93
+ if (passed)
94
+ console.log(chalk_1.default.green('✅ Recebido +PONG\\r\\n do servidor!'));
95
+ child.kill();
96
+ resolve(passed);
97
+ }
98
+ catch (e) {
99
+ if (child)
100
+ child.kill();
101
+ resolve(false);
102
+ }
103
+ });
104
+ }
105
+ if (stageOrder === 3) {
106
+ console.log(chalk_1.default.blue(`[Stage 3: Redis Concurrent] Verificando múltiplas conexões simultâneas...`));
107
+ return new Promise(async (resolve) => {
108
+ let child = null;
109
+ try {
110
+ child = (0, child_process_1.spawn)('npx', ['ts-node', '-T', 'src/index.ts'], { cwd });
111
+ await new Promise(r => setTimeout(r, 2000));
112
+ const net = require('net');
113
+ let connectedCount = 0;
114
+ const clients = [new net.Socket(), new net.Socket(), new net.Socket()];
115
+ try {
116
+ await Promise.all(clients.map(client => {
117
+ return new Promise((res, rej) => {
118
+ client.connect(6379, '127.0.0.1', () => {
119
+ connectedCount++;
120
+ res();
121
+ });
122
+ client.on('error', rej);
123
+ setTimeout(() => rej(new Error('Timeout Concorrência')), 2000);
124
+ });
125
+ }));
126
+ if (connectedCount === 3) {
127
+ console.log(chalk_1.default.green('✅ O servidor lidou com 3 conexões simultâneas sem travar!'));
128
+ clients.forEach(c => c.destroy());
129
+ child.kill();
130
+ return resolve(true);
131
+ }
132
+ }
133
+ catch (e) {
134
+ console.error('\\n❌ FALHA DE ASSERÇÃO\\n📝 Servidor bloqueou conexões paralelas.');
135
+ clients.forEach(c => c.destroy());
136
+ child.kill();
137
+ return resolve(false);
138
+ }
139
+ }
140
+ catch (e) {
141
+ if (child)
142
+ child.kill();
143
+ resolve(false);
144
+ }
145
+ });
146
+ }
147
+ console.log(chalk_1.default.yellow(`⚠️ Nenhuma suite implementada ainda para Stage ${stageOrder} de Redis. Bypass (Aprovado).`));
148
+ return true;
149
+ }
@@ -0,0 +1,61 @@
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 path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const child_process_1 = require("child_process");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function runStage(stageOrder, cwd) {
12
+ if (stageOrder === 1) {
13
+ console.log(chalk_1.default.blue(`[Stage 1: Shell Echo] Verificando comando echo do seu Shell...`));
14
+ // O Shell Stage 1 geralmente envolve criar um prompt e lidar com comandos triviais
15
+ const testFilePath = path_1.default.join(cwd, '.eval-stage1.ts');
16
+ const code = `
17
+ const { execSync } = require('child_process');
18
+ const fs = require('fs');
19
+
20
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
21
+ if (!condition) {
22
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
23
+ console.error('📝 ' + failMsg);
24
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
25
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
26
+ console.error('');
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ async function testSuite() {
32
+ try {
33
+ // Vamos passar 'echo Hello World' para o shell e ver se o output possui Hello World
34
+ console.log('-> Injetando comandos via stdin no seu Shell (src/index.ts)...');
35
+ const output = execSync('echo "echo Hello World\\nexit 0" | npx ts-node src/index.ts', { cwd: '${cwd}', stdio: 'pipe' });
36
+
37
+ assert(output.toString().includes('Hello World'), 'O comando echo não funcionou no seu Shell.', 'Hello World', output.toString());
38
+ console.log('PASS');
39
+ } catch(e: any) {
40
+ console.error(e.stderr?.toString() || e.message);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ testSuite();
45
+ `;
46
+ try {
47
+ fs_1.default.writeFileSync(testFilePath, code);
48
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage1.ts', { cwd, stdio: 'pipe' });
49
+ return stdout.toString().includes('PASS');
50
+ }
51
+ catch (err) {
52
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
53
+ return false;
54
+ }
55
+ finally {
56
+ if (fs_1.default.existsSync(testFilePath))
57
+ fs_1.default.unlinkSync(testFilePath);
58
+ }
59
+ }
60
+ return true;
61
+ }
@@ -0,0 +1,61 @@
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 path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const child_process_1 = require("child_process");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ function runStage(stageOrder, cwd) {
12
+ if (stageOrder === 1) {
13
+ console.log(chalk_1.default.blue(`[Stage 1: Vercel CLI] Verificando comando deploy...`));
14
+ const testFilePath = path_1.default.join(cwd, '.eval-stage1.ts');
15
+ const code = `
16
+ const { execSync } = require('child_process');
17
+ const fs = require('fs');
18
+
19
+ function assert(condition: boolean, failMsg: string, expected?: any, got?: any) {
20
+ if (!condition) {
21
+ console.error('\\n❌ FALHA DE ASSERÇÃO');
22
+ console.error('📝 ' + failMsg);
23
+ if (expected !== undefined) console.error('🎯 Esperado : ' + String(expected));
24
+ if (got !== undefined) console.error('💥 Recebido : ' + String(got));
25
+ console.error('');
26
+ process.exit(1);
27
+ }
28
+ }
29
+
30
+ async function testSuite() {
31
+ try {
32
+ console.log('-> Executando seu comando: node src/index.js deploy');
33
+ const output = execSync('npx ts-node src/index.ts deploy', { cwd: '${cwd}', stdio: 'pipe' });
34
+
35
+ const content = output.toString().toLowerCase();
36
+ assert(content.includes('deploy') || content.includes('build') || content.includes('uploading'), 'O comando deploy não iniciou o fluxo esperado.', 'Deploying...', output.toString());
37
+
38
+ console.log('PASS');
39
+ } catch(e: any) {
40
+ console.error(e.stderr?.toString() || e.message);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ testSuite();
45
+ `;
46
+ try {
47
+ fs_1.default.writeFileSync(testFilePath, code);
48
+ const stdout = (0, child_process_1.execSync)('npx ts-node -T .eval-stage1.ts', { cwd, stdio: 'pipe' });
49
+ return stdout.toString().includes('PASS');
50
+ }
51
+ catch (err) {
52
+ console.log(chalk_1.default.red(err.stderr?.toString().trim() || err.stdout?.toString().trim() || err.message));
53
+ return false;
54
+ }
55
+ finally {
56
+ if (fs_1.default.existsSync(testFilePath))
57
+ fs_1.default.unlinkSync(testFilePath);
58
+ }
59
+ }
60
+ return true;
61
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@devquest/cli",
3
+ "version": "1.0.0",
4
+ "description": "DevQuest CLI - Construa seu próprio Redis, Docker, Git e mais",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "devquest": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build",
18
+ "dev": "ts-node src/index.ts",
19
+ "start": "node dist/index.js"
20
+ },
21
+ "dependencies": {
22
+ "axios": "^1.6.8",
23
+ "chalk": "^4.1.2",
24
+ "commander": "^12.0.0",
25
+ "keytar": "^7.9.0",
26
+ "simple-git": "^3.24.0",
27
+ "ws": "^8.16.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.12.7",
31
+ "@types/ws": "^8.5.10",
32
+ "ts-node": "^10.9.2",
33
+ "typescript": "^5.4.5"
34
+ }
35
+ }