@coze-arch/cli 0.0.37 → 0.1.3-alpha.32023e

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 (169) hide show
  1. package/README.md +69 -104
  2. package/docs/deploy.md +181 -0
  3. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +7 -3
  4. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +108 -19
  5. package/lib/__templates__/expo/.cozeproj/scripts/prepare-node-modules.sh +142 -0
  6. package/lib/__templates__/expo/.cozeproj/scripts/prod_build.sh +3 -3
  7. package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +1 -1
  8. package/lib/__templates__/expo/.cozeproj/scripts/server_dev_run.sh +1 -1
  9. package/lib/__templates__/expo/client/metro.config.js +3 -1
  10. package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
  11. package/lib/__templates__/expo/server/package.json +2 -2
  12. package/lib/__templates__/expo/server/src/index.ts +1 -1
  13. package/lib/__templates__/expo/template.config.js +11 -2
  14. package/lib/__templates__/native-static/.coze +4 -2
  15. package/lib/__templates__/native-static/scripts/serve.ps1 +7 -0
  16. package/lib/__templates__/nextjs/.coze +5 -0
  17. package/lib/__templates__/nextjs/package.json +2 -2
  18. package/lib/__templates__/nextjs/pnpm-lock.yaml +57 -341
  19. package/lib/__templates__/nextjs/scripts/build.ps1 +16 -0
  20. package/lib/__templates__/nextjs/scripts/build.sh +1 -1
  21. package/lib/__templates__/nextjs/scripts/dev.ps1 +63 -0
  22. package/lib/__templates__/nextjs/scripts/dev.sh +234 -7
  23. package/lib/__templates__/nextjs/scripts/prepare-node-modules.sh +142 -0
  24. package/lib/__templates__/nextjs/scripts/prepare.ps1 +18 -0
  25. package/lib/__templates__/nextjs/scripts/prepare.sh +1 -4
  26. package/lib/__templates__/nextjs/scripts/start.ps1 +11 -0
  27. package/lib/__templates__/nextjs/scripts/validate.ps1 +8 -0
  28. package/lib/__templates__/nextjs/template.config.js +0 -57
  29. package/lib/__templates__/nuxt-vue/.coze +5 -0
  30. package/lib/__templates__/nuxt-vue/package.json +1 -1
  31. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +5 -5
  32. package/lib/__templates__/nuxt-vue/scripts/build.ps1 +12 -0
  33. package/lib/__templates__/nuxt-vue/scripts/build.sh +1 -1
  34. package/lib/__templates__/nuxt-vue/scripts/dev.ps1 +63 -0
  35. package/lib/__templates__/nuxt-vue/scripts/dev.sh +232 -3
  36. package/lib/__templates__/nuxt-vue/scripts/prepare-node-modules.sh +142 -0
  37. package/lib/__templates__/nuxt-vue/scripts/prepare.ps1 +12 -0
  38. package/lib/__templates__/nuxt-vue/scripts/prepare.sh +1 -4
  39. package/lib/__templates__/nuxt-vue/scripts/start.ps1 +11 -0
  40. package/lib/__templates__/nuxt-vue/scripts/validate.ps1 +8 -0
  41. package/lib/__templates__/phaser/.coze +18 -0
  42. package/lib/__templates__/phaser/README.md +45 -0
  43. package/lib/__templates__/phaser/_gitignore +23 -0
  44. package/lib/__templates__/phaser/_npmrc +14 -0
  45. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  46. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  47. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  48. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  49. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  50. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  51. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  52. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  53. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  54. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  55. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  56. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  57. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  58. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  59. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  60. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  61. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  62. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  63. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  64. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  65. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  66. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  67. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  68. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  69. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  70. package/lib/__templates__/phaser/debug-runtime/flow-graph.json +20 -0
  71. package/lib/__templates__/phaser/index.html +12 -0
  72. package/lib/__templates__/phaser/package.json +31 -0
  73. package/lib/__templates__/phaser/pnpm-lock.yaml +661 -0
  74. package/lib/__templates__/phaser/scripts/build.ps1 +12 -0
  75. package/lib/__templates__/phaser/scripts/build.sh +8 -0
  76. package/lib/__templates__/phaser/scripts/dev.ps1 +49 -0
  77. package/lib/__templates__/phaser/scripts/dev.sh +82 -0
  78. package/lib/__templates__/phaser/scripts/prepare.ps1 +8 -0
  79. package/lib/__templates__/phaser/scripts/prepare.sh +7 -0
  80. package/lib/__templates__/phaser/scripts/spawn-detached.cjs +29 -0
  81. package/lib/__templates__/phaser/scripts/start.ps1 +49 -0
  82. package/lib/__templates__/phaser/scripts/start.sh +82 -0
  83. package/lib/__templates__/phaser/scripts/validate.ps1 +7 -0
  84. package/lib/__templates__/phaser/scripts/validate.sh +7 -0
  85. package/lib/__templates__/phaser/src/assets.ts +15 -0
  86. package/lib/__templates__/phaser/src/global.d.ts +39 -0
  87. package/lib/__templates__/phaser/src/main.ts +28 -0
  88. package/lib/__templates__/phaser/src/scene/BootScene.ts +213 -0
  89. package/lib/__templates__/phaser/src/scene/DevLoadingScene.ts +69 -0
  90. package/lib/__templates__/phaser/src/style.css +28 -0
  91. package/lib/__templates__/phaser/src/types.ts +32 -0
  92. package/lib/__templates__/phaser/src/utils/asset-loader.ts +319 -0
  93. package/lib/__templates__/phaser/src/utils/index.ts +2 -0
  94. package/lib/__templates__/phaser/src/utils/mark-editable.ts +13 -0
  95. package/lib/__templates__/phaser/template.config.js +43 -0
  96. package/lib/__templates__/phaser/tsconfig.json +23 -0
  97. package/lib/__templates__/phaser/vite.config.ts +96 -0
  98. package/lib/__templates__/phaser-static/.coze +15 -0
  99. package/lib/__templates__/phaser-static/README.md +40 -0
  100. package/lib/__templates__/phaser-static/_gitignore +6 -0
  101. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  102. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  103. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  104. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  105. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  106. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  107. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  108. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  109. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  110. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  111. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  112. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  113. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  114. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  115. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  116. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  117. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  118. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  119. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  120. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  121. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  122. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  123. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  124. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  125. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  126. package/lib/__templates__/phaser-static/debug-runtime/flow-graph.json +20 -0
  127. package/lib/__templates__/phaser-static/index.html +17 -0
  128. package/lib/__templates__/phaser-static/scripts/serve.ps1 +7 -0
  129. package/lib/__templates__/phaser-static/src/assets.js +15 -0
  130. package/lib/__templates__/phaser-static/src/main.js +26 -0
  131. package/lib/__templates__/phaser-static/src/phaser.js +7 -0
  132. package/lib/__templates__/phaser-static/src/scene/BootScene.js +205 -0
  133. package/lib/__templates__/phaser-static/src/scene/DevLoadingScene.js +67 -0
  134. package/lib/__templates__/phaser-static/src/style.css +28 -0
  135. package/lib/__templates__/phaser-static/src/utils/asset-loader.js +267 -0
  136. package/lib/__templates__/phaser-static/src/utils/index.js +2 -0
  137. package/lib/__templates__/phaser-static/src/utils/mark-editable.js +3 -0
  138. package/lib/__templates__/phaser-static/template.config.js +35 -0
  139. package/lib/__templates__/pi-agent/package.json +1 -1
  140. package/lib/__templates__/pi-agent/pnpm-lock.yaml +5 -5
  141. package/lib/__templates__/taro/.cozeproj/scripts/deploy_build.sh +3 -2
  142. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +1 -1
  143. package/lib/__templates__/taro/.cozeproj/scripts/dev_build.sh +3 -0
  144. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +178 -68
  145. package/lib/__templates__/taro/.cozeproj/scripts/prepare-node-modules.sh +142 -0
  146. package/lib/__templates__/taro/AGENTS.md +11 -7
  147. package/lib/__templates__/taro/README.md +4 -4
  148. package/lib/__templates__/taro/config/index.ts +10 -2
  149. package/lib/__templates__/taro/pnpm-lock.yaml +5 -5
  150. package/lib/__templates__/taro/server/package.json +1 -1
  151. package/lib/__templates__/taro/server/src/main.ts +8 -2
  152. package/lib/__templates__/templates.json +51 -1
  153. package/lib/__templates__/vite/.coze +5 -0
  154. package/lib/__templates__/vite/package.json +1 -1
  155. package/lib/__templates__/vite/pnpm-lock.yaml +5 -5
  156. package/lib/__templates__/vite/scripts/build.ps1 +16 -0
  157. package/lib/__templates__/vite/scripts/build.sh +1 -1
  158. package/lib/__templates__/vite/scripts/dev.ps1 +63 -0
  159. package/lib/__templates__/vite/scripts/dev.sh +110 -1
  160. package/lib/__templates__/vite/scripts/prepare-node-modules.sh +142 -0
  161. package/lib/__templates__/vite/scripts/prepare.ps1 +18 -0
  162. package/lib/__templates__/vite/scripts/prepare.sh +1 -4
  163. package/lib/__templates__/vite/scripts/start.ps1 +11 -0
  164. package/lib/__templates__/vite/scripts/validate.ps1 +8 -0
  165. package/lib/__templates__/vite/template.config.js +0 -57
  166. package/lib/cli.js +3751 -222
  167. package/lib/deploy/package-project.js +244 -0
  168. package/lib/deploy/package-project.py +183 -0
  169. package/package.json +5 -2
@@ -0,0 +1,12 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { (Get-Location).Path }
4
+ Set-Location $workspace
5
+
6
+ Write-Host "Installing dependencies..."
7
+ & pnpm install --prefer-frozen-lockfile --prefer-offline --loglevel debug --reporter=append-only
8
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
9
+
10
+ Write-Host "Building the Nuxt.js project..."
11
+ & pnpm nuxt build
12
+ exit $LASTEXITCODE
@@ -6,7 +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 --loglevel debug --reporter=append-only
9
+ bash "$COZE_WORKSPACE_PATH/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline --loglevel debug --reporter=append-only
10
10
 
11
11
  echo "Building the Nuxt.js project..."
12
12
  pnpm nuxt build
@@ -0,0 +1,63 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ function Get-WorkspacePath {
4
+ if ($env:COZE_WORKSPACE_PATH) {
5
+ return $env:COZE_WORKSPACE_PATH
6
+ }
7
+ return (Get-Location).Path
8
+ }
9
+
10
+ function Stop-ProcessTree([int] $processId) {
11
+ if ($processId -gt 0 -and (Get-Process -Id $processId -ErrorAction SilentlyContinue)) {
12
+ & taskkill.exe /PID $processId /T /F *> $null
13
+ }
14
+ }
15
+
16
+ function Stop-ListeningProcess([int] $port) {
17
+ $connections = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
18
+ $connections | Select-Object -ExpandProperty OwningProcess -Unique | ForEach-Object {
19
+ Stop-ProcessTree $_
20
+ }
21
+ }
22
+
23
+ $workspace = Get-WorkspacePath
24
+ $defaultPort = <%= port %>
25
+ $port = if ($env:DEPLOY_RUN_PORT) { [int] $env:DEPLOY_RUN_PORT } elseif ($env:PORT) { [int] $env:PORT } else { $defaultPort }
26
+ Set-Location $workspace
27
+ Stop-ListeningProcess $port
28
+
29
+ <% if (process.env.NODE_ENV === 'test') { %>
30
+ $env:PORT = "$port"
31
+ & pnpm nuxt dev
32
+ exit $LASTEXITCODE
33
+ <% } else { %>
34
+ $logDirectory = if ($env:COZE_LOG_DIR) { $env:COZE_LOG_DIR } else { Join-Path $workspace "logs" }
35
+ $logFile = Join-Path $logDirectory "server.log"
36
+ $errorLogFile = Join-Path $logDirectory "server-error.log"
37
+ $pidFile = Join-Path $logDirectory "server.pid"
38
+ New-Item -ItemType Directory -Force -Path $logDirectory | Out-Null
39
+
40
+ if (Test-Path $pidFile) {
41
+ Stop-ProcessTree ([int] (Get-Content $pidFile -Raw))
42
+ Remove-Item $pidFile -Force
43
+ }
44
+
45
+ $previousPort = $env:PORT
46
+ $env:PORT = "$port"
47
+ try {
48
+ $process = Start-Process -FilePath $env:ComSpec -ArgumentList @("/d", "/s", "/c", "pnpm nuxt dev") -WorkingDirectory $workspace -RedirectStandardOutput $logFile -RedirectStandardError $errorLogFile -PassThru
49
+ } finally {
50
+ if ($null -eq $previousPort) { Remove-Item Env:PORT -ErrorAction SilentlyContinue } else { $env:PORT = $previousPort }
51
+ }
52
+
53
+ $process.Id | Set-Content $pidFile
54
+ Start-Sleep -Seconds 1
55
+ if ($process.HasExited) {
56
+ Get-Content $errorLogFile -Tail 20 -ErrorAction SilentlyContinue
57
+ Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
58
+ exit 1
59
+ }
60
+
61
+ Write-Host "Nuxt dev server started (PID: $($process.Id))."
62
+ Write-Host "Log file: $logFile"
63
+ <% } %>
@@ -14,9 +14,29 @@ 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
@@ -24,7 +44,7 @@ kill_port_if_listening() {
24
44
  echo "Port ${DEPLOY_RUN_PORT} in use by PIDs: ${pids} (SIGKILL)"
25
45
  echo "${pids}" | xargs -I {} kill -9 {}
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
49
  echo "Warning: port ${DEPLOY_RUN_PORT} still busy after SIGKILL, PIDs: ${pids}"
30
50
  else
@@ -32,8 +52,217 @@ kill_port_if_listening() {
32
52
  fi
33
53
  }
34
54
 
55
+ <% if (process.env.NODE_ENV === 'test') { %>
35
56
  echo "Clearing port ${DEPLOY_RUN_PORT} before start."
36
57
  kill_port_if_listening
37
58
  echo "Starting Nuxt dev server on port ${DEPLOY_RUN_PORT}..."
38
59
 
39
- PORT=${DEPLOY_RUN_PORT} pnpm nuxt dev
60
+ # 测试环境保持服务为前台进程,便于 e2e 收集输出并停止进程。
61
+ exec env PORT="${DEPLOY_RUN_PORT}" pnpm nuxt dev
62
+ <% } else { %>
63
+ LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
64
+ LOG_FILE="${LOG_DIR}/server.log"
65
+ PID_FILE="${LOG_DIR}/server.pid"
66
+
67
+ # detached 出去的进程没人负责回收,超过这个时长就自己退出,避免端口与内存长期泄露。
68
+ MAX_RUNTIME_SECONDS=1200
69
+
70
+ # 真正被 detach 的是这层 bash wrapper:它是进程组 leader,组内 watchdog 到点回收整组
71
+ # (wrapper -> pnpm -> nuxt -> node);被包的进程自己先退出时也顺手清空进程组,不留残余。
72
+ RUN_WITH_TIMEOUT='
73
+ timeout_seconds=$1
74
+ shift
75
+
76
+ "$@" &
77
+ child_pid=$!
78
+
79
+ # 先忽略 TERM,才能在向整组发 TERM(自己也在组里)之后存活下来补一发 KILL。
80
+ ( trap "" TERM
81
+ sleep "${timeout_seconds}"
82
+ echo "[dev] 后台进程运行超过 ${timeout_seconds}s,回收进程组 $$。"
83
+ kill -TERM -- "-$$" 2>/dev/null || true
84
+ sleep 5
85
+ kill -KILL -- "-$$" 2>/dev/null || true
86
+ ) &
87
+
88
+ wait "${child_pid}"
89
+ kill -KILL -- "-$$" 2>/dev/null || true
90
+ '
91
+
92
+ # coze-daemon 会在 runtime shell 退出时清理原进程组。
93
+ # 通过 Node detached spawn 创建独立 session/进程组,并将 stdio 直接写入日志。
94
+ # 返回的 PID 是 wrapper 的,同时也是整个进程组的 PGID,后续按组回收。
95
+ spawn_detached() {
96
+ local cwd="$1"
97
+ local log_file="$2"
98
+ shift 2
99
+
100
+ node - "$cwd" "$log_file" \
101
+ /bin/bash -c "${RUN_WITH_TIMEOUT}" detached-runner "${MAX_RUNTIME_SECONDS}" "$@" <<'NODE'
102
+ const fs = require('node:fs');
103
+ const { spawn } = require('node:child_process');
104
+
105
+ const [cwd, logFile, command, ...args] = process.argv.slice(2);
106
+ if (!cwd || !logFile || !command) {
107
+ throw new Error('spawn_detached 缺少 cwd、log_file 或 command');
108
+ }
109
+
110
+ const logFd = fs.openSync(logFile, 'a');
111
+ try {
112
+ const child = spawn(command, args, {
113
+ cwd,
114
+ detached: true,
115
+ env: process.env,
116
+ stdio: ['ignore', logFd, logFd],
117
+ });
118
+ child.unref();
119
+ process.stdout.write(String(child.pid));
120
+ } finally {
121
+ fs.closeSync(logFd);
122
+ }
123
+ NODE
124
+ }
125
+
126
+ stop_detached() {
127
+ local pid="${1:-}"
128
+ if [[ -z "${pid}" ]]; then
129
+ return
130
+ fi
131
+
132
+ kill -TERM -- "-${pid}" 2>/dev/null || kill -TERM "${pid}" 2>/dev/null || true
133
+ sleep 1
134
+ kill -KILL -- "-${pid}" 2>/dev/null || true
135
+ }
136
+
137
+ READY_RETRIES=10
138
+ READY_PROBE_HOSTS=("127.0.0.1" "::1")
139
+ LOG_TAIL_LINES=40
140
+
141
+ dump_log() {
142
+ echo "---- tail -n ${LOG_TAIL_LINES} ${LOG_FILE} ----" >&2
143
+ tail -n "${LOG_TAIL_LINES}" "${LOG_FILE}" >&2 || true
144
+ echo "---- end of ${LOG_FILE} ----" >&2
145
+ }
146
+
147
+ # 仅用于无 ss/lsof 时的兜底探测。服务可能只 bind IPv4 loopback、只 bind IPv6 loopback,
148
+ # 或 bind 双栈通配地址,逐个 loopback 试连即可覆盖。
149
+ port_connectable() {
150
+ local port=$1
151
+ local host
152
+ for host in "${READY_PROBE_HOSTS[@]}"; do
153
+ if command -v nc >/dev/null 2>&1; then
154
+ if nc -z -w 1 "${host}" "${port}" >/dev/null 2>&1; then
155
+ return 0
156
+ fi
157
+ elif (exec 3<>"/dev/tcp/${host}/${port}") >/dev/null 2>&1; then
158
+ return 0
159
+ fi
160
+ done
161
+ return 1
162
+ }
163
+
164
+ port_probe_available() {
165
+ command -v ss >/dev/null 2>&1 || command -v lsof >/dev/null 2>&1
166
+ }
167
+
168
+ # 端口监听者是否属于本进程组:spawn detached 后 PID == PGID,子孙进程继承该 PGID。
169
+ # 0 = 是;1 = 不是(无人监听,或监听者不属于本进程组);2 = 无探测工具,无法判断
170
+ port_listened_by_pgid() {
171
+ local port=$1 pgid=$2
172
+ local listener owner
173
+
174
+ if ! port_probe_available; then
175
+ return 2
176
+ fi
177
+
178
+ for listener in $(list_port_pids "${port}" 2>/dev/null); do
179
+ owner=$(ps -o pgid= -p "${listener}" 2>/dev/null | tr -d ' ')
180
+ if [[ "${owner}" == "${pgid}" ]]; then
181
+ return 0
182
+ fi
183
+ done
184
+ return 1
185
+ }
186
+
187
+ wait_for_ready() {
188
+ local pid=$1 port=$2
189
+ local attempt=0 owned
190
+
191
+ if ! port_probe_available; then
192
+ echo "Warning: 缺少 ss 与 lsof,无法确认端口监听者归属,仅按 loopback 可连接性判断就绪。" >&2
193
+ fi
194
+
195
+ while [[ "${attempt}" -lt "${READY_RETRIES}" ]]; do
196
+ if ! kill -0 "${pid}" 2>/dev/null; then
197
+ echo "Dev server 进程在启动过程中退出。" >&2
198
+ return 1
199
+ fi
200
+
201
+ if port_listened_by_pgid "${port}" "${pid}"; then
202
+ owned=0
203
+ else
204
+ owned=$?
205
+ fi
206
+ if [[ "${owned}" -eq 0 ]]; then
207
+ return 0
208
+ fi
209
+ if [[ "${owned}" -eq 2 ]] && port_connectable "${port}"; then
210
+ return 0
211
+ fi
212
+
213
+ sleep 1
214
+ attempt=$((attempt + 1))
215
+ done
216
+
217
+ return 2
218
+ }
219
+
220
+ # 监听端口的是孙进程(pnpm -> nuxt -> node),只清端口会漏掉上层 pnpm/nuxt,
221
+ # 所以先按上次记录的 PID 把整个进程组回收掉。
222
+ if [[ -f "${PID_FILE}" ]]; then
223
+ stop_detached "$(cat "${PID_FILE}" 2>/dev/null || true)"
224
+ rm -f "${PID_FILE}"
225
+ fi
226
+
227
+ echo "Clearing port ${DEPLOY_RUN_PORT} before start."
228
+ kill_port_if_listening
229
+ echo "Starting Nuxt dev server on port ${DEPLOY_RUN_PORT}..."
230
+
231
+ mkdir -p "${LOG_DIR}"
232
+ : > "${LOG_FILE}"
233
+
234
+ export PORT="${DEPLOY_RUN_PORT}"
235
+ server_pid="$(spawn_detached "${COZE_WORKSPACE_PATH}" "${LOG_FILE}" \
236
+ "$(command -v pnpm)" nuxt dev)"
237
+ if [[ -z "${server_pid}" ]]; then
238
+ echo "Dev server failed to start: 未获取到后台进程 PID。" >&2
239
+ dump_log
240
+ exit 1
241
+ fi
242
+ echo "${server_pid}" > "${PID_FILE}"
243
+
244
+ if wait_for_ready "${server_pid}" "${DEPLOY_RUN_PORT}"; then
245
+ ready_status=0
246
+ else
247
+ ready_status=$?
248
+ fi
249
+
250
+ if [[ "${ready_status}" -ne 0 ]]; then
251
+ if [[ "${ready_status}" -eq 2 ]]; then
252
+ # 进程还活着但迟迟没监听端口:多半是编译/运行时报错后 nuxt watch 驻留,日志里有真正原因。
253
+ echo "Dev server 在 ${READY_RETRIES} 次探测(每次 1s)内未就绪于端口 ${DEPLOY_RUN_PORT}(进程 ${server_pid} 仍在运行,已回收)。" >&2
254
+ else
255
+ echo "Dev server failed to start. See ${LOG_FILE}." >&2
256
+ fi
257
+ # 先 dump 再回收,避免 kill 的输出混进日志尾部;exit 1 意味着不留下任何后台进程。
258
+ dump_log
259
+ stop_detached "${server_pid}"
260
+ rm -f "${PID_FILE}"
261
+ exit 1
262
+ fi
263
+
264
+ echo "Dev server started (PID: ${server_pid}), listening on port ${DEPLOY_RUN_PORT}."
265
+ echo "Auto stop after ${MAX_RUNTIME_SECONDS}s."
266
+ echo "Log file: ${LOG_FILE}"
267
+ echo "PID file: ${PID_FILE}"
268
+ <% } %>
@@ -0,0 +1,142 @@
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
+ COZE_ROOT="${COZE_DIR_PATH:-${HOME}/Coze/tmp}"
32
+ PROJECT_ID="$(basename "$PROJECT_ROOT")-$(printf '%s' "$PROJECT_ROOT" | cksum | awk '{print $1}')"
33
+ mkdir -p "$COZE_ROOT/nm"
34
+ COZE_ROOT="$(cd "$COZE_ROOT" && pwd -P)"
35
+ WORK_DIR="$COZE_ROOT/nm/$PROJECT_ID"
36
+ mkdir -p "$WORK_DIR"
37
+ case "$WORK_DIR" in
38
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*)
39
+ echo "[node_modules] local dependency directory must not be on Coze Drive: $WORK_DIR" >&2
40
+ exit 1
41
+ ;;
42
+ esac
43
+
44
+ # Mirror the project with symlinks. node_modules stays real in WORK_DIR;
45
+ # workspace directories stay real too, so pnpm never writes them to Drive.
46
+ mirror_root() {
47
+ local entry name
48
+ while IFS= read -r -d '' entry; do
49
+ name="$(basename "$entry")"
50
+ case "$name" in
51
+ node_modules|pnpm-lock.yaml) continue ;;
52
+ client|server)
53
+ [ -f "$PROJECT_ROOT/$name/package.json" ] && continue
54
+ ;;
55
+ esac
56
+ rm -rf "$entry"
57
+ done < <(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -print0)
58
+
59
+ while IFS= read -r -d '' entry; do
60
+ name="$(basename "$entry")"
61
+ case "$name" in
62
+ node_modules|pnpm-lock.yaml) continue ;;
63
+ client|server)
64
+ [ -f "$entry/package.json" ] && continue
65
+ ;;
66
+ esac
67
+ ln -s "$entry" "$WORK_DIR/$name"
68
+ done < <(find "$PROJECT_ROOT" -mindepth 1 -maxdepth 1 -print0)
69
+ }
70
+
71
+ mirror_workspace() {
72
+ local workspace="$1" source_dir="$PROJECT_ROOT/$1" work_dir="$WORK_DIR/$1"
73
+ local entry name
74
+ if [ -L "$work_dir" ] || { [ -e "$work_dir" ] && [ ! -d "$work_dir" ]; }; then
75
+ rm -rf "$work_dir"
76
+ fi
77
+ mkdir -p "$work_dir"
78
+
79
+ while IFS= read -r -d '' entry; do
80
+ [ "$(basename "$entry")" = "node_modules" ] || rm -rf "$entry"
81
+ done < <(find "$work_dir" -mindepth 1 -maxdepth 1 -print0)
82
+
83
+ while IFS= read -r -d '' entry; do
84
+ name="$(basename "$entry")"
85
+ [ "$name" = "node_modules" ] || ln -s "$entry" "$work_dir/$name"
86
+ done < <(find "$source_dir" -mindepth 1 -maxdepth 1 -print0)
87
+ }
88
+
89
+ mirror_root
90
+ for workspace in client server; do
91
+ if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
92
+ mirror_workspace "$workspace"
93
+ fi
94
+ done
95
+
96
+ # pnpm refuses to update a symlinked lockfile, so install against a local copy.
97
+ rm -rf "$WORK_DIR/pnpm-lock.yaml"
98
+ if [ -f "$PROJECT_ROOT/pnpm-lock.yaml" ]; then
99
+ cp "$PROJECT_ROOT/pnpm-lock.yaml" "$WORK_DIR/pnpm-lock.yaml"
100
+ fi
101
+
102
+ echo "[node_modules] Installing dependencies in $WORK_DIR"
103
+ if ! (cd "$WORK_DIR" && pnpm install "$@"); then
104
+ rm -f "$WORK_DIR/pnpm-lock.yaml"
105
+ exit 1
106
+ fi
107
+
108
+ if [ -f "$WORK_DIR/pnpm-lock.yaml" ]; then
109
+ temporary_lockfile="$(mktemp "$PROJECT_ROOT/.pnpm-lock.yaml.XXXXXX")"
110
+ cp "$WORK_DIR/pnpm-lock.yaml" "$temporary_lockfile"
111
+ chmod 0644 "$temporary_lockfile"
112
+ mv -f "$temporary_lockfile" "$PROJECT_ROOT/pnpm-lock.yaml"
113
+ fi
114
+
115
+ link_node_modules() {
116
+ local source_dir="$1" target_dir="$2"
117
+ local current=""
118
+ mkdir -p "$target_dir"
119
+ if [ -L "$source_dir/node_modules" ]; then
120
+ current="$(readlink "$source_dir/node_modules")"
121
+ case "$current" in
122
+ "$COZE_ROOT"/nm/*/node_modules|"$COZE_ROOT"/nm/*/*/node_modules) ;;
123
+ *)
124
+ echo "[node_modules] refusing to replace unmanaged symlink: $source_dir/node_modules" >&2
125
+ exit 1
126
+ ;;
127
+ esac
128
+ rm "$source_dir/node_modules"
129
+ elif [ -e "$source_dir/node_modules" ]; then
130
+ rm -rf "$source_dir/node_modules"
131
+ fi
132
+ ln -s "$target_dir" "$source_dir/node_modules"
133
+ }
134
+
135
+ link_node_modules "$PROJECT_ROOT" "$WORK_DIR/node_modules"
136
+ for workspace in client server; do
137
+ if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
138
+ link_node_modules "$PROJECT_ROOT/$workspace" "$WORK_DIR/$workspace/node_modules"
139
+ fi
140
+ done
141
+
142
+ echo "[node_modules] Dependencies ready"
@@ -0,0 +1,12 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { (Get-Location).Path }
4
+ Set-Location $workspace
5
+
6
+ Write-Host "Installing dependencies..."
7
+ & pnpm install --prefer-frozen-lockfile --prefer-offline
8
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
9
+
10
+ Write-Host "Preparing Nuxt..."
11
+ & pnpm exec nuxt prepare
12
+ exit $LASTEXITCODE
@@ -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
@@ -0,0 +1,11 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { (Get-Location).Path }
4
+ $defaultPort = <%= port %>
5
+ $port = if ($env:DEPLOY_RUN_PORT) { $env:DEPLOY_RUN_PORT } elseif ($env:PORT) { $env:PORT } else { $defaultPort }
6
+ Set-Location $workspace
7
+
8
+ Write-Host "Starting Nuxt.js service on port $port for production..."
9
+ $env:PORT = "$port"
10
+ & node .output/server/index.mjs
11
+ exit $LASTEXITCODE
@@ -0,0 +1,8 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $workspace = if ($env:COZE_WORKSPACE_PATH) { $env:COZE_WORKSPACE_PATH } else { (Get-Location).Path }
4
+ Set-Location $workspace
5
+
6
+ Write-Host "Running validate..."
7
+ & pnpm validate
8
+ exit $LASTEXITCODE
@@ -0,0 +1,18 @@
1
+ [project]
2
+ requires = ["nodejs-24"]
3
+ project_type = "game"
4
+ game_type = "phaser"
5
+
6
+ [dev]
7
+ build = ["bash", "./scripts/prepare.sh"]
8
+ build_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/prepare.ps1"]
9
+ run = ["bash", "./scripts/dev.sh"]
10
+ run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/dev.ps1"]
11
+ validate = ["bash", "./scripts/validate.sh"]
12
+ validate_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/validate.ps1"]
13
+
14
+ [deploy]
15
+ build = ["bash", "./scripts/build.sh"]
16
+ build_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/build.ps1"]
17
+ run = ["bash", "./scripts/start.sh"]
18
+ run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/start.ps1"]
@@ -0,0 +1,45 @@
1
+ # <%= appName %>
2
+
3
+ 这是一个基于 Phaser、Vite 和 TypeScript 的 2D 游戏项目,由扣子编程 CLI 创建。
4
+
5
+ ## 快速开始
6
+
7
+ ```bash
8
+ coze-dev dev
9
+ ```
10
+
11
+ 开发服务器默认运行在 [http://127.0.0.1:<%= port %>](http://127.0.0.1:<%= port %>)。
12
+
13
+ ```bash
14
+ coze-dev validate # TypeScript 类型检查
15
+ coze-dev build # 生产构建
16
+ coze-dev start # 预览生产构建
17
+ ```
18
+
19
+ ## 文件结构
20
+
21
+ ```text
22
+ ├── assets/ # 图片、序列帧、视频和音频
23
+ ├── debug-runtime/ # 调试 Flow
24
+ ├── src/assets.ts # 素材 key 与路径
25
+ ├── src/main.ts # 游戏入口
26
+ ├── src/scene/BootScene.ts # 启动与全量加载
27
+ ├── src/utils/asset-loader.ts # 批量和单素材加载
28
+ ├── src/types.ts # 类型定义
29
+ └── vite.config.ts # Bridge 注入与素材目录映射
30
+ ```
31
+
32
+ - 素材按类型放在 `assets/`,并登记到 `src/assets.ts` 对应的分类表。
33
+ - 序列帧只登记文件夹;Loader 从 `manifest.json` 读取帧数和帧率。
34
+ - `BootScene` 加载游戏素材;运行时可用 `loadAsset(this, key)` 单独加载。
35
+ - 开发和生产都使用稳定的 `assets/...` 路径,生产构建原样复制素材目录。
36
+
37
+ `IMAGES`、`IMAGE_SEQUENCES`、`VIDEOS` 和 `AUDIOS` 是四个素材分类表。
38
+
39
+ ## 可视化编辑标识
40
+
41
+ 用 `markEditable` 标记可在编辑器中调整的对象;第三个参数可选,仅用于编辑器展示名称:
42
+
43
+ ```ts
44
+ markEditable('ui.start-button', button, { label: '开始按钮' });
45
+ ```
@@ -0,0 +1,23 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+
9
+ *.log
10
+ *.tsbuildinfo
11
+ logs/
12
+ .vite/
13
+ .cache/
14
+ node-compile-cache/
15
+
16
+ .DS_Store
17
+ ._*
18
+ .idea/
19
+ .vscode/
20
+
21
+ .coze-logs/
22
+ .codegraph/
23
+ .preview/
@@ -0,0 +1,14 @@
1
+ loglevel=error
2
+ registry=https://registry.npmmirror.com
3
+
4
+ strictStorePkgContentCheck=false
5
+ verifyStoreIntegrity=false
6
+ network-concurrency=16
7
+ fetch-retries=3
8
+ fetch-timeout=60000
9
+ strict-peer-dependencies=false
10
+ auto-install-peers=true
11
+ lockfile=true
12
+ prefer-frozen-lockfile=true
13
+ resolution-mode=highest
14
+ update-notifier=false