@coze-arch/cli 0.1.4-alpha.7fcfb3 → 0.1.4-alpha.99c367
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +2 -0
- package/lib/__templates__/expo/.cozeproj/scripts/prepare-node-modules.sh +242 -52
- package/lib/__templates__/nextjs/eslint.config.mjs +11 -5
- package/lib/__templates__/nextjs/package.json +1 -1
- package/lib/__templates__/nextjs/scripts/build.sh +0 -1
- package/lib/__templates__/nextjs/scripts/dev.ps1 +2 -2
- package/lib/__templates__/nextjs/scripts/dev.sh +26 -4
- package/lib/__templates__/nextjs/scripts/prepare-node-modules.sh +242 -52
- package/lib/__templates__/nuxt-vue/scripts/prepare-node-modules.sh +242 -52
- package/lib/__templates__/taro/.cozeproj/scripts/prepare-node-modules.sh +242 -52
- package/lib/__templates__/vite/scripts/prepare-node-modules.sh +242 -52
- package/lib/cli.js +1 -1
- package/package.json +1 -1
- package/lib/__templates__/nextjs/scripts/prepare-next-output.sh +0 -44
|
@@ -40,84 +40,246 @@ case "$WORK_DIR" in
|
|
|
40
40
|
esac
|
|
41
41
|
|
|
42
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
|
+
lock_acquired=false
|
|
62
|
+
release_lock() {
|
|
63
|
+
local owner_pid=""
|
|
64
|
+
if [ "$lock_acquired" != true ] || [ ! -f "$LOCK_DIR/pid" ]; then
|
|
65
|
+
return 0
|
|
66
|
+
fi
|
|
67
|
+
IFS= read -r owner_pid < "$LOCK_DIR/pid" || true
|
|
68
|
+
if [ "$owner_pid" = "$$" ]; then
|
|
69
|
+
rm -f "$LOCK_DIR/pid"
|
|
70
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lock_started_at="$(date +%s)"
|
|
75
|
+
missing_owner_checks=0
|
|
43
76
|
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
77
|
+
if lock_owner_is_alive; then
|
|
78
|
+
missing_owner_checks=0
|
|
79
|
+
else
|
|
80
|
+
missing_owner_checks=$((missing_owner_checks + 1))
|
|
81
|
+
if [ "$missing_owner_checks" -ge 2 ]; then
|
|
82
|
+
stale_lock_dir="$LOCK_DIR.stale.$$"
|
|
83
|
+
if mv "$LOCK_DIR" "$stale_lock_dir" 2>/dev/null; then
|
|
84
|
+
echo "[node_modules] Recovering stale dependency preparation lock."
|
|
85
|
+
rm -rf "$stale_lock_dir"
|
|
86
|
+
fi
|
|
87
|
+
missing_owner_checks=0
|
|
88
|
+
continue
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
lock_now="$(date +%s)"
|
|
93
|
+
if [ $((lock_now - lock_started_at)) -ge "$LOCK_TIMEOUT_SECONDS" ]; then
|
|
94
|
+
echo "[node_modules] Timed out waiting ${LOCK_TIMEOUT_SECONDS}s for dependency preparation lock: $LOCK_DIR" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
44
97
|
echo "[node_modules] Waiting for another dependency preparation for this project..."
|
|
45
98
|
sleep 1
|
|
46
99
|
done
|
|
47
|
-
|
|
100
|
+
printf '%s\n' "$$" > "$LOCK_DIR/pid"
|
|
101
|
+
lock_acquired=true
|
|
102
|
+
trap release_lock EXIT
|
|
48
103
|
|
|
49
104
|
mkdir -p "$WORK_DIR"
|
|
105
|
+
INSTALL_STATE_FILE="$WORK_DIR/.install-state"
|
|
106
|
+
|
|
107
|
+
# Previous versions mirrored the whole project into WORK_DIR. That gives pnpm
|
|
108
|
+
# and framework dev servers two paths for the same source tree, which breaks
|
|
109
|
+
# file watching and on-demand compilation. Keep only managed local outputs.
|
|
110
|
+
cleanup_legacy_workspace() {
|
|
111
|
+
local workspace_dir="$1" entry name
|
|
112
|
+
if [ -L "$workspace_dir" ] || { [ -e "$workspace_dir" ] && [ ! -d "$workspace_dir" ]; }; then
|
|
113
|
+
rm -rf "$workspace_dir"
|
|
114
|
+
return
|
|
115
|
+
fi
|
|
116
|
+
if [ ! -d "$workspace_dir" ]; then
|
|
117
|
+
return 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
while IFS= read -r -d '' entry; do
|
|
121
|
+
name="$(basename "$entry")"
|
|
122
|
+
[ "$name" = "node_modules" ] || rm -rf "$entry"
|
|
123
|
+
done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
|
|
124
|
+
}
|
|
50
125
|
|
|
51
|
-
|
|
52
|
-
# workspace directories stay real too, so pnpm never writes them to Drive.
|
|
53
|
-
mirror_root() {
|
|
126
|
+
cleanup_legacy_shadow_project() {
|
|
54
127
|
local entry name
|
|
55
128
|
while IFS= read -r -d '' entry; do
|
|
56
129
|
name="$(basename "$entry")"
|
|
57
130
|
case "$name" in
|
|
58
|
-
node_modules|
|
|
59
|
-
client|server)
|
|
60
|
-
[ -f "$PROJECT_ROOT/$name/package.json" ] && continue
|
|
61
|
-
;;
|
|
131
|
+
node_modules|client|server|.install-state|.next) continue ;;
|
|
62
132
|
esac
|
|
63
133
|
rm -rf "$entry"
|
|
64
134
|
done < <(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -print0)
|
|
65
135
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
node_modules|pnpm-lock.yaml) continue ;;
|
|
70
|
-
client|server)
|
|
71
|
-
[ -f "$entry/package.json" ] && continue
|
|
72
|
-
;;
|
|
73
|
-
esac
|
|
74
|
-
ln -s "$entry" "$WORK_DIR/$name"
|
|
75
|
-
done < <(find "$PROJECT_ROOT" -mindepth 1 -maxdepth 1 -print0)
|
|
136
|
+
for workspace in client server; do
|
|
137
|
+
cleanup_legacy_workspace "$WORK_DIR/$workspace"
|
|
138
|
+
done
|
|
76
139
|
}
|
|
77
140
|
|
|
78
|
-
|
|
79
|
-
local
|
|
80
|
-
|
|
81
|
-
if [ -
|
|
82
|
-
|
|
141
|
+
link_install_entry() {
|
|
142
|
+
local source_path="$1" target_path="$2"
|
|
143
|
+
rm -rf "$target_path"
|
|
144
|
+
if [ -e "$source_path" ]; then
|
|
145
|
+
ln -s "$source_path" "$target_path"
|
|
83
146
|
fi
|
|
84
|
-
|
|
147
|
+
}
|
|
85
148
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
149
|
+
hash_install_input() {
|
|
150
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
151
|
+
sha256sum | awk '{print $1}'
|
|
152
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
153
|
+
shasum -a 256 | awk '{print $1}'
|
|
154
|
+
else
|
|
155
|
+
cksum | awk '{print $1 "-" $2}'
|
|
156
|
+
fi
|
|
157
|
+
}
|
|
89
158
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
159
|
+
calculate_install_fingerprint() {
|
|
160
|
+
local filename workspace
|
|
161
|
+
{
|
|
162
|
+
printf 'prepare-node-modules-version=2\n'
|
|
163
|
+
printf 'pnpm-version=%s\n' "$(pnpm --version)"
|
|
164
|
+
for filename in package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc; do
|
|
165
|
+
printf 'file=%s\n' "$filename"
|
|
166
|
+
if [ -f "$PROJECT_ROOT/$filename" ]; then
|
|
167
|
+
cat "$PROJECT_ROOT/$filename"
|
|
168
|
+
else
|
|
169
|
+
printf '<missing>\n'
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
172
|
+
for workspace in client server; do
|
|
173
|
+
printf 'file=%s/package.json\n' "$workspace"
|
|
174
|
+
if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
|
|
175
|
+
cat "$PROJECT_ROOT/$workspace/package.json"
|
|
176
|
+
else
|
|
177
|
+
printf '<missing>\n'
|
|
178
|
+
fi
|
|
179
|
+
done
|
|
180
|
+
} | hash_install_input
|
|
94
181
|
}
|
|
95
182
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
183
|
+
dependencies_are_ready() {
|
|
184
|
+
local workspace
|
|
185
|
+
[ -d "$WORK_DIR/node_modules" ] || return 1
|
|
186
|
+
for workspace in client server; do
|
|
187
|
+
if [ -f "$PROJECT_ROOT/$workspace/package.json" ] && [ ! -d "$WORK_DIR/$workspace/node_modules" ]; then
|
|
188
|
+
return 1
|
|
189
|
+
fi
|
|
190
|
+
done
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
write_install_state() {
|
|
194
|
+
local fingerprint="$1" temporary_state
|
|
195
|
+
temporary_state="$(mktemp "$WORK_DIR/.install-state.XXXXXX")"
|
|
196
|
+
printf '%s\n' "$fingerprint" > "$temporary_state"
|
|
197
|
+
mv -f "$temporary_state" "$INSTALL_STATE_FILE"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
prepare_next_output() {
|
|
201
|
+
local next_root="$PROJECT_ROOT/.next"
|
|
202
|
+
local next_dev="$next_root/dev"
|
|
203
|
+
local target="$WORK_DIR/.next/dev"
|
|
204
|
+
local current=""
|
|
205
|
+
if [ ! -f "$PROJECT_ROOT/next.config.js" ] &&
|
|
206
|
+
[ ! -f "$PROJECT_ROOT/next.config.mjs" ] &&
|
|
207
|
+
[ ! -f "$PROJECT_ROOT/next.config.ts" ]; then
|
|
208
|
+
return 0
|
|
100
209
|
fi
|
|
101
|
-
done
|
|
102
210
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
211
|
+
# Migrate links created by the previous implementation, which linked the
|
|
212
|
+
# entire .next directory. Keep the root local so production output stays in
|
|
213
|
+
# the project while only development output is redirected to local disk.
|
|
214
|
+
if [ -L "$next_root" ]; then
|
|
215
|
+
current="$(readlink "$next_root")"
|
|
216
|
+
case "$current" in
|
|
217
|
+
"$NM_ROOT"/*/.next)
|
|
218
|
+
rm "$next_root"
|
|
219
|
+
;;
|
|
220
|
+
*)
|
|
221
|
+
echo "[node_modules] refusing to replace unmanaged .next symlink: $next_root" >&2
|
|
222
|
+
exit 1
|
|
223
|
+
;;
|
|
224
|
+
esac
|
|
225
|
+
elif [ -e "$next_root" ] && [ ! -d "$next_root" ]; then
|
|
226
|
+
echo "[node_modules] refusing to replace non-directory Next output: $next_root" >&2
|
|
227
|
+
exit 1
|
|
228
|
+
fi
|
|
229
|
+
mkdir -p "$next_root"
|
|
108
230
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
231
|
+
if [ "${COZE_LOCAL_NEXT_OUTPUT:-}" != "1" ]; then
|
|
232
|
+
# .next/dev is separate from production output in Next 16. Leave its
|
|
233
|
+
# managed link in place so production preparation does not discard a dev
|
|
234
|
+
# cache or modify output used by a running dev server.
|
|
235
|
+
return 0
|
|
236
|
+
fi
|
|
114
237
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
fi
|
|
238
|
+
mkdir -p "$target"
|
|
239
|
+
if [ -L "$next_dev" ]; then
|
|
240
|
+
current="$(readlink "$next_dev")"
|
|
241
|
+
if [ "$current" = "$target" ]; then
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
case "$current" in
|
|
245
|
+
"$NM_ROOT"/*/.next/dev) rm "$next_dev" ;;
|
|
246
|
+
*)
|
|
247
|
+
echo "[node_modules] refusing to replace unmanaged .next/dev symlink: $next_dev" >&2
|
|
248
|
+
exit 1
|
|
249
|
+
;;
|
|
250
|
+
esac
|
|
251
|
+
elif [ -e "$next_dev" ]; then
|
|
252
|
+
rm -rf "$next_dev"
|
|
253
|
+
fi
|
|
254
|
+
# macOS 的 ln -sfn 不会直接替换实体目录,所以上面先清理,再覆盖链接。
|
|
255
|
+
ln -sfn "$target" "$next_dev"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
copy_install_metadata() {
|
|
259
|
+
local filename source_file target_file workspace
|
|
260
|
+
for filename in pnpm-lock.yaml pnpm-workspace.yaml .npmrc; do
|
|
261
|
+
source_file="$PROJECT_ROOT/$filename"
|
|
262
|
+
target_file="$WORK_DIR/$filename"
|
|
263
|
+
rm -f "$target_file"
|
|
264
|
+
if [ -f "$source_file" ]; then
|
|
265
|
+
cp "$source_file" "$target_file"
|
|
266
|
+
fi
|
|
267
|
+
done
|
|
268
|
+
|
|
269
|
+
link_install_entry "$PROJECT_ROOT/package.json" "$WORK_DIR/package.json"
|
|
270
|
+
link_install_entry "$PROJECT_ROOT/patches" "$WORK_DIR/patches"
|
|
271
|
+
link_install_entry "$PROJECT_ROOT/scripts" "$WORK_DIR/scripts"
|
|
272
|
+
|
|
273
|
+
for workspace in client server; do
|
|
274
|
+
if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
|
|
275
|
+
mkdir -p "$WORK_DIR/$workspace"
|
|
276
|
+
link_install_entry "$PROJECT_ROOT/$workspace/package.json" "$WORK_DIR/$workspace/package.json"
|
|
277
|
+
link_install_entry "$PROJECT_ROOT/$workspace/scripts" "$WORK_DIR/$workspace/scripts"
|
|
278
|
+
else
|
|
279
|
+
rm -rf "$WORK_DIR/$workspace"
|
|
280
|
+
fi
|
|
281
|
+
done
|
|
282
|
+
}
|
|
121
283
|
|
|
122
284
|
link_node_modules() {
|
|
123
285
|
local source_dir="$1" target_dir="$2"
|
|
@@ -139,11 +301,39 @@ link_node_modules() {
|
|
|
139
301
|
ln -s "$target_dir" "$source_dir/node_modules"
|
|
140
302
|
}
|
|
141
303
|
|
|
304
|
+
cleanup_legacy_shadow_project
|
|
305
|
+
install_fingerprint="$(calculate_install_fingerprint)"
|
|
306
|
+
copy_install_metadata
|
|
307
|
+
|
|
308
|
+
installed_fingerprint=""
|
|
309
|
+
if [ -f "$INSTALL_STATE_FILE" ]; then
|
|
310
|
+
IFS= read -r installed_fingerprint < "$INSTALL_STATE_FILE" || true
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
if [ "$installed_fingerprint" = "$install_fingerprint" ] && dependencies_are_ready; then
|
|
314
|
+
echo "[node_modules] Reusing unchanged dependencies in $WORK_DIR"
|
|
315
|
+
else
|
|
316
|
+
rm -f "$INSTALL_STATE_FILE"
|
|
317
|
+
echo "[node_modules] Installing dependencies in $WORK_DIR"
|
|
318
|
+
if ! (cd "$WORK_DIR" && pnpm install "$@"); then
|
|
319
|
+
exit 1
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
if [ -f "$WORK_DIR/pnpm-lock.yaml" ]; then
|
|
323
|
+
temporary_lockfile="$(mktemp "$PROJECT_ROOT/.pnpm-lock.yaml.XXXXXX")"
|
|
324
|
+
cp "$WORK_DIR/pnpm-lock.yaml" "$temporary_lockfile"
|
|
325
|
+
chmod 0644 "$temporary_lockfile"
|
|
326
|
+
mv -f "$temporary_lockfile" "$PROJECT_ROOT/pnpm-lock.yaml"
|
|
327
|
+
fi
|
|
328
|
+
write_install_state "$(calculate_install_fingerprint)"
|
|
329
|
+
fi
|
|
330
|
+
|
|
142
331
|
link_node_modules "$PROJECT_ROOT" "$WORK_DIR/node_modules"
|
|
143
332
|
for workspace in client server; do
|
|
144
333
|
if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
|
|
145
334
|
link_node_modules "$PROJECT_ROOT/$workspace" "$WORK_DIR/$workspace/node_modules"
|
|
146
335
|
fi
|
|
147
336
|
done
|
|
337
|
+
prepare_next_output
|
|
148
338
|
|
|
149
339
|
echo "[node_modules] Dependencies ready"
|
|
@@ -40,84 +40,246 @@ case "$WORK_DIR" in
|
|
|
40
40
|
esac
|
|
41
41
|
|
|
42
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
|
+
lock_acquired=false
|
|
62
|
+
release_lock() {
|
|
63
|
+
local owner_pid=""
|
|
64
|
+
if [ "$lock_acquired" != true ] || [ ! -f "$LOCK_DIR/pid" ]; then
|
|
65
|
+
return 0
|
|
66
|
+
fi
|
|
67
|
+
IFS= read -r owner_pid < "$LOCK_DIR/pid" || true
|
|
68
|
+
if [ "$owner_pid" = "$$" ]; then
|
|
69
|
+
rm -f "$LOCK_DIR/pid"
|
|
70
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lock_started_at="$(date +%s)"
|
|
75
|
+
missing_owner_checks=0
|
|
43
76
|
while ! mkdir "$LOCK_DIR" 2>/dev/null; do
|
|
77
|
+
if lock_owner_is_alive; then
|
|
78
|
+
missing_owner_checks=0
|
|
79
|
+
else
|
|
80
|
+
missing_owner_checks=$((missing_owner_checks + 1))
|
|
81
|
+
if [ "$missing_owner_checks" -ge 2 ]; then
|
|
82
|
+
stale_lock_dir="$LOCK_DIR.stale.$$"
|
|
83
|
+
if mv "$LOCK_DIR" "$stale_lock_dir" 2>/dev/null; then
|
|
84
|
+
echo "[node_modules] Recovering stale dependency preparation lock."
|
|
85
|
+
rm -rf "$stale_lock_dir"
|
|
86
|
+
fi
|
|
87
|
+
missing_owner_checks=0
|
|
88
|
+
continue
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
lock_now="$(date +%s)"
|
|
93
|
+
if [ $((lock_now - lock_started_at)) -ge "$LOCK_TIMEOUT_SECONDS" ]; then
|
|
94
|
+
echo "[node_modules] Timed out waiting ${LOCK_TIMEOUT_SECONDS}s for dependency preparation lock: $LOCK_DIR" >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
44
97
|
echo "[node_modules] Waiting for another dependency preparation for this project..."
|
|
45
98
|
sleep 1
|
|
46
99
|
done
|
|
47
|
-
|
|
100
|
+
printf '%s\n' "$$" > "$LOCK_DIR/pid"
|
|
101
|
+
lock_acquired=true
|
|
102
|
+
trap release_lock EXIT
|
|
48
103
|
|
|
49
104
|
mkdir -p "$WORK_DIR"
|
|
105
|
+
INSTALL_STATE_FILE="$WORK_DIR/.install-state"
|
|
106
|
+
|
|
107
|
+
# Previous versions mirrored the whole project into WORK_DIR. That gives pnpm
|
|
108
|
+
# and framework dev servers two paths for the same source tree, which breaks
|
|
109
|
+
# file watching and on-demand compilation. Keep only managed local outputs.
|
|
110
|
+
cleanup_legacy_workspace() {
|
|
111
|
+
local workspace_dir="$1" entry name
|
|
112
|
+
if [ -L "$workspace_dir" ] || { [ -e "$workspace_dir" ] && [ ! -d "$workspace_dir" ]; }; then
|
|
113
|
+
rm -rf "$workspace_dir"
|
|
114
|
+
return
|
|
115
|
+
fi
|
|
116
|
+
if [ ! -d "$workspace_dir" ]; then
|
|
117
|
+
return 0
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
while IFS= read -r -d '' entry; do
|
|
121
|
+
name="$(basename "$entry")"
|
|
122
|
+
[ "$name" = "node_modules" ] || rm -rf "$entry"
|
|
123
|
+
done < <(find "$workspace_dir" -mindepth 1 -maxdepth 1 -print0)
|
|
124
|
+
}
|
|
50
125
|
|
|
51
|
-
|
|
52
|
-
# workspace directories stay real too, so pnpm never writes them to Drive.
|
|
53
|
-
mirror_root() {
|
|
126
|
+
cleanup_legacy_shadow_project() {
|
|
54
127
|
local entry name
|
|
55
128
|
while IFS= read -r -d '' entry; do
|
|
56
129
|
name="$(basename "$entry")"
|
|
57
130
|
case "$name" in
|
|
58
|
-
node_modules|
|
|
59
|
-
client|server)
|
|
60
|
-
[ -f "$PROJECT_ROOT/$name/package.json" ] && continue
|
|
61
|
-
;;
|
|
131
|
+
node_modules|client|server|.install-state|.next) continue ;;
|
|
62
132
|
esac
|
|
63
133
|
rm -rf "$entry"
|
|
64
134
|
done < <(find "$WORK_DIR" -mindepth 1 -maxdepth 1 -print0)
|
|
65
135
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
node_modules|pnpm-lock.yaml) continue ;;
|
|
70
|
-
client|server)
|
|
71
|
-
[ -f "$entry/package.json" ] && continue
|
|
72
|
-
;;
|
|
73
|
-
esac
|
|
74
|
-
ln -s "$entry" "$WORK_DIR/$name"
|
|
75
|
-
done < <(find "$PROJECT_ROOT" -mindepth 1 -maxdepth 1 -print0)
|
|
136
|
+
for workspace in client server; do
|
|
137
|
+
cleanup_legacy_workspace "$WORK_DIR/$workspace"
|
|
138
|
+
done
|
|
76
139
|
}
|
|
77
140
|
|
|
78
|
-
|
|
79
|
-
local
|
|
80
|
-
|
|
81
|
-
if [ -
|
|
82
|
-
|
|
141
|
+
link_install_entry() {
|
|
142
|
+
local source_path="$1" target_path="$2"
|
|
143
|
+
rm -rf "$target_path"
|
|
144
|
+
if [ -e "$source_path" ]; then
|
|
145
|
+
ln -s "$source_path" "$target_path"
|
|
83
146
|
fi
|
|
84
|
-
|
|
147
|
+
}
|
|
85
148
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
149
|
+
hash_install_input() {
|
|
150
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
151
|
+
sha256sum | awk '{print $1}'
|
|
152
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
153
|
+
shasum -a 256 | awk '{print $1}'
|
|
154
|
+
else
|
|
155
|
+
cksum | awk '{print $1 "-" $2}'
|
|
156
|
+
fi
|
|
157
|
+
}
|
|
89
158
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
159
|
+
calculate_install_fingerprint() {
|
|
160
|
+
local filename workspace
|
|
161
|
+
{
|
|
162
|
+
printf 'prepare-node-modules-version=2\n'
|
|
163
|
+
printf 'pnpm-version=%s\n' "$(pnpm --version)"
|
|
164
|
+
for filename in package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc; do
|
|
165
|
+
printf 'file=%s\n' "$filename"
|
|
166
|
+
if [ -f "$PROJECT_ROOT/$filename" ]; then
|
|
167
|
+
cat "$PROJECT_ROOT/$filename"
|
|
168
|
+
else
|
|
169
|
+
printf '<missing>\n'
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
172
|
+
for workspace in client server; do
|
|
173
|
+
printf 'file=%s/package.json\n' "$workspace"
|
|
174
|
+
if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
|
|
175
|
+
cat "$PROJECT_ROOT/$workspace/package.json"
|
|
176
|
+
else
|
|
177
|
+
printf '<missing>\n'
|
|
178
|
+
fi
|
|
179
|
+
done
|
|
180
|
+
} | hash_install_input
|
|
94
181
|
}
|
|
95
182
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
183
|
+
dependencies_are_ready() {
|
|
184
|
+
local workspace
|
|
185
|
+
[ -d "$WORK_DIR/node_modules" ] || return 1
|
|
186
|
+
for workspace in client server; do
|
|
187
|
+
if [ -f "$PROJECT_ROOT/$workspace/package.json" ] && [ ! -d "$WORK_DIR/$workspace/node_modules" ]; then
|
|
188
|
+
return 1
|
|
189
|
+
fi
|
|
190
|
+
done
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
write_install_state() {
|
|
194
|
+
local fingerprint="$1" temporary_state
|
|
195
|
+
temporary_state="$(mktemp "$WORK_DIR/.install-state.XXXXXX")"
|
|
196
|
+
printf '%s\n' "$fingerprint" > "$temporary_state"
|
|
197
|
+
mv -f "$temporary_state" "$INSTALL_STATE_FILE"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
prepare_next_output() {
|
|
201
|
+
local next_root="$PROJECT_ROOT/.next"
|
|
202
|
+
local next_dev="$next_root/dev"
|
|
203
|
+
local target="$WORK_DIR/.next/dev"
|
|
204
|
+
local current=""
|
|
205
|
+
if [ ! -f "$PROJECT_ROOT/next.config.js" ] &&
|
|
206
|
+
[ ! -f "$PROJECT_ROOT/next.config.mjs" ] &&
|
|
207
|
+
[ ! -f "$PROJECT_ROOT/next.config.ts" ]; then
|
|
208
|
+
return 0
|
|
100
209
|
fi
|
|
101
|
-
done
|
|
102
210
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
211
|
+
# Migrate links created by the previous implementation, which linked the
|
|
212
|
+
# entire .next directory. Keep the root local so production output stays in
|
|
213
|
+
# the project while only development output is redirected to local disk.
|
|
214
|
+
if [ -L "$next_root" ]; then
|
|
215
|
+
current="$(readlink "$next_root")"
|
|
216
|
+
case "$current" in
|
|
217
|
+
"$NM_ROOT"/*/.next)
|
|
218
|
+
rm "$next_root"
|
|
219
|
+
;;
|
|
220
|
+
*)
|
|
221
|
+
echo "[node_modules] refusing to replace unmanaged .next symlink: $next_root" >&2
|
|
222
|
+
exit 1
|
|
223
|
+
;;
|
|
224
|
+
esac
|
|
225
|
+
elif [ -e "$next_root" ] && [ ! -d "$next_root" ]; then
|
|
226
|
+
echo "[node_modules] refusing to replace non-directory Next output: $next_root" >&2
|
|
227
|
+
exit 1
|
|
228
|
+
fi
|
|
229
|
+
mkdir -p "$next_root"
|
|
108
230
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
231
|
+
if [ "${COZE_LOCAL_NEXT_OUTPUT:-}" != "1" ]; then
|
|
232
|
+
# .next/dev is separate from production output in Next 16. Leave its
|
|
233
|
+
# managed link in place so production preparation does not discard a dev
|
|
234
|
+
# cache or modify output used by a running dev server.
|
|
235
|
+
return 0
|
|
236
|
+
fi
|
|
114
237
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
fi
|
|
238
|
+
mkdir -p "$target"
|
|
239
|
+
if [ -L "$next_dev" ]; then
|
|
240
|
+
current="$(readlink "$next_dev")"
|
|
241
|
+
if [ "$current" = "$target" ]; then
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
case "$current" in
|
|
245
|
+
"$NM_ROOT"/*/.next/dev) rm "$next_dev" ;;
|
|
246
|
+
*)
|
|
247
|
+
echo "[node_modules] refusing to replace unmanaged .next/dev symlink: $next_dev" >&2
|
|
248
|
+
exit 1
|
|
249
|
+
;;
|
|
250
|
+
esac
|
|
251
|
+
elif [ -e "$next_dev" ]; then
|
|
252
|
+
rm -rf "$next_dev"
|
|
253
|
+
fi
|
|
254
|
+
# macOS 的 ln -sfn 不会直接替换实体目录,所以上面先清理,再覆盖链接。
|
|
255
|
+
ln -sfn "$target" "$next_dev"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
copy_install_metadata() {
|
|
259
|
+
local filename source_file target_file workspace
|
|
260
|
+
for filename in pnpm-lock.yaml pnpm-workspace.yaml .npmrc; do
|
|
261
|
+
source_file="$PROJECT_ROOT/$filename"
|
|
262
|
+
target_file="$WORK_DIR/$filename"
|
|
263
|
+
rm -f "$target_file"
|
|
264
|
+
if [ -f "$source_file" ]; then
|
|
265
|
+
cp "$source_file" "$target_file"
|
|
266
|
+
fi
|
|
267
|
+
done
|
|
268
|
+
|
|
269
|
+
link_install_entry "$PROJECT_ROOT/package.json" "$WORK_DIR/package.json"
|
|
270
|
+
link_install_entry "$PROJECT_ROOT/patches" "$WORK_DIR/patches"
|
|
271
|
+
link_install_entry "$PROJECT_ROOT/scripts" "$WORK_DIR/scripts"
|
|
272
|
+
|
|
273
|
+
for workspace in client server; do
|
|
274
|
+
if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
|
|
275
|
+
mkdir -p "$WORK_DIR/$workspace"
|
|
276
|
+
link_install_entry "$PROJECT_ROOT/$workspace/package.json" "$WORK_DIR/$workspace/package.json"
|
|
277
|
+
link_install_entry "$PROJECT_ROOT/$workspace/scripts" "$WORK_DIR/$workspace/scripts"
|
|
278
|
+
else
|
|
279
|
+
rm -rf "$WORK_DIR/$workspace"
|
|
280
|
+
fi
|
|
281
|
+
done
|
|
282
|
+
}
|
|
121
283
|
|
|
122
284
|
link_node_modules() {
|
|
123
285
|
local source_dir="$1" target_dir="$2"
|
|
@@ -139,11 +301,39 @@ link_node_modules() {
|
|
|
139
301
|
ln -s "$target_dir" "$source_dir/node_modules"
|
|
140
302
|
}
|
|
141
303
|
|
|
304
|
+
cleanup_legacy_shadow_project
|
|
305
|
+
install_fingerprint="$(calculate_install_fingerprint)"
|
|
306
|
+
copy_install_metadata
|
|
307
|
+
|
|
308
|
+
installed_fingerprint=""
|
|
309
|
+
if [ -f "$INSTALL_STATE_FILE" ]; then
|
|
310
|
+
IFS= read -r installed_fingerprint < "$INSTALL_STATE_FILE" || true
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
if [ "$installed_fingerprint" = "$install_fingerprint" ] && dependencies_are_ready; then
|
|
314
|
+
echo "[node_modules] Reusing unchanged dependencies in $WORK_DIR"
|
|
315
|
+
else
|
|
316
|
+
rm -f "$INSTALL_STATE_FILE"
|
|
317
|
+
echo "[node_modules] Installing dependencies in $WORK_DIR"
|
|
318
|
+
if ! (cd "$WORK_DIR" && pnpm install "$@"); then
|
|
319
|
+
exit 1
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
if [ -f "$WORK_DIR/pnpm-lock.yaml" ]; then
|
|
323
|
+
temporary_lockfile="$(mktemp "$PROJECT_ROOT/.pnpm-lock.yaml.XXXXXX")"
|
|
324
|
+
cp "$WORK_DIR/pnpm-lock.yaml" "$temporary_lockfile"
|
|
325
|
+
chmod 0644 "$temporary_lockfile"
|
|
326
|
+
mv -f "$temporary_lockfile" "$PROJECT_ROOT/pnpm-lock.yaml"
|
|
327
|
+
fi
|
|
328
|
+
write_install_state "$(calculate_install_fingerprint)"
|
|
329
|
+
fi
|
|
330
|
+
|
|
142
331
|
link_node_modules "$PROJECT_ROOT" "$WORK_DIR/node_modules"
|
|
143
332
|
for workspace in client server; do
|
|
144
333
|
if [ -f "$PROJECT_ROOT/$workspace/package.json" ]; then
|
|
145
334
|
link_node_modules "$PROJECT_ROOT/$workspace" "$WORK_DIR/$workspace/node_modules"
|
|
146
335
|
fi
|
|
147
336
|
done
|
|
337
|
+
prepare_next_output
|
|
148
338
|
|
|
149
339
|
echo "[node_modules] Dependencies ready"
|