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)):{}; // 黄豆短剧配置写的是 API_PROXY_TARGET,这里归一成 1047 脚本读取的 UPSTREAM_API_URL。 const apiConfig=values=>({...values,...(!Object.hasOwn(values,'UPSTREAM_API_URL')&&(Object.hasOwn(values,'API_BASE_URL')||Object.hasOwn(values,'API_PROXY_TARGET'))?{UPSTREAM_API_URL:values.API_BASE_URL||values.API_PROXY_TARGET}:{})}); /** 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)), ...(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、API_PROXY_TARGET)必须为 current 或有效的 HTTP(S) 接口地址`);} const port=String(env.PORT||'26124'); 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;} }