agent-device-proxy 0.1.6 → 0.2.0
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/.env.example +19 -0
- package/ARCHITECTURE.md +263 -0
- package/README.md +43 -159
- package/dist/src/224.js +1 -1
- package/dist/src/36.js +12 -18
- package/dist/src/agent-proxy-server.d.ts +2 -0
- package/dist/src/bin.js +14 -2
- package/dist/src/client.d.ts +2 -0
- package/dist/src/dev.d.ts +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +1 -1
- package/dist/src/server/config.d.ts +15 -0
- package/dist/src/server/daemon-forward.d.ts +12 -0
- package/dist/src/server/daemon-state.d.ts +6 -0
- package/dist/src/server/errors.d.ts +9 -0
- package/dist/src/server/http.d.ts +9 -0
- package/dist/src/server/metro-bridge-descriptor.d.ts +4 -0
- package/dist/src/server/metro-bridge-proxy.d.ts +8 -0
- package/dist/src/server/metro-bridge-types.d.ts +33 -0
- package/dist/src/server/metro-bridge.d.ts +3 -0
- package/dist/src/server/metro.d.ts +25 -0
- package/dist/src/server/routes.d.ts +12 -0
- package/dist/src/server.d.ts +8 -0
- package/dist/src/server.js +1 -1
- package/dist/src/types.d.ts +60 -0
- package/package.json +13 -12
- package/scripts/macos-remote-setup.sh +1117 -0
- package/dist/src/402.js +0 -1
- package/dist/src/403.js +0 -2
- package/dist/src/47.js +0 -1
- package/dist/src/493.js +0 -1
- package/dist/src/metro-runtime.js +0 -1
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# SSH-friendly host setup for self-hosted macOS mobile execution nodes.
|
|
5
|
+
# Installs Xcode toolchain + simulator defaults, Android SDK/emulator defaults,
|
|
6
|
+
# Node.js toolchain, and CLI dependencies used by runtime scripts.
|
|
7
|
+
|
|
8
|
+
MACOS_SETUP_BREW_UPDATE="${MACOS_SETUP_BREW_UPDATE:-true}"
|
|
9
|
+
MACOS_SETUP_INSTALL_XCODE="${MACOS_SETUP_INSTALL_XCODE:-true}"
|
|
10
|
+
MACOS_SETUP_XCODE_VERSION="${MACOS_SETUP_XCODE_VERSION:-latest}"
|
|
11
|
+
MACOS_SETUP_STRICT_XCODE="${MACOS_SETUP_STRICT_XCODE:-true}"
|
|
12
|
+
MACOS_SETUP_INSTALL_IOS_RUNTIME="${MACOS_SETUP_INSTALL_IOS_RUNTIME:-true}"
|
|
13
|
+
MACOS_SETUP_IOS_SIMULATOR_NAME="${MACOS_SETUP_IOS_SIMULATOR_NAME:-iPhone 17 Pro}"
|
|
14
|
+
MACOS_SETUP_INSTALL_ANDROID="${MACOS_SETUP_INSTALL_ANDROID:-true}"
|
|
15
|
+
MACOS_SETUP_ANDROID_API="${MACOS_SETUP_ANDROID_API:-latest}"
|
|
16
|
+
MACOS_SETUP_ANDROID_DEVICE_PROFILE="${MACOS_SETUP_ANDROID_DEVICE_PROFILE:-pixel_9_pro_xl}"
|
|
17
|
+
MACOS_SETUP_ANDROID_AVD_NAME="${MACOS_SETUP_ANDROID_AVD_NAME:-Pixel_9_Pro_XL}"
|
|
18
|
+
MACOS_SETUP_INSTALL_AGENT_DEVICE="${MACOS_SETUP_INSTALL_AGENT_DEVICE:-true}"
|
|
19
|
+
MACOS_SETUP_INSTALL_COCOAPODS="${MACOS_SETUP_INSTALL_COCOAPODS:-true}"
|
|
20
|
+
MACOS_SETUP_INSTALL_ZULU_JDK="${MACOS_SETUP_INSTALL_ZULU_JDK:-${MACOS_SETUP_INSTALL_OPENJDK:-true}}"
|
|
21
|
+
MACOS_SETUP_SHELL_PROFILE="${MACOS_SETUP_SHELL_PROFILE:-$HOME/.zprofile}"
|
|
22
|
+
ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}"
|
|
23
|
+
BREW_READY="false"
|
|
24
|
+
BREW_UPDATED="false"
|
|
25
|
+
DRY_RUN="false"
|
|
26
|
+
DRY_RUN_INSTALLS=()
|
|
27
|
+
DRY_RUN_SKIPS=()
|
|
28
|
+
|
|
29
|
+
prime_default_macos_paths() {
|
|
30
|
+
local candidate
|
|
31
|
+
for candidate in /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sbin; do
|
|
32
|
+
if [[ -d "$candidate" && ":$PATH:" != *":$candidate:"* ]]; then
|
|
33
|
+
PATH="$candidate:$PATH"
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
export PATH
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
resolve_login_shell() {
|
|
40
|
+
if [[ -n "${SHELL:-}" && -x "${SHELL:-}" ]]; then
|
|
41
|
+
printf "%s\n" "$SHELL"
|
|
42
|
+
return
|
|
43
|
+
fi
|
|
44
|
+
if command -v dscl >/dev/null 2>&1; then
|
|
45
|
+
dscl . -read "/Users/$USER" UserShell 2>/dev/null | awk '/UserShell:/ {print $2; exit}'
|
|
46
|
+
return
|
|
47
|
+
fi
|
|
48
|
+
printf "\n"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
prime_login_shell_environment() {
|
|
52
|
+
local login_shell
|
|
53
|
+
login_shell="$(resolve_login_shell)"
|
|
54
|
+
if [[ -z "$login_shell" || ! -x "$login_shell" ]]; then
|
|
55
|
+
return
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
local shell_output
|
|
59
|
+
shell_output="$(
|
|
60
|
+
"$login_shell" -lc '
|
|
61
|
+
printf "__MACOS_REMOTE_SETUP_ENV_START__\n"
|
|
62
|
+
printf "PATH=%s\n" "$PATH"
|
|
63
|
+
printf "ANDROID_SDK_ROOT=%s\n" "${ANDROID_SDK_ROOT:-}"
|
|
64
|
+
printf "ANDROID_HOME=%s\n" "${ANDROID_HOME:-}"
|
|
65
|
+
printf "JAVA_HOME=%s\n" "${JAVA_HOME:-}"
|
|
66
|
+
printf "__MACOS_REMOTE_SETUP_ENV_END__\n"
|
|
67
|
+
' 2>/dev/null || true
|
|
68
|
+
)"
|
|
69
|
+
|
|
70
|
+
if [[ "$shell_output" != *"__MACOS_REMOTE_SETUP_ENV_START__"* ]]; then
|
|
71
|
+
return
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
local env_lines
|
|
75
|
+
env_lines="$(
|
|
76
|
+
printf "%s\n" "$shell_output" | awk '
|
|
77
|
+
$0 == "__MACOS_REMOTE_SETUP_ENV_START__" { capture=1; next }
|
|
78
|
+
$0 == "__MACOS_REMOTE_SETUP_ENV_END__" { capture=0; exit }
|
|
79
|
+
capture { print }
|
|
80
|
+
'
|
|
81
|
+
)"
|
|
82
|
+
|
|
83
|
+
local key value
|
|
84
|
+
while IFS='=' read -r key value; do
|
|
85
|
+
case "$key" in
|
|
86
|
+
PATH)
|
|
87
|
+
if [[ -n "$value" ]]; then
|
|
88
|
+
PATH="$value"
|
|
89
|
+
fi
|
|
90
|
+
;;
|
|
91
|
+
ANDROID_SDK_ROOT)
|
|
92
|
+
if [[ -n "$value" ]]; then
|
|
93
|
+
ANDROID_SDK_ROOT="$value"
|
|
94
|
+
fi
|
|
95
|
+
;;
|
|
96
|
+
ANDROID_HOME)
|
|
97
|
+
if [[ -n "$value" ]]; then
|
|
98
|
+
ANDROID_HOME="$value"
|
|
99
|
+
fi
|
|
100
|
+
;;
|
|
101
|
+
JAVA_HOME)
|
|
102
|
+
if [[ -n "$value" ]]; then
|
|
103
|
+
JAVA_HOME="$value"
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
done <<<"$env_lines"
|
|
108
|
+
|
|
109
|
+
export PATH ANDROID_SDK_ROOT ANDROID_HOME JAVA_HOME
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
normalize_bool() {
|
|
113
|
+
local raw
|
|
114
|
+
raw="$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
|
115
|
+
case "$raw" in
|
|
116
|
+
1|true|yes|on) printf "true\n" ;;
|
|
117
|
+
0|false|no|off) printf "false\n" ;;
|
|
118
|
+
*) echo "Error: expected boolean-like value, got '$1'." >&2; exit 64 ;;
|
|
119
|
+
esac
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
log() {
|
|
123
|
+
printf "[macos-remote-setup] %s\n" "$1"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
warn() {
|
|
127
|
+
printf "[macos-remote-setup] Warning: %s\n" "$1" >&2
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fail() {
|
|
131
|
+
printf "[macos-remote-setup] Error: %s\n" "$1" >&2
|
|
132
|
+
exit 64
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
usage() {
|
|
136
|
+
cat <<'EOF_USAGE'
|
|
137
|
+
Usage: macos-remote-setup.sh [--dry-run|-n] [--help|-h]
|
|
138
|
+
|
|
139
|
+
Options:
|
|
140
|
+
-n, --dry-run Detect and print what would be installed/skipped without applying changes.
|
|
141
|
+
-h, --help Show this help.
|
|
142
|
+
EOF_USAGE
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
parse_args() {
|
|
146
|
+
while [[ "$#" -gt 0 ]]; do
|
|
147
|
+
case "$1" in
|
|
148
|
+
-n|--dry-run)
|
|
149
|
+
DRY_RUN="true"
|
|
150
|
+
;;
|
|
151
|
+
-h|--help)
|
|
152
|
+
usage
|
|
153
|
+
exit 0
|
|
154
|
+
;;
|
|
155
|
+
*)
|
|
156
|
+
fail "unknown argument: $1"
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
shift
|
|
160
|
+
done
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
record_dry_run_install() {
|
|
164
|
+
local label="$1"
|
|
165
|
+
DRY_RUN_INSTALLS+=("$label")
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
record_dry_run_skip() {
|
|
169
|
+
local label="$1"
|
|
170
|
+
DRY_RUN_SKIPS+=("$label")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
log_dry_run_command() {
|
|
174
|
+
local rendered=""
|
|
175
|
+
local arg
|
|
176
|
+
for arg in "$@"; do
|
|
177
|
+
rendered+=$(printf "%q " "$arg")
|
|
178
|
+
done
|
|
179
|
+
log "[dry-run] would run: ${rendered% }"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
run_as_admin() {
|
|
183
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
184
|
+
if [[ "$(id -u)" == "0" ]]; then
|
|
185
|
+
log_dry_run_command "$@"
|
|
186
|
+
else
|
|
187
|
+
log_dry_run_command sudo "$@"
|
|
188
|
+
fi
|
|
189
|
+
return
|
|
190
|
+
fi
|
|
191
|
+
if [[ "$(id -u)" == "0" ]]; then
|
|
192
|
+
"$@"
|
|
193
|
+
return
|
|
194
|
+
fi
|
|
195
|
+
sudo "$@"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
ensure_macos_host() {
|
|
199
|
+
if [[ "$(uname -s)" != "Darwin" ]]; then
|
|
200
|
+
fail "this script requires macOS (Darwin)."
|
|
201
|
+
fi
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
ensure_homebrew() {
|
|
205
|
+
if command -v brew >/dev/null 2>&1; then
|
|
206
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
207
|
+
record_dry_run_skip "homebrew"
|
|
208
|
+
fi
|
|
209
|
+
return
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
213
|
+
record_dry_run_install "homebrew"
|
|
214
|
+
log "Homebrew not found."
|
|
215
|
+
log "[dry-run] would run: NONINTERACTIVE=1 /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
|
216
|
+
return
|
|
217
|
+
fi
|
|
218
|
+
log "Homebrew not found. Installing Homebrew."
|
|
219
|
+
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
load_brew_shellenv() {
|
|
223
|
+
local brew_bin
|
|
224
|
+
for brew_bin in /opt/homebrew/bin/brew /usr/local/bin/brew; do
|
|
225
|
+
if [[ -x "$brew_bin" ]]; then
|
|
226
|
+
eval "$("$brew_bin" shellenv)"
|
|
227
|
+
return
|
|
228
|
+
fi
|
|
229
|
+
done
|
|
230
|
+
fail "brew executable was not found after installation."
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
ensure_brew_ready() {
|
|
234
|
+
if [[ "$BREW_READY" == "true" ]]; then
|
|
235
|
+
return
|
|
236
|
+
fi
|
|
237
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
238
|
+
ensure_homebrew
|
|
239
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
240
|
+
BREW_READY="true"
|
|
241
|
+
return
|
|
242
|
+
fi
|
|
243
|
+
fi
|
|
244
|
+
load_brew_shellenv
|
|
245
|
+
BREW_READY="true"
|
|
246
|
+
if [[ "$MACOS_SETUP_BREW_UPDATE" == "true" && "$BREW_UPDATED" != "true" ]]; then
|
|
247
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
248
|
+
log_dry_run_command brew update
|
|
249
|
+
else
|
|
250
|
+
log "Running brew update."
|
|
251
|
+
brew update
|
|
252
|
+
fi
|
|
253
|
+
BREW_UPDATED="true"
|
|
254
|
+
fi
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
brew_install_formula_if_missing() {
|
|
258
|
+
local name="$1"
|
|
259
|
+
ensure_brew_ready
|
|
260
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
261
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
262
|
+
record_dry_run_install "brew formula: $name"
|
|
263
|
+
log_dry_run_command brew install "$name"
|
|
264
|
+
return
|
|
265
|
+
fi
|
|
266
|
+
fail "brew is required to install formula: $name"
|
|
267
|
+
fi
|
|
268
|
+
if brew list --formula "$name" >/dev/null 2>&1; then
|
|
269
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
270
|
+
record_dry_run_skip "brew formula: $name"
|
|
271
|
+
fi
|
|
272
|
+
return
|
|
273
|
+
fi
|
|
274
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
275
|
+
record_dry_run_install "brew formula: $name"
|
|
276
|
+
log_dry_run_command brew install "$name"
|
|
277
|
+
return
|
|
278
|
+
fi
|
|
279
|
+
log "Installing Homebrew formula: $name"
|
|
280
|
+
brew install "$name"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
brew_install_cask_if_missing() {
|
|
284
|
+
local name="$1"
|
|
285
|
+
ensure_brew_ready
|
|
286
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
287
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
288
|
+
record_dry_run_install "brew cask: $name"
|
|
289
|
+
log_dry_run_command brew install --cask "$name"
|
|
290
|
+
return
|
|
291
|
+
fi
|
|
292
|
+
fail "brew is required to install cask: $name"
|
|
293
|
+
fi
|
|
294
|
+
if brew list --cask "$name" >/dev/null 2>&1; then
|
|
295
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
296
|
+
record_dry_run_skip "brew cask: $name"
|
|
297
|
+
fi
|
|
298
|
+
return
|
|
299
|
+
fi
|
|
300
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
301
|
+
record_dry_run_install "brew cask: $name"
|
|
302
|
+
log_dry_run_command brew install --cask "$name"
|
|
303
|
+
return
|
|
304
|
+
fi
|
|
305
|
+
log "Installing Homebrew cask: $name"
|
|
306
|
+
brew install --cask "$name"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ensure_profile_export_block() {
|
|
310
|
+
local profile_path="$1"
|
|
311
|
+
local marker_begin="# >>> macos-remote-setup >>>"
|
|
312
|
+
local marker_end="# <<< macos-remote-setup <<<"
|
|
313
|
+
local block
|
|
314
|
+
block="$(cat <<EOF_BLOCK
|
|
315
|
+
$marker_begin
|
|
316
|
+
export ANDROID_SDK_ROOT="$ANDROID_SDK_ROOT"
|
|
317
|
+
export ANDROID_HOME="$ANDROID_SDK_ROOT"
|
|
318
|
+
if [[ -d "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" ]]; then
|
|
319
|
+
export JAVA_HOME="/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"
|
|
320
|
+
elif [[ -x "/usr/libexec/java_home" ]]; then
|
|
321
|
+
export JAVA_HOME="$(/usr/libexec/java_home -v 17 2>/dev/null || true)"
|
|
322
|
+
fi
|
|
323
|
+
if [[ -n "\${JAVA_HOME:-}" ]]; then
|
|
324
|
+
export PATH="\$JAVA_HOME/bin:\$PATH"
|
|
325
|
+
fi
|
|
326
|
+
export PATH="\$PATH:\$ANDROID_SDK_ROOT/platform-tools:\$ANDROID_SDK_ROOT/emulator:\$ANDROID_SDK_ROOT/cmdline-tools/latest/bin"
|
|
327
|
+
if [[ -d "\$HOME/.bun/bin" ]]; then
|
|
328
|
+
export PATH="\$HOME/.bun/bin:\$PATH"
|
|
329
|
+
fi
|
|
330
|
+
if command -v brew >/dev/null 2>&1; then
|
|
331
|
+
eval "\$(brew shellenv)"
|
|
332
|
+
fi
|
|
333
|
+
$marker_end
|
|
334
|
+
EOF_BLOCK
|
|
335
|
+
)"
|
|
336
|
+
|
|
337
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
338
|
+
if [[ -f "$profile_path" ]]; then
|
|
339
|
+
if grep -Fq "$marker_begin" "$profile_path"; then
|
|
340
|
+
record_dry_run_skip "shell profile block: $profile_path"
|
|
341
|
+
else
|
|
342
|
+
record_dry_run_install "shell profile block: $profile_path"
|
|
343
|
+
fi
|
|
344
|
+
else
|
|
345
|
+
record_dry_run_install "shell profile block: $profile_path"
|
|
346
|
+
fi
|
|
347
|
+
log_dry_run_command update-shell-profile "$profile_path"
|
|
348
|
+
return
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
mkdir -p "$(dirname "$profile_path")"
|
|
352
|
+
touch "$profile_path"
|
|
353
|
+
if grep -Fq "$marker_begin" "$profile_path"; then
|
|
354
|
+
awk -v begin="$marker_begin" -v end="$marker_end" -v replace="$block" '
|
|
355
|
+
$0 == begin { print replace; in_block=1; next }
|
|
356
|
+
$0 == end { in_block=0; next }
|
|
357
|
+
!in_block { print }
|
|
358
|
+
' "$profile_path" >"${profile_path}.tmp"
|
|
359
|
+
mv "${profile_path}.tmp" "$profile_path"
|
|
360
|
+
return
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
{
|
|
364
|
+
printf "\n%s\n" "$block"
|
|
365
|
+
} >>"$profile_path"
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
ensure_path_for_current_session() {
|
|
369
|
+
export ANDROID_HOME="$ANDROID_SDK_ROOT"
|
|
370
|
+
if [[ -d "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" ]]; then
|
|
371
|
+
export JAVA_HOME="/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"
|
|
372
|
+
elif [[ -x "/usr/libexec/java_home" ]]; then
|
|
373
|
+
export JAVA_HOME="$(/usr/libexec/java_home -v 17 2>/dev/null || true)"
|
|
374
|
+
fi
|
|
375
|
+
if [[ -n "${JAVA_HOME:-}" ]]; then
|
|
376
|
+
export PATH="$JAVA_HOME/bin:$PATH"
|
|
377
|
+
fi
|
|
378
|
+
export PATH="$PATH:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin"
|
|
379
|
+
if [[ -d "$HOME/.bun/bin" ]]; then
|
|
380
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
381
|
+
fi
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
find_sdkmanager_bin() {
|
|
385
|
+
find_android_cmdline_tool "sdkmanager"
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
find_avdmanager_bin() {
|
|
389
|
+
find_android_cmdline_tool "avdmanager"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
find_android_cmdline_tool() {
|
|
393
|
+
local tool_name="$1"
|
|
394
|
+
local candidate
|
|
395
|
+
local brew_prefix=""
|
|
396
|
+
if command -v "$tool_name" >/dev/null 2>&1; then
|
|
397
|
+
command -v "$tool_name"
|
|
398
|
+
return
|
|
399
|
+
fi
|
|
400
|
+
if command -v brew >/dev/null 2>&1; then
|
|
401
|
+
brew_prefix="$(brew --prefix 2>/dev/null || true)"
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
for candidate in \
|
|
405
|
+
"$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/$tool_name" \
|
|
406
|
+
"$brew_prefix/share/android-commandlinetools/cmdline-tools/latest/bin/$tool_name" \
|
|
407
|
+
"/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin/$tool_name" \
|
|
408
|
+
"/usr/local/share/android-commandlinetools/cmdline-tools/latest/bin/$tool_name"; do
|
|
409
|
+
if [[ -x "$candidate" ]]; then
|
|
410
|
+
printf "%s\n" "$candidate"
|
|
411
|
+
return
|
|
412
|
+
fi
|
|
413
|
+
done
|
|
414
|
+
printf "\n"
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
find_emulator_bin() {
|
|
418
|
+
if command -v emulator >/dev/null 2>&1; then
|
|
419
|
+
command -v emulator
|
|
420
|
+
return
|
|
421
|
+
fi
|
|
422
|
+
if [[ -x "$ANDROID_SDK_ROOT/emulator/emulator" ]]; then
|
|
423
|
+
printf "%s\n" "$ANDROID_SDK_ROOT/emulator/emulator"
|
|
424
|
+
return
|
|
425
|
+
fi
|
|
426
|
+
printf "\n"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
current_xcode_version() {
|
|
430
|
+
xcodebuild -version 2>/dev/null | awk '/^Xcode / {print $2; exit}' || true
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
resolve_latest_ios_runtime_identifier() {
|
|
434
|
+
xcrun simctl list runtimes --json 2>/dev/null | node -e '
|
|
435
|
+
const fs = require("node:fs");
|
|
436
|
+
const parsed = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
|
|
437
|
+
const runtimes = Array.isArray(parsed.runtimes) ? parsed.runtimes : [];
|
|
438
|
+
const ios = runtimes.filter((r) =>
|
|
439
|
+
r && typeof r.identifier === "string" &&
|
|
440
|
+
r.identifier.includes(".iOS-") &&
|
|
441
|
+
r.isAvailable !== false &&
|
|
442
|
+
!(typeof r.availability === "string" && r.availability.includes("unavailable"))
|
|
443
|
+
);
|
|
444
|
+
ios.sort((a, b) => String(a.identifier).localeCompare(String(b.identifier), undefined, { numeric: true }));
|
|
445
|
+
process.stdout.write(ios.length > 0 ? ios[ios.length - 1].identifier : "");
|
|
446
|
+
' || true
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
android_package_installed() {
|
|
450
|
+
local pkg="$1"
|
|
451
|
+
case "$pkg" in
|
|
452
|
+
platform-tools)
|
|
453
|
+
[[ -x "$ANDROID_SDK_ROOT/platform-tools/adb" ]]
|
|
454
|
+
;;
|
|
455
|
+
emulator)
|
|
456
|
+
[[ -x "$ANDROID_SDK_ROOT/emulator/emulator" ]]
|
|
457
|
+
;;
|
|
458
|
+
"cmdline-tools;latest")
|
|
459
|
+
[[ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]]
|
|
460
|
+
;;
|
|
461
|
+
*)
|
|
462
|
+
[[ -d "$ANDROID_SDK_ROOT/${pkg//;/\/}" ]]
|
|
463
|
+
;;
|
|
464
|
+
esac
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
resolve_java17_home() {
|
|
468
|
+
if [[ -d "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" ]]; then
|
|
469
|
+
printf "%s\n" "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"
|
|
470
|
+
return
|
|
471
|
+
fi
|
|
472
|
+
if [[ -x "/usr/libexec/java_home" ]]; then
|
|
473
|
+
/usr/libexec/java_home -v 17 2>/dev/null || true
|
|
474
|
+
return
|
|
475
|
+
fi
|
|
476
|
+
printf "\n"
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
find_zulu17_pkg() {
|
|
480
|
+
if ! command -v brew >/dev/null 2>&1; then
|
|
481
|
+
printf "\n"
|
|
482
|
+
return
|
|
483
|
+
fi
|
|
484
|
+
local cask_dir
|
|
485
|
+
cask_dir="$(brew --prefix)/Caskroom/zulu@17"
|
|
486
|
+
if [[ ! -d "$cask_dir" ]]; then
|
|
487
|
+
printf "\n"
|
|
488
|
+
return
|
|
489
|
+
fi
|
|
490
|
+
find "$cask_dir" -maxdepth 3 -type f -name "*.pkg" -print 2>/dev/null | head -n 1 || true
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
ensure_zulu17_jdk() {
|
|
494
|
+
if [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_ZULU_JDK")" != "true" ]]; then
|
|
495
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
496
|
+
record_dry_run_skip "zulu@17 jdk"
|
|
497
|
+
fi
|
|
498
|
+
return
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
local java_home
|
|
502
|
+
java_home="$(resolve_java17_home)"
|
|
503
|
+
if [[ -n "$java_home" ]]; then
|
|
504
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
505
|
+
record_dry_run_skip "zulu@17 jdk"
|
|
506
|
+
fi
|
|
507
|
+
export JAVA_HOME="$java_home"
|
|
508
|
+
export PATH="$JAVA_HOME/bin:$PATH"
|
|
509
|
+
return
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
513
|
+
record_dry_run_install "zulu@17 jdk"
|
|
514
|
+
fi
|
|
515
|
+
brew_install_cask_if_missing "zulu@17"
|
|
516
|
+
local zulu_pkg
|
|
517
|
+
zulu_pkg="$(find_zulu17_pkg)"
|
|
518
|
+
if [[ -n "$zulu_pkg" ]]; then
|
|
519
|
+
log "Installing Azul Zulu JDK 17 package."
|
|
520
|
+
run_as_admin installer -pkg "$zulu_pkg" -target /
|
|
521
|
+
else
|
|
522
|
+
warn "Zulu JDK installer package not found in Caskroom; manual install may be required."
|
|
523
|
+
brew info --cask zulu@17 || true
|
|
524
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
525
|
+
return
|
|
526
|
+
fi
|
|
527
|
+
fi
|
|
528
|
+
|
|
529
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
530
|
+
log "[dry-run] would export JAVA_HOME from installed JDK 17 path."
|
|
531
|
+
return
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
java_home="$(resolve_java17_home)"
|
|
535
|
+
if [[ -z "$java_home" ]]; then
|
|
536
|
+
fail "JDK 17 not detected after zulu@17 install. Install 'Double-Click to Install Azul Zulu JDK 17.pkg' from the zulu@17 Caskroom and rerun."
|
|
537
|
+
fi
|
|
538
|
+
|
|
539
|
+
export JAVA_HOME="$java_home"
|
|
540
|
+
export PATH="$JAVA_HOME/bin:$PATH"
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
ensure_xcodes_cli() {
|
|
544
|
+
if command -v xcodes >/dev/null 2>&1; then
|
|
545
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
546
|
+
record_dry_run_skip "xcodes cli"
|
|
547
|
+
fi
|
|
548
|
+
return
|
|
549
|
+
fi
|
|
550
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
551
|
+
record_dry_run_install "xcodes cli"
|
|
552
|
+
fi
|
|
553
|
+
ensure_brew_ready
|
|
554
|
+
log "Installing xcodes CLI."
|
|
555
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
556
|
+
log_dry_run_command brew tap xcodesorg/made
|
|
557
|
+
log_dry_run_command brew install xcodes
|
|
558
|
+
return
|
|
559
|
+
fi
|
|
560
|
+
brew tap xcodesorg/made >/dev/null 2>&1 || true
|
|
561
|
+
if ! brew install xcodes >/dev/null 2>&1; then
|
|
562
|
+
brew install xcodesorg/made/xcodes
|
|
563
|
+
fi
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
ensure_latest_xcode_install() {
|
|
567
|
+
local strict="$1"
|
|
568
|
+
local install_help
|
|
569
|
+
local -a install_args=("install")
|
|
570
|
+
|
|
571
|
+
if [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_XCODE")" != "true" ]]; then
|
|
572
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
573
|
+
record_dry_run_skip "xcode"
|
|
574
|
+
fi
|
|
575
|
+
return
|
|
576
|
+
fi
|
|
577
|
+
if [[ "$MACOS_SETUP_XCODE_VERSION" == "latest" && -n "$(resolve_latest_xcode_developer_dir)" ]]; then
|
|
578
|
+
log "Xcode app already present; skipping xcodes install."
|
|
579
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
580
|
+
record_dry_run_skip "xcode"
|
|
581
|
+
fi
|
|
582
|
+
return
|
|
583
|
+
fi
|
|
584
|
+
if [[ "$MACOS_SETUP_XCODE_VERSION" != "latest" ]]; then
|
|
585
|
+
local installed_version
|
|
586
|
+
installed_version="$(current_xcode_version)"
|
|
587
|
+
if [[ -n "$installed_version" && "$installed_version" == "$MACOS_SETUP_XCODE_VERSION" ]]; then
|
|
588
|
+
log "Requested Xcode version already active ($installed_version); skipping xcodes install."
|
|
589
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
590
|
+
record_dry_run_skip "xcode $MACOS_SETUP_XCODE_VERSION"
|
|
591
|
+
fi
|
|
592
|
+
return
|
|
593
|
+
fi
|
|
594
|
+
fi
|
|
595
|
+
|
|
596
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
597
|
+
if [[ "$MACOS_SETUP_XCODE_VERSION" == "latest" ]]; then
|
|
598
|
+
record_dry_run_install "xcode latest"
|
|
599
|
+
else
|
|
600
|
+
record_dry_run_install "xcode $MACOS_SETUP_XCODE_VERSION"
|
|
601
|
+
fi
|
|
602
|
+
fi
|
|
603
|
+
ensure_xcodes_cli
|
|
604
|
+
if command -v xcodes >/dev/null 2>&1; then
|
|
605
|
+
install_help="$(xcodes help install 2>/dev/null || xcodes install --help 2>/dev/null || true)"
|
|
606
|
+
else
|
|
607
|
+
install_help=""
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
if [[ "$MACOS_SETUP_XCODE_VERSION" == "latest" ]]; then
|
|
611
|
+
if grep -Fq -- "--latest" <<<"$install_help"; then
|
|
612
|
+
install_args+=("--latest")
|
|
613
|
+
else
|
|
614
|
+
install_args+=("latest")
|
|
615
|
+
fi
|
|
616
|
+
else
|
|
617
|
+
install_args+=("$MACOS_SETUP_XCODE_VERSION")
|
|
618
|
+
fi
|
|
619
|
+
|
|
620
|
+
if grep -Fq -- "--select" <<<"$install_help"; then
|
|
621
|
+
install_args+=("--select")
|
|
622
|
+
fi
|
|
623
|
+
|
|
624
|
+
log "Installing/selecting Xcode via xcodes (${install_args[*]})."
|
|
625
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
626
|
+
log_dry_run_command xcodes "${install_args[@]}"
|
|
627
|
+
return
|
|
628
|
+
fi
|
|
629
|
+
if ! xcodes "${install_args[@]}"; then
|
|
630
|
+
if [[ "$strict" == "true" ]]; then
|
|
631
|
+
fail "xcodes install failed. Authenticate with xcodes and rerun."
|
|
632
|
+
fi
|
|
633
|
+
warn "xcodes install failed. Continuing because strict mode is disabled."
|
|
634
|
+
fi
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
resolve_latest_xcode_developer_dir() {
|
|
638
|
+
local app_path
|
|
639
|
+
app_path="$(ls -dt /Applications/Xcode*.app 2>/dev/null | head -n 1 || true)"
|
|
640
|
+
if [[ -n "$app_path" && -d "$app_path/Contents/Developer" ]]; then
|
|
641
|
+
printf "%s\n" "$app_path/Contents/Developer"
|
|
642
|
+
return
|
|
643
|
+
fi
|
|
644
|
+
printf "\n"
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
ensure_xcode_cli_setup() {
|
|
648
|
+
local developer_dir
|
|
649
|
+
developer_dir="$(resolve_latest_xcode_developer_dir)"
|
|
650
|
+
if [[ -z "$developer_dir" ]]; then
|
|
651
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
652
|
+
warn "no local Xcode app currently detected; dry-run cannot validate xcode-select setup."
|
|
653
|
+
return
|
|
654
|
+
fi
|
|
655
|
+
fail "could not resolve /Applications/Xcode*.app. Install Xcode first."
|
|
656
|
+
fi
|
|
657
|
+
|
|
658
|
+
log "Setting active developer directory: $developer_dir"
|
|
659
|
+
run_as_admin xcode-select -s "$developer_dir"
|
|
660
|
+
run_as_admin xcodebuild -license accept >/dev/null 2>&1 || true
|
|
661
|
+
run_as_admin xcodebuild -runFirstLaunch >/dev/null 2>&1 || true
|
|
662
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
663
|
+
return
|
|
664
|
+
fi
|
|
665
|
+
|
|
666
|
+
if ! xcode-select -p >/dev/null 2>&1; then
|
|
667
|
+
fail "xcode-select is not configured after setup."
|
|
668
|
+
fi
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
ensure_ios_runtime_and_simulator() {
|
|
672
|
+
local runtime_identifier
|
|
673
|
+
runtime_identifier="$(resolve_latest_ios_runtime_identifier)"
|
|
674
|
+
if [[ -n "$runtime_identifier" ]]; then
|
|
675
|
+
log "iOS simulator runtime already available; skipping runtime download."
|
|
676
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
677
|
+
record_dry_run_skip "ios simulator runtime"
|
|
678
|
+
fi
|
|
679
|
+
elif [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_IOS_RUNTIME")" == "true" ]]; then
|
|
680
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
681
|
+
record_dry_run_install "ios simulator runtime"
|
|
682
|
+
fi
|
|
683
|
+
log "Attempting to ensure iOS simulator platform runtime."
|
|
684
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
685
|
+
log_dry_run_command xcodebuild -downloadPlatform iOS
|
|
686
|
+
else
|
|
687
|
+
xcodebuild -downloadPlatform iOS >/dev/null 2>&1 || warn "xcodebuild -downloadPlatform iOS failed; continuing."
|
|
688
|
+
runtime_identifier="$(resolve_latest_ios_runtime_identifier)"
|
|
689
|
+
fi
|
|
690
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
691
|
+
record_dry_run_skip "ios simulator runtime (disabled)"
|
|
692
|
+
fi
|
|
693
|
+
|
|
694
|
+
if [[ -z "$runtime_identifier" ]]; then
|
|
695
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
696
|
+
warn "no available iOS runtime currently; simulator creation plan may be incomplete in dry-run mode."
|
|
697
|
+
return
|
|
698
|
+
fi
|
|
699
|
+
fail "no available iOS simulator runtime found after setup."
|
|
700
|
+
fi
|
|
701
|
+
|
|
702
|
+
local existing_udid
|
|
703
|
+
existing_udid="$(xcrun simctl list devices --json 2>/dev/null | node -e '
|
|
704
|
+
const fs = require("node:fs");
|
|
705
|
+
const target = process.argv[1];
|
|
706
|
+
const parsed = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
|
|
707
|
+
for (const list of Object.values(parsed.devices || {})) {
|
|
708
|
+
if (!Array.isArray(list)) continue;
|
|
709
|
+
for (const device of list) {
|
|
710
|
+
if (!device || typeof device !== "object") continue;
|
|
711
|
+
if (device.name !== target) continue;
|
|
712
|
+
if (device.isAvailable === false) continue;
|
|
713
|
+
if (typeof device.udid === "string") {
|
|
714
|
+
process.stdout.write(device.udid);
|
|
715
|
+
process.exit(0);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
' "$MACOS_SETUP_IOS_SIMULATOR_NAME" || true)"
|
|
720
|
+
if [[ -n "$existing_udid" ]]; then
|
|
721
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
722
|
+
record_dry_run_skip "ios simulator: $MACOS_SETUP_IOS_SIMULATOR_NAME"
|
|
723
|
+
fi
|
|
724
|
+
return
|
|
725
|
+
fi
|
|
726
|
+
|
|
727
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
728
|
+
record_dry_run_install "ios simulator: $MACOS_SETUP_IOS_SIMULATOR_NAME"
|
|
729
|
+
fi
|
|
730
|
+
|
|
731
|
+
local device_type_identifier
|
|
732
|
+
device_type_identifier="$(xcrun simctl list devicetypes --json 2>/dev/null | node -e '
|
|
733
|
+
const fs = require("node:fs");
|
|
734
|
+
const target = process.argv[1].trim().toLowerCase();
|
|
735
|
+
const parsed = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
|
|
736
|
+
const list = Array.isArray(parsed.devicetypes) ? parsed.devicetypes : [];
|
|
737
|
+
const exact = list.find((e) =>
|
|
738
|
+
e && typeof e.name === "string" && typeof e.identifier === "string" &&
|
|
739
|
+
e.name.trim().toLowerCase() === target
|
|
740
|
+
);
|
|
741
|
+
if (exact) {
|
|
742
|
+
process.stdout.write(exact.identifier);
|
|
743
|
+
process.exit(0);
|
|
744
|
+
}
|
|
745
|
+
const fallback = list.find((e) =>
|
|
746
|
+
e && typeof e.name === "string" && typeof e.identifier === "string" && /^iphone/i.test(e.name)
|
|
747
|
+
);
|
|
748
|
+
if (fallback) process.stdout.write(fallback.identifier);
|
|
749
|
+
' "$MACOS_SETUP_IOS_SIMULATOR_NAME" || true)"
|
|
750
|
+
|
|
751
|
+
if [[ -z "$device_type_identifier" ]]; then
|
|
752
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
753
|
+
warn "unable to resolve iOS simulator device type in dry-run mode."
|
|
754
|
+
return
|
|
755
|
+
fi
|
|
756
|
+
fail "failed to resolve iOS simulator device type."
|
|
757
|
+
fi
|
|
758
|
+
|
|
759
|
+
log "Creating default iOS simulator: $MACOS_SETUP_IOS_SIMULATOR_NAME"
|
|
760
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
761
|
+
log_dry_run_command xcrun simctl create "$MACOS_SETUP_IOS_SIMULATOR_NAME" "$device_type_identifier" "$runtime_identifier"
|
|
762
|
+
return
|
|
763
|
+
fi
|
|
764
|
+
xcrun simctl create "$MACOS_SETUP_IOS_SIMULATOR_NAME" "$device_type_identifier" "$runtime_identifier" >/dev/null
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
ensure_node_and_package_managers() {
|
|
768
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
769
|
+
brew_install_formula_if_missing "node"
|
|
770
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
771
|
+
record_dry_run_skip "node"
|
|
772
|
+
fi
|
|
773
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
774
|
+
brew_install_formula_if_missing "jq"
|
|
775
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
776
|
+
record_dry_run_skip "jq"
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
if [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_COCOAPODS")" == "true" ]] && ! command -v pod >/dev/null 2>&1; then
|
|
780
|
+
brew_install_formula_if_missing "cocoapods"
|
|
781
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
782
|
+
if [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_COCOAPODS")" == "true" ]]; then
|
|
783
|
+
record_dry_run_skip "cocoapods"
|
|
784
|
+
else
|
|
785
|
+
record_dry_run_skip "cocoapods (disabled)"
|
|
786
|
+
fi
|
|
787
|
+
fi
|
|
788
|
+
ensure_zulu17_jdk
|
|
789
|
+
ensure_path_for_current_session
|
|
790
|
+
|
|
791
|
+
if command -v corepack >/dev/null 2>&1; then
|
|
792
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
793
|
+
log_dry_run_command corepack enable
|
|
794
|
+
else
|
|
795
|
+
corepack enable >/dev/null 2>&1 || true
|
|
796
|
+
fi
|
|
797
|
+
if ! command -v pnpm >/dev/null 2>&1; then
|
|
798
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
799
|
+
record_dry_run_install "pnpm"
|
|
800
|
+
log_dry_run_command corepack prepare pnpm@latest --activate
|
|
801
|
+
else
|
|
802
|
+
corepack prepare pnpm@latest --activate
|
|
803
|
+
fi
|
|
804
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
805
|
+
record_dry_run_skip "pnpm"
|
|
806
|
+
fi
|
|
807
|
+
if ! command -v yarn >/dev/null 2>&1; then
|
|
808
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
809
|
+
record_dry_run_install "yarn"
|
|
810
|
+
log_dry_run_command corepack prepare yarn@stable --activate
|
|
811
|
+
else
|
|
812
|
+
corepack prepare yarn@stable --activate
|
|
813
|
+
fi
|
|
814
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
815
|
+
record_dry_run_skip "yarn"
|
|
816
|
+
fi
|
|
817
|
+
else
|
|
818
|
+
local -a missing_pkg_managers=()
|
|
819
|
+
if ! command -v pnpm >/dev/null 2>&1; then
|
|
820
|
+
missing_pkg_managers+=("pnpm")
|
|
821
|
+
fi
|
|
822
|
+
if ! command -v yarn >/dev/null 2>&1; then
|
|
823
|
+
missing_pkg_managers+=("yarn")
|
|
824
|
+
fi
|
|
825
|
+
if [[ "${#missing_pkg_managers[@]}" -gt 0 ]]; then
|
|
826
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
827
|
+
local manager
|
|
828
|
+
for manager in "${missing_pkg_managers[@]}"; do
|
|
829
|
+
record_dry_run_install "$manager"
|
|
830
|
+
done
|
|
831
|
+
log_dry_run_command npm install -g "${missing_pkg_managers[@]}"
|
|
832
|
+
else
|
|
833
|
+
npm install -g "${missing_pkg_managers[@]}"
|
|
834
|
+
fi
|
|
835
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
836
|
+
record_dry_run_skip "pnpm"
|
|
837
|
+
record_dry_run_skip "yarn"
|
|
838
|
+
fi
|
|
839
|
+
fi
|
|
840
|
+
|
|
841
|
+
ensure_bun
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
ensure_bun() {
|
|
845
|
+
if command -v bun >/dev/null 2>&1; then
|
|
846
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
847
|
+
record_dry_run_skip "bun"
|
|
848
|
+
fi
|
|
849
|
+
return
|
|
850
|
+
fi
|
|
851
|
+
|
|
852
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
853
|
+
record_dry_run_install "bun"
|
|
854
|
+
log_dry_run_command /bin/bash -c "curl -fsSL https://bun.sh/install | bash"
|
|
855
|
+
return
|
|
856
|
+
fi
|
|
857
|
+
|
|
858
|
+
log "Installing Bun with the official install script."
|
|
859
|
+
/bin/bash -c "$(curl -fsSL https://bun.sh/install)"
|
|
860
|
+
if [[ -d "$HOME/.bun/bin" ]]; then
|
|
861
|
+
export PATH="$HOME/.bun/bin:$PATH"
|
|
862
|
+
fi
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
ensure_android_sdk_and_emulator() {
|
|
866
|
+
if [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_ANDROID")" != "true" ]]; then
|
|
867
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
868
|
+
record_dry_run_skip "android sdk/emulator"
|
|
869
|
+
fi
|
|
870
|
+
return
|
|
871
|
+
fi
|
|
872
|
+
|
|
873
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
874
|
+
log_dry_run_command mkdir -p "$ANDROID_SDK_ROOT"
|
|
875
|
+
else
|
|
876
|
+
mkdir -p "$ANDROID_SDK_ROOT"
|
|
877
|
+
fi
|
|
878
|
+
if ! command -v sdkmanager >/dev/null 2>&1 || ! command -v avdmanager >/dev/null 2>&1; then
|
|
879
|
+
brew_install_cask_if_missing "android-commandlinetools"
|
|
880
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
881
|
+
record_dry_run_skip "brew cask: android-commandlinetools"
|
|
882
|
+
fi
|
|
883
|
+
if ! command -v adb >/dev/null 2>&1; then
|
|
884
|
+
brew_install_formula_if_missing "android-platform-tools"
|
|
885
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
886
|
+
record_dry_run_skip "brew formula: android-platform-tools"
|
|
887
|
+
fi
|
|
888
|
+
ensure_path_for_current_session
|
|
889
|
+
if [[ -z "$(resolve_java17_home)" ]]; then
|
|
890
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
891
|
+
warn "java runtime 17 not found currently; dry-run cannot validate android sdk steps."
|
|
892
|
+
return
|
|
893
|
+
fi
|
|
894
|
+
fail "java runtime 17 not found. Enable MACOS_SETUP_INSTALL_ZULU_JDK=true and rerun."
|
|
895
|
+
fi
|
|
896
|
+
|
|
897
|
+
local sdkmanager_bin
|
|
898
|
+
sdkmanager_bin="$(find_sdkmanager_bin)"
|
|
899
|
+
if [[ -z "$sdkmanager_bin" ]]; then
|
|
900
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
901
|
+
warn "sdkmanager not found currently; dry-run cannot resolve exact Android package plan."
|
|
902
|
+
return
|
|
903
|
+
fi
|
|
904
|
+
fail "sdkmanager not found after android-commandlinetools install."
|
|
905
|
+
fi
|
|
906
|
+
local avdmanager_bin
|
|
907
|
+
avdmanager_bin="$(find_avdmanager_bin)"
|
|
908
|
+
if [[ -z "$avdmanager_bin" ]]; then
|
|
909
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
910
|
+
warn "avdmanager not found currently; dry-run cannot resolve exact Android AVD plan."
|
|
911
|
+
return
|
|
912
|
+
fi
|
|
913
|
+
fail "avdmanager not found after android-commandlinetools install."
|
|
914
|
+
fi
|
|
915
|
+
|
|
916
|
+
log "Accepting Android SDK licenses."
|
|
917
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
918
|
+
log_dry_run_command "$sdkmanager_bin" --sdk_root="$ANDROID_SDK_ROOT" --licenses
|
|
919
|
+
else
|
|
920
|
+
yes | "$sdkmanager_bin" --sdk_root="$ANDROID_SDK_ROOT" --licenses >/dev/null || true
|
|
921
|
+
fi
|
|
922
|
+
|
|
923
|
+
local sdk_list
|
|
924
|
+
sdk_list="$("$sdkmanager_bin" --sdk_root="$ANDROID_SDK_ROOT" --list | tr -d '\r')"
|
|
925
|
+
local api_level
|
|
926
|
+
if [[ "$MACOS_SETUP_ANDROID_API" == "latest" ]]; then
|
|
927
|
+
api_level="$(printf "%s\n" "$sdk_list" | grep -Eo 'system-images;android-[0-9]+;google_apis(_playstore)?;(arm64-v8a|x86_64)' | sed -E 's/^system-images;android-([0-9]+);.*$/\1/' | sort -uV | tail -n 1 || true)"
|
|
928
|
+
else
|
|
929
|
+
api_level="$MACOS_SETUP_ANDROID_API"
|
|
930
|
+
fi
|
|
931
|
+
|
|
932
|
+
if [[ -z "$api_level" ]]; then
|
|
933
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
934
|
+
warn "unable to resolve Android API level from current sdkmanager list in dry-run."
|
|
935
|
+
return
|
|
936
|
+
fi
|
|
937
|
+
fail "unable to resolve Android API level from sdkmanager system image list."
|
|
938
|
+
fi
|
|
939
|
+
|
|
940
|
+
local arch_suffix="x86_64"
|
|
941
|
+
if [[ "$(uname -m)" == "arm64" ]]; then
|
|
942
|
+
arch_suffix="arm64-v8a"
|
|
943
|
+
fi
|
|
944
|
+
|
|
945
|
+
local system_image_pkg=""
|
|
946
|
+
local candidate
|
|
947
|
+
for candidate in \
|
|
948
|
+
"system-images;android-$api_level;google_apis;$arch_suffix" \
|
|
949
|
+
"system-images;android-$api_level;google_apis_playstore;$arch_suffix"; do
|
|
950
|
+
if printf "%s\n" "$sdk_list" | grep -Fq "$candidate"; then
|
|
951
|
+
system_image_pkg="$candidate"
|
|
952
|
+
break
|
|
953
|
+
fi
|
|
954
|
+
done
|
|
955
|
+
if [[ -z "$system_image_pkg" ]]; then
|
|
956
|
+
fail "unable to resolve Android system image package for api=$api_level arch=$arch_suffix."
|
|
957
|
+
fi
|
|
958
|
+
|
|
959
|
+
local -a packages=("cmdline-tools;latest" "platform-tools" "emulator" "$system_image_pkg")
|
|
960
|
+
local -a missing_packages=()
|
|
961
|
+
local pkg
|
|
962
|
+
for pkg in "${packages[@]}"; do
|
|
963
|
+
if ! android_package_installed "$pkg"; then
|
|
964
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
965
|
+
record_dry_run_install "android sdk package: $pkg"
|
|
966
|
+
fi
|
|
967
|
+
missing_packages+=("$pkg")
|
|
968
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
969
|
+
record_dry_run_skip "android sdk package: $pkg"
|
|
970
|
+
fi
|
|
971
|
+
done
|
|
972
|
+
|
|
973
|
+
if [[ "${#missing_packages[@]}" -gt 0 ]]; then
|
|
974
|
+
log "Installing missing Android SDK packages: ${missing_packages[*]}"
|
|
975
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
976
|
+
log_dry_run_command "$sdkmanager_bin" --sdk_root="$ANDROID_SDK_ROOT" --install "${missing_packages[@]}"
|
|
977
|
+
else
|
|
978
|
+
"$sdkmanager_bin" --sdk_root="$ANDROID_SDK_ROOT" --install "${missing_packages[@]}"
|
|
979
|
+
fi
|
|
980
|
+
else
|
|
981
|
+
log "Android SDK packages already present; skipping sdkmanager install."
|
|
982
|
+
fi
|
|
983
|
+
|
|
984
|
+
local emulator_bin
|
|
985
|
+
emulator_bin="$(find_emulator_bin)"
|
|
986
|
+
if [[ -z "$emulator_bin" ]]; then
|
|
987
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
988
|
+
warn "emulator binary not found currently; dry-run cannot validate AVD creation step."
|
|
989
|
+
return
|
|
990
|
+
fi
|
|
991
|
+
fail "emulator binary not found after SDK package install."
|
|
992
|
+
fi
|
|
993
|
+
|
|
994
|
+
if ! "$emulator_bin" -list-avds | grep -Fxq "$MACOS_SETUP_ANDROID_AVD_NAME"; then
|
|
995
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
996
|
+
record_dry_run_install "android avd: $MACOS_SETUP_ANDROID_AVD_NAME"
|
|
997
|
+
fi
|
|
998
|
+
log "Creating default Android AVD: $MACOS_SETUP_ANDROID_AVD_NAME"
|
|
999
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
1000
|
+
log_dry_run_command "$avdmanager_bin" create avd -n "$MACOS_SETUP_ANDROID_AVD_NAME" -k "$system_image_pkg" -d "$MACOS_SETUP_ANDROID_DEVICE_PROFILE" --force
|
|
1001
|
+
return
|
|
1002
|
+
fi
|
|
1003
|
+
if ! (set +o pipefail; echo "no" | "$avdmanager_bin" create avd \
|
|
1004
|
+
-n "$MACOS_SETUP_ANDROID_AVD_NAME" \
|
|
1005
|
+
-k "$system_image_pkg" \
|
|
1006
|
+
-d "$MACOS_SETUP_ANDROID_DEVICE_PROFILE" \
|
|
1007
|
+
--force >/dev/null 2>&1); then
|
|
1008
|
+
(set +o pipefail; echo "no" | "$avdmanager_bin" create avd \
|
|
1009
|
+
-n "$MACOS_SETUP_ANDROID_AVD_NAME" \
|
|
1010
|
+
-k "$system_image_pkg" \
|
|
1011
|
+
--force >/dev/null)
|
|
1012
|
+
fi
|
|
1013
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
1014
|
+
record_dry_run_skip "android avd: $MACOS_SETUP_ANDROID_AVD_NAME"
|
|
1015
|
+
fi
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
ensure_agent_device_cli() {
|
|
1019
|
+
if [[ "$(normalize_bool "$MACOS_SETUP_INSTALL_AGENT_DEVICE")" != "true" ]]; then
|
|
1020
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
1021
|
+
record_dry_run_skip "agent-device"
|
|
1022
|
+
record_dry_run_skip "agent-device-proxy"
|
|
1023
|
+
fi
|
|
1024
|
+
return
|
|
1025
|
+
fi
|
|
1026
|
+
if command -v agent-device >/dev/null 2>&1 && command -v agent-device-proxy >/dev/null 2>&1; then
|
|
1027
|
+
log "agent-device and agent-device-proxy already installed; skipping npm global install."
|
|
1028
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
1029
|
+
record_dry_run_skip "agent-device"
|
|
1030
|
+
record_dry_run_skip "agent-device-proxy"
|
|
1031
|
+
fi
|
|
1032
|
+
return
|
|
1033
|
+
fi
|
|
1034
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
1035
|
+
record_dry_run_install "agent-device"
|
|
1036
|
+
record_dry_run_install "agent-device-proxy"
|
|
1037
|
+
log_dry_run_command npm install -g agent-device@latest agent-device-proxy@latest
|
|
1038
|
+
return
|
|
1039
|
+
fi
|
|
1040
|
+
log "Installing/updating global agent-device and agent-device-proxy CLIs."
|
|
1041
|
+
npm install -g agent-device@latest agent-device-proxy@latest
|
|
1042
|
+
|
|
1043
|
+
local missing_bins=()
|
|
1044
|
+
local candidate
|
|
1045
|
+
for candidate in agent-device agent-device-proxy; do
|
|
1046
|
+
if ! command -v "$candidate" >/dev/null 2>&1; then
|
|
1047
|
+
missing_bins+=("$candidate")
|
|
1048
|
+
fi
|
|
1049
|
+
done
|
|
1050
|
+
|
|
1051
|
+
if [[ "${#missing_bins[@]}" -gt 0 ]]; then
|
|
1052
|
+
fail "npm global install completed, but these commands are still unavailable on PATH: ${missing_bins[*]}"
|
|
1053
|
+
fi
|
|
1054
|
+
|
|
1055
|
+
log "Installed agent-device: $(command -v agent-device)"
|
|
1056
|
+
log "Installed agent-device-proxy: $(command -v agent-device-proxy)"
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
print_dry_run_summary() {
|
|
1060
|
+
local item
|
|
1061
|
+
if [[ "$DRY_RUN" != "true" ]]; then
|
|
1062
|
+
return
|
|
1063
|
+
fi
|
|
1064
|
+
|
|
1065
|
+
log "Dry-run summary:"
|
|
1066
|
+
if [[ "${#DRY_RUN_INSTALLS[@]}" -gt 0 ]]; then
|
|
1067
|
+
log " would install:"
|
|
1068
|
+
for item in "${DRY_RUN_INSTALLS[@]}"; do
|
|
1069
|
+
log " - $item"
|
|
1070
|
+
done
|
|
1071
|
+
else
|
|
1072
|
+
log " would install: none"
|
|
1073
|
+
fi
|
|
1074
|
+
|
|
1075
|
+
if [[ "${#DRY_RUN_SKIPS[@]}" -gt 0 ]]; then
|
|
1076
|
+
log " would skip:"
|
|
1077
|
+
for item in "${DRY_RUN_SKIPS[@]}"; do
|
|
1078
|
+
log " - $item"
|
|
1079
|
+
done
|
|
1080
|
+
else
|
|
1081
|
+
log " would skip: none"
|
|
1082
|
+
fi
|
|
1083
|
+
log "No changes were applied."
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
main() {
|
|
1087
|
+
parse_args "$@"
|
|
1088
|
+
ensure_macos_host
|
|
1089
|
+
prime_login_shell_environment
|
|
1090
|
+
prime_default_macos_paths
|
|
1091
|
+
MACOS_SETUP_BREW_UPDATE="$(normalize_bool "$MACOS_SETUP_BREW_UPDATE")"
|
|
1092
|
+
MACOS_SETUP_STRICT_XCODE="$(normalize_bool "$MACOS_SETUP_STRICT_XCODE")"
|
|
1093
|
+
|
|
1094
|
+
ensure_node_and_package_managers
|
|
1095
|
+
ensure_latest_xcode_install "$MACOS_SETUP_STRICT_XCODE"
|
|
1096
|
+
ensure_xcode_cli_setup
|
|
1097
|
+
ensure_ios_runtime_and_simulator
|
|
1098
|
+
ensure_android_sdk_and_emulator
|
|
1099
|
+
ensure_agent_device_cli
|
|
1100
|
+
ensure_profile_export_block "$MACOS_SETUP_SHELL_PROFILE"
|
|
1101
|
+
print_dry_run_summary
|
|
1102
|
+
|
|
1103
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
1104
|
+
log "Dry-run complete."
|
|
1105
|
+
return
|
|
1106
|
+
fi
|
|
1107
|
+
|
|
1108
|
+
log "Setup complete."
|
|
1109
|
+
log "Run these verification commands:"
|
|
1110
|
+
log " xcode-select -p"
|
|
1111
|
+
log " xcrun simctl list runtimes | grep iOS"
|
|
1112
|
+
log " pnpm --version && yarn --version && bun --version && node -v"
|
|
1113
|
+
log " sdkmanager --sdk_root=\"$ANDROID_SDK_ROOT\" --list | head -n 20"
|
|
1114
|
+
log " pnpm --filter @platform/runtime podman:ios:preflight"
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
main "$@"
|