refactor(cli): naku 与 pack:release 合并为 pack CLI——release/build 子命令,--yes 非交互

This commit is contained in:
2026-10-29 15:36:06 +08:00
parent 8d4a5c4f94
commit 29019fc5d5
7 changed files with 26 additions and 55 deletions
+1 -2
View File
@@ -35,8 +35,7 @@ Bun 工具链(选型见 `docs/design/技术选型.md`)。**Windows 上 `bun`
| `bun run build:web` | 前端类型检查 + 产物构建 | | `bun run build:web` | 前端类型检查 + 产物构建 |
| `bun run build:server` | 编译后端单文件 → `bin/naku-server`(当前平台) | | `bun run build:server` | 编译后端单文件 → `bin/naku-server`(当前平台) |
| `bun run build:engine` | 编译同步引擎单文件 → `bin/naku-engine`(当前平台) | | `bun run build:engine` | 编译同步引擎单文件 → `bin/naku-engine`(当前平台) |
| `bun run naku` | 打包/构建 CLI(citty + clack):`naku pack` 交互打部署包 → `release/`;`naku build` 本地构建 → `bin/`;支持 `--yes` / `--target` / `--only` | | `bun run pack` | 打包/构建 CLI(citty + clack):`pack release` 交互打部署包 → `release/`;`pack build` 本地构建 → `bin/`;支持 `--yes` / `--target` / `--only`;`pack release --yes` 非交互打部署包(目标默认 linux-x64) |
| `bun run pack:release` | 非交互打部署包(= `naku pack --yes`,目标默认 linux-x64) |
环境变量见 `.env.example`(复制为 `.env` 后填写)。 环境变量见 `.env.example`(复制为 `.env` 后填写)。
+3 -3
View File
@@ -88,13 +88,13 @@ bun run dev:web # 前端 Vite(5175)
### 本地打包 ### 本地打包
```bash ```bash
bun run naku pack bun run pack release
# 交互选:目标平台 · 打包内容 · 是否 tar.gz # 交互选:目标平台 · 打包内容 · 是否 tar.gz
# 产出 release/:dist · naku-server · naku-engine · naku-migrate · drizzle/ # 产出 release/:dist · naku-server · naku-engine · naku-migrate · drizzle/
# 非交互(CI / 一键): # 非交互(CI / 一键):
bun run pack:release bun run pack release --yes
# 或 bun run naku pack --yes --target=bun-linux-arm64 --only=dist,naku-server # 或 bun run pack release --yes --target=bun-linux-arm64 --only=dist,naku-server
``` ```
### 服务器 ### 服务器
+1 -1
View File
@@ -54,7 +54,7 @@
├─ packages/ ├─ packages/
│ ├─ core/ # 业务核心:轮询与 request_id 去重 · 折算链 · 加密体系 · API 语义与 DTO 类型 │ ├─ core/ # 业务核心:轮询与 request_id 去重 · 折算链 · 加密体系 · API 语义与 DTO 类型
│ └─ db/ # 数据访问层:Drizzle schema(pg 方言)+ 仓储 + 迁移 CLI │ └─ db/ # 数据访问层:Drizzle schema(pg 方言)+ 仓储 + 迁移 CLI
├─ scripts/ # 打包/构建 CLI(naku pack / build) ├─ scripts/ # 打包/构建 CLI(pack release / build)
``` ```
**依赖规则(单向)**:`apps/*` → `packages/db` → `packages/core`;core 导出的 DTO 类型可被 web 以纯类型依赖引用(前后端共用一份契约类型)。 **依赖规则(单向)**:`apps/*` → `packages/db` → `packages/core`;core 导出的 DTO 类型可被 web 以纯类型依赖引用(前后端共用一份契约类型)。
+12 -12
View File
@@ -28,7 +28,7 @@
```text ```text
本地(打包) 服务器(运营方) 本地(打包) 服务器(运营方)
────────── ──────────────── ────────── ────────────────
bun run pack:release /opt/naku/ ← 约定目录 bun run pack release --yes /opt/naku/ ← 约定目录
├─ 前端 dist/ ├─ docker-compose.yml ├─ 前端 dist/ ├─ docker-compose.yml
├─ naku-server(linux 单文件) ├─ Dockerfile ├─ naku-server(linux 单文件) ├─ Dockerfile
├─ naku-engine(linux 单文件) ├─ .dockerignore ├─ naku-engine(linux 单文件) ├─ .dockerignore
@@ -53,7 +53,7 @@ bun run pack:release /opt/naku/ ← 约定目录
## 3. 产物包 `release/` ## 3. 产物包 `release/`
本地 `bun run pack:release` 产出,内容固定为: 本地 `bun run pack release --yes` 产出,内容固定为:
```text ```text
release/ release/
@@ -68,13 +68,13 @@ release/
| 项 | 约定 | | 项 | 约定 |
|----|------| |----|------|
| 命令入口 | 优先 `bun run naku pack`(交互:平台 / 内容 / tar.gz);一键等价 `bun run pack:release` 或 `naku pack --yes` | | 命令入口 | 优先 `bun run pack release`(交互:平台 / 内容 / tar.gz);一键等价 `bun run pack release --yes` |
| 脚本落点 | `scripts/cli.ts`(citty + clack)· `scripts/pack-core.ts`(纯打包逻辑)· `scripts/pack-release.ts`(非交互兼容入口) | | 脚本落点 | `scripts/cli.ts`(citty + clack)· `scripts/pack-core.ts`(纯打包逻辑) |
| 目标平台 | 默认 `bun-linux-x64`;`--target=` 可切 `bun-linux-arm64` / `bun-darwin-*` / `bun-windows-*`(含 arm64) | | 目标平台 | 默认 `bun-linux-x64`;`--target=` 可切 `bun-linux-arm64` / `bun-darwin-*` / `bun-windows-*`(含 arm64) |
| 构建顺序 | 清空输出目录 → `build:web` → 三次 `--compile` → 拷 `drizzle/` →(可选)tar.gz | | 构建顺序 | 清空输出目录 → `build:web` → 三次 `--compile` → 拷 `drizzle/` →(可选)tar.gz |
| 禁止入包 | `.env` · 源码 · `node_modules` · 任何密钥 | | 禁止入包 | `.env` · 源码 · `node_modules` · 任何密钥 |
| 校验 | 打包后检查五条目都在且非空(Windows 本机编译允许 `.exe` 后缀);缺一即失败退出 | | 校验 | 打包后检查五条目都在且非空(Windows 本机编译允许 `.exe` 后缀);缺一即失败退出 |
| 本地试跑 | `naku build` / `build:server` / `build:engine` → `bin/`(当前平台),与部署包 `release/` 分离 | | 本地试跑 | `pack build` / `build:server` / `build:engine` → `bin/`(当前平台),与部署包 `release/` 分离 |
### 3.2 为何是单文件而不是「服务器上构建」或「上传源码」 ### 3.2 为何是单文件而不是「服务器上构建」或「上传源码」
@@ -211,7 +211,7 @@ loadEnv → createDb(DATABASE_URL) → runMigrations(db, MIGRATIONS_FOLDER) →
```bash ```bash
# 本地 # 本地
bun run pack:release bun run pack release --yes
# 服务器 # 服务器
# 将 docker-compose.yml · Dockerfile · .dockerignore · Caddyfile · entrypoint-*.sh 放入 /opt/naku/ # 将 docker-compose.yml · Dockerfile · .dockerignore · Caddyfile · entrypoint-*.sh 放入 /opt/naku/
@@ -228,7 +228,7 @@ docker compose ps
```bash ```bash
# 本地 # 本地
bun run pack:release bun run pack release --yes
# 上传,整目录覆盖 /opt/naku/release/ # 上传,整目录覆盖 /opt/naku/release/
# 服务器(固定命令) # 服务器(固定命令)
@@ -299,13 +299,13 @@ docker compose ps
| 文件 | 动作 | | 文件 | 动作 |
|------|------| |------|------|
| `scripts/cli.ts` · `scripts/pack-core.ts` · `scripts/pack-release.ts` | 打包/构建 CLI 与核心逻辑 | | `scripts/cli.ts` · `scripts/pack-core.ts` | 打包/构建 CLI 与核心逻辑 |
| `packages/db/src/migrate-cli.ts` | 迁移 CLI 入口 | | `packages/db/src/migrate-cli.ts` | 迁移 CLI 入口 |
| `package.json` | `naku` · `pack:release` · `build:server` · `build:engine` | | `package.json` | `pack` · `build:server` · `build:engine` |
| `Dockerfile` · `docker-compose.yml` · `.dockerignore` | 两阶段镜像 · 五服务(含 `container_name`) | | `Dockerfile` · `docker-compose.yml` · `.dockerignore` | 两阶段镜像 · 五服务(含 `container_name`) |
| `Caddyfile` · `entrypoint-pg.sh` · `entrypoint-app.sh` | 与 compose 同级平铺 | | `Caddyfile` · `entrypoint-pg.sh` · `entrypoint-app.sh` | 与 compose 同级平铺 |
| `README.md` | 「部署」章与文档地图 | | `README.md` | 「部署」章与文档地图 |
| `AGENTS.md` | 命令表(pack / build / naku) | | `AGENTS.md` | 命令表(pack / build) |
| `.env.example` | 部署语义注释 | | `.env.example` | 部署语义注释 |
--- ---
@@ -315,13 +315,13 @@ docker compose ps
1. **`migrate` 独立单文件**:多一个产物,换来「先迁后跑、失败不启」的清晰门闩。若嫌多,可改为 server 启动时迁(仍幂等),但失去 migrate 失败阻断 engine 的不对称控制。 1. **`migrate` 独立单文件**:多一个产物,换来「先迁后跑、失败不启」的清晰门闩。若嫌多,可改为 server 启动时迁(仍幂等),但失去 migrate 失败阻断 engine 的不对称控制。
2. **目标平台写死 linux-x64**:匹配常见服务器;ARM 用打包参数切换,不为此引入交叉编译矩阵。 2. **目标平台写死 linux-x64**:匹配常见服务器;ARM 用打包参数切换,不为此引入交叉编译矩阵。
3. **`DATABASE_URL` 单源 + 两个 entrypoint**:少一处密码不一致;多两个常驻小 shell。若偏好 compose 原生 `POSTGRES_*`,需接受密码在 `.env` 写两处(或 `DATABASE_URL` 仅应用用)。 3. **`DATABASE_URL` 单源 + 两个 entrypoint**:少一处密码不一致;多两个常驻小 shell。若偏好 compose 原生 `POSTGRES_*`,需接受密码在 `.env` 写两处(或 `DATABASE_URL` 仅应用用)。
4. **产物包而非源码包**:服务器零构建;代价是发版必须在本地(或 CI)跑 `pack:release`。 4. **产物包而非源码包**:服务器零构建;代价是发版必须在本地(或 CI)跑 `pack release --yes`。
--- ---
## 12. 验收清单(实现后) ## 12. 验收清单(实现后)
- [ ] `bun run naku pack --yes`(或 `pack:release`)在 Windows 产出完整 `release/`(五条目齐全) - [ ] `bun run pack release --yes` 在 Windows 产出完整 `release/`(五条目齐全)
- [ ] 首次安装按 §6.1 一次命令链可起,`docker compose ps` 形态符合 §6.4 - [ ] 首次安装按 §6.1 一次命令链可起,`docker compose ps` 形态符合 §6.4
- [ ] 仅替换 `release/` 后重跑固定命令,版本切换成功且数据保留(卷不重建) - [ ] 仅替换 `release/` 后重跑固定命令,版本切换成功且数据保留(卷不重建)
- [ ] 连续两次固定命令:第二次 `migrate` 不重复应用已存在迁移(日志可证) - [ ] 连续两次固定命令:第二次 `migrate` 不重复应用已存在迁移(日志可证)
+1 -2
View File
@@ -18,8 +18,7 @@
"build:web": "bun run --cwd apps/web build", "build:web": "bun run --cwd apps/web build",
"build:server": "bun build --compile --outfile=bin/naku-server apps/server/src/index.ts", "build:server": "bun build --compile --outfile=bin/naku-server apps/server/src/index.ts",
"build:engine": "bun build --compile --outfile=bin/naku-engine apps/engine/src/index.ts", "build:engine": "bun build --compile --outfile=bin/naku-engine apps/engine/src/index.ts",
"pack:release": "bun run scripts/pack-release.ts", "pack": "bun run scripts/cli.ts"
"naku": "bun run scripts/cli.ts"
}, },
"devDependencies": { "devDependencies": {
"@clack/prompts": "^1.8.1", "@clack/prompts": "^1.8.1",
+8 -8
View File
@@ -1,5 +1,5 @@
/** /**
* naku 打包/构建 CLI:citty 定义命令,@clack/prompts 负责交互。 * pack 打包/构建 CLI:citty 定义命令,@clack/prompts 负责交互。
* 有 TTY 且未给全 flag 时进交互;--yes / 无 TTY 则纯 flag。 * 有 TTY 且未给全 flag 时进交互;--yes / 无 TTY 则纯 flag。
*/ */
import { defineCommand, runMain } from "citty"; import { defineCommand, runMain } from "citty";
@@ -34,9 +34,9 @@ const targetOption = {
}, },
} as const; } as const;
const pack = defineCommand({ const release = defineCommand({
meta: { meta: {
name: "pack", name: "release",
description: "打部署产物包(dist + 单文件 + drizzle)", description: "打部署产物包(dist + 单文件 + drizzle)",
}, },
args: { args: {
@@ -62,7 +62,7 @@ const pack = defineCommand({
}, },
async run({ args }) { async run({ args }) {
const interactive = isInteractive(args.yes as boolean | undefined); const interactive = isInteractive(args.yes as boolean | undefined);
p.intro("NAKU 打包"); p.intro("PACK 打包");
// --- target --- // --- target ---
let target = args.target as string | undefined; let target = args.target as string | undefined;
@@ -205,7 +205,7 @@ const build = defineCommand({
} }
only = [positional as PackItem]; only = [positional as PackItem];
} else if (interactive) { } else if (interactive) {
p.intro("NAKU 本地构建"); p.intro("PACK 本地构建");
only = assertNotCancel( only = assertNotCancel(
await p.multiselect({ await p.multiselect({
message: "构建什么", message: "构建什么",
@@ -245,11 +245,11 @@ const build = defineCommand({
const main = defineCommand({ const main = defineCommand({
meta: { meta: {
name: "naku", name: "pack",
version: "0.0.0", version: "0.0.0",
description: "NAKU 打包 / 本地构建", description: "PACK 打包 / 本地构建",
}, },
subCommands: { pack, build }, subCommands: { release, build },
}); });
runMain(main); runMain(main);
-27
View File
@@ -1,27 +0,0 @@
/**
* 兼容入口:等价于 `naku pack --yes --target=bun-linux-x64`。
* 交互请用 `bun run naku pack`。
*/
import { packRelease } from "./pack-core.ts";
function readTarget(): string {
const arg = process.argv.find((a) => a.startsWith("--target="));
return arg?.slice("--target=".length) || process.env.PACK_TARGET || "bun-linux-x64";
}
const target = readTarget();
console.log(`[pack-release] non-interactive pack (target=${target})`);
try {
const out = await packRelease({
target,
only: ["dist", "naku-server", "naku-engine", "naku-migrate", "drizzle"],
outDir: "release",
tarball: false,
onLog: (m) => console.log("[pack-release]", m),
});
console.log("[pack-release] done →", out);
} catch (err) {
console.error("[pack-release]", err instanceof Error ? err.message : err);
process.exit(1);
}