@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 +3 -1
- package/docs/deploy.md +9 -2
- package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +6 -1
- package/lib/__templates__/nextjs/scripts/dev.sh +129 -12
- package/lib/cli.js +327 -162
- package/package.json +1 -1
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
|
|
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`
|
|
4
|
-
|
|
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
|
-
|
|
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=$(
|
|
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=$(
|
|
47
|
+
pids=$(list_port_pids "${DEPLOY_RUN_PORT}")
|
|
28
48
|
if [[ -n "${pids}" ]]; then
|
|
29
|
-
echo "
|
|
30
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
<% } %>
|