mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 13:52:09 +03:00
Require dashboard session cookies on protected management APIs and reject bearer API keys with explicit 403 responses to prevent privilege escalation across provider, settings, and model alias routes. Add a dedicated payload rules management surface with dashboard UI, OpenAPI documentation, route normalization, and tests for hot-reloaded runtime updates. Consolidate provider catalog metadata for dashboard pages, add Perplexity web-cookie provider support, retire the legacy provider creation page, and improve upstream proxy handling. Harden startup and runtime behavior by moving cloud sync bootstrap to server instrumentation, skipping background services during build/test, making models.dev sync abortable, pruning isolated build artifacts, and improving DB backup and recovery safeguards.
190 lines
5.5 KiB
JavaScript
190 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
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 backupRoot = path.join(os.tmpdir(), `omniroute-build-isolated-${process.pid}-${Date.now()}`);
|
|
|
|
export function getTransientBuildPaths(rootDir = projectRoot, env = process.env) {
|
|
const paths = [
|
|
{
|
|
label: "legacy app snapshot",
|
|
sourcePath: path.join(rootDir, "app"),
|
|
backupPath: path.join(backupRoot, "app"),
|
|
},
|
|
];
|
|
|
|
if (env.OMNIROUTE_BUILD_MOVE_TASKS === "1") {
|
|
paths.push({
|
|
label: "task planning workspace",
|
|
sourcePath: path.join(rootDir, "_tasks"),
|
|
backupPath: path.join(backupRoot, "_tasks"),
|
|
});
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
async function exists(targetPath) {
|
|
try {
|
|
await fs.access(targetPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function movePath(sourcePath, destinationPath, fsImpl = fs) {
|
|
const mkdir = typeof fsImpl.mkdir === "function" ? fsImpl.mkdir.bind(fsImpl) : fs.mkdir.bind(fs);
|
|
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
|
|
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 pruneStandaloneArtifacts(rootDir = projectRoot, fsImpl = fs) {
|
|
const standaloneRoot = path.join(rootDir, ".next", "standalone");
|
|
const pruneTargets = [path.join(standaloneRoot, "_tasks")];
|
|
|
|
for (const targetPath of pruneTargets) {
|
|
if (!(await exists(targetPath))) continue;
|
|
await fsImpl.rm(targetPath, { recursive: true, force: true });
|
|
console.log(
|
|
`[build-next-isolated] Pruned standalone artifact: ${path.relative(rootDir, targetPath)}`
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function main() {
|
|
const movedPaths = [];
|
|
const transientBuildPaths = getTransientBuildPaths();
|
|
|
|
try {
|
|
for (const entry of transientBuildPaths) {
|
|
if (!(await exists(entry.sourcePath))) continue;
|
|
await movePath(entry.sourcePath, entry.backupPath);
|
|
movedPaths.push(entry);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
try {
|
|
await pruneStandaloneArtifacts(projectRoot);
|
|
} catch (pruneErr) {
|
|
console.warn(
|
|
"[build-next-isolated] Non-fatal error pruning standalone artifacts:",
|
|
pruneErr
|
|
);
|
|
}
|
|
}
|
|
process.exitCode = result.code;
|
|
} catch (error) {
|
|
console.error("[build-next-isolated] Build failed:", error);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
while (movedPaths.length > 0) {
|
|
const entry = movedPaths.pop();
|
|
if (!entry) continue;
|
|
try {
|
|
await movePath(entry.backupPath, entry.sourcePath);
|
|
} catch (restoreError) {
|
|
console.error(
|
|
`[build-next-isolated] Failed to restore ${entry.label} from ${entry.backupPath}:`,
|
|
restoreError
|
|
);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await fs.rm(backupRoot, { recursive: true, force: true });
|
|
} catch (cleanupError) {
|
|
console.warn("[build-next-isolated] Failed to clean temporary backup root:", cleanupError);
|
|
}
|
|
}
|
|
}
|
|
|
|
const entryScript = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;
|
|
|
|
if (entryScript === import.meta.url) {
|
|
await main();
|
|
}
|