chore: split local development services
This commit is contained in:
@@ -64,16 +64,24 @@ docker compose down
|
||||
systemctl --user stop creatorhub-browser-gateway.service
|
||||
```
|
||||
|
||||
## 本地开发(热加载)
|
||||
## 本地开发(分开启动)
|
||||
|
||||
首次安装前端依赖:
|
||||
|
||||
```bash
|
||||
npm --prefix web ci
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- `pnpm dev`:同时运行 Go control-plane 和 Vite HMR;
|
||||
- `pnpm dev:backend`:启动 PostgreSQL 依赖,检查宿主机 native gateway 后运行 control-plane;
|
||||
- `pnpm dev:frontend`:单独运行前端。
|
||||
分别在四个终端运行:
|
||||
|
||||
```bash
|
||||
pnpm env # PostgreSQL 依赖
|
||||
pnpm gateway # native browser gateway
|
||||
pnpm backend # Go control-plane,Air 自动重载
|
||||
pnpm frontend # Vite 前端,支持 HMR
|
||||
```
|
||||
|
||||
`pnpm backend` 不会自动启动数据库或 gateway;先启动 `env` 和 `gateway`。`pnpm gateway` 直接运行当前用户的 native gateway,不要求安装 systemd 服务。修改 `browser_gateway` 的 Python 文件后,重启该命令;新增或删除前端路由后,重跑 `pnpm --dir web run routes:generate`。
|
||||
|
||||
服务地址:
|
||||
|
||||
|
||||
+12
-2
@@ -93,12 +93,22 @@ Compose 中的 control-plane 访问宿主机 gateway 时,网关 Endpoint 登
|
||||
|
||||
### 裸机开发
|
||||
|
||||
首次安装前端依赖:
|
||||
|
||||
```bash
|
||||
npm --prefix web ci
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
`pnpm dev:backend` 只启动 PostgreSQL 依赖,并在启动 control-plane 前检查宿主机 gateway;它不会创建 browser runtime 或替代 gateway。
|
||||
分别在终端启动依赖和服务:
|
||||
|
||||
```bash
|
||||
pnpm env # PostgreSQL
|
||||
pnpm gateway # native browser gateway
|
||||
pnpm backend # Go control-plane,Air 热加载
|
||||
pnpm frontend # Vite,HMR
|
||||
```
|
||||
|
||||
`pnpm backend` 不会隐式启动 PostgreSQL 或 gateway;`pnpm gateway` 直接运行当前用户的 native gateway,不要求 systemd。停止各服务时关闭对应终端即可。
|
||||
|
||||
默认联调地址:
|
||||
|
||||
|
||||
+4
-4
@@ -3,9 +3,9 @@
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev:deps": "docker compose -f compose.yaml -f compose.dev.yaml up -d postgres",
|
||||
"dev:backend": "node scripts/dev-backend.mjs",
|
||||
"dev:frontend": "pnpm -C web run dev",
|
||||
"dev": "node scripts/dev.mjs"
|
||||
"env": "docker compose -f compose.yaml -f compose.dev.yaml up -d postgres",
|
||||
"backend": "node scripts/dev-backend.mjs",
|
||||
"frontend": "pnpm -C web run dev",
|
||||
"gateway": "node scripts/dev-gateway.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-21
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
// dev-backend: 启动 PostgreSQL 依赖,并连接宿主机 native browser gateway 后运行 control-plane。
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
// backend: 运行带 Air 热加载的 Go control-plane。
|
||||
import { spawn } from "node:child_process";
|
||||
import { readFileSync, existsSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
@@ -37,25 +37,6 @@ if (!overrides.LOG_LEVEL) overrides.LOG_LEVEL = "debug";
|
||||
if (!overrides.NATIVE_GATEWAY_ENDPOINT) overrides.NATIVE_GATEWAY_ENDPOINT = "http://127.0.0.1:8081";
|
||||
|
||||
const env = { ...process.env, ...overrides };
|
||||
const compose = spawnSync(
|
||||
"docker",
|
||||
["compose", "-f", "compose.yaml", "-f", "compose.dev.yaml", "up", "-d", "postgres"],
|
||||
{ cwd: root, env, stdio: "inherit" },
|
||||
);
|
||||
if (compose.status !== 0) {
|
||||
console.error("启动 PostgreSQL 容器失败,请确认 Docker 只用于数据库依赖。");
|
||||
process.exit(compose.status ?? 1);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${overrides.NATIVE_GATEWAY_ENDPOINT}/healthz`, { signal: AbortSignal.timeout(2000) });
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
} catch (error) {
|
||||
console.error(`[dev-backend] native browser gateway 不可达:${overrides.NATIVE_GATEWAY_ENDPOINT} (${error})`);
|
||||
console.error("请先以非 root 用户启动 browser_gateway.server.http 或对应的 systemd user service。");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const air = spawn("air", ["-c", ".air.toml", ...process.argv.slice(2)], {
|
||||
cwd: root,
|
||||
env,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
// gateway: 直接运行开发用 native browser gateway;不依赖 systemd。
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
||||
|
||||
function readEnvFile(filePath) {
|
||||
if (!existsSync(filePath)) return {};
|
||||
const values = {};
|
||||
for (const line of readFileSync(filePath, "utf8").split("\n")) {
|
||||
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line.trim());
|
||||
if (match) values[match[1]] = match[2];
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
const rootEnv = readEnvFile(path.join(root, ".env"));
|
||||
const gatewayEnvPath = process.env.CREATORHUB_BROWSER_GATEWAY_ENV
|
||||
?? path.join(os.homedir(), ".config", "creatorhub", "browser-gateway.env");
|
||||
const gatewayEnv = readEnvFile(gatewayEnvPath);
|
||||
const env = { ...process.env, ...rootEnv, ...gatewayEnv };
|
||||
if (!env.LISTEN_ADDR) env.LISTEN_ADDR = "0.0.0.0:8081";
|
||||
|
||||
const gateway = spawn("python3", ["-m", "browser_gateway.server.http"], {
|
||||
cwd: root,
|
||||
env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
for (const signal of ["SIGINT", "SIGTERM"]) {
|
||||
process.on(signal, () => gateway.kill(signal));
|
||||
}
|
||||
|
||||
gateway.on("exit", (code, signal) => {
|
||||
if (signal) process.exit(1);
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// dev: 同时跑 dev:backend(air 热加载 control-plane)与 dev:frontend(vite HMR)。
|
||||
// Ctrl-C 结束两个子进程。
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
||||
const pnpm = process.env.npm_execpath || "pnpm";
|
||||
|
||||
const procs = [
|
||||
spawn(pnpm, ["run", "dev:backend"], { cwd: root, stdio: "inherit" }),
|
||||
spawn(pnpm, ["run", "dev:frontend"], { cwd: root, stdio: "inherit" }),
|
||||
];
|
||||
|
||||
for (const signal of ["SIGINT", "SIGTERM"]) {
|
||||
process.on(signal, () => {
|
||||
for (const p of procs) p.kill(signal);
|
||||
});
|
||||
}
|
||||
|
||||
let remaining = procs.length;
|
||||
for (const p of procs) {
|
||||
p.on("exit", (code) => {
|
||||
if (--remaining === 0) process.exit(code ?? 0);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user