zig-mobile-runner 0.1.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.
Files changed (225) hide show
  1. package/CHANGELOG.md +484 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/FEATURES.md +112 -0
  4. package/LICENSE +21 -0
  5. package/README.md +255 -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 +144 -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 +156 -0
  43. package/docs/app-integration.md +316 -0
  44. package/docs/benchmarking.md +275 -0
  45. package/docs/client-installation.md +141 -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/dsl.md +57 -0
  50. package/docs/install.md +233 -0
  51. package/docs/market-positioning.md +70 -0
  52. package/docs/npm.md +359 -0
  53. package/docs/protocol-fixtures/README.md +8 -0
  54. package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
  55. package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
  56. package/docs/protocol-versioning.md +65 -0
  57. package/docs/protocol.md +560 -0
  58. package/docs/publication.md +77 -0
  59. package/docs/release-audit.md +99 -0
  60. package/docs/release-candidate.md +111 -0
  61. package/docs/release-evidence.md +188 -0
  62. package/docs/release-notes-template.md +58 -0
  63. package/docs/roadmap.md +334 -0
  64. package/docs/scenario-authoring.md +88 -0
  65. package/docs/shipping.md +170 -0
  66. package/docs/trace-privacy.md +88 -0
  67. package/docs/troubleshooting.md +256 -0
  68. package/examples/android-app-auth-probe.json +89 -0
  69. package/examples/android-app-error-state.json +13 -0
  70. package/examples/android-app-login-smoke.json +192 -0
  71. package/examples/android-app-onboarding.json +12 -0
  72. package/examples/android-app-referral-deep-link.json +12 -0
  73. package/examples/android-shim-smoke.json +19 -0
  74. package/examples/demo-failure.json +12 -0
  75. package/examples/demo-fake.json +14 -0
  76. package/examples/ios-dev-client-open-link.json +26 -0
  77. package/examples/ios-dev-client-route-snapshot.json +24 -0
  78. package/examples/ios-shim-smoke.json +23 -0
  79. package/examples/ios-smoke.json +9 -0
  80. package/go.work +3 -0
  81. package/npm/agents.mjs +183 -0
  82. package/npm/app-config.mjs +95 -0
  83. package/npm/build-zmr.mjs +21 -0
  84. package/npm/commands.mjs +104 -0
  85. package/npm/generated-files.mjs +50 -0
  86. package/npm/index.mjs +75 -0
  87. package/npm/init-app.mjs +80 -0
  88. package/npm/package-scripts.mjs +72 -0
  89. package/npm/postinstall.mjs +21 -0
  90. package/npm/scaffold.mjs +179 -0
  91. package/npm/scenarios.mjs +93 -0
  92. package/npm/setup.mjs +69 -0
  93. package/npm/wizard.mjs +117 -0
  94. package/npm/zmr.mjs +23 -0
  95. package/package.json +114 -0
  96. package/prebuilds/darwin-arm64/zmr +0 -0
  97. package/prebuilds/darwin-x64/zmr +0 -0
  98. package/prebuilds/linux-arm64/zmr +0 -0
  99. package/prebuilds/linux-x64/zmr +0 -0
  100. package/schemas/README.md +26 -0
  101. package/schemas/action-result.schema.json +27 -0
  102. package/schemas/capabilities-output.schema.json +98 -0
  103. package/schemas/devices-output.schema.json +25 -0
  104. package/schemas/doctor-output.schema.json +51 -0
  105. package/schemas/explain-output.schema.json +51 -0
  106. package/schemas/import-output.schema.json +23 -0
  107. package/schemas/init-output.schema.json +71 -0
  108. package/schemas/json-rpc.schema.json +55 -0
  109. package/schemas/release-manifest.schema.json +43 -0
  110. package/schemas/release-readiness-output.schema.json +127 -0
  111. package/schemas/run-output.schema.json +43 -0
  112. package/schemas/scenario.schema.json +128 -0
  113. package/schemas/schemas-output.schema.json +26 -0
  114. package/schemas/semantic-snapshot.schema.json +116 -0
  115. package/schemas/snapshot.schema.json +60 -0
  116. package/schemas/trace-event.schema.json +14 -0
  117. package/schemas/trace-manifest.schema.json +59 -0
  118. package/schemas/validate-output.schema.json +42 -0
  119. package/schemas/version-output.schema.json +23 -0
  120. package/schemas/zmr-config.schema.json +75 -0
  121. package/scripts/android-emulator.sh +126 -0
  122. package/scripts/assert-ios-physical-ready.sh +213 -0
  123. package/scripts/benchmark-command.sh +307 -0
  124. package/scripts/benchmark.sh +359 -0
  125. package/scripts/benchmark_gate.py +117 -0
  126. package/scripts/benchmark_result_row.py +88 -0
  127. package/scripts/compare-benchmarks.py +288 -0
  128. package/scripts/create-android-demo-app.sh +342 -0
  129. package/scripts/create-ios-demo-app.sh +261 -0
  130. package/scripts/demo-android-real.sh +232 -0
  131. package/scripts/demo-ios-real.sh +270 -0
  132. package/scripts/demo.sh +464 -0
  133. package/scripts/device-matrix.sh +338 -0
  134. package/scripts/ensure-ios-shim-target.rb +237 -0
  135. package/scripts/install-android-shim.sh +281 -0
  136. package/scripts/install-ios-shim.sh +589 -0
  137. package/scripts/pilot-gate.sh +560 -0
  138. package/scripts/release-readiness.py +838 -0
  139. package/scripts/release-readiness.sh +91 -0
  140. package/scripts/run-android-pilot.sh +561 -0
  141. package/scripts/run-ios-pilot.sh +509 -0
  142. package/shims/android/README.md +21 -0
  143. package/shims/android/ZMRShimInstrumentedTest.java +152 -0
  144. package/shims/android/protocol.md +18 -0
  145. package/shims/ios/README.md +50 -0
  146. package/shims/ios/ZMRShim.swift +110 -0
  147. package/shims/ios/ZMRShimUITestCase.swift +475 -0
  148. package/shims/ios/protocol.md +74 -0
  149. package/skills/zmr-mobile-testing/SKILL.md +127 -0
  150. package/src/android.zig +344 -0
  151. package/src/android_device_info.zig +99 -0
  152. package/src/android_emulator.zig +154 -0
  153. package/src/android_screen_recording.zig +112 -0
  154. package/src/android_shell.zig +112 -0
  155. package/src/bundle.zig +124 -0
  156. package/src/bundle_redaction.zig +272 -0
  157. package/src/bundle_tar.zig +123 -0
  158. package/src/cli_devices.zig +97 -0
  159. package/src/cli_doctor.zig +114 -0
  160. package/src/cli_import.zig +70 -0
  161. package/src/cli_info.zig +39 -0
  162. package/src/cli_init.zig +72 -0
  163. package/src/cli_output.zig +467 -0
  164. package/src/cli_run.zig +259 -0
  165. package/src/cli_serve.zig +287 -0
  166. package/src/cli_trace.zig +111 -0
  167. package/src/cli_validate.zig +41 -0
  168. package/src/command.zig +211 -0
  169. package/src/config.zig +305 -0
  170. package/src/config_diagnostics.zig +212 -0
  171. package/src/config_paths.zig +49 -0
  172. package/src/device_registry.zig +37 -0
  173. package/src/doctor.zig +412 -0
  174. package/src/doctor_hints.zig +52 -0
  175. package/src/errors.zig +55 -0
  176. package/src/fake_device.zig +163 -0
  177. package/src/health.zig +28 -0
  178. package/src/importer.zig +343 -0
  179. package/src/importer_json.zig +100 -0
  180. package/src/importer_model.zig +103 -0
  181. package/src/ios.zig +399 -0
  182. package/src/ios_devices.zig +219 -0
  183. package/src/ios_lifecycle.zig +72 -0
  184. package/src/ios_shim.zig +242 -0
  185. package/src/ios_snapshot.zig +20 -0
  186. package/src/json_fields.zig +80 -0
  187. package/src/json_rpc.zig +150 -0
  188. package/src/json_rpc_methods.zig +318 -0
  189. package/src/json_rpc_observation.zig +31 -0
  190. package/src/json_rpc_params.zig +52 -0
  191. package/src/json_rpc_protocol.zig +110 -0
  192. package/src/json_rpc_trace.zig +73 -0
  193. package/src/main.zig +135 -0
  194. package/src/mcp.zig +234 -0
  195. package/src/mcp_protocol.zig +64 -0
  196. package/src/mcp_trace.zig +83 -0
  197. package/src/report.zig +346 -0
  198. package/src/report_html.zig +63 -0
  199. package/src/report_values.zig +27 -0
  200. package/src/run_options.zig +152 -0
  201. package/src/runner.zig +280 -0
  202. package/src/runner_actions.zig +109 -0
  203. package/src/runner_config.zig +6 -0
  204. package/src/runner_diagnostics.zig +268 -0
  205. package/src/runner_events.zig +170 -0
  206. package/src/runner_native.zig +88 -0
  207. package/src/runner_waits.zig +300 -0
  208. package/src/scaffold.zig +472 -0
  209. package/src/scenario.zig +346 -0
  210. package/src/scenario_fields.zig +50 -0
  211. package/src/schema_registry.zig +53 -0
  212. package/src/selector.zig +84 -0
  213. package/src/semantic.zig +171 -0
  214. package/src/trace.zig +315 -0
  215. package/src/trace_json.zig +340 -0
  216. package/src/trace_summary.zig +218 -0
  217. package/src/trace_summary_diagnostic.zig +202 -0
  218. package/src/types.zig +120 -0
  219. package/src/uiautomator.zig +164 -0
  220. package/src/validation.zig +187 -0
  221. package/src/version.zig +22 -0
  222. package/viewer/app.js +373 -0
  223. package/viewer/index.html +126 -0
  224. package/viewer/parser.js +233 -0
  225. package/viewer/styles.css +585 -0
@@ -0,0 +1,589 @@
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
+ APP_ROOT=""
15
+ SCHEME=""
16
+ TEST_TARGET=""
17
+ APP_TARGET=""
18
+ BUNDLE_ID=""
19
+ TEST_BUNDLE_ID=""
20
+ DEVICE="booted"
21
+ DEVICE_TYPE="simulator"
22
+ CONFIGURATION="Debug"
23
+ WORKSPACE=""
24
+ PROJECT=""
25
+ DERIVED_DATA_PATH=""
26
+ DEPLOYMENT_TARGET="15.0"
27
+ PATCH_XCODEPROJ=0
28
+
29
+ usage() {
30
+ cat <<'USAGE'
31
+ Usage:
32
+ scripts/install-ios-shim.sh --app-root <dir> --scheme <UITestScheme> --bundle-id <id> [options]
33
+
34
+ Writes an app-local .zmr/ios-shim command and XCTest source file.
35
+
36
+ Options:
37
+ --app-root <dir> App repository root. Required.
38
+ --scheme <scheme> Xcode UI test scheme that includes ZMRShimUITestCase. Required.
39
+ --bundle-id <id> App bundle id under test. Required.
40
+ --app-target <name> App target name. Enables generated Xcode target helper.
41
+ --test-target <name> UI test target name. Default: scheme.
42
+ --test-bundle-id <id> UI test bundle id. Default: <bundle-id>.zmr-uitests.
43
+ --workspace <path> Workspace path relative to app root.
44
+ --project <path> Xcode project path relative to app root.
45
+ --derived-data-path <path>
46
+ Derived data path relative to app root.
47
+ --device <udid|booted> Simulator or physical-device destination id. Default: booted.
48
+ --device-type <type> simulator or physical. Default: simulator.
49
+ --configuration <name> Xcode build configuration. Default: Debug.
50
+ --deployment-target <version>
51
+ iOS deployment target for generated UI test target. Default: 15.0.
52
+ --patch-xcodeproj Run the generated Xcodeproj helper immediately.
53
+ -h, --help Show this help.
54
+
55
+ After running, use .zmr/ensure-ios-shim-target.sh to create/update the UI test
56
+ target when --project or --workspace and --app-target are available, then pass
57
+ --ios-shim ./.zmr/ios-shim to zmr.
58
+ USAGE
59
+ }
60
+
61
+ die() {
62
+ echo "error: $*" >&2
63
+ exit 2
64
+ }
65
+
66
+ require_value() {
67
+ local flag="$1"
68
+ local value="${2-}"
69
+ if [[ -z "$value" || "$value" == --* ]]; then
70
+ die "$flag requires a value"
71
+ fi
72
+ printf '%s\n' "$value"
73
+ }
74
+
75
+ while [[ $# -gt 0 ]]; do
76
+ case "$1" in
77
+ --app-root)
78
+ APP_ROOT="$(require_value "$1" "${2-}")"
79
+ shift 2
80
+ ;;
81
+ --scheme)
82
+ SCHEME="$(require_value "$1" "${2-}")"
83
+ shift 2
84
+ ;;
85
+ --bundle-id)
86
+ BUNDLE_ID="$(require_value "$1" "${2-}")"
87
+ shift 2
88
+ ;;
89
+ --app-target)
90
+ APP_TARGET="$(require_value "$1" "${2-}")"
91
+ shift 2
92
+ ;;
93
+ --test-bundle-id)
94
+ TEST_BUNDLE_ID="$(require_value "$1" "${2-}")"
95
+ shift 2
96
+ ;;
97
+ --workspace)
98
+ WORKSPACE="$(require_value "$1" "${2-}")"
99
+ shift 2
100
+ ;;
101
+ --project)
102
+ PROJECT="$(require_value "$1" "${2-}")"
103
+ shift 2
104
+ ;;
105
+ --derived-data-path)
106
+ DERIVED_DATA_PATH="$(require_value "$1" "${2-}")"
107
+ shift 2
108
+ ;;
109
+ --test-target)
110
+ TEST_TARGET="$(require_value "$1" "${2-}")"
111
+ shift 2
112
+ ;;
113
+ --device)
114
+ DEVICE="$(require_value "$1" "${2-}")"
115
+ shift 2
116
+ ;;
117
+ --device-type)
118
+ DEVICE_TYPE="$(require_value "$1" "${2-}")"
119
+ shift 2
120
+ ;;
121
+ --configuration)
122
+ CONFIGURATION="$(require_value "$1" "${2-}")"
123
+ shift 2
124
+ ;;
125
+ --deployment-target)
126
+ DEPLOYMENT_TARGET="$(require_value "$1" "${2-}")"
127
+ shift 2
128
+ ;;
129
+ --patch-xcodeproj)
130
+ PATCH_XCODEPROJ=1
131
+ shift
132
+ ;;
133
+ -h|--help)
134
+ usage
135
+ exit 0
136
+ ;;
137
+ *)
138
+ die "unknown argument: $1"
139
+ ;;
140
+ esac
141
+ done
142
+
143
+ [[ -n "$APP_ROOT" ]] || die "--app-root is required"
144
+ [[ -n "$SCHEME" ]] || die "--scheme is required"
145
+ [[ -n "$BUNDLE_ID" ]] || die "--bundle-id is required"
146
+ if [[ -n "$WORKSPACE" && -n "$PROJECT" ]]; then
147
+ die "--workspace and --project are mutually exclusive"
148
+ fi
149
+ if [[ -z "$TEST_TARGET" ]]; then
150
+ TEST_TARGET="$SCHEME"
151
+ fi
152
+ if [[ -z "$TEST_BUNDLE_ID" ]]; then
153
+ TEST_BUNDLE_ID="$BUNDLE_ID.zmr-uitests"
154
+ fi
155
+ if [[ "$PATCH_XCODEPROJ" -eq 1 ]]; then
156
+ [[ -n "$PROJECT" || -n "$WORKSPACE" ]] || die "--patch-xcodeproj requires --project or --workspace"
157
+ [[ -n "$APP_TARGET" ]] || die "--patch-xcodeproj requires --app-target"
158
+ fi
159
+ if [[ "$DEVICE_TYPE" != "simulator" && "$DEVICE_TYPE" != "physical" ]]; then
160
+ die "--device-type must be simulator or physical"
161
+ fi
162
+ if [[ "$DEVICE_TYPE" == "physical" && "$DEVICE" == "booted" ]]; then
163
+ die "--device-type physical requires --device <physical-device-id>"
164
+ fi
165
+
166
+ mkdir -p "$APP_ROOT"
167
+ APP_ROOT="$(cd "$APP_ROOT" && pwd)"
168
+ mkdir -p "$APP_ROOT/.zmr" "$APP_ROOT/.zmr/shims/ios"
169
+ rm -f "$APP_ROOT/.zmr/ios-shim-state/build-for-testing.ready"
170
+ cp "$ROOT/shims/ios/ZMRShim.swift" "$APP_ROOT/.zmr/shims/ios/ZMRShim.swift"
171
+ cp "$ROOT/shims/ios/ZMRShimUITestCase.swift" "$APP_ROOT/.zmr/ZMRShimUITestCase.swift"
172
+ cp "$ROOT/scripts/ensure-ios-shim-target.rb" "$APP_ROOT/.zmr/ensure-ios-shim-target.rb"
173
+
174
+ cat > "$APP_ROOT/.zmr/ZMRShimUITests-Info.plist" <<'EOF'
175
+ <?xml version="1.0" encoding="UTF-8"?>
176
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
177
+ <plist version="1.0">
178
+ <dict>
179
+ <key>CFBundleDevelopmentRegion</key>
180
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
181
+ <key>CFBundleExecutable</key>
182
+ <string>$(EXECUTABLE_NAME)</string>
183
+ <key>CFBundleIdentifier</key>
184
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
185
+ <key>CFBundleInfoDictionaryVersion</key>
186
+ <string>6.0</string>
187
+ <key>CFBundleName</key>
188
+ <string>$(PRODUCT_NAME)</string>
189
+ <key>CFBundlePackageType</key>
190
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
191
+ <key>CFBundleShortVersionString</key>
192
+ <string>1.0</string>
193
+ <key>CFBundleVersion</key>
194
+ <string>1</string>
195
+ <key>ZMR_APP_BUNDLE_ID</key>
196
+ <string>$(ZMR_APP_BUNDLE_ID)</string>
197
+ <key>ZMR_SHIM_REQUEST_FILE</key>
198
+ <string>$(ZMR_SHIM_REQUEST_FILE)</string>
199
+ <key>ZMR_SHIM_RESPONSE_FILE</key>
200
+ <string>$(ZMR_SHIM_RESPONSE_FILE)</string>
201
+ <key>ZMR_SHIM_MODE</key>
202
+ <string>$(ZMR_SHIM_MODE)</string>
203
+ <key>ZMR_SHIM_SERVER_DIR</key>
204
+ <string>$(ZMR_SHIM_SERVER_DIR)</string>
205
+ </dict>
206
+ </plist>
207
+ EOF
208
+
209
+ cat > "$APP_ROOT/.zmr/ios-shim" <<EOF
210
+ #!/usr/bin/env bash
211
+ set -euo pipefail
212
+
213
+ cd "$APP_ROOT"
214
+
215
+ STATE_DIR="$APP_ROOT/.zmr/ios-shim-state"
216
+ SERVER_DIR="\$STATE_DIR/server"
217
+ PID_FILE="\$STATE_DIR/xcodebuild.pid"
218
+ READY_FILE="\$SERVER_DIR/ready"
219
+ BUILD_READY_FILE="\$STATE_DIR/build-for-testing.ready"
220
+ LOG_FILE="\$STATE_DIR/xcodebuild.log"
221
+ STDIN_FILE="\$(mktemp)"
222
+ trap 'rm -f "\$STDIN_FILE"' EXIT
223
+
224
+ mkdir -p "\$STATE_DIR" "\$SERVER_DIR"
225
+
226
+ cat > "\$STDIN_FILE"
227
+
228
+ XCODEBUILD_ARGS=()
229
+ if [[ -n "$WORKSPACE" ]]; then
230
+ XCODEBUILD_ARGS+=(-workspace "$WORKSPACE")
231
+ elif [[ -n "$PROJECT" ]]; then
232
+ XCODEBUILD_ARGS+=(-project "$PROJECT")
233
+ fi
234
+ if [[ -n "$DERIVED_DATA_PATH" ]]; then
235
+ XCODEBUILD_ARGS+=(-derivedDataPath "$DERIVED_DATA_PATH")
236
+ fi
237
+
238
+ tail_log() {
239
+ if [[ -f "\$LOG_FILE" ]]; then
240
+ tail -120 "\$LOG_FILE" >&2
241
+ fi
242
+ }
243
+
244
+ resolve_destination() {
245
+ local destination_id="$DEVICE"
246
+ if [[ "\$destination_id" == "booted" ]]; then
247
+ if [[ "$DEVICE_TYPE" == "physical" ]]; then
248
+ echo "physical iOS shim requires an explicit --device id" >&2
249
+ exit 2
250
+ fi
251
+ destination_id="\$(xcrun simctl list devices booted | sed -n 's/.*(\([0-9A-Fa-f-][0-9A-Fa-f-]*\)) (Booted).*/\1/p' | head -n 1)"
252
+ fi
253
+ if [[ -z "\$destination_id" ]]; then
254
+ echo "no booted iOS simulator found" >&2
255
+ exit 2
256
+ fi
257
+ printf '%s' "\$destination_id"
258
+ }
259
+
260
+ destination_spec() {
261
+ local destination_id platform_name
262
+ destination_id="\$(resolve_destination)"
263
+ if [[ "$DEVICE_TYPE" == "physical" ]]; then
264
+ platform_name="iOS"
265
+ else
266
+ platform_name="iOS Simulator"
267
+ fi
268
+ printf 'platform=%s,id=%s' "\$platform_name" "\$destination_id"
269
+ }
270
+
271
+ is_server_running() {
272
+ if [[ ! -f "\$PID_FILE" ]]; then
273
+ return 1
274
+ fi
275
+ local pid
276
+ pid="\$(cat "\$PID_FILE" 2>/dev/null || true)"
277
+ [[ -n "\$pid" ]] && kill -0 "\$pid" 2>/dev/null
278
+ }
279
+
280
+ run_oneshot() {
281
+ local request_file response_file oneshot_log destination_id
282
+ request_file="\$(mktemp "\$STATE_DIR/request.XXXXXX")"
283
+ response_file="\$(mktemp "\$STATE_DIR/response.XXXXXX")"
284
+ oneshot_log="\$(mktemp "\$STATE_DIR/xcodebuild.oneshot.XXXXXX.log")"
285
+ cp "\$STDIN_FILE" "\$request_file"
286
+ destination_id="\$(destination_spec)"
287
+
288
+ if ! xcodebuild test \\
289
+ "\${XCODEBUILD_ARGS[@]}" \\
290
+ -scheme "$SCHEME" \\
291
+ -configuration "$CONFIGURATION" \\
292
+ -destination "\$destination_id" \\
293
+ -only-testing:"$TEST_TARGET/ZMRShimUITestCase/testRunZMRCommand" \\
294
+ ZMR_SHIM_MODE="oneshot" \\
295
+ ZMR_SHIM_REQUEST_FILE="\$request_file" \\
296
+ ZMR_SHIM_RESPONSE_FILE="\$response_file" \\
297
+ ZMR_APP_BUNDLE_ID="$BUNDLE_ID" \\
298
+ >"\$oneshot_log" 2>&1; then
299
+ tail -120 "\$oneshot_log" >&2
300
+ exit 1
301
+ fi
302
+
303
+ cat "\$response_file"
304
+ printf '\\n'
305
+ }
306
+
307
+ wait_for_ready() {
308
+ local deadline
309
+ deadline=\$((SECONDS + \${ZMR_IOS_SHIM_START_TIMEOUT_SECONDS:-180}))
310
+ while (( SECONDS < deadline )); do
311
+ if [[ -f "\$READY_FILE" ]]; then
312
+ return 0
313
+ fi
314
+ if ! is_server_running; then
315
+ echo "iOS shim server exited before it became ready" >&2
316
+ tail_log
317
+ exit 1
318
+ fi
319
+ sleep 0.2
320
+ done
321
+ echo "timed out waiting for iOS shim server readiness" >&2
322
+ tail_log
323
+ exit 1
324
+ }
325
+
326
+ build_for_testing() {
327
+ if [[ "\${ZMR_IOS_SHIM_FORCE_REBUILD:-}" != "1" && -f "\$BUILD_READY_FILE" ]]; then
328
+ return 0
329
+ fi
330
+
331
+ local destination_id build_log
332
+ destination_id="\$(destination_spec)"
333
+ build_log="\$STATE_DIR/xcodebuild.build.log"
334
+
335
+ if ! xcodebuild build-for-testing \\
336
+ "\${XCODEBUILD_ARGS[@]}" \\
337
+ -scheme "$SCHEME" \\
338
+ -configuration "$CONFIGURATION" \\
339
+ -destination "\$destination_id" \\
340
+ ZMR_SHIM_MODE="server" \\
341
+ ZMR_SHIM_SERVER_DIR="\$SERVER_DIR" \\
342
+ ZMR_APP_BUNDLE_ID="$BUNDLE_ID" \\
343
+ >"\$build_log" 2>&1; then
344
+ tail -120 "\$build_log" >&2
345
+ exit 1
346
+ fi
347
+
348
+ touch "\$BUILD_READY_FILE"
349
+ }
350
+
351
+ start_server() {
352
+ if is_server_running; then
353
+ wait_for_ready
354
+ return 0
355
+ fi
356
+
357
+ rm -f "\$READY_FILE" "\$SERVER_DIR"/request-*.json "\$SERVER_DIR"/response-*.json "\$SERVER_DIR/stop"
358
+ : > "\$LOG_FILE"
359
+ build_for_testing
360
+
361
+ local destination_id
362
+ destination_id="\$(destination_spec)"
363
+ nohup xcodebuild test-without-building \\
364
+ "\${XCODEBUILD_ARGS[@]}" \\
365
+ -scheme "$SCHEME" \\
366
+ -configuration "$CONFIGURATION" \\
367
+ -destination "\$destination_id" \\
368
+ -only-testing:"$TEST_TARGET/ZMRShimUITestCase/testRunZMRCommand" \\
369
+ ZMR_SHIM_MODE="server" \\
370
+ ZMR_SHIM_SERVER_DIR="\$SERVER_DIR" \\
371
+ ZMR_APP_BUNDLE_ID="$BUNDLE_ID" \\
372
+ >"\$LOG_FILE" 2>&1 < /dev/null &
373
+
374
+ echo "\$!" > "\$PID_FILE"
375
+ wait_for_ready
376
+ }
377
+
378
+ send_request() {
379
+ start_server
380
+
381
+ local REQUEST_ID request_file response_file tmp_request deadline
382
+ REQUEST_ID="\$(date +%s%N)-\$\$"
383
+ request_file="\$SERVER_DIR/request-\$REQUEST_ID.json"
384
+ response_file="\$SERVER_DIR/response-\$REQUEST_ID.json"
385
+ tmp_request="\$request_file.tmp"
386
+ cp "\$STDIN_FILE" "\$tmp_request"
387
+ mv "\$tmp_request" "\$request_file"
388
+
389
+ deadline=\$((SECONDS + \${ZMR_IOS_SHIM_RESPONSE_TIMEOUT_SECONDS:-180}))
390
+ while (( SECONDS < deadline )); do
391
+ if [[ -f "\$response_file" ]]; then
392
+ cat "\$response_file"
393
+ printf '\\n'
394
+ rm -f "\$response_file"
395
+ return 0
396
+ fi
397
+ if ! is_server_running; then
398
+ echo "iOS shim server exited while waiting for response \$REQUEST_ID" >&2
399
+ tail_log
400
+ exit 1
401
+ fi
402
+ sleep 0.05
403
+ done
404
+
405
+ echo "timed out waiting for iOS shim response \$REQUEST_ID" >&2
406
+ tail_log
407
+ exit 1
408
+ }
409
+
410
+ if [[ "\${ZMR_IOS_SHIM_ONESHOT:-}" == "1" ]]; then
411
+ run_oneshot
412
+ else
413
+ send_request
414
+ fi
415
+ EOF
416
+
417
+ chmod +x "$APP_ROOT/.zmr/ios-shim"
418
+
419
+ shell_quote() {
420
+ printf '%q' "$1"
421
+ }
422
+
423
+ patch_zmr_config() {
424
+ local config_file="$APP_ROOT/.zmr/config.json"
425
+ ruby -rjson -e '
426
+ path = ARGV.fetch(0)
427
+ app_id = ARGV.fetch(1)
428
+ config = if File.exist?(path)
429
+ JSON.parse(File.read(path))
430
+ else
431
+ { "schemaVersion" => 1, "appId" => app_id }
432
+ end
433
+ abort "error: .zmr/config.json must be a JSON object" unless config.is_a?(Hash)
434
+ config["schemaVersion"] ||= 1
435
+ config["appId"] ||= app_id unless app_id.empty?
436
+ tools = config["tools"]
437
+ abort "error: .zmr/config.json tools must be a JSON object" if tools && !tools.is_a?(Hash)
438
+ tools ||= {}
439
+ tools["iosShimPath"] = "./.zmr/ios-shim"
440
+ config["tools"] = tools
441
+ File.write(path, "#{JSON.pretty_generate(config)}\n")
442
+ ' "$config_file" "$BUNDLE_ID"
443
+ }
444
+
445
+ patch_zmr_config
446
+
447
+ cat > "$APP_ROOT/.zmr/ensure-ios-shim-target.sh" <<EOF
448
+ #!/usr/bin/env bash
449
+ set -euo pipefail
450
+
451
+ APP_ROOT=$(shell_quote "$APP_ROOT")
452
+ PROJECT=$(shell_quote "$PROJECT")
453
+ WORKSPACE=$(shell_quote "$WORKSPACE")
454
+ APP_TARGET=$(shell_quote "$APP_TARGET")
455
+ TEST_TARGET=$(shell_quote "$TEST_TARGET")
456
+ SCHEME=$(shell_quote "$SCHEME")
457
+ BUNDLE_ID=$(shell_quote "$BUNDLE_ID")
458
+ TEST_BUNDLE_ID=$(shell_quote "$TEST_BUNDLE_ID")
459
+ DEPLOYMENT_TARGET=$(shell_quote "$DEPLOYMENT_TARGET")
460
+
461
+ cd "\$APP_ROOT"
462
+
463
+ if [[ -z "\$APP_TARGET" ]]; then
464
+ echo "error: rerun install-ios-shim.sh with --app-target to enable Xcode target patching" >&2
465
+ exit 2
466
+ fi
467
+
468
+ if [[ -z "\$PROJECT" ]]; then
469
+ if [[ -z "\$WORKSPACE" ]]; then
470
+ echo "error: rerun install-ios-shim.sh with --project or --workspace to enable Xcode target patching" >&2
471
+ exit 2
472
+ fi
473
+ PROJECT="\$(ruby -rrexml/document -rpathname -e '
474
+ app_root = Pathname.new(ARGV[0]).expand_path
475
+ workspace = app_root.join(ARGV[1]).expand_path
476
+ app_target = ARGV[2].to_s
477
+ bundle_id = ARGV[3].to_s
478
+ data = workspace.join("contents.xcworkspacedata")
479
+ abort "error: workspace metadata not found at #{data}" unless data.file?
480
+ doc = REXML::Document.new(data.read)
481
+ projects = []
482
+ doc.elements.each("//FileRef") do |element|
483
+ location = element.attributes["location"].to_s
484
+ next unless location.end_with?(".xcodeproj")
485
+ project_path = location.sub(/\A(?:group|container|self):/, "")
486
+ project = Pathname.new(project_path)
487
+ project = workspace.dirname.join(project).expand_path unless project.absolute?
488
+ projects << project
489
+ end
490
+ projects.uniq!
491
+ abort "error: workspace #{ARGV[1]} does not reference an .xcodeproj; rerun with --project" if projects.empty?
492
+ if projects.length > 1
493
+ begin
494
+ require "xcodeproj"
495
+ rescue LoadError
496
+ abort "error: workspace #{ARGV[1]} references multiple .xcodeproj files; install the xcodeproj gem or rerun with --project"
497
+ end
498
+ projects = projects.select do |project_path|
499
+ begin
500
+ Xcodeproj::Project.open(project_path.to_s).targets.any? { |target| target.name == app_target }
501
+ rescue StandardError
502
+ false
503
+ end
504
+ end
505
+ abort "error: workspace #{ARGV[1]} references multiple .xcodeproj files, and none contain target #{app_target}; rerun with --project" if projects.empty?
506
+ if projects.length > 1 && !bundle_id.empty?
507
+ bundle_matches = projects.select do |project_path|
508
+ begin
509
+ Xcodeproj::Project.open(project_path.to_s).targets.any? do |target|
510
+ target.name == app_target &&
511
+ target.build_configurations.any? do |configuration|
512
+ configuration.build_settings["PRODUCT_BUNDLE_IDENTIFIER"].to_s == bundle_id
513
+ end
514
+ end
515
+ rescue StandardError
516
+ false
517
+ end
518
+ end
519
+ projects = bundle_matches unless bundle_matches.empty?
520
+ end
521
+ abort "error: workspace #{ARGV[1]} references multiple .xcodeproj files containing target #{app_target}; bundle id #{bundle_id} did not disambiguate; rerun with --project" if projects.length > 1
522
+ end
523
+ puts projects.first.relative_path_from(app_root).to_s
524
+ ' "\$APP_ROOT" "\$WORKSPACE" "\$APP_TARGET" "\$BUNDLE_ID")"
525
+ fi
526
+
527
+ ruby "\$APP_ROOT/.zmr/ensure-ios-shim-target.rb" \\
528
+ --project "\$PROJECT" \\
529
+ --app-target "\$APP_TARGET" \\
530
+ --test-target "\$TEST_TARGET" \\
531
+ --scheme "\$SCHEME" \\
532
+ --bundle-id "\$BUNDLE_ID" \\
533
+ --test-bundle-id "\$TEST_BUNDLE_ID" \\
534
+ --deployment-target "\$DEPLOYMENT_TARGET" \\
535
+ --source ".zmr/ZMRShimUITestCase.swift" \\
536
+ --source ".zmr/shims/ios/ZMRShim.swift" \\
537
+ --info-plist ".zmr/ZMRShimUITests-Info.plist"
538
+ EOF
539
+
540
+ chmod +x "$APP_ROOT/.zmr/ensure-ios-shim-target.sh"
541
+
542
+ if [[ "$PATCH_XCODEPROJ" -eq 1 ]]; then
543
+ "$APP_ROOT/.zmr/ensure-ios-shim-target.sh"
544
+ fi
545
+
546
+ cat > "$APP_ROOT/.zmr/ios-shim.README.md" <<EOF
547
+ # ZMR iOS Shim
548
+
549
+ Generated for scheme \`$SCHEME\`, UI test target \`$TEST_TARGET\`, and bundle id \`$BUNDLE_ID\`.
550
+
551
+ When the app has an Xcode project or workspace, run:
552
+
553
+ \`\`\`bash
554
+ .zmr/ensure-ios-shim-target.sh
555
+ \`\`\`
556
+
557
+ The helper uses the Ruby \`xcodeproj\` gem to create/update the UI test target,
558
+ add the shim sources, set the test bundle Info.plist, and write a shared scheme.
559
+ For workspaces, it resolves the project automatically when there is one project,
560
+ or when exactly one project contains \`--app-target\`, or when \`--bundle-id\`
561
+ disambiguates matching app targets. Rerun the installer with \`--project\` for
562
+ still-ambiguous workspaces. Install the gem with
563
+ \`gem install xcodeproj\` or your app's Bundler setup.
564
+
565
+ If you do not want ZMR to patch the Xcode project, add these files to the app's
566
+ UI test target manually:
567
+
568
+ - \`.zmr/ZMRShimUITestCase.swift\`
569
+ - \`.zmr/shims/ios/ZMRShim.swift\`
570
+ - \`.zmr/ZMRShimUITests-Info.plist\`
571
+
572
+ Run ZMR with:
573
+
574
+ \`\`\`bash
575
+ zmr run .zmr/ios-smoke.json --platform ios --ios-shim ./.zmr/ios-shim
576
+ \`\`\`
577
+
578
+ The command caches \`build-for-testing\` output under \`.zmr/ios-shim-state/\`
579
+ and uses \`test-without-building\` for selector commands. Set
580
+ \`ZMR_IOS_SHIM_FORCE_REBUILD=1\` after app-side target changes, or
581
+ \`ZMR_IOS_SHIM_ONESHOT=1\` to force a cold XCTest run for debugging Xcode target
582
+ wiring.
583
+ EOF
584
+
585
+ echo "wrote $APP_ROOT/.zmr/ios-shim"
586
+ echo "wrote $APP_ROOT/.zmr/ZMRShimUITestCase.swift"
587
+ echo "wrote $APP_ROOT/.zmr/shims/ios/ZMRShim.swift"
588
+ echo "wrote $APP_ROOT/.zmr/ZMRShimUITests-Info.plist"
589
+ echo "wrote $APP_ROOT/.zmr/ensure-ios-shim-target.sh"