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.
- package/CHANGELOG.md +484 -0
- package/CONTRIBUTING.md +42 -0
- package/FEATURES.md +112 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/SECURITY.md +34 -0
- package/build.zig +38 -0
- package/build.zig.zon +7 -0
- package/clients/README.md +144 -0
- package/clients/go/README.md +24 -0
- package/clients/go/examples/fake-session/main.go +93 -0
- package/clients/go/go.mod +3 -0
- package/clients/go/zmr/client.go +432 -0
- package/clients/kotlin/README.md +35 -0
- package/clients/kotlin/build.gradle.kts +35 -0
- package/clients/kotlin/settings.gradle.kts +15 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
- package/clients/python/README.md +29 -0
- package/clients/python/examples/fake_session.py +48 -0
- package/clients/python/pyproject.toml +13 -0
- package/clients/python/zmr_client.py +202 -0
- package/clients/rust/Cargo.lock +107 -0
- package/clients/rust/Cargo.toml +10 -0
- package/clients/rust/README.md +19 -0
- package/clients/rust/examples/fake_session.rs +70 -0
- package/clients/rust/src/lib.rs +461 -0
- package/clients/swift/Package.swift +16 -0
- package/clients/swift/README.md +36 -0
- package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
- package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
- package/clients/typescript/README.md +34 -0
- package/clients/typescript/examples/fake-session.mjs +36 -0
- package/clients/typescript/index.d.ts +144 -0
- package/clients/typescript/index.mjs +192 -0
- package/clients/typescript/package.json +8 -0
- package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
- package/docs/adr/0002-app-local-zmr-contract.md +39 -0
- package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
- package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
- package/docs/adr/README.md +12 -0
- package/docs/ai-agents.md +156 -0
- package/docs/app-integration.md +316 -0
- package/docs/benchmarking.md +275 -0
- package/docs/client-installation.md +141 -0
- package/docs/clients.md +98 -0
- package/docs/config.md +175 -0
- package/docs/demo.md +259 -0
- package/docs/dsl.md +57 -0
- package/docs/install.md +233 -0
- package/docs/market-positioning.md +70 -0
- package/docs/npm.md +359 -0
- package/docs/protocol-fixtures/README.md +8 -0
- package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
- package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
- package/docs/protocol-versioning.md +65 -0
- package/docs/protocol.md +560 -0
- package/docs/publication.md +77 -0
- package/docs/release-audit.md +99 -0
- package/docs/release-candidate.md +111 -0
- package/docs/release-evidence.md +188 -0
- package/docs/release-notes-template.md +58 -0
- package/docs/roadmap.md +334 -0
- package/docs/scenario-authoring.md +88 -0
- package/docs/shipping.md +170 -0
- package/docs/trace-privacy.md +88 -0
- package/docs/troubleshooting.md +256 -0
- package/examples/android-app-auth-probe.json +89 -0
- package/examples/android-app-error-state.json +13 -0
- package/examples/android-app-login-smoke.json +192 -0
- package/examples/android-app-onboarding.json +12 -0
- package/examples/android-app-referral-deep-link.json +12 -0
- package/examples/android-shim-smoke.json +19 -0
- package/examples/demo-failure.json +12 -0
- package/examples/demo-fake.json +14 -0
- package/examples/ios-dev-client-open-link.json +26 -0
- package/examples/ios-dev-client-route-snapshot.json +24 -0
- package/examples/ios-shim-smoke.json +23 -0
- package/examples/ios-smoke.json +9 -0
- package/go.work +3 -0
- package/npm/agents.mjs +183 -0
- package/npm/app-config.mjs +95 -0
- package/npm/build-zmr.mjs +21 -0
- package/npm/commands.mjs +104 -0
- package/npm/generated-files.mjs +50 -0
- package/npm/index.mjs +75 -0
- package/npm/init-app.mjs +80 -0
- package/npm/package-scripts.mjs +72 -0
- package/npm/postinstall.mjs +21 -0
- package/npm/scaffold.mjs +179 -0
- package/npm/scenarios.mjs +93 -0
- package/npm/setup.mjs +69 -0
- package/npm/wizard.mjs +117 -0
- package/npm/zmr.mjs +23 -0
- package/package.json +114 -0
- package/prebuilds/darwin-arm64/zmr +0 -0
- package/prebuilds/darwin-x64/zmr +0 -0
- package/prebuilds/linux-arm64/zmr +0 -0
- package/prebuilds/linux-x64/zmr +0 -0
- package/schemas/README.md +26 -0
- package/schemas/action-result.schema.json +27 -0
- package/schemas/capabilities-output.schema.json +98 -0
- package/schemas/devices-output.schema.json +25 -0
- package/schemas/doctor-output.schema.json +51 -0
- package/schemas/explain-output.schema.json +51 -0
- package/schemas/import-output.schema.json +23 -0
- package/schemas/init-output.schema.json +71 -0
- package/schemas/json-rpc.schema.json +55 -0
- package/schemas/release-manifest.schema.json +43 -0
- package/schemas/release-readiness-output.schema.json +127 -0
- package/schemas/run-output.schema.json +43 -0
- package/schemas/scenario.schema.json +128 -0
- package/schemas/schemas-output.schema.json +26 -0
- package/schemas/semantic-snapshot.schema.json +116 -0
- package/schemas/snapshot.schema.json +60 -0
- package/schemas/trace-event.schema.json +14 -0
- package/schemas/trace-manifest.schema.json +59 -0
- package/schemas/validate-output.schema.json +42 -0
- package/schemas/version-output.schema.json +23 -0
- package/schemas/zmr-config.schema.json +75 -0
- package/scripts/android-emulator.sh +126 -0
- package/scripts/assert-ios-physical-ready.sh +213 -0
- package/scripts/benchmark-command.sh +307 -0
- package/scripts/benchmark.sh +359 -0
- package/scripts/benchmark_gate.py +117 -0
- package/scripts/benchmark_result_row.py +88 -0
- package/scripts/compare-benchmarks.py +288 -0
- package/scripts/create-android-demo-app.sh +342 -0
- package/scripts/create-ios-demo-app.sh +261 -0
- package/scripts/demo-android-real.sh +232 -0
- package/scripts/demo-ios-real.sh +270 -0
- package/scripts/demo.sh +464 -0
- package/scripts/device-matrix.sh +338 -0
- package/scripts/ensure-ios-shim-target.rb +237 -0
- package/scripts/install-android-shim.sh +281 -0
- package/scripts/install-ios-shim.sh +589 -0
- package/scripts/pilot-gate.sh +560 -0
- package/scripts/release-readiness.py +838 -0
- package/scripts/release-readiness.sh +91 -0
- package/scripts/run-android-pilot.sh +561 -0
- package/scripts/run-ios-pilot.sh +509 -0
- package/shims/android/README.md +21 -0
- package/shims/android/ZMRShimInstrumentedTest.java +152 -0
- package/shims/android/protocol.md +18 -0
- package/shims/ios/README.md +50 -0
- package/shims/ios/ZMRShim.swift +110 -0
- package/shims/ios/ZMRShimUITestCase.swift +475 -0
- package/shims/ios/protocol.md +74 -0
- package/skills/zmr-mobile-testing/SKILL.md +127 -0
- package/src/android.zig +344 -0
- package/src/android_device_info.zig +99 -0
- package/src/android_emulator.zig +154 -0
- package/src/android_screen_recording.zig +112 -0
- package/src/android_shell.zig +112 -0
- package/src/bundle.zig +124 -0
- package/src/bundle_redaction.zig +272 -0
- package/src/bundle_tar.zig +123 -0
- package/src/cli_devices.zig +97 -0
- package/src/cli_doctor.zig +114 -0
- package/src/cli_import.zig +70 -0
- package/src/cli_info.zig +39 -0
- package/src/cli_init.zig +72 -0
- package/src/cli_output.zig +467 -0
- package/src/cli_run.zig +259 -0
- package/src/cli_serve.zig +287 -0
- package/src/cli_trace.zig +111 -0
- package/src/cli_validate.zig +41 -0
- package/src/command.zig +211 -0
- package/src/config.zig +305 -0
- package/src/config_diagnostics.zig +212 -0
- package/src/config_paths.zig +49 -0
- package/src/device_registry.zig +37 -0
- package/src/doctor.zig +412 -0
- package/src/doctor_hints.zig +52 -0
- package/src/errors.zig +55 -0
- package/src/fake_device.zig +163 -0
- package/src/health.zig +28 -0
- package/src/importer.zig +343 -0
- package/src/importer_json.zig +100 -0
- package/src/importer_model.zig +103 -0
- package/src/ios.zig +399 -0
- package/src/ios_devices.zig +219 -0
- package/src/ios_lifecycle.zig +72 -0
- package/src/ios_shim.zig +242 -0
- package/src/ios_snapshot.zig +20 -0
- package/src/json_fields.zig +80 -0
- package/src/json_rpc.zig +150 -0
- package/src/json_rpc_methods.zig +318 -0
- package/src/json_rpc_observation.zig +31 -0
- package/src/json_rpc_params.zig +52 -0
- package/src/json_rpc_protocol.zig +110 -0
- package/src/json_rpc_trace.zig +73 -0
- package/src/main.zig +135 -0
- package/src/mcp.zig +234 -0
- package/src/mcp_protocol.zig +64 -0
- package/src/mcp_trace.zig +83 -0
- package/src/report.zig +346 -0
- package/src/report_html.zig +63 -0
- package/src/report_values.zig +27 -0
- package/src/run_options.zig +152 -0
- package/src/runner.zig +280 -0
- package/src/runner_actions.zig +109 -0
- package/src/runner_config.zig +6 -0
- package/src/runner_diagnostics.zig +268 -0
- package/src/runner_events.zig +170 -0
- package/src/runner_native.zig +88 -0
- package/src/runner_waits.zig +300 -0
- package/src/scaffold.zig +472 -0
- package/src/scenario.zig +346 -0
- package/src/scenario_fields.zig +50 -0
- package/src/schema_registry.zig +53 -0
- package/src/selector.zig +84 -0
- package/src/semantic.zig +171 -0
- package/src/trace.zig +315 -0
- package/src/trace_json.zig +340 -0
- package/src/trace_summary.zig +218 -0
- package/src/trace_summary_diagnostic.zig +202 -0
- package/src/types.zig +120 -0
- package/src/uiautomator.zig +164 -0
- package/src/validation.zig +187 -0
- package/src/version.zig +22 -0
- package/viewer/app.js +373 -0
- package/viewer/index.html +126 -0
- package/viewer/parser.js +233 -0
- package/viewer/styles.css +585 -0
package/src/cli_info.zig
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const schema_registry = @import("schema_registry.zig");
|
|
4
|
+
const version = @import("version.zig");
|
|
5
|
+
|
|
6
|
+
pub fn parseJsonFlag(args: []const []const u8) !bool {
|
|
7
|
+
var json = false;
|
|
8
|
+
for (args) |arg| {
|
|
9
|
+
if (std.mem.eql(u8, arg, "--json")) {
|
|
10
|
+
json = true;
|
|
11
|
+
} else {
|
|
12
|
+
return error.UnknownFlag;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return json;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub fn runVersion(allocator: std.mem.Allocator, args: *std.process.ArgIterator) !void {
|
|
19
|
+
const json = try parseArgIterator(allocator, args);
|
|
20
|
+
const stdout = std.fs.File.stdout().deprecatedWriter();
|
|
21
|
+
if (json) return try version.writeJson(stdout);
|
|
22
|
+
try version.writePlain(stdout);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub fn runSchemas(allocator: std.mem.Allocator, args: *std.process.ArgIterator) !void {
|
|
26
|
+
const json = try parseArgIterator(allocator, args);
|
|
27
|
+
const stdout = std.fs.File.stdout().deprecatedWriter();
|
|
28
|
+
if (json) return try schema_registry.writeJson(stdout);
|
|
29
|
+
for (schema_registry.all()) |schema_info| {
|
|
30
|
+
try stdout.print("{s}\t{s}\n", .{ schema_info.name, schema_info.path });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn parseArgIterator(allocator: std.mem.Allocator, args: *std.process.ArgIterator) !bool {
|
|
35
|
+
var raw_args = std.ArrayList([]const u8).empty;
|
|
36
|
+
defer raw_args.deinit(allocator);
|
|
37
|
+
while (args.next()) |arg| try raw_args.append(allocator, arg);
|
|
38
|
+
return parseJsonFlag(raw_args.items);
|
|
39
|
+
}
|
package/src/cli_init.zig
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const cli_output = @import("cli_output.zig");
|
|
4
|
+
const scaffold = @import("scaffold.zig");
|
|
5
|
+
|
|
6
|
+
pub const ParsedArgs = struct {
|
|
7
|
+
path: []const u8 = "zmr-scenario.json",
|
|
8
|
+
dir: []const u8 = ".",
|
|
9
|
+
app_id: []const u8 = "com.example.mobiletest",
|
|
10
|
+
app_scaffold: bool = false,
|
|
11
|
+
force: bool = false,
|
|
12
|
+
json: bool = false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
pub fn parseArgs(args: []const []const u8) !ParsedArgs {
|
|
16
|
+
var parsed = ParsedArgs{};
|
|
17
|
+
var path_set = false;
|
|
18
|
+
var index: usize = 0;
|
|
19
|
+
while (index < args.len) : (index += 1) {
|
|
20
|
+
const arg = args[index];
|
|
21
|
+
if (std.mem.eql(u8, arg, "--app-id")) {
|
|
22
|
+
index += 1;
|
|
23
|
+
parsed.app_id = if (index < args.len) args[index] else return error.MissingAppId;
|
|
24
|
+
} else if (std.mem.eql(u8, arg, "--app")) {
|
|
25
|
+
parsed.app_scaffold = true;
|
|
26
|
+
} else if (std.mem.eql(u8, arg, "--dir")) {
|
|
27
|
+
index += 1;
|
|
28
|
+
parsed.dir = if (index < args.len) args[index] else return error.MissingDirectory;
|
|
29
|
+
} else if (std.mem.eql(u8, arg, "--force")) {
|
|
30
|
+
parsed.force = true;
|
|
31
|
+
} else if (std.mem.eql(u8, arg, "--json")) {
|
|
32
|
+
parsed.json = true;
|
|
33
|
+
} else if (std.mem.startsWith(u8, arg, "--")) {
|
|
34
|
+
return error.UnknownFlag;
|
|
35
|
+
} else if (parsed.app_scaffold) {
|
|
36
|
+
return error.UnknownFlag;
|
|
37
|
+
} else if (!path_set) {
|
|
38
|
+
parsed.path = arg;
|
|
39
|
+
path_set = true;
|
|
40
|
+
} else {
|
|
41
|
+
return error.UnknownFlag;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn run(allocator: std.mem.Allocator, args: *std.process.ArgIterator) !void {
|
|
48
|
+
var raw_args = std.ArrayList([]const u8).empty;
|
|
49
|
+
defer raw_args.deinit(allocator);
|
|
50
|
+
while (args.next()) |arg| try raw_args.append(allocator, arg);
|
|
51
|
+
|
|
52
|
+
const parsed = try parseArgs(raw_args.items);
|
|
53
|
+
const stdout = std.fs.File.stdout().deprecatedWriter();
|
|
54
|
+
if (parsed.app_scaffold) {
|
|
55
|
+
try scaffold.writeAppScaffold(allocator, parsed.dir, parsed.app_id, parsed.force);
|
|
56
|
+
if (parsed.json) return try cli_output.writeInitAppJson(stdout, parsed.dir, parsed.app_id);
|
|
57
|
+
for (scaffold.app_created_files) |path| {
|
|
58
|
+
try stdout.print("created {s}/{s}\n", .{ parsed.dir, path });
|
|
59
|
+
}
|
|
60
|
+
try stdout.writeAll("next: zmr doctor --strict --json --config ");
|
|
61
|
+
try cli_output.writeJoinedPathShellArg(stdout, parsed.dir, scaffold.app_config_file);
|
|
62
|
+
try stdout.writeAll("\n");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try scaffold.writeStarterScenario(allocator, parsed.path, parsed.app_id, parsed.force);
|
|
67
|
+
if (parsed.json) return try cli_output.writeInitScenarioJson(stdout, parsed.path, parsed.app_id);
|
|
68
|
+
try stdout.print("created {s}\n", .{parsed.path});
|
|
69
|
+
try stdout.writeAll("next: zmr validate ");
|
|
70
|
+
try cli_output.writeShellArg(stdout, parsed.path);
|
|
71
|
+
try stdout.writeAll("\n");
|
|
72
|
+
}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const doctor = @import("doctor.zig");
|
|
3
|
+
const importer = @import("importer.zig");
|
|
4
|
+
const scaffold = @import("scaffold.zig");
|
|
5
|
+
const trace = @import("trace.zig");
|
|
6
|
+
const trace_summary = @import("trace_summary.zig");
|
|
7
|
+
const validation = @import("validation.zig");
|
|
8
|
+
|
|
9
|
+
pub fn writeImportJson(writer: anytype, format: []const u8, source_path: []const u8, result: importer.ImportResult) !void {
|
|
10
|
+
try writer.writeAll("{\"ok\":true,\"format\":");
|
|
11
|
+
try trace.writeJsonString(writer, format);
|
|
12
|
+
try writer.writeAll(",\"source\":");
|
|
13
|
+
try trace.writeJsonString(writer, source_path);
|
|
14
|
+
try writer.writeAll(",\"out\":");
|
|
15
|
+
try trace.writeJsonString(writer, result.out_path);
|
|
16
|
+
try writer.writeAll(",\"name\":");
|
|
17
|
+
try trace.writeJsonString(writer, result.name);
|
|
18
|
+
try writer.writeAll(",\"appId\":");
|
|
19
|
+
if (result.app_id) |app_id| {
|
|
20
|
+
try trace.writeJsonString(writer, app_id);
|
|
21
|
+
} else {
|
|
22
|
+
try writer.writeAll("null");
|
|
23
|
+
}
|
|
24
|
+
try writer.print(",\"stepCount\":{d}", .{result.step_count});
|
|
25
|
+
try writer.writeAll(",\"next\":\"zmr validate ");
|
|
26
|
+
try writeShellArgJsonContent(writer, result.out_path);
|
|
27
|
+
try writer.writeAll("\"");
|
|
28
|
+
try writeImportNextCommandsJson(writer, result.out_path);
|
|
29
|
+
try writer.writeAll("}\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn writeImportNextCommandsJson(writer: anytype, out_path: []const u8) !void {
|
|
33
|
+
try writer.writeAll(",\"nextCommands\":[\"zmr validate --json ");
|
|
34
|
+
try writeShellArgJsonContent(writer, out_path);
|
|
35
|
+
try writer.writeAll("\",\"zmr run ");
|
|
36
|
+
try writeShellArgJsonContent(writer, out_path);
|
|
37
|
+
try writer.writeAll(" --json --trace-dir traces/zmr-run\"]");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn writeInitAppJson(writer: anytype, dir: []const u8, app_id: []const u8) !void {
|
|
41
|
+
try writer.writeAll("{\"ok\":true,\"mode\":\"app\",\"dir\":");
|
|
42
|
+
try trace.writeJsonString(writer, dir);
|
|
43
|
+
try writer.writeAll(",\"appId\":");
|
|
44
|
+
try trace.writeJsonString(writer, app_id);
|
|
45
|
+
try writer.writeAll(",\"created\":[");
|
|
46
|
+
for (scaffold.app_created_files, 0..) |path, index| {
|
|
47
|
+
if (index > 0) try writer.writeAll(",");
|
|
48
|
+
try writeJoinedPathJson(writer, dir, path);
|
|
49
|
+
}
|
|
50
|
+
try writer.writeAll("],\"configPath\":");
|
|
51
|
+
try writeJoinedPathJson(writer, dir, scaffold.app_config_file);
|
|
52
|
+
try writer.writeAll(",\"androidScenarioPath\":");
|
|
53
|
+
try writeJoinedPathJson(writer, dir, scaffold.app_android_smoke_file);
|
|
54
|
+
try writer.writeAll(",\"iosScenarioPath\":");
|
|
55
|
+
try writeJoinedPathJson(writer, dir, scaffold.app_ios_smoke_file);
|
|
56
|
+
try writer.writeAll(",\"deviceMatrixPath\":");
|
|
57
|
+
try writeJoinedPathJson(writer, dir, scaffold.app_device_matrix_file);
|
|
58
|
+
try writer.writeAll(",\"agentInstructionsPath\":");
|
|
59
|
+
try writeJoinedPathJson(writer, dir, scaffold.app_agents_file);
|
|
60
|
+
try writer.writeAll(",\"next\":");
|
|
61
|
+
try writer.writeAll("\"zmr doctor --strict --json --config ");
|
|
62
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, scaffold.app_config_file);
|
|
63
|
+
try writer.writeAll("\"");
|
|
64
|
+
try writer.writeAll(",\"nextCommands\":[");
|
|
65
|
+
try writeInitDoctorCommandJson(writer, dir);
|
|
66
|
+
try writer.writeAll(",");
|
|
67
|
+
try trace.writeJsonString(writer, "zmr schemas --json");
|
|
68
|
+
try writer.writeAll(",");
|
|
69
|
+
try writeInitValidateCommandJson(writer, dir, scaffold.app_android_smoke_file);
|
|
70
|
+
try writer.writeAll(",");
|
|
71
|
+
try writeInitValidateCommandJson(writer, dir, scaffold.app_ios_smoke_file);
|
|
72
|
+
try writer.writeAll("]");
|
|
73
|
+
try writeInitSmokeCommandsJson(writer, dir);
|
|
74
|
+
try writer.print(",\"scriptCount\":{d}", .{scaffold.app_script_names.len});
|
|
75
|
+
try writer.writeAll(",\"scriptNames\":[");
|
|
76
|
+
for (scaffold.app_script_names, 0..) |script_name, index| {
|
|
77
|
+
if (index > 0) try writer.writeAll(",");
|
|
78
|
+
try trace.writeJsonString(writer, script_name);
|
|
79
|
+
}
|
|
80
|
+
try writer.writeAll("]}\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn writeInitDoctorCommandJson(writer: anytype, dir: []const u8) !void {
|
|
84
|
+
try writer.writeAll("\"zmr doctor --strict --json --config ");
|
|
85
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, scaffold.app_config_file);
|
|
86
|
+
try writer.writeAll("\"");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn writeInitValidateCommandJson(writer: anytype, dir: []const u8, scenario_path: []const u8) !void {
|
|
90
|
+
try writer.writeAll("\"zmr validate --json ");
|
|
91
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, scenario_path);
|
|
92
|
+
try writer.writeAll("\"");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn writeInitSmokeCommandsJson(writer: anytype, dir: []const u8) !void {
|
|
96
|
+
try writer.writeAll(",\"smokeCommands\":[\"zmr run ");
|
|
97
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, scaffold.app_android_smoke_file);
|
|
98
|
+
try writer.writeAll(" --device emulator-5554 --trace-dir ");
|
|
99
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, "traces/zmr-android");
|
|
100
|
+
try writer.writeAll("\",\"zmr run ");
|
|
101
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, scaffold.app_ios_smoke_file);
|
|
102
|
+
try writer.writeAll(" --platform ios --device booted --trace-dir ");
|
|
103
|
+
try writeJoinedPathShellArgJsonContent(writer, dir, "traces/zmr-ios");
|
|
104
|
+
try writer.writeAll("\"]");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn writeInitScenarioJson(writer: anytype, path: []const u8, app_id: []const u8) !void {
|
|
108
|
+
try writer.writeAll("{\"ok\":true,\"mode\":\"scenario\",\"appId\":");
|
|
109
|
+
try trace.writeJsonString(writer, app_id);
|
|
110
|
+
try writer.writeAll(",\"created\":[");
|
|
111
|
+
try trace.writeJsonString(writer, path);
|
|
112
|
+
try writer.writeAll("],\"next\":\"zmr validate ");
|
|
113
|
+
try writeShellArgJsonContent(writer, path);
|
|
114
|
+
try writer.writeAll("\"");
|
|
115
|
+
try writeScenarioNextCommandsJson(writer, path);
|
|
116
|
+
try writer.writeAll("}\n");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn writeScenarioNextCommandsJson(writer: anytype, path: []const u8) !void {
|
|
120
|
+
try writer.writeAll(",\"nextCommands\":[\"zmr validate --json ");
|
|
121
|
+
try writeShellArgJsonContent(writer, path);
|
|
122
|
+
try writer.writeAll("\",\"zmr run ");
|
|
123
|
+
try writeShellArgJsonContent(writer, path);
|
|
124
|
+
try writer.writeAll(" --json --trace-dir traces/zmr-run\"]");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pub fn writeRunSummaryJson(
|
|
128
|
+
allocator: std.mem.Allocator,
|
|
129
|
+
writer: anytype,
|
|
130
|
+
trace_dir: ?[]const u8,
|
|
131
|
+
fallback_scenario: []const u8,
|
|
132
|
+
fallback_app_id: []const u8,
|
|
133
|
+
run_error: ?anyerror,
|
|
134
|
+
) !void {
|
|
135
|
+
if (trace_dir) |dir| {
|
|
136
|
+
if (trace_summary.read(allocator, dir)) |summary_value| {
|
|
137
|
+
var summary = summary_value;
|
|
138
|
+
defer summary.deinit(allocator);
|
|
139
|
+
return try writeRunSummaryFromTraceSummary(writer, dir, summary, run_error);
|
|
140
|
+
} else |_| {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const ok = run_error == null;
|
|
144
|
+
try writer.writeAll("{\"ok\":");
|
|
145
|
+
try writer.writeAll(if (ok) "true" else "false");
|
|
146
|
+
try writer.writeAll(",\"status\":");
|
|
147
|
+
try trace.writeJsonString(writer, if (ok) "passed" else "failed");
|
|
148
|
+
try writer.writeAll(",\"scenario\":");
|
|
149
|
+
try trace.writeJsonString(writer, fallback_scenario);
|
|
150
|
+
try writer.writeAll(",\"appId\":");
|
|
151
|
+
try trace.writeJsonString(writer, fallback_app_id);
|
|
152
|
+
if (run_error) |err| {
|
|
153
|
+
try writer.writeAll(",\"error\":");
|
|
154
|
+
try trace.writeJsonString(writer, @errorName(err));
|
|
155
|
+
}
|
|
156
|
+
try writer.writeAll("}\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pub fn writeDoctorText(writer: anytype, config_check: ?doctor.Check, checks: []const doctor.Check) !void {
|
|
160
|
+
const healthy = doctorChecksHealthy(config_check, checks);
|
|
161
|
+
if (config_check) |check| {
|
|
162
|
+
try writer.print("{s}\t{s}\t{s}\n", .{ check.name, @tagName(check.status), check.detail });
|
|
163
|
+
if (check.hint) |hint| {
|
|
164
|
+
try writer.print("{s}-hint\t{s}\n", .{ check.name, hint });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (checks) |check| {
|
|
168
|
+
try writer.print("{s}\t{s}\t{s}\n", .{ check.name, @tagName(check.status), check.detail });
|
|
169
|
+
if (check.hint) |hint| {
|
|
170
|
+
try writer.print("{s}-hint\t{s}\n", .{ check.name, hint });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
try writer.print("status\t{s}\n", .{if (healthy) "ok" else "needs-attention"});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
pub fn writeDoctorJson(writer: anytype, config_check: ?doctor.Check, checks: []const doctor.Check) !void {
|
|
177
|
+
const healthy = doctorChecksHealthy(config_check, checks);
|
|
178
|
+
try writer.writeAll("{\"ok\":");
|
|
179
|
+
try writer.writeAll(if (healthy) "true" else "false");
|
|
180
|
+
try writer.writeAll(",\"checks\":[");
|
|
181
|
+
var index: usize = 0;
|
|
182
|
+
if (config_check) |check| {
|
|
183
|
+
try writeDoctorCheckJson(writer, check);
|
|
184
|
+
index += 1;
|
|
185
|
+
}
|
|
186
|
+
for (checks) |check| {
|
|
187
|
+
if (index > 0) try writer.writeAll(",");
|
|
188
|
+
try writeDoctorCheckJson(writer, check);
|
|
189
|
+
index += 1;
|
|
190
|
+
}
|
|
191
|
+
try writer.writeAll("]}\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
pub fn doctorChecksHealthy(config_check: ?doctor.Check, checks: []const doctor.Check) bool {
|
|
195
|
+
var healthy = true;
|
|
196
|
+
if (config_check) |check| {
|
|
197
|
+
if (check.status != .ok) healthy = false;
|
|
198
|
+
}
|
|
199
|
+
for (checks) |check| {
|
|
200
|
+
if (check.status != .ok) healthy = false;
|
|
201
|
+
}
|
|
202
|
+
return healthy;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
pub fn writeValidationText(writer: anytype, path: []const u8, result: validation.Result) !void {
|
|
206
|
+
if (result.ok) {
|
|
207
|
+
try writer.print("{s}: ok ({s}, {d} steps)\n", .{ path, result.name.?, result.step_count });
|
|
208
|
+
} else {
|
|
209
|
+
try writer.print("{s}: invalid [{s}] {s}", .{ path, result.error_code.?, result.message.? });
|
|
210
|
+
if (result.path) |field_path| {
|
|
211
|
+
try writer.print(" at {s}", .{field_path});
|
|
212
|
+
}
|
|
213
|
+
if (result.line) |line| {
|
|
214
|
+
try writer.print(" line {d}", .{line});
|
|
215
|
+
if (result.column) |column| try writer.print(" column {d}", .{column});
|
|
216
|
+
}
|
|
217
|
+
try writer.writeAll("\n");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
pub fn writeValidationJson(writer: anytype, path: []const u8, result: validation.Result) !void {
|
|
222
|
+
try writer.writeAll("{\"ok\":");
|
|
223
|
+
try writer.writeAll(if (result.ok) "true" else "false");
|
|
224
|
+
try writer.writeAll(",\"path\":");
|
|
225
|
+
try trace.writeJsonString(writer, path);
|
|
226
|
+
if (result.ok) {
|
|
227
|
+
try writer.writeAll(",\"name\":");
|
|
228
|
+
try trace.writeJsonString(writer, result.name.?);
|
|
229
|
+
if (result.app_id) |app_id| {
|
|
230
|
+
try writer.writeAll(",\"appId\":");
|
|
231
|
+
try trace.writeJsonString(writer, app_id);
|
|
232
|
+
}
|
|
233
|
+
try writer.print(",\"stepCount\":{d}", .{result.step_count});
|
|
234
|
+
try writeValidationNextCommandsJson(writer, path);
|
|
235
|
+
} else {
|
|
236
|
+
try writer.writeAll(",\"errorCode\":");
|
|
237
|
+
try trace.writeJsonString(writer, result.error_code.?);
|
|
238
|
+
try writer.writeAll(",\"message\":");
|
|
239
|
+
try trace.writeJsonString(writer, result.message.?);
|
|
240
|
+
if (result.path) |field_path| {
|
|
241
|
+
try writer.writeAll(",\"fieldPath\":");
|
|
242
|
+
try trace.writeJsonString(writer, field_path);
|
|
243
|
+
}
|
|
244
|
+
if (result.line) |line| try writer.print(",\"line\":{d}", .{line});
|
|
245
|
+
if (result.column) |column| try writer.print(",\"column\":{d}", .{column});
|
|
246
|
+
}
|
|
247
|
+
try writer.writeAll("}\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fn writeValidationNextCommandsJson(writer: anytype, path: []const u8) !void {
|
|
251
|
+
try writer.writeAll(",\"nextCommands\":[\"zmr run ");
|
|
252
|
+
try writeShellArgJsonContent(writer, path);
|
|
253
|
+
try writer.writeAll(" --json --trace-dir traces/zmr-run\"]");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
fn writeRunSummaryFromTraceSummary(
|
|
257
|
+
writer: anytype,
|
|
258
|
+
trace_dir: []const u8,
|
|
259
|
+
summary: trace_summary.Summary,
|
|
260
|
+
run_error: ?anyerror,
|
|
261
|
+
) !void {
|
|
262
|
+
try writer.writeAll("{\"ok\":");
|
|
263
|
+
try writer.writeAll(if (std.mem.eql(u8, summary.status, "passed")) "true" else "false");
|
|
264
|
+
try writer.writeAll(",\"status\":");
|
|
265
|
+
try trace.writeJsonString(writer, summary.status);
|
|
266
|
+
try writer.writeAll(",\"scenario\":");
|
|
267
|
+
try trace.writeJsonString(writer, summary.scenario_name);
|
|
268
|
+
if (summary.app_id) |value| {
|
|
269
|
+
try writer.writeAll(",\"appId\":");
|
|
270
|
+
try trace.writeJsonString(writer, value);
|
|
271
|
+
}
|
|
272
|
+
try writer.writeAll(",\"traceDir\":");
|
|
273
|
+
try trace.writeJsonString(writer, trace_dir);
|
|
274
|
+
try writer.writeAll(",\"eventsPath\":");
|
|
275
|
+
try trace.writeJsonString(writer, summary.events_path);
|
|
276
|
+
try writer.writeAll(",\"artifactsDir\":");
|
|
277
|
+
try trace.writeJsonString(writer, summary.artifacts_dir);
|
|
278
|
+
if (summary.duration_ms) |value| try writer.print(",\"durationMs\":{d}", .{value});
|
|
279
|
+
if (summary.event_count) |value| try writer.print(",\"eventCount\":{d}", .{value});
|
|
280
|
+
if (summary.snapshot_count) |value| try writer.print(",\"snapshotCount\":{d}", .{value});
|
|
281
|
+
if (summary.partial_failure_count) |value| try writer.print(",\"partialFailureCount\":{d}", .{value});
|
|
282
|
+
if (summary.failed_step_index) |value| try writer.print(",\"failedStepIndex\":{d}", .{value});
|
|
283
|
+
if (summary.error_name) |value| {
|
|
284
|
+
try writer.writeAll(",\"error\":");
|
|
285
|
+
try trace.writeJsonString(writer, value);
|
|
286
|
+
} else if (run_error) |err| {
|
|
287
|
+
try writer.writeAll(",\"error\":");
|
|
288
|
+
try trace.writeJsonString(writer, @errorName(err));
|
|
289
|
+
}
|
|
290
|
+
if (summary.partial_failure) |partial| {
|
|
291
|
+
try writer.writeAll(",\"partialFailure\":");
|
|
292
|
+
try trace_summary.writePartialFailureJson(writer, partial);
|
|
293
|
+
}
|
|
294
|
+
if (summary.report_path) |value| {
|
|
295
|
+
try writer.writeAll(",\"reportPath\":");
|
|
296
|
+
try trace.writeJsonString(writer, value);
|
|
297
|
+
}
|
|
298
|
+
try writeRunNextCommandsJson(writer, trace_dir);
|
|
299
|
+
try writer.writeAll("}\n");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fn writeRunNextCommandsJson(writer: anytype, trace_dir: []const u8) !void {
|
|
303
|
+
try writer.writeAll(",\"nextCommands\":[\"zmr report ");
|
|
304
|
+
try writeShellArgJsonContent(writer, trace_dir);
|
|
305
|
+
try writer.writeAll(" --out ");
|
|
306
|
+
try writeJoinedPathShellArgJsonContent(writer, trace_dir, "report.html");
|
|
307
|
+
try writer.writeAll("\",\"zmr explain ");
|
|
308
|
+
try writeShellArgJsonContent(writer, trace_dir);
|
|
309
|
+
try writer.writeAll(" --json\",\"zmr export ");
|
|
310
|
+
try writeShellArgJsonContent(writer, trace_dir);
|
|
311
|
+
try writer.writeAll(" --out ");
|
|
312
|
+
try writePathWithSuffixShellArgJsonContent(writer, trace_dir, ".zmrtrace");
|
|
313
|
+
try writer.writeAll(" --redact\"]");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn writeDoctorCheckJson(writer: anytype, check: doctor.Check) !void {
|
|
317
|
+
try writer.writeAll("{\"name\":");
|
|
318
|
+
try trace.writeJsonString(writer, check.name);
|
|
319
|
+
try writer.writeAll(",\"status\":");
|
|
320
|
+
try trace.writeJsonString(writer, @tagName(check.status));
|
|
321
|
+
if (check.error_code) |error_code| {
|
|
322
|
+
try writer.writeAll(",\"errorCode\":");
|
|
323
|
+
try trace.writeJsonString(writer, error_code);
|
|
324
|
+
}
|
|
325
|
+
try writer.writeAll(",\"detail\":");
|
|
326
|
+
try trace.writeJsonString(writer, check.detail);
|
|
327
|
+
if (check.count) |count| try writer.print(",\"count\":{d}", .{count});
|
|
328
|
+
if (check.ready_count) |ready_count| try writer.print(",\"readyCount\":{d}", .{ready_count});
|
|
329
|
+
if (check.script_count) |script_count| try writer.print(",\"scriptCount\":{d}", .{script_count});
|
|
330
|
+
if (check.script_names) |script_names| {
|
|
331
|
+
try writer.writeAll(",\"scriptNames\":[");
|
|
332
|
+
for (script_names, 0..) |script_name, index| {
|
|
333
|
+
if (index > 0) try writer.writeAll(",");
|
|
334
|
+
try trace.writeJsonString(writer, script_name);
|
|
335
|
+
}
|
|
336
|
+
try writer.writeAll("]");
|
|
337
|
+
}
|
|
338
|
+
if (check.hint) |hint| {
|
|
339
|
+
try writer.writeAll(",\"hint\":");
|
|
340
|
+
try trace.writeJsonString(writer, hint);
|
|
341
|
+
}
|
|
342
|
+
if (check.field_path) |field_path| {
|
|
343
|
+
try writer.writeAll(",\"fieldPath\":");
|
|
344
|
+
try trace.writeJsonString(writer, field_path);
|
|
345
|
+
}
|
|
346
|
+
try writer.writeAll("}");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fn writeJoinedPathJson(writer: anytype, root: []const u8, child: []const u8) !void {
|
|
350
|
+
try writer.writeAll("\"");
|
|
351
|
+
try writeJoinedPathJsonContent(writer, root, child);
|
|
352
|
+
try writer.writeAll("\"");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fn writeJoinedPathJsonContent(writer: anytype, root: []const u8, child: []const u8) !void {
|
|
356
|
+
try writeJsonStringContent(writer, root);
|
|
357
|
+
if (root.len > 0 and !std.mem.endsWith(u8, root, "/")) try writer.writeAll("/");
|
|
358
|
+
try writeJsonStringContent(writer, child);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
pub fn writeJoinedPathShellArgJsonContent(writer: anytype, root: []const u8, child: []const u8) !void {
|
|
362
|
+
if (isShellSafe(root) and isShellSafe(child)) {
|
|
363
|
+
try writeJoinedPathJsonContent(writer, root, child);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try writer.writeAll("'");
|
|
368
|
+
try writeShellQuotedJsonContent(writer, root);
|
|
369
|
+
if (root.len > 0 and !std.mem.endsWith(u8, root, "/")) try writer.writeAll("/");
|
|
370
|
+
try writeShellQuotedJsonContent(writer, child);
|
|
371
|
+
try writer.writeAll("'");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
pub fn writeShellArgJsonContent(writer: anytype, value: []const u8) !void {
|
|
375
|
+
if (isShellSafe(value)) {
|
|
376
|
+
try writeJsonStringContent(writer, value);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
try writer.writeAll("'");
|
|
381
|
+
try writeShellQuotedJsonContent(writer, value);
|
|
382
|
+
try writer.writeAll("'");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
pub fn writePathWithSuffixShellArgJsonContent(writer: anytype, value: []const u8, suffix: []const u8) !void {
|
|
386
|
+
if (isShellSafe(value) and isShellSafe(suffix)) {
|
|
387
|
+
try writeJsonStringContent(writer, value);
|
|
388
|
+
try writeJsonStringContent(writer, suffix);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
try writer.writeAll("'");
|
|
393
|
+
try writeShellQuotedJsonContent(writer, value);
|
|
394
|
+
try writeShellQuotedJsonContent(writer, suffix);
|
|
395
|
+
try writer.writeAll("'");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
pub fn writeJoinedPathShellArg(writer: anytype, root: []const u8, child: []const u8) !void {
|
|
399
|
+
if (isShellSafe(root) and isShellSafe(child)) {
|
|
400
|
+
try writer.writeAll(root);
|
|
401
|
+
if (root.len > 0 and !std.mem.endsWith(u8, root, "/")) try writer.writeAll("/");
|
|
402
|
+
try writer.writeAll(child);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try writer.writeAll("'");
|
|
407
|
+
try writeShellQuotedText(writer, root);
|
|
408
|
+
if (root.len > 0 and !std.mem.endsWith(u8, root, "/")) try writer.writeAll("/");
|
|
409
|
+
try writeShellQuotedText(writer, child);
|
|
410
|
+
try writer.writeAll("'");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
pub fn writeShellArg(writer: anytype, value: []const u8) !void {
|
|
414
|
+
if (isShellSafe(value)) {
|
|
415
|
+
try writer.writeAll(value);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
try writer.writeAll("'");
|
|
420
|
+
try writeShellQuotedText(writer, value);
|
|
421
|
+
try writer.writeAll("'");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fn writeShellQuotedJsonContent(writer: anytype, value: []const u8) !void {
|
|
425
|
+
for (value) |ch| {
|
|
426
|
+
if (ch == '\'') {
|
|
427
|
+
try writeJsonStringContent(writer, "'\\''");
|
|
428
|
+
} else {
|
|
429
|
+
try writeJsonStringContent(writer, &.{ch});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
fn writeShellQuotedText(writer: anytype, value: []const u8) !void {
|
|
435
|
+
for (value) |ch| {
|
|
436
|
+
if (ch == '\'') {
|
|
437
|
+
try writer.writeAll("'\\''");
|
|
438
|
+
} else {
|
|
439
|
+
try writer.writeAll(&.{ch});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
fn isShellSafe(value: []const u8) bool {
|
|
445
|
+
if (value.len == 0) return false;
|
|
446
|
+
for (value) |ch| {
|
|
447
|
+
switch (ch) {
|
|
448
|
+
'A'...'Z', 'a'...'z', '0'...'9', '_', '.', '/', ':', '=', '@', '%', '+', ',', '-' => {},
|
|
449
|
+
else => return false,
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
fn writeJsonStringContent(writer: anytype, value: []const u8) !void {
|
|
456
|
+
for (value) |ch| {
|
|
457
|
+
switch (ch) {
|
|
458
|
+
'"' => try writer.writeAll("\\\""),
|
|
459
|
+
'\\' => try writer.writeAll("\\\\"),
|
|
460
|
+
'\n' => try writer.writeAll("\\n"),
|
|
461
|
+
'\r' => try writer.writeAll("\\r"),
|
|
462
|
+
'\t' => try writer.writeAll("\\t"),
|
|
463
|
+
0...7, 11, 12, 14...31 => try writer.print("\\u{x:0>4}", .{ch}),
|
|
464
|
+
else => try writer.writeAll(&.{ch}),
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|