mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
feat: v3.6.4 — Combo Builder v2, Composite Tiers, P2C Credentials, Observability Layer
## New Features - Combo Builder v2 wizard UI (multi-stage: Basics → Steps → Strategy → Review) - Combo Step Architecture Schema v2 (ComboModelStep, ComboRefStep, pinned accounts) - Composite Tiers system for tiered model routing with fallback chains - Model Capabilities Registry (unified resolver merging specs + registry + synced data) - Observability module (buildHealthPayload, buildTelemetryPayload, buildSessionsSummary) - Session & Quota Monitor panels on Health dashboard - Combo Health per-target analytics via resolveNestedComboTargets() - Combo Builder Options API (GET /api/combos/builder/options) ## Performance - Middleware lazy loading (apiAuth, db/settings, modelSyncScheduler) - E2E auth bypass mode (NEXT_PUBLIC_OMNIROUTE_E2E_MODE) ## Bug Fixes - P2C credential selection with quota headroom awareness - Fixed-account combo steps bypass model cooldowns/circuit breakers - Combo metrics per-target tracking (byTarget with executionKey) - Call logs schema expansion (7 new columns + composite index) - Quota monitor lifecycle enrichment (status, snapshots, summary) - Codex quota fetcher hardening ## Maintenance - DB migration 021 (combo_call_log_targets) - Combo CRUD normalization on read - Playwright config + build script improvements - OpenAPI spec version sync to 3.6.4 ## Tests - 16 new test suites + 12 existing test updates - 86 files changed, +8318 -1378 lines
This commit is contained in:
@@ -52,7 +52,7 @@ function runNextBuild() {
|
||||
const child = spawn(process.execPath, [nextBin, "build"], {
|
||||
cwd: projectRoot,
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
env: resolveNextBuildEnv(process.env),
|
||||
});
|
||||
|
||||
const forward = (signal) => {
|
||||
@@ -74,6 +74,13 @@ function runNextBuild() {
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveNextBuildEnv(baseEnv = process.env) {
|
||||
return {
|
||||
...baseEnv,
|
||||
NEXT_PRIVATE_BUILD_WORKER: baseEnv.NEXT_PRIVATE_BUILD_WORKER || "0",
|
||||
};
|
||||
}
|
||||
|
||||
export async function main() {
|
||||
let moved = false;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync, renameSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync, renameSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
resolveRuntimePorts,
|
||||
sanitizeColorEnv,
|
||||
@@ -16,10 +17,19 @@ const cwd = process.cwd();
|
||||
const appDir = join(cwd, "app");
|
||||
const srcAppDir = join(cwd, "src", "app");
|
||||
const appPage = join(appDir, "page.tsx");
|
||||
const backupDir = join(cwd, "app.__qa_backup");
|
||||
const defaultBackupDir = join(cwd, "app.__qa_backup");
|
||||
const backupDir = resolvePlaywrightAppBackupDir({
|
||||
cwd,
|
||||
baseBackupExists: existsSync(defaultBackupDir),
|
||||
appDirExists: existsSync(appDir),
|
||||
});
|
||||
const usingAlternativeBackupDir = backupDir !== defaultBackupDir;
|
||||
const buildScript = join(cwd, "scripts", "build-next-isolated.mjs");
|
||||
const standaloneServer = join(cwd, testDistDir(), "standalone", "server.js");
|
||||
const buildIdFile = join(cwd, testDistDir(), "BUILD_ID");
|
||||
const rootStaticDir = join(cwd, testDistDir(), "static");
|
||||
const rootPublicDir = join(cwd, "public");
|
||||
const standaloneStaticDir = join(cwd, testDistDir(), "standalone", ".next", "static");
|
||||
const standalonePublicDir = join(cwd, testDistDir(), "standalone", "public");
|
||||
|
||||
let appDirMoved = false;
|
||||
|
||||
@@ -27,19 +37,90 @@ function testDistDir() {
|
||||
return process.env.NEXT_DIST_DIR || ".next";
|
||||
}
|
||||
|
||||
export function resolvePlaywrightAppBackupDir({
|
||||
cwd,
|
||||
baseBackupExists,
|
||||
appDirExists,
|
||||
pid = process.pid,
|
||||
now = Date.now(),
|
||||
}) {
|
||||
const baseBackupDir = join(cwd, "app.__qa_backup");
|
||||
if (!baseBackupExists || !appDirExists) {
|
||||
return baseBackupDir;
|
||||
}
|
||||
|
||||
return join(cwd, `app.__qa_backup.${pid}.${now}`);
|
||||
}
|
||||
|
||||
function shouldMoveAppDir() {
|
||||
return existsSync(appDir) && !existsSync(appPage) && existsSync(srcAppDir);
|
||||
}
|
||||
|
||||
export function directoryHasEntries(dirPath) {
|
||||
try {
|
||||
return readdirSync(dirPath).length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function standaloneAssetsNeedSync({
|
||||
standaloneServerPath,
|
||||
rootStaticDirPath,
|
||||
standaloneStaticDirPath,
|
||||
}) {
|
||||
return (
|
||||
existsSync(standaloneServerPath) &&
|
||||
existsSync(rootStaticDirPath) &&
|
||||
!directoryHasEntries(standaloneStaticDirPath)
|
||||
);
|
||||
}
|
||||
|
||||
export function syncStandaloneRuntimeAssets({
|
||||
standaloneServerPath,
|
||||
rootStaticDirPath,
|
||||
standaloneStaticDirPath,
|
||||
rootPublicDirPath,
|
||||
standalonePublicDirPath,
|
||||
log = console,
|
||||
}) {
|
||||
if (!existsSync(standaloneServerPath)) return false;
|
||||
|
||||
let changed = false;
|
||||
|
||||
if (existsSync(rootPublicDirPath) && !directoryHasEntries(standalonePublicDirPath)) {
|
||||
cpSync(rootPublicDirPath, standalonePublicDirPath, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (existsSync(rootStaticDirPath) && !directoryHasEntries(standaloneStaticDirPath)) {
|
||||
mkdirSync(dirname(standaloneStaticDirPath), {
|
||||
recursive: true,
|
||||
});
|
||||
cpSync(rootStaticDirPath, standaloneStaticDirPath, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
log.log("[Playwright WebServer] Rehydrated standalone static/public assets");
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
function prepareAppDir() {
|
||||
if (!shouldMoveAppDir()) return;
|
||||
|
||||
if (existsSync(backupDir)) {
|
||||
if (usingAlternativeBackupDir) {
|
||||
console.warn(
|
||||
"[Playwright WebServer] app.__qa_backup already exists; leaving app/ in place. " +
|
||||
"If tests hit 404 on every route, clear app/ artifacts before running e2e."
|
||||
"[Playwright WebServer] Existing app.__qa_backup detected; using a per-run backup dir instead."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
renameSync(appDir, backupDir);
|
||||
@@ -55,14 +136,6 @@ function restoreAppDir() {
|
||||
console.log("[Playwright WebServer] Restored app/ directory");
|
||||
}
|
||||
|
||||
process.on("exit", restoreAppDir);
|
||||
process.on("uncaughtException", (error) => {
|
||||
restoreAppDir();
|
||||
throw error;
|
||||
});
|
||||
|
||||
prepareAppDir();
|
||||
|
||||
const bootstrapEnvVars = bootstrapEnv({ quiet: true });
|
||||
const runtimePorts = resolveRuntimePorts(bootstrapEnvVars);
|
||||
const testServerEnv = {
|
||||
@@ -73,8 +146,17 @@ const testServerEnv = {
|
||||
OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK: process.env.OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK || "1",
|
||||
OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK: process.env.OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK || "1",
|
||||
OMNIROUTE_HIDE_HEALTHCHECK_LOGS: process.env.OMNIROUTE_HIDE_HEALTHCHECK_LOGS || "1",
|
||||
...(process.env.OMNIROUTE_USE_TURBOPACK
|
||||
? {
|
||||
OMNIROUTE_USE_TURBOPACK: process.env.OMNIROUTE_USE_TURBOPACK,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
export function shouldUseWebpackForPlaywrightDev({ mode, env }) {
|
||||
return mode === "dev" && env.OMNIROUTE_USE_TURBOPACK !== "1";
|
||||
}
|
||||
|
||||
function runChild(command, args, env) {
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(command, args, {
|
||||
@@ -100,7 +182,7 @@ function runChild(command, args, env) {
|
||||
async function runBuildForStart() {
|
||||
if (mode !== "start") return;
|
||||
if (process.env.OMNIROUTE_PLAYWRIGHT_SKIP_BUILD === "1") return;
|
||||
if (existsSync(buildIdFile)) return;
|
||||
console.log("[Playwright WebServer] Building fresh standalone app for this run...");
|
||||
|
||||
const buildEnv = withRuntimePortEnv(testServerEnv, runtimePorts);
|
||||
const result = await runChild(process.execPath, [buildScript], buildEnv);
|
||||
@@ -115,18 +197,37 @@ async function runBuildForStart() {
|
||||
}
|
||||
}
|
||||
|
||||
await runBuildForStart();
|
||||
if (mode === "start") {
|
||||
if (existsSync(standaloneServer)) {
|
||||
spawnWithForwardedSignals(process.execPath, [standaloneServer], {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...withRuntimePortEnv(testServerEnv, runtimePorts),
|
||||
PORT: String(runtimePorts.dashboardPort),
|
||||
HOSTNAME: process.env.HOSTNAME || "127.0.0.1",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
export async function main() {
|
||||
process.on("exit", restoreAppDir);
|
||||
process.on("uncaughtException", (error) => {
|
||||
restoreAppDir();
|
||||
throw error;
|
||||
});
|
||||
|
||||
prepareAppDir();
|
||||
await runBuildForStart();
|
||||
|
||||
if (mode === "start") {
|
||||
if (existsSync(standaloneServer)) {
|
||||
syncStandaloneRuntimeAssets({
|
||||
standaloneServerPath: standaloneServer,
|
||||
rootStaticDirPath: rootStaticDir,
|
||||
standaloneStaticDirPath: standaloneStaticDir,
|
||||
rootPublicDirPath: rootPublicDir,
|
||||
standalonePublicDirPath: standalonePublicDir,
|
||||
});
|
||||
|
||||
spawnWithForwardedSignals(process.execPath, [standaloneServer], {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...withRuntimePortEnv(testServerEnv, runtimePorts),
|
||||
PORT: String(runtimePorts.dashboardPort),
|
||||
HOSTNAME: process.env.HOSTNAME || "127.0.0.1",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const args = [
|
||||
"./node_modules/next/dist/bin/next",
|
||||
"start",
|
||||
@@ -138,18 +239,26 @@ if (mode === "start") {
|
||||
stdio: "inherit",
|
||||
env: withRuntimePortEnv(testServerEnv, runtimePorts),
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
const args = [
|
||||
"./node_modules/next/dist/bin/next",
|
||||
mode,
|
||||
"--webpack",
|
||||
"--port",
|
||||
String(runtimePorts.dashboardPort),
|
||||
];
|
||||
|
||||
if (shouldUseWebpackForPlaywrightDev({ mode, env: testServerEnv })) {
|
||||
args.splice(2, 0, "--webpack");
|
||||
}
|
||||
|
||||
spawnWithForwardedSignals(process.execPath, args, {
|
||||
stdio: "inherit",
|
||||
env: withRuntimePortEnv(testServerEnv, runtimePorts),
|
||||
});
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
await main();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user