import { spawn } from 'node:child_process'; import { existsSync, mkdirSync, readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const runtimeDir = join(rootDir, 'runtime', 'nianxx-play'); const serverPath = join(runtimeDir, 'server.js'); function parseEnvValue(raw) { const value = raw.trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { try { return JSON.parse(value); } catch { return value.slice(1, -1); } } return value; } function parseEnvFile(filePath) { if (!existsSync(filePath)) return {}; const env = {}; const raw = readFileSync(filePath, 'utf8'); for (const line of raw.split(/\r?\n/)) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/); if (!match) continue; env[match[1]] = parseEnvValue(match[2]); } return env; } function loadEnv() { const envFiles = [ join(rootDir, '.env'), join(rootDir, '.env.local'), ]; if (process.env.NIANXXPLAY_LOAD_BUNDLED_ENV === '1') { envFiles.push(join(runtimeDir, '.env.runtime')); } const fileEnv = {}; for (const file of envFiles) { Object.assign(fileEnv, parseEnvFile(file)); } return { ...fileEnv, ...process.env }; } if (!existsSync(serverPath)) { console.error(`Zhinian Creation Assistant runtime entry not found: ${serverPath}`); process.exit(1); } const baseEnv = loadEnv(); const port = baseEnv.PORT || baseEnv.NIANXX_PLAY_PORT || '3000'; const hostname = baseEnv.HOSTNAME || '127.0.0.1'; const runtimeRoot = baseEnv.NIANXXPLAY_RUNTIME_DIR || join(rootDir, '.runtime'); const dataDir = baseEnv.NIANXXPLAY_DATA_DIR || join(runtimeRoot, 'data'); const uploadDir = baseEnv.NIANXXPLAY_UPLOAD_DIR || join(runtimeRoot, 'uploads'); const resultDir = baseEnv.NIANXXPLAY_RESULT_DIR || join(runtimeRoot, 'generated-results'); mkdirSync(dataDir, { recursive: true }); mkdirSync(uploadDir, { recursive: true }); mkdirSync(resultDir, { recursive: true }); const env = { ...baseEnv, PORT: String(port), HOSTNAME: hostname, NODE_ENV: 'production', NEXT_TELEMETRY_DISABLED: '1', NIANXXPLAY_RUNTIME_DIR: runtimeRoot, NIANXXPLAY_DATA_DIR: dataDir, NIANXXPLAY_UPLOAD_DIR: uploadDir, NIANXXPLAY_RESULT_DIR: resultDir, NIANXXPLAY_PUBLIC_BASE_URL: baseEnv.NIANXXPLAY_PUBLIC_BASE_URL || `http://${hostname}:${port}`, NIANXXPLAY_DESKTOP_MANAGED: '1', }; console.log(`[Zhinian Creation Assistant] Starting on http://${hostname}:${port}`); console.log(`[Zhinian Creation Assistant] Runtime data: ${runtimeRoot}`); const child = spawn(process.execPath, [serverPath], { cwd: runtimeDir, env, stdio: 'inherit', }); function stop(signal) { if (!child.killed) child.kill(signal); } process.on('SIGINT', () => stop('SIGINT')); process.on('SIGTERM', () => stop('SIGTERM')); child.on('exit', (code, signal) => { if (signal) { process.exit(0); } process.exit(code ?? 0); });