@coze-arch/cli 0.0.36-alpha.844e20 → 0.0.36-alpha.98d71c

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/docs/deploy.md CHANGED
@@ -54,7 +54,7 @@ coze-dev deploy code deploy logs|log-count|count|build-log|config
54
54
  coze-dev deploy code domain list [--unit-type dev|prod]
55
55
  coze-dev deploy code domain add <domain>
56
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
57
+ coze-dev deploy code mini-program status|bind|submit-code|unbind|auth|preview
58
58
  ```
59
59
 
60
60
  `code app list` 是真分页:一次只返回一页(`--page-size` 默认 20、上限 100),`has_more` 为真时用返回的 `next_page_token` 取下一页;要一次拿到名下全部应用用 `--all`,它会自己翻到底。
@@ -101,21 +101,17 @@ V2 接口的所有参数均通过 POST JSON body 发送,不会放入 URL。确
101
101
  `scripts/package-project.py` 仍随源码和 npm 包保留,仅作为 Python 参考备份与行为对照,
102
102
  生产打包链路不会调用 Python。
103
103
 
104
- 服务和小程序部署会读取源码根目录的 `.env` 并加密上传;`COZE_` 前缀为平台保留,CLI
105
- 会过滤这类变量,避免覆盖部署运行时由平台注入的配置。
104
+ 环境变量以当前线上成功版本的 Secret 快照为准,CLI 不再读取源码根目录的 `.env`。
105
+ 更新服务或小程序时,CLI 会读取线上快照中的用户变量并重新加密上传;首次部署没有线上
106
+ 快照,因此使用空的用户变量集合。平台系统变量由 Secret 服务管理,不作为用户变量上传。
106
107
 
107
- 本地环境变量可通过以下命令管理,默认操作当前目录的 `.env`,也可通过
108
- `--source-root` 指定项目目录;这些命令不会请求部署服务:
108
+ 通过 `app_id` 查看当前线上版本的环境变量及快照状态:
109
109
 
110
110
  ```bash
111
111
  coze-dev deploy code env list
112
- coze-dev deploy code env set API_KEY "secret value"
113
- coze-dev deploy code env delete API_KEY
114
- coze-dev deploy code env list --source-root ./projects/example
112
+ coze-dev deploy code env list --app-id 7672644451805691910
115
113
  ```
116
114
 
117
- `env set` 和 `env delete` 不允许操作 `COZE_` 前缀的保留变量。
118
-
119
115
  小程序部署可重复指定渠道:微信为 `10000127`,抖音为 `10000126`:
120
116
 
121
117
  ```bash
@@ -123,6 +119,13 @@ coze-dev deploy code deploy --source-root . --application-type mini_program \
123
119
  --connector-id 10000127 --yes --wait
124
120
  ```
125
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
+
126
129
  绑定小程序 AppID 时,默认从源码目录 `.coze [project]` 读取该目录的 `project_id`:
127
130
 
128
131
  ```bash
@@ -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 服务
@@ -14,6 +14,9 @@ DEPLOY_RUN_PORT="${DEPLOY_RUN_PORT:-${PORT}}"
14
14
 
15
15
  cd "${COZE_WORKSPACE_PATH}"
16
16
 
17
+ LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
18
+ LOG_SERVER_FILE="${LOG_DIR}/server.log"
19
+
17
20
  kill_port_if_listening() {
18
21
  local pids
19
22
  pids=$(ss -H -lntp 2>/dev/null | awk -v port="${DEPLOY_RUN_PORT}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | paste -sd' ' - || true)
@@ -36,4 +39,10 @@ echo "Clearing port ${DEPLOY_RUN_PORT} before start."
36
39
  kill_port_if_listening
37
40
  echo "Starting HTTP service on port ${DEPLOY_RUN_PORT} for dev..."
38
41
 
39
- PORT=${DEPLOY_RUN_PORT} pnpm tsx watch src/server.ts
42
+ mkdir -p "${LOG_DIR}"
43
+ : > "${LOG_SERVER_FILE}"
44
+
45
+ # 保持服务为前台进程,让外层 coze-dev dev 的 PID 在整个服务生命周期内有效。
46
+ # 同时通过 tee 将同一份输出写入 logs/server.log。
47
+ exec env PORT="${DEPLOY_RUN_PORT}" pnpm tsx watch src/server.ts \
48
+ > >(tee -a "${LOG_SERVER_FILE}") 2>&1
@@ -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
@@ -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/
@@ -2,10 +2,81 @@
2
2
  set -Eeuo pipefail
3
3
 
4
4
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
5
- PORT="${DEPLOY_RUN_PORT:-${PORT:-<%= port %>}}"
5
+ PORT="${PORT:-<%= port %>}"
6
+ DEPLOY_RUN_PORT="${DEPLOY_RUN_PORT:-${PORT}}"
6
7
  cd "${COZE_WORKSPACE_PATH}"
7
8
 
8
- COZE_PHASER_GAME_ENV=DEV pnpm vite \
9
- --port "${PORT}" \
9
+ get_listening_pids() {
10
+ local port="$1"
11
+
12
+ if command -v lsof >/dev/null 2>&1; then
13
+ lsof -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true
14
+ elif command -v ss >/dev/null 2>&1; then
15
+ ss -H -lntp 2>/dev/null \
16
+ | awk -v port="${port}" '$4 ~ ":"port"$"' \
17
+ | grep -o 'pid=[0-9]*' \
18
+ | cut -d= -f2 \
19
+ | sort -u || true
20
+ else
21
+ echo "Warning: neither lsof nor ss found, cannot check port ${port}." >&2
22
+ fi
23
+ }
24
+
25
+ kill_port_if_listening() {
26
+ local port="$1"
27
+ local pids
28
+
29
+ pids="$(get_listening_pids "${port}")"
30
+ if [[ -z "${pids}" ]]; then
31
+ echo "Port ${port} is free."
32
+ return
33
+ fi
34
+
35
+ echo "Port ${port} is in use by PIDs: ${pids//$'\n'/ } (SIGKILL)"
36
+ echo "${pids}" | xargs kill -9 2>/dev/null || true
37
+ sleep 1
38
+
39
+ pids="$(get_listening_pids "${port}")"
40
+ if [[ -n "${pids}" ]]; then
41
+ echo "Failed to clear port ${port}; remaining PIDs: ${pids//$'\n'/ }." >&2
42
+ return 1
43
+ fi
44
+
45
+ echo "Port ${port} cleared."
46
+ }
47
+
48
+ echo "Clearing port ${DEPLOY_RUN_PORT} before start."
49
+ kill_port_if_listening "${DEPLOY_RUN_PORT}"
50
+
51
+ <% if (process.env.NODE_ENV === 'test') { %>
52
+ # Keep the process attached in tests so the test runner can collect logs and stop it.
53
+ exec env COZE_PHASER_GAME_ENV=DEV pnpm vite \
54
+ --port "${DEPLOY_RUN_PORT}" \
10
55
  --host 127.0.0.1 \
11
56
  --strictPort
57
+ <% } else { %>
58
+ LOG_DIR="${COZE_WORKSPACE_PATH}/logs"
59
+ LOG_FILE="${LOG_DIR}/phaser-dev.log"
60
+ PID_FILE="${LOG_DIR}/phaser-dev.pid"
61
+ mkdir -p "${LOG_DIR}"
62
+
63
+ echo "Starting Phaser dev server on port ${DEPLOY_RUN_PORT}..."
64
+ server_pid="$(COZE_PHASER_GAME_ENV=DEV node scripts/spawn-detached.cjs \
65
+ "${LOG_FILE}" pnpm vite \
66
+ --port "${DEPLOY_RUN_PORT}" \
67
+ --host 127.0.0.1 \
68
+ --strictPort)"
69
+ echo "${server_pid}" > "${PID_FILE}"
70
+
71
+ sleep 1
72
+ if ! kill -0 "${server_pid}" 2>/dev/null; then
73
+ echo "Phaser dev server failed to start. See ${LOG_FILE}." >&2
74
+ tail -n 20 "${LOG_FILE}" >&2 || true
75
+ rm -f "${PID_FILE}"
76
+ exit 1
77
+ fi
78
+
79
+ echo "Phaser dev server started (PID: ${server_pid})."
80
+ echo "Log file: ${LOG_FILE}"
81
+ echo "PID file: ${PID_FILE}"
82
+ <% } %>
@@ -0,0 +1,29 @@
1
+ const fs = require('node:fs');
2
+ const { spawn } = require('node:child_process');
3
+
4
+ const [logFile, command, ...args] = process.argv.slice(2);
5
+
6
+ if (!logFile || !command) {
7
+ process.stderr.write(
8
+ 'Usage: node scripts/spawn-detached.cjs <log-file> <command> [...args]\n',
9
+ );
10
+ process.exit(1);
11
+ }
12
+
13
+ const logFd = fs.openSync(logFile, 'a');
14
+ const child = spawn(command, args, {
15
+ cwd: process.cwd(),
16
+ detached: process.platform !== 'win32',
17
+ env: process.env,
18
+ stdio: ['ignore', logFd, logFd],
19
+ });
20
+
21
+ fs.closeSync(logFd);
22
+ child.unref();
23
+
24
+ if (!child.pid) {
25
+ process.stderr.write('Failed to start detached process.\n');
26
+ process.exit(1);
27
+ }
28
+
29
+ process.stdout.write(String(child.pid));
@@ -2,10 +2,81 @@
2
2
  set -Eeuo pipefail
3
3
 
4
4
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
5
- PORT="${DEPLOY_RUN_PORT:-${PORT:-<%= port %>}}"
5
+ PORT="${PORT:-<%= port %>}"
6
+ DEPLOY_RUN_PORT="${DEPLOY_RUN_PORT:-${PORT}}"
6
7
  cd "${COZE_WORKSPACE_PATH}"
7
8
 
8
- COZE_PHASER_GAME_ENV=PROD pnpm vite preview \
9
- --port "${PORT}" \
9
+ get_listening_pids() {
10
+ local port="$1"
11
+
12
+ if command -v lsof >/dev/null 2>&1; then
13
+ lsof -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true
14
+ elif command -v ss >/dev/null 2>&1; then
15
+ ss -H -lntp 2>/dev/null \
16
+ | awk -v port="${port}" '$4 ~ ":"port"$"' \
17
+ | grep -o 'pid=[0-9]*' \
18
+ | cut -d= -f2 \
19
+ | sort -u || true
20
+ else
21
+ echo "Warning: neither lsof nor ss found, cannot check port ${port}." >&2
22
+ fi
23
+ }
24
+
25
+ kill_port_if_listening() {
26
+ local port="$1"
27
+ local pids
28
+
29
+ pids="$(get_listening_pids "${port}")"
30
+ if [[ -z "${pids}" ]]; then
31
+ echo "Port ${port} is free."
32
+ return
33
+ fi
34
+
35
+ echo "Port ${port} is in use by PIDs: ${pids//$'\n'/ } (SIGKILL)"
36
+ echo "${pids}" | xargs kill -9 2>/dev/null || true
37
+ sleep 1
38
+
39
+ pids="$(get_listening_pids "${port}")"
40
+ if [[ -n "${pids}" ]]; then
41
+ echo "Failed to clear port ${port}; remaining PIDs: ${pids//$'\n'/ }." >&2
42
+ return 1
43
+ fi
44
+
45
+ echo "Port ${port} cleared."
46
+ }
47
+
48
+ echo "Clearing port ${DEPLOY_RUN_PORT} before start."
49
+ kill_port_if_listening "${DEPLOY_RUN_PORT}"
50
+
51
+ <% if (process.env.NODE_ENV === 'test') { %>
52
+ # Keep the process attached in tests so the test runner can collect logs and stop it.
53
+ exec env COZE_PHASER_GAME_ENV=PROD pnpm vite preview \
54
+ --port "${DEPLOY_RUN_PORT}" \
10
55
  --host 127.0.0.1 \
11
56
  --strictPort
57
+ <% } else { %>
58
+ LOG_DIR="${COZE_WORKSPACE_PATH}/logs"
59
+ LOG_FILE="${LOG_DIR}/phaser-start.log"
60
+ PID_FILE="${LOG_DIR}/phaser-start.pid"
61
+ mkdir -p "${LOG_DIR}"
62
+
63
+ echo "Starting Phaser production server on port ${DEPLOY_RUN_PORT}..."
64
+ server_pid="$(COZE_PHASER_GAME_ENV=PROD node scripts/spawn-detached.cjs \
65
+ "${LOG_FILE}" pnpm vite preview \
66
+ --port "${DEPLOY_RUN_PORT}" \
67
+ --host 127.0.0.1 \
68
+ --strictPort)"
69
+ echo "${server_pid}" > "${PID_FILE}"
70
+
71
+ sleep 1
72
+ if ! kill -0 "${server_pid}" 2>/dev/null; then
73
+ echo "Phaser production server failed to start. See ${LOG_FILE}." >&2
74
+ tail -n 20 "${LOG_FILE}" >&2 || true
75
+ rm -f "${PID_FILE}"
76
+ exit 1
77
+ fi
78
+
79
+ echo "Phaser production server started (PID: ${server_pid})."
80
+ echo "Log file: ${LOG_FILE}"
81
+ echo "PID file: ${PID_FILE}"
82
+ <% } %>
@@ -8,6 +8,9 @@ cd "${COZE_WORKSPACE_PATH}"
8
8
  # PID 文件,用于追踪上一次启动的进程树
9
9
  # ---------------------------------------------------------
10
10
  PID_FILE="/tmp/coze-dev-run.pid"
11
+ LOG_DIR="/tmp/coze-logs"
12
+ LOG_FILE="${LOG_DIR}/dev.log"
13
+ DEV_PID=""
11
14
 
12
15
  # ---------------------------------------------------------
13
16
  # 工具函数
@@ -89,7 +92,8 @@ cleanup_previous_run() {
89
92
  # 2. 安装依赖
90
93
  # ---------------------------------------------------------
91
94
  echo "📦 Installing dependencies..."
92
- pnpm install
95
+ PNPM_BIN="$(command -v pnpm)"
96
+ "${PNPM_BIN}" install
93
97
  echo "✅ Dependencies installed successfully!"
94
98
 
95
99
  # ---------------------------------------------------------
@@ -108,11 +112,14 @@ kill_port_if_listening "${SERVER_PORT}"
108
112
  # 4. 退出时自动清理子进程(信号 trap)
109
113
  # ---------------------------------------------------------
110
114
  cleanup_on_exit() {
111
- echo "🛑 dev_run.sh exiting, cleaning up child processes..."
112
- # 杀掉当前脚本的所有子进程
113
- kill -- -$$ 2>/dev/null || true
115
+ local exit_code=$?
116
+ trap - EXIT INT TERM HUP
117
+ if [[ -n "${DEV_PID}" ]] && kill -0 "${DEV_PID}" 2>/dev/null; then
118
+ echo "🛑 dev_run.sh exiting, cleaning up child processes..."
119
+ kill_process_tree "${DEV_PID}"
120
+ fi
114
121
  rm -f "${PID_FILE}"
115
- exit 0
122
+ exit "${exit_code}"
116
123
  }
117
124
  trap cleanup_on_exit EXIT INT TERM HUP
118
125
 
@@ -134,17 +141,16 @@ start_service() {
134
141
  echo "Starting Taro H5 Dev Server and NestJS Server..."
135
142
 
136
143
  export PORT=${DEPLOY_RUN_PORT}
137
- rm -f /tmp/coze-logs/dev.log
138
- mkdir -p /tmp/coze-logs
144
+ mkdir -p "${LOG_DIR}"
145
+ : > "${LOG_FILE}"
139
146
 
140
- # 后台启动并记录 PID
141
- pnpm dev 2>&1 | tee /tmp/coze-logs/dev.log &
142
- local dev_pid=$!
143
- echo "${dev_pid}" > "${PID_FILE}"
144
- echo "📝 Dev process started with PID: ${dev_pid} (saved to ${PID_FILE})"
147
+ # 后台启动以便记录真实 pnpm PID,但脚本持续 wait,保持外层 CLI 生命周期有效。
148
+ "${PNPM_BIN}" dev > >(tee -a "${LOG_FILE}") 2>&1 &
149
+ DEV_PID=$!
150
+ echo "${DEV_PID}" > "${PID_FILE}"
151
+ echo "📝 Dev process started with PID: ${DEV_PID} (saved to ${PID_FILE})"
145
152
 
146
- # 前台等待,保证 trap 能正常捕获信号
147
- wait "${dev_pid}" || true
153
+ wait "${DEV_PID}"
148
154
  }
149
155
 
150
156
  echo "Starting HTTP services on port ${DEPLOY_RUN_PORT} (web) and ${SERVER_PORT} (server)..."
@@ -2,6 +2,7 @@
2
2
  import { spawn } from 'child_process';
3
3
  import { resolve, join, basename } from 'path';
4
4
  import { appendFileSync, openSync, closeSync, mkdirSync } from 'fs';
5
+ import { homedir } from 'os';
5
6
 
6
7
 
7
8
 
@@ -81,7 +82,8 @@ const config = {
81
82
  const projectRoot = resolve(outputPath);
82
83
 
83
84
  // Determine log directory
84
- const logDir = process.env.COZE_LOG_DIR || resolve(__dirname, '../.log');
85
+ const cozeHome = process.env.COZE_HOME || join(homedir(), '.coze');
86
+ const logDir = process.env.COZE_LOG_DIR || join(cozeHome, 'logs');
85
87
  mkdirSync(logDir, { recursive: true });
86
88
 
87
89
  // Use project name in log file to avoid conflicts