@coze-arch/cli 0.0.36-alpha.b293fc → 0.0.36-alpha.fd6b86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,17 @@ rush update
19
19
 
20
20
  ## 使用
21
21
 
22
+ ### 部署应用
23
+
24
+ ```bash
25
+ coze-dev deploy auth login
26
+ coze-dev deploy code deploy --source-root . --yes --wait
27
+ ```
28
+
29
+ 完整命令、参数和示例见 [部署命令文档](./docs/deploy.md)。部署命令与其它
30
+ `coze-dev` 能力共享 `~/.coze/cli/config.json` 登录态,也支持通过
31
+ `COZE_API_TOKEN` 提供固定运行时凭证。
32
+
22
33
  ### 初始化项目
23
34
 
24
35
  ```bash
package/docs/deploy.md ADDED
@@ -0,0 +1,171 @@
1
+ # coze-dev deploy
2
+
3
+ `coze-dev` 内置的部署命令,复用主 Coze CLI 保存在
4
+ `~/.coze/cli/config.json` 中的登录状态。
5
+
6
+ ## 安装与运行
7
+
8
+ ```bash
9
+ npm install -g @coze-arch/cli
10
+ coze-dev deploy auth login
11
+ coze-dev deploy auth status
12
+ coze-dev deploy doctor
13
+ ```
14
+
15
+ 默认输出 JSON,可通过 `--format text` 切换为文本输出。
16
+
17
+ 首次部署不需要手动查找 `space_id` 或 `project_id`。当 `.coze [project]` 没有
18
+ `project_id` 时,CLI 会复用已配置的 Space;如果尚未配置,会查询当前账号
19
+ 可用的 Space、自动选择第一个并保存,然后根据 `.coze` 的
20
+ `[project].project_type` 创建当前源码目录专属的普通编程 Project。
21
+ 映射与部署服务端对 `.coze` 的解析保持一致:`agent`→Agent、`workflow`/`automation`→Automation、
22
+ `app`→App、`skill`→Skill、`web`/`backend`→GeneralWeb、`miniapp`/`miniprogram`→WechatMiniProgram;
23
+ 其他值或未声明 `project_type` 时创建通用 General Project。不会复用共享的 CLIBilling Project。
24
+
25
+ > Project 类型不只是元信息:它决定注入运行时的 `COZE_PROJECT_TYPE`、是否为该 Project 开通
26
+ > Postgres、以及流量网关是否强制入站鉴权。类型建错时部署照样成功,但服务可能起不来 —— 例如
27
+ > agent 模板靠 `COZE_PROJECT_TYPE` 选择要加载的图,并在启动时读取 `PGDATABASE_URL`。
28
+ > 类型在创建 Project 时一次性确定,之后再改 `.coze project_type` 不会改变已有 Project 的类型。
29
+
30
+ 指定 PPE 泳道时,增加全局参数 `--lane`。CLI 会为所有部署 API 请求发送
31
+ `x-tt-env` 和 `x-use-ppe: 1`,该参数只对当前命令生效:
32
+
33
+ ```bash
34
+ coze-dev deploy --lane ppe_deploy code app list
35
+ coze-dev deploy --lane ppe_deploy code deploy --source-root .
36
+ ```
37
+
38
+ ## 命令
39
+
40
+ ```text
41
+ coze-dev deploy context show
42
+ coze-dev deploy auth login|status|logout
43
+ coze-dev deploy context use --space-id <space_id>
44
+ coze-dev deploy doctor
45
+ coze-dev deploy code app list [--page-size <n>] [--page-token <token>] [--all]
46
+ coze-dev deploy code app show --app-id <app_id>
47
+ coze-dev deploy code app rename <name> --app-id <app_id> --yes
48
+ coze-dev deploy code app delete --app-id <app_id> --yes
49
+ coze-dev deploy code app analytics|metrics --app-id <app_id> [--time-range <range>]
50
+ coze-dev deploy code app usage --app-id <app_id> --bill-month <YYYY-MM>
51
+ coze-dev deploy [--lane <ppe_lane>] code deploy [--source-root <directory>] [--app-id <app_id>] [--application-type <type>] [--name <name>] [--connector-id <id>]
52
+ coze-dev deploy code deploy status|list|online|rollback-check|cancel|rollback|stop|resume|pause
53
+ coze-dev deploy code deploy logs|log-count|count|build-log|config
54
+ coze-dev deploy code domain list [--unit-type dev|prod]
55
+ coze-dev deploy code domain add <domain>
56
+ coze-dev deploy code domain set-prefix <prefix> --app-id <app_id> --yes
57
+ coze-dev deploy code mini-program status|bind|submit-code|unbind|auth|preview
58
+ ```
59
+
60
+ `code app list` 是真分页:一次只返回一页(`--page-size` 默认 20、上限 100),`has_more` 为真时用返回的 `next_page_token` 取下一页;要一次拿到名下全部应用用 `--all`,它会自己翻到底。
61
+
62
+ 部署确认阶段不传 `--yes`;确认后使用相同参数增加 `--yes`,需要等待终态时再增加
63
+ `--wait`。CLI 会为每次请求自动生成一个 40 位十六进制源码快照 ID,用于兼容 V2 接口的
64
+ `commit_hash` 字段,用户无需指定。该 ID 不代表 Git commit;源码目录无需由 Git 管理,
65
+ 打包时始终读取目录的最新内容。CLI 会把当前源码目录专属的 `project_id` 和唯一 `app_id`
66
+ 写入 `.coze [project]`,ID 使用引号包裹的十进制字符串。后续类型一致时自动复用该
67
+ Application;类型不一致时创建新 Application、覆盖 `app_id` 并提示。也可用 `--project-id`
68
+ 或 `--app-id` 显式指定已有资源,显式指定时不会额外创建 Project。
69
+
70
+ 首次部署的应用名称可通过 `--name` 指定;显式参数优先于 `.coze` 的
71
+ `[project].name`。Pages 部署没有 `.coze` 时,CLI 会先生成最小配置再写入绑定。
72
+
73
+ ```toml
74
+ [project]
75
+ name = "Phaser 游戏"
76
+ project_type = "web"
77
+ project_id = "987654321"
78
+ app_id = "123456789"
79
+ ```
80
+
81
+ Pages 首次部署没有 `app_id`,必须显式指定交付类型。Pages 将
82
+ `--source-root` 视为项目根目录:根目录已有 `index.html` 时直接打包;否则读取 `.coze`,
83
+ 要求 `project.template = "nextjs"` 且 `deploy.build` 为非空命令数组。CLI 不在本地安装
84
+ 依赖或执行 Pages 构建,只上传源码;Deploy 服务在 Sandbox 中注入 Next.js 静态导出配置,
85
+ 执行 `deploy.build`,并要求最终产出 `out/index.html`:
86
+
87
+ ```bash
88
+ coze-dev deploy code deploy --source-root . --application-type pages
89
+ coze-dev deploy code deploy --source-root . --application-type pages --yes --wait
90
+ ```
91
+
92
+ 创建成功后 CLI 会把 Pages Application 写入 `.coze`;后续可继续传
93
+ `--application-type pages` 自动复用,或显式传 `--app-id`。
94
+
95
+ V2 接口的所有参数均通过 POST JSON body 发送,不会放入 URL。确认非 Pages 部署后,CLI
96
+ 会执行 `.coze` 中可选的 `dev.pack` 命令,将
97
+ 本地部署内容打成无根目录的 `tar.gz`,再通过 `source_package_base64` 上传。Pages 部署
98
+ 统一上传源码根目录:已有 `index.html` 时服务端跳过构建,没有时由服务端按 Snapshot 中的
99
+ `.coze deploy.build` 构建。打包由 CLI 自带的 Node.js 脚本执行,并排除 `.coze`、`.env`、
100
+ `.git`、`.venv`、`.codegraph`、`node_modules`、`.next`、`site-packages` 和 `__pycache__`。
101
+ `scripts/package-project.py` 仍随源码和 npm 包保留,仅作为 Python 参考备份与行为对照,
102
+ 生产打包链路不会调用 Python。
103
+
104
+ 环境变量以当前线上成功版本的 Secret 快照为准,CLI 不再读取源码根目录的 `.env`。
105
+ 更新服务或小程序时,CLI 会读取线上快照中的用户变量并重新加密上传;首次部署没有线上
106
+ 快照,因此使用空的用户变量集合。平台系统变量由 Secret 服务管理,不作为用户变量上传。
107
+
108
+ 通过 `app_id` 查看当前线上版本的环境变量及快照状态:
109
+
110
+ ```bash
111
+ coze-dev deploy code env list
112
+ coze-dev deploy code env list --app-id 7672644451805691910
113
+ ```
114
+
115
+ 小程序部署可重复指定渠道:微信为 `10000127`,抖音为 `10000126`:
116
+
117
+ ```bash
118
+ coze-dev deploy code deploy --source-root . --application-type mini_program \
119
+ --connector-id 10000127 --yes --wait
120
+ ```
121
+
122
+ 小程序预览先完成授权,再执行 preview。preview 会打包微信的 dist/ 或抖音的 dist-tt/ 并上传。Project ID 可通过参数传入或从 .coze 自动读取。
123
+
124
+ ```bash
125
+ coze-dev deploy code mini-program auth --platform wechat --mini-app-id wx123 --source-root .
126
+ coze-dev deploy code mini-program preview --platform wechat --mini-app-id wx123 --source-root .
127
+ ```
128
+
129
+ 绑定小程序 AppID 时,默认从源码目录 `.coze [project]` 读取该目录的 `project_id`:
130
+
131
+ ```bash
132
+ coze-dev deploy code mini-program bind \
133
+ --platform wechat \
134
+ --mini-app-id wx123 \
135
+ --redirect-url https://code.coze.cn/coding/auth/callback/miniprogram
136
+ ```
137
+
138
+ 返回 `bind_status=1` 时,打开 `need_authorize_data.auth_url` 完成授权。回调页会自动
139
+ 提交授权码;无回调页的调用方也可使用 `mini-program submit-code` 手动提交。
140
+
141
+ `code deploy status` 和 `list` 会返回 `deploy_detail[].deploy_log_key`。只有一个日志
142
+ key 时,`code deploy build-log --deploy-id <id>` 会自动解析;存在多个 key 时按错误
143
+ 详情中的 `available_keys` 选择一个并传 `--key`。
144
+
145
+ 应用维度的管理和观测命令均支持 `--app-id`;省略时从当前目录的 `.coze [project]` 读取:
146
+
147
+ ```bash
148
+ coze-dev deploy code app rename "新应用名称" --app-id <app_id> --yes
149
+ coze-dev deploy code deploy online --app-id <app_id>
150
+ coze-dev deploy code deploy rollback-check --app-id <app_id> --deploy-id <history_id>
151
+ coze-dev deploy code deploy log-count --app-id <app_id>
152
+ coze-dev deploy code deploy count --app-id <app_id>
153
+ coze-dev deploy code app metrics --app-id <app_id> --time-range week
154
+ coze-dev deploy code app usage --app-id <app_id> --bill-month 2026-08
155
+ coze-dev deploy code domain list --app-id <app_id> --unit-type dev
156
+ ```
157
+
158
+ `domain list` 默认查询 Prod 域名;传 `--unit-type dev` 查询开发态预览域名。
159
+ 审核 Unit 是内部流水线槽位,不支持从 CLI 查询。
160
+
161
+ `code domain set-prefix` 改的是**系统分配的默认域名**(`<prefix>.coze.site`)里的 prefix,
162
+ 后缀不变;`code domain add` 加进来的自定义域名不受影响 —— 那类域名服务端只有 create/delete,
163
+ 域名串本身改不了。改完旧默认域名立刻不再指向本应用,已经发出去的链接会失效,所以必须传 `--yes`。
164
+ 前缀的长度、保留词、是否被占用由服务端判定,CLI 只拦形态明显不合法的值(大写、首尾连字符等)。
165
+ 这是**权益能力**:账号没有对应权益时服务端会直接拒绝。
166
+
167
+ `code app delete` 删除应用,终态不可撤销,必须传 `--yes`。服务端**只接受已停服的应用**:
168
+ 仍在服务时会返回一条带当前状态的错误,先跑 `code deploy stop --yes` 再删。CLI 不会替你先停 ——
169
+ 停服会真的把线上服务摘掉,那一步要由你自己确认。删除会一并停掉该应用名下的部署单元、
170
+ 软删它的域名并清理残留的函数;已删除的应用再删一次是幂等成功。删完记得把 `.coze [project]`
171
+ 里的 `app_id` 去掉,否则同目录下一次 `code deploy` 会打到这个已删除的应用上被服务端拒。
@@ -1,17 +1,20 @@
1
+ #!/usr/bin/env bash
2
+
1
3
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
2
4
  PREVIEW_DIR="/source/preview"
3
5
  LOG_DIR="${COZE_LOG_DIR:-$ROOT_DIR/logs}"
4
6
  LOG_CLIENT_FILE="$LOG_DIR/client.log"
7
+ LOG_SERVER_FILE="$LOG_DIR/server.log"
5
8
  mkdir -p "$LOG_DIR"
6
9
 
7
10
  # ==================== 配置项 ====================
8
11
  # Server 服务配置
9
12
  SERVER_HOST="0.0.0.0"
10
- SERVER_PORT="9091"
13
+ SERVER_PORT="${SERVER_PORT:-9091}"
11
14
  # Expo 项目配置
12
15
  EXPO_HOST="0.0.0.0"
13
16
  EXPO_DIR="expo"
14
- EXPO_PORT="5000"
17
+ EXPO_PORT="${EXPO_PORT:-5000}"
15
18
  WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-http://127.0.0.1:${SERVER_PORT}}"
16
19
  ASSUME_YES="1"
17
20
  EXPO_PUBLIC_BACKEND_BASE_URL="${EXPO_PUBLIC_BACKEND_BASE_URL:-$WEB_URL}"
@@ -22,6 +25,7 @@ export EXPO_PUBLIC_BACKEND_BASE_URL EXPO_PACKAGER_PROXY_URL EXPO_PUBLIC_COZE_PRO
22
25
  # 运行时变量(为避免 set -u 的未绑定错误,预置为空)
23
26
  SERVER_PID=""
24
27
  EXPO_PID=""
28
+ PNPM_BIN=""
25
29
 
26
30
  # ==================== 工具函数 ====================
27
31
  check_command() {
@@ -112,22 +116,67 @@ wait_port_connectable() {
112
116
  return 1
113
117
  }
114
118
 
115
- start_expo() {
116
- local offline="${1:-0}"
119
+ # coze-daemon 会在 runtime shell 退出时清理原进程组。
120
+ # 通过 Node detached spawn 创建独立 session/进程组,并将 stdio 直接写入日志。
121
+ spawn_detached() {
122
+ local cwd="$1"
123
+ local log_file="$2"
124
+ shift 2
125
+
126
+ node - "$cwd" "$log_file" "$@" <<'NODE'
127
+ const fs = require('node:fs');
128
+ const { spawn } = require('node:child_process');
117
129
 
118
- pushd "$ROOT_DIR/client"
130
+ const [cwd, logFile, command, ...args] = process.argv.slice(2);
131
+ if (!cwd || !logFile || !command) {
132
+ throw new Error('spawn_detached 缺少 cwd、log_file 或 command');
133
+ }
134
+
135
+ const logFd = fs.openSync(logFile, 'a');
136
+ try {
137
+ const child = spawn(command, args, {
138
+ cwd,
139
+ detached: true,
140
+ env: process.env,
141
+ stdio: ['ignore', logFd, logFd],
142
+ });
143
+ child.unref();
144
+ process.stdout.write(String(child.pid));
145
+ } finally {
146
+ fs.closeSync(logFd);
147
+ }
148
+ NODE
149
+ }
119
150
 
151
+ stop_detached() {
152
+ local pid="${1:-}"
153
+ if [ -z "$pid" ]; then
154
+ return
155
+ fi
156
+
157
+ kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
158
+ sleep 1
159
+ kill -KILL -- "-$pid" 2>/dev/null || true
160
+ }
161
+
162
+ start_expo() {
163
+ local offline="${1:-0}"
164
+ local -a expo_env
165
+ expo_env=(
166
+ "EXPO_NO_DEPENDENCY_VALIDATION=1"
167
+ "EXPO_PUBLIC_BACKEND_BASE_URL=$EXPO_PUBLIC_BACKEND_BASE_URL"
168
+ "EXPO_PACKAGER_PROXY_URL=$EXPO_PACKAGER_PROXY_URL"
169
+ "EXPO_PUBLIC_COZE_PROJECT_ID=$EXPO_PUBLIC_COZE_PROJECT_ID"
170
+ )
120
171
  if [ "$offline" = "1" ]; then
121
- ( EXPO_OFFLINE=1 EXPO_NO_DEPENDENCY_VALIDATION=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" EXPO_PUBLIC_COZE_PROJECT_ID="$EXPO_PUBLIC_COZE_PROJECT_ID" \
122
- nohup npx expo start --clear --port "$EXPO_PORT" 2>&1 | pipe_to_log "CLIENT" "$LOG_CLIENT_FILE" ) &
123
- else
124
- ( EXPO_NO_DEPENDENCY_VALIDATION=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" EXPO_PUBLIC_COZE_PROJECT_ID="$EXPO_PUBLIC_COZE_PROJECT_ID" \
125
- nohup npx expo start --clear --port "$EXPO_PORT" 2>&1 | pipe_to_log "CLIENT" "$LOG_CLIENT_FILE" ) &
172
+ expo_env+=("EXPO_OFFLINE=1")
126
173
  fi
127
- EXPO_PID=$!
128
- disown $EXPO_PID 2>/dev/null || true
129
174
 
130
- popd
175
+ EXPO_PID="$(spawn_detached \
176
+ "$ROOT_DIR/client" \
177
+ "$LOG_CLIENT_FILE" \
178
+ /usr/bin/env "${expo_env[@]}" \
179
+ "$PNPM_BIN" exec expo start --clear --port "$EXPO_PORT")"
131
180
  }
132
181
 
133
182
  detect_expo_fetch_failed() {
@@ -168,6 +217,8 @@ check_command "npm"
168
217
  check_command "pnpm"
169
218
  check_command "lsof"
170
219
  check_command "bash"
220
+ check_command "node"
221
+ PNPM_BIN="$(command -v pnpm)"
171
222
 
172
223
  # 端口占用预检查与处理
173
224
  ensure_port SERVER_PORT "$SERVER_PORT"
@@ -175,9 +226,12 @@ ensure_port EXPO_PORT "$EXPO_PORT"
175
226
 
176
227
  echo "==================== 启动 server 服务 ===================="
177
228
  echo "正在执行:pnpm run dev (server)"
178
- ( pushd "$ROOT_DIR/server" > /dev/null && SERVER_PORT="$SERVER_PORT" nohup pnpm run dev; popd > /dev/null ) &
179
- SERVER_PID=$!
180
- disown $SERVER_PID 2>/dev/null || true
229
+ : > "$LOG_SERVER_FILE"
230
+ export SERVER_PORT
231
+ SERVER_PID="$(spawn_detached \
232
+ "$ROOT_DIR/server" \
233
+ /dev/null \
234
+ "$PNPM_BIN" run dev)"
181
235
  if [ -z "${SERVER_PID}" ]; then
182
236
  echo "无法获取 server 后台进程 PID"
183
237
  fi
@@ -185,11 +239,12 @@ echo "server 服务已启动,进程 ID:${SERVER_PID:-unknown}"
185
239
 
186
240
  echo "==================== 启动 Expo 项目 ===================="
187
241
  echo "开始启动 Expo 服务,端口 ${EXPO_PORT}"
242
+ : > "$LOG_CLIENT_FILE"
188
243
  start_expo 0
189
244
  if detect_expo_fetch_failed 8; then
190
245
  echo "Expo 启动检测到网络错误:TypeError: fetch failed,启用离线模式重试"
191
- if [ -n "${EXPO_PID}" ]; then kill -9 "$EXPO_PID" 2>/dev/null || true; fi
192
- : > "$ROOT_DIR/logs/client.log"
246
+ stop_detached "$EXPO_PID"
247
+ : > "$LOG_CLIENT_FILE"
193
248
  start_expo 1
194
249
  fi
195
250
  # 输出以下环境变量,确保 Expo 项目能正确连接到 Server 服务
@@ -269,8 +269,8 @@ importers:
269
269
  specifier: ^2.8.5
270
270
  version: 2.8.6
271
271
  coze-coding-dev-sdk:
272
- specifier: ^0.7.24
273
- version: 0.7.24(openai@6.16.0(ws@8.19.0)(zod@4.3.6))(ws@8.19.0)
272
+ specifier: ^0.7.25-beta.4
273
+ version: 0.7.25-beta.4(openai@6.16.0(ws@8.19.0)(zod@4.3.6))(ws@8.19.0)
274
274
  dayjs:
275
275
  specifier: ^1.11.20
276
276
  version: 1.11.20
@@ -2643,8 +2643,8 @@ packages:
2643
2643
  resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
2644
2644
  engines: {node: '>=10'}
2645
2645
 
2646
- coze-coding-dev-sdk@0.7.24:
2647
- resolution: {integrity: sha512-oL9Tg5PwxP6iMixonzzMO4aBjOKWcFUMVM+iz5dPyaqQkG84HP1y9DqK5DJVmtdfJuYdAW7pzOatDqLvgvZiXg==}
2646
+ coze-coding-dev-sdk@0.7.25-beta.4:
2647
+ resolution: {integrity: sha512-+PCoJ8yhsAIlx3YT898bCkB2gx5EMBunJMP7/kI/Jq/8dj7SDU9EUBZ2Z8ZJocSaPBT9T6dVvFHBsDLZh4FN/Q==}
2648
2648
  engines: {node: '>=18.0.0'}
2649
2649
  hasBin: true
2650
2650
 
@@ -9088,7 +9088,7 @@ snapshots:
9088
9088
  path-type: 4.0.0
9089
9089
  yaml: 1.10.2
9090
9090
 
9091
- coze-coding-dev-sdk@0.7.24(openai@6.16.0(ws@8.19.0)(zod@4.3.6))(ws@8.19.0):
9091
+ coze-coding-dev-sdk@0.7.25-beta.4(openai@6.16.0(ws@8.19.0)(zod@4.3.6))(ws@8.19.0):
9092
9092
  dependencies:
9093
9093
  '@langchain/core': 1.1.17(openai@6.16.0(ws@8.19.0)(zod@4.3.6))
9094
9094
  '@langchain/openai': 1.2.3(@langchain/core@1.1.17(openai@6.16.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0)
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "@supabase/supabase-js": "2.95.3",
14
14
  "cors": "^2.8.5",
15
- "coze-coding-dev-sdk": "^0.7.24",
15
+ "coze-coding-dev-sdk": "^0.7.25-beta.4",
16
16
  "dayjs": "^1.11.20",
17
17
  "dotenv": "^17.2.3",
18
18
  "drizzle-orm": "^0.45.1",
@@ -32,8 +32,89 @@ kill_port_if_listening() {
32
32
  fi
33
33
  }
34
34
 
35
+ <% if (process.env.NODE_ENV === 'test') { %>
36
+ echo "Clearing port ${DEPLOY_RUN_PORT} before start."
37
+ kill_port_if_listening
38
+ echo "Starting HTTP service on port ${DEPLOY_RUN_PORT} for dev..."
39
+
40
+ # 测试环境保持服务为前台进程,便于 e2e 收集输出并停止进程。
41
+ exec env PORT="${DEPLOY_RUN_PORT}" pnpm tsx watch src/server.ts
42
+ <% } else { %>
43
+ LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
44
+ LOG_FILE="${LOG_DIR}/server.log"
45
+ PID_FILE="${LOG_DIR}/server.pid"
46
+
47
+ # coze-daemon 会在 runtime shell 退出时清理原进程组。
48
+ # 通过 Node detached spawn 创建独立 session/进程组,并将 stdio 直接写入日志。
49
+ spawn_detached() {
50
+ local cwd="$1"
51
+ local log_file="$2"
52
+ shift 2
53
+
54
+ node - "$cwd" "$log_file" "$@" <<'NODE'
55
+ const fs = require('node:fs');
56
+ const { spawn } = require('node:child_process');
57
+
58
+ const [cwd, logFile, command, ...args] = process.argv.slice(2);
59
+ if (!cwd || !logFile || !command) {
60
+ throw new Error('spawn_detached 缺少 cwd、log_file 或 command');
61
+ }
62
+
63
+ const logFd = fs.openSync(logFile, 'a');
64
+ try {
65
+ const child = spawn(command, args, {
66
+ cwd,
67
+ detached: true,
68
+ env: process.env,
69
+ stdio: ['ignore', logFd, logFd],
70
+ });
71
+ child.unref();
72
+ process.stdout.write(String(child.pid));
73
+ } finally {
74
+ fs.closeSync(logFd);
75
+ }
76
+ NODE
77
+ }
78
+
79
+ stop_detached() {
80
+ local pid="${1:-}"
81
+ if [[ -z "${pid}" ]]; then
82
+ return
83
+ fi
84
+
85
+ kill -TERM -- "-${pid}" 2>/dev/null || kill -TERM "${pid}" 2>/dev/null || true
86
+ sleep 1
87
+ kill -KILL -- "-${pid}" 2>/dev/null || true
88
+ }
89
+
90
+ # 监听端口的是孙进程(pnpm -> tsx -> node),只清端口会漏掉上层 pnpm/tsx,
91
+ # 所以先按上次记录的 PID 把整个进程组回收掉。
92
+ if [[ -f "${PID_FILE}" ]]; then
93
+ stop_detached "$(cat "${PID_FILE}" 2>/dev/null || true)"
94
+ rm -f "${PID_FILE}"
95
+ fi
96
+
35
97
  echo "Clearing port ${DEPLOY_RUN_PORT} before start."
36
98
  kill_port_if_listening
37
99
  echo "Starting HTTP service on port ${DEPLOY_RUN_PORT} for dev..."
38
100
 
39
- PORT=${DEPLOY_RUN_PORT} pnpm tsx watch src/server.ts
101
+ mkdir -p "${LOG_DIR}"
102
+ : > "${LOG_FILE}"
103
+
104
+ export PORT="${DEPLOY_RUN_PORT}"
105
+ server_pid="$(spawn_detached "${COZE_WORKSPACE_PATH}" "${LOG_FILE}" \
106
+ "$(command -v pnpm)" tsx watch src/server.ts)"
107
+ echo "${server_pid}" > "${PID_FILE}"
108
+
109
+ sleep 1
110
+ if [[ -z "${server_pid}" ]] || ! kill -0 "${server_pid}" 2>/dev/null; then
111
+ echo "Dev server failed to start. See ${LOG_FILE}." >&2
112
+ tail -n 20 "${LOG_FILE}" >&2 || true
113
+ rm -f "${PID_FILE}"
114
+ exit 1
115
+ fi
116
+
117
+ echo "Dev server started (PID: ${server_pid})."
118
+ echo "Log file: ${LOG_FILE}"
119
+ echo "PID file: ${PID_FILE}"
120
+ <% } %>
@@ -3,6 +3,7 @@
3
3
  import { spawn } from 'child_process';
4
4
  import { resolve, join, basename } from 'path';
5
5
  import { appendFileSync, openSync, closeSync, mkdirSync } from 'fs';
6
+ import { homedir } from 'os';
6
7
 
7
8
 
8
9
 
@@ -74,7 +75,8 @@ const config = {
74
75
  const projectRoot = resolve(outputPath);
75
76
 
76
77
  // Determine log directory
77
- const logDir = process.env.COZE_LOG_DIR || resolve(__dirname, '../.log');
78
+ const cozeHome = process.env.COZE_HOME || join(homedir(), '.coze');
79
+ const logDir = process.env.COZE_LOG_DIR || join(cozeHome, 'logs');
78
80
  mkdirSync(logDir, { recursive: true });
79
81
 
80
82
  // Use project name in log file to avoid conflicts
@@ -32,8 +32,89 @@ kill_port_if_listening() {
32
32
  fi
33
33
  }
34
34
 
35
+ <% if (process.env.NODE_ENV === 'test') { %>
36
+ echo "Clearing port ${DEPLOY_RUN_PORT} before start."
37
+ kill_port_if_listening
38
+ echo "Starting Nuxt dev server on port ${DEPLOY_RUN_PORT}..."
39
+
40
+ # 测试环境保持服务为前台进程,便于 e2e 收集输出并停止进程。
41
+ exec env PORT="${DEPLOY_RUN_PORT}" pnpm nuxt dev
42
+ <% } else { %>
43
+ LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
44
+ LOG_FILE="${LOG_DIR}/server.log"
45
+ PID_FILE="${LOG_DIR}/server.pid"
46
+
47
+ # coze-daemon 会在 runtime shell 退出时清理原进程组。
48
+ # 通过 Node detached spawn 创建独立 session/进程组,并将 stdio 直接写入日志。
49
+ spawn_detached() {
50
+ local cwd="$1"
51
+ local log_file="$2"
52
+ shift 2
53
+
54
+ node - "$cwd" "$log_file" "$@" <<'NODE'
55
+ const fs = require('node:fs');
56
+ const { spawn } = require('node:child_process');
57
+
58
+ const [cwd, logFile, command, ...args] = process.argv.slice(2);
59
+ if (!cwd || !logFile || !command) {
60
+ throw new Error('spawn_detached 缺少 cwd、log_file 或 command');
61
+ }
62
+
63
+ const logFd = fs.openSync(logFile, 'a');
64
+ try {
65
+ const child = spawn(command, args, {
66
+ cwd,
67
+ detached: true,
68
+ env: process.env,
69
+ stdio: ['ignore', logFd, logFd],
70
+ });
71
+ child.unref();
72
+ process.stdout.write(String(child.pid));
73
+ } finally {
74
+ fs.closeSync(logFd);
75
+ }
76
+ NODE
77
+ }
78
+
79
+ stop_detached() {
80
+ local pid="${1:-}"
81
+ if [[ -z "${pid}" ]]; then
82
+ return
83
+ fi
84
+
85
+ kill -TERM -- "-${pid}" 2>/dev/null || kill -TERM "${pid}" 2>/dev/null || true
86
+ sleep 1
87
+ kill -KILL -- "-${pid}" 2>/dev/null || true
88
+ }
89
+
90
+ # 监听端口的是孙进程(pnpm -> nuxt -> node),只清端口会漏掉上层 pnpm/nuxt,
91
+ # 所以先按上次记录的 PID 把整个进程组回收掉。
92
+ if [[ -f "${PID_FILE}" ]]; then
93
+ stop_detached "$(cat "${PID_FILE}" 2>/dev/null || true)"
94
+ rm -f "${PID_FILE}"
95
+ fi
96
+
35
97
  echo "Clearing port ${DEPLOY_RUN_PORT} before start."
36
98
  kill_port_if_listening
37
99
  echo "Starting Nuxt dev server on port ${DEPLOY_RUN_PORT}..."
38
100
 
39
- PORT=${DEPLOY_RUN_PORT} pnpm nuxt dev
101
+ mkdir -p "${LOG_DIR}"
102
+ : > "${LOG_FILE}"
103
+
104
+ export PORT="${DEPLOY_RUN_PORT}"
105
+ server_pid="$(spawn_detached "${COZE_WORKSPACE_PATH}" "${LOG_FILE}" \
106
+ "$(command -v pnpm)" nuxt dev)"
107
+ echo "${server_pid}" > "${PID_FILE}"
108
+
109
+ sleep 1
110
+ if [[ -z "${server_pid}" ]] || ! kill -0 "${server_pid}" 2>/dev/null; then
111
+ echo "Dev server failed to start. See ${LOG_FILE}." >&2
112
+ tail -n 20 "${LOG_FILE}" >&2 || true
113
+ rm -f "${PID_FILE}"
114
+ exit 1
115
+ fi
116
+
117
+ echo "Dev server started (PID: ${server_pid})."
118
+ echo "Log file: ${LOG_FILE}"
119
+ echo "PID file: ${PID_FILE}"
120
+ <% } %>
@@ -8,6 +8,7 @@ coverage/
8
8
 
9
9
  *.log
10
10
  *.tsbuildinfo
11
+ logs/
11
12
  .vite/
12
13
  .cache/
13
14
  node-compile-cache/
@@ -24,7 +24,7 @@
24
24
  "typescript": "^5.9.3",
25
25
  "vite": "^8.2.0"
26
26
  },
27
- "packageManager": "pnpm@9.15.9",
27
+ "packageManager": "pnpm@9.0.0",
28
28
  "engines": {
29
29
  "pnpm": ">=9.0.0"
30
30
  }