@coze-arch/cli 0.1.5 → 0.1.8-alpha.4500b5

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.
Files changed (57) hide show
  1. package/docs/deploy.md +12 -1
  2. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +3 -3
  3. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +11 -3
  4. package/lib/__templates__/expo/.cozeproj/scripts/prepare-node-modules.sh +212 -0
  5. package/lib/__templates__/expo/.cozeproj/scripts/prod_build.sh +3 -3
  6. package/lib/__templates__/expo/client/components/Screen.tsx +65 -25
  7. package/lib/__templates__/expo/client/heroui/components/toast/toast.tsx +6 -2
  8. package/lib/__templates__/expo/client/heroui/helpers/internal/hooks/use-controllable-state.ts +1 -1
  9. package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
  10. package/lib/__templates__/expo/server/package.json +1 -1
  11. package/lib/__templates__/native-static/.coze +2 -2
  12. package/lib/__templates__/nextjs/eslint.config.mjs +11 -5
  13. package/lib/__templates__/nextjs/next.config.ts +10 -0
  14. package/lib/__templates__/nextjs/package.json +3 -6
  15. package/lib/__templates__/nextjs/pnpm-lock.yaml +19 -950
  16. package/lib/__templates__/nextjs/scripts/build.ps1 +1 -1
  17. package/lib/__templates__/nextjs/scripts/build.sh +2 -2
  18. package/lib/__templates__/nextjs/scripts/dev.ps1 +2 -2
  19. package/lib/__templates__/nextjs/scripts/dev.sh +86 -10
  20. package/lib/__templates__/nextjs/scripts/prepare-node-modules.sh +212 -0
  21. package/lib/__templates__/nextjs/scripts/prepare.sh +1 -4
  22. package/lib/__templates__/nextjs/src/app/layout.tsx +0 -4
  23. package/lib/__templates__/nextjs/src/server.ts +1 -1
  24. package/lib/__templates__/nextjs/template.config.js +1 -1
  25. package/lib/__templates__/nuxt-vue/package.json +1 -1
  26. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +5 -5
  27. package/lib/__templates__/nuxt-vue/scripts/build.sh +1 -1
  28. package/lib/__templates__/nuxt-vue/scripts/dev.sh +11 -3
  29. package/lib/__templates__/nuxt-vue/scripts/prepare-node-modules.sh +212 -0
  30. package/lib/__templates__/nuxt-vue/scripts/prepare.sh +1 -4
  31. package/lib/__templates__/pi-agent/package.json +1 -1
  32. package/lib/__templates__/pi-agent/pnpm-lock.yaml +5 -5
  33. package/lib/__templates__/taro/.cozeproj/scripts/deploy_build.sh +5 -1
  34. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +4 -0
  35. package/lib/__templates__/taro/.cozeproj/scripts/dev_build.sh +3 -0
  36. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.ps1 +4 -2
  37. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +29 -7
  38. package/lib/__templates__/taro/.cozeproj/scripts/init_env.sh +4 -0
  39. package/lib/__templates__/taro/.cozeproj/scripts/pack.ps1 +3 -1
  40. package/lib/__templates__/taro/.cozeproj/scripts/pack.sh +8 -0
  41. package/lib/__templates__/taro/.cozeproj/scripts/prepare-node-modules.sh +212 -0
  42. package/lib/__templates__/taro/.cozeproj/scripts/validate.ps1 +3 -1
  43. package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +4 -0
  44. package/lib/__templates__/taro/_gitignore +1 -0
  45. package/lib/__templates__/taro/pnpm-lock.yaml +5 -5
  46. package/lib/__templates__/taro/server/package.json +1 -1
  47. package/lib/__templates__/vite/package.json +1 -1
  48. package/lib/__templates__/vite/pnpm-lock.yaml +5 -5
  49. package/lib/__templates__/vite/scripts/build.sh +1 -1
  50. package/lib/__templates__/vite/scripts/dev.ps1 +2 -2
  51. package/lib/__templates__/vite/scripts/dev.sh +11 -3
  52. package/lib/__templates__/vite/scripts/prepare-node-modules.sh +212 -0
  53. package/lib/__templates__/vite/scripts/prepare.sh +1 -4
  54. package/lib/__templates__/vite/template.config.js +1 -1
  55. package/lib/cli.js +147 -16
  56. package/package.json +2 -2
  57. package/lib/__templates__/nextjs/.babelrc +0 -15
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env bash
2
+ # Install dependencies on local disk and expose them to the source tree by symlink.
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
6
+ if [ "$(basename "$(dirname "$SCRIPT_DIR")")" = ".cozeproj" ]; then
7
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd -P)"
8
+ else
9
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
10
+ fi
11
+
12
+ command -v pnpm >/dev/null 2>&1 || {
13
+ echo "[node_modules] pnpm is required" >&2
14
+ exit 1
15
+ }
16
+
17
+ DRIVE_ROOT="${COZE_DRIVE_ROOT:-/Coze/Drive}"
18
+ if [ -d "$DRIVE_ROOT" ]; then
19
+ DRIVE_ROOT="$(cd "$DRIVE_ROOT" && pwd -P)"
20
+ else
21
+ DRIVE_ROOT="${DRIVE_ROOT%/}"
22
+ fi
23
+ case "$PROJECT_ROOT" in
24
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*) ;;
25
+ *)
26
+ (cd "$PROJECT_ROOT" && pnpm install "$@")
27
+ exit 0
28
+ ;;
29
+ esac
30
+
31
+ NM_ROOT="/tmp/nm"
32
+ PROJECT_ID="$(basename "$PROJECT_ROOT")-$(printf '%s' "$PROJECT_ROOT" | cksum | awk '{print $1}')"
33
+ mkdir -p "$NM_ROOT"
34
+ WORK_DIR="$NM_ROOT/$PROJECT_ID"
35
+ case "$WORK_DIR" in
36
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*)
37
+ echo "[node_modules] local dependency directory must not be on Coze Drive: $WORK_DIR" >&2
38
+ exit 1
39
+ ;;
40
+ esac
41
+
42
+ LOCK_DIR="$NM_ROOT/$PROJECT_ID.lock"
43
+ LOCK_TIMEOUT_SECONDS="${COZE_NODE_MODULES_LOCK_TIMEOUT_SECONDS:-120}"
44
+ case "$LOCK_TIMEOUT_SECONDS" in
45
+ ''|*[!0-9]*|0)
46
+ echo "[node_modules] COZE_NODE_MODULES_LOCK_TIMEOUT_SECONDS must be a positive integer" >&2
47
+ exit 1
48
+ ;;
49
+ esac
50
+
51
+ lock_owner_is_alive() {
52
+ local owner_pid=""
53
+ [ -f "$LOCK_DIR/pid" ] || return 1
54
+ IFS= read -r owner_pid < "$LOCK_DIR/pid" || return 1
55
+ case "$owner_pid" in
56
+ ''|*[!0-9]*) return 1 ;;
57
+ esac
58
+ kill -0 "$owner_pid" 2>/dev/null
59
+ }
60
+
61
+ release_lock() {
62
+ local owner_pid=""
63
+ [ -f "$LOCK_DIR/pid" ] || return 0
64
+ IFS= read -r owner_pid < "$LOCK_DIR/pid" || true
65
+ if [ "$owner_pid" = "$$" ]; then
66
+ rm -f "$LOCK_DIR/pid"
67
+ rmdir "$LOCK_DIR" 2>/dev/null || true
68
+ fi
69
+ }
70
+
71
+ lock_started_at="$(date +%s)"
72
+ missing_owner_checks=0
73
+ while ! mkdir "$LOCK_DIR" 2>/dev/null; do
74
+ if lock_owner_is_alive; then
75
+ missing_owner_checks=0
76
+ else
77
+ missing_owner_checks=$((missing_owner_checks + 1))
78
+ if [ "$missing_owner_checks" -ge 2 ]; then
79
+ stale_lock_dir="$LOCK_DIR.stale.$$"
80
+ if mv "$LOCK_DIR" "$stale_lock_dir" 2>/dev/null; then
81
+ echo "[node_modules] Recovering stale dependency preparation lock."
82
+ rm -rf "$stale_lock_dir"
83
+ fi
84
+ missing_owner_checks=0
85
+ continue
86
+ fi
87
+ fi
88
+
89
+ lock_now="$(date +%s)"
90
+ if [ $((lock_now - lock_started_at)) -ge "$LOCK_TIMEOUT_SECONDS" ]; then
91
+ echo "[node_modules] Timed out waiting ${LOCK_TIMEOUT_SECONDS}s for dependency preparation lock: $LOCK_DIR" >&2
92
+ exit 1
93
+ fi
94
+ echo "[node_modules] Waiting for another dependency preparation for this project..."
95
+ sleep 1
96
+ done
97
+ printf '%s\n' "$$" > "$LOCK_DIR/pid"
98
+ trap release_lock EXIT
99
+
100
+ mkdir -p "$WORK_DIR"
101
+
102
+ # Previous versions mirrored the whole project into WORK_DIR. That gives pnpm
103
+ # and framework dev servers two paths for the same source tree, which breaks
104
+ # file watching and on-demand compilation. Keep only managed local outputs.
105
+ cleanup_legacy_workspace() {
106
+ local workspace_dir="$1" entry name
107
+ if [ -L "$workspace_dir" ] || { [ -e "$workspace_dir" ] && [ ! -d "$workspace_dir" ]; }; then
108
+ rm -rf "$workspace_dir"
109
+ return
110
+ fi
111
+ if [ ! -d "$workspace_dir" ]; then
112
+ return 0
113
+ fi
114
+
115
+ while IFS= read -r -d '' entry; do
116
+ name="$(basename "$entry")"
117
+ [ "$name" = "node_modules" ] || rm -rf "$entry"
118
+ done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
119
+ }
120
+
121
+ cleanup_legacy_shadow_project() {
122
+ local entry name
123
+ while IFS= read -r -d '' entry; do
124
+ name="$(basename "$entry")"
125
+ case "$name" in
126
+ node_modules|client|server) continue ;;
127
+ esac
128
+ rm -rf "$entry"
129
+ done < <(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -print0)
130
+
131
+ for workspace in client server; do
132
+ cleanup_legacy_workspace "$WORK_DIR/$workspace"
133
+ done
134
+ }
135
+
136
+ link_install_entry() {
137
+ local source_path="$1" target_path="$2"
138
+ rm -rf "$target_path"
139
+ if [ -e "$source_path" ]; then
140
+ ln -s "$source_path" "$target_path"
141
+ fi
142
+ }
143
+
144
+ copy_install_metadata() {
145
+ local filename source_file target_file workspace
146
+ for filename in pnpm-lock.yaml pnpm-workspace.yaml .npmrc; do
147
+ source_file="$PROJECT_ROOT/$filename"
148
+ target_file="$WORK_DIR/$filename"
149
+ rm -f "$target_file"
150
+ if [ -f "$source_file" ]; then
151
+ cp "$source_file" "$target_file"
152
+ fi
153
+ done
154
+
155
+ link_install_entry "$PROJECT_ROOT/package.json" "$WORK_DIR/package.json"
156
+ link_install_entry "$PROJECT_ROOT/patches" "$WORK_DIR/patches"
157
+ link_install_entry "$PROJECT_ROOT/scripts" "$WORK_DIR/scripts"
158
+
159
+ for workspace in client server; do
160
+ if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
161
+ mkdir -p "$WORK_DIR/$workspace"
162
+ link_install_entry "$PROJECT_ROOT/$workspace/package.json" "$WORK_DIR/$workspace/package.json"
163
+ link_install_entry "$PROJECT_ROOT/$workspace/scripts" "$WORK_DIR/$workspace/scripts"
164
+ else
165
+ rm -rf "$WORK_DIR/$workspace"
166
+ fi
167
+ done
168
+ }
169
+
170
+ link_node_modules() {
171
+ local source_dir="$1" target_dir="$2"
172
+ local current=""
173
+ mkdir -p "$target_dir"
174
+ if [ -L "$source_dir/node_modules" ]; then
175
+ current="$(readlink "$source_dir/node_modules")"
176
+ case "$current" in
177
+ "$NM_ROOT"/*/node_modules|"$NM_ROOT"/*/*/node_modules) ;;
178
+ *)
179
+ echo "[node_modules] refusing to replace unmanaged symlink: $source_dir/node_modules" >&2
180
+ exit 1
181
+ ;;
182
+ esac
183
+ rm "$source_dir/node_modules"
184
+ elif [ -e "$source_dir/node_modules" ]; then
185
+ rm -rf "$source_dir/node_modules"
186
+ fi
187
+ ln -s "$target_dir" "$source_dir/node_modules"
188
+ }
189
+
190
+ cleanup_legacy_shadow_project
191
+ copy_install_metadata
192
+
193
+ echo "[node_modules] Installing dependencies in $WORK_DIR"
194
+ if ! (cd "$WORK_DIR" && pnpm install "$@"); then
195
+ exit 1
196
+ fi
197
+
198
+ if [ -f "$WORK_DIR/pnpm-lock.yaml" ]; then
199
+ temporary_lockfile="$(mktemp "$PROJECT_ROOT/.pnpm-lock.yaml.XXXXXX")"
200
+ cp "$WORK_DIR/pnpm-lock.yaml" "$temporary_lockfile"
201
+ chmod 0644 "$temporary_lockfile"
202
+ mv -f "$temporary_lockfile" "$PROJECT_ROOT/pnpm-lock.yaml"
203
+ fi
204
+
205
+ link_node_modules "$PROJECT_ROOT" "$WORK_DIR/node_modules"
206
+ for workspace in client server; do
207
+ if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
208
+ link_node_modules "$PROJECT_ROOT/$workspace" "$WORK_DIR/$workspace/node_modules"
209
+ fi
210
+ done
211
+
212
+ echo "[node_modules] Dependencies ready"
@@ -6,10 +6,7 @@ COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-$(pwd)}"
6
6
  cd "${COZE_WORKSPACE_PATH}"
7
7
 
8
8
  echo "Installing dependencies..."
9
- pnpm install --prefer-frozen-lockfile --prefer-offline
10
- if command -v coze-dev > /dev/null 2>&1 && coze-dev check-bins --help > /dev/null 2>&1; then
11
- coze-dev check-bins --fix
12
- fi
9
+ bash "$COZE_WORKSPACE_PATH/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
13
10
 
14
11
  echo "Preparing Nuxt project..."
15
12
  pnpm exec nuxt prepare
@@ -22,7 +22,7 @@
22
22
  "@mariozechner/pi-ai": "^0.63.2",
23
23
  "@mariozechner/pi-coding-agent": "^0.63.2",
24
24
  "@sinclair/typebox": "^0.34.48",
25
- "coze-coding-dev-sdk": "^0.7.28",
25
+ "coze-coding-dev-sdk": "^0.7.30",
26
26
  "@radix-ui/react-select": "^2.2.6",
27
27
  "@radix-ui/react-slot": "^1.2.4",
28
28
  "@streamdown/code": "^1.1.1",
@@ -36,8 +36,8 @@ importers:
36
36
  specifier: ^2.1.1
37
37
  version: 2.1.1
38
38
  coze-coding-dev-sdk:
39
- specifier: ^0.7.28
40
- version: 0.7.28(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
39
+ specifier: ^0.7.30
40
+ version: 0.7.30(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
41
41
  express:
42
42
  specifier: ^4.21.2
43
43
  version: 4.22.1
@@ -2085,8 +2085,8 @@ packages:
2085
2085
  cose-base@2.2.0:
2086
2086
  resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
2087
2087
 
2088
- coze-coding-dev-sdk@0.7.28:
2089
- resolution: {integrity: sha512-ku7AiVWUmgCrUuMT1m2iTmHIwG39EfmcAhPASd8M7XyKxIC6A1cjIIe/3YpStRsj8pMF9LU+pxHWxisf+F44oQ==}
2088
+ coze-coding-dev-sdk@0.7.30:
2089
+ resolution: {integrity: sha512-BhyMFh7zZPFeN9VRxvqPJ5NS8WO+MJD/bVdzXkuJivCgvc4e2BnpT895rGodLV0TvgJUYYk2O0XJOZSU1OFRyA==}
2090
2090
  engines: {node: '>=18.0.0'}
2091
2091
  hasBin: true
2092
2092
 
@@ -6120,7 +6120,7 @@ snapshots:
6120
6120
  dependencies:
6121
6121
  layout-base: 2.0.1
6122
6122
 
6123
- coze-coding-dev-sdk@0.7.28(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0):
6123
+ coze-coding-dev-sdk@0.7.30(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0):
6124
6124
  dependencies:
6125
6125
  '@langchain/core': 1.1.40(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
6126
6126
  '@langchain/openai': 1.4.3(@langchain/core@1.1.40(openai@6.34.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))(ws@8.20.0)
@@ -1,6 +1,10 @@
1
1
  #!/bin/bash
2
2
  set -Eeuo pipefail
3
3
 
4
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
+ COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
6
+ export COZE_WORKSPACE_PATH
7
+
4
8
  cd "${COZE_WORKSPACE_PATH}"
5
9
  if [ -f "./.cozeproj/scripts/init_env.sh" ]; then
6
10
  echo "⚙️ Initializing environment..."
@@ -11,7 +15,7 @@ else
11
15
  fi
12
16
  echo "Installing dependencies..."
13
17
  # 安装所有依赖(包含 Taro 核心和 React)
14
- pnpm install
18
+ bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
15
19
 
16
20
  echo "Building the Taro project..."
17
21
  pnpm build
@@ -1,6 +1,10 @@
1
1
  #!/bin/bash
2
2
  set -Eeuo pipefail
3
3
 
4
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
+ COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
6
+ export COZE_WORKSPACE_PATH
7
+
4
8
  start_service() {
5
9
  cd "${COZE_WORKSPACE_PATH}/server/dist"
6
10
 
@@ -1,2 +1,5 @@
1
1
  #!/bin/bash
2
2
  set -Eeuo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
+ bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
@@ -28,7 +28,9 @@ function Start-DevProcess([string] $workingDirectory, [string] $logFile, [hashta
28
28
  }
29
29
  }
30
30
 
31
- $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { (Get-Location).Path }
31
+ $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path
32
+ $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { $projectRoot }
33
+ $env:COZE_WORKSPACE_PATH = $workspace
32
34
  $port = if ($env:DEPLOY_RUN_PORT) { [int] $env:DEPLOY_RUN_PORT } elseif ($env:PORT) { [int] $env:PORT } else { <%= port %> }
33
35
  $serverPort = if ($env:SERVER_PORT) { [int] $env:SERVER_PORT } else { <%= serverPort %> }
34
36
  $logDirectory = if ($env:COZE_LOG_DIR) { $env:COZE_LOG_DIR } else { Join-Path $workspace "logs" }
@@ -43,7 +45,7 @@ if (Test-Path $pidFile) {
43
45
  Stop-ListeningProcess $port
44
46
  Stop-ListeningProcess $serverPort
45
47
 
46
- $environment = @{ PORT = "$port"; SERVER_PORT = "$serverPort" }
48
+ $environment = @{ COZE_WORKSPACE_PATH = $workspace; PORT = "$port"; SERVER_PORT = "$serverPort" }
47
49
  if ($env:COZE_PROJECT_DOMAIN_DEFAULT) {
48
50
  $environment.PROJECT_DOMAIN = $env:COZE_PROJECT_DOMAIN_DEFAULT
49
51
  }
@@ -4,6 +4,7 @@ set -Eeuo pipefail
4
4
 
5
5
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
6
6
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
7
+ export COZE_WORKSPACE_PATH
7
8
  cd "${COZE_WORKSPACE_PATH}"
8
9
 
9
10
  # ---------------------------------------------------------
@@ -31,11 +32,15 @@ kill_process_tree() {
31
32
  }
32
33
 
33
34
  # detached 出去的进程没人负责回收,超过这个时长就自己退出,避免端口与内存长期泄露。
34
- MAX_RUNTIME_SECONDS=1200
35
+ MAX_RUNTIME_SECONDS=3600
36
+
37
+ timeout_watchdog_enabled() {
38
+ [[ -z "${COZE_EVAL:-}" && -z "${COZE_PROJECT_TYPE:-}" ]]
39
+ }
35
40
 
36
41
  # 真正被 detach 的是这层 bash wrapper:它是进程组 leader,组内 watchdog 到点回收整组
37
42
  # (wrapper -> pnpm -> taro/nest);被包的进程自己先退出时也顺手清空进程组,不留残余。
38
- RUN_WITH_TIMEOUT='
43
+ RUN_WITH_TIMEOUT="$(declare -f timeout_watchdog_enabled)"'
39
44
  timeout_seconds=$1
40
45
  shift
41
46
 
@@ -43,6 +48,7 @@ shift
43
48
  child_pid=$!
44
49
 
45
50
  # 先忽略 TERM,才能在向整组发 TERM(自己也在组里)之后存活下来补一发 KILL。
51
+ if timeout_watchdog_enabled; then
46
52
  ( trap "" TERM
47
53
  sleep "${timeout_seconds}"
48
54
  echo "[dev] 后台进程运行超过 ${timeout_seconds}s,回收进程组 $$。"
@@ -50,6 +56,7 @@ child_pid=$!
50
56
  sleep 5
51
57
  kill -KILL -- "-$$" 2>/dev/null || true
52
58
  ) &
59
+ fi
53
60
 
54
61
  wait "${child_pid}"
55
62
  kill -KILL -- "-$$" 2>/dev/null || true
@@ -155,12 +162,24 @@ list_port_pids() {
155
162
 
156
163
  kill_port_if_listening() {
157
164
  local port=$1
165
+ # 用于错误提示的端口环境变量名称,例如 DEPLOY_RUN_PORT 或 SERVER_PORT。
166
+ local port_env_name=$2
158
167
  local pids
168
+ local pid
159
169
  pids=$(list_port_pids "${port}")
160
170
  if [[ -z "${pids}" ]]; then
161
171
  echo "Port ${port} is free."
162
172
  return
163
173
  fi
174
+
175
+ # 端口可能由另一个项目占用。必须在杀进程前校验全部 PID,避免清理到一半才发现外部进程。
176
+ for pid in ${pids}; do
177
+ if ! process_belongs_to_workspace "${pid}"; then
178
+ echo "Error: port ${port} is occupied by PID ${pid}, which does not belong to workspace ${COZE_WORKSPACE_PATH}. Refusing to kill it. Set ${port_env_name} to another port and retry." >&2
179
+ return 1
180
+ fi
181
+ done
182
+
164
183
  echo "Port ${port} in use by PIDs: ${pids} (SIGKILL)"
165
184
  for pid in ${pids}; do
166
185
  kill_process_tree "${pid}"
@@ -168,7 +187,8 @@ kill_port_if_listening() {
168
187
  sleep 1
169
188
  pids=$(list_port_pids "${port}")
170
189
  if [[ -n "${pids}" ]]; then
171
- echo "Warning: port ${port} still busy after SIGKILL, PIDs: ${pids}"
190
+ echo "Error: port ${port} is still occupied after cleaning up this project's processes, PIDs: ${pids}. Check the remaining processes manually or set ${port_env_name} to another port and retry." >&2
191
+ return 1
172
192
  else
173
193
  echo "Port ${port} cleared."
174
194
  fi
@@ -194,7 +214,7 @@ cleanup_previous_run() {
194
214
  # ---------------------------------------------------------
195
215
  echo "📦 Installing dependencies..."
196
216
  PNPM_BIN="$(command -v pnpm)"
197
- "${PNPM_BIN}" install
217
+ bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
198
218
  echo "✅ Dependencies installed successfully!"
199
219
 
200
220
  # ---------------------------------------------------------
@@ -210,9 +230,9 @@ mkdir -p "${LOG_DIR}"
210
230
  cleanup_previous_run
211
231
 
212
232
  echo "Clearing port ${PORT} (web) before start."
213
- kill_port_if_listening "${PORT}"
233
+ kill_port_if_listening "${PORT}" "DEPLOY_RUN_PORT"
214
234
  echo "Clearing port ${SERVER_PORT} (server) before start."
215
- kill_port_if_listening "${SERVER_PORT}"
235
+ kill_port_if_listening "${SERVER_PORT}" "SERVER_PORT"
216
236
 
217
237
  # ---------------------------------------------------------
218
238
  # 4. 启动服务
@@ -252,7 +272,9 @@ start_service() {
252
272
  fi
253
273
 
254
274
  echo "📝 Dev process started with PID: ${DEV_PID}"
255
- echo "Auto stop after ${MAX_RUNTIME_SECONDS}s."
275
+ if timeout_watchdog_enabled; then
276
+ echo "Auto stop after ${MAX_RUNTIME_SECONDS}s."
277
+ fi
256
278
  echo "Log file: ${LOG_FILE}"
257
279
  echo "PID file: ${PID_FILE}"
258
280
  }
@@ -1,5 +1,9 @@
1
1
  #!/bin/bash
2
2
 
3
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
4
+ COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
5
+ export COZE_WORKSPACE_PATH
6
+
3
7
  cd "${COZE_WORKSPACE_PATH}"
4
8
 
5
9
  echo "✅ 初始化完成"
@@ -1,6 +1,8 @@
1
1
  $ErrorActionPreference = "Stop"
2
2
 
3
- $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { (Get-Location).Path }
3
+ $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot "../..")).Path
4
+ $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { $projectRoot }
5
+ $env:COZE_WORKSPACE_PATH = $workspace
4
6
  Set-Location $workspace
5
7
 
6
8
  & pnpm build:pack
@@ -1,3 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
4
+ COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
5
+ export COZE_WORKSPACE_PATH
6
+
7
+ cd "${COZE_WORKSPACE_PATH}"
8
+
1
9
  # build_weapp.sh - 通过 PID 文件精确杀掉自己上次的构建进程
2
10
  PID_FILE="/tmp/coze-build_weapp.pid"
3
11
 
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env bash
2
+ # Install dependencies on local disk and expose them to the source tree by symlink.
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
6
+ if [ "$(basename "$(dirname "$SCRIPT_DIR")")" = ".cozeproj" ]; then
7
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd -P)"
8
+ else
9
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
10
+ fi
11
+
12
+ command -v pnpm >/dev/null 2>&1 || {
13
+ echo "[node_modules] pnpm is required" >&2
14
+ exit 1
15
+ }
16
+
17
+ DRIVE_ROOT="${COZE_DRIVE_ROOT:-/Coze/Drive}"
18
+ if [ -d "$DRIVE_ROOT" ]; then
19
+ DRIVE_ROOT="$(cd "$DRIVE_ROOT" && pwd -P)"
20
+ else
21
+ DRIVE_ROOT="${DRIVE_ROOT%/}"
22
+ fi
23
+ case "$PROJECT_ROOT" in
24
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*) ;;
25
+ *)
26
+ (cd "$PROJECT_ROOT" && pnpm install "$@")
27
+ exit 0
28
+ ;;
29
+ esac
30
+
31
+ NM_ROOT="/tmp/nm"
32
+ PROJECT_ID="$(basename "$PROJECT_ROOT")-$(printf '%s' "$PROJECT_ROOT" | cksum | awk '{print $1}')"
33
+ mkdir -p "$NM_ROOT"
34
+ WORK_DIR="$NM_ROOT/$PROJECT_ID"
35
+ case "$WORK_DIR" in
36
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*)
37
+ echo "[node_modules] local dependency directory must not be on Coze Drive: $WORK_DIR" >&2
38
+ exit 1
39
+ ;;
40
+ esac
41
+
42
+ LOCK_DIR="$NM_ROOT/$PROJECT_ID.lock"
43
+ LOCK_TIMEOUT_SECONDS="${COZE_NODE_MODULES_LOCK_TIMEOUT_SECONDS:-120}"
44
+ case "$LOCK_TIMEOUT_SECONDS" in
45
+ ''|*[!0-9]*|0)
46
+ echo "[node_modules] COZE_NODE_MODULES_LOCK_TIMEOUT_SECONDS must be a positive integer" >&2
47
+ exit 1
48
+ ;;
49
+ esac
50
+
51
+ lock_owner_is_alive() {
52
+ local owner_pid=""
53
+ [ -f "$LOCK_DIR/pid" ] || return 1
54
+ IFS= read -r owner_pid < "$LOCK_DIR/pid" || return 1
55
+ case "$owner_pid" in
56
+ ''|*[!0-9]*) return 1 ;;
57
+ esac
58
+ kill -0 "$owner_pid" 2>/dev/null
59
+ }
60
+
61
+ release_lock() {
62
+ local owner_pid=""
63
+ [ -f "$LOCK_DIR/pid" ] || return 0
64
+ IFS= read -r owner_pid < "$LOCK_DIR/pid" || true
65
+ if [ "$owner_pid" = "$$" ]; then
66
+ rm -f "$LOCK_DIR/pid"
67
+ rmdir "$LOCK_DIR" 2>/dev/null || true
68
+ fi
69
+ }
70
+
71
+ lock_started_at="$(date +%s)"
72
+ missing_owner_checks=0
73
+ while ! mkdir "$LOCK_DIR" 2>/dev/null; do
74
+ if lock_owner_is_alive; then
75
+ missing_owner_checks=0
76
+ else
77
+ missing_owner_checks=$((missing_owner_checks + 1))
78
+ if [ "$missing_owner_checks" -ge 2 ]; then
79
+ stale_lock_dir="$LOCK_DIR.stale.$$"
80
+ if mv "$LOCK_DIR" "$stale_lock_dir" 2>/dev/null; then
81
+ echo "[node_modules] Recovering stale dependency preparation lock."
82
+ rm -rf "$stale_lock_dir"
83
+ fi
84
+ missing_owner_checks=0
85
+ continue
86
+ fi
87
+ fi
88
+
89
+ lock_now="$(date +%s)"
90
+ if [ $((lock_now - lock_started_at)) -ge "$LOCK_TIMEOUT_SECONDS" ]; then
91
+ echo "[node_modules] Timed out waiting ${LOCK_TIMEOUT_SECONDS}s for dependency preparation lock: $LOCK_DIR" >&2
92
+ exit 1
93
+ fi
94
+ echo "[node_modules] Waiting for another dependency preparation for this project..."
95
+ sleep 1
96
+ done
97
+ printf '%s\n' "$$" > "$LOCK_DIR/pid"
98
+ trap release_lock EXIT
99
+
100
+ mkdir -p "$WORK_DIR"
101
+
102
+ # Previous versions mirrored the whole project into WORK_DIR. That gives pnpm
103
+ # and framework dev servers two paths for the same source tree, which breaks
104
+ # file watching and on-demand compilation. Keep only managed local outputs.
105
+ cleanup_legacy_workspace() {
106
+ local workspace_dir="$1" entry name
107
+ if [ -L "$workspace_dir" ] || { [ -e "$workspace_dir" ] && [ ! -d "$workspace_dir" ]; }; then
108
+ rm -rf "$workspace_dir"
109
+ return
110
+ fi
111
+ if [ ! -d "$workspace_dir" ]; then
112
+ return 0
113
+ fi
114
+
115
+ while IFS= read -r -d '' entry; do
116
+ name="$(basename "$entry")"
117
+ [ "$name" = "node_modules" ] || rm -rf "$entry"
118
+ done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
119
+ }
120
+
121
+ cleanup_legacy_shadow_project() {
122
+ local entry name
123
+ while IFS= read -r -d '' entry; do
124
+ name="$(basename "$entry")"
125
+ case "$name" in
126
+ node_modules|client|server) continue ;;
127
+ esac
128
+ rm -rf "$entry"
129
+ done < <(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -print0)
130
+
131
+ for workspace in client server; do
132
+ cleanup_legacy_workspace "$WORK_DIR/$workspace"
133
+ done
134
+ }
135
+
136
+ link_install_entry() {
137
+ local source_path="$1" target_path="$2"
138
+ rm -rf "$target_path"
139
+ if [ -e "$source_path" ]; then
140
+ ln -s "$source_path" "$target_path"
141
+ fi
142
+ }
143
+
144
+ copy_install_metadata() {
145
+ local filename source_file target_file workspace
146
+ for filename in pnpm-lock.yaml pnpm-workspace.yaml .npmrc; do
147
+ source_file="$PROJECT_ROOT/$filename"
148
+ target_file="$WORK_DIR/$filename"
149
+ rm -f "$target_file"
150
+ if [ -f "$source_file" ]; then
151
+ cp "$source_file" "$target_file"
152
+ fi
153
+ done
154
+
155
+ link_install_entry "$PROJECT_ROOT/package.json" "$WORK_DIR/package.json"
156
+ link_install_entry "$PROJECT_ROOT/patches" "$WORK_DIR/patches"
157
+ link_install_entry "$PROJECT_ROOT/scripts" "$WORK_DIR/scripts"
158
+
159
+ for workspace in client server; do
160
+ if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
161
+ mkdir -p "$WORK_DIR/$workspace"
162
+ link_install_entry "$PROJECT_ROOT/$workspace/package.json" "$WORK_DIR/$workspace/package.json"
163
+ link_install_entry "$PROJECT_ROOT/$workspace/scripts" "$WORK_DIR/$workspace/scripts"
164
+ else
165
+ rm -rf "$WORK_DIR/$workspace"
166
+ fi
167
+ done
168
+ }
169
+
170
+ link_node_modules() {
171
+ local source_dir="$1" target_dir="$2"
172
+ local current=""
173
+ mkdir -p "$target_dir"
174
+ if [ -L "$source_dir/node_modules" ]; then
175
+ current="$(readlink "$source_dir/node_modules")"
176
+ case "$current" in
177
+ "$NM_ROOT"/*/node_modules|"$NM_ROOT"/*/*/node_modules) ;;
178
+ *)
179
+ echo "[node_modules] refusing to replace unmanaged symlink: $source_dir/node_modules" >&2
180
+ exit 1
181
+ ;;
182
+ esac
183
+ rm "$source_dir/node_modules"
184
+ elif [ -e "$source_dir/node_modules" ]; then
185
+ rm -rf "$source_dir/node_modules"
186
+ fi
187
+ ln -s "$target_dir" "$source_dir/node_modules"
188
+ }
189
+
190
+ cleanup_legacy_shadow_project
191
+ copy_install_metadata
192
+
193
+ echo "[node_modules] Installing dependencies in $WORK_DIR"
194
+ if ! (cd "$WORK_DIR" && pnpm install "$@"); then
195
+ exit 1
196
+ fi
197
+
198
+ if [ -f "$WORK_DIR/pnpm-lock.yaml" ]; then
199
+ temporary_lockfile="$(mktemp "$PROJECT_ROOT/.pnpm-lock.yaml.XXXXXX")"
200
+ cp "$WORK_DIR/pnpm-lock.yaml" "$temporary_lockfile"
201
+ chmod 0644 "$temporary_lockfile"
202
+ mv -f "$temporary_lockfile" "$PROJECT_ROOT/pnpm-lock.yaml"
203
+ fi
204
+
205
+ link_node_modules "$PROJECT_ROOT" "$WORK_DIR/node_modules"
206
+ for workspace in client server; do
207
+ if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
208
+ link_node_modules "$PROJECT_ROOT/$workspace" "$WORK_DIR/$workspace/node_modules"
209
+ fi
210
+ done
211
+
212
+ echo "[node_modules] Dependencies ready"