Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-11-05 23:07:59 +07:00
co-authored by Claude Opus 5.5
commit f65d10e998
377 changed files with 31301 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
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;
}
}