diff --git a/docs/development.md b/docs/development.md index 1794702..f7fe43a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -6,9 +6,13 @@ # 安装依赖 npm install -# 开发模式(Web) +# 开发模式(同时启动 CLI 内容服务器 :3000 与 Web 前端 :5173) npm run dev +# 也可分别启动 +npm run dev:cli +npm run dev:web + # 构建(Web) npm run build @@ -244,19 +248,46 @@ npm test -- --watch ## 调试 +开发环境由两个进程组成,必须使用不同端口: + +- Web 前端:rsbuild 开发服务器,端口 **5173** +- CLI 内容服务器:`tttk serve`,端口 **3000**(内容 API + Journal) + +rsbuild 会把 `/__CONTENT_INDEX.json`、`/__COMPLETIONS.json`、`/content`、`/.ttrpg` 代理到 CLI 的 +3000 端口,因此必须先启动 CLI,否则前端拿不到内容。 + ### Web 开发 +`npm run dev` 会同时启动两个进程;也可以分开运行: + ```bash +# 同时启动 CLI(3000)与 Web(5173) npm run dev -# 访问 http://localhost:3000 + +# 或分别启动 +npm run dev:cli # CLI 内容服务器,占用 3000 +npm run dev:web # Web 前端,占用 5173 +# 访问 http://localhost:5173 ``` ### CLI 调试 -```bash -# 使用 ts-node 直接运行 -npm run ttrpg serve ./content +CLI 通过 ts-node 运行,加上 `--inspect` 与 `--enable-source-maps` 后即可在编辑器中附加断点。 +如需调试启动/扫描逻辑,用 `--inspect-brk` 在用户代码执行前暂停: +```bash +node --inspect-brk --enable-source-maps --loader ts-node/esm ./src/cli/index.ts serve ./content + +# 或使用脚本(不带 --inspect-brk) +npm run tttk:inspect -- serve ./content +``` + +然后在 Zed / VS Code 中附加到 `localhost:9229`,或打开 `chrome://inspect`。 + +CLI 只监听内容目录(`.md`/`.csv`/`.yarn`/`.svg`)并重建内存中的注册表,**不会**热重载自身源码; +修改 `src/cli/**` 后需要重启进程。 + +```bash # 或构建后运行 npm run cli:build node dist/cli/index.js serve ./content diff --git a/package.json b/package.json index 029bcfd..f105dc6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "node": ">=18" }, "scripts": { - "dev": "rsbuild dev", + "dev": "node scripts/dev.mjs", + "dev:web": "rsbuild dev", + "dev:cli": "node --enable-source-maps --loader ts-node/esm ./src/cli/index.ts serve ./content", "build": "npm run build:web && npm run build:cli", "build:web": "rsbuild build", "build:cli": "tsc -p tsconfig.cli.json", @@ -29,6 +31,7 @@ "cli:build": "npm run build:cli", "ttrpg": "node --loader ts-node/esm ./src/cli/index.ts", "tttk": "node --loader ts-node/esm ./src/cli/index.ts", + "tttk:inspect": "node --inspect --enable-source-maps --loader ts-node/esm ./src/cli/index.ts", "gen:reference": "node --loader ts-node/esm ./scripts/generate-reference.ts", "gen:skill-reference": "node --loader ts-node/esm ./scripts/generate-reference.ts --out .agents/skills/tttk-authoring/reference.md", "test": "jest", diff --git a/rsbuild.config.ts b/rsbuild.config.ts index c9e5918..53dfcbe 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -48,6 +48,9 @@ export default defineConfig({ }, }, server: { + // Dev web server runs on its own port; the CLI content server keeps + // port 3000 (see the proxy targets below). + port: 5173, proxy: { // Proxy content API requests to the CLI dev server (npm run tttk) "/__CONTENT_INDEX.json": "http://localhost:3000", diff --git a/scripts/dev.mjs b/scripts/dev.mjs new file mode 100644 index 0000000..df30cff --- /dev/null +++ b/scripts/dev.mjs @@ -0,0 +1,55 @@ +#!/usr/bin/env node +/** + * Dev runner: starts the CLI content server (port 3000) and the rsbuild web + * dev server (port 5173) together, then tears both down when either exits. + * + * npm run dev # both (this script) + * npm run dev:cli # CLI content server only + * npm run dev:web # web dev server only + */ +import { spawn } from "node:child_process"; + +const isWindows = process.platform === "win32"; +const children = new Set(); +let shuttingDown = false; + +/** Terminate a child and everything it spawned. */ +function killTree(child) { + if (child.exitCode !== null || child.signalCode !== null) return; + if (isWindows) { + spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { + stdio: "ignore", + }); + } else { + try { + // Negative pid targets the process group created via `detached`. + process.kill(-child.pid, "SIGTERM"); + } catch { + child.kill("SIGTERM"); + } + } +} + +function shutdown(code) { + if (shuttingDown) return; + shuttingDown = true; + for (const child of children) killTree(child); + process.exitCode = code ?? 0; +} + +for (const script of ["dev:cli", "dev:web"]) { + const child = spawn("npm", ["run", script], { + stdio: "inherit", + shell: true, + detached: !isWindows, + }); + children.add(child); + child.on("exit", (code) => shutdown(code ?? 0)); + child.on("error", (error) => { + console.error(`[dev] Failed to start ${script}: ${error.message}`); + shutdown(1); + }); +} + +process.on("SIGINT", () => shutdown(0)); +process.on("SIGTERM", () => shutdown(0));