@osfactory/har 0.64.1 → 1.0.0-rc.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 +15981 -6646
- package/dist/prompts/system-authoring.md +155 -0
- package/dist/templates/adaptation-prompt-init.md +67 -43
- package/dist/templates/adaptation-prompt-maintain.md +38 -38
- package/dist/templates/agent-skills/har-maintain.md +2 -2
- package/dist/templates/agent-skills/setup-har.md +6 -4
- package/dist/templates/cursor-rule.mdc.template +3 -3
- package/dist/templates/har-boilerplate/docker-compose.agent.yml +1 -1
- package/dist/templates/har-boilerplate/harness.env +11 -155
- package/dist/templates/har-boilerplate/stages.json +51 -2
- package/dist/templates/har-boilerplate-cli/docker-compose.agent.yml +1 -1
- package/dist/templates/har-boilerplate-cli/harness.env +12 -138
- package/dist/templates/har-boilerplate-cli/stages.json +42 -2
- package/dist/templates/har-boilerplate-ios/harness.env +10 -126
- package/dist/templates/har-boilerplate-ios/stages.json +42 -2
- package/dist/templates/migration-prompt.md +54 -0
- package/dist/templates/plugins/custom-stage-skeleton.sh +11 -20
- package/dist/templates/plugins/gitleaks/.github/workflows/gitleaks.yml +1 -1
- package/dist/templates/plugins/gitleaks/.har/stages/GITLEAKS.md +1 -1
- package/dist/templates/plugins/gitleaks/.har/stages/secrets-scan.sh +8 -23
- package/dist/templates/plugins/gitleaks/README.md +1 -1
- package/dist/templates/plugins/gitleaks/template.manifest.json +20 -5
- package/dist/templates/plugins/kerno/.github/workflows/kerno.yml +1 -1
- package/dist/templates/plugins/kerno/.har/stages/backend-validation.sh +8 -23
- package/dist/templates/plugins/kerno/README.md +1 -1
- package/dist/templates/plugins/kerno/template.manifest.json +20 -9
- package/dist/templates/plugins/local-plugin-skeleton/README.md +46 -0
- package/dist/templates/plugins/playwright/.har/stages/browser-e2e.sh +8 -23
- package/dist/templates/plugins/playwright/template.manifest.json +36 -13
- package/dist/templates/plugins/rocketsim/.har/stages/rocketsim-flows.sh +8 -23
- package/dist/templates/plugins/rocketsim/template.manifest.json +20 -8
- package/dist/templates/plugins/semgrep/.har/stages/sast.sh +10 -26
- package/dist/templates/plugins/semgrep/README.md +1 -1
- package/dist/templates/plugins/semgrep/template.manifest.json +16 -7
- package/dist/templates/plugins/trivy/.har/stages/vuln-scan.sh +8 -23
- package/dist/templates/plugins/trivy/README.md +1 -1
- package/dist/templates/plugins/trivy/template.manifest.json +20 -8
- package/dist/templates/{har-boilerplate-cli/CLAUDE.agent.md → profiles/cli/docs/claude-agent/definition-of-done.md} +0 -42
- package/dist/templates/profiles/cli/docs/claude-agent/do-not.md +6 -0
- package/dist/templates/profiles/cli/docs/claude-agent/environment.md +12 -0
- package/dist/templates/profiles/cli/docs/claude-agent/project-commands.md +10 -0
- package/dist/templates/profiles/cli/docs/claude-agent/readiness.md +6 -0
- package/dist/templates/profiles/cli/docs/readme/for-coding-agents.md +9 -0
- package/dist/templates/profiles/cli/docs/readme/intro.md +7 -0
- package/dist/templates/profiles/cli/docs/readme/port-shared-services-cli-profile.md +25 -0
- package/dist/templates/profiles/cli/docs/readme/quick-start.md +24 -0
- package/dist/templates/profiles/cli/docs/readme/verification-contract.md +22 -0
- package/dist/templates/profiles/cli/docs/readme/what-s-in-here.md +27 -0
- package/dist/templates/profiles/cli/profile.manifest.json +56 -1
- package/dist/templates/profiles/default/docs/claude-agent/definition-of-done.md +24 -0
- package/dist/templates/profiles/default/docs/claude-agent/do-not.md +10 -0
- package/dist/templates/profiles/default/docs/claude-agent/environment.md +19 -0
- package/dist/templates/profiles/default/docs/claude-agent/project-commands.md +10 -0
- package/dist/templates/profiles/default/docs/claude-agent/readiness.md +12 -0
- package/dist/templates/profiles/default/docs/readme/architecture.md +11 -0
- package/dist/templates/profiles/default/docs/readme/for-coding-agents.md +9 -0
- package/dist/templates/profiles/default/docs/readme/intro.md +7 -0
- package/dist/templates/profiles/default/docs/readme/port-shared-services.md +41 -0
- package/dist/templates/profiles/default/docs/readme/quick-start.md +24 -0
- package/dist/templates/profiles/default/docs/readme/readiness-layers.md +16 -0
- package/dist/templates/profiles/default/docs/readme/verification-contract.md +17 -0
- package/dist/templates/profiles/default/docs/readme/what-s-in-here.md +28 -0
- package/dist/templates/profiles/default/profile.manifest.json +62 -1
- package/dist/templates/profiles/ios/docs/claude-agent/definition-of-done.md +23 -0
- package/dist/templates/profiles/ios/docs/claude-agent/do-not.md +6 -0
- package/dist/templates/profiles/ios/docs/claude-agent/environment.md +14 -0
- package/dist/templates/profiles/ios/docs/claude-agent/intro.md +3 -0
- package/dist/templates/profiles/ios/docs/claude-agent/project-commands.md +21 -0
- package/dist/templates/profiles/ios/docs/claude-agent/readiness.md +6 -0
- package/dist/templates/profiles/ios/docs/claude-agent/user-flow-validation-rocketsim.md +11 -0
- package/dist/templates/profiles/ios/docs/readme/configuration.md +7 -0
- package/dist/templates/profiles/ios/docs/readme/intro.md +7 -0
- package/dist/templates/profiles/ios/docs/readme/port-shared-services-ios-profile.md +25 -0
- package/dist/templates/profiles/ios/docs/readme/quick-start.md +22 -0
- package/dist/templates/profiles/ios/docs/readme/simulators.md +45 -0
- package/dist/templates/profiles/ios/docs/readme/user-flow-validation.md +9 -0
- package/dist/templates/profiles/ios/docs/readme/verification-contract.md +13 -0
- package/dist/templates/profiles/ios/docs/readme/what-s-in-here.md +27 -0
- package/dist/templates/profiles/ios/profile.manifest.json +59 -3
- package/dist/templates/runtime-bundles/pm2-runtime/attach.sh +8 -15
- package/dist/templates/runtime-bundles/pm2-runtime/env.template +1 -1
- package/dist/templates/runtime-bundles/pm2-runtime/stages/api-health.sh +9 -0
- package/dist/templates/runtime-bundles/shared-kernel/STAGES.md +32 -18
- package/dist/templates/runtime-bundles/shared-kernel/agent-cli.sh +35 -0
- package/dist/templates/runtime-bundles/shared-kernel/launch.sh +35 -0
- package/dist/templates/runtime-bundles/shared-kernel/lib/verify-runner.mjs +129 -0
- package/dist/templates/runtime-bundles/shared-kernel/preflight.sh +35 -0
- package/dist/templates/runtime-bundles/shared-kernel/setup-infra.sh +35 -0
- package/dist/templates/runtime-bundles/shared-kernel/stages/README.sh +1 -1
- package/dist/templates/runtime-bundles/shared-kernel/stages/readiness.sh +14 -0
- package/dist/templates/runtime-bundles/shared-kernel/teardown.sh +35 -0
- package/dist/templates/runtime-bundles/shared-kernel/verify.sh +35 -0
- package/dist/templates/shared-docs/claude-agent/intro.md +3 -0
- package/dist/templates/shared-docs/readme/maintaining-this-harness.md +14 -0
- package/dist/templates/shared-docs/readme/run-history.md +5 -0
- package/dist/templates/shared-docs/readme/session-lifecycle.md +20 -0
- package/package.json +1 -1
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +0 -83
- package/dist/templates/har-boilerplate/README.md +0 -199
- package/dist/templates/har-boilerplate/STAGES.md +0 -99
- package/dist/templates/har-boilerplate/agent-cli.sh +0 -225
- package/dist/templates/har-boilerplate/agent-slot.sh +0 -850
- package/dist/templates/har-boilerplate/attach.sh +0 -22
- package/dist/templates/har-boilerplate/ecosystem.agent.template.cjs +0 -52
- package/dist/templates/har-boilerplate/env.template +0 -40
- package/dist/templates/har-boilerplate/launch.sh +0 -323
- package/dist/templates/har-boilerplate/preflight.sh +0 -32
- package/dist/templates/har-boilerplate/provision-toolchain.sh +0 -766
- package/dist/templates/har-boilerplate/setup-infra.sh +0 -178
- package/dist/templates/har-boilerplate/stages/README.sh +0 -11
- package/dist/templates/har-boilerplate/teardown.sh +0 -87
- package/dist/templates/har-boilerplate/verify.sh +0 -249
- package/dist/templates/har-boilerplate-cli/README.md +0 -157
- package/dist/templates/har-boilerplate-cli/STAGES.md +0 -99
- package/dist/templates/har-boilerplate-cli/agent-cli.sh +0 -160
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +0 -832
- package/dist/templates/har-boilerplate-cli/launch.sh +0 -216
- package/dist/templates/har-boilerplate-cli/preflight.sh +0 -28
- package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +0 -766
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +0 -178
- package/dist/templates/har-boilerplate-cli/stages/README.sh +0 -11
- package/dist/templates/har-boilerplate-cli/teardown.sh +0 -66
- package/dist/templates/har-boilerplate-cli/verify.sh +0 -210
- package/dist/templates/har-boilerplate-ios/ADAPT-PROMPT.md +0 -63
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +0 -90
- package/dist/templates/har-boilerplate-ios/README.md +0 -173
- package/dist/templates/har-boilerplate-ios/STAGES.md +0 -99
- package/dist/templates/har-boilerplate-ios/agent-cli.sh +0 -149
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +0 -479
- package/dist/templates/har-boilerplate-ios/launch.sh +0 -206
- package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +0 -766
- package/dist/templates/har-boilerplate-ios/setup-infra.sh +0 -112
- package/dist/templates/har-boilerplate-ios/simulator.sh +0 -492
- package/dist/templates/har-boilerplate-ios/stages/README.sh +0 -11
- package/dist/templates/har-boilerplate-ios/teardown.sh +0 -68
- package/dist/templates/har-boilerplate-ios/verify.sh +0 -185
- package/dist/templates/runtime-bundles/pm2-runtime/README.md +0 -4
- package/dist/templates/runtime-bundles/shared-kernel/README.md +0 -3
- package/dist/templates/runtime-bundles/shared-kernel/provision-toolchain.sh +0 -766
- package/dist/templates/runtime-bundles/xcode-sim/README.md +0 -4
- package/dist/templates/runtime-bundles/xcode-sim/simulator.sh +0 -492
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Per-slot iOS Simulator allocation.
|
|
3
|
-
#
|
|
4
|
-
# One booted simulator shared by every agent slot means concurrent xcodebuild
|
|
5
|
-
# destinations, installs of the same bundle id, and UI flows collide. Each slot
|
|
6
|
-
# instead gets a simulator of its own, created at launch and deleted at teardown:
|
|
7
|
-
# unique by construction, pristine on every launch, and never a device the
|
|
8
|
-
# developer is using by hand.
|
|
9
|
-
#
|
|
10
|
-
# Source AFTER harness.env and agent-slot.sh — read_slot_field, slot_registry_file
|
|
11
|
-
# and SCRIPT_DIR come from there.
|
|
12
|
-
#
|
|
13
|
-
# What a slot holds is recorded in .har/simulators/agent-<id>.json, so teardown
|
|
14
|
-
# knows whether the device was HAR's to delete.
|
|
15
|
-
|
|
16
|
-
# ── Configuration ─────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
# Preferred device family: auto (infer from HARNESS_SIMULATOR_NAME) | iPhone | iPad
|
|
19
|
-
har_sim_preferred_family() {
|
|
20
|
-
local configured="${HARNESS_SIMULATOR_FAMILY:-auto}"
|
|
21
|
-
case "$configured" in
|
|
22
|
-
iPhone|iPad) echo "$configured"; return ;;
|
|
23
|
-
esac
|
|
24
|
-
case "${HARNESS_SIMULATOR_NAME:-}" in
|
|
25
|
-
iPad*|iPAD*|ipad*) echo "iPad" ;;
|
|
26
|
-
*) echo "iPhone" ;;
|
|
27
|
-
esac
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
har_sim_per_slot_enabled() {
|
|
31
|
-
[ "${HARNESS_SIMULATOR_SHARED:-false}" != "true" ]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
har_sim_log() { echo "==> simulator: $*" >&2; }
|
|
35
|
-
|
|
36
|
-
# "iPad Air 11-inch (M4)" → "iPad-Air-11-inch-M4"
|
|
37
|
-
har_sim_slug() {
|
|
38
|
-
printf '%s' "$1" | tr -c 'A-Za-z0-9._-' '-' | tr -s '-' | sed 's/^-//; s/-$//'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Short digest of the repository path. Two checkouts can share a folder name,
|
|
42
|
-
# and therefore HARNESS_PROJECT_NAME, but not a path — without it the prefix
|
|
43
|
-
# sweep below would delete the other checkout's live device.
|
|
44
|
-
har_sim_repo_digest() {
|
|
45
|
-
local repo
|
|
46
|
-
# -P so the same checkout reached through a symlink yields the same digest.
|
|
47
|
-
repo="$(cd -P "${SCRIPT_DIR}/.." 2>/dev/null && pwd -P || echo "${SCRIPT_DIR}")"
|
|
48
|
-
node -e 'process.stdout.write(require("crypto").createHash("sha1").update(process.argv[1]).digest("hex").slice(0, 6))' "$repo"
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
# Stable part of a slot's device name. The model is appended after it, so a
|
|
52
|
-
# device left over from a launch on another model is still recognised as this
|
|
53
|
-
# slot's and cleaned up.
|
|
54
|
-
har_sim_device_label_prefix() {
|
|
55
|
-
echo "har-$(har_sim_slug "${HARNESS_PROJECT_NAME:-har}")-$(har_sim_repo_digest)-agent-${1}"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
har_sim_device_label() {
|
|
59
|
-
local prefix model
|
|
60
|
-
prefix="$(har_sim_device_label_prefix "$1")"
|
|
61
|
-
model="$(har_sim_slug "${2:-}")"
|
|
62
|
-
[ -n "$model" ] && echo "${prefix}-${model}" || echo "$prefix"
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
# ── Claims ────────────────────────────────────────────────────────────────────
|
|
66
|
-
# Slots never compete for a device, so a claim only records what teardown must
|
|
67
|
-
# release — no locking, no cross-slot exclusion.
|
|
68
|
-
|
|
69
|
-
har_sim_claims_dir() { echo "${SCRIPT_DIR}/simulators"; }
|
|
70
|
-
har_sim_claim_file() { echo "$(har_sim_claims_dir)/agent-${1}.json"; }
|
|
71
|
-
|
|
72
|
-
har_sim_write_claim() {
|
|
73
|
-
local agent_id="$1" udid="$2" name="$3" created="$4" file
|
|
74
|
-
file="$(har_sim_claim_file "$agent_id")"
|
|
75
|
-
mkdir -p "$(dirname "$file")"
|
|
76
|
-
node -e '
|
|
77
|
-
const fs = require("fs");
|
|
78
|
-
const [file, agentId, udid, name, created] = process.argv.slice(1);
|
|
79
|
-
fs.writeFileSync(file, JSON.stringify({
|
|
80
|
-
agentId: Number(agentId),
|
|
81
|
-
udid,
|
|
82
|
-
name,
|
|
83
|
-
createdByHar: created === "true",
|
|
84
|
-
claimedAt: new Date().toISOString(),
|
|
85
|
-
}, null, 2) + "\n");
|
|
86
|
-
' "$file" "$agent_id" "$udid" "$name" "$created"
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Agent id of another live slot holding this udid, if any. Only a pinned
|
|
90
|
-
# HARNESS_SIMULATOR_UDID can produce that overlap.
|
|
91
|
-
har_sim_claim_owner_of() {
|
|
92
|
-
local udid="$1" self="$2" dir file id
|
|
93
|
-
dir="$(har_sim_claims_dir)"
|
|
94
|
-
[ -d "$dir" ] || return 0
|
|
95
|
-
for file in "$dir"/agent-*.json; do
|
|
96
|
-
[ -f "$file" ] || continue
|
|
97
|
-
id="$(basename "$file")"
|
|
98
|
-
id="${id#agent-}"
|
|
99
|
-
id="${id%.json}"
|
|
100
|
-
[ "$id" = "$self" ] && continue
|
|
101
|
-
[ -f "$(slot_registry_file "$id")" ] || continue
|
|
102
|
-
if [ "$(read_slot_field "$file" udid || true)" = "$udid" ]; then
|
|
103
|
-
echo "$id"
|
|
104
|
-
return 0
|
|
105
|
-
fi
|
|
106
|
-
done
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
# ── Device discovery ──────────────────────────────────────────────────────────
|
|
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
|
-
|
|
124
|
-
# Unavailable devices are skipped: a device whose runtime was uninstalled must
|
|
125
|
-
# not pass validation only to fail at boot.
|
|
126
|
-
har_sim_device_field() {
|
|
127
|
-
local devices
|
|
128
|
-
devices="$(har_sim_simctl list devices --json)" || return 1
|
|
129
|
-
printf '%s' "$devices" | node -e '
|
|
130
|
-
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
131
|
-
const [want, field] = process.argv.slice(1);
|
|
132
|
-
const device = Object.values(data.devices || {})
|
|
133
|
-
.flat()
|
|
134
|
-
.filter((entry) => entry && entry.isAvailable !== false)
|
|
135
|
-
.find((entry) => entry.udid === want);
|
|
136
|
-
if (device && device[field]) process.stdout.write(String(device[field]));
|
|
137
|
-
' "$1" "$2" 2>/dev/null || true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
har_sim_device_state() { har_sim_device_field "$1" state; }
|
|
141
|
-
har_sim_device_name() { har_sim_device_field "$1" name; }
|
|
142
|
-
|
|
143
|
-
# UDID of an existing device with this exact name, newest runtime first.
|
|
144
|
-
# A substring match would resolve "iPhone 16" to "iPhone 16 Pro Max".
|
|
145
|
-
har_sim_device_by_name() {
|
|
146
|
-
local want="${1:-}" devices
|
|
147
|
-
[ -n "$want" ] || return 0
|
|
148
|
-
devices="$(har_sim_simctl list devices available --json)" || return 1
|
|
149
|
-
printf '%s' "$devices" | node -e '
|
|
150
|
-
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
151
|
-
const want = process.argv[1];
|
|
152
|
-
const rows = [];
|
|
153
|
-
for (const [runtime, devices] of Object.entries(data.devices || {})) {
|
|
154
|
-
if (!runtime.includes("SimRuntime.iOS-")) continue;
|
|
155
|
-
const parts = runtime.match(/iOS-(\d+)(?:-(\d+))?/);
|
|
156
|
-
const version = parts ? Number(parts[1]) * 1000 + Number(parts[2] || 0) : 0;
|
|
157
|
-
for (const device of devices || []) {
|
|
158
|
-
if (!device.udid || device.isAvailable === false) continue;
|
|
159
|
-
if (device.name !== want) continue;
|
|
160
|
-
rows.push({ udid: device.udid, version });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
rows.sort((a, b) => b.version - a.version);
|
|
164
|
-
if (rows[0]) process.stdout.write(rows[0].udid);
|
|
165
|
-
' "$want" 2>/dev/null || true
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
# The shared-mode device, as "<udid>\t<name>".
|
|
169
|
-
har_sim_resolve_configured() {
|
|
170
|
-
local udid
|
|
171
|
-
udid="$(har_sim_device_by_name "${HARNESS_SIMULATOR_NAME:-}")"
|
|
172
|
-
[ -n "$udid" ] || return 0
|
|
173
|
-
printf '%s\t%s' "$udid" "${HARNESS_SIMULATOR_NAME}"
|
|
174
|
-
}
|
|
175
|
-
|
|
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.
|
|
180
|
-
har_sim_devicetypes_json() {
|
|
181
|
-
if [ -z "${_HAR_SIM_TYPES_JSON:-}" ]; then
|
|
182
|
-
_HAR_SIM_TYPES_JSON="$(har_sim_simctl list devicetypes --json)" || _HAR_SIM_TYPES_JSON='{}'
|
|
183
|
-
fi
|
|
184
|
-
printf '%s' "$_HAR_SIM_TYPES_JSON"
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
har_sim_runtimes_json() {
|
|
188
|
-
if [ -z "${_HAR_SIM_RUNTIMES_JSON:-}" ]; then
|
|
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
|
|
198
|
-
fi
|
|
199
|
-
printf '%s' "$_HAR_SIM_RUNTIMES_JSON"
|
|
200
|
-
}
|
|
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
|
-
|
|
236
|
-
# Model and runtime to create this slot's device from. Failures carry the models
|
|
237
|
-
# this machine can create, so the caller never has to re-derive that list.
|
|
238
|
-
# Prints one of:
|
|
239
|
-
# OK <deviceTypeId> <deviceTypeName> <runtimeId> <runtimeVersion>
|
|
240
|
-
# NO_RUNTIME no iOS runtime installed
|
|
241
|
-
# NO_MODEL <models> HARNESS_SIMULATOR_NAME is not a device model
|
|
242
|
-
# NO_RUNTIME_FOR_MODEL <models> model exists, no installed runtime supports it
|
|
243
|
-
# SIMCTL_UNAVAILABLE simctl could not be asked at all
|
|
244
|
-
har_sim_plan_creation() {
|
|
245
|
-
local types runtimes
|
|
246
|
-
types="$(har_sim_devicetypes_json)"
|
|
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
|
|
255
|
-
node -e '
|
|
256
|
-
const [typesRaw, runtimesRaw, wantName, family] = process.argv.slice(1);
|
|
257
|
-
const types = JSON.parse(typesRaw).devicetypes || [];
|
|
258
|
-
const version = (value) => {
|
|
259
|
-
const [major = 0, minor = 0] = String(value || "0").split(".").map(Number);
|
|
260
|
-
return major * 1000 + minor;
|
|
261
|
-
};
|
|
262
|
-
const runtimes = (JSON.parse(runtimesRaw).runtimes || [])
|
|
263
|
-
.filter((runtime) => runtime.isAvailable !== false)
|
|
264
|
-
.filter((runtime) => String(runtime.identifier || "").includes("SimRuntime.iOS-"))
|
|
265
|
-
.sort((a, b) => version(b.version) - version(a.version));
|
|
266
|
-
|
|
267
|
-
const isPad = (entry) => /ipad/i.test(entry.productFamily || entry.name || "");
|
|
268
|
-
const inFamily = (entry) => (family === "iPad" ? isPad(entry) : !isPad(entry));
|
|
269
|
-
|
|
270
|
-
// Models the newest runtime could create, named so an error can list them.
|
|
271
|
-
// Truncation is stated rather than silent — a model cut off the list still exists.
|
|
272
|
-
function availableModels() {
|
|
273
|
-
const runtime = runtimes[0];
|
|
274
|
-
const supported =
|
|
275
|
-
runtime && Array.isArray(runtime.supportedDeviceTypes) && runtime.supportedDeviceTypes.length
|
|
276
|
-
? runtime.supportedDeviceTypes
|
|
277
|
-
: types;
|
|
278
|
-
const names = supported.filter(inFamily).map((entry) => entry.name);
|
|
279
|
-
const shown = names.slice(0, 8).join(", ");
|
|
280
|
-
return names.length > 8 ? `${shown}, … (${names.length - 8} more)` : shown;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function plan() {
|
|
284
|
-
if (!runtimes.length) return "NO_RUNTIME";
|
|
285
|
-
if (wantName && !types.some((type) => type.name === wantName)) {
|
|
286
|
-
return `NO_MODEL\t${availableModels()}`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
for (const runtime of runtimes) {
|
|
290
|
-
const supported =
|
|
291
|
-
Array.isArray(runtime.supportedDeviceTypes) && runtime.supportedDeviceTypes.length
|
|
292
|
-
? runtime.supportedDeviceTypes
|
|
293
|
-
: types;
|
|
294
|
-
// Apple lists supported device types newest first within each family.
|
|
295
|
-
const pool = supported.filter(inFamily);
|
|
296
|
-
const chosen = wantName ? pool.find((type) => type.name === wantName) : pool[0];
|
|
297
|
-
if (chosen) {
|
|
298
|
-
return `OK\t${chosen.identifier}\t${chosen.name}\t${runtime.identifier}\t${runtime.version}`;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return wantName ? `NO_RUNTIME_FOR_MODEL\t${availableModels()}` : "NO_RUNTIME";
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
process.stdout.write(plan());
|
|
305
|
-
' "$types" "$runtimes" "${1:-}" "${2:-iPhone}" 2>/dev/null || true
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
# Every device whose name is <prefix> or starts with "<prefix>-", so a slot's
|
|
309
|
-
# leftovers are found whatever model they were created from.
|
|
310
|
-
har_sim_devices_with_prefix() {
|
|
311
|
-
local devices
|
|
312
|
-
devices="$(har_sim_simctl list devices --json)" || return 1
|
|
313
|
-
printf '%s' "$devices" | node -e '
|
|
314
|
-
const data = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
315
|
-
const prefix = process.argv[1];
|
|
316
|
-
const matches = Object.values(data.devices || {})
|
|
317
|
-
.flat()
|
|
318
|
-
.filter((device) => device && device.udid)
|
|
319
|
-
.filter((device) => device.name === prefix || String(device.name || "").startsWith(`${prefix}-`))
|
|
320
|
-
.map((device) => device.udid);
|
|
321
|
-
process.stdout.write(matches.join("\n"));
|
|
322
|
-
' "$1" 2>/dev/null || true
|
|
323
|
-
}
|
|
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.
|
|
327
|
-
har_sim_delete_devices_with_prefix() {
|
|
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
|
|
334
|
-
while read -r udid; do
|
|
335
|
-
[ -n "$udid" ] || continue
|
|
336
|
-
xcrun simctl delete "$udid" >/dev/null 2>&1 || true
|
|
337
|
-
done <<< "$list"
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
# ── Boot ──────────────────────────────────────────────────────────────────────
|
|
341
|
-
|
|
342
|
-
har_sim_boot() {
|
|
343
|
-
local udid="$1" i
|
|
344
|
-
xcrun simctl boot "$udid" >/dev/null 2>&1 || true
|
|
345
|
-
if xcrun simctl bootstatus "$udid" -b >/dev/null 2>&1; then
|
|
346
|
-
return 0
|
|
347
|
-
fi
|
|
348
|
-
# Fallback for a toolchain without bootstatus: the plain listing is one
|
|
349
|
-
# subprocess, where the JSON form costs xcrun plus node on every poll.
|
|
350
|
-
for i in $(seq 1 30); do
|
|
351
|
-
if xcrun simctl list devices 2>/dev/null | grep -q "${udid}.*Booted"; then
|
|
352
|
-
return 0
|
|
353
|
-
fi
|
|
354
|
-
sleep 2
|
|
355
|
-
done
|
|
356
|
-
return 1
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
# ── Acquire / release ─────────────────────────────────────────────────────────
|
|
360
|
-
|
|
361
|
-
# Prints "<udid>\t<name>\t<createdByHar>".
|
|
362
|
-
_har_sim_select() {
|
|
363
|
-
local agent_id="$1"
|
|
364
|
-
local want="${HARNESS_SIMULATOR_NAME:-}"
|
|
365
|
-
local family owner name plan status models device_type model runtime label udid
|
|
366
|
-
|
|
367
|
-
# An explicit UDID is a deliberate override — never fall back off it.
|
|
368
|
-
if [ -n "${HARNESS_SIMULATOR_UDID:-}" ]; then
|
|
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
|
-
}
|
|
374
|
-
if [ -z "$name" ]; then
|
|
375
|
-
har_sim_log "HARNESS_SIMULATOR_UDID=${HARNESS_SIMULATOR_UDID} is not a known device"
|
|
376
|
-
return 1
|
|
377
|
-
fi
|
|
378
|
-
owner="$(har_sim_claim_owner_of "$HARNESS_SIMULATOR_UDID" "$agent_id")"
|
|
379
|
-
if [ -n "$owner" ]; then
|
|
380
|
-
har_sim_log "warning: agent ${owner} already holds ${name} — a pinned HARNESS_SIMULATOR_UDID"
|
|
381
|
-
har_sim_log " gives every slot the same device; unset it to get one per slot."
|
|
382
|
-
fi
|
|
383
|
-
printf '%s\t%s\t%s' "$HARNESS_SIMULATOR_UDID" "$name" "false"
|
|
384
|
-
return 0
|
|
385
|
-
fi
|
|
386
|
-
|
|
387
|
-
family="$(har_sim_preferred_family)"
|
|
388
|
-
plan="$(har_sim_plan_creation "$want" "$family")"
|
|
389
|
-
status="${plan%%$'\t'*}"
|
|
390
|
-
# Failure statuses carry the creatable models after the first tab.
|
|
391
|
-
models=""
|
|
392
|
-
[ "$plan" != "$status" ] && models="${plan#*$'\t'}"
|
|
393
|
-
|
|
394
|
-
if [ "$status" = "SIMCTL_UNAVAILABLE" ]; then
|
|
395
|
-
har_sim_log_simctl_unavailable
|
|
396
|
-
return 1
|
|
397
|
-
fi
|
|
398
|
-
|
|
399
|
-
if [ "$status" = "OK" ]; then
|
|
400
|
-
IFS=$'\t' read -r _ device_type model runtime _ <<< "$plan"
|
|
401
|
-
label="$(har_sim_device_label "$agent_id" "$model")"
|
|
402
|
-
# A crashed launch can leave the slot's device behind — start from scratch,
|
|
403
|
-
# including a device this slot created from a different model.
|
|
404
|
-
har_sim_delete_devices_with_prefix "$(har_sim_device_label_prefix "$agent_id")"
|
|
405
|
-
har_sim_log "creating ${label} (${model}, ${runtime##*SimRuntime.})"
|
|
406
|
-
udid="$(xcrun simctl create "$label" "$device_type" "$runtime" 2>/dev/null | tr -d '[:space:]')"
|
|
407
|
-
if [ -z "$udid" ]; then
|
|
408
|
-
har_sim_log "xcrun simctl create failed for ${label} (${device_type}, ${runtime})"
|
|
409
|
-
return 1
|
|
410
|
-
fi
|
|
411
|
-
printf '%s\t%s\t%s' "$udid" "$label" "true"
|
|
412
|
-
return 0
|
|
413
|
-
fi
|
|
414
|
-
|
|
415
|
-
# Not a model: fall back to an existing device carrying that name, which is
|
|
416
|
-
# how a hand-renamed simulator is targeted.
|
|
417
|
-
if [ "$status" = "NO_MODEL" ]; then
|
|
418
|
-
udid="$(har_sim_device_by_name "$want")"
|
|
419
|
-
if [ -n "$udid" ]; then
|
|
420
|
-
har_sim_log "'${want}' is not a device model — using the existing device with that name"
|
|
421
|
-
printf '%s\t%s\t%s' "$udid" "$want" "false"
|
|
422
|
-
return 0
|
|
423
|
-
fi
|
|
424
|
-
har_sim_log "HARNESS_SIMULATOR_NAME='${want}' is neither a device model nor an existing device"
|
|
425
|
-
har_sim_log " available ${family} models: ${models}"
|
|
426
|
-
return 1
|
|
427
|
-
fi
|
|
428
|
-
|
|
429
|
-
if [ "$status" = "NO_RUNTIME_FOR_MODEL" ]; then
|
|
430
|
-
har_sim_log "no installed iOS runtime supports '${want}'"
|
|
431
|
-
har_sim_log " install a newer runtime in Xcode → Settings → Components, or pick another model"
|
|
432
|
-
har_sim_log " available ${family} models: ${models}"
|
|
433
|
-
return 1
|
|
434
|
-
fi
|
|
435
|
-
|
|
436
|
-
har_sim_log "no iOS simulator runtime is installed"
|
|
437
|
-
har_sim_log " install one in Xcode → Settings → Components"
|
|
438
|
-
return 1
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
# Reserve a simulator for a slot, boot it, and record the claim.
|
|
442
|
-
# Prints "<udid>\t<name>" on success.
|
|
443
|
-
har_sim_acquire() {
|
|
444
|
-
local agent_id="$1" udid name created
|
|
445
|
-
local result="" rc=0
|
|
446
|
-
# `|| rc=$?` keeps a failed selection from tripping `set -e` in the caller.
|
|
447
|
-
result="$(_har_sim_select "$agent_id")" || rc=$?
|
|
448
|
-
[ "$rc" -eq 0 ] || return "$rc"
|
|
449
|
-
|
|
450
|
-
udid="$(printf '%s' "$result" | cut -f1)"
|
|
451
|
-
name="$(printf '%s' "$result" | cut -f2)"
|
|
452
|
-
created="$(printf '%s' "$result" | cut -f3)"
|
|
453
|
-
if [ -z "$udid" ]; then
|
|
454
|
-
har_sim_log "no simulator could be selected"
|
|
455
|
-
return 1
|
|
456
|
-
fi
|
|
457
|
-
har_sim_write_claim "$agent_id" "$udid" "$name" "$created"
|
|
458
|
-
|
|
459
|
-
if ! har_sim_boot "$udid"; then
|
|
460
|
-
har_sim_log "device ${name} (${udid}) did not reach Booted state"
|
|
461
|
-
return 1
|
|
462
|
-
fi
|
|
463
|
-
printf '%s\t%s' "$udid" "$name"
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
# Drop a slot's claim. Devices HAR created are deleted; a device the developer
|
|
467
|
-
# owns is left alone, and never even shut down.
|
|
468
|
-
har_sim_release() {
|
|
469
|
-
local agent_id="$1" file udid created
|
|
470
|
-
file="$(har_sim_claim_file "$agent_id")"
|
|
471
|
-
if [ -f "$file" ]; then
|
|
472
|
-
udid="$(read_slot_field "$file" udid || true)"
|
|
473
|
-
created="$(read_slot_field "$file" createdByHar || true)"
|
|
474
|
-
rm -f "$file"
|
|
475
|
-
if [ "$created" = "true" ] && [ -n "$udid" ]; then
|
|
476
|
-
xcrun simctl shutdown "$udid" >/dev/null 2>&1 || true
|
|
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
|
|
485
|
-
fi
|
|
486
|
-
fi
|
|
487
|
-
|
|
488
|
-
# A launch killed between `simctl create` and the claim leaves a device no
|
|
489
|
-
# claim points at. Only HAR creates devices under this prefix, so sweeping it
|
|
490
|
-
# can never remove one of the developer's.
|
|
491
|
-
har_sim_delete_devices_with_prefix "$(har_sim_device_label_prefix "$agent_id")"
|
|
492
|
-
}
|