Spaces:
Runtime error
Runtime error
| // scripts/check/check-circular-deps.mjs | |
| // Gate: dpdm circular-deps cross-check (segunda opinião complementar ao check-cycles.mjs). | |
| // | |
| // check-cycles.mjs usa AST-TS próprio mas cobre apenas 5 sub-árvores + somente | |
| // imports relativos. Este script usa dpdm (v4) que rastreia path-aliases via | |
| // tsconfig.json e cobre entrypoints de alto risco. | |
| // | |
| // Advisory nesta versão: exit 0 sempre; imprime `circularDeps=N` para baseline. | |
| // Direção da catraca: down (não pode subir). Adicionar ao quality-baseline.json | |
| // como `{ value: N, direction: "down" }` após a primeira run verde no CI. | |
| // | |
| // Escopo limitado a 4 entrypoints principais para manter o tempo de análise | |
| // abaixo de 60s. dpdm rastreia transitivamente todas as deps de cada entry. | |
| // Cobrir mais entries aumenta o tempo sem proporcional ganho (as deps core se | |
| // repetem via transitividade). | |
| // | |
| // Nota: dpdm pode reportar mais ciclos que check-cycles.mjs porque conta | |
| // permutações de paths que passam pelo mesmo SCC, não apenas SCCs únicos. | |
| // Isso é esperado — ferramentas diferentes, métricas complementares. | |
| import { execFileSync } from "node:child_process"; | |
| import { existsSync } from "node:fs"; | |
| import { readFileSync } from "node:fs"; | |
| import { resolve, dirname } from "node:path"; | |
| import { fileURLToPath, pathToFileURL } from "node:url"; | |
| import os from "node:os"; | |
| import path from "node:path"; | |
| import fs from "node:fs"; | |
| const __dirname = dirname(fileURLToPath(import.meta.url)); | |
| const projectRoot = resolve(__dirname, "../.."); | |
| // Entrypoints: cobrem o pipeline principal (chat, combo, MCP, DB). | |
| // Mantido enxuto para que `dpdm -T` (transform) termine em < 60s. | |
| const ENTRYPOINTS = [ | |
| "open-sse/handlers/chatCore.ts", | |
| "open-sse/services/combo.ts", | |
| "open-sse/mcp-server/index.ts", | |
| "src/lib/db/core.ts", | |
| ]; | |
| const DPDM_BIN = resolve(projectRoot, "node_modules/.bin/dpdm"); | |
| const TSCONFIG = resolve(projectRoot, "tsconfig.json"); | |
| /** | |
| * Parseia a saída JSON do dpdm e retorna a contagem de ciclos. | |
| * Função exportada para ser testada isoladamente sem executar o dpdm. | |
| * | |
| * @param {string} jsonStr - string com o JSON de saída do dpdm (campo "circulars"). | |
| * @returns {{ count: number, circulars: string[][] }} | |
| */ | |
| export function parseDpdmOutput(jsonStr) { | |
| let parsed; | |
| try { | |
| parsed = JSON.parse(jsonStr); | |
| } catch { | |
| throw new Error(`dpdm JSON parse failed: ${jsonStr.slice(0, 200)}`); | |
| } | |
| const circulars = Array.isArray(parsed.circulars) ? parsed.circulars : []; | |
| return { count: circulars.length, circulars }; | |
| } | |
| /** | |
| * Executa o dpdm e retorna a string JSON bruta do arquivo de saída. | |
| * | |
| * @returns {string} conteúdo JSON do arquivo temporário. | |
| */ | |
| function runDpdm() { | |
| if (!existsSync(DPDM_BIN)) { | |
| throw new Error(`dpdm binary not found at ${DPDM_BIN}. Run: npm install`); | |
| } | |
| const tmpFile = path.join(os.tmpdir(), `dpdm-output-${process.pid}.json`); | |
| try { | |
| execFileSync( | |
| "node", | |
| [ | |
| DPDM_BIN, | |
| "--circular", | |
| "--no-warning", | |
| "--no-tree", | |
| "-T", | |
| "--tsconfig", | |
| TSCONFIG, | |
| "-o", | |
| tmpFile, | |
| ...ENTRYPOINTS, | |
| ], | |
| { | |
| cwd: projectRoot, | |
| stdio: "inherit", | |
| timeout: 120_000, | |
| } | |
| ); | |
| if (!existsSync(tmpFile)) { | |
| throw new Error(`dpdm did not produce output file at ${tmpFile}`); | |
| } | |
| const raw = readFileSync(tmpFile, "utf8"); | |
| return raw; | |
| } finally { | |
| try { | |
| fs.unlinkSync(tmpFile); | |
| } catch { | |
| // best-effort cleanup | |
| } | |
| } | |
| } | |
| function main() { | |
| console.log("[circular-deps] Running dpdm cross-check..."); | |
| console.log(`[circular-deps] Entrypoints: ${ENTRYPOINTS.join(", ")}`); | |
| let raw; | |
| try { | |
| raw = runDpdm(); | |
| } catch (err) { | |
| console.error(`[circular-deps] ERROR running dpdm: ${err.message}`); | |
| process.exit(1); | |
| } | |
| let result; | |
| try { | |
| result = parseDpdmOutput(raw); | |
| } catch (err) { | |
| console.error(`[circular-deps] ERROR parsing dpdm output: ${err.message}`); | |
| process.exit(1); | |
| } | |
| // Advisory mode: always exit 0. Catraca pode ser adicionada no quality-baseline.json | |
| // após baseline ser estabelecida. | |
| console.log(`[circular-deps] circularDeps=${result.count}`); | |
| console.log( | |
| `[circular-deps] Advisory — add to quality-baseline.json: { "value": ${result.count}, "direction": "down" }` | |
| ); | |
| process.exit(0); | |
| } | |
| // Permite que o módulo seja importado em testes sem executar main(). | |
| if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main(); | |