zeno-mobile-runner 0.2.1 → 0.2.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 (74) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/FEATURES.md +1 -1
  3. package/README.md +1 -1
  4. package/build.zig.zon +2 -2
  5. package/clients/kotlin/README.md +1 -1
  6. package/clients/kotlin/build.gradle.kts +1 -1
  7. package/clients/python/pyproject.toml +1 -1
  8. package/clients/rust/Cargo.lock +1 -1
  9. package/clients/rust/Cargo.toml +1 -1
  10. package/clients/typescript/package.json +1 -1
  11. package/docs/protocol-fixtures/core-session.responses.jsonl +1 -1
  12. package/docs/protocol.md +10 -10
  13. package/package.json +3 -1
  14. package/prebuilds/darwin-arm64/zmr +0 -0
  15. package/prebuilds/darwin-x64/zmr +0 -0
  16. package/prebuilds/linux-arm64/zmr +0 -0
  17. package/prebuilds/linux-x64/zmr +0 -0
  18. package/scripts/create-react-native-expo-demo-app.sh +11 -13
  19. package/shims/ios/ZMRShim.swift +40 -12
  20. package/shims/ios/ZMRShimUITestCase.swift +135 -15
  21. package/src/android.zig +10 -9
  22. package/src/android_emulator.zig +22 -11
  23. package/src/android_screen_recording.zig +11 -7
  24. package/src/bundle.zig +10 -9
  25. package/src/bundle_redaction.zig +29 -28
  26. package/src/bundle_tar.zig +15 -12
  27. package/src/cli_devices.zig +7 -3
  28. package/src/cli_discover.zig +7 -3
  29. package/src/cli_doctor.zig +7 -3
  30. package/src/cli_draft.zig +51 -47
  31. package/src/cli_explore.zig +7 -3
  32. package/src/cli_import.zig +8 -4
  33. package/src/cli_info.zig +13 -6
  34. package/src/cli_init.zig +9 -5
  35. package/src/cli_inspect.zig +8 -4
  36. package/src/cli_run.zig +22 -16
  37. package/src/cli_serve.zig +3 -3
  38. package/src/cli_trace.zig +25 -12
  39. package/src/cli_validate.zig +8 -4
  40. package/src/command.zig +81 -99
  41. package/src/config.zig +2 -1
  42. package/src/config_diagnostics.zig +2 -1
  43. package/src/config_paths.zig +2 -1
  44. package/src/doctor.zig +8 -7
  45. package/src/doctor_hints.zig +1 -1
  46. package/src/errors.zig +5 -5
  47. package/src/importer.zig +8 -7
  48. package/src/ios.zig +19 -18
  49. package/src/ios_devices.zig +6 -5
  50. package/src/ios_lifecycle.zig +4 -4
  51. package/src/json_rpc.zig +39 -40
  52. package/src/json_rpc_methods.zig +8 -8
  53. package/src/json_rpc_observation.zig +9 -8
  54. package/src/json_rpc_params.zig +1 -1
  55. package/src/json_rpc_trace.zig +22 -21
  56. package/src/main.zig +19 -10
  57. package/src/mcp.zig +28 -19
  58. package/src/mcp_trace.zig +30 -29
  59. package/src/report.zig +39 -36
  60. package/src/report_html.zig +5 -4
  61. package/src/runner.zig +2 -1
  62. package/src/runner_actions.zig +20 -17
  63. package/src/runner_diagnostics.zig +4 -4
  64. package/src/runner_events.zig +55 -51
  65. package/src/runner_native.zig +21 -19
  66. package/src/runner_waits.zig +46 -41
  67. package/src/scaffold.zig +25 -24
  68. package/src/scenario.zig +4 -3
  69. package/src/stdio.zig +129 -0
  70. package/src/trace.zig +34 -26
  71. package/src/trace_summary.zig +3 -2
  72. package/src/trace_summary_diagnostic.zig +15 -13
  73. package/src/validation.zig +5 -4
  74. package/src/version.zig +1 -1
package/src/ios.zig CHANGED
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const command = @import("command.zig");
3
4
  const ios_devices = @import("ios_devices.zig");
4
5
  const ios_lifecycle = @import("ios_lifecycle.zig");
@@ -190,11 +191,11 @@ pub const IosDevice = struct {
190
191
  .duration_ms = @as(u32, @intCast(@min(timeout_ms, std.math.maxInt(u32)))),
191
192
  });
192
193
  }
193
- std.Thread.sleep(timeout_ms * std.time.ns_per_ms);
194
+ stdio.sleepNs(timeout_ms * std.time.ns_per_ms);
194
195
  }
195
196
 
196
197
  pub fn snapshot(self: *IosDevice, writer: ?*trace.TraceWriter) !types.ObservationSnapshot {
197
- const id = if (writer) |tw| try tw.nextSnapshotId() else try std.fmt.allocPrint(self.allocator, "snapshot-{d}", .{std.time.milliTimestamp()});
198
+ const id = if (writer) |tw| try tw.nextSnapshotId() else try std.fmt.allocPrint(self.allocator, "snapshot-{d}", .{stdio.nowMs()});
198
199
  errdefer self.allocator.free(id);
199
200
 
200
201
  var screenshot_artifact: ?[]const u8 = null;
@@ -239,7 +240,7 @@ pub const IosDevice = struct {
239
240
 
240
241
  return .{
241
242
  .id = id,
242
- .timestamp_ms = std.time.milliTimestamp(),
243
+ .timestamp_ms = stdio.nowMs(),
243
244
  .viewport = viewport,
244
245
  .active_package = active_package,
245
246
  .active_activity = null,
@@ -257,21 +258,21 @@ pub const IosDevice = struct {
257
258
  defer self.allocator.free(response);
258
259
  return try ios_shim.parseScreenshotPng(self.allocator, response);
259
260
  }
260
- const path = try std.fmt.allocPrint(self.allocator, "/tmp/zmr-ios-screenshot-{d}.png", .{std.time.nanoTimestamp()});
261
+ const path = try std.fmt.allocPrint(self.allocator, "/tmp/zmr-ios-screenshot-{d}.png", .{stdio.nowNs()});
261
262
  defer self.allocator.free(path);
262
- defer std.fs.cwd().deleteFile(path) catch {};
263
+ defer std.Io.Dir.cwd().deleteFile(stdio.io(), path) catch {};
263
264
 
264
265
  const result = try self.runSimctl(&.{ "io", self.target(), "screenshot", path }, default_max_output);
265
266
  defer result.deinit(self.allocator);
266
267
  try result.ensureSuccess();
267
- return try std.fs.cwd().readFileAlloc(self.allocator, path, default_max_output);
268
+ return try stdio.readFileAlloc(self.allocator, path, default_max_output);
268
269
  }
269
270
 
270
271
  fn logDelta(self: *IosDevice) !?[]const u8 {
271
272
  if (self.target_kind == .physical) return null;
272
273
  const result = try self.runSimctl(&.{ "spawn", self.target(), "log", "show", "--style", "compact", "--last", "30s" }, 1024 * 1024);
273
274
  defer result.deinit(self.allocator);
274
- if (result.term != .Exited or result.term.Exited != 0) return null;
275
+ if (result.term != .exited or result.term.exited != 0) return null;
275
276
  return try self.allocator.dupe(u8, result.stdout);
276
277
  }
277
278
 
@@ -282,15 +283,15 @@ pub const IosDevice = struct {
282
283
  }
283
284
 
284
285
  fn recordSnapshotSemanticFailure(self: *IosDevice, writer: *trace.TraceWriter, screenshot_artifact: []const u8, err: anyerror) !void {
285
- var payload = std.ArrayList(u8).empty;
286
- defer payload.deinit(writer.allocator);
287
- const out = payload.writer(writer.allocator);
286
+ var payload: std.Io.Writer.Allocating = .init(writer.allocator);
287
+ defer payload.deinit();
288
+ const out = &payload.writer;
288
289
  try out.writeAll("{\"status\":\"failed\",\"artifactStatus\":\"captured\",\"semanticStatus\":\"failed\",\"error\":");
289
290
  try trace.writeJsonString(out, @errorName(err));
290
291
  try out.writeAll(",\"screenshotArtifact\":");
291
292
  try trace.writeJsonString(out, screenshot_artifact);
292
293
  try out.writeAll(",\"source\":\"ios-xctest-shim\"}");
293
- try writer.recordEvent("observe.snapshot.semanticExtraction", payload.items);
294
+ try writer.recordEvent("observe.snapshot.semanticExtraction", out.buffered());
294
295
  _ = self;
295
296
  }
296
297
 
@@ -340,19 +341,19 @@ pub const IosDevice = struct {
340
341
  fn runShimWithTimeout(self: *IosDevice, shim_command: ios_shim.Command, timeout_ms: u64) ![]u8 {
341
342
  const path = self.shim_path orelse return error.IosXCTestShimRequired;
342
343
 
343
- var input = std.ArrayList(u8).empty;
344
- defer input.deinit(self.allocator);
345
- try ios_shim.writeCommandJson(input.writer(self.allocator), shim_command);
344
+ var input: std.Io.Writer.Allocating = .init(self.allocator);
345
+ defer input.deinit();
346
+ try ios_shim.writeCommandJson(&input.writer, shim_command);
346
347
 
347
348
  var attempt: usize = 0;
348
349
  while (attempt < shim_command_attempts) {
349
350
  attempt += 1;
350
- const result = try command.runWithInputTimeout(self.allocator, &.{path}, input.items, 4 * 1024 * 1024, timeout_ms);
351
+ const result = try command.runWithInputTimeout(self.allocator, &.{path}, input.writer.buffered(), 4 * 1024 * 1024, timeout_ms);
351
352
  defer result.deinit(self.allocator);
352
353
 
353
354
  result.ensureSuccess() catch |err| {
354
355
  if (attempt < shim_command_attempts and err == error.CommandFailed and isTransientShimBootstrapFailure(result)) {
355
- std.Thread.sleep(shim_bootstrap_retry_delay_ms * std.time.ns_per_ms);
356
+ stdio.sleepNs(shim_bootstrap_retry_delay_ms * std.time.ns_per_ms);
356
357
  continue;
357
358
  }
358
359
  return err;
@@ -391,7 +392,7 @@ pub const IosDevice = struct {
391
392
  fn isTransientShimBootstrapFailure(result: command.ExecResult) bool {
392
393
  if (result.timed_out) return false;
393
394
  switch (result.term) {
394
- .Exited => |code| if (code == 0) return false,
395
+ .exited => |code| if (code == 0) return false,
395
396
  else => return false,
396
397
  }
397
398
  return std.mem.indexOf(u8, result.stderr, "iOS shim server exited before it became ready") != null or
@@ -400,7 +401,7 @@ fn isTransientShimBootstrapFailure(result: command.ExecResult) bool {
400
401
  }
401
402
 
402
403
  fn shimTimeoutMs() u64 {
403
- return parseShimTimeoutMs(std.posix.getenv(shim_timeout_env));
404
+ return parseShimTimeoutMs(stdio.getenv(shim_timeout_env));
404
405
  }
405
406
 
406
407
  fn parseShimTimeoutMs(raw: ?[]const u8) u64 {
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const command = @import("command.zig");
3
4
  const types = @import("types.zig");
4
5
 
@@ -39,7 +40,7 @@ pub fn runSimctlCommand(
39
40
  }
40
41
  result.deinit(allocator);
41
42
  attempt += 1;
42
- std.Thread.sleep(simctl_retry_delay_ms * std.time.ns_per_ms);
43
+ stdio.sleepNs(simctl_retry_delay_ms * std.time.ns_per_ms);
43
44
  }
44
45
  }
45
46
 
@@ -62,9 +63,9 @@ pub fn runDevicectlJsonCommand(
62
63
  xcrun_path: []const u8,
63
64
  extra: []const []const u8,
64
65
  ) ![]u8 {
65
- const path = try std.fmt.allocPrint(allocator, "/tmp/zmr-devicectl-{d}.json", .{std.time.nanoTimestamp()});
66
+ const path = try std.fmt.allocPrint(allocator, "/tmp/zmr-devicectl-{d}.json", .{stdio.nowNs()});
66
67
  defer allocator.free(path);
67
- defer std.fs.cwd().deleteFile(path) catch {};
68
+ defer std.Io.Dir.deleteFileAbsolute(stdio.io(), path) catch {};
68
69
 
69
70
  var argv = std.ArrayList([]const u8).empty;
70
71
  defer argv.deinit(allocator);
@@ -76,7 +77,7 @@ pub fn runDevicectlJsonCommand(
76
77
  const result = try command.run(allocator, argv.items, default_max_output);
77
78
  defer result.deinit(allocator);
78
79
  try result.ensureSuccess();
79
- return try std.fs.cwd().readFileAlloc(allocator, path, default_max_output);
80
+ return try std.Io.Dir.cwd().readFileAlloc(stdio.io(), path, allocator, .limited(default_max_output));
80
81
  }
81
82
 
82
83
  pub fn parseSimulatorsJson(allocator: std.mem.Allocator, content: []const u8) ![]types.DeviceInfo {
@@ -158,7 +159,7 @@ pub fn findPidForBundleId(allocator: std.mem.Allocator, content: []const u8, app
158
159
  fn isRetriableSimctlFailure(result: command.ExecResult) bool {
159
160
  if (result.timed_out) return false;
160
161
  switch (result.term) {
161
- .Exited => |code| if (code == 0) return false,
162
+ .exited => |code| if (code == 0) return false,
162
163
  else => return false,
163
164
  }
164
165
  return std.mem.indexOf(u8, result.stderr, "CoreSimulatorService connection became invalid") != null or
@@ -65,7 +65,7 @@ pub fn uninstallPhysicalBestEffort(
65
65
 
66
66
  pub fn isMissingInstalledApp(result: command.ExecResult) bool {
67
67
  switch (result.term) {
68
- .Exited => |code| if (code == 0) return false,
68
+ .exited => |code| if (code == 0) return false,
69
69
  else => return false,
70
70
  }
71
71
  return std.mem.indexOf(u8, result.stderr, "No installed application with bundle identifier") != null;
@@ -73,7 +73,7 @@ pub fn isMissingInstalledApp(result: command.ExecResult) bool {
73
73
 
74
74
  pub fn isAppNotRunning(result: command.ExecResult) bool {
75
75
  switch (result.term) {
76
- .Exited => |code| if (code == 0) return false,
76
+ .exited => |code| if (code == 0) return false,
77
77
  else => return false,
78
78
  }
79
79
  return std.mem.indexOf(u8, result.stderr, "found nothing to terminate") != null;
@@ -89,7 +89,7 @@ test "simctl terminate missing running app is best-effort" {
89
89
  try std.testing.expect(isAppNotRunning(.{
90
90
  .stdout = stdout,
91
91
  .stderr = stderr,
92
- .term = .{ .Exited = 3 },
92
+ .term = .{ .exited = 3 },
93
93
  }));
94
94
  }
95
95
 
@@ -103,6 +103,6 @@ test "simctl terminate success is not classified as already stopped" {
103
103
  try std.testing.expect(!isAppNotRunning(.{
104
104
  .stdout = stdout,
105
105
  .stderr = stderr,
106
- .term = .{ .Exited = 0 },
106
+ .term = .{ .exited = 0 },
107
107
  }));
108
108
  }
package/src/json_rpc.zig CHANGED
@@ -1,9 +1,12 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const errors = @import("errors.zig");
3
4
  const methods = @import("json_rpc_methods.zig");
4
5
  const protocol = @import("json_rpc_protocol.zig");
5
6
  const trace = @import("trace.zig");
6
7
 
8
+ const net = std.Io.net;
9
+
7
10
  pub const ServeOptions = struct {
8
11
  transport: []const u8 = "stdio",
9
12
  };
@@ -13,12 +16,19 @@ pub fn serveStdio(allocator: std.mem.Allocator, device: anytype) !void {
13
16
  }
14
17
 
15
18
  pub fn serveStdioWithTrace(allocator: std.mem.Allocator, device: anytype, live_trace: ?*trace.TraceWriter) !void {
16
- var stdin = std.fs.File.stdin().deprecatedReader();
17
- const stdout = std.fs.File.stdout().deprecatedWriter();
19
+ var stdin_io: stdio.Input = .{};
20
+ stdin_io.init(.stdin());
21
+ const stdin = stdin_io.reader();
22
+
23
+ var stdout_io: stdio.Output = .{};
24
+ stdout_io.init(.stdout());
25
+ defer stdout_io.deinit();
26
+ const stdout = stdout_io.writer();
18
27
 
19
28
  while (true) {
20
- const line = stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 16 * 1024 * 1024) catch |err| {
29
+ const line = stdio.readLineAlloc(stdin, allocator, 16 * 1024 * 1024) catch |err| {
21
30
  try protocol.writeError(stdout, null, -32700, @errorName(err));
31
+ try stdout_io.flush();
22
32
  continue;
23
33
  };
24
34
  const owned_line = line orelse break;
@@ -26,6 +36,7 @@ pub fn serveStdioWithTrace(allocator: std.mem.Allocator, device: anytype, live_t
26
36
  const trimmed = std.mem.trim(u8, owned_line, " \t\r\n");
27
37
  if (trimmed.len == 0) continue;
28
38
  try dispatchLineWithTrace(allocator, device, trimmed, stdout, live_trace);
39
+ try stdout_io.flush();
29
40
  }
30
41
  }
31
42
 
@@ -34,45 +45,33 @@ pub fn serveTcp(allocator: std.mem.Allocator, device: anytype, port: u16) !void
34
45
  }
35
46
 
36
47
  pub fn serveTcpWithTrace(allocator: std.mem.Allocator, device: anytype, port: u16, live_trace: ?*trace.TraceWriter) !void {
37
- const address = try std.net.Address.parseIp("127.0.0.1", port);
38
- var server = try address.listen(.{ .reuse_address = true });
39
- defer server.deinit();
48
+ const address = try net.IpAddress.parse("127.0.0.1", port);
49
+ var server = try address.listen(stdio.io(), .{ .reuse_address = true });
50
+ defer server.deinit(stdio.io());
40
51
 
41
52
  while (true) {
42
- var connection = try server.accept();
43
- defer connection.stream.close();
44
- try serveTcpConnection(allocator, device, connection.stream, live_trace);
53
+ const connection = try server.accept(stdio.io());
54
+ defer connection.close(stdio.io());
55
+ try serveTcpConnection(allocator, device, connection, live_trace);
45
56
  }
46
57
  }
47
58
 
48
- fn serveTcpConnection(allocator: std.mem.Allocator, device: anytype, stream: std.net.Stream, live_trace: ?*trace.TraceWriter) !void {
59
+ fn serveTcpConnection(allocator: std.mem.Allocator, device: anytype, stream: net.Stream, live_trace: ?*trace.TraceWriter) !void {
49
60
  var write_buffer: [8192]u8 = undefined;
50
- var stream_writer = stream.writer(&write_buffer);
61
+ var stream_writer = stream.writer(stdio.io(), &write_buffer);
51
62
  const writer = &stream_writer.interface;
52
63
 
53
- var line = std.ArrayList(u8).empty;
54
- defer line.deinit(allocator);
55
-
56
64
  var read_buffer: [4096]u8 = undefined;
65
+ var stream_reader = stream.reader(stdio.io(), &read_buffer);
57
66
  while (true) {
58
- const n = try stream.read(&read_buffer);
59
- if (n == 0) break;
60
- for (read_buffer[0..n]) |ch| {
61
- if (ch == '\n') {
62
- const trimmed = std.mem.trim(u8, line.items, " \t\r\n");
63
- if (trimmed.len != 0) {
64
- try dispatchLineWithTrace(allocator, device, trimmed, writer, live_trace);
65
- try writer.flush();
66
- }
67
- line.clearRetainingCapacity();
68
- } else {
69
- try line.append(allocator, ch);
70
- }
71
- }
72
- }
73
-
74
- if (line.items.len != 0) {
75
- const trimmed = std.mem.trim(u8, line.items, " \t\r\n");
67
+ const line = stdio.readLineAlloc(&stream_reader.interface, allocator, 16 * 1024 * 1024) catch |err| {
68
+ try protocol.writeError(writer, null, -32700, @errorName(err));
69
+ try writer.flush();
70
+ continue;
71
+ };
72
+ const owned_line = line orelse break;
73
+ defer allocator.free(owned_line);
74
+ const trimmed = std.mem.trim(u8, owned_line, " \t\r\n");
76
75
  if (trimmed.len != 0) {
77
76
  try dispatchLineWithTrace(allocator, device, trimmed, writer, live_trace);
78
77
  try writer.flush();
@@ -124,21 +123,21 @@ pub fn dispatchLineWithTrace(
124
123
  }
125
124
 
126
125
  fn recordRpcEvent(tw: *trace.TraceWriter, kind: []const u8, method: []const u8, id: ?std.json.Value) !void {
127
- var payload = std.ArrayList(u8).empty;
128
- defer payload.deinit(tw.allocator);
129
- const writer = payload.writer(tw.allocator);
126
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
127
+ defer payload.deinit();
128
+ const writer = &payload.writer;
130
129
  try writer.writeAll("{\"method\":");
131
130
  try trace.writeJsonString(writer, method);
132
131
  try writer.writeAll(",\"id\":");
133
132
  try protocol.writeId(writer, id);
134
133
  try writer.writeAll("}");
135
- try tw.recordEvent(kind, payload.items);
134
+ try tw.recordEvent(kind, writer.buffered());
136
135
  }
137
136
 
138
137
  fn recordRpcErrorEvent(tw: *trace.TraceWriter, method: []const u8, id: ?std.json.Value, err: anyerror) !void {
139
- var payload = std.ArrayList(u8).empty;
140
- defer payload.deinit(tw.allocator);
141
- const writer = payload.writer(tw.allocator);
138
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
139
+ defer payload.deinit();
140
+ const writer = &payload.writer;
142
141
  try writer.writeAll("{\"method\":");
143
142
  try trace.writeJsonString(writer, method);
144
143
  try writer.writeAll(",\"id\":");
@@ -146,5 +145,5 @@ fn recordRpcErrorEvent(tw: *trace.TraceWriter, method: []const u8, id: ?std.json
146
145
  try writer.writeAll(",\"error\":");
147
146
  try trace.writeJsonString(writer, @errorName(err));
148
147
  try writer.writeAll("}");
149
- try tw.recordEvent("rpc.error", payload.items);
148
+ try tw.recordEvent("rpc.error", writer.buffered());
150
149
  }
@@ -107,13 +107,13 @@ fn dispatchAppMethod(
107
107
  }
108
108
 
109
109
  fn recordAppOpenLink(tw: *trace.TraceWriter, url: []const u8) !void {
110
- var payload = std.ArrayList(u8).empty;
111
- defer payload.deinit(tw.allocator);
112
- const writer = payload.writer(tw.allocator);
110
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
111
+ defer payload.deinit();
112
+ const writer = &payload.writer;
113
113
  try writer.writeAll("{\"status\":\"ok\",\"url\":");
114
114
  try trace.writeJsonString(writer, url);
115
115
  try writer.writeAll("}");
116
- try tw.recordEvent("app.openLink", payload.items);
116
+ try tw.recordEvent("app.openLink", writer.buffered());
117
117
  }
118
118
 
119
119
  fn dispatchObserveMethod(
@@ -312,10 +312,10 @@ fn dispatchScenarioMethod(
312
312
  const path = try params_parser.requiredString(params, "path");
313
313
  var result = try validation.validateFile(allocator, path);
314
314
  defer result.deinit(allocator);
315
- var payload = std.ArrayList(u8).empty;
316
- defer payload.deinit(allocator);
317
- try cli_output.writeValidationJson(payload.writer(allocator), path, result);
318
- try protocol.writeResultRaw(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
315
+ var payload: std.Io.Writer.Allocating = .init(allocator);
316
+ defer payload.deinit();
317
+ try cli_output.writeValidationJson(&payload.writer, path, result);
318
+ try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
319
319
  return true;
320
320
  }
321
321
  return false;
@@ -20,12 +20,13 @@ pub fn writeResult(writer: anytype, id: ?std.json.Value, snap: types.Observation
20
20
  pub fn recordArtifact(tw: *trace.TraceWriter, kind: []const u8, snap: types.ObservationSnapshot) !void {
21
21
  const path = try tw.writeSnapshot(snap);
22
22
  defer tw.allocator.free(path);
23
- var payload = std.ArrayList(u8).empty;
24
- defer payload.deinit(tw.allocator);
25
- try payload.writer(tw.allocator).writeAll("{\"path\":");
26
- try trace.writeJsonString(payload.writer(tw.allocator), path);
27
- try payload.writer(tw.allocator).writeAll(",\"snapshotId\":");
28
- try trace.writeJsonString(payload.writer(tw.allocator), snap.id);
29
- try payload.writer(tw.allocator).writeAll("}");
30
- try tw.recordEvent(kind, payload.items);
23
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
24
+ defer payload.deinit();
25
+ const out = &payload.writer;
26
+ try out.writeAll("{\"path\":");
27
+ try trace.writeJsonString(out, path);
28
+ try out.writeAll(",\"snapshotId\":");
29
+ try trace.writeJsonString(out, snap.id);
30
+ try out.writeAll("}");
31
+ try tw.recordEvent(kind, out.buffered());
31
32
  }
@@ -56,5 +56,5 @@ pub fn optionalDirection(params: ?std.json.Value, key: []const u8, default_value
56
56
  if (value != .string) return error.ParamMustBeString;
57
57
  if (std.mem.eql(u8, value.string, "down")) return .down;
58
58
  if (std.mem.eql(u8, value.string, "up")) return .up;
59
- return error.UnknownScrollDirection;
59
+ return error.unknownScrollDirection;
60
60
  }
@@ -4,6 +4,7 @@ const cli_explore = @import("cli_explore.zig");
4
4
  const protocol = @import("json_rpc_protocol.zig");
5
5
  const report = @import("report.zig");
6
6
  const runner_events = @import("runner_events.zig");
7
+ const stdio = @import("stdio.zig");
7
8
  const trace = @import("trace.zig");
8
9
 
9
10
  pub fn writeEventsResult(
@@ -23,7 +24,7 @@ pub fn writeEventsResult(
23
24
 
24
25
  const events_path = try std.fs.path.join(allocator, &.{ tw.root_dir, "events.jsonl" });
25
26
  defer allocator.free(events_path);
26
- const content = std.fs.cwd().readFileAlloc(allocator, events_path, 64 * 1024 * 1024) catch |err| switch (err) {
27
+ const content = stdio.readFileAlloc(allocator, events_path, 64 * 1024 * 1024) catch |err| switch (err) {
27
28
  error.FileNotFound => try allocator.dupe(u8, ""),
28
29
  else => return err,
29
30
  };
@@ -35,9 +36,9 @@ pub fn writeEventsResult(
35
36
  try trace.writeJsonString(writer, tw.root_dir);
36
37
  try writer.print(",\"afterSeq\":{d},\"nextSeq\":", .{after_seq});
37
38
 
38
- var events_json = std.ArrayList(u8).empty;
39
- defer events_json.deinit(allocator);
40
- var events_writer = events_json.writer(allocator);
39
+ var events_json: std.Io.Writer.Allocating = .init(allocator);
40
+ defer events_json.deinit();
41
+ const events_writer = &events_json.writer;
41
42
  var next_seq = after_seq;
42
43
  var emitted: u64 = 0;
43
44
 
@@ -60,20 +61,20 @@ pub fn writeEventsResult(
60
61
  }
61
62
 
62
63
  try writer.print("{d},\"latestSeq\":{d},\"events\":[", .{ next_seq, tw.event_count });
63
- try writer.writeAll(events_json.items);
64
+ try writer.writeAll(events_writer.buffered());
64
65
  try writer.writeAll("]}}\n");
65
66
  }
66
67
 
67
68
  pub fn recordSimplePayload(tw: *trace.TraceWriter, kind: []const u8, key: []const u8, value: []const u8) !void {
68
- var payload = std.ArrayList(u8).empty;
69
- defer payload.deinit(tw.allocator);
70
- const writer = payload.writer(tw.allocator);
69
+ var payload: std.Io.Writer.Allocating = .init(tw.allocator);
70
+ defer payload.deinit();
71
+ const writer = &payload.writer;
71
72
  try writer.writeAll("{");
72
73
  try trace.writeJsonString(writer, key);
73
74
  try writer.writeAll(":");
74
75
  try trace.writeJsonString(writer, value);
75
76
  try writer.writeAll("}");
76
- try tw.recordEvent(kind, payload.items);
77
+ try tw.recordEvent(kind, writer.buffered());
77
78
  }
78
79
 
79
80
  pub fn writeDiscoverResult(
@@ -113,10 +114,10 @@ pub fn writeDiscoverResult(
113
114
  discovered.summary.validated,
114
115
  );
115
116
 
116
- var payload = std.ArrayList(u8).empty;
117
- defer payload.deinit(allocator);
118
- try cli_discover.writeJson(payload.writer(allocator), discovered.summary, discovered.validation);
119
- try protocol.writeResultRaw(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
117
+ var payload: std.Io.Writer.Allocating = .init(allocator);
118
+ defer payload.deinit();
119
+ try cli_discover.writeJson(&payload.writer, discovered.summary, discovered.validation);
120
+ try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
120
121
  }
121
122
 
122
123
  pub fn writeExploreResult(
@@ -159,10 +160,10 @@ pub fn writeExploreResult(
159
160
  explored.discovered.summary.validated,
160
161
  );
161
162
 
162
- var payload = std.ArrayList(u8).empty;
163
- defer payload.deinit(allocator);
164
- try cli_explore.writeJson(payload.writer(allocator), explored.summary, explored.discovered.summary, explored.discovered.validation);
165
- try protocol.writeResultRaw(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
163
+ var payload: std.Io.Writer.Allocating = .init(allocator);
164
+ defer payload.deinit();
165
+ try cli_explore.writeJson(&payload.writer, explored.summary, explored.discovered.summary, explored.discovered.validation);
166
+ try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
166
167
  }
167
168
 
168
169
  pub fn writeExplainResult(
@@ -177,9 +178,9 @@ pub fn writeExplainResult(
177
178
  };
178
179
 
179
180
  try tw.flushManifest();
180
- var payload = std.ArrayList(u8).empty;
181
- defer payload.deinit(allocator);
182
- try report.writeTraceExplanationJson(allocator, tw.root_dir, payload.writer(allocator));
183
- try protocol.writeResultRaw(writer, id, std.mem.trimRight(u8, payload.items, " \t\r\n"));
181
+ var payload: std.Io.Writer.Allocating = .init(allocator);
182
+ defer payload.deinit();
183
+ try report.writeTraceExplanationJson(allocator, tw.root_dir, &payload.writer);
184
+ try protocol.writeResultRaw(writer, id, std.mem.trimEnd(u8, payload.writer.buffered(), " \t\r\n"));
184
185
  try tw.recordEvent("trace.explain", "{\"status\":\"ok\"}");
185
186
  }
package/src/main.zig CHANGED
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const stdio = @import("stdio.zig");
2
3
  const cli_devices = @import("cli_devices.zig");
3
4
  const cli_discover = @import("cli_discover.zig");
4
5
  const cli_doctor = @import("cli_doctor.zig");
@@ -14,8 +15,8 @@ const cli_trace = @import("cli_trace.zig");
14
15
  const cli_validate = @import("cli_validate.zig");
15
16
  const errors = @import("errors.zig");
16
17
 
17
- pub fn main() void {
18
- mainInner() catch |err| {
18
+ pub fn main(init: std.process.Init.Minimal) void {
19
+ mainInner(init) catch |err| {
19
20
  // stdout's consumer went away (e.g. `zmr ... | head`); exit quietly
20
21
  // with the conventional SIGPIPE status instead of reporting an error.
21
22
  if (err == error.BrokenPipe) std.process.exit(141);
@@ -24,7 +25,7 @@ pub fn main() void {
24
25
  };
25
26
  }
26
27
 
27
- fn mainInner() !void {
28
+ fn mainInner(init: std.process.Init.Minimal) !void {
28
29
  const GeneralAllocator = if (@hasDecl(std.heap, "GeneralPurposeAllocator"))
29
30
  std.heap.GeneralPurposeAllocator
30
31
  else
@@ -32,8 +33,10 @@ fn mainInner() !void {
32
33
  var gpa = GeneralAllocator(.{}){};
33
34
  defer _ = gpa.deinit();
34
35
  const allocator = gpa.allocator();
36
+ stdio.initProcess(init, allocator);
37
+ defer stdio.deinitProcess();
35
38
 
36
- var args = try std.process.argsWithAllocator(allocator);
39
+ var args = try std.process.Args.Iterator.initAllocator(init.args, allocator);
37
40
  defer args.deinit();
38
41
  _ = args.next();
39
42
  const command_name = args.next() orelse {
@@ -80,26 +83,29 @@ fn mainInner() !void {
80
83
  } else {
81
84
  std.debug.print("unknown command: {s}\n\n", .{command_name});
82
85
  try usage();
83
- return error.UnknownCommand;
86
+ return error.unknownCommand;
84
87
  }
85
88
  }
86
89
 
87
90
  fn writeTopLevelError(err: anyerror) void {
88
91
  const public = errors.classify(err);
89
- const stderr = std.fs.File.stderr().deprecatedWriter();
92
+ var stderr_io: stdio.Output = .{};
93
+ stderr_io.init(.stderr());
94
+ defer stderr_io.deinit();
95
+ const stderr = stderr_io.writer();
90
96
  stderr.print("error[{s}]: {s}\n", .{ public.code, public.message }) catch {};
91
97
  if (err == error.CommandFailed) {
92
98
  stderr.writeAll("hint: run `zmr doctor --json` for setup diagnostics.\n") catch {};
93
99
  }
94
- if (err == error.UnknownFlag) {
100
+ if (err == error.unknownFlag) {
95
101
  stderr.writeAll("hint: run `zmr help` for each command's flags and arguments.\n") catch {};
96
102
  }
97
103
  }
98
104
 
99
105
  fn exitCodeForError(err: anyerror) u8 {
100
106
  return switch (err) {
101
- error.UnknownCommand,
102
- error.UnknownFlag,
107
+ error.unknownCommand,
108
+ error.unknownFlag,
103
109
  error.MissingScenarioPath,
104
110
  error.MissingDeviceSerial,
105
111
  error.MissingTraceDir,
@@ -125,7 +131,10 @@ fn exitCodeForError(err: anyerror) u8 {
125
131
  }
126
132
 
127
133
  fn usage() !void {
128
- const stdout = std.fs.File.stdout().deprecatedWriter();
134
+ var stdout_io: stdio.Output = .{};
135
+ stdout_io.init(.stdout());
136
+ defer stdout_io.deinit();
137
+ const stdout = stdout_io.writer();
129
138
  try stdout.writeAll(
130
139
  \\zmr - Zeno Mobile Runner
131
140
  \\