Files
memind/scripts/dev-plaza.mjs
T
john 9b4a25799f Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks
response templates by message type and intent (translate, summary,
rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync,
zero I/O, auto-falls back to config.ackText on any error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 15:19:03 +08:00

204 lines
6.2 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
/**
* Standalone Plaza dev: Portal API + Next.js + optional :80 reverse proxy.
*
* Usage:
* pnpm dev:plaza
* sudo pnpm dev:plaza-proxy # separate terminal, maps plaza.tkmind.cn:80 -> :3001
*/
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 = fs.existsSync(path.join(root, '../test-memindplaza/app/plaza/package.json'))
? path.join(root, '../test-memindplaza/app/plaza')
: path.join(root, '../tkmind_go/ui/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 publicPort = Number(process.env.PLAZA_PUBLIC_PORT ?? 443);
const plazaHost = process.env.PLAZA_LOCAL_HOST ?? 'plaza.tkmind.cn';
const plazaPublicScheme = String(process.env.PLAZA_PUBLIC_BASE ?? 'https://plaza.tkmind.cn').startsWith('http://')
? 'http'
: 'https';
const plazaPublicBase = (
process.env.PLAZA_PUBLIC_BASE ??
(publicPort === 80 || publicPort === 443
? `${plazaPublicScheme}://${plazaHost}`
: `${plazaPublicScheme}://${plazaHost}:${publicPort}`)
).replace(/\/$/, '');
const portalUrl = `http://127.0.0.1:${portalPort}`;
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 plazaHostConfigured(host) {
try {
const hosts = fs.readFileSync('/etc/hosts', 'utf8');
return new RegExp(`^\\s*127\\.0\\.0\\.1\\s+${host.replace('.', '\\.')}(\\s|$)`, 'm').test(hosts);
} catch {
return false;
}
}
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 = 80) {
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 dnsServer;
let stopping = false;
function spawnChild(command, args, label, cwd = root, extraEnv = {}) {
const child = spawn(command, args, {
cwd,
env: { ...process.env, ...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');
dnsServer?.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 源码,例如 ../test-memindplaza/app/plaza`);
process.exit(1);
}
function plazaResolverConfigured(host) {
try {
const body = fs.readFileSync(`/etc/resolver/${host}`, 'utf8');
return body.includes('tkmind-plaza-local');
} catch {
return false;
}
}
if (!plazaHostConfigured(plazaHost) || !plazaResolverConfigured(plazaHost)) {
console.warn('');
console.warn(`${plazaHost} 本地 DNS 未完整配置(Tailscale/Clash 会劫持解析)`);
console.warn(' 请执行:sudo pnpm setup:plaza-dns');
console.warn('');
}
const mindSpacePublicBase = (
process.env.H5_PUBLIC_BASE_URL ?? `http://localhost:${portalPort}`
).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 独立开发模式`);
console.log(` 公开地址 ${plazaPublicBase}/plaza`);
console.log(` 内部端口 ${plazaPort}`);
freePort(plazaPort);
freePort(Number(process.env.PLAZA_LOCAL_DNS_PORT ?? 5533));
try {
console.log('==> 启动 Plaza 本地 DNS(覆盖 Tailscale fake-ip');
dnsServer = spawnChild('node', ['scripts/plaza-local-dns-server.mjs'], 'plaza-dns');
if (!(await portOpen(portalPort))) {
console.log(`==> 启动 Portal @ ${portalUrl}`);
portal = spawnChild('node', ['server.mjs'], 'portal');
await waitFor(() => portOpen(portalPort), 'Portal');
} else {
console.log(`==> Portal 已在运行: ${portalUrl}`);
}
console.log(`==> 启动 Plaza Next.js (内部 :${plazaPort})`);
plaza = spawnChild(
'npm',
['run', 'dev', '--', '-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}:${plazaPort}` },
});
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('');
console.log('公开访问(需 80 端口代理):');
console.log(` ${plazaPublicBase}/plaza`);
console.log('');
console.log('若浏览器还打不开 plaza.tkmind.cn,另开终端执行:');
console.log(' sudo pnpm dev:plaza-proxy');
} catch (err) {
console.error(err instanceof Error ? err.message : err);
shutdown(1);
}