mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Release v3.8.30 (#4267)
Release v3.8.30 — see CHANGELOG.md [3.8.30] for the full release notes.
This commit is contained in:
committed by
GitHub
parent
ab8096071c
commit
db362b0126
@@ -23,7 +23,12 @@ const BASELINE_PATH = path.resolve(
|
||||
);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const CONFIG_PATH = path.join(ROOT, "eslint.complexity.config.mjs");
|
||||
const ESLINT_ARGS = [
|
||||
// Exported for the gate's own unit test (tests/unit/build/check-complexity.test.ts), which
|
||||
// locks the scan scope to the one documented in eslint.complexity.config.mjs `files` and in
|
||||
// complexity-baseline.json. The positional paths MUST match that scope (src+open-sse+electron+bin)
|
||||
// — ESLint flat config only walks the directories passed here, so a `files` glob for bin/electron
|
||||
// is inert unless the directory is also passed as a positional argument.
|
||||
export const ESLINT_ARGS = [
|
||||
"eslint",
|
||||
"--no-config-lookup",
|
||||
"--config",
|
||||
@@ -32,6 +37,8 @@ const ESLINT_ARGS = [
|
||||
"json",
|
||||
"src",
|
||||
"open-sse",
|
||||
"electron",
|
||||
"bin",
|
||||
];
|
||||
|
||||
/** Avalia a contagem atual de violações contra o baseline. */
|
||||
|
||||
@@ -40,6 +40,8 @@ const HANDLERS_DIR = path.join(cwd, "open-sse/handlers");
|
||||
export const INTENTIONALLY_INTERNAL = new Set([
|
||||
"_rowTypes", // type-only: 5 importers internos em db/ (AgentBridge/Inspector row types)
|
||||
"accessTokens", // intentionally-internal: 4 rotas /api/cli/* (connect, whoami, tokens, tokens/[id]) + server/authz/accessTokenAuth.ts via import direto "@/lib/db/accessTokens" (Rule #2)
|
||||
"apiKeyColumnFallbacks", // db-internal: importado só por db/apiKeys.ts (API_KEY_COLUMN_FALLBACKS — fallbacks de coluna split do apiKeys.ts)
|
||||
"apiKeyUsageLimitFields", // db-internal: importado só por db/apiKeys.ts (helpers de campo de limite de uso split do apiKeys.ts; mig 101)
|
||||
"cleanup", // intentionally-internal: 3 API routes (purge-quota-snapshots, purge-call-logs, purge-detailed-logs)
|
||||
"cliToolState", // intentionally-internal: 14+ API routes em /api/cli-tools/*-settings
|
||||
"comboForecast", // intentionally-internal: src/lib/usage/comboForecast.ts
|
||||
|
||||
@@ -106,6 +106,8 @@ const ENV_VAR_ALLOWLIST = new Set([
|
||||
"NINEROUTER_API_KEY", // injected into the 9router subprocess at spawn (EMBEDDED-SERVICES.md)
|
||||
"CLAUDE_CODE_MAX_OUTPUT_TOKENS", // Claude Code CLI's own env var (CODEX-CLI-CONFIGURATION.md)
|
||||
"CODEX_HOME", // Codex CLI's own config-home env var (CODEX-CLI-CONFIGURATION.md)
|
||||
"GEMINI_API_KEY", // Gemini CLI's own API-key env var, set by `omniroute setup-gemini` (REMOTE-MODE.md)
|
||||
"GOOGLE_GEMINI_BASE_URL", // Gemini CLI's own base-URL env var, set by `omniroute setup-gemini` (REMOTE-MODE.md)
|
||||
"REDIS_PORT", // docker-compose host-port override (DOCKER_GUIDE.md)
|
||||
"AUTO_UPDATE_HOST_REPO_DIR", // docker-compose self-update mount (DOCKER_GUIDE.md)
|
||||
"LINUX_GPG_KEY", // electron AppImage signing key, CI/build only (ELECTRON_GUIDE.md)
|
||||
|
||||
@@ -20,6 +20,8 @@ const BASELINE_PATH = path.resolve(
|
||||
);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const SCAN_DIRS = ["src", "open-sse", "electron", "bin"];
|
||||
// Test files live under tests/ plus co-located *.test.ts(x) inside the source dirs.
|
||||
const TEST_SCAN_DIRS = ["tests", ...SCAN_DIRS];
|
||||
// Directories to skip when walking — build artifacts and installed packages.
|
||||
const SKIP_DIRS = new Set(["node_modules", "dist-electron", ".next", ".build", "dist", "coverage"]);
|
||||
|
||||
@@ -71,6 +73,29 @@ function collectLoc() {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Walk for TEST files: collects *.test.ts / *.test.tsx (the inverse of walk(),
|
||||
// which deliberately excludes them). Skips .d.ts and the same SKIP_DIRS.
|
||||
function walkTests(dir, acc = []) {
|
||||
if (!fs.existsSync(dir)) return acc;
|
||||
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, e.name);
|
||||
if (e.isDirectory()) {
|
||||
if (!SKIP_DIRS.has(e.name)) walkTests(p, acc);
|
||||
} else if (/\.test\.tsx?$/.test(e.name) && !/\.d\.ts$/.test(e.name)) {
|
||||
acc.push(p);
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
function collectTestLoc() {
|
||||
const out = {};
|
||||
for (const d of TEST_SCAN_DIRS)
|
||||
for (const f of walkTests(path.join(ROOT, d)))
|
||||
out[path.relative(ROOT, f).replace(/\\/g, "/")] = countLines(f);
|
||||
return out;
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(BASELINE_PATH)) {
|
||||
console.error(`[file-size] FAIL — ${path.basename(BASELINE_PATH)} ausente.`);
|
||||
@@ -82,28 +107,73 @@ function main() {
|
||||
const current = collectLoc();
|
||||
const { violations, improvements } = evaluateFileSizes(current, frozen, cap);
|
||||
|
||||
if (UPDATE && violations.length === 0 && improvements.length) {
|
||||
for (const [file, loc] of improvements) {
|
||||
if (loc <= cap)
|
||||
delete frozen[file]; // caiu para dentro do cap → sai do baseline
|
||||
else frozen[file] = loc; // continua grande mas encolheu → trava no novo valor
|
||||
// Test-file gate (Layer 1 anti-reinflation): same shrink-only + new-≤cap semantics,
|
||||
// reusing evaluateFileSizes against the testFrozen baseline + testCap.
|
||||
const testCap = baseline.testCap;
|
||||
const testFrozen = baseline.testFrozen || {};
|
||||
const currentTests = collectTestLoc();
|
||||
const { violations: testViolations, improvements: testImprovements } =
|
||||
typeof testCap === "number"
|
||||
? evaluateFileSizes(currentTests, testFrozen, testCap)
|
||||
: { violations: [], improvements: [] };
|
||||
|
||||
if (UPDATE) {
|
||||
let changed = false;
|
||||
if (violations.length === 0 && improvements.length) {
|
||||
for (const [file, loc] of improvements) {
|
||||
if (loc <= cap)
|
||||
delete frozen[file]; // caiu para dentro do cap → sai do baseline
|
||||
else frozen[file] = loc; // continua grande mas encolheu → trava no novo valor
|
||||
}
|
||||
baseline.frozen = Object.fromEntries(Object.entries(frozen).sort());
|
||||
changed = true;
|
||||
console.log(`[file-size] baseline ratcheado: ${improvements.length} arquivo(s) encolheram`);
|
||||
}
|
||||
baseline.frozen = Object.fromEntries(Object.entries(frozen).sort());
|
||||
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baseline, null, 2) + "\n");
|
||||
console.log(`[file-size] baseline ratcheado: ${improvements.length} arquivo(s) encolheram`);
|
||||
if (typeof testCap === "number" && testViolations.length === 0 && testImprovements.length) {
|
||||
for (const [file, loc] of testImprovements) {
|
||||
if (loc <= testCap)
|
||||
delete testFrozen[file]; // caiu para dentro do testCap → sai do baseline
|
||||
else testFrozen[file] = loc; // continua grande mas encolheu → trava no novo valor
|
||||
}
|
||||
baseline.testFrozen = Object.fromEntries(Object.entries(testFrozen).sort());
|
||||
changed = true;
|
||||
console.log(
|
||||
`[test-file-size] baseline ratcheado: ${testImprovements.length} arquivo(s) de teste encolheram`
|
||||
);
|
||||
}
|
||||
if (changed) fs.writeFileSync(BASELINE_PATH, JSON.stringify(baseline, null, 2) + "\n");
|
||||
}
|
||||
|
||||
let failed = false;
|
||||
if (violations.length) {
|
||||
console.error(
|
||||
`[file-size] ${violations.length} violação(ões):\n` +
|
||||
violations.map((v) => " ✗ " + v).join("\n") +
|
||||
`\n → modularize/extraia (DRY) para encolher, ou (último caso) ajuste file-size-baseline.json com justificativa.`
|
||||
);
|
||||
process.exit(1);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(
|
||||
`[file-size] OK — ${Object.keys(frozen).length} arquivos congelados, cap ${cap} para novos (${Object.keys(current).length} arquivos verificados)`
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`[file-size] OK — ${Object.keys(frozen).length} arquivos congelados, cap ${cap} para novos (${Object.keys(current).length} arquivos verificados)`
|
||||
);
|
||||
|
||||
if (typeof testCap === "number") {
|
||||
if (testViolations.length) {
|
||||
console.error(
|
||||
`[test-file-size] ${testViolations.length} test file violation(s) (testCap ${testCap}):\n` +
|
||||
testViolations.map((v) => " ✗ " + v).join("\n") +
|
||||
`\n → split the test file (extract helpers/sub-suites) to shrink it, or (last resort) adjust testFrozen in file-size-baseline.json with justification.`
|
||||
);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(
|
||||
`[test-file-size] OK — ${Object.keys(testFrozen).length} test files congelados, testCap ${testCap} para novos (${Object.keys(currentTests).length} test files verificados)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) process.exit(1);
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main();
|
||||
|
||||
@@ -68,18 +68,28 @@ export function mutationScoreForFile(fileData) {
|
||||
/**
|
||||
* Score por arquivo a partir de um ou mais reports (batches). Arquivos sem mutante
|
||||
* coberto (score null) são omitidos.
|
||||
*
|
||||
* ⭐ Sibling batches que fatiam o MESMO arquivo (auth.ts split em a1:1-1109 + a2:1110-2218
|
||||
* por mutation range; accountFallback em b1/b2) carregam fatias DISJUNTAS dos mutantes
|
||||
* daquele arquivo. O score verdadeiro do arquivo precisa de TODAS as fatias juntas, então
|
||||
* unimos `files[<arquivo>].mutants` entre os reports ANTES de pontuar — não sobrescrever
|
||||
* (senão a última fatia venceria e reportaria só metade do arquivo).
|
||||
* @param {object|object[]} reportOrReports parsed mutation.json (ou array)
|
||||
* @returns {Record<string, number>}
|
||||
*/
|
||||
export function measureMutationScores(reportOrReports) {
|
||||
const reports = Array.isArray(reportOrReports) ? reportOrReports : [reportOrReports];
|
||||
const out = {};
|
||||
const mutantsByFile = {};
|
||||
for (const report of reports) {
|
||||
for (const [file, data] of Object.entries(report?.files || {})) {
|
||||
const score = mutationScoreForFile(data);
|
||||
if (score !== null) out[file] = score;
|
||||
(mutantsByFile[file] ||= []).push(...(data?.mutants || []));
|
||||
}
|
||||
}
|
||||
const out = {};
|
||||
for (const [file, mutants] of Object.entries(mutantsByFile)) {
|
||||
const score = mutationScoreForFile({ mutants });
|
||||
if (score !== null) out[file] = score;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,8 @@ const ENV_KEY_RE = /(clientId|clientSecret|apiKey)Env\s*:/;
|
||||
// TODO(6A.8): Consider tightening CRED_KEY_RE to exclude function-signature contexts — but
|
||||
// that adds complexity; the FP rate is low (1 file). Frozen by file:line:value key.
|
||||
export const KNOWN_LITERAL_CREDS = new Set([
|
||||
"open-sse/services/usage.ts:546:minimax", // TODO(6A.8): pre-existing FP — TS fn-param type, not a credential (moved 543→546 by #3838 usage.ts comment)
|
||||
"open-sse/services/usage.ts:546:minimax-cn", // TODO(6A.8): pre-existing FP — TS fn-param type, not a credential (moved 543→546 by #3838 usage.ts comment)
|
||||
"open-sse/services/usage.ts:547:minimax", // TODO(6A.8): pre-existing FP — TS fn-param type, not a credential (moved 543→547 by #3838 usage.ts comment + #4293 Codex Spark extraction)
|
||||
"open-sse/services/usage.ts:547:minimax-cn", // TODO(6A.8): pre-existing FP — TS fn-param type, not a credential (moved 543→547 by #3838 usage.ts comment + #4293 Codex Spark extraction)
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,7 +53,7 @@ export const COLLECTORS = [
|
||||
// "vitest" e explodem no node runner). Subdir novo: adicione aqui E nos scripts
|
||||
// (o drift-check + o gate de órfãos forçam a manutenção em sincronia).
|
||||
{
|
||||
glob: "tests/unit/{api,auth,authz,build,cli,cli-helper,compression,correctness,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts",
|
||||
glob: "tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts",
|
||||
sources: ["package.json", ".github/workflows/ci.yml"],
|
||||
},
|
||||
// Node native runner — test:integration (top-level only; tests/integration/services/ NÃO roda)
|
||||
|
||||
Reference in New Issue
Block a user