@coze-arch/cli 0.0.36-alpha.2c0689 → 0.0.36-alpha.3ab612

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
@@ -69,7 +69,9 @@ build_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Fil
69
69
 
70
70
  ## 部署
71
71
 
72
- `coze-dev deploy` 内置于同一 CLI,登录态默认保存在 `~/.coze/cli/config.json`:
72
+ `coze-dev deploy` 内置于同一 CLI。认证优先读取
73
+ `~/.coze/desktop/runtime.beta.json`,其次读取 `~/.coze/desktop/runtime.json`;
74
+ 桌面 Token 不可用时再回退到 `COZE_API_TOKEN` 和 `~/.coze/cli/config.json` 登录态:
73
75
 
74
76
  ```bash
75
77
  coze-dev deploy auth login
package/docs/deploy.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # coze-dev deploy
2
2
 
3
- `coze-dev` 内置的部署命令,复用主 Coze CLI 保存在
4
- `~/.coze/cli/config.json` 中的登录状态。
3
+ `coze-dev` 内置的部署命令。认证凭据按以下优先级读取:
4
+
5
+ 1. `~/.coze/desktop/runtime.beta.json` 的 `patToken.accessToken`
6
+ 2. `~/.coze/desktop/runtime.json` 的 `patToken.accessToken`
7
+ 3. 非空环境变量 `COZE_API_TOKEN`
8
+ 4. 主 Coze CLI 保存在 `~/.coze/cli/config.json` 中的登录状态
9
+
10
+ 桌面运行时文件不存在、不可读、JSON 无效或 Token 字段缺失、为空时,CLI
11
+ 静默尝试下一来源,不输出或持久化 Token。
5
12
 
6
13
  ## 安装与运行
7
14
 
@@ -16,7 +16,12 @@ EXPO_HOST="0.0.0.0"
16
16
  EXPO_DIR="expo"
17
17
  # DEPLOY_RUN_PORT 是平台注入的对外端口,EXPO_PORT 保留为本地覆盖项。
18
18
  EXPO_PORT="${DEPLOY_RUN_PORT:-${EXPO_PORT:-<%= port %>}}"
19
- WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-http://127.0.0.1:${SERVER_PORT}}"
19
+ COZE_HARDWARE_DID="${COZE_HARDWARE_DID:-}"
20
+ if [ -n "${EXPO_PORT:-}" ] && [ -n "${COZE_HARDWARE_DID}" ]; then
21
+ WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-https://cozedaemon-${COZE_HARDWARE_DID}-${EXPO_PORT}.dev.coze.site}"
22
+ else
23
+ WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-http://127.0.0.1:${SERVER_PORT}}"
24
+ fi
20
25
  ASSUME_YES="1"
21
26
  EXPO_PUBLIC_BACKEND_BASE_URL="${EXPO_PUBLIC_BACKEND_BASE_URL:-$WEB_URL}"
22
27
  EXPO_PUBLIC_COZE_PROJECT_ID="${COZE_PROJECT_ID:-}"
@@ -14,22 +14,42 @@ DEPLOY_RUN_PORT="${DEPLOY_RUN_PORT:-${PORT}}"
14
14
 
15
15
  cd "${COZE_WORKSPACE_PATH}"
16
16
 
17
+ list_port_pids() {
18
+ local port=$1
19
+ local pids=""
20
+ local has_tool=0
21
+
22
+ if command -v ss >/dev/null 2>&1; then
23
+ has_tool=1
24
+ pids=$(ss -H -lntp 2>/dev/null | awk -v port="${port}" '$4 ~ ":"port"$"' | grep -o 'pid=[0-9]*' | cut -d= -f2 | sort -u | paste -sd' ' - || true)
25
+ fi
26
+ if [[ -z "${pids}" ]] && command -v lsof >/dev/null 2>&1; then
27
+ has_tool=1
28
+ pids=$(lsof -t -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null | sort -u | paste -sd' ' - || true)
29
+ fi
30
+ if [[ "${has_tool}" -eq 0 ]]; then
31
+ echo "Warning: neither ss nor lsof available, cannot inspect port ${port}." >&2
32
+ fi
33
+
34
+ echo "${pids}"
35
+ }
36
+
17
37
  kill_port_if_listening() {
18
38
  local pids
19
- 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)
39
+ pids=$(list_port_pids "${DEPLOY_RUN_PORT}")
20
40
  if [[ -z "${pids}" ]]; then
21
41
  echo "Port ${DEPLOY_RUN_PORT} is free."
22
42
  return
23
43
  fi
24
44
  echo "Port ${DEPLOY_RUN_PORT} in use by PIDs: ${pids} (SIGKILL)"
25
- echo "${pids}" | xargs -I {} kill -9 {}
45
+ echo "${pids}" | xargs -I {} kill -9 {} || true
26
46
  sleep 1
27
- 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)
47
+ pids=$(list_port_pids "${DEPLOY_RUN_PORT}")
28
48
  if [[ -n "${pids}" ]]; then
29
- echo "Warning: port ${DEPLOY_RUN_PORT} still busy after SIGKILL, PIDs: ${pids}"
30
- else
31
- echo "Port ${DEPLOY_RUN_PORT} cleared."
49
+ echo "端口 ${DEPLOY_RUN_PORT} PID ${pids} 占用且无法清理(SIGKILL 后仍在监听),dev server 无法启动。" >&2
50
+ exit 1
32
51
  fi
52
+ echo "Port ${DEPLOY_RUN_PORT} cleared."
33
53
  }
34
54
 
35
55
  <% if (process.env.NODE_ENV === 'test') { %>
@@ -44,8 +64,6 @@ LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
44
64
  LOG_FILE="${LOG_DIR}/server.log"
45
65
  PID_FILE="${LOG_DIR}/server.pid"
46
66
 
47
- # coze-daemon 会在 runtime shell 退出时清理原进程组。
48
- # 通过 Node detached spawn 创建独立 session/进程组,并将 stdio 直接写入日志。
49
67
  spawn_detached() {
50
68
  local cwd="$1"
51
69
  local log_file="$2"
@@ -87,6 +105,89 @@ stop_detached() {
87
105
  kill -KILL -- "-${pid}" 2>/dev/null || true
88
106
  }
89
107
 
108
+ READY_RETRIES=10
109
+ READY_PROBE_HOSTS=("127.0.0.1" "::1")
110
+ LOG_TAIL_LINES=40
111
+
112
+ dump_log() {
113
+ echo "---- tail -n ${LOG_TAIL_LINES} ${LOG_FILE} ----" >&2
114
+ tail -n "${LOG_TAIL_LINES}" "${LOG_FILE}" >&2 || true
115
+ echo "---- end of ${LOG_FILE} ----" >&2
116
+ }
117
+
118
+ # 仅用于无 ss/lsof 时的兜底探测。服务可能只 bind IPv4 loopback、只 bind IPv6 loopback
119
+ # (server.listen(port, 'localhost') 会变成 ::1 独占),或 bind 双栈通配地址,
120
+ port_connectable() {
121
+ local port=$1
122
+ local host
123
+ for host in "${READY_PROBE_HOSTS[@]}"; do
124
+ if command -v nc >/dev/null 2>&1; then
125
+ if nc -z -w 1 "${host}" "${port}" >/dev/null 2>&1; then
126
+ return 0
127
+ fi
128
+ elif (exec 3<>"/dev/tcp/${host}/${port}") >/dev/null 2>&1; then
129
+ return 0
130
+ fi
131
+ done
132
+ return 1
133
+ }
134
+
135
+ port_probe_available() {
136
+ command -v ss >/dev/null 2>&1 || command -v lsof >/dev/null 2>&1
137
+ }
138
+
139
+ # 端口监听者是否属于本进程组:spawn detached 后 PID == PGID,子孙进程继承该 PGID。
140
+ # 0 = 是;1 = 不是(无人监听,或监听者不属于本进程组);2 = 无探测工具,无法判断
141
+ port_listened_by_pgid() {
142
+ local port=$1 pgid=$2
143
+ local listener owner
144
+
145
+ if ! port_probe_available; then
146
+ return 2
147
+ fi
148
+
149
+ for listener in $(list_port_pids "${port}" 2>/dev/null); do
150
+ owner=$(ps -o pgid= -p "${listener}" 2>/dev/null | tr -d ' ')
151
+ if [[ "${owner}" == "${pgid}" ]]; then
152
+ return 0
153
+ fi
154
+ done
155
+ return 1
156
+ }
157
+
158
+ wait_for_ready() {
159
+ local pid=$1 port=$2
160
+ local attempt=0 owned
161
+
162
+ if ! port_probe_available; then
163
+ echo "Warning: 缺少 ss 与 lsof,无法确认端口监听者归属,仅按 loopback 可连接性判断就绪。" >&2
164
+ fi
165
+
166
+ while [[ "${attempt}" -lt "${READY_RETRIES}" ]]; do
167
+ if ! kill -0 "${pid}" 2>/dev/null; then
168
+ echo "Dev server 进程在启动过程中退出。" >&2
169
+ return 1
170
+ fi
171
+
172
+ if port_listened_by_pgid "${port}" "${pid}"; then
173
+ owned=0
174
+ else
175
+ owned=$?
176
+ fi
177
+ if [[ "${owned}" -eq 0 ]]; then
178
+ return 0
179
+ fi
180
+ if [[ "${owned}" -eq 2 ]] && port_connectable "${port}"; then
181
+ return 0
182
+ fi
183
+
184
+ sleep 1
185
+ attempt=$((attempt + 1))
186
+ done
187
+
188
+ return 2
189
+ }
190
+
90
191
  # 监听端口的是孙进程(pnpm -> tsx -> node),只清端口会漏掉上层 pnpm/tsx,
91
192
  # 所以先按上次记录的 PID 把整个进程组回收掉。
92
193
  if [[ -f "${PID_FILE}" ]]; then
@@ -104,17 +205,33 @@ mkdir -p "${LOG_DIR}"
104
205
  export PORT="${DEPLOY_RUN_PORT}"
105
206
  server_pid="$(spawn_detached "${COZE_WORKSPACE_PATH}" "${LOG_FILE}" \
106
207
  "$(command -v pnpm)" tsx watch src/server.ts)"
208
+ if [[ -z "${server_pid}" ]]; then
209
+ echo "Dev server failed to start: 未获取到后台进程 PID。" >&2
210
+ dump_log
211
+ exit 1
212
+ fi
107
213
  echo "${server_pid}" > "${PID_FILE}"
108
214
 
109
- sleep 1
110
- if [[ -z "${server_pid}" ]] || ! kill -0 "${server_pid}" 2>/dev/null; then
215
+ if wait_for_ready "${server_pid}" "${DEPLOY_RUN_PORT}"; then
216
+ ready_status=0
217
+ else
218
+ ready_status=$?
219
+ fi
220
+
221
+ if [[ "${ready_status}" -eq 2 ]]; then
222
+ # 进程还活着但迟迟没监听端口:多半是编译/运行时报错后 tsx watch 驻留,日志里有真正原因。
223
+ echo "Dev server 在 ${READY_RETRIES} 次探测(每次 1s)内未就绪于端口 ${DEPLOY_RUN_PORT}(进程 ${server_pid} 仍在运行)。" >&2
224
+ dump_log
225
+ exit 1
226
+ elif [[ "${ready_status}" -ne 0 ]]; then
111
227
  echo "Dev server failed to start. See ${LOG_FILE}." >&2
112
- tail -n 20 "${LOG_FILE}" >&2 || true
228
+ dump_log
229
+ stop_detached "${server_pid}"
113
230
  rm -f "${PID_FILE}"
114
231
  exit 1
115
232
  fi
116
233
 
117
- echo "Dev server started (PID: ${server_pid})."
234
+ echo "Dev server started (PID: ${server_pid}), listening on port ${DEPLOY_RUN_PORT}."
118
235
  echo "Log file: ${LOG_FILE}"
119
236
  echo "PID file: ${PID_FILE}"
120
237
  <% } %>