diff --git a/package.json b/package.json index 00a17b1..f57a865 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite --mode dev", + "dev:mock": "node scripts/start-mock.mjs", "typecheck": "vue-tsc --noEmit", "build": "vite build --mode base", "build:base": "vite build --mode base", diff --git a/scripts/start-mock.mjs b/scripts/start-mock.mjs new file mode 100644 index 0000000..577aa45 --- /dev/null +++ b/scripts/start-mock.mjs @@ -0,0 +1,88 @@ +#!/usr/bin/env node +/** + * start-mock.mjs —— 无 VPN 一键离线演示(npm run dev:mock)。 + * + * 做三件事(零第三方依赖,Node 20 自带 fetch): + * 1. 拉起 YschTheme 的本地静态通道 scripts/dev-serve.mjs(9527,/dist/pc 挂 /static/); + * 2. 探活 9527/static/manifest.json(若端口已被上次残留进程占用且可服务,同样放行); + * 3. 以 YH_THEME_PROXY_TARGET=http://localhost:9527 spawn vite --mode mock(8131)。 + * + * 效果:浏览器访问 http://localhost:8131 —— 三页治理台账全量可演示(mock 数据), + * 主题 CSS/切换器经本地 9527 供给,全程不触网、不需要 VPN。 + * 主题静态资源不拷进 front/public(应用侧不持主题副本是主题仓硬原则)。 + * VPN 恢复后照旧 npm run dev 走真后端,两者互不影响。Ctrl+C 退出会一并收掉两个子进程。 + */ +import { spawn } from "node:child_process"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const frontDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const themeDir = path.resolve(frontDir, "..", "..", "YschTheme"); +const themeServeScript = path.join(themeDir, "scripts", "dev-serve.mjs"); +const viteBin = path.join(frontDir, "node_modules", "vite", "bin", "vite.js"); +const THEME_PORT = 9527; + +const fail = (msg) => { + console.error(`[start-mock] ${msg}`); + process.exit(1); +}; + +if (!existsSync(themeServeScript)) { + fail(`找不到主题静态服务脚本 ${themeServeScript}(YschTheme 仓位置异常?)`); +} +if (!existsSync(viteBin)) { + fail(`找不到 ${viteBin}(先在 front 目录 npm install)`); +} + +/* 1. 本地静态通道(YschTheme 零依赖实现,serve dist/pc 于 /static/) */ +const themeServer = spawn(process.execPath, [themeServeScript], { + cwd: themeDir, + stdio: ["ignore", "pipe", "pipe"], +}); +themeServer.stdout.on("data", (d) => process.stdout.write(`[theme] ${d}`)); +themeServer.stderr.on("data", (d) => process.stderr.write(`[theme] ${d}`)); + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +async function waitThemeReady(tries = 24) { + for (let i = 0; i < tries; i++) { + try { + const res = await fetch(`http://localhost:${THEME_PORT}/static/manifest.json`); + if (res.ok) return true; + } catch { + /* 尚未就绪/端口被占(占位进程若可服务也会在上面返回 ok) */ + } + await sleep(250); + } + return false; +} + +/* 2. 探活:主题通道就绪才起前端(演示强依赖主题 CSS) */ +if (!(await waitThemeReady())) { + themeServer.kill(); + fail(`9527 主题静态通道未就绪 —— 检查 ${themeServeScript} 与 YschTheme/dist/pc 是否存在(必要时先在 YschTheme 跑 npm run gen)`); +} +console.log(`[start-mock] 主题静态通道就绪 → http://localhost:${THEME_PORT}/static/`); + +/* 3. vite --mode mock(dev server 8131;/static 代理由 YH_THEME_PROXY_TARGET 指向 9527) */ +const vite = spawn(process.execPath, [viteBin, "--mode", "mock"], { + cwd: frontDir, + stdio: "inherit", + env: { ...process.env, YH_THEME_PROXY_TARGET: `http://localhost:${THEME_PORT}` }, +}); + +const shutdown = () => { + vite.kill(); + themeServer.kill(); +}; +vite.on("exit", (code) => { + themeServer.kill(); + process.exit(code ?? 0); +}); +themeServer.on("exit", () => { + /* 9527 若由残留进程提供则本子进程会因端口占用退出,探活已放行,不必终止演示 */ +}); +for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => { + shutdown(); + process.exit(0); +});