@coze-arch/cli 0.1.8-alpha.53dd57 → 0.1.8-alpha.5d5f9d

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.
@@ -119,7 +119,9 @@ cleanup_legacy_workspace() {
119
119
  done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
120
120
  }
121
121
 
122
- # Preserve existing project caches during dependency preparation.
122
+ # caches is reserved for the sibling prepare scripts of this project. They keep
123
+ # regenerable framework output there (the Next helper points .next/dev into it), and that
124
+ # output has to outlive an install, so this cleanup must not read it as leftover.
123
125
  cleanup_legacy_shadow_project() {
124
126
  local entry name
125
127
  while IFS= read -r -d '' entry; do
@@ -5,10 +5,10 @@ project_type = "web"
5
5
 
6
6
  [dev]
7
7
  build = []
8
- run = [ "sh", "-c", "exec python3 -m http.server ${DEPLOY_RUN_PORT} --bind 0.0.0.0" ]
8
+ run = ["python3", "-m", "http.server", "${DEPLOY_RUN_PORT}", "--bind", "0.0.0.0"]
9
9
  run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/serve.ps1"]
10
10
 
11
11
  [deploy]
12
12
  build = []
13
- run = [ "sh", "-c", "exec python3 -m http.server ${DEPLOY_RUN_PORT} --bind 0.0.0.0" ]
13
+ run = ["python3", "-m", "http.server", "${DEPLOY_RUN_PORT}", "--bind", "0.0.0.0"]
14
14
  run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/serve.ps1"]
@@ -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 Next.js project..."
12
12
  pnpm next build --webpack
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env bash
2
+ # Keep Next development output off Coze Drive without moving production output.
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
+ DRIVE_ROOT="${COZE_DRIVE_ROOT:-/Coze/Drive}"
13
+ if [ -d "$DRIVE_ROOT" ]; then
14
+ DRIVE_ROOT="$(cd "$DRIVE_ROOT" && pwd -P)"
15
+ else
16
+ DRIVE_ROOT="${DRIVE_ROOT%/}"
17
+ fi
18
+ case "$PROJECT_ROOT" in
19
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*) ;;
20
+ *) exit 0 ;;
21
+ esac
22
+
23
+ # Next writes every on-demand development build into .next/dev. On Coze Drive each of
24
+ # those writes goes through the sync daemon, so only that subtree moves to local disk.
25
+ # Production output stays in the project directory, which keeps `next build` and
26
+ # deployment unchanged.
27
+ #
28
+ # One local directory per project, shared with prepare-node-modules.sh: node_modules and
29
+ # this cache sit side by side under WORK_DIR. The caches subdirectory is the slot that
30
+ # script reserves for output that must survive an install, so nothing here is cleaned up
31
+ # behind our back. PROJECT_ID has to stay identical to the one it computes.
32
+ NM_ROOT="/tmp/nm"
33
+ PROJECT_ID="$(basename "$PROJECT_ROOT")-$(printf '%s' "$PROJECT_ROOT" | cksum | awk '{print $1}')"
34
+ WORK_DIR="$NM_ROOT/$PROJECT_ID"
35
+ NEXT_ROOT="$PROJECT_ROOT/.next"
36
+ NEXT_DEV="$NEXT_ROOT/dev"
37
+ LEGACY_NEXT_ROOT="$WORK_DIR/.next"
38
+ LOCAL_NEXT_DEV="$WORK_DIR/caches/next-dev"
39
+
40
+ case "$LOCAL_NEXT_DEV" in
41
+ "$DRIVE_ROOT"|"$DRIVE_ROOT"/*)
42
+ echo "[next] local development output must not be on Coze Drive: $LOCAL_NEXT_DEV" >&2
43
+ exit 1
44
+ ;;
45
+ esac
46
+
47
+ # Coze Drive sync does not preserve symlinks: a synced copy of a link arrives as a
48
+ # regular file holding the link target. Recognize the links this scheme creates so a
49
+ # project that came back from sync repairs itself instead of failing to start.
50
+ is_flattened_managed_link() {
51
+ local candidate="$1" content=""
52
+ [ -f "$candidate" ] || return 1
53
+ [ ! -L "$candidate" ] || return 1
54
+ [ "$(wc -c < "$candidate")" -le 4096 ] || return 1
55
+ content="$(tr -d '\r\n' < "$candidate")"
56
+ case "$content" in
57
+ "$NM_ROOT"/*) return 0 ;;
58
+ esac
59
+ return 1
60
+ }
61
+
62
+ # Earlier versions linked the whole .next directory. Bring any production build made
63
+ # back then into the project; from here on only .next/dev is redirected.
64
+ restore_legacy_next_root() {
65
+ local entry
66
+ rm -f "$NEXT_ROOT"
67
+ mkdir -p "$NEXT_ROOT"
68
+ if [ -d "$LEGACY_NEXT_ROOT" ]; then
69
+ while IFS= read -r -d '' entry; do
70
+ mv "$entry" "$NEXT_ROOT/"
71
+ done < <(find -P "$LEGACY_NEXT_ROOT" -mindepth 1 -maxdepth 1 -print0)
72
+ rmdir "$LEGACY_NEXT_ROOT" 2>/dev/null || true
73
+ fi
74
+ }
75
+
76
+ if [ -L "$NEXT_ROOT" ]; then
77
+ case "$(readlink "$NEXT_ROOT")" in
78
+ "$NM_ROOT"/*)
79
+ echo "[next] Restoring legacy .next output to the project directory."
80
+ restore_legacy_next_root
81
+ ;;
82
+ *)
83
+ echo "[next] refusing to replace unmanaged .next symlink: $NEXT_ROOT" >&2
84
+ exit 1
85
+ ;;
86
+ esac
87
+ elif is_flattened_managed_link "$NEXT_ROOT"; then
88
+ echo "[next] Recovering .next flattened by Coze Drive sync."
89
+ restore_legacy_next_root
90
+ elif [ -e "$NEXT_ROOT" ] && [ ! -d "$NEXT_ROOT" ]; then
91
+ echo "[next] refusing to replace non-directory Next output: $NEXT_ROOT" >&2
92
+ exit 1
93
+ fi
94
+
95
+ mkdir -p "$NEXT_ROOT" "$LOCAL_NEXT_DEV"
96
+
97
+ if [ -L "$NEXT_DEV" ]; then
98
+ if [ "$(readlink "$NEXT_DEV")" = "$LOCAL_NEXT_DEV" ]; then
99
+ exit 0
100
+ fi
101
+ case "$(readlink "$NEXT_DEV")" in
102
+ "$NM_ROOT"/*) rm "$NEXT_DEV" ;;
103
+ *)
104
+ echo "[next] refusing to replace unmanaged .next/dev symlink: $NEXT_DEV" >&2
105
+ exit 1
106
+ ;;
107
+ esac
108
+ elif [ -e "$NEXT_DEV" ]; then
109
+ # .next/dev holds development output only, and Next regenerates it on demand.
110
+ rm -rf "$NEXT_DEV"
111
+ fi
112
+
113
+ # A second dev start can win the race between the removal above and this link.
114
+ if ! ln -s "$LOCAL_NEXT_DEV" "$NEXT_DEV" 2>/dev/null; then
115
+ if [ -L "$NEXT_DEV" ] && [ "$(readlink "$NEXT_DEV")" = "$LOCAL_NEXT_DEV" ]; then
116
+ exit 0
117
+ fi
118
+ echo "[next] failed to link development output: $NEXT_DEV" >&2
119
+ exit 1
120
+ fi
121
+
122
+ echo "[next] Development output linked to $LOCAL_NEXT_DEV"
@@ -119,7 +119,9 @@ cleanup_legacy_workspace() {
119
119
  done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
120
120
  }
121
121
 
122
- # Preserve existing project caches during dependency preparation.
122
+ # caches is reserved for the sibling prepare scripts of this project. They keep
123
+ # regenerable framework output there (the Next helper points .next/dev into it), and that
124
+ # output has to outlive an install, so this cleanup must not read it as leftover.
123
125
  cleanup_legacy_shadow_project() {
124
126
  local entry name
125
127
  while IFS= read -r -d '' entry; do
@@ -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
@@ -119,7 +119,9 @@ cleanup_legacy_workspace() {
119
119
  done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
120
120
  }
121
121
 
122
- # Preserve existing project caches during dependency preparation.
122
+ # caches is reserved for the sibling prepare scripts of this project. They keep
123
+ # regenerable framework output there (the Next helper points .next/dev into it), and that
124
+ # output has to outlive an install, so this cleanup must not read it as leftover.
123
125
  cleanup_legacy_shadow_project() {
124
126
  local entry name
125
127
  while IFS= read -r -d '' entry; do
@@ -8,7 +8,7 @@ build = ["bash", ".cozeproj/scripts/dev_build.sh"]
8
8
  build_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ".cozeproj/scripts/dev_build.ps1"]
9
9
  run = ["bash", ".cozeproj/scripts/dev_run.sh"]
10
10
  run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ".cozeproj/scripts/dev_run.ps1"]
11
- deps = ["git", "rsync"] # -> apt install git rsync
11
+ deps = ["git"] # -> apt install git
12
12
  pack = ["bash", ".cozeproj/scripts/pack.sh"]
13
13
  pack_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ".cozeproj/scripts/pack.ps1"]
14
14
  validate = ["bash", ".cozeproj/scripts/validate.sh"]
@@ -17,4 +17,4 @@ validate_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-
17
17
  [deploy]
18
18
  build = ["bash", ".cozeproj/scripts/deploy_build.sh"]
19
19
  run = ["bash", ".cozeproj/scripts/deploy_run.sh"]
20
- deps = ["git", "rsync"] # -> apt install git rsync
20
+ deps = ["git"] # -> apt install git
@@ -2,9 +2,6 @@
2
2
  set -Eeuo pipefail
3
3
 
4
4
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
- if [[ "${COZE_TARO_LOCAL_ACTIVE:-}" != "${ROOT_DIR}" ]]; then
6
- exec node "$ROOT_DIR/.cozeproj/scripts/local-workspace.cjs" build "$@"
7
- fi
8
5
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
9
6
  export COZE_WORKSPACE_PATH
10
7
 
@@ -18,9 +15,7 @@ else
18
15
  fi
19
16
  echo "Installing dependencies..."
20
17
  # 安装所有依赖(包含 Taro 核心和 React)
21
- if [[ "${COZE_TARO_LOCAL_MIRROR:-}" != "1" ]]; then
22
- bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
23
- fi
18
+ bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
24
19
 
25
20
  echo "Building the Taro project..."
26
21
  pnpm build
@@ -2,13 +2,4 @@
2
2
  set -Eeuo pipefail
3
3
 
4
4
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
- if [[ "${COZE_TARO_LOCAL_ACTIVE:-}" != "${ROOT_DIR}" ]]; then
6
- exec node "$ROOT_DIR/.cozeproj/scripts/local-workspace.cjs" prepare "$@"
7
- fi
8
- COZE_WORKSPACE_PATH="${ROOT_DIR}"
9
- export COZE_WORKSPACE_PATH
10
-
11
- cd "${COZE_WORKSPACE_PATH}"
12
- if [[ "${COZE_TARO_LOCAL_MIRROR:-}" != "1" ]]; then
13
- bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
14
- fi
5
+ bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
@@ -3,9 +3,6 @@ echo "⚙️ dev_run.sh 开始运行"
3
3
  set -Eeuo pipefail
4
4
 
5
5
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
6
- if [[ "${COZE_TARO_LOCAL_ACTIVE:-}" != "${ROOT_DIR}" ]]; then
7
- exec node "$ROOT_DIR/.cozeproj/scripts/local-workspace.cjs" dev "$@"
8
- fi
9
6
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
10
7
  export COZE_WORKSPACE_PATH
11
8
  cd "${COZE_WORKSPACE_PATH}"
@@ -13,7 +10,7 @@ cd "${COZE_WORKSPACE_PATH}"
13
10
  # ---------------------------------------------------------
14
11
  # 项目级日志目录
15
12
  # ---------------------------------------------------------
16
- LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
13
+ LOG_DIR="${COZE_WORKSPACE_PATH}/logs"
17
14
  LOG_FILE="${LOG_DIR}/dev.log"
18
15
  PID_FILE="${LOG_DIR}/dev.pid"
19
16
  DEV_PID=""
@@ -122,15 +119,8 @@ process_belongs_to_workspace() {
122
119
 
123
120
  case "${cwd}" in
124
121
  "${COZE_WORKSPACE_PATH}"|"${COZE_WORKSPACE_PATH}"/*) return 0 ;;
122
+ *) return 1 ;;
125
123
  esac
126
-
127
- if [[ -n "${COZE_TARO_SOURCE_PATH:-}" ]]; then
128
- case "${cwd}" in
129
- "${COZE_TARO_SOURCE_PATH}"|"${COZE_TARO_SOURCE_PATH}"/*) return 0 ;;
130
- esac
131
- fi
132
-
133
- return 1
134
124
  }
135
125
 
136
126
  process_group_belongs_to_workspace() {
@@ -224,9 +214,7 @@ cleanup_previous_run() {
224
214
  # ---------------------------------------------------------
225
215
  echo "📦 Installing dependencies..."
226
216
  PNPM_BIN="$(command -v pnpm)"
227
- if [[ "${COZE_TARO_LOCAL_MIRROR:-}" != "1" ]]; then
228
- bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
229
- fi
217
+ bash "$ROOT_DIR/.cozeproj/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline
230
218
  echo "✅ Dependencies installed successfully!"
231
219
 
232
220
  # ---------------------------------------------------------
@@ -268,7 +256,7 @@ start_service() {
268
256
  DEV_PID="$(spawn_detached \
269
257
  "${COZE_WORKSPACE_PATH}" \
270
258
  "${LOG_FILE}" \
271
- "$(command -v node)" "$ROOT_DIR/.cozeproj/scripts/local-workspace.cjs" watch "${PNPM_BIN}" dev)"
259
+ "${PNPM_BIN}" dev)"
272
260
  if [[ -z "${DEV_PID}" ]]; then
273
261
  echo "❌ 无法获取 dev 后台进程 PID"
274
262
  return 1
@@ -291,45 +279,5 @@ start_service() {
291
279
  echo "PID file: ${PID_FILE}"
292
280
  }
293
281
 
294
- warmup_preview() {
295
- local base="http://127.0.0.1:${PORT}"
296
- local waited=0
297
-
298
- if ! command -v curl >/dev/null 2>&1; then
299
- echo "Warmup skipped: curl is not available."
300
- return 0
301
- fi
302
-
303
- echo "🔥 Warmup: waiting for web server to accept connections..."
304
- while (( waited < 60 )); do
305
- if curl -s -o /dev/null --max-time 3 "${base}/"; then
306
- break
307
- fi
308
- sleep 2
309
- waited=$(( waited + 2 ))
310
- done
311
-
312
- local app_config="${COZE_WORKSPACE_PATH}/src/app.config.ts"
313
- local pages=""
314
- if [[ -f "${app_config}" ]]; then
315
- pages=$(grep -oE "[\"']pages/[^\"']+[\"']" "${app_config}" | tr -d "\"'" | sort -u || true)
316
- fi
317
-
318
- local p
319
- for p in "/" "/app.config.ts"; do
320
- echo "🔥 Warmup ${p}"
321
- curl -s -o /dev/null --max-time 300 "${base}${p}" || echo "Warmup ${p} timed out or failed (ignored)."
322
- done
323
- for p in ${pages}; do
324
- echo "🔥 Warmup /${p}.tsx"
325
- curl -s -o /dev/null --max-time 300 "${base}/${p}.tsx" || echo "Warmup /${p}.tsx timed out or failed (ignored)."
326
- done
327
- }
328
-
329
282
  echo "Starting HTTP services on port ${PORT} (web) and ${SERVER_PORT} (server)..."
330
283
  start_service
331
-
332
- if [[ "${COZE_TARO_LOCAL_MIRROR:-}" = "1" && "${COZE_DEV_SKIP_WARMUP:-0}" != "1" ]]; then
333
- warmup_preview || true
334
- echo "✅ Warmup finished (or timed out). Preview should load fast now."
335
- fi
@@ -1,18 +1,13 @@
1
1
  #!/bin/bash
2
- set -Eeuo pipefail
3
2
 
4
3
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
- if [[ "${COZE_TARO_LOCAL_ACTIVE:-}" != "${ROOT_DIR}" ]]; then
6
- exec node "$ROOT_DIR/.cozeproj/scripts/local-workspace.cjs" pack "$@"
7
- fi
8
4
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
9
5
  export COZE_WORKSPACE_PATH
10
6
 
11
7
  cd "${COZE_WORKSPACE_PATH}"
12
8
 
13
- LOG_DIR="${COZE_LOG_DIR:-/tmp}"
14
- mkdir -p "$LOG_DIR"
15
- PID_FILE="$LOG_DIR/coze-build_weapp.pid"
9
+ # build_weapp.sh - 通过 PID 文件精确杀掉自己上次的构建进程
10
+ PID_FILE="/tmp/coze-build_weapp.pid"
16
11
 
17
12
  # 杀掉上次的构建进程组
18
13
  if [ -f "$PID_FILE" ]; then
@@ -26,16 +21,11 @@ if [ -f "$PID_FILE" ]; then
26
21
  rm -f "$PID_FILE"
27
22
  fi
28
23
 
29
- # 用 setsid 创建新的进程组,方便下次整组杀掉;无 setsid 的环境退化为普通后台进程。
30
- if command -v setsid >/dev/null 2>&1; then
31
- setsid pnpm build:pack &
32
- else
33
- pnpm build:pack &
34
- fi
35
- BUILD_PID=$!
36
- echo "$BUILD_PID" > "$PID_FILE"
24
+ # 用 setsid 创建新的进程组,方便下次整组杀掉
25
+ setsid pnpm build:pack &
26
+ echo $! > "$PID_FILE"
37
27
 
38
28
  echo "构建已启动 (PID: $(cat $PID_FILE))"
39
29
 
40
- wait "$BUILD_PID"
30
+ wait $!
41
31
  rm -f "$PID_FILE"
@@ -119,7 +119,9 @@ cleanup_legacy_workspace() {
119
119
  done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
120
120
  }
121
121
 
122
- # Preserve existing project caches during dependency preparation.
122
+ # caches is reserved for the sibling prepare scripts of this project. They keep
123
+ # regenerable framework output there (the Next helper points .next/dev into it), and that
124
+ # output has to outlive an install, so this cleanup must not read it as leftover.
123
125
  cleanup_legacy_shadow_project() {
124
126
  local entry name
125
127
  while IFS= read -r -d '' entry; do
@@ -2,9 +2,6 @@
2
2
  set -Eeuo pipefail
3
3
 
4
4
  ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
5
- if [[ "${COZE_TARO_LOCAL_ACTIVE:-}" != "${ROOT_DIR}" ]]; then
6
- exec node "$ROOT_DIR/.cozeproj/scripts/local-workspace.cjs" validate "$@"
7
- fi
8
5
  COZE_WORKSPACE_PATH="${COZE_WORKSPACE_PATH:-${ROOT_DIR}}"
9
6
  export COZE_WORKSPACE_PATH
10
7
 
@@ -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 frontend with Vite..."
12
12
  pnpm vite build
@@ -119,7 +119,9 @@ cleanup_legacy_workspace() {
119
119
  done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
120
120
  }
121
121
 
122
- # Preserve existing project caches during dependency preparation.
122
+ # caches is reserved for the sibling prepare scripts of this project. They keep
123
+ # regenerable framework output there (the Next helper points .next/dev into it), and that
124
+ # output has to outlive an install, so this cleanup must not read it as leftover.
123
125
  cleanup_legacy_shadow_project() {
124
126
  local entry name
125
127
  while IFS= read -r -d '' entry; do
package/lib/cli.js CHANGED
@@ -2114,7 +2114,7 @@ const EventBuilder = {
2114
2114
  };
2115
2115
 
2116
2116
  var name = "@coze-arch/cli";
2117
- var version = "0.1.8-alpha.53dd57";
2117
+ var version = "0.1.8-alpha.5d5f9d";
2118
2118
  var description = "coze coding devtools cli";
2119
2119
  var license = "MIT";
2120
2120
  var author = "fanwenjie.fe@bytedance.com";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coze-arch/cli",
3
- "version": "0.1.8-alpha.53dd57",
3
+ "version": "0.1.8-alpha.5d5f9d",
4
4
  "private": false,
5
5
  "description": "coze coding devtools cli",
6
6
  "license": "MIT",
@@ -1,317 +0,0 @@
1
- #!/usr/bin/env node
2
- // Taro source is owned by Drive. Only these wrappers may write to the local workspace.
3
- /* eslint-disable @typescript-eslint/no-require-imports -- Standalone CommonJS runtime shipped without CLI dependencies. */
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
- const os = require('node:os');
7
- const { createHash } = require('node:crypto');
8
- const { spawn, execFileSync } = require('node:child_process');
9
- const { setTimeout: delay } = require('node:timers/promises');
10
-
11
- const scriptRoot = fs.realpathSync(path.resolve(__dirname, '..', '..'));
12
- const source = fs.realpathSync(
13
- process.env.COZE_TARO_LOCAL_ACTIVE === scriptRoot && process.env.COZE_TARO_SOURCE_PATH
14
- ? process.env.COZE_TARO_SOURCE_PATH : scriptRoot,
15
- );
16
- const drivePath = path.resolve(process.env.COZE_DRIVE_ROOT || '/Coze/Drive');
17
- const drive = fs.existsSync(drivePath) ? fs.realpathSync(drivePath) : drivePath;
18
- const inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path.sep}`);
19
- const onDrive = inside(drive, source);
20
- const digest = value => createHash('sha256').update(value).digest('hex');
21
- // Separate from /tmp/nm: older dependency helpers clean unknown entries there.
22
- const cacheRoot = path.join(os.tmpdir(), `coze-taro-${process.getuid()}`);
23
- const projectCache = path.join(cacheRoot, digest(source));
24
- const exclusions = [
25
- 'node_modules', '.git', '/logs', '.pnpm-store', '.next', '.nuxt', '.output',
26
- '/dist', '/dist-web', '/dist-tt', '/server/dist', '/dist-server', '.cache', '.turbo', '*.tsbuildinfo',
27
- '.eslintcache', '.stylelintcache', '/next-env.d.ts',
28
- ];
29
- const outputDirs = ['dist', 'dist-web', 'dist-tt', 'server/dist'];
30
- const installArgs = ['install', '--prefer-frozen-lockfile', '--prefer-offline'];
31
- let interrupted = false;
32
- const children = new Set();
33
-
34
- function stopTree(child) {
35
- if (!child.pid || child.exitCode !== null) return;
36
- // Keep descendants in the detached launcher's group so readiness ownership works.
37
- // Also stop grandchildren when the test/foreground path is signalled directly.
38
- let rows = [];
39
- try {
40
- rows = execFileSync('ps', ['-axo', 'pid=,ppid='], { encoding: 'utf8' })
41
- .trim().split('\n').map(line => line.trim().split(/\s+/).map(Number));
42
- } catch { /* The launcher still owns and reaps its process group. */ }
43
- const pids = [child.pid];
44
- for (let i = 0; i < pids.length; i++) {
45
- for (const [pid, parent] of rows) if (parent === pids[i]) pids.push(pid);
46
- }
47
- for (const pid of pids.reverse()) {
48
- try { process.kill(pid, 'SIGTERM'); } catch { /* Already exited. */ }
49
- }
50
- const timer = setTimeout(() => {
51
- for (const pid of pids) {
52
- try { process.kill(pid, 'SIGKILL'); } catch { /* Already exited. */ }
53
- }
54
- }, 2000);
55
- timer.unref();
56
- }
57
-
58
- for (const signal of ['SIGINT', 'SIGTERM']) {
59
- process.on(signal, () => {
60
- interrupted = true;
61
- process.exitCode = signal === 'SIGINT' ? 130 : 143;
62
- for (const child of children) stopTree(child);
63
- });
64
- }
65
-
66
- function run(command, args, cwd, env = process.env) {
67
- if (interrupted) return Promise.reject(new Error('Interrupted'));
68
- return new Promise((resolve, reject) => {
69
- const child = spawn(command, args, { cwd, env, stdio: 'inherit' });
70
- children.add(child);
71
- child.once('error', error => { children.delete(child); reject(error); });
72
- child.once('exit', (code, signal) => {
73
- children.delete(child);
74
- if (code === 0) resolve();
75
- else reject(new Error(`${command} exited with ${signal || code}`));
76
- });
77
- });
78
- }
79
-
80
- function ensureCache() {
81
- for (const dir of [cacheRoot, projectCache]) {
82
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
83
- if (fs.lstatSync(dir).isSymbolicLink() || fs.statSync(dir).uid !== process.getuid()) {
84
- throw new Error(`Refusing unmanaged workspace directory: ${dir}`);
85
- }
86
- if (inside(drive, fs.realpathSync(dir))) {
87
- throw new Error(`Local workspace must be outside Coze Drive: ${dir}`);
88
- }
89
- }
90
- }
91
-
92
- async function locked(action, name = 'sync') {
93
- const lock = path.join(projectCache, `${name}.lock`);
94
- const started = Date.now();
95
- let missingOwner = 0;
96
- while (true) {
97
- if (interrupted) throw new Error('Interrupted');
98
- try { fs.mkdirSync(lock); break; } catch (error) {
99
- if (error.code !== 'EEXIST') throw error;
100
- }
101
- let alive = false;
102
- try {
103
- const pid = Number(fs.readFileSync(path.join(lock, 'pid'), 'utf8'));
104
- if (Number.isInteger(pid) && pid > 0) { process.kill(pid, 0); alive = true; }
105
- } catch (error) { if (error.code === 'EPERM') alive = true; }
106
- missingOwner = alive ? 0 : missingOwner + 1;
107
- if (missingOwner >= 2) {
108
- const stale = `${lock}.stale-${process.pid}`;
109
- try {
110
- fs.renameSync(lock, stale);
111
- fs.rmSync(stale, { recursive: true });
112
- } catch (error) { if (error.code !== 'ENOENT') throw error; }
113
- missingOwner = 0;
114
- continue;
115
- }
116
- if (Date.now() - started > 120000) throw new Error(`Timed out waiting for ${lock}`);
117
- await delay(500);
118
- }
119
- fs.writeFileSync(path.join(lock, 'pid'), String(process.pid));
120
- try { return await action(); }
121
- finally { fs.rmSync(lock, { recursive: true, force: true }); }
122
- }
123
-
124
- async function sync(local) {
125
- if (!fs.existsSync(path.join(source, 'package.json'))) {
126
- throw new Error(`Source project is unavailable: ${source}`);
127
- }
128
- fs.mkdirSync(local, { recursive: true });
129
- if (fs.realpathSync(local) !== local) throw new Error(`Refusing workspace symlink: ${local}`);
130
- // Checksums catch same-size edits with unchanged/epoch Drive mtimes. Do not use -t:
131
- // changed local files get fresh mtimes and unchanged files retain watcher snapshots.
132
- // Materialize source links so the framework never follows them back onto Drive.
133
- await run('rsync', [
134
- '-rLp', '--checksum', '--delete', '--delay-updates',
135
- ...exclusions.map(entry => `--exclude=${entry}`), `${source}/`, `${local}/`,
136
- ], source);
137
- }
138
-
139
- function readOptional(file) {
140
- try { return fs.readFileSync(file); }
141
- catch (error) { if (error.code === 'ENOENT') return Buffer.alloc(0); throw error; }
142
- }
143
-
144
- function installInput(root) {
145
- const hash = createHash('sha256');
146
- const visit = (dir, all = false) => {
147
- for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
148
- if (['node_modules', '.git', 'logs', '.pnpm-store', '.next', '.nuxt', '.output', 'dist', 'dist-web', 'dist-tt', 'dist-server', '.cache', '.turbo'].includes(entry.name)) continue;
149
- const file = path.join(dir, entry.name);
150
- // Follow source links just as rsync -L does, while detecting loops explicitly.
151
- const real = fs.realpathSync(file);
152
- if (entry.isDirectory() || (entry.isSymbolicLink() && fs.statSync(file).isDirectory())) {
153
- if (ancestors.has(real)) throw new Error(`Source symlink cycle: ${file}`);
154
- ancestors.add(real);
155
- visit(file, all || entry.name === 'patches');
156
- ancestors.delete(real);
157
- }
158
- else if (all || ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', '.npmrc', '.pnpmfile.cjs', '.pnpmfile.js'].includes(entry.name)) {
159
- hash.update(path.relative(root, file)).update('\0').update(readOptional(file)).update('\0');
160
- }
161
- }
162
- };
163
- const ancestors = new Set([fs.realpathSync(root)]);
164
- visit(root);
165
- return hash.digest('hex');
166
- }
167
-
168
- function localEnv(local) {
169
- return {
170
- ...process.env,
171
- COZE_TARO_LOCAL_ACTIVE: local,
172
- COZE_TARO_SOURCE_PATH: source,
173
- COZE_TARO_LOCAL_MIRROR: onDrive ? '1' : '',
174
- COZE_WORKSPACE_PATH: local,
175
- // Keep the existing discoverable log/PID location in the source project.
176
- COZE_LOG_DIR: process.env.COZE_LOG_DIR || path.join(source, 'logs'),
177
- PWD: local,
178
- INIT_CWD: local,
179
- ...(onDrive ? { npm_config_store_dir: path.join(cacheRoot, 'store') } : {}),
180
- };
181
- }
182
-
183
- async function install(local, mode) {
184
- const state = path.join(projectCache, `${mode}.install`);
185
- const version = execFileSync('pnpm', ['--version'], { encoding: 'utf8' }).trim();
186
- const fingerprint = () => digest([
187
- installInput(local), version, process.version, process.env.NODE_ENV,
188
- process.env.npm_config_production, process.env.NPM_CONFIG_PRODUCTION,
189
- process.env.PNPM_CONFIG_PRODUCTION,
190
- ].join('\n'));
191
- // Lifecycle scripts may generate files from arbitrary source, so do not reuse
192
- // their install based on dependency metadata alone. Read manifests as text here
193
- // (no executable hooks or private CLI dependencies in the standalone helper).
194
- const manifest = readOptional(path.join(local, 'package.json')).toString();
195
- const hasLifecycle = /"(?:install|postinstall|prepare)"\s*:/.test(manifest);
196
- const hasHooks = ['.pnpmfile.cjs', '.pnpmfile.js'].some(file => fs.existsSync(path.join(local, file))) ||
197
- /^\s*pnpmfile\s*=/m.test(readOptional(path.join(local, '.npmrc')).toString());
198
- if (!hasLifecycle && !hasHooks && readOptional(state).toString() === fingerprint() &&
199
- fs.existsSync(path.join(local, 'node_modules', '.modules.yaml'))) return;
200
- fs.rmSync(state, { force: true });
201
- const before = installInput(source);
202
- if (before !== installInput(local)) {
203
- throw new Error('Dependency inputs changed during sync; rerun dev_build.sh.');
204
- }
205
- await run('pnpm', [...installArgs, '--store-dir', path.join(cacheRoot, 'store')], local, localEnv(local));
206
- if (before !== installInput(source)) {
207
- throw new Error('Dependency inputs changed on Drive during installation; rerun dev_build.sh. Lockfile was not overwritten.');
208
- }
209
- const lockfile = path.join(local, 'pnpm-lock.yaml');
210
- if (fs.existsSync(lockfile) && !readOptional(lockfile).equals(readOptional(path.join(source, 'pnpm-lock.yaml')))) {
211
- const temporary = path.join(source, `.pnpm-lock.yaml.${process.pid}.tmp`);
212
- try {
213
- fs.copyFileSync(lockfile, temporary);
214
- fs.renameSync(temporary, path.join(source, 'pnpm-lock.yaml'));
215
- } finally { fs.rmSync(temporary, { force: true }); }
216
- }
217
- fs.writeFileSync(state, fingerprint());
218
- }
219
-
220
- async function watch(args) {
221
- const local = fs.realpathSync(process.env.COZE_TARO_LOCAL_ACTIVE || path.resolve(__dirname, '..', '..'));
222
- if (!onDrive) return run(args[0], args.slice(1), local);
223
- ensureCache();
224
- if (local !== path.join(fs.realpathSync(projectCache), 'dev')) {
225
- throw new Error(`Refusing to watch an unmanaged workspace: ${local}`);
226
- }
227
- const preparedInput = installInput(local);
228
- let running = true;
229
- let syncError;
230
- const command = run(args[0], args.slice(1), local).finally(() => { running = false; });
231
- const poll = (async () => {
232
- while (running && !interrupted) {
233
- await delay(1000);
234
- if (!running || interrupted) break;
235
- try {
236
- await locked(async () => {
237
- await sync(local);
238
- if (installInput(local) !== preparedInput) {
239
- throw new Error('Dependency inputs changed; rerun dev_build.sh and dev_run.sh before previewing.');
240
- }
241
- });
242
- }
243
- catch (error) {
244
- syncError = error;
245
- for (const child of children) stopTree(child);
246
- break;
247
- }
248
- }
249
- })();
250
- const results = await Promise.allSettled([command, poll]);
251
- if (syncError) throw syncError;
252
- if (results[0].status === 'rejected') throw results[0].reason;
253
- }
254
-
255
- async function syncOutputBack(local) {
256
- for (const output of outputDirs) {
257
- const localOutput = path.join(local, output);
258
- const sourceOutput = path.join(source, output);
259
- if (!fs.existsSync(localOutput)) continue;
260
- fs.rmSync(sourceOutput, { recursive: true, force: true });
261
- fs.mkdirSync(path.dirname(sourceOutput), { recursive: true });
262
- await run('rsync', ['-a', '--delete', `${localOutput}/`, `${sourceOutput}/`], local);
263
- }
264
- }
265
-
266
- function cleanOutputs(local) {
267
- for (const output of outputDirs) {
268
- fs.rmSync(path.join(local, output), { recursive: true, force: true });
269
- }
270
- }
271
-
272
- async function main() {
273
- const [action, ...args] = process.argv.slice(2);
274
- if (action === 'watch') return watch(args);
275
- if (!['prepare', 'dev', 'build', 'pack', 'validate'].includes(action)) throw new Error(`Unknown Taro action: ${action}`);
276
- const scriptByAction = {
277
- prepare: 'dev_build.sh',
278
- dev: 'dev_run.sh',
279
- build: 'deploy_build.sh',
280
- pack: 'pack.sh',
281
- validate: 'validate.sh',
282
- };
283
- if (!onDrive) {
284
- return run('bash', [path.join(source, '.cozeproj', 'scripts', scriptByAction[action]), ...args], source, localEnv(source));
285
- }
286
- ensureCache();
287
- const mode = action === 'validate' ? 'check' : action === 'build' || action === 'pack' ? 'build' : 'dev';
288
- const local = path.join(fs.realpathSync(projectCache), mode);
289
- console.log(`[taro] Source: ${source}\n[taro] Local ${mode} workspace: ${local}`);
290
- if (action === 'validate') {
291
- return locked(async () => {
292
- await locked(async () => {
293
- await sync(local);
294
- await install(local, mode);
295
- });
296
- await run('bash', [path.join(local, '.cozeproj', 'scripts', 'validate.sh'), ...args], local, localEnv(local));
297
- }, 'check');
298
- }
299
- await locked(async () => {
300
- await sync(local);
301
- await install(local, mode);
302
- }, mode);
303
- if (action === 'build' || action === 'pack') {
304
- cleanOutputs(local);
305
- }
306
- await run('bash', [path.join(local, '.cozeproj', 'scripts', scriptByAction[action]), ...args], local, localEnv(local));
307
- if (action === 'build' || action === 'pack') {
308
- await locked(async () => {
309
- await syncOutputBack(local);
310
- }, 'output');
311
- }
312
- }
313
-
314
- main().catch(error => {
315
- console.error(`[taro] ${error.message}`);
316
- process.exitCode = process.exitCode || 1;
317
- });