@coze-arch/cli 0.1.5 → 0.1.8-alpha.6e99f9

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 (45) 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 +382 -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__/nextjs/eslint.config.mjs +11 -5
  10. package/lib/__templates__/nextjs/next.config.ts +10 -0
  11. package/lib/__templates__/nextjs/package.json +2 -5
  12. package/lib/__templates__/nextjs/pnpm-lock.yaml +14 -945
  13. package/lib/__templates__/nextjs/scripts/build.ps1 +1 -1
  14. package/lib/__templates__/nextjs/scripts/build.sh +2 -2
  15. package/lib/__templates__/nextjs/scripts/dev.ps1 +2 -2
  16. package/lib/__templates__/nextjs/scripts/dev.sh +93 -10
  17. package/lib/__templates__/nextjs/scripts/prepare-next-dev-output.sh +122 -0
  18. package/lib/__templates__/nextjs/scripts/prepare-node-modules.sh +382 -0
  19. package/lib/__templates__/nextjs/scripts/prepare.sh +1 -4
  20. package/lib/__templates__/nextjs/src/app/layout.tsx +0 -4
  21. package/lib/__templates__/nextjs/src/server.ts +1 -1
  22. package/lib/__templates__/nuxt-vue/scripts/build.sh +1 -1
  23. package/lib/__templates__/nuxt-vue/scripts/dev.sh +11 -3
  24. package/lib/__templates__/nuxt-vue/scripts/prepare-node-modules.sh +382 -0
  25. package/lib/__templates__/nuxt-vue/scripts/prepare.sh +1 -4
  26. package/lib/__templates__/taro/.cozeproj/scripts/deploy_build.sh +5 -1
  27. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +4 -0
  28. package/lib/__templates__/taro/.cozeproj/scripts/dev_build.sh +3 -0
  29. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.ps1 +4 -2
  30. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +29 -7
  31. package/lib/__templates__/taro/.cozeproj/scripts/init_env.sh +4 -0
  32. package/lib/__templates__/taro/.cozeproj/scripts/pack.ps1 +3 -1
  33. package/lib/__templates__/taro/.cozeproj/scripts/pack.sh +8 -0
  34. package/lib/__templates__/taro/.cozeproj/scripts/prepare-node-modules.sh +382 -0
  35. package/lib/__templates__/taro/.cozeproj/scripts/validate.ps1 +3 -1
  36. package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +4 -0
  37. package/lib/__templates__/taro/_gitignore +1 -0
  38. package/lib/__templates__/vite/scripts/build.sh +1 -1
  39. package/lib/__templates__/vite/scripts/dev.ps1 +2 -2
  40. package/lib/__templates__/vite/scripts/dev.sh +11 -3
  41. package/lib/__templates__/vite/scripts/prepare-node-modules.sh +382 -0
  42. package/lib/__templates__/vite/scripts/prepare.sh +1 -4
  43. package/lib/cli.js +147 -16
  44. package/package.json +2 -2
  45. package/lib/__templates__/nextjs/.babelrc +0 -15
@@ -8,7 +8,7 @@ Write-Host "Installing dependencies..."
8
8
  if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
9
9
 
10
10
  Write-Host "Building the Next.js project..."
11
- & pnpm next build
11
+ & pnpm next build --webpack
12
12
  if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
13
13
 
14
14
  Write-Host "Bundling server with tsup..."
@@ -6,10 +6,10 @@ 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
- pnpm next build
12
+ pnpm next build --webpack
13
13
 
14
14
  echo "Bundling server with tsup..."
15
15
  pnpm tsup src/server.ts --format cjs --platform node --target node20 --outDir dist --no-splitting --no-minify
@@ -28,7 +28,7 @@ Stop-ListeningProcess $port
28
28
 
29
29
  <% if (process.env.NODE_ENV === 'test') { %>
30
30
  $env:PORT = "$port"
31
- & pnpm tsx watch src/server.ts
31
+ & pnpm next dev --webpack --hostname 0.0.0.0 --port $port
32
32
  exit $LASTEXITCODE
33
33
  <% } else { %>
34
34
  $logDirectory = if ($env:COZE_LOG_DIR) { $env:COZE_LOG_DIR } else { Join-Path $workspace "logs" }
@@ -45,7 +45,7 @@ if (Test-Path $pidFile) {
45
45
  $previousPort = $env:PORT
46
46
  $env:PORT = "$port"
47
47
  try {
48
- $process = Start-Process -FilePath $env:ComSpec -ArgumentList @("/d", "/s", "/c", "pnpm tsx watch src/server.ts") -WorkingDirectory $workspace -RedirectStandardOutput $logFile -RedirectStandardError $errorLogFile -PassThru
48
+ $process = Start-Process -FilePath $env:ComSpec -ArgumentList @("/d", "/s", "/c", "pnpm next dev --webpack --hostname 0.0.0.0 --port $port") -WorkingDirectory $workspace -RedirectStandardOutput $logFile -RedirectStandardError $errorLogFile -PassThru
49
49
  } finally {
50
50
  if ($null -eq $previousPort) { Remove-Item Env:PORT -ErrorAction SilentlyContinue } else { $env:PORT = $previousPort }
51
51
  }
@@ -52,24 +52,83 @@ kill_port_if_listening() {
52
52
  echo "Port ${DEPLOY_RUN_PORT} cleared."
53
53
  }
54
54
 
55
+ # Next 的路由清单由 watchpack 每次启动重新扫 src 下的 app/pages 目录现算,而 watchpack
56
+ # 不会进入软链目录,软链子树里的 page/route 会被静默跳过:既不注册路由,也不打任何日志,
57
+ # 表现为该路由一直 404 而 .next 里没有它的产物。这里只做提示,不改动源码。
58
+ warn_source_symlinks() {
59
+ local source_dir="${COZE_WORKSPACE_PATH}/src"
60
+ local links link
61
+ if [[ ! -e "${source_dir}" ]]; then
62
+ return
63
+ fi
64
+ links=$(find "${source_dir}" -type l 2>/dev/null || true)
65
+ if [[ -z "${links}" ]]; then
66
+ return
67
+ fi
68
+ echo "Warning: 以下路径是软链,Next 不会进入其中,里面的 page/route 不会被编译:" >&2
69
+ while IFS= read -r link; do
70
+ echo " ${link}" >&2
71
+ done <<< "${links}"
72
+ }
73
+
74
+ # 云盘挂载偶尔会把新建文件的 mtime 设为 Unix epoch,这种时间戳会让所有按 mtime 判断新旧的
75
+ # 环节(Next 的首轮路由扫描、webpack 的 timestamp 快照、tsbuildinfo)拿到错误的结论。
76
+ # 只修 mtime 确实异常的那些条目:无条件刷新整棵 app/pages 会让 webpack 每次启动都重算全部
77
+ # 路由文件的 hash,并让云盘把它们全部重传一遍。不跟随软链、不改文件内容。
78
+ # 判定 mtime 是否异常的下界。真实的项目文件不可能早于这个时间,落在它之前的只有被云盘写坏的
79
+ # 时间戳,所以用它把"需要修"和"本来就好"区分开。
80
+ DRIVE_MTIME_FLOOR="1971-01-01"
81
+
82
+ refresh_drive_route_mtimes() {
83
+ local drive_root="${COZE_DRIVE_ROOT:-/Coze/Drive}"
84
+ local project_root route_dir
85
+ project_root="$(pwd -P)"
86
+ if [[ -d "${drive_root}" ]]; then
87
+ drive_root="$(cd "${drive_root}" && pwd -P)"
88
+ else
89
+ drive_root="${drive_root%/}"
90
+ fi
91
+
92
+ case "${project_root}" in
93
+ "${drive_root}"|"${drive_root}"/*) ;;
94
+ *) return ;;
95
+ esac
96
+
97
+ for route_dir in "${COZE_WORKSPACE_PATH}/src/app" "${COZE_WORKSPACE_PATH}/src/pages"; do
98
+ [[ -d "${route_dir}" ]] || continue
99
+ if ! find -P "${route_dir}" \( -type f -o -type d \) ! -newermt "${DRIVE_MTIME_FLOOR}" -exec touch {} + 2>/dev/null; then
100
+ echo "Warning: failed to refresh Next route timestamps in ${route_dir}." >&2
101
+ fi
102
+ done
103
+ }
104
+
55
105
  <% if (process.env.NODE_ENV === 'test') { %>
56
106
  echo "Clearing port ${DEPLOY_RUN_PORT} before start."
57
107
  kill_port_if_listening
108
+ COZE_REUSE_NODE_MODULES=1 bash "${COZE_WORKSPACE_PATH}/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline --loglevel debug --reporter=append-only
109
+ bash "${COZE_WORKSPACE_PATH}/scripts/prepare-next-dev-output.sh"
110
+ warn_source_symlinks
111
+ refresh_drive_route_mtimes
58
112
  echo "Starting HTTP service on port ${DEPLOY_RUN_PORT} for dev..."
59
113
 
60
114
  # 测试环境保持服务为前台进程,便于 e2e 收集输出并停止进程。
61
- exec env PORT="${DEPLOY_RUN_PORT}" pnpm tsx watch src/server.ts
115
+ # 开发环境直接使用 Next dev CLI,避免云盘文件事件在 custom server 链路中丢失。
116
+ exec pnpm next dev --webpack --hostname 0.0.0.0 --port "${DEPLOY_RUN_PORT}"
62
117
  <% } else { %>
63
118
  LOG_DIR="${COZE_LOG_DIR:-${COZE_WORKSPACE_PATH}/logs}"
64
119
  LOG_FILE="${LOG_DIR}/app.log"
65
120
  PID_FILE="${LOG_DIR}/server.pid"
66
121
 
67
122
  # detached 出去的进程没人负责回收,超过这个时长就自己退出,避免端口与内存长期泄露。
68
- MAX_RUNTIME_SECONDS=1200
123
+ MAX_RUNTIME_SECONDS=3600
124
+
125
+ timeout_watchdog_enabled() {
126
+ [[ -z "${COZE_EVAL:-}" && -z "${COZE_PROJECT_TYPE:-}" ]]
127
+ }
69
128
 
70
129
  # 真正被 detach 的是这层 bash wrapper:它是进程组 leader,组内 watchdog 到点回收整组
71
- # (wrapper -> pnpm -> tsx -> node);被包的进程自己先退出时也顺手清空进程组,不留残余。
72
- RUN_WITH_TIMEOUT='
130
+ # (wrapper -> pnpm -> next -> node);被包的进程自己先退出时也顺手清空进程组,不留残余。
131
+ RUN_WITH_TIMEOUT="$(declare -f timeout_watchdog_enabled)"'
73
132
  timeout_seconds=$1
74
133
  shift
75
134
 
@@ -77,6 +136,7 @@ shift
77
136
  child_pid=$!
78
137
 
79
138
  # 先忽略 TERM,才能在向整组发 TERM(自己也在组里)之后存活下来补一发 KILL。
139
+ if timeout_watchdog_enabled; then
80
140
  ( trap "" TERM
81
141
  sleep "${timeout_seconds}"
82
142
  echo "[dev] 后台进程运行超过 ${timeout_seconds}s,回收进程组 $$。"
@@ -84,6 +144,7 @@ child_pid=$!
84
144
  sleep 5
85
145
  kill -KILL -- "-$$" 2>/dev/null || true
86
146
  ) &
147
+ fi
87
148
 
88
149
  wait "${child_pid}"
89
150
  kill -KILL -- "-$$" 2>/dev/null || true
@@ -132,7 +193,7 @@ stop_detached() {
132
193
  kill -KILL -- "-${pid}" 2>/dev/null || true
133
194
  }
134
195
 
135
- READY_RETRIES=10
196
+ READY_RETRIES=30
136
197
  READY_PROBE_HOSTS=("127.0.0.1" "::1")
137
198
  LOG_TAIL_LINES=40
138
199
 
@@ -142,6 +203,21 @@ dump_log() {
142
203
  echo "---- end of ${LOG_FILE} ----" >&2
143
204
  }
144
205
 
206
+ # 服务刚就绪时 app-paths-manifest.json 还是空的(dev 按需编译,没请求就没产物),
207
+ # 但 .next/dev/types/routes.d.ts 已经写出了本次启动发现到的全部路由。把它落进日志,
208
+ # 排查时才能区分“路由没被发现”和“路由发现了但还没编译”。
209
+ dump_discovered_routes() {
210
+ local route_types="${COZE_WORKSPACE_PATH}/.next/dev/types/routes.d.ts"
211
+ if [[ ! -f "${route_types}" ]]; then
212
+ return
213
+ fi
214
+ {
215
+ echo "---- discovered routes (${route_types}) ----"
216
+ grep -E '^type (AppRoutes|AppRouteHandlerRoutes|PageRoutes) ' "${route_types}" || true
217
+ echo "---- end of discovered routes ----"
218
+ } >> "${LOG_FILE}"
219
+ }
220
+
145
221
  # 仅用于无 ss/lsof 时的兜底探测。服务可能只 bind IPv4 loopback、只 bind IPv6 loopback
146
222
  # (server.listen(port, 'localhost') 会变成 ::1 独占),或 bind 双栈通配地址,
147
223
  port_connectable() {
@@ -215,7 +291,7 @@ wait_for_ready() {
215
291
  return 2
216
292
  }
217
293
 
218
- # 监听端口的是孙进程(pnpm -> tsx -> node),只清端口会漏掉上层 pnpm/tsx
294
+ # 监听端口的是孙进程(pnpm -> next -> node),只清端口会漏掉上层 pnpm/next
219
295
  # 所以先按上次记录的 PID 把整个进程组回收掉。
220
296
  if [[ -f "${PID_FILE}" ]]; then
221
297
  stop_detached "$(cat "${PID_FILE}" 2>/dev/null || true)"
@@ -224,6 +300,10 @@ fi
224
300
 
225
301
  echo "Clearing port ${DEPLOY_RUN_PORT} before start."
226
302
  kill_port_if_listening
303
+ COZE_REUSE_NODE_MODULES=1 bash "${COZE_WORKSPACE_PATH}/scripts/prepare-node-modules.sh" --prefer-frozen-lockfile --prefer-offline --loglevel debug --reporter=append-only
304
+ bash "${COZE_WORKSPACE_PATH}/scripts/prepare-next-dev-output.sh"
305
+ warn_source_symlinks
306
+ refresh_drive_route_mtimes
227
307
  echo "Starting HTTP service on port ${DEPLOY_RUN_PORT} for dev..."
228
308
 
229
309
  mkdir -p "${LOG_DIR}"
@@ -231,7 +311,7 @@ mkdir -p "${LOG_DIR}"
231
311
 
232
312
  export PORT="${DEPLOY_RUN_PORT}"
233
313
  server_pid="$(spawn_detached "${COZE_WORKSPACE_PATH}" "${LOG_FILE}" \
234
- "$(command -v pnpm)" tsx watch src/server.ts)"
314
+ "$(command -v pnpm)" next dev --webpack --hostname 0.0.0.0 --port "${DEPLOY_RUN_PORT}")"
235
315
  if [[ -z "${server_pid}" ]]; then
236
316
  echo "Dev server failed to start: 未获取到后台进程 PID。" >&2
237
317
  dump_log
@@ -247,8 +327,7 @@ fi
247
327
 
248
328
  if [[ "${ready_status}" -ne 0 ]]; then
249
329
  if [[ "${ready_status}" -eq 2 ]]; then
250
- # 进程还活着但迟迟没监听端口:多半是编译/运行时报错后 tsx watch 驻留,日志里有真正原因。
251
- echo "Dev server 在 ${READY_RETRIES} 次探测(每次 1s)内未就绪于端口 ${DEPLOY_RUN_PORT}(进程 ${server_pid} 仍在运行,已回收)。" >&2
330
+ echo "Dev server did not listen on port ${DEPLOY_RUN_PORT} after ${READY_RETRIES} attempts; stopping PID ${server_pid}." >&2
252
331
  else
253
332
  echo "Dev server failed to start. See ${LOG_FILE}." >&2
254
333
  fi
@@ -259,8 +338,12 @@ if [[ "${ready_status}" -ne 0 ]]; then
259
338
  exit 1
260
339
  fi
261
340
 
341
+ dump_discovered_routes
342
+
262
343
  echo "Dev server started (PID: ${server_pid}), listening on port ${DEPLOY_RUN_PORT}."
263
- echo "Auto stop after ${MAX_RUNTIME_SECONDS}s."
344
+ if timeout_watchdog_enabled; then
345
+ echo "Auto stop after ${MAX_RUNTIME_SECONDS}s."
346
+ fi
264
347
  echo "Log file: ${LOG_FILE}"
265
348
  echo "PID file: ${PID_FILE}"
266
349
  <% } %>
@@ -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"