zeno-mobile-runner 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/FEATURES.md +111 -0
  4. package/LICENSE +21 -0
  5. package/README.md +176 -0
  6. package/SECURITY.md +34 -0
  7. package/build.zig +38 -0
  8. package/build.zig.zon +7 -0
  9. package/clients/README.md +149 -0
  10. package/clients/go/README.md +24 -0
  11. package/clients/go/examples/fake-session/main.go +93 -0
  12. package/clients/go/go.mod +3 -0
  13. package/clients/go/zmr/client.go +432 -0
  14. package/clients/kotlin/README.md +35 -0
  15. package/clients/kotlin/build.gradle.kts +35 -0
  16. package/clients/kotlin/settings.gradle.kts +15 -0
  17. package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
  18. package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
  19. package/clients/python/README.md +29 -0
  20. package/clients/python/examples/fake_session.py +48 -0
  21. package/clients/python/pyproject.toml +13 -0
  22. package/clients/python/zmr_client.py +202 -0
  23. package/clients/rust/Cargo.lock +107 -0
  24. package/clients/rust/Cargo.toml +10 -0
  25. package/clients/rust/README.md +19 -0
  26. package/clients/rust/examples/fake_session.rs +70 -0
  27. package/clients/rust/src/lib.rs +461 -0
  28. package/clients/swift/Package.swift +16 -0
  29. package/clients/swift/README.md +36 -0
  30. package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
  31. package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
  32. package/clients/typescript/README.md +34 -0
  33. package/clients/typescript/examples/fake-session.mjs +36 -0
  34. package/clients/typescript/index.d.ts +144 -0
  35. package/clients/typescript/index.mjs +192 -0
  36. package/clients/typescript/package.json +8 -0
  37. package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
  38. package/docs/adr/0002-app-local-zmr-contract.md +39 -0
  39. package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
  40. package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
  41. package/docs/adr/README.md +12 -0
  42. package/docs/ai-agents.md +154 -0
  43. package/docs/app-integration.md +330 -0
  44. package/docs/benchmarking.md +273 -0
  45. package/docs/client-installation.md +133 -0
  46. package/docs/clients.md +98 -0
  47. package/docs/config.md +175 -0
  48. package/docs/demo.md +259 -0
  49. package/docs/frameworks.md +72 -0
  50. package/docs/install.md +95 -0
  51. package/docs/npm.md +356 -0
  52. package/docs/protocol-fixtures/README.md +8 -0
  53. package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
  54. package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
  55. package/docs/protocol-versioning.md +65 -0
  56. package/docs/protocol.md +560 -0
  57. package/docs/scenario-authoring.md +88 -0
  58. package/docs/trace-privacy.md +88 -0
  59. package/docs/troubleshooting.md +256 -0
  60. package/examples/android-app-auth-probe.json +89 -0
  61. package/examples/android-app-error-state.json +13 -0
  62. package/examples/android-app-login-smoke.json +192 -0
  63. package/examples/android-app-onboarding.json +12 -0
  64. package/examples/android-app-referral-deep-link.json +12 -0
  65. package/examples/android-shim-smoke.json +19 -0
  66. package/examples/demo-failure.json +12 -0
  67. package/examples/demo-fake.json +14 -0
  68. package/examples/ios-dev-client-open-link.json +26 -0
  69. package/examples/ios-dev-client-route-snapshot.json +24 -0
  70. package/examples/ios-shim-smoke.json +23 -0
  71. package/examples/ios-smoke.json +9 -0
  72. package/go.work +3 -0
  73. package/npm/agents.mjs +183 -0
  74. package/npm/app-config.mjs +95 -0
  75. package/npm/build-zmr.mjs +21 -0
  76. package/npm/commands.mjs +104 -0
  77. package/npm/generated-files.mjs +50 -0
  78. package/npm/index.mjs +75 -0
  79. package/npm/init-app.mjs +80 -0
  80. package/npm/package-scripts.mjs +72 -0
  81. package/npm/postinstall.mjs +21 -0
  82. package/npm/scaffold.mjs +179 -0
  83. package/npm/scenarios.mjs +93 -0
  84. package/npm/setup.mjs +69 -0
  85. package/npm/wizard.mjs +117 -0
  86. package/npm/zmr.mjs +23 -0
  87. package/package.json +118 -0
  88. package/schemas/README.md +26 -0
  89. package/schemas/action-result.schema.json +27 -0
  90. package/schemas/capabilities-output.schema.json +98 -0
  91. package/schemas/devices-output.schema.json +25 -0
  92. package/schemas/doctor-output.schema.json +51 -0
  93. package/schemas/explain-output.schema.json +51 -0
  94. package/schemas/import-output.schema.json +23 -0
  95. package/schemas/init-output.schema.json +71 -0
  96. package/schemas/json-rpc.schema.json +55 -0
  97. package/schemas/release-manifest.schema.json +43 -0
  98. package/schemas/release-readiness-output.schema.json +127 -0
  99. package/schemas/run-output.schema.json +43 -0
  100. package/schemas/scenario.schema.json +128 -0
  101. package/schemas/schemas-output.schema.json +26 -0
  102. package/schemas/semantic-snapshot.schema.json +116 -0
  103. package/schemas/snapshot.schema.json +60 -0
  104. package/schemas/trace-event.schema.json +14 -0
  105. package/schemas/trace-manifest.schema.json +59 -0
  106. package/schemas/validate-output.schema.json +42 -0
  107. package/schemas/version-output.schema.json +23 -0
  108. package/schemas/zmr-config.schema.json +75 -0
  109. package/scripts/android-emulator.sh +126 -0
  110. package/scripts/assert-ios-physical-ready.sh +213 -0
  111. package/scripts/benchmark-command.sh +307 -0
  112. package/scripts/benchmark.sh +359 -0
  113. package/scripts/benchmark_gate.py +117 -0
  114. package/scripts/benchmark_result_row.py +88 -0
  115. package/scripts/compare-benchmarks.py +288 -0
  116. package/scripts/create-android-demo-app.sh +342 -0
  117. package/scripts/create-ios-demo-app.sh +261 -0
  118. package/scripts/demo-android-real.sh +232 -0
  119. package/scripts/demo-ios-real.sh +270 -0
  120. package/scripts/demo.sh +464 -0
  121. package/scripts/device-matrix.sh +338 -0
  122. package/scripts/ensure-ios-shim-target.rb +237 -0
  123. package/scripts/install-android-shim.sh +281 -0
  124. package/scripts/install-ios-shim.sh +589 -0
  125. package/scripts/pilot-gate.sh +560 -0
  126. package/scripts/release-readiness.py +838 -0
  127. package/scripts/release-readiness.sh +91 -0
  128. package/scripts/run-android-pilot.sh +561 -0
  129. package/scripts/run-ios-pilot.sh +509 -0
  130. package/shims/android/README.md +21 -0
  131. package/shims/android/ZMRShimInstrumentedTest.java +152 -0
  132. package/shims/android/protocol.md +18 -0
  133. package/shims/ios/README.md +50 -0
  134. package/shims/ios/ZMRShim.swift +110 -0
  135. package/shims/ios/ZMRShimUITestCase.swift +518 -0
  136. package/shims/ios/protocol.md +74 -0
  137. package/skills/zmr-mobile-testing/SKILL.md +127 -0
  138. package/src/android.zig +344 -0
  139. package/src/android_device_info.zig +99 -0
  140. package/src/android_emulator.zig +154 -0
  141. package/src/android_screen_recording.zig +112 -0
  142. package/src/android_shell.zig +112 -0
  143. package/src/bundle.zig +124 -0
  144. package/src/bundle_redaction.zig +272 -0
  145. package/src/bundle_tar.zig +123 -0
  146. package/src/cli_devices.zig +97 -0
  147. package/src/cli_doctor.zig +114 -0
  148. package/src/cli_import.zig +70 -0
  149. package/src/cli_info.zig +39 -0
  150. package/src/cli_init.zig +72 -0
  151. package/src/cli_output.zig +467 -0
  152. package/src/cli_run.zig +259 -0
  153. package/src/cli_serve.zig +287 -0
  154. package/src/cli_trace.zig +111 -0
  155. package/src/cli_validate.zig +41 -0
  156. package/src/command.zig +211 -0
  157. package/src/config.zig +305 -0
  158. package/src/config_diagnostics.zig +212 -0
  159. package/src/config_paths.zig +49 -0
  160. package/src/device_registry.zig +37 -0
  161. package/src/doctor.zig +412 -0
  162. package/src/doctor_hints.zig +52 -0
  163. package/src/errors.zig +55 -0
  164. package/src/fake_device.zig +163 -0
  165. package/src/health.zig +28 -0
  166. package/src/importer.zig +343 -0
  167. package/src/importer_json.zig +100 -0
  168. package/src/importer_model.zig +103 -0
  169. package/src/ios.zig +399 -0
  170. package/src/ios_devices.zig +219 -0
  171. package/src/ios_lifecycle.zig +72 -0
  172. package/src/ios_shim.zig +242 -0
  173. package/src/ios_snapshot.zig +20 -0
  174. package/src/json_fields.zig +80 -0
  175. package/src/json_rpc.zig +150 -0
  176. package/src/json_rpc_methods.zig +318 -0
  177. package/src/json_rpc_observation.zig +31 -0
  178. package/src/json_rpc_params.zig +52 -0
  179. package/src/json_rpc_protocol.zig +110 -0
  180. package/src/json_rpc_trace.zig +73 -0
  181. package/src/main.zig +131 -0
  182. package/src/mcp.zig +234 -0
  183. package/src/mcp_protocol.zig +64 -0
  184. package/src/mcp_trace.zig +83 -0
  185. package/src/report.zig +346 -0
  186. package/src/report_html.zig +63 -0
  187. package/src/report_values.zig +27 -0
  188. package/src/run_options.zig +152 -0
  189. package/src/runner.zig +280 -0
  190. package/src/runner_actions.zig +109 -0
  191. package/src/runner_config.zig +6 -0
  192. package/src/runner_diagnostics.zig +268 -0
  193. package/src/runner_events.zig +170 -0
  194. package/src/runner_native.zig +88 -0
  195. package/src/runner_waits.zig +300 -0
  196. package/src/scaffold.zig +472 -0
  197. package/src/scenario.zig +346 -0
  198. package/src/scenario_fields.zig +50 -0
  199. package/src/schema_registry.zig +53 -0
  200. package/src/selector.zig +84 -0
  201. package/src/semantic.zig +171 -0
  202. package/src/trace.zig +315 -0
  203. package/src/trace_json.zig +340 -0
  204. package/src/trace_summary.zig +218 -0
  205. package/src/trace_summary_diagnostic.zig +202 -0
  206. package/src/types.zig +120 -0
  207. package/src/uiautomator.zig +164 -0
  208. package/src/validation.zig +187 -0
  209. package/src/version.zig +22 -0
  210. package/viewer/app.js +373 -0
  211. package/viewer/index.html +126 -0
  212. package/viewer/parser.js +233 -0
  213. package/viewer/styles.css +585 -0
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SOURCE="${BASH_SOURCE[0]}"
5
+ while [[ -h "$SOURCE" ]]; do
6
+ SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
7
+ SOURCE="$(readlink "$SOURCE")"
8
+ if [[ "$SOURCE" != /* ]]; then
9
+ SOURCE="$SOURCE_DIR/$SOURCE"
10
+ fi
11
+ done
12
+
13
+ ROOT="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
14
+ OUT="/tmp/zmr-android-demo-$(date +%Y%m%d-%H%M%S)"
15
+ APP_ID="com.example.mobiletest"
16
+ DEVICE="emulator-5554"
17
+ AVD=""
18
+ RUNS="1"
19
+ TRACE_ROOT=""
20
+ API="35"
21
+ BUILD_TOOLS="35.0.1"
22
+ ANDROID_SDK="${ANDROID_HOME:-$HOME/Library/Android/sdk}"
23
+ ADB="${ADB:-adb}"
24
+ EMULATOR="${EMULATOR:-emulator}"
25
+ AUTO_BOOT_EMULATOR=1
26
+ DRY_RUN=0
27
+
28
+ usage() {
29
+ cat <<'USAGE'
30
+ Usage:
31
+ scripts/demo-android-real.sh [options]
32
+
33
+ Creates a generic public Android demo app, installs it on an Android
34
+ emulator/device, and runs the ZMR Android smoke scenario with trace output.
35
+
36
+ Options:
37
+ --out <dir> Demo app output directory. Default: /tmp/zmr-android-demo-<timestamp>.
38
+ --app-id <id> Android application id. Default: com.example.mobiletest.
39
+ --device <serial> Android device/emulator serial. Default: emulator-5554.
40
+ --avd <name> AVD to boot when the requested device is not ready.
41
+ --runs <n> Scenario run count. Default: 1.
42
+ --trace-root <dir> Trace output directory. Default: <out>/traces/pilot.
43
+ --api <level> Android platform API level. Default: 35.
44
+ --build-tools <ver> Android build-tools version. Default: 35.0.1.
45
+ --android-sdk <path> Android SDK root. Default: ANDROID_HOME or ~/Library/Android/sdk.
46
+ --adb <path> adb path. Default: adb.
47
+ --emulator <path> emulator path. Default: emulator.
48
+ --no-auto-boot-emulator Require an already ready device/emulator.
49
+ --dry-run Print commands without executing them.
50
+ -h, --help Show this help.
51
+ USAGE
52
+ }
53
+
54
+ die() {
55
+ echo "error: $*" >&2
56
+ exit 2
57
+ }
58
+
59
+ require_value() {
60
+ local flag="$1"
61
+ local value="${2-}"
62
+ if [[ -z "$value" || "$value" == --* ]]; then
63
+ die "$flag requires a value"
64
+ fi
65
+ printf '%s\n' "$value"
66
+ }
67
+
68
+ quote_cmd() {
69
+ local quoted=()
70
+ local arg
71
+ for arg in "$@"; do
72
+ quoted+=("$(printf '%q' "$arg")")
73
+ done
74
+ printf '%s\n' "${quoted[*]}"
75
+ }
76
+
77
+ run() {
78
+ echo "+ $(quote_cmd "$@")"
79
+ if [[ "$DRY_RUN" -eq 0 ]]; then
80
+ "$@"
81
+ fi
82
+ }
83
+
84
+ run_allow_fail() {
85
+ echo "+ $(quote_cmd "$@")"
86
+ if [[ "$DRY_RUN" -eq 0 ]]; then
87
+ "$@" >/dev/null 2>&1 || true
88
+ fi
89
+ }
90
+
91
+ run_background() {
92
+ echo "+ $(quote_cmd "$@") &"
93
+ if [[ "$DRY_RUN" -eq 0 ]]; then
94
+ "$@" &
95
+ fi
96
+ }
97
+
98
+ device_ready() {
99
+ echo "+ $(quote_cmd "$ADB" -s "$DEVICE" get-state)"
100
+ if [[ "$DRY_RUN" -eq 1 ]]; then
101
+ return 1
102
+ fi
103
+ local state
104
+ state="$("$ADB" -s "$DEVICE" get-state 2>/dev/null | tr -d '\r' || true)"
105
+ [[ "$state" == "device" ]]
106
+ }
107
+
108
+ ensure_android_device_ready() {
109
+ if device_ready; then
110
+ return 0
111
+ fi
112
+
113
+ if [[ "$AUTO_BOOT_EMULATOR" -eq 0 ]]; then
114
+ die "device $DEVICE is not ready and auto boot is disabled"
115
+ fi
116
+ [[ -n "$AVD" ]] || die "device $DEVICE is not ready; pass --avd <name> to boot an emulator or start a device manually"
117
+
118
+ echo "+ auto boot Android emulator $AVD when $DEVICE is not ready"
119
+ run_background "$ROOT/scripts/android-emulator.sh" boot --avd "$AVD" --device "$DEVICE"
120
+ run "$ROOT/scripts/android-emulator.sh" wait-ready --device "$DEVICE"
121
+ }
122
+
123
+ while [[ $# -gt 0 ]]; do
124
+ case "$1" in
125
+ --out)
126
+ OUT="$(require_value "$1" "${2-}")"
127
+ shift 2
128
+ ;;
129
+ --app-id)
130
+ APP_ID="$(require_value "$1" "${2-}")"
131
+ shift 2
132
+ ;;
133
+ --device)
134
+ DEVICE="$(require_value "$1" "${2-}")"
135
+ shift 2
136
+ ;;
137
+ --avd)
138
+ AVD="$(require_value "$1" "${2-}")"
139
+ shift 2
140
+ ;;
141
+ --runs)
142
+ RUNS="$(require_value "$1" "${2-}")"
143
+ shift 2
144
+ ;;
145
+ --trace-root)
146
+ TRACE_ROOT="$(require_value "$1" "${2-}")"
147
+ shift 2
148
+ ;;
149
+ --api)
150
+ API="$(require_value "$1" "${2-}")"
151
+ shift 2
152
+ ;;
153
+ --build-tools)
154
+ BUILD_TOOLS="$(require_value "$1" "${2-}")"
155
+ shift 2
156
+ ;;
157
+ --android-sdk)
158
+ ANDROID_SDK="$(require_value "$1" "${2-}")"
159
+ shift 2
160
+ ;;
161
+ --adb)
162
+ ADB="$(require_value "$1" "${2-}")"
163
+ shift 2
164
+ ;;
165
+ --emulator)
166
+ EMULATOR="$(require_value "$1" "${2-}")"
167
+ shift 2
168
+ ;;
169
+ --no-auto-boot-emulator)
170
+ AUTO_BOOT_EMULATOR=0
171
+ shift
172
+ ;;
173
+ --dry-run)
174
+ DRY_RUN=1
175
+ shift
176
+ ;;
177
+ -h|--help)
178
+ usage
179
+ exit 0
180
+ ;;
181
+ *)
182
+ die "unknown argument: $1"
183
+ ;;
184
+ esac
185
+ done
186
+
187
+ [[ -n "$OUT" ]] || die "--out must not be empty"
188
+ [[ "$APP_ID" =~ ^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$ ]] || die "--app-id must be a Java-style package id"
189
+ [[ "$RUNS" =~ ^[0-9]+$ && "$RUNS" -ge 1 ]] || die "--runs must be a positive integer"
190
+ [[ "$API" =~ ^[0-9]+$ ]] || die "--api must be an integer"
191
+ [[ -n "$BUILD_TOOLS" ]] || die "--build-tools must be non-empty"
192
+
193
+ if [[ -z "$TRACE_ROOT" ]]; then
194
+ TRACE_ROOT="$OUT/traces/pilot"
195
+ fi
196
+
197
+ APK="$OUT/build/app-debug.apk"
198
+ SCENARIO="$OUT/.zmr/android-smoke.json"
199
+
200
+ echo "Android real demo app: $OUT"
201
+ echo "Android real demo traces: $TRACE_ROOT"
202
+ if [[ "$DRY_RUN" -eq 1 ]]; then
203
+ echo "DRY RUN: commands will be printed but not executed"
204
+ fi
205
+
206
+ run "$ROOT/scripts/create-android-demo-app.sh" \
207
+ --out "$OUT" \
208
+ --app-id "$APP_ID" \
209
+ --api "$API" \
210
+ --build-tools "$BUILD_TOOLS" \
211
+ --android-sdk "$ANDROID_SDK"
212
+
213
+ ensure_android_device_ready
214
+
215
+ run_allow_fail "$ADB" -s "$DEVICE" uninstall "$APP_ID"
216
+ run "$ADB" -s "$DEVICE" install -r "$APK"
217
+
218
+ run "$ROOT/scripts/benchmark.sh" \
219
+ --zmr "$SCENARIO" \
220
+ --device "$DEVICE" \
221
+ --app-id "$APP_ID" \
222
+ --runs "$RUNS" \
223
+ --trace-root "$TRACE_ROOT" \
224
+ --min-pass-rate 100 \
225
+ --max-failures 0
226
+
227
+ cat <<EOF
228
+
229
+ Android real demo complete.
230
+ App directory: $OUT
231
+ Trace directory: $TRACE_ROOT
232
+ EOF
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SOURCE="${BASH_SOURCE[0]}"
5
+ while [[ -h "$SOURCE" ]]; do
6
+ SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
7
+ SOURCE="$(readlink "$SOURCE")"
8
+ if [[ "$SOURCE" != /* ]]; then
9
+ SOURCE="$SOURCE_DIR/$SOURCE"
10
+ fi
11
+ done
12
+
13
+ ROOT="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
14
+ OUT="/tmp/zmr-ios-demo-$(date +%Y%m%d-%H%M%S)"
15
+ APP_NAME="ZMRDemo"
16
+ APP_ID="com.example.mobiletest"
17
+ DEVICE="booted"
18
+ DEPLOYMENT_TARGET="16.0"
19
+ RUNS="1"
20
+ TRACE_ROOT=""
21
+ XCRUN="${XCRUN:-xcrun}"
22
+ AUTO_BOOT_SIMULATOR=1
23
+ CLEANUP_BUILD_PRODUCTS=0
24
+ DRY_RUN=0
25
+
26
+ usage() {
27
+ cat <<'USAGE'
28
+ Usage:
29
+ scripts/demo-ios-real.sh [options]
30
+
31
+ Creates a generic public iOS simulator demo app, builds it, and runs the real
32
+ ZMR iOS pilot with the generated XCTest shim.
33
+
34
+ Options:
35
+ --out <dir> Demo app output directory. Default: /tmp/zmr-ios-demo-<timestamp>.
36
+ --name <name> App target name. Default: ZMRDemo.
37
+ --app-id <id> App bundle id. Default: com.example.mobiletest.
38
+ --device <udid|booted> Simulator target. Default: booted.
39
+ --deployment-target <ver> iOS deployment target. Default: 16.0.
40
+ --runs <n> Pilot run count. Default: 1.
41
+ --trace-root <dir> Trace output directory. Default: <out>/traces/pilot.
42
+ --xcrun <path> xcrun path. Default: xcrun.
43
+ --no-auto-boot-simulator Require an already booted simulator.
44
+ --cleanup-build-products Remove generated DerivedData after pilot traces are written.
45
+ --dry-run Print commands without executing them.
46
+ -h, --help Show this help.
47
+ USAGE
48
+ }
49
+
50
+ die() {
51
+ echo "error: $*" >&2
52
+ exit 2
53
+ }
54
+
55
+ require_value() {
56
+ local flag="$1"
57
+ local value="${2-}"
58
+ if [[ -z "$value" || "$value" == --* ]]; then
59
+ die "$flag requires a value"
60
+ fi
61
+ printf '%s\n' "$value"
62
+ }
63
+
64
+ quote_cmd() {
65
+ local quoted=()
66
+ local arg
67
+ for arg in "$@"; do
68
+ quoted+=("$(printf '%q' "$arg")")
69
+ done
70
+ printf '%s\n' "${quoted[*]}"
71
+ }
72
+
73
+ run() {
74
+ echo "+ $(quote_cmd "$@")"
75
+ if [[ "$DRY_RUN" -eq 0 ]]; then
76
+ "$@"
77
+ fi
78
+ }
79
+
80
+ available_ios_simulators() {
81
+ "$XCRUN" simctl list devices available --json | python3 -c '
82
+ import json
83
+ import sys
84
+
85
+ data = json.load(sys.stdin)
86
+ for runtime, devices in data.get("devices", {}).items():
87
+ if "iOS" not in runtime:
88
+ continue
89
+ for device in devices:
90
+ if device.get("isAvailable", True) and device.get("state") in ("Shutdown", "Booted"):
91
+ udid = device.get("udid")
92
+ if udid:
93
+ print(udid)
94
+ '
95
+ }
96
+
97
+ simulator_is_booted() {
98
+ local wanted="${1:-}"
99
+ local booted_text
100
+ booted_text="$("$XCRUN" simctl list devices booted 2>/dev/null || true)"
101
+ if [[ -z "$wanted" || "$wanted" == "booted" ]]; then
102
+ [[ "$booted_text" == *"(Booted)"* ]]
103
+ return
104
+ fi
105
+ [[ "$booted_text" == *"$wanted"* && "$booted_text" == *"(Booted)"* ]]
106
+ }
107
+
108
+ ensure_ios_simulator_ready() {
109
+ if [[ "$AUTO_BOOT_SIMULATOR" -eq 0 ]]; then
110
+ return 0
111
+ fi
112
+
113
+ run "$XCRUN" simctl list devices booted
114
+ if [[ "$DRY_RUN" -eq 1 ]]; then
115
+ if [[ "$DEVICE" == "booted" ]]; then
116
+ echo "+ auto boot first available iOS simulator when no simulator is booted"
117
+ echo "+ try available iOS simulators until one boots"
118
+ else
119
+ echo "+ auto boot iOS simulator $DEVICE when it is not booted"
120
+ fi
121
+ run "$XCRUN" simctl bootstatus "$DEVICE" -b
122
+ return 0
123
+ fi
124
+
125
+ if simulator_is_booted "$DEVICE"; then
126
+ run "$XCRUN" simctl bootstatus "$DEVICE" -b
127
+ return 0
128
+ fi
129
+
130
+ local boot_target="$DEVICE"
131
+ if [[ "$DEVICE" == "booted" ]]; then
132
+ local candidates=()
133
+ local listed_candidate
134
+ while IFS= read -r listed_candidate; do
135
+ [[ -n "$listed_candidate" ]] && candidates+=("$listed_candidate")
136
+ done < <(available_ios_simulators)
137
+ if [[ "${#candidates[@]}" -eq 0 ]]; then
138
+ die "no available iOS simulator found to boot"
139
+ fi
140
+ local candidate status
141
+ for candidate in "${candidates[@]}"; do
142
+ echo "+ $(quote_cmd "$XCRUN" simctl boot "$candidate")"
143
+ set +e
144
+ "$XCRUN" simctl boot "$candidate"
145
+ status=$?
146
+ set -e
147
+ if [[ "$status" -eq 0 ]]; then
148
+ run "$XCRUN" simctl bootstatus "$candidate" -b
149
+ return 0
150
+ fi
151
+ echo "warning: failed to boot iOS simulator $candidate; trying next available simulator" >&2
152
+ done
153
+ die "no available iOS simulator could be booted"
154
+ fi
155
+
156
+ run "$XCRUN" simctl boot "$boot_target"
157
+ run "$XCRUN" simctl bootstatus "$boot_target" -b
158
+ }
159
+
160
+ while [[ $# -gt 0 ]]; do
161
+ case "$1" in
162
+ --out)
163
+ OUT="$(require_value "$1" "${2-}")"
164
+ shift 2
165
+ ;;
166
+ --name)
167
+ APP_NAME="$(require_value "$1" "${2-}")"
168
+ shift 2
169
+ ;;
170
+ --app-id)
171
+ APP_ID="$(require_value "$1" "${2-}")"
172
+ shift 2
173
+ ;;
174
+ --device)
175
+ DEVICE="$(require_value "$1" "${2-}")"
176
+ shift 2
177
+ ;;
178
+ --deployment-target)
179
+ DEPLOYMENT_TARGET="$(require_value "$1" "${2-}")"
180
+ shift 2
181
+ ;;
182
+ --runs)
183
+ RUNS="$(require_value "$1" "${2-}")"
184
+ shift 2
185
+ ;;
186
+ --trace-root)
187
+ TRACE_ROOT="$(require_value "$1" "${2-}")"
188
+ shift 2
189
+ ;;
190
+ --xcrun)
191
+ XCRUN="$(require_value "$1" "${2-}")"
192
+ shift 2
193
+ ;;
194
+ --no-auto-boot-simulator)
195
+ AUTO_BOOT_SIMULATOR=0
196
+ shift
197
+ ;;
198
+ --cleanup-build-products)
199
+ CLEANUP_BUILD_PRODUCTS=1
200
+ shift
201
+ ;;
202
+ --dry-run)
203
+ DRY_RUN=1
204
+ shift
205
+ ;;
206
+ -h|--help)
207
+ usage
208
+ exit 0
209
+ ;;
210
+ *)
211
+ die "unknown argument: $1"
212
+ ;;
213
+ esac
214
+ done
215
+
216
+ [[ -n "$OUT" ]] || die "--out must not be empty"
217
+ [[ "$APP_NAME" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || die "--name must be a valid Swift identifier"
218
+ [[ "$RUNS" =~ ^[0-9]+$ && "$RUNS" -ge 1 ]] || die "--runs must be a positive integer"
219
+
220
+ if [[ -z "$TRACE_ROOT" ]]; then
221
+ TRACE_ROOT="$OUT/traces/pilot"
222
+ fi
223
+
224
+ PROJECT_PATH="$OUT/ios/$APP_NAME.xcodeproj"
225
+ DERIVED_DATA="$OUT/DerivedData"
226
+ APP_PATH="$DERIVED_DATA/Build/Products/Debug-iphonesimulator/$APP_NAME.app"
227
+ IOS_SHIM="$OUT/.zmr/ios-shim"
228
+
229
+ echo "iOS real demo app: $OUT"
230
+ echo "iOS real demo traces: $TRACE_ROOT"
231
+ if [[ "$DRY_RUN" -eq 1 ]]; then
232
+ echo "DRY RUN: commands will be printed but not executed"
233
+ fi
234
+
235
+ run "$ROOT/scripts/create-ios-demo-app.sh" \
236
+ --out "$OUT" \
237
+ --name "$APP_NAME" \
238
+ --bundle-id "$APP_ID" \
239
+ --deployment-target "$DEPLOYMENT_TARGET"
240
+
241
+ run xcodebuild \
242
+ -project "$PROJECT_PATH" \
243
+ -scheme "$APP_NAME" \
244
+ -destination "generic/platform=iOS Simulator" \
245
+ -configuration Debug \
246
+ -derivedDataPath "$DERIVED_DATA" \
247
+ build
248
+
249
+ ensure_ios_simulator_ready
250
+
251
+ run "$ROOT/scripts/run-ios-pilot.sh" \
252
+ --app-root "$OUT" \
253
+ --app-path "$APP_PATH" \
254
+ --device "$DEVICE" \
255
+ --app-id "$APP_ID" \
256
+ --xcrun "$XCRUN" \
257
+ --ios-shim "$IOS_SHIM" \
258
+ --runs "$RUNS" \
259
+ --trace-root "$TRACE_ROOT"
260
+
261
+ if [[ "$CLEANUP_BUILD_PRODUCTS" -eq 1 ]]; then
262
+ run rm -rf "$DERIVED_DATA"
263
+ fi
264
+
265
+ cat <<EOF
266
+
267
+ iOS real demo complete.
268
+ App directory: $OUT
269
+ Trace directory: $TRACE_ROOT
270
+ EOF