mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
## 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
135 lines
3.8 KiB
JavaScript
135 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { spawn } from "node:child_process";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
/**
|
|
* This repository contains a legacy `app/` snapshot (packaging/runtime artifacts)
|
|
* alongside the active Next.js source in `src/app/`. Next.js route discovery scans
|
|
* both and fails the build on legacy files. We temporarily move the legacy folder
|
|
* out of the project root during `next build`, then restore it in all outcomes.
|
|
*/
|
|
|
|
const projectRoot = process.cwd();
|
|
const legacyAppDir = path.join(projectRoot, "app");
|
|
const backupDir = path.join(projectRoot, `.app-build-backup-${process.pid}-${Date.now()}`);
|
|
|
|
async function exists(targetPath) {
|
|
try {
|
|
await fs.access(targetPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function movePath(sourcePath, destinationPath, fsImpl = fs) {
|
|
try {
|
|
await fsImpl.rename(sourcePath, destinationPath);
|
|
} catch (error) {
|
|
if (error?.code !== "EXDEV") {
|
|
throw error;
|
|
}
|
|
|
|
console.warn(
|
|
`[build-next-isolated] EXDEV while moving ${sourcePath} -> ${destinationPath}; falling back to copy/remove`
|
|
);
|
|
await fsImpl.cp(sourcePath, destinationPath, {
|
|
recursive: true,
|
|
preserveTimestamps: true,
|
|
force: false,
|
|
errorOnExist: true,
|
|
});
|
|
await fsImpl.rm(sourcePath, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function runNextBuild() {
|
|
return new Promise((resolve) => {
|
|
const nextBin = path.join(projectRoot, "node_modules", "next", "dist", "bin", "next");
|
|
const child = spawn(process.execPath, [nextBin, "build"], {
|
|
cwd: projectRoot,
|
|
stdio: "inherit",
|
|
env: resolveNextBuildEnv(process.env),
|
|
});
|
|
|
|
const forward = (signal) => {
|
|
if (!child.killed) child.kill(signal);
|
|
};
|
|
|
|
process.on("SIGINT", forward);
|
|
process.on("SIGTERM", forward);
|
|
|
|
child.on("exit", (code, signal) => {
|
|
process.off("SIGINT", forward);
|
|
process.off("SIGTERM", forward);
|
|
if (signal) {
|
|
resolve({ code: 1, signal });
|
|
return;
|
|
}
|
|
resolve({ code: code ?? 1, signal: null });
|
|
});
|
|
});
|
|
}
|
|
|
|
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;
|
|
|
|
try {
|
|
if (await exists(legacyAppDir)) {
|
|
await movePath(legacyAppDir, backupDir);
|
|
moved = true;
|
|
}
|
|
|
|
const result = await runNextBuild();
|
|
if (result.code === 0 && (await exists(path.join(projectRoot, ".next", "standalone")))) {
|
|
console.log("[build-next-isolated] Copying static assets for standalone server...");
|
|
try {
|
|
await fs.cp(
|
|
path.join(projectRoot, "public"),
|
|
path.join(projectRoot, ".next", "standalone", "public"),
|
|
{ recursive: true }
|
|
);
|
|
await fs.cp(
|
|
path.join(projectRoot, ".next", "static"),
|
|
path.join(projectRoot, ".next", "standalone", ".next", "static"),
|
|
{ recursive: true }
|
|
);
|
|
} catch (copyErr) {
|
|
console.warn("[build-next-isolated] Non-fatal error copying static assets:", copyErr);
|
|
}
|
|
}
|
|
process.exitCode = result.code;
|
|
} catch (error) {
|
|
console.error("[build-next-isolated] Build failed:", error);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
if (moved) {
|
|
try {
|
|
await movePath(backupDir, legacyAppDir);
|
|
} catch (restoreError) {
|
|
console.error(
|
|
`[build-next-isolated] Failed to restore legacy app dir from ${backupDir}:`,
|
|
restoreError
|
|
);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const entryScript = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;
|
|
|
|
if (entryScript === import.meta.url) {
|
|
await main();
|
|
}
|