Files
OmniRoute/scripts/build-next-isolated.mjs
diegosouzapw 8cf78ddf00 feat(auth): enforce dashboard sessions for management routes
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.
2026-04-17 16:45:27 -03:00

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();
}