@osfactory/har 0.54.0 → 0.54.1
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/dist/index.js +3 -3
- package/dist/templates/har-boilerplate/agent-slot.sh +31 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +31 -0
- package/dist/templates/har-boilerplate-ios/README.md +18 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +31 -0
- package/dist/templates/har-boilerplate-ios/setup-infra.sh +11 -0
- package/dist/templates/har-boilerplate-ios/simulator.sh +106 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70292,9 +70292,9 @@ init_logging();
|
|
|
70292
70292
|
init_manifest();
|
|
70293
70293
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
70294
70294
|
var PROFILE_HINTS = {
|
|
70295
|
-
default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.
|
|
70296
|
-
cli: "CLI/library profile (typical SWE-bench) \u2014 no PM2. Optional Docker Compose via HARNESS_INFRA_SERVICES. Git worktree by default. Launch provisions toolchain declaratively (HARNESS_ECOSYSTEM auto-detects common ecosystems); verify must use resolved tool paths from .env.agent.<id>, never hardcoded interpreter or package-manager paths.
|
|
70297
|
-
ios: "iOS mobile app profile \u2014 xcodebuild + iOS Simulator in an isolated git worktree. Set HARNESS_XCODE_SCHEME, workspace/project, HARNESS_SIMULATOR_NAME, HARNESS_BUNDLE_ID. Launch writes XCODEBUILD_BIN to .env.agent.<id>; verify uses it.
|
|
70295
|
+
default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.",
|
|
70296
|
+
cli: "CLI/library profile (typical SWE-bench) \u2014 no PM2. Optional Docker Compose via HARNESS_INFRA_SERVICES. Git worktree by default. Launch provisions toolchain declaratively (HARNESS_ECOSYSTEM auto-detects common ecosystems); verify must use resolved tool paths from .env.agent.<id>, never hardcoded interpreter or package-manager paths.",
|
|
70297
|
+
ios: "iOS mobile app profile \u2014 xcodebuild + iOS Simulator in an isolated git worktree. Set HARNESS_XCODE_SCHEME, workspace/project, HARNESS_SIMULATOR_NAME, HARNESS_BUNDLE_ID. Launch writes XCODEBUILD_BIN to .env.agent.<id>; verify uses it."
|
|
70298
70298
|
};
|
|
70299
70299
|
function loadTemplate(name) {
|
|
70300
70300
|
const filePath = resolveTemplateFile(name);
|
|
@@ -445,6 +445,35 @@ try {
|
|
|
445
445
|
# optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
|
|
446
446
|
# SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
|
|
447
447
|
# SLOT_STATUS, SLOT_LAST_ERROR, SLOT_WORK_UNIT_ID, SLOT_ATTEMPT_ID
|
|
448
|
+
# Mission Control re-reads slot state only when something syncs, and a sync takes
|
|
449
|
+
# about twenty seconds — too long to make a slot change wait for it. Detached, and
|
|
450
|
+
# coalesced so a run of slot changes leaves one sync going, then one more if needed.
|
|
451
|
+
har_notify_control() {
|
|
452
|
+
local root bin lock
|
|
453
|
+
root="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
454
|
+
bin="$(command -v har 2>/dev/null || echo "${root}/node_modules/.bin/har")"
|
|
455
|
+
[ -x "$bin" ] || return 0
|
|
456
|
+
|
|
457
|
+
lock="${TMPDIR:-/tmp}/har-control-sync.$(id -u)"
|
|
458
|
+
: > "${lock}.pending"
|
|
459
|
+
mkdir "$lock" 2>/dev/null || return 0
|
|
460
|
+
(
|
|
461
|
+
trap "" HUP
|
|
462
|
+
trap 'rmdir "$lock" 2>/dev/null || true' EXIT
|
|
463
|
+
cd "$root" || exit 0
|
|
464
|
+
while [ -f "${lock}.pending" ]; do
|
|
465
|
+
rm -f "${lock}.pending"
|
|
466
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
467
|
+
timeout 120 "$bin" control sync || true
|
|
468
|
+
else
|
|
469
|
+
"$bin" control sync || true
|
|
470
|
+
fi
|
|
471
|
+
done
|
|
472
|
+
# Redirected as a whole, not per command: a background job that keeps the inherited
|
|
473
|
+
# stdout open makes any caller reading that pipe wait for it.
|
|
474
|
+
) >/dev/null 2>&1 &
|
|
475
|
+
}
|
|
476
|
+
|
|
448
477
|
write_slot_registry() {
|
|
449
478
|
local file
|
|
450
479
|
file="$(slot_registry_file "$SLOT_AGENT_ID")"
|
|
@@ -474,10 +503,12 @@ for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PR
|
|
|
474
503
|
}
|
|
475
504
|
fs.writeFileSync(process.argv[1], JSON.stringify(entry, null, 2) + "\n");
|
|
476
505
|
' "$file"
|
|
506
|
+
har_notify_control
|
|
477
507
|
}
|
|
478
508
|
|
|
479
509
|
remove_slot_registry() {
|
|
480
510
|
rm -f "$(slot_registry_file "$1")"
|
|
511
|
+
har_notify_control
|
|
481
512
|
}
|
|
482
513
|
|
|
483
514
|
# Exit 0 when the worktree has uncommitted or untracked changes.
|
|
@@ -427,6 +427,35 @@ try {
|
|
|
427
427
|
# optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
|
|
428
428
|
# SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
|
|
429
429
|
# SLOT_STATUS, SLOT_LAST_ERROR, SLOT_WORK_UNIT_ID, SLOT_ATTEMPT_ID
|
|
430
|
+
# Mission Control re-reads slot state only when something syncs, and a sync takes
|
|
431
|
+
# about twenty seconds — too long to make a slot change wait for it. Detached, and
|
|
432
|
+
# coalesced so a run of slot changes leaves one sync going, then one more if needed.
|
|
433
|
+
har_notify_control() {
|
|
434
|
+
local root bin lock
|
|
435
|
+
root="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
436
|
+
bin="$(command -v har 2>/dev/null || echo "${root}/node_modules/.bin/har")"
|
|
437
|
+
[ -x "$bin" ] || return 0
|
|
438
|
+
|
|
439
|
+
lock="${TMPDIR:-/tmp}/har-control-sync.$(id -u)"
|
|
440
|
+
: > "${lock}.pending"
|
|
441
|
+
mkdir "$lock" 2>/dev/null || return 0
|
|
442
|
+
(
|
|
443
|
+
trap "" HUP
|
|
444
|
+
trap 'rmdir "$lock" 2>/dev/null || true' EXIT
|
|
445
|
+
cd "$root" || exit 0
|
|
446
|
+
while [ -f "${lock}.pending" ]; do
|
|
447
|
+
rm -f "${lock}.pending"
|
|
448
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
449
|
+
timeout 120 "$bin" control sync || true
|
|
450
|
+
else
|
|
451
|
+
"$bin" control sync || true
|
|
452
|
+
fi
|
|
453
|
+
done
|
|
454
|
+
# Redirected as a whole, not per command: a background job that keeps the inherited
|
|
455
|
+
# stdout open makes any caller reading that pipe wait for it.
|
|
456
|
+
) >/dev/null 2>&1 &
|
|
457
|
+
}
|
|
458
|
+
|
|
430
459
|
write_slot_registry() {
|
|
431
460
|
local file
|
|
432
461
|
file="$(slot_registry_file "$SLOT_AGENT_ID")"
|
|
@@ -456,10 +485,12 @@ for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PR
|
|
|
456
485
|
}
|
|
457
486
|
fs.writeFileSync(process.argv[1], JSON.stringify(entry, null, 2) + "\n");
|
|
458
487
|
' "$file"
|
|
488
|
+
har_notify_control
|
|
459
489
|
}
|
|
460
490
|
|
|
461
491
|
remove_slot_registry() {
|
|
462
492
|
rm -f "$(slot_registry_file "$1")"
|
|
493
|
+
har_notify_control
|
|
463
494
|
}
|
|
464
495
|
|
|
465
496
|
# Exit 0 when the worktree has uncommitted or untracked changes.
|
|
@@ -109,6 +109,24 @@ launch fails and lists the models this machine can create.
|
|
|
109
109
|
If `HARNESS_SIMULATOR_NAME` is not a model but names an existing device, that
|
|
110
110
|
device is used as-is and never deleted — how a hand-renamed simulator is targeted.
|
|
111
111
|
|
|
112
|
+
### Running from an agent sandbox
|
|
113
|
+
|
|
114
|
+
`xcrun simctl` reaches CoreSimulatorService over XPC. Coding agents that sandbox
|
|
115
|
+
their shell (Codex, Claude Code and others) usually deny that lookup, so `simctl`
|
|
116
|
+
fails while `xcodebuild` keeps working — every simulator command then dies with
|
|
117
|
+
`CoreSimulatorService connection became invalid`. Nothing is missing on the machine.
|
|
118
|
+
|
|
119
|
+
Launch and teardown say so when it happens. Either run `har env launch <id>` and
|
|
120
|
+
`har env teardown <id>` from a normal terminal and let the agent work in the
|
|
121
|
+
worktree, or grant the agent unsandboxed access to `xcrun simctl` — the escalation
|
|
122
|
+
must cover every subcommand the harness uses (`list`, `create`, `boot`,
|
|
123
|
+
`bootstatus`, `shutdown`, `delete`), not just `list`.
|
|
124
|
+
|
|
125
|
+
The same messages distinguish a second cause: if `xcrun simctl` is missing from
|
|
126
|
+
the selected developer directory — no Xcode, or `xcode-select` pointing at the
|
|
127
|
+
Command Line Tools — they say so and print the current selection instead of
|
|
128
|
+
blaming a sandbox.
|
|
129
|
+
|
|
112
130
|
The device lands in `.env.agent.<id>` as `HARNESS_SIMULATOR_UDID`,
|
|
113
131
|
`HARNESS_SIMULATOR_DEVICE_NAME` and `HARNESS_IOS_DESTINATION` — the model stays in
|
|
114
132
|
`HARNESS_SIMULATOR_NAME`, so the two never mean the same thing in one place; what a slot holds is tracked in `.har/simulators/`.
|
|
@@ -95,6 +95,35 @@ try {
|
|
|
95
95
|
# optional: SLOT_SUFFIX, SLOT_WORKTREE_PATH, SLOT_BRANCH, SLOT_BASE_BRANCH,
|
|
96
96
|
# SLOT_BASE_COMMIT, SLOT_PORTS_JSON, SLOT_PREVIEW_URLS_JSON,
|
|
97
97
|
# SLOT_STATUS, SLOT_LAST_ERROR, SLOT_WORK_UNIT_ID, SLOT_ATTEMPT_ID
|
|
98
|
+
# Mission Control re-reads slot state only when something syncs, and a sync takes
|
|
99
|
+
# about twenty seconds — too long to make a slot change wait for it. Detached, and
|
|
100
|
+
# coalesced so a run of slot changes leaves one sync going, then one more if needed.
|
|
101
|
+
har_notify_control() {
|
|
102
|
+
local root bin lock
|
|
103
|
+
root="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
104
|
+
bin="$(command -v har 2>/dev/null || echo "${root}/node_modules/.bin/har")"
|
|
105
|
+
[ -x "$bin" ] || return 0
|
|
106
|
+
|
|
107
|
+
lock="${TMPDIR:-/tmp}/har-control-sync.$(id -u)"
|
|
108
|
+
: > "${lock}.pending"
|
|
109
|
+
mkdir "$lock" 2>/dev/null || return 0
|
|
110
|
+
(
|
|
111
|
+
trap "" HUP
|
|
112
|
+
trap 'rmdir "$lock" 2>/dev/null || true' EXIT
|
|
113
|
+
cd "$root" || exit 0
|
|
114
|
+
while [ -f "${lock}.pending" ]; do
|
|
115
|
+
rm -f "${lock}.pending"
|
|
116
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
117
|
+
timeout 120 "$bin" control sync || true
|
|
118
|
+
else
|
|
119
|
+
"$bin" control sync || true
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
# Redirected as a whole, not per command: a background job that keeps the inherited
|
|
123
|
+
# stdout open makes any caller reading that pipe wait for it.
|
|
124
|
+
) >/dev/null 2>&1 &
|
|
125
|
+
}
|
|
126
|
+
|
|
98
127
|
write_slot_registry() {
|
|
99
128
|
local file
|
|
100
129
|
file="$(slot_registry_file "$SLOT_AGENT_ID")"
|
|
@@ -124,10 +153,12 @@ for (const [key, env] of [["ports", "SLOT_PORTS_JSON"], ["previewUrls", "SLOT_PR
|
|
|
124
153
|
}
|
|
125
154
|
fs.writeFileSync(process.argv[1], JSON.stringify(entry, null, 2) + "\n");
|
|
126
155
|
' "$file"
|
|
156
|
+
har_notify_control
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
remove_slot_registry() {
|
|
130
160
|
rm -f "$(slot_registry_file "$1")"
|
|
161
|
+
har_notify_control
|
|
131
162
|
}
|
|
132
163
|
|
|
133
164
|
# Exit 0 when the worktree has uncommitted or untracked changes.
|
|
@@ -19,6 +19,14 @@ source "$SCRIPT_DIR/simulator.sh"
|
|
|
19
19
|
|
|
20
20
|
log() { echo "==> $*" >&2; }
|
|
21
21
|
|
|
22
|
+
# simctl could not be asked, so nothing it did not return is evidence of anything
|
|
23
|
+
# missing on this machine. The wording lives in simulator.sh so launch and this
|
|
24
|
+
# script cannot drift apart on which of the two causes they name.
|
|
25
|
+
fail_simctl_unavailable() {
|
|
26
|
+
har_sim_log_simctl_unavailable
|
|
27
|
+
exit 1
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
# ── Xcode check ───────────────────────────────────────────────────────────────
|
|
23
31
|
if ! command -v xcodebuild >/dev/null 2>&1; then
|
|
24
32
|
echo "Error: xcodebuild not found — install Xcode from the App Store." >&2
|
|
@@ -40,6 +48,8 @@ if har_sim_per_slot_enabled; then
|
|
|
40
48
|
PLAN_STATUS="${PLAN%%$'\t'*}"
|
|
41
49
|
if [ "$PLAN_STATUS" = "OK" ]; then
|
|
42
50
|
log "Per-slot simulators: launch creates one $(printf '%s' "$PLAN" | cut -f3) per agent."
|
|
51
|
+
elif [ "$PLAN_STATUS" = "SIMCTL_UNAVAILABLE" ]; then
|
|
52
|
+
fail_simctl_unavailable
|
|
43
53
|
elif [ "$PLAN_STATUS" = "NO_MODEL" ] && [ -n "$(har_sim_device_by_name "${HARNESS_SIMULATOR_NAME:-}")" ]; then
|
|
44
54
|
# Supported fallback: the name is an existing device rather than a model.
|
|
45
55
|
log "Per-slot simulators: launch reuses the existing device '${SIMULATOR_NAME}'."
|
|
@@ -63,6 +73,7 @@ else
|
|
|
63
73
|
UDID="${RESOLVED%%$'\t'*}"
|
|
64
74
|
|
|
65
75
|
if [ -z "$UDID" ]; then
|
|
76
|
+
har_sim_simctl_available || fail_simctl_unavailable
|
|
66
77
|
echo "Error: Simulator '${SIMULATOR_NAME}' not found in available devices." >&2
|
|
67
78
|
echo " Available simulators:" >&2
|
|
68
79
|
xcrun simctl list devices available 2>/dev/null | grep -E 'iPhone|iPad' | head -20 >&2
|
|
@@ -108,10 +108,25 @@ har_sim_claim_owner_of() {
|
|
|
108
108
|
|
|
109
109
|
# ── Device discovery ──────────────────────────────────────────────────────────
|
|
110
110
|
|
|
111
|
+
# Every simctl call goes through here, so "simctl could not be asked" stays
|
|
112
|
+
# distinguishable from "simctl answered, and the answer is empty" at the one place
|
|
113
|
+
# that still knows the difference. Non-zero means the first; callers check it
|
|
114
|
+
# instead of reading an empty list as a fact about the machine.
|
|
115
|
+
har_sim_simctl() {
|
|
116
|
+
local out rc=0
|
|
117
|
+
out="$(xcrun simctl "$@" 2>/dev/null)" || rc=$?
|
|
118
|
+
if [ "$rc" -ne 0 ] || [ -z "$out" ]; then
|
|
119
|
+
return 1
|
|
120
|
+
fi
|
|
121
|
+
printf '%s' "$out"
|
|
122
|
+
}
|
|
123
|
+
|
|
111
124
|
# Unavailable devices are skipped: a device whose runtime was uninstalled must
|
|
112
125
|
# not pass validation only to fail at boot.
|
|
113
126
|
har_sim_device_field() {
|
|
114
|
-
|
|
127
|
+
local devices
|
|
128
|
+
devices="$(har_sim_simctl list devices --json)" || return 1
|
|
129
|
+
printf '%s' "$devices" | node -e '
|
|
115
130
|
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
116
131
|
const [want, field] = process.argv.slice(1);
|
|
117
132
|
const device = Object.values(data.devices || {})
|
|
@@ -128,9 +143,10 @@ har_sim_device_name() { har_sim_device_field "$1" name; }
|
|
|
128
143
|
# UDID of an existing device with this exact name, newest runtime first.
|
|
129
144
|
# A substring match would resolve "iPhone 16" to "iPhone 16 Pro Max".
|
|
130
145
|
har_sim_device_by_name() {
|
|
131
|
-
local want="${1:-}"
|
|
146
|
+
local want="${1:-}" devices
|
|
132
147
|
[ -n "$want" ] || return 0
|
|
133
|
-
|
|
148
|
+
devices="$(har_sim_simctl list devices available --json)" || return 1
|
|
149
|
+
printf '%s' "$devices" | node -e '
|
|
134
150
|
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
135
151
|
const want = process.argv[1];
|
|
136
152
|
const rows = [];
|
|
@@ -157,22 +173,66 @@ har_sim_resolve_configured() {
|
|
|
157
173
|
printf '%s\t%s' "$udid" "${HARNESS_SIMULATOR_NAME}"
|
|
158
174
|
}
|
|
159
175
|
|
|
160
|
-
# `simctl list` is a subprocess and a JSON parse
|
|
161
|
-
#
|
|
176
|
+
# `simctl list` is a subprocess and a JSON parse, so each answer is cached — but in
|
|
177
|
+
# the calling shell, not the process: `$(har_sim_runtimes_json)` fetches, then throws
|
|
178
|
+
# the cache and the availability flag away with the subshell that held them. Call
|
|
179
|
+
# these directly and read the variable whenever either one matters.
|
|
162
180
|
har_sim_devicetypes_json() {
|
|
163
181
|
if [ -z "${_HAR_SIM_TYPES_JSON:-}" ]; then
|
|
164
|
-
_HAR_SIM_TYPES_JSON="$(
|
|
182
|
+
_HAR_SIM_TYPES_JSON="$(har_sim_simctl list devicetypes --json)" || _HAR_SIM_TYPES_JSON='{}'
|
|
165
183
|
fi
|
|
166
184
|
printf '%s' "$_HAR_SIM_TYPES_JSON"
|
|
167
185
|
}
|
|
168
186
|
|
|
169
187
|
har_sim_runtimes_json() {
|
|
170
188
|
if [ -z "${_HAR_SIM_RUNTIMES_JSON:-}" ]; then
|
|
171
|
-
_HAR_SIM_RUNTIMES_JSON="$(
|
|
189
|
+
if _HAR_SIM_RUNTIMES_JSON="$(har_sim_simctl list runtimes --json)"; then
|
|
190
|
+
_HAR_SIM_SIMCTL_OK=true
|
|
191
|
+
else
|
|
192
|
+
_HAR_SIM_RUNTIMES_JSON='{}'
|
|
193
|
+
_HAR_SIM_SIMCTL_OK=false
|
|
194
|
+
# A second call, but only ever when simctl is already failing — and failing
|
|
195
|
+
# is the fast path. Its stderr is the only thing that tells the causes apart.
|
|
196
|
+
_HAR_SIM_SIMCTL_ERROR="$(xcrun simctl list runtimes --json 2>&1 >/dev/null || true)"
|
|
197
|
+
fi
|
|
172
198
|
fi
|
|
173
199
|
printf '%s' "$_HAR_SIM_RUNTIMES_JSON"
|
|
174
200
|
}
|
|
175
201
|
|
|
202
|
+
# Ask simctl once, recording in this shell whether it answered and why not.
|
|
203
|
+
har_sim_probe_simctl() {
|
|
204
|
+
har_sim_runtimes_json >/dev/null
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
har_sim_simctl_available() {
|
|
208
|
+
har_sim_probe_simctl
|
|
209
|
+
[ "${_HAR_SIM_SIMCTL_OK:-true}" = "true" ]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Two unrelated problems make simctl unusable — a sandbox denying the
|
|
213
|
+
# CoreSimulatorService lookup, and a developer directory with no simctl in it.
|
|
214
|
+
# Naming the wrong one costs the reader the hunt this check exists to prevent.
|
|
215
|
+
har_sim_log_simctl_unavailable() {
|
|
216
|
+
# Teardown hits two failures in a row; the cause only needs saying once.
|
|
217
|
+
[ -z "${_HAR_SIM_SIMCTL_CAUSE_REPORTED:-}" ] || return 0
|
|
218
|
+
_HAR_SIM_SIMCTL_CAUSE_REPORTED=1
|
|
219
|
+
# For a launch the failing call happened inside the command substitution around
|
|
220
|
+
# har_sim_plan_creation, so the reason died with that subshell — ask again here.
|
|
221
|
+
har_sim_probe_simctl
|
|
222
|
+
case "${_HAR_SIM_SIMCTL_ERROR:-}" in
|
|
223
|
+
*"not a developer tool"*|*"unable to find utility"*|*"command not found"*)
|
|
224
|
+
har_sim_log "'xcrun simctl' is not in the selected developer directory"
|
|
225
|
+
har_sim_log " install Xcode, then point the toolchain at it:"
|
|
226
|
+
har_sim_log " sudo xcode-select -s /Applications/Xcode.app"
|
|
227
|
+
har_sim_log " current selection: $(xcode-select -p 2>/dev/null || echo 'none')"
|
|
228
|
+
return
|
|
229
|
+
;;
|
|
230
|
+
esac
|
|
231
|
+
har_sim_log "cannot reach CoreSimulatorService — 'xcrun simctl' is unusable here"
|
|
232
|
+
har_sim_log " this usually means the command is running inside an agent sandbox"
|
|
233
|
+
har_sim_log " run it from a normal terminal, or let the agent run 'xcrun simctl' unsandboxed"
|
|
234
|
+
}
|
|
235
|
+
|
|
176
236
|
# Model and runtime to create this slot's device from. Failures carry the models
|
|
177
237
|
# this machine can create, so the caller never has to re-derive that list.
|
|
178
238
|
# Prints one of:
|
|
@@ -180,10 +240,18 @@ har_sim_runtimes_json() {
|
|
|
180
240
|
# NO_RUNTIME no iOS runtime installed
|
|
181
241
|
# NO_MODEL <models> HARNESS_SIMULATOR_NAME is not a device model
|
|
182
242
|
# NO_RUNTIME_FOR_MODEL <models> model exists, no installed runtime supports it
|
|
243
|
+
# SIMCTL_UNAVAILABLE simctl could not be asked at all
|
|
183
244
|
har_sim_plan_creation() {
|
|
184
245
|
local types runtimes
|
|
185
246
|
types="$(har_sim_devicetypes_json)"
|
|
186
|
-
|
|
247
|
+
# Not `$(...)`: that would discard the availability flag with the subshell.
|
|
248
|
+
har_sim_runtimes_json >/dev/null
|
|
249
|
+
runtimes="$_HAR_SIM_RUNTIMES_JSON"
|
|
250
|
+
# Checked before the empty lists are read as an answer about the machine.
|
|
251
|
+
if ! har_sim_simctl_available; then
|
|
252
|
+
printf 'SIMCTL_UNAVAILABLE'
|
|
253
|
+
return
|
|
254
|
+
fi
|
|
187
255
|
node -e '
|
|
188
256
|
const [typesRaw, runtimesRaw, wantName, family] = process.argv.slice(1);
|
|
189
257
|
const types = JSON.parse(typesRaw).devicetypes || [];
|
|
@@ -240,7 +308,9 @@ process.stdout.write(plan());
|
|
|
240
308
|
# Every device whose name is <prefix> or starts with "<prefix>-", so a slot's
|
|
241
309
|
# leftovers are found whatever model they were created from.
|
|
242
310
|
har_sim_devices_with_prefix() {
|
|
243
|
-
|
|
311
|
+
local devices
|
|
312
|
+
devices="$(har_sim_simctl list devices --json)" || return 1
|
|
313
|
+
printf '%s' "$devices" | node -e '
|
|
244
314
|
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
245
315
|
const prefix = process.argv[1];
|
|
246
316
|
const matches = Object.values(data.devices || {})
|
|
@@ -252,12 +322,19 @@ process.stdout.write(matches.join("\n"));
|
|
|
252
322
|
' "$1" 2>/dev/null || true
|
|
253
323
|
}
|
|
254
324
|
|
|
325
|
+
# Returns 0 even when it could not look: teardown.sh runs under `set -e`, and
|
|
326
|
+
# aborting there would leave the slot registry behind on top of the device.
|
|
255
327
|
har_sim_delete_devices_with_prefix() {
|
|
256
|
-
local udid
|
|
328
|
+
local udid list
|
|
329
|
+
if ! list="$(har_sim_devices_with_prefix "$1")"; then
|
|
330
|
+
har_sim_log "cannot check for leftover devices named ${1}* — none were removed"
|
|
331
|
+
har_sim_log_simctl_unavailable
|
|
332
|
+
return 0
|
|
333
|
+
fi
|
|
257
334
|
while read -r udid; do
|
|
258
335
|
[ -n "$udid" ] || continue
|
|
259
336
|
xcrun simctl delete "$udid" >/dev/null 2>&1 || true
|
|
260
|
-
done <<< "$
|
|
337
|
+
done <<< "$list"
|
|
261
338
|
}
|
|
262
339
|
|
|
263
340
|
# ── Boot ──────────────────────────────────────────────────────────────────────
|
|
@@ -289,7 +366,11 @@ _har_sim_select() {
|
|
|
289
366
|
|
|
290
367
|
# An explicit UDID is a deliberate override — never fall back off it.
|
|
291
368
|
if [ -n "${HARNESS_SIMULATOR_UDID:-}" ]; then
|
|
292
|
-
|
|
369
|
+
# A failed lookup is "could not ask"; an empty one is "no such device".
|
|
370
|
+
name="$(har_sim_device_name "$HARNESS_SIMULATOR_UDID")" || {
|
|
371
|
+
har_sim_log_simctl_unavailable
|
|
372
|
+
return 1
|
|
373
|
+
}
|
|
293
374
|
if [ -z "$name" ]; then
|
|
294
375
|
har_sim_log "HARNESS_SIMULATOR_UDID=${HARNESS_SIMULATOR_UDID} is not a known device"
|
|
295
376
|
return 1
|
|
@@ -310,6 +391,11 @@ _har_sim_select() {
|
|
|
310
391
|
models=""
|
|
311
392
|
[ "$plan" != "$status" ] && models="${plan#*$'\t'}"
|
|
312
393
|
|
|
394
|
+
if [ "$status" = "SIMCTL_UNAVAILABLE" ]; then
|
|
395
|
+
har_sim_log_simctl_unavailable
|
|
396
|
+
return 1
|
|
397
|
+
fi
|
|
398
|
+
|
|
313
399
|
if [ "$status" = "OK" ]; then
|
|
314
400
|
IFS=$'\t' read -r _ device_type model runtime _ <<< "$plan"
|
|
315
401
|
label="$(har_sim_device_label "$agent_id" "$model")"
|
|
@@ -388,8 +474,14 @@ har_sim_release() {
|
|
|
388
474
|
rm -f "$file"
|
|
389
475
|
if [ "$created" = "true" ] && [ -n "$udid" ]; then
|
|
390
476
|
xcrun simctl shutdown "$udid" >/dev/null 2>&1 || true
|
|
391
|
-
xcrun simctl delete "$udid" >/dev/null 2>&1
|
|
392
|
-
|
|
477
|
+
if xcrun simctl delete "$udid" >/dev/null 2>&1; then
|
|
478
|
+
echo "✓ Deleted simulator created for this slot ($udid)"
|
|
479
|
+
else
|
|
480
|
+
# Naming it is the whole remedy left: the claim is gone, so only the name
|
|
481
|
+
# prefix sweep at the next launch can still find this device.
|
|
482
|
+
har_sim_log "could not delete ${udid}, created for agent ${agent_id} — it is still on this machine"
|
|
483
|
+
har_sim_simctl_available || har_sim_log_simctl_unavailable
|
|
484
|
+
fi
|
|
393
485
|
fi
|
|
394
486
|
fi
|
|
395
487
|
|