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,72 @@
1
+ export function packageScripts(config) {
2
+ return {
3
+ "zmr:doctor": config.scripts.doctor,
4
+ "zmr:schemas": config.scripts.schemas,
5
+ "zmr:validate": config.scripts.validate,
6
+ "zmr:android": config.scripts.android,
7
+ "zmr:android:report": config.scripts.androidReport,
8
+ "zmr:android:dev-client": config.scripts.androidDevClient,
9
+ "zmr:android:dev-client:report": config.scripts.androidDevClientReport,
10
+ "zmr:android:reliability": config.scripts.androidReliability,
11
+ "zmr:ios": config.scripts.ios,
12
+ "zmr:ios:report": config.scripts.iosReport,
13
+ "zmr:ios:dev-client": config.scripts.iosDevClient,
14
+ "zmr:ios:dev-client:report": config.scripts.iosDevClientReport,
15
+ "zmr:ios:reliability": config.scripts.iosReliability,
16
+ "zmr:matrix": config.scripts.matrix,
17
+ "zmr:pilot": config.scripts.pilotGate,
18
+ "zmr:readiness": config.scripts.readiness,
19
+ "zmr:serve": config.scripts.serve,
20
+ "zmr:mcp": config.scripts.mcp,
21
+ "zmr:explain": config.scripts.explain,
22
+ "zmr:export": config.scripts.exportTrace,
23
+ };
24
+ }
25
+
26
+ export function applyPackageScripts(pkg, config, { android = true, ios = true } = {}) {
27
+ pkg.scripts ??= {};
28
+ const scripts = packageScripts(config);
29
+ const setScript = (name, value) => {
30
+ if (value == null) delete pkg.scripts[name];
31
+ else pkg.scripts[name] = value;
32
+ };
33
+
34
+ setScript("zmr:doctor", scripts["zmr:doctor"]);
35
+ setScript("zmr:schemas", scripts["zmr:schemas"]);
36
+ setScript("zmr:validate", scripts["zmr:validate"]);
37
+ if (android) {
38
+ setScript("zmr:android", scripts["zmr:android"]);
39
+ setScript("zmr:android:report", scripts["zmr:android:report"]);
40
+ setScript("zmr:android:reliability", scripts["zmr:android:reliability"]);
41
+ setScript("zmr:android:dev-client", config.scripts.androidDevClient);
42
+ setScript("zmr:android:dev-client:report", config.scripts.androidDevClientReport);
43
+ } else {
44
+ setScript("zmr:android", null);
45
+ setScript("zmr:android:report", null);
46
+ setScript("zmr:android:reliability", null);
47
+ setScript("zmr:android:dev-client", null);
48
+ setScript("zmr:android:dev-client:report", null);
49
+ }
50
+ if (ios) {
51
+ setScript("zmr:ios", scripts["zmr:ios"]);
52
+ setScript("zmr:ios:report", scripts["zmr:ios:report"]);
53
+ setScript("zmr:ios:reliability", scripts["zmr:ios:reliability"]);
54
+ setScript("zmr:ios:dev-client", config.scripts.iosDevClient);
55
+ setScript("zmr:ios:dev-client:report", config.scripts.iosDevClientReport);
56
+ } else {
57
+ setScript("zmr:ios", null);
58
+ setScript("zmr:ios:report", null);
59
+ setScript("zmr:ios:reliability", null);
60
+ setScript("zmr:ios:dev-client", null);
61
+ setScript("zmr:ios:dev-client:report", null);
62
+ }
63
+ if (android || ios) setScript("zmr:matrix", scripts["zmr:matrix"]);
64
+ else setScript("zmr:matrix", null);
65
+ setScript("zmr:pilot", scripts["zmr:pilot"]);
66
+ setScript("zmr:readiness", config.scripts.readiness ? scripts["zmr:readiness"] : null);
67
+ setScript("zmr:serve", scripts["zmr:serve"]);
68
+ setScript("zmr:mcp", scripts["zmr:mcp"]);
69
+ setScript("zmr:explain", scripts["zmr:explain"]);
70
+ setScript("zmr:export", scripts["zmr:export"]);
71
+ return pkg;
72
+ }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { resolveBinary } from "./index.mjs";
4
+
5
+ if (resolveBinary()) process.exit(0);
6
+ if (process.env.ZMR_SKIP_POSTINSTALL_BUILD === "1") process.exit(0);
7
+
8
+ const hasZig = spawnSync("zig", ["version"], { stdio: "ignore" }).status === 0;
9
+ if (!hasZig) {
10
+ console.warn("zig-mobile-runner: no prebuilt zmr binary found and Zig is not installed.");
11
+ console.warn("zig-mobile-runner: install a release package with prebuilds, install Zig and run `npm run build:zmr`, or set ZMR_BIN.");
12
+ process.exit(0);
13
+ }
14
+
15
+ const result = spawnSync(process.execPath, [new URL("./build-zmr.mjs", import.meta.url).pathname], {
16
+ stdio: "inherit",
17
+ });
18
+
19
+ if (result.status !== 0) {
20
+ console.warn("zig-mobile-runner: postinstall build failed; set ZMR_BIN or run `npm run build:zmr` after fixing Zig.");
21
+ }
@@ -0,0 +1,179 @@
1
+ import {
2
+ agentInstructions,
3
+ } from "./agents.mjs";
4
+ import { appConfig } from "./app-config.mjs";
5
+ import {
6
+ shellQuote,
7
+ } from "./commands.mjs";
8
+ import {
9
+ deviceMatrix,
10
+ scenarioFiles,
11
+ } from "./scenarios.mjs";
12
+
13
+ export const appScaffoldFiles = [
14
+ "config.json",
15
+ "android-smoke.json",
16
+ "ios-smoke.json",
17
+ "device-matrix.json",
18
+ "AGENTS.md",
19
+ ];
20
+
21
+ export const appScriptNames = [
22
+ "doctor",
23
+ "schemas",
24
+ "validate",
25
+ "android",
26
+ "androidReport",
27
+ "androidDevClient",
28
+ "androidDevClientReport",
29
+ "androidReliability",
30
+ "ios",
31
+ "iosReport",
32
+ "iosDevClient",
33
+ "iosDevClientReport",
34
+ "iosReliability",
35
+ "matrix",
36
+ "pilotGate",
37
+ "readiness",
38
+ "serve",
39
+ "mcp",
40
+ "explain",
41
+ "exportTrace",
42
+ ];
43
+
44
+ export {
45
+ agentInstructions,
46
+ nextStepCommands,
47
+ } from "./agents.mjs";
48
+ export { appConfig } from "./app-config.mjs";
49
+ export {
50
+ devClientRunCommand,
51
+ devClientReportCommand,
52
+ matrixCommand,
53
+ pilotGateCommand,
54
+ readinessCommand,
55
+ reliabilityCommand,
56
+ shellJoin,
57
+ shellQuote,
58
+ smokeReportCommand,
59
+ smokeRunCommand,
60
+ validateCommand,
61
+ } from "./commands.mjs";
62
+ export {
63
+ devClientScenario,
64
+ deviceMatrix,
65
+ scenarioFiles,
66
+ smokeScenario,
67
+ } from "./scenarios.mjs";
68
+ export {
69
+ applyPackageScripts,
70
+ packageScripts,
71
+ } from "./package-scripts.mjs";
72
+ export {
73
+ ensureTraceIgnore,
74
+ writeJsonFile,
75
+ writePackageScripts,
76
+ writeScaffoldFiles,
77
+ writeTextFile,
78
+ } from "./generated-files.mjs";
79
+ export {
80
+ formatWizardCheckResult,
81
+ parseScaffoldArgs,
82
+ readOptionValue,
83
+ wizardChecks,
84
+ } from "./setup.mjs";
85
+
86
+ export function scaffoldPlan(appId, { android = true, ios = true, androidShim = "", iosShim = "", expoDevClientScheme = "", packageScripts = false } = {}) {
87
+ const config = appConfig(appId, { android, ios, androidShim, iosShim, expoDevClientScheme });
88
+ return {
89
+ config,
90
+ files: [
91
+ { kind: "json", path: "config.json", value: config, overwrite: true },
92
+ ...scenarioFiles(appId, { android, ios, expoDevClientScheme }).map((file) => ({
93
+ kind: "json",
94
+ path: file.path,
95
+ value: file.scenario,
96
+ overwrite: false,
97
+ })),
98
+ {
99
+ kind: "json",
100
+ path: "device-matrix.json",
101
+ value: deviceMatrix(appId, android, ios, androidShim, iosShim),
102
+ overwrite: true,
103
+ },
104
+ {
105
+ kind: "text",
106
+ path: "AGENTS.md",
107
+ value: agentInstructions(appId, { android, ios, packageScripts, scripts: config.scripts }),
108
+ overwrite: true,
109
+ },
110
+ ],
111
+ };
112
+ }
113
+
114
+ export function scaffoldFiles(appId, options = {}) {
115
+ return scaffoldPlan(appId, options).files;
116
+ }
117
+
118
+ export function appInitOutput(appRoot, appId, plan, { packageScripts = false } = {}) {
119
+ const filePath = (name) => pathJoin(appRoot, ".zmr", name);
120
+ const configPath = filePath("config.json");
121
+ const generatedPath = (name) => plan.files.some((file) => file.path === name) ? filePath(name) : undefined;
122
+ const androidScenarioPath = generatedPath("android-smoke.json");
123
+ const iosScenarioPath = generatedPath("ios-smoke.json");
124
+ const androidDevClientScenarioPath = generatedPath("android-dev-client-smoke.json");
125
+ const iosDevClientScenarioPath = generatedPath("ios-dev-client-open-link.json");
126
+ const scenarioPaths = plan.files
127
+ .filter((file) => file.kind === "json" && file.path !== "config.json" && file.path !== "device-matrix.json")
128
+ .map((file) => filePath(file.path));
129
+ const scriptNames = appScriptNames.filter((name) => plan.config.scripts[name] != null);
130
+ const doctorCommand = packageScripts ? "npm run zmr:doctor" : `zmr doctor --strict --json --config ${shellQuote(configPath)}`;
131
+ const schemaCommand = packageScripts ? "npm run zmr:schemas" : "zmr schemas --json";
132
+ const validateCommands = packageScripts
133
+ ? ["npm run zmr:validate"]
134
+ : scenarioPaths.map((scenarioPath) => `zmr validate --json ${shellQuote(scenarioPath)}`);
135
+ const smokeCommands = [];
136
+ if (packageScripts) {
137
+ if (androidScenarioPath) smokeCommands.push("npm run zmr:android");
138
+ if (iosScenarioPath) smokeCommands.push("npm run zmr:ios");
139
+ } else {
140
+ if (androidScenarioPath) {
141
+ smokeCommands.push(
142
+ `zmr run ${shellQuote(androidScenarioPath)} --device emulator-5554 --trace-dir ${shellQuote(pathJoin(appRoot, "traces", "zmr-android"))}`,
143
+ );
144
+ }
145
+ if (iosScenarioPath) {
146
+ smokeCommands.push(
147
+ `zmr run ${shellQuote(iosScenarioPath)} --platform ios --device booted --trace-dir ${shellQuote(pathJoin(appRoot, "traces", "zmr-ios"))}`,
148
+ );
149
+ }
150
+ }
151
+ const output = {
152
+ ok: true,
153
+ mode: "app",
154
+ dir: appRoot,
155
+ appId,
156
+ created: plan.files.map((file) => filePath(file.path)),
157
+ configPath,
158
+ deviceMatrixPath: filePath("device-matrix.json"),
159
+ agentInstructionsPath: filePath("AGENTS.md"),
160
+ next: doctorCommand,
161
+ nextCommands: [
162
+ doctorCommand,
163
+ schemaCommand,
164
+ ...validateCommands,
165
+ ],
166
+ smokeCommands,
167
+ scriptCount: scriptNames.length,
168
+ scriptNames,
169
+ };
170
+ if (androidScenarioPath) output.androidScenarioPath = androidScenarioPath;
171
+ if (iosScenarioPath) output.iosScenarioPath = iosScenarioPath;
172
+ if (androidDevClientScenarioPath) output.androidDevClientScenarioPath = androidDevClientScenarioPath;
173
+ if (iosDevClientScenarioPath) output.iosDevClientScenarioPath = iosDevClientScenarioPath;
174
+ return output;
175
+ }
176
+
177
+ function pathJoin(...parts) {
178
+ return parts.join("/").replace(/\/+/g, "/");
179
+ }
@@ -0,0 +1,93 @@
1
+ export function deviceMatrix(appId, android = true, ios = true, androidShim = "", iosShim = "") {
2
+ const devices = [];
3
+ if (android) {
4
+ const androidDevice = {
5
+ name: "android-emulator",
6
+ platform: "android",
7
+ serial: "emulator-5554",
8
+ scenario: ".zmr/android-smoke.json",
9
+ adb: "adb",
10
+ };
11
+ if (androidShim) androidDevice.androidShim = androidShim;
12
+ devices.push(androidDevice);
13
+ }
14
+ if (ios) {
15
+ const iosDevice = {
16
+ name: "ios-simulator",
17
+ platform: "ios",
18
+ iosDeviceType: "simulator",
19
+ serial: "booted",
20
+ scenario: ".zmr/ios-smoke.json",
21
+ xcrun: "xcrun",
22
+ };
23
+ if (iosShim) iosDevice.iosShim = iosShim;
24
+ devices.push(iosDevice);
25
+ }
26
+ return {
27
+ runs: 1,
28
+ appId,
29
+ devices,
30
+ };
31
+ }
32
+
33
+ export function smokeScenario(name, appId) {
34
+ return {
35
+ name,
36
+ appId,
37
+ steps: [
38
+ { action: "launch" },
39
+ { action: "assertHealthy" },
40
+ { action: "snapshot" },
41
+ ],
42
+ };
43
+ }
44
+
45
+ export function scenarioFiles(appId, { android = true, ios = true, expoDevClientScheme = "" } = {}) {
46
+ const files = [];
47
+ if (android) files.push({ path: "android-smoke.json", scenario: smokeScenario("Android smoke", appId) });
48
+ if (ios) files.push({ path: "ios-smoke.json", scenario: smokeScenario("iOS smoke", appId) });
49
+ if (expoDevClientScheme) {
50
+ if (android) {
51
+ files.push({
52
+ path: "android-dev-client-smoke.json",
53
+ scenario: devClientScenario("Android Expo dev-client smoke", appId, expoDevClientScheme, "http://10.0.2.2:8081"),
54
+ });
55
+ }
56
+ if (ios) {
57
+ files.push({
58
+ path: "ios-dev-client-open-link.json",
59
+ scenario: devClientScenario("iOS Expo dev-client open-link smoke", appId, expoDevClientScheme, "http://127.0.0.1:8081"),
60
+ });
61
+ }
62
+ }
63
+ return files;
64
+ }
65
+
66
+ export function devClientScenario(name, appId, scheme, metroUrl) {
67
+ return {
68
+ name,
69
+ appId,
70
+ steps: [
71
+ { action: "stop" },
72
+ {
73
+ action: "openLink",
74
+ url: `exp+${scheme}://expo-development-client/?url=${encodeURIComponent(metroUrl)}`,
75
+ },
76
+ {
77
+ action: "waitAny",
78
+ selectors: [
79
+ { textContains: "Downloading" },
80
+ { textContains: "Connected to:" },
81
+ { textContains: "Reload" },
82
+ { textContains: "Continue" },
83
+ { textContains: "Sign in" },
84
+ { textContains: "Home" },
85
+ { textContains: "Unable to load" },
86
+ ],
87
+ timeoutMs: 120000,
88
+ },
89
+ { action: "assertHealthy" },
90
+ { action: "snapshot" },
91
+ ],
92
+ };
93
+ }
package/npm/setup.mjs ADDED
@@ -0,0 +1,69 @@
1
+ export function readOptionValue(argv, index, flag) {
2
+ const value = argv[index];
3
+ if (!value || value.startsWith("--")) {
4
+ throw new Error(`${flag} requires a value`);
5
+ }
6
+ return value;
7
+ }
8
+
9
+ export function parseScaffoldArgs(args, { packageJson = false, wizard = false } = {}) {
10
+ const parsed = {
11
+ dir: process.cwd(),
12
+ appId: "com.example.mobiletest",
13
+ android: false,
14
+ androidShim: "",
15
+ ios: false,
16
+ iosShim: "",
17
+ expoDevClientScheme: "",
18
+ };
19
+ if (packageJson || wizard) {
20
+ parsed.packageJson = false;
21
+ }
22
+ if (wizard) {
23
+ parsed.yes = false;
24
+ }
25
+ parsed.json = false;
26
+
27
+ for (let i = 0; i < args.length; i += 1) {
28
+ const arg = args[i];
29
+ if (arg === "--help" || arg === "-h") return { help: true };
30
+ if (arg === "--dir") parsed.dir = readOptionValue(args, ++i, arg);
31
+ else if (arg === "--app-id") parsed.appId = readOptionValue(args, ++i, arg);
32
+ else if (arg === "--android") parsed.android = true;
33
+ else if (arg === "--android-shim") parsed.androidShim = readOptionValue(args, ++i, arg);
34
+ else if (arg === "--ios") parsed.ios = true;
35
+ else if (arg === "--ios-shim") parsed.iosShim = readOptionValue(args, ++i, arg);
36
+ else if (arg === "--expo-dev-client-scheme") parsed.expoDevClientScheme = readOptionValue(args, ++i, arg);
37
+ else if (arg === "--json") parsed.json = true;
38
+ else if ((packageJson || wizard) && arg === "--package-json") parsed.packageJson = true;
39
+ else if (wizard && (arg === "--yes" || arg === "-y")) parsed.yes = true;
40
+ else throw new Error(`unknown argument: ${arg}`);
41
+ }
42
+
43
+ if (!parsed.android && !parsed.ios) {
44
+ parsed.android = true;
45
+ parsed.ios = true;
46
+ }
47
+ return parsed;
48
+ }
49
+
50
+ export function wizardChecks({ android = true, ios = true, nodePath = process.execPath, zmrPath = "zmr" } = {}) {
51
+ const checks = [
52
+ { label: "node", command: nodePath, args: ["--version"], required: true },
53
+ { label: "zmr", command: zmrPath || "zmr", args: ["version"], required: true },
54
+ ];
55
+ if (android) checks.push({ label: "adb", command: "adb", args: ["version"], required: false });
56
+ if (ios) checks.push({ label: "xcrun", command: "xcrun", args: ["--version"], required: false });
57
+ checks.push({ label: "zig", command: "zig", args: ["version"], required: false });
58
+ return checks;
59
+ }
60
+
61
+ export function formatWizardCheckResult(label, result, { required = false } = {}) {
62
+ if (result.status === 0) {
63
+ const firstLine = (result.stdout || result.stderr || "").split(/\r?\n/).find(Boolean) ?? "ok";
64
+ return `${label}\tok\t${firstLine}`;
65
+ }
66
+ const status = required ? "missing" : "warning";
67
+ const detail = result.error?.message ?? `exit ${result.status ?? "unknown"}`;
68
+ return `${label}\t${status}\t${detail}`;
69
+ }
package/npm/wizard.mjs ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import path from "node:path";
4
+ import readline from "node:readline/promises";
5
+ import { stdin as input, stdout as output } from "node:process";
6
+ import { resolveBinary } from "./index.mjs";
7
+ import {
8
+ appInitOutput,
9
+ ensureTraceIgnore,
10
+ formatWizardCheckResult,
11
+ nextStepCommands,
12
+ parseScaffoldArgs,
13
+ scaffoldPlan,
14
+ writePackageScripts,
15
+ writeScaffoldFiles,
16
+ wizardChecks,
17
+ } from "./scaffold.mjs";
18
+
19
+ const options = parseArgs(process.argv.slice(2));
20
+
21
+ if (!options.json) {
22
+ console.log("ZMR setup wizard");
23
+ console.log("================");
24
+ }
25
+
26
+ if (!options.yes && !options.json) {
27
+ await promptForMissingOptions(options);
28
+ }
29
+
30
+ const appRoot = path.resolve(options.dir);
31
+
32
+ if (!options.json) {
33
+ console.log("");
34
+ console.log("Checking necessities");
35
+ for (const checkSpec of wizardChecks({
36
+ android: options.android,
37
+ ios: options.ios,
38
+ nodePath: process.execPath,
39
+ zmrPath: resolveBinary() ?? "zmr",
40
+ })) {
41
+ check(checkSpec.label, checkSpec.command, checkSpec.args, { required: checkSpec.required });
42
+ }
43
+ }
44
+
45
+ const plan = scaffoldPlan(options.appId, {
46
+ android: options.android,
47
+ ios: options.ios,
48
+ androidShim: options.androidShim,
49
+ iosShim: options.iosShim,
50
+ expoDevClientScheme: options.expoDevClientScheme,
51
+ packageScripts: options.packageJson,
52
+ });
53
+ const { config, files } = plan;
54
+ for (const result of writeScaffoldFiles(path.join(appRoot, ".zmr"), files, { cwd: appRoot })) {
55
+ if (!options.json) console.log(`${result.status} ${result.path}`);
56
+ }
57
+ const ignoredPath = ensureTraceIgnore(appRoot, { cwd: appRoot });
58
+ if (ignoredPath && !options.json) console.log(`updated ${ignoredPath}`);
59
+ if (options.packageJson) patchPackageJson(appRoot, options.android, options.ios);
60
+
61
+ if (options.json) {
62
+ process.stdout.write(`${JSON.stringify(appInitOutput(appRoot, options.appId, plan, { packageScripts: options.packageJson }))}\n`);
63
+ } else {
64
+ console.log("");
65
+ console.log("Next steps");
66
+ printNextSteps(config.scripts, options);
67
+ }
68
+
69
+ function parseArgs(args) {
70
+ try {
71
+ const parsed = parseScaffoldArgs(args, { wizard: true });
72
+ if (parsed.help) {
73
+ usage();
74
+ process.exit(0);
75
+ }
76
+ return parsed;
77
+ } catch (error) {
78
+ console.error(error.message);
79
+ usage();
80
+ process.exit(2);
81
+ }
82
+ }
83
+
84
+ async function promptForMissingOptions(parsed) {
85
+ const rl = readline.createInterface({ input, output });
86
+ try {
87
+ parsed.appId = (await rl.question(`App id [${parsed.appId}]: `)).trim() || parsed.appId;
88
+ const patch = (await rl.question("Patch package.json scripts? [Y/n]: ")).trim().toLowerCase();
89
+ parsed.packageJson = patch === "" || patch === "y" || patch === "yes";
90
+ } finally {
91
+ rl.close();
92
+ }
93
+ }
94
+
95
+ function usage() {
96
+ console.log("Usage: zmr-wizard [--dir <app-root>] [--app-id <id>] [--android] [--android-shim <path>] [--ios] [--ios-shim <path>] [--expo-dev-client-scheme <scheme>] [--package-json] [--yes] [--json]");
97
+ }
98
+
99
+ function printNextSteps(scripts, parsed) {
100
+ for (const step of nextStepCommands({ scripts }, {
101
+ android: parsed.android,
102
+ ios: parsed.ios,
103
+ packageScripts: parsed.packageJson,
104
+ })) {
105
+ console.log(` ${step.command}`);
106
+ }
107
+ }
108
+
109
+ function check(label, command, args, opts = {}) {
110
+ const result = spawnSync(command, args, { encoding: "utf8" });
111
+ console.log(` ${formatWizardCheckResult(label, result, opts)}`);
112
+ }
113
+
114
+ function patchPackageJson(root, android, ios) {
115
+ const result = writePackageScripts(root, config, { android, ios, cwd: root });
116
+ if (result && !options.json) console.log(`${result.status} ${result.path}`);
117
+ }
package/npm/zmr.mjs ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { spawnZmr, missingBinaryMessage } from "./index.mjs";
3
+
4
+ let child;
5
+ try {
6
+ child = spawnZmr(process.argv.slice(2), { stdio: "inherit" });
7
+ } catch (error) {
8
+ console.error(error?.message || missingBinaryMessage());
9
+ process.exit(127);
10
+ }
11
+
12
+ child.on("error", (error) => {
13
+ console.error(error?.message || String(error));
14
+ process.exit(127);
15
+ });
16
+
17
+ child.on("exit", (code, signal) => {
18
+ if (signal) {
19
+ process.kill(process.pid, signal);
20
+ return;
21
+ }
22
+ process.exit(code ?? 1);
23
+ });
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "zig-mobile-runner",
3
+ "version": "0.1.0",
4
+ "description": "Agent-native mobile app test runner powered by Zig.",
5
+ "main": "npm/index.mjs",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/johnmikel/zig-mobile-runner.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/johnmikel/zig-mobile-runner/issues"
12
+ },
13
+ "homepage": "https://github.com/johnmikel/zig-mobile-runner#readme",
14
+ "bin": {
15
+ "zmr": "npm/zmr.mjs",
16
+ "zmr-init": "npm/init-app.mjs",
17
+ "zmr-wizard": "npm/wizard.mjs",
18
+ "zmr-benchmark": "scripts/benchmark.sh",
19
+ "zmr-benchmark-command": "scripts/benchmark-command.sh",
20
+ "zmr-compare-benchmarks": "scripts/compare-benchmarks.py",
21
+ "zmr-device-matrix": "scripts/device-matrix.sh",
22
+ "zmr-pilot-gate": "scripts/pilot-gate.sh",
23
+ "zmr-assert-ios-physical-ready": "scripts/assert-ios-physical-ready.sh",
24
+ "zmr-release-readiness": "scripts/release-readiness.sh",
25
+ "zmr-install-android-shim": "scripts/install-android-shim.sh",
26
+ "zmr-install-ios-shim": "scripts/install-ios-shim.sh",
27
+ "zmr-create-android-demo-app": "scripts/create-android-demo-app.sh",
28
+ "zmr-create-ios-demo-app": "scripts/create-ios-demo-app.sh",
29
+ "zmr-demo-android": "scripts/demo-android-real.sh",
30
+ "zmr-demo-ios": "scripts/demo-ios-real.sh"
31
+ },
32
+ "files": [
33
+ "npm/",
34
+ "clients/README.md",
35
+ "go.work",
36
+ "clients/typescript/",
37
+ "clients/python/README.md",
38
+ "clients/python/pyproject.toml",
39
+ "clients/python/zmr_client.py",
40
+ "clients/python/examples/",
41
+ "clients/go/",
42
+ "!clients/go/**/*_test.go",
43
+ "clients/rust/Cargo.toml",
44
+ "clients/rust/Cargo.lock",
45
+ "clients/rust/README.md",
46
+ "clients/rust/src/",
47
+ "clients/rust/examples/",
48
+ "clients/swift/Package.swift",
49
+ "clients/swift/README.md",
50
+ "clients/swift/Sources/",
51
+ "clients/kotlin/build.gradle.kts",
52
+ "clients/kotlin/settings.gradle.kts",
53
+ "clients/kotlin/README.md",
54
+ "clients/kotlin/src/",
55
+ "!clients/kotlin/src/test/",
56
+ "prebuilds/",
57
+ "src/",
58
+ "!src/*_tests.zig",
59
+ "!src/test_harness.zig",
60
+ "examples/",
61
+ "schemas/",
62
+ "skills/",
63
+ "shims/",
64
+ "viewer/",
65
+ "docs/",
66
+ "scripts/",
67
+ "!scripts/build-npm-package.sh",
68
+ "!scripts/build-release.sh",
69
+ "!scripts/ci-gate.sh",
70
+ "!scripts/coverage.sh",
71
+ "!scripts/generate-homebrew-formula.mjs",
72
+ "!scripts/generate-release-manifest.mjs",
73
+ "!scripts/generate-release-metadata.mjs",
74
+ "!scripts/notarize-macos-release.sh",
75
+ "!scripts/release-candidate.sh",
76
+ "!scripts/release-gate.sh",
77
+ "!scripts/release-smoke.sh",
78
+ "!scripts/sign-macos-release.sh",
79
+ "!scripts/verify-release-artifacts.sh",
80
+ "!scripts/__pycache__/",
81
+ "!scripts/**/*.pyc",
82
+ "!scripts/_cod[e]x_write_test.txt",
83
+ "!scripts/python_redirect_test.txt",
84
+ "build.zig",
85
+ "build.zig.zon",
86
+ "README.md",
87
+ "FEATURES.md",
88
+ "LICENSE",
89
+ "SECURITY.md",
90
+ "CONTRIBUTING.md",
91
+ "CHANGELOG.md"
92
+ ],
93
+ "scripts": {
94
+ "postinstall": "node npm/postinstall.mjs",
95
+ "build:zmr": "node npm/build-zmr.mjs",
96
+ "pack:npm": "bash scripts/build-npm-package.sh",
97
+ "zmr:demo": "node npm/zmr.mjs validate examples/demo-fake.json",
98
+ "test": "node --test tests/viewer-parser.test.mjs tests/npm-scaffold-helpers.test.mjs tests/npm-cli.test.mjs tests/npm-package.test.mjs tests/typescript-client.test.mjs && python3 -W error -m unittest tests/python_client_test.py && bash tests/go-client-test.sh && bash tests/rust-client-test.sh"
99
+ },
100
+ "keywords": [
101
+ "mobile-testing",
102
+ "android",
103
+ "ios",
104
+ "adb",
105
+ "simctl",
106
+ "ai-agents",
107
+ "test-runner",
108
+ "zig"
109
+ ],
110
+ "license": "MIT",
111
+ "engines": {
112
+ "node": ">=18"
113
+ }
114
+ }
Binary file