Files
memind/scripts/start-plaza-prod.mjs
T

171 lines
5.1 KiB
JavaScript
Raw 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.
#!/usr/bin/env node
/**
* Plaza 本机正式模式:Portal API + Next.js build/start(供 Cloudflare Tunnel 回源)。
*
* Usage:
* pnpm start:plaza
* pnpm start:plaza -- --skip-build # 跳过 next build(已有 .next 产物时)
*/
import { spawn, execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const defaultPlazaDir = path.join(root, '../memind_plaza');
const plazaDir = process.env.PLAZA_APP_DIR ?? defaultPlazaDir;
const portalPort = Number(process.env.H5_PORT ?? 8081);
const plazaPort = Number(process.env.PLAZA_PORT ?? 3001);
const plazaHost = process.env.PLAZA_LOCAL_HOST ?? 'plaza.tkmind.cn';
const plazaPublicBase = (
process.env.PLAZA_PUBLIC_BASE ?? 'https://plaza.tkmind.cn'
).replace(/\/$/, '');
const portalUrl = `http://127.0.0.1:${portalPort}`;
const skipBuild = process.argv.includes('--skip-build');
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) return;
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 0) continue;
const key = trimmed.slice(0, eq).trim();
const value = trimmed.slice(eq + 1).trim();
if (!process.env[key]) process.env[key] = value;
}
}
loadEnvFile(path.join(root, '../../.env.local'));
loadEnvFile(path.join(root, '.env'));
function freePort(port) {
try {
execSync(`lsof -ti TCP:${port} -sTCP:LISTEN | xargs kill -9`, { stdio: 'ignore' });
} catch {
// already free
}
}
async function portOpen(port) {
try {
const res = await fetch(`http://127.0.0.1:${port}/auth/status`);
return res.ok;
} catch {
return false;
}
}
async function waitFor(check, label, retries = 120) {
for (let i = 0; i < retries; i += 1) {
try {
if (await check()) return;
} catch {
// still starting
}
await new Promise((resolve) => setTimeout(resolve, 500));
}
throw new Error(`${label} 启动超时`);
}
let portal;
let plaza;
let stopping = false;
function spawnChild(command, args, label, cwd = root, extraEnv = {}) {
const child = spawn(command, args, {
cwd,
env: { ...process.env, NODE_ENV: 'production', ...extraEnv },
stdio: 'inherit',
});
child.on('exit', (code, signal) => {
if (signal || stopping) return;
if (code && code !== 0) {
console.error(`[${label}] exited with code ${code}`);
shutdown(code ?? 1);
}
});
return child;
}
function shutdown(code = 0) {
if (stopping) return;
stopping = true;
portal?.kill('SIGTERM');
plaza?.kill('SIGTERM');
setTimeout(() => process.exit(code), 300);
}
process.on('SIGINT', () => shutdown(0));
process.on('SIGTERM', () => shutdown(0));
if (!fs.existsSync(path.join(plazaDir, 'package.json'))) {
console.error(`未找到 Plaza Next.js${plazaDir}。请设置 PLAZA_APP_DIR 指向本地 Plaza 源码,例如 ../memind_plaza`);
process.exit(1);
}
const mindSpacePublicBase = (
process.env.H5_PUBLIC_BASE_URL ??
process.env.VITE_MINDSPACE_BASE ??
'https://m.tkmind.cn'
).replace(/\/$/, '');
const plazaEnv = {
PLAZA_API_PROXY: portalUrl,
PLAZA_API_BASE: portalUrl,
PLAZA_PUBLIC_BASE: plazaPublicBase,
NEXT_PUBLIC_API_BASE: plazaPublicBase,
NEXT_PUBLIC_SITE_BASE: plazaPublicBase,
NEXT_PUBLIC_MINDSPACE_BASE: mindSpacePublicBase,
NEXT_PUBLIC_PLAZA_BASE: '/plaza',
};
console.log('==> Plaza 本机正式模式(next build + start');
console.log(` 公开地址 ${plazaPublicBase}/plaza`);
console.log(` 内部端口 ${plazaPort}`);
freePort(plazaPort);
try {
if (!(await portOpen(portalPort))) {
console.log(`==> 启动 Portal @ ${portalUrl}`);
portal = spawnChild('node', ['server.mjs'], 'portal');
await waitFor(() => portOpen(portalPort), 'Portal');
} else {
console.log(`==> Portal 已在运行: ${portalUrl}`);
}
if (!skipBuild) {
console.log('==> 构建 Plaza Next.js(生产)…');
execSync('npm run build', { cwd: plazaDir, stdio: 'inherit', env: { ...process.env, ...plazaEnv } });
} else {
console.log('==> 跳过 next build--skip-build');
}
console.log(`==> 启动 Plaza Next.js 正式服务 (0.0.0.0:${plazaPort})`);
plaza = spawnChild(
'npm',
['run', 'start', '--', '-H', '0.0.0.0', '-p', String(plazaPort)],
'plaza',
plazaDir,
plazaEnv,
);
await waitFor(async () => {
const res = await fetch(`http://127.0.0.1:${plazaPort}/plaza`, {
headers: { Host: plazaHost },
});
return res.ok || res.status === 307 || res.status === 308;
}, 'Plaza Next.js');
console.log('');
console.log('Plaza 正式模式已就绪:');
console.log(` 本机 http://127.0.0.1:${plazaPort}/plaza`);
console.log(` 公网 ${plazaPublicBase}/plaza (需 Cloudflare Tunnel 回源 :${plazaPort}`);
console.log('');
console.log('隧道未配置或外网 502 时:pnpm setup:plaza-tunnel');
} catch (err) {
console.error(err instanceof Error ? err.message : err);
shutdown(1);
}