import http from "node:http"; import { spawn } from "node:child_process"; import { pathToFileURL } from "node:url"; import path from "node:path"; import { readFile, readdir } from "node:fs/promises"; type ModuleDefinition = { id: string; label: string; entrypoint: string; queries: Record; implementations: Record) => { sql: string }>>; schedules?: Record; }; const root = process.cwd(); const srcDir = path.join(root, "src"); const portArg = Number(process.argv.find((arg) => arg.startsWith("--port="))?.split("=")[1]); const startPort = Number.isFinite(portArg) && portArg > 0 ? portArg : 4317; async function findModuleFiles(dir: string): Promise { const entries = await readdir(dir, { withFileTypes: true }); const files = await Promise.all( entries.map(async (entry) => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) return findModuleFiles(fullPath); if (entry.isFile() && entry.name.endsWith(".module.ts")) return [fullPath]; return []; }), ); return files.flat(); } async function loadModules() { const files = await findModuleFiles(srcDir); const modules = await Promise.all( files.map(async (file) => { const imported = await import(pathToFileURL(file).href + `?t=${Date.now()}`); const module = imported.default as ModuleDefinition; return { file: path.relative(root, file).replace(/\\/g, "/"), module, }; }), ); return modules.sort((a, b) => a.module.id.localeCompare(b.module.id)); } function json(res: http.ServerResponse, status: number, data: unknown) { res.writeHead(status, { "content-type": "application/json; charset=utf-8" }); res.end(JSON.stringify(data, null, 2)); } function text(res: http.ServerResponse, status: number, data: string) { res.writeHead(status, { "content-type": "text/plain; charset=utf-8" }); res.end(data); } function html(res: http.ServerResponse, status: number, data: string) { res.writeHead(status, { "content-type": "text/html; charset=utf-8" }); res.end(data); } function jpg(res: http.ServerResponse, status: number, data: Buffer) { res.writeHead(status, { "content-type": "image/jpeg", "cache-control": "public, max-age=3600", }); res.end(data); } async function readJsonBody(req: http.IncomingMessage) { const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); const raw = Buffer.concat(chunks).toString("utf8"); return raw ? JSON.parse(raw) : {}; } function defaultArgs(params: string[]) { const args: Record = { ctx_user_companies: "1,2,3", ctx_user_companies_for_module: "1,2,3", }; for (const param of params) args[param] = `:${param}`; return args; } function vetProject() { return new Promise<{ code: number | null; output: string }>((resolve) => { const child = spawn(process.platform === "win32" ? "npm.cmd" : "npm", ["run", "vet"], { cwd: root, shell: false, }); let output = ""; child.stdout.on("data", (chunk) => (output += chunk.toString())); child.stderr.on("data", (chunk) => (output += chunk.toString())); child.on("close", (code) => resolve({ code, output })); }); } function parseManifestValue(value: string) { const trimmed = value.trim(); if (trimmed.toLowerCase() === "null") return null; if (trimmed.toLowerCase() === "true") return true; if (trimmed.toLowerCase() === "false") return false; if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed); if ( (trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"')) ) { return trimmed.slice(1, -1); } return value; } function findBearerToken(value: unknown): string | null { if (!value || typeof value !== "object") return null; if ( "token" in value && typeof (value as Record).token === "string" && (value as Record).token.trim() ) { return (value as Record).token.replace(/^Bearer\s+/i, "").trim(); } const preferredKeys = new Set([ "access_token", "accessToken", "bearer", "bearerToken", "token", "jwt", ]); const preferredLowerKeys = new Set([...preferredKeys].map((key) => key.toLowerCase())); const queue: unknown[] = [value]; while (queue.length) { const current = queue.shift(); if (!current || typeof current !== "object") continue; for (const [key, nested] of Object.entries(current)) { if (typeof nested === "string" && preferredKeys.has(key)) { return nested.replace(/^Bearer\s+/i, "").trim(); } if (typeof nested === "string" && preferredLowerKeys.has(key.toLowerCase())) { return nested.replace(/^Bearer\s+/i, "").trim(); } if (nested && typeof nested === "object") queue.push(nested); } } return null; } async function postLogin(loginUrl: string, payload: Record) { const response = await fetch(loginUrl, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), }); const textBody = await response.text(); let parsedBody: unknown = textBody; try { parsedBody = textBody ? JSON.parse(textBody) : null; } catch { parsedBody = textBody; } return { ok: response.ok, status: response.status, bearerToken: findBearerToken(parsedBody), responseBody: parsedBody, }; } async function handleApi(req: http.IncomingMessage, res: http.ServerResponse) { const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`); if (req.method === "GET" && url.pathname === "/api/modules") { const modules = await loadModules(); return json( res, 200, modules.map(({ file, module }) => ({ file, id: module.id, label: module.label, entrypoint: module.entrypoint, queries: Object.entries(module.queries).map(([key, query]) => ({ key, name: query.name, params: query.params, })), systems: Object.keys(module.implementations || {}), })), ); } if (req.method === "GET" && url.pathname === "/assets/logo-davinti.jpg") { const image = await readFile(path.join(root, "scripts", "assets", "logo-davinti.jpg")); return jpg(res, 200, image); } if (req.method === "GET" && url.pathname === "/api/docs") { const markdown = await readFile(path.join(root, "scripts", "module-test-app.md"), "utf8"); return json(res, 200, { markdown }); } if (req.method === "POST" && url.pathname === "/api/render") { const body = await readJsonBody(req); const modules = await loadModules(); const found = modules.find(({ module }) => module.id === body.moduleId); if (!found) return json(res, 404, { error: "Module not found" }); const query = found.module.queries[body.queryKey]; const implementation = found.module.implementations?.[body.system]?.[body.queryKey]; if (!query || !implementation) return json(res, 404, { error: "Query implementation not found" }); const args = { ...defaultArgs(query.params), ...(body.args || {}) }; const result = implementation(args); return json(res, 200, { sql: result.sql, args }); } if (req.method === "POST" && url.pathname === "/api/login") { const body = await readJsonBody(req); const loginUrl = String(body.loginUrl || ""); const email = String(body.email || ""); const senha = String(body.senha || ""); if (!loginUrl || !email || !senha) { return json(res, 400, { error: "loginUrl, email e senha sao obrigatorios" }); } const attempts = [ { email, senha }, { email, password: senha }, ]; const results = []; for (const payload of attempts) { const result = await postLogin(loginUrl, payload); results.push(result); if (result.ok && result.bearerToken) { return json(res, 200, { status: result.status, bearerToken: result.bearerToken, responseBody: result.responseBody, }); } } const lastResult = results[results.length - 1]; const successfulWithoutToken = results.find((result) => result.ok); if (successfulWithoutToken) { return json(res, 502, { error: "Login realizado, mas nao encontrei token no retorno", status: successfulWithoutToken.status, responseBody: successfulWithoutToken.responseBody, }); } return json(res, lastResult.status, { error: "Login retornou erro HTTP " + lastResult.status, status: lastResult.status, responseBody: lastResult.responseBody, }); } if (req.method === "POST" && url.pathname === "/api/manifest-execute") { const body = await readJsonBody(req); const baseUrl = String(body.baseUrl || "").replace(/\/+$/, ""); const moduleId = String(body.moduleId || ""); const queryKey = String(body.queryKey || ""); const clientId = String(body.clientId || ""); const bearerToken = String(body.bearerToken || ""); const params = body.params || {}; if (!baseUrl || !moduleId || !queryKey) { return json(res, 400, { error: "baseUrl, moduleId e queryKey sao obrigatorios" }); } const endpoint = `${baseUrl}/api/manifest/modules/${encodeURIComponent(moduleId)}/queries/${encodeURIComponent(queryKey)}/execute`; const headers: Record = { "content-type": "application/json", }; if (clientId) headers["x-client-id"] = clientId; if (bearerToken) headers.authorization = `Bearer ${bearerToken}`; const response = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify(params), }); const textBody = await response.text(); let parsedBody: unknown = textBody; try { parsedBody = textBody ? JSON.parse(textBody) : null; } catch { parsedBody = textBody; } return json(res, 200, { status: response.status, ok: response.ok, url: endpoint, requestBody: params, responseBody: parsedBody, }); } if (req.method === "POST" && url.pathname === "/api/vet") { const result = await vetProject(); return json(res, result.code === 0 ? 200 : 500, result); } return false; } const page = String.raw` App Dono Modulos - Teste Local
App Dono Modulos Teste local de queries e manifesto remoto
Vitruvio manifest tools

Modulo de teste

Local + remoto

Selecao

Os valores sao expressoes SQL cruas. Para testar literal de data, use aspas: '2026-05-08'.

Autenticacao

Parametros

Manifesto


    
`; const server = http.createServer(async (req, res) => { try { const handled = await handleApi(req, res); if (handled !== false) return; if (req.method === "GET" && (req.url === "/" || req.url?.startsWith("/?"))) { return html(res, 200, page); } return text(res, 404, "Not found"); } catch (error) { return json(res, 500, { error: error instanceof Error ? error.message : String(error) }); } }); function listen(port: number) { server.once("error", (error: NodeJS.ErrnoException) => { if (error.code === "EADDRINUSE") listen(port + 1); else throw error; }); server.listen(port, () => { const address = server.address(); const actualPort = typeof address === "object" && address ? address.port : port; console.log(`Module test app: http://localhost:${actualPort}`); }); } listen(startPort);