Files
谢宇宁andClaude Opus 5.5 f65d10e998 Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:07:59 +07:00

143 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { parse } from "dotenv";
const projectRoot = fileURLToPath(new URL("../", import.meta.url));
const directories = {
development: ".next-dev",
test: ".next-test",
production: ".next",
};
const commands = new Set(["dev", "build", "start"]);
const readConfig = (file) =>
existsSync(file) ? parse(readFileSync(file)) : {};
// Normalize each layer before merging so a legacy process override still wins over a profile.
const apiConfig = (values) => ({
...values,
...(!Object.hasOwn(values, "UPSTREAM_API_URL") &&
Object.hasOwn(values, "API_BASE_URL")
? { UPSTREAM_API_URL: values.API_BASE_URL }
: {}),
});
/** APP_ENV selects the deployment; NODE_ENV keeps Next's development/production semantics. */
export function nextCommand(
environment = "development",
command = "dev",
args = [],
{ root = projectRoot, variables = process.env } = {},
) {
if (!Object.hasOwn(directories, environment) || !commands.has(command))
throw new Error(
"Usage: node scripts/next-env.mjs development|test|production dev|build|start",
);
const base = resolve(root, "config", environment + ".env");
if (!existsSync(base))
throw new Error(`缺少环境配置:config/${environment}.env`);
const env = {
...apiConfig(readConfig(base)),
// Preserve the old local-development setup without leaking it into test/production.
...(environment === "development"
? apiConfig(readConfig(resolve(root, ".env.local")))
: {}),
...apiConfig(
readConfig(resolve(root, "config", environment + ".local.env")),
),
...apiConfig(
Object.fromEntries(
Object.entries(variables).filter(([, value]) => value !== undefined),
),
),
APP_ENV: environment,
APP_DIST_DIR: directories[environment],
NODE_ENV: command === "dev" ? "development" : "production",
};
try {
const value = (env.UPSTREAM_API_URL || "").trim();
if (value === "current") env.UPSTREAM_API_URL = value;
else {
const api = new URL(value);
if (
!["http:", "https:"].includes(api.protocol) ||
api.username ||
api.password ||
api.search ||
api.hash
)
throw new Error();
env.UPSTREAM_API_URL = api.href.replace(/\/+$/, "");
}
env.API_BASE_URL = env.UPSTREAM_API_URL;
} catch {
throw new Error(
`config/${environment}.env 的 UPSTREAM_API_URL(兼容 API_BASE_URL)必须为 current 或有效的 HTTP(S) 接口地址`,
);
}
const port = String(env.PORT || "21030");
if (!/^\d+$/.test(port) || Number(port) < 1 || Number(port) > 65535)
throw new Error("PORT 必须在 1–65535 之间");
env.PORT = port;
const hasOption = (names) =>
args.some((arg) =>
names.some((name) => arg === name || arg.startsWith(name + "=")),
);
const options =
command === "build"
? []
: [
...(hasOption(["--hostname", "-H"]) ? [] : ["--hostname", "0.0.0.0"]),
...(hasOption(["--port", "-p"]) ? [] : ["--port", port]),
];
return {
executable: process.execPath,
args: [
resolve(root, "node_modules/next/dist/bin/next"),
command,
...options,
...args,
],
cwd: root,
env,
};
}
export async function runNext(environment, command, args, options) {
const spec = nextCommand(environment, command, args, options);
const child = spawn(spec.executable, spec.args, {
cwd: spec.cwd,
env: spec.env,
stdio: "inherit",
});
const interrupt = () => child.kill("SIGINT"),
terminate = () => child.kill("SIGTERM");
process.on("SIGINT", interrupt);
process.on("SIGTERM", terminate);
try {
return await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("exit", (code, signal) =>
resolve(code ?? (signal === "SIGINT" ? 130 : 143)),
);
});
} finally {
process.off("SIGINT", interrupt);
process.off("SIGTERM", terminate);
}
}
if (
process.argv[1] &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
) {
try {
const [environment = "development", command = "dev", ...args] =
process.argv.slice(2);
process.exitCode = await runNext(environment, command, args);
} catch (error) {
console.error(error instanceof Error ? error.message : "启动 Next.js 失败");
process.exitCode = 1;
}
}