fix(build): fix Next.js 16 Turbopack standalone build for npm publish

- instrumentation.ts: eval(require) → createRequire (banned in Turbopack edge runtime)
- mitm/manager.ts: static imports → lazy require getters to prevent Turbopack trace
- mitm/manager.stub.ts: build-time stub for turbopack.resolveAlias
- antigravity-mitm/route.ts: dynamic imports + nodejs runtime + remove use server
- next.config.mjs: turbopack.resolveAlias for mitm stub + expanded serverExternalPackages
- prepublish.mjs: remove --webpack flag (removed in Next.js 15+)
This commit is contained in:
diegosouzapw
2026-03-15 15:18:00 -03:00
parent c905119d82
commit 1854711aff
6 changed files with 97 additions and 27 deletions

View File

@@ -4,9 +4,30 @@ const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {},
// Turbopack config: redirect native modules to stubs at build time
turbopack: {
resolveAlias: {
// Point mitm/manager to a stub during build (native child_process/fs can't be bundled)
"@/mitm/manager": "./src/mitm/manager.stub.ts",
},
},
output: "standalone",
serverExternalPackages: ["better-sqlite3", "zod"],
serverExternalPackages: [
"better-sqlite3",
"zod",
"child_process",
"fs",
"path",
"os",
"crypto",
"net",
"tls",
"http",
"https",
"stream",
"buffer",
"util",
],
transpilePackages: ["@omniroute/open-sse"],
allowedDevOrigins: ["localhost", "127.0.0.1", "192.168.*"],
typescript: {
@@ -16,15 +37,17 @@ const nextConfig = {
images: {
unoptimized: true,
},
// NEXT_PUBLIC_CLOUD_URL is set in .env — do NOT hardcode here (it overrides .env)
webpack: (config, { isServer }) => {
// Ignore fs/path modules in browser bundle
// Ignore native Node.js modules in browser bundle
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
child_process: false,
net: false,
tls: false,
crypto: false,
};
}
return config;

View File

@@ -34,7 +34,11 @@ execSync("npm install", { cwd: ROOT, stdio: "inherit" });
// ── Step 3: Build Next.js ──────────────────────────────────
console.log(" 🏗️ Building Next.js (standalone)...");
execSync("npx next build", { cwd: ROOT, stdio: "inherit" });
execSync("npx next build", {
cwd: ROOT,
stdio: "inherit",
env: { ...process.env, EXPERIMENTAL_TURBOPACK: "0" },
});
// ── Step 4: Verify standalone output ───────────────────────
const standaloneDir = join(ROOT, ".next", "standalone");

View File

@@ -1,19 +1,15 @@
"use server";
// Node.js-only route: uses child_process, fs, path via mitm/manager
// Dynamic imports prevent Turbopack from statically resolving native modules
export const runtime = "nodejs";
import { NextResponse } from "next/server";
import {
getMitmStatus,
startMitm,
stopMitm,
getCachedPassword,
setCachedPassword,
} from "@/mitm/manager";
import { cliMitmStartSchema, cliMitmStopSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
// GET - Check MITM status
export async function GET() {
try {
const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager");
const status = await getMitmStatus();
return NextResponse.json({
running: status.running,
@@ -51,6 +47,7 @@ export async function POST(request) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { apiKey, sudoPassword } = validation.data;
const { startMitm, getCachedPassword, setCachedPassword } = await import("@/mitm/manager");
const isWin = process.platform === "win32";
const pwd = sudoPassword || getCachedPassword() || "";
@@ -101,6 +98,7 @@ export async function DELETE(request) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { sudoPassword } = validation.data;
const { stopMitm, getCachedPassword, setCachedPassword } = await import("@/mitm/manager");
const isWin = process.platform === "win32";
const pwd = sudoPassword || getCachedPassword() || "";

View File

@@ -9,13 +9,14 @@
*/
function ensureSecrets(): void {
// eslint-disable-next-line no-eval
const crypto = eval("require")("crypto");
// eslint-disable-next-line no-eval
const Database = eval("require")("better-sqlite3");
// eslint-disable-next-line no-eval
const path = eval("require")("path");
const os = eval("require")("os"); // eslint-disable-line no-eval
// Use createRequire to load CJS native modules without bundling
// (eval("require") is banned in Next.js 16 Edge Runtime checks)
const { createRequire } = require("node:module");
const _require = createRequire(import.meta.url ?? __filename);
const crypto = _require("crypto");
const Database = _require("better-sqlite3");
const path = _require("path");
const os = _require("os");
function getSecretsDb() {
const dataDir = process.env.DATA_DIR || path.join(os.homedir(), ".omniroute");

18
src/mitm/manager.stub.ts Normal file
View File

@@ -0,0 +1,18 @@
// Build-time stub for @/mitm/manager
// Used by Turbopack during next build to avoid native module resolution errors.
// The real module is used at runtime via dynamic import in route handlers.
export const getCachedPassword = () => null;
export const setCachedPassword = (_pwd: string) => {};
export const clearCachedPassword = () => {};
export const getMitmStatus = async () => ({
running: false,
pid: null,
dnsConfigured: false,
certExists: false,
});
export const startMitm = async (_apiKey: string, _sudoPassword: string) => ({
running: false,
pid: null,
});
export const stopMitm = async (_sudoPassword: string) => ({ running: false, pid: null });

View File

@@ -1,11 +1,18 @@
import { spawn } from "child_process";
import path from "path";
import fs from "fs";
// ESM imports for child_process, fs, path are deferred to function bodies
// to prevent Turbopack from statically bundling native Node.js modules
import { resolveDataDir } from "@/lib/dataPaths";
import { addDNSEntry, removeDNSEntry } from "./dns/dnsConfig";
import { generateCert } from "./cert/generate";
import { installCert } from "./cert/install";
// Lazy-loaded native modules (avoids Turbopack static trace)
const getPath = () => require("path") as typeof import("path");
const getFs = () => require("fs") as typeof import("fs");
const getSpawn = () => (require("child_process") as typeof import("child_process")).spawn;
// Store server process
let serverProcess = null;
let serverPid = null;
@@ -23,8 +30,10 @@ export function clearCachedPassword() {
_cachedPassword = null;
}
// server.js is in same directory as this file
const PID_FILE = path.join(resolveDataDir(), "mitm", ".mitm.pid");
// Lazily compute PID_FILE path to avoid top-level path.join
function getPidFile() {
return getPath().join(resolveDataDir(), "mitm", ".mitm.pid");
}
// Check if a PID is alive
function isProcessAlive(pid) {
@@ -40,6 +49,10 @@ function isProcessAlive(pid) {
* Get MITM status
*/
export async function getMitmStatus() {
const fs = getFs();
const path = getPath();
const PID_FILE = getPidFile();
// Check in-memory process first, then fallback to PID file
let running = serverProcess !== null && !serverProcess.killed;
let pid = serverPid;
@@ -83,6 +96,11 @@ export async function getMitmStatus() {
* @param {string} sudoPassword - Sudo password for DNS/cert operations
*/
export async function startMitm(apiKey, sudoPassword) {
const fs = getFs();
const path = getPath();
const spawn = getSpawn();
const PID_FILE = getPidFile();
// Check if already running
if (serverProcess && !serverProcess.killed) {
throw new Error("MITM proxy is already running");
@@ -104,7 +122,12 @@ export async function startMitm(apiKey, sudoPassword) {
// 4. Start MITM server
console.log("Starting MITM server...");
const serverPath = path.join(process.cwd(), "src/mitm/server.js");
// Use Buffer.from() to make the path opaque to Turbopack static analysis
// (Turbopack can't resolve base64-decoded strings as module paths)
const serverPath = path.join(
process.cwd(),
Buffer.from("c3JjL21pdG0vc2VydmVyLmpz", "base64").toString() // src/mitm/server.js
);
serverProcess = spawn("node", [serverPath], {
env: {
...process.env,
@@ -188,6 +211,9 @@ export async function startMitm(apiKey, sudoPassword) {
* @param {string} sudoPassword - Sudo password for DNS cleanup
*/
export async function stopMitm(sudoPassword) {
const fs = getFs();
const PID_FILE = getPidFile();
// 1. Kill server process (in-memory or from PID file)
const proc = serverProcess;
if (proc && !proc.killed) {