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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ZMR iOS shim selector demo",
|
|
3
|
+
"appId": "com.example.mobiletest",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "action": "launch" },
|
|
6
|
+
{
|
|
7
|
+
"action": "waitVisible",
|
|
8
|
+
"selector": { "text": "Continue" },
|
|
9
|
+
"timeoutMs": 1000
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"action": "tap",
|
|
13
|
+
"selector": { "resourceId": "continue_button" }
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"action": "typeText",
|
|
17
|
+
"selector": { "resourceId": "demo_input" },
|
|
18
|
+
"text": "hello"
|
|
19
|
+
},
|
|
20
|
+
{ "action": "hideKeyboard" },
|
|
21
|
+
{ "action": "snapshot" }
|
|
22
|
+
]
|
|
23
|
+
}
|
package/go.work
ADDED
package/npm/agents.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devClientReportCommand,
|
|
3
|
+
matrixCommand,
|
|
4
|
+
pilotGateCommand,
|
|
5
|
+
readinessCommand,
|
|
6
|
+
reliabilityCommand,
|
|
7
|
+
smokeReportCommand,
|
|
8
|
+
smokeRunCommand,
|
|
9
|
+
validateCommand,
|
|
10
|
+
} from "./commands.mjs";
|
|
11
|
+
|
|
12
|
+
export function nextStepCommands(config, { android = true, ios = true, packageScripts = false } = {}) {
|
|
13
|
+
const command = (scriptName, directCommand) => ({
|
|
14
|
+
label: scriptName,
|
|
15
|
+
command: packageScripts ? `npm run ${scriptName}` : directCommand,
|
|
16
|
+
});
|
|
17
|
+
const steps = [];
|
|
18
|
+
if (android) {
|
|
19
|
+
steps.push(command("zmr:android", config.scripts.android));
|
|
20
|
+
steps.push(command("zmr:android:report", config.scripts.androidReport));
|
|
21
|
+
if (config.scripts.androidDevClient) steps.push(command("zmr:android:dev-client", config.scripts.androidDevClient));
|
|
22
|
+
if (config.scripts.androidDevClientReport) steps.push(command("zmr:android:dev-client:report", config.scripts.androidDevClientReport));
|
|
23
|
+
steps.push(command("zmr:android:reliability", config.scripts.androidReliability));
|
|
24
|
+
}
|
|
25
|
+
if (ios) {
|
|
26
|
+
steps.push(command("zmr:ios", config.scripts.ios));
|
|
27
|
+
steps.push(command("zmr:ios:report", config.scripts.iosReport));
|
|
28
|
+
if (config.scripts.iosDevClient) steps.push(command("zmr:ios:dev-client", config.scripts.iosDevClient));
|
|
29
|
+
if (config.scripts.iosDevClientReport) steps.push(command("zmr:ios:dev-client:report", config.scripts.iosDevClientReport));
|
|
30
|
+
steps.push(command("zmr:ios:reliability", config.scripts.iosReliability));
|
|
31
|
+
}
|
|
32
|
+
if (android || ios) {
|
|
33
|
+
steps.push(command("zmr:matrix", config.scripts.matrix));
|
|
34
|
+
steps.push(command("zmr:pilot", config.scripts.pilotGate));
|
|
35
|
+
if (android && ios) steps.push(command("zmr:readiness", config.scripts.readiness));
|
|
36
|
+
}
|
|
37
|
+
steps.push(command("zmr:serve", config.scripts.serve));
|
|
38
|
+
steps.push(command("zmr:mcp", config.scripts.mcp));
|
|
39
|
+
steps.push(command("zmr:explain", config.scripts.explain));
|
|
40
|
+
steps.push(command("zmr:export", config.scripts.exportTrace));
|
|
41
|
+
steps.push(command("zmr:schemas", config.scripts.schemas));
|
|
42
|
+
steps.push(command("zmr:validate", config.scripts.validate));
|
|
43
|
+
steps.push(command("zmr:doctor", config.scripts.doctor));
|
|
44
|
+
return steps;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function agentInstructions(appId, { android = true, ios = true, packageScripts = false, scripts = {} } = {}) {
|
|
48
|
+
const command = (name, fallback) => scripts[name] ?? fallback;
|
|
49
|
+
const appCommand = (scriptName, directCommand) => packageScripts ? `npm run ${scriptName}` : directCommand;
|
|
50
|
+
const doctorCommand = appCommand("zmr:doctor", command("doctor", "zmr doctor --strict --json --config .zmr/config.json"));
|
|
51
|
+
const schemasCommand = appCommand("zmr:schemas", command("schemas", "zmr schemas --json"));
|
|
52
|
+
const validateDirectCommand = command("validate", validateCommand({ android, ios }));
|
|
53
|
+
const validateAppCommand = appCommand("zmr:validate", validateDirectCommand);
|
|
54
|
+
const serveCommand = appCommand("zmr:serve", command("serve", "zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent"));
|
|
55
|
+
const mcpCommand = appCommand("zmr:mcp", command("mcp", "zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent"));
|
|
56
|
+
const explainCommand = appCommand("zmr:explain", command("explain", "zmr explain traces/zmr-agent --json"));
|
|
57
|
+
const exportCommand = appCommand("zmr:export", command("exportTrace", "zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact"));
|
|
58
|
+
const setupChecks = [
|
|
59
|
+
doctorCommand,
|
|
60
|
+
schemasCommand,
|
|
61
|
+
validateAppCommand,
|
|
62
|
+
];
|
|
63
|
+
const directRuns = [];
|
|
64
|
+
const nextStepScripts = {
|
|
65
|
+
doctor: command("doctor", "zmr doctor --strict --json --config .zmr/config.json"),
|
|
66
|
+
schemas: command("schemas", "zmr schemas --json"),
|
|
67
|
+
matrix: command("matrix", matrixCommand()),
|
|
68
|
+
pilotGate: command("pilotGate", pilotGateCommand({ android, ios, appId })),
|
|
69
|
+
serve: command("serve", "zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent"),
|
|
70
|
+
mcp: command("mcp", "zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent"),
|
|
71
|
+
explain: command("explain", "zmr explain traces/zmr-agent --json"),
|
|
72
|
+
exportTrace: command("exportTrace", "zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact"),
|
|
73
|
+
};
|
|
74
|
+
nextStepScripts.validate = validateDirectCommand;
|
|
75
|
+
if (android) {
|
|
76
|
+
directRuns.push(appCommand("zmr:android", command("android", smokeRunCommand({ platform: "android" }))));
|
|
77
|
+
directRuns.push(appCommand("zmr:android:report", command("androidReport", smokeReportCommand({ platform: "android" }))));
|
|
78
|
+
nextStepScripts.android = command("android", smokeRunCommand({ platform: "android" }));
|
|
79
|
+
nextStepScripts.androidReport = command("androidReport", smokeReportCommand({ platform: "android" }));
|
|
80
|
+
if (scripts.androidDevClient) {
|
|
81
|
+
directRuns.push(appCommand("zmr:android:dev-client", scripts.androidDevClient));
|
|
82
|
+
directRuns.push(appCommand("zmr:android:dev-client:report", command("androidDevClientReport", scripts.androidDevClientReport ?? devClientReportCommand({ platform: "android" }))));
|
|
83
|
+
nextStepScripts.androidDevClient = command("androidDevClient", scripts.androidDevClient);
|
|
84
|
+
nextStepScripts.androidDevClientReport = command("androidDevClientReport", scripts.androidDevClientReport ?? devClientReportCommand({ platform: "android" }));
|
|
85
|
+
}
|
|
86
|
+
nextStepScripts.androidReliability = command("androidReliability", reliabilityCommand({
|
|
87
|
+
scenario: ".zmr/android-smoke.json",
|
|
88
|
+
device: "emulator-5554",
|
|
89
|
+
appId,
|
|
90
|
+
traceRoot: "traces/zmr-android-reliability",
|
|
91
|
+
maxP95Ms: 30000,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
if (ios) {
|
|
95
|
+
directRuns.push(appCommand("zmr:ios", command("ios", smokeRunCommand({ platform: "ios" }))));
|
|
96
|
+
directRuns.push(appCommand("zmr:ios:report", command("iosReport", smokeReportCommand({ platform: "ios" }))));
|
|
97
|
+
nextStepScripts.ios = command("ios", smokeRunCommand({ platform: "ios" }));
|
|
98
|
+
nextStepScripts.iosReport = command("iosReport", smokeReportCommand({ platform: "ios" }));
|
|
99
|
+
if (scripts.iosDevClient) {
|
|
100
|
+
directRuns.push(appCommand("zmr:ios:dev-client", scripts.iosDevClient));
|
|
101
|
+
directRuns.push(appCommand("zmr:ios:dev-client:report", command("iosDevClientReport", scripts.iosDevClientReport ?? devClientReportCommand({ platform: "ios" }))));
|
|
102
|
+
nextStepScripts.iosDevClient = command("iosDevClient", scripts.iosDevClient);
|
|
103
|
+
nextStepScripts.iosDevClientReport = command("iosDevClientReport", scripts.iosDevClientReport ?? devClientReportCommand({ platform: "ios" }));
|
|
104
|
+
}
|
|
105
|
+
nextStepScripts.iosReliability = command("iosReliability", reliabilityCommand({
|
|
106
|
+
scenario: ".zmr/ios-smoke.json",
|
|
107
|
+
platform: "ios",
|
|
108
|
+
device: "booted",
|
|
109
|
+
appId,
|
|
110
|
+
xcrun: "xcrun",
|
|
111
|
+
traceRoot: "traces/zmr-ios-reliability",
|
|
112
|
+
maxP95Ms: 45000,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
if (android && ios) {
|
|
116
|
+
nextStepScripts.readiness = command("readiness", readinessCommand());
|
|
117
|
+
}
|
|
118
|
+
const readinessCommandText = appCommand("zmr:readiness", command("readiness", readinessCommand()));
|
|
119
|
+
const appSectionTitle = packageScripts ? "App Scripts" : "App Commands";
|
|
120
|
+
const appSectionCommands = nextStepCommands({ scripts: nextStepScripts }, { android, ios, packageScripts })
|
|
121
|
+
.map((step) => step.command);
|
|
122
|
+
const releaseClaims = android && ios
|
|
123
|
+
? `\`\`\`bash
|
|
124
|
+
${readinessCommandText}
|
|
125
|
+
\`\`\`
|
|
126
|
+
|
|
127
|
+
Do not claim production readiness from smoke runs alone. Use \`satisfied\` for proven requirements; do not infer readiness from raw \`passed\` evidence. Use \`recommendedWording\` and keep \`claimLimitations\` intact when summarizing readiness. When readiness is blocked, follow \`nextSteps[].commands\` in order. Use \`nextSteps[].covers\` to map each command back to the blocked requirements it resolves.`
|
|
128
|
+
: "Do not claim production readiness from a single-platform setup. Enable Android and iOS, then collect the full pilot evidence matrix before running the production readiness gate.";
|
|
129
|
+
|
|
130
|
+
return `# ZMR Agent Instructions
|
|
131
|
+
|
|
132
|
+
App id: \`${appId}\`
|
|
133
|
+
|
|
134
|
+
Start from the app checkout. Keep generated scenarios and config under \`.zmr/\`, and write run output under \`traces/\`.
|
|
135
|
+
|
|
136
|
+
## Setup Checks
|
|
137
|
+
|
|
138
|
+
\`\`\`bash
|
|
139
|
+
${setupChecks.join("\n")}
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
## Interactive Agent Session
|
|
143
|
+
|
|
144
|
+
\`\`\`bash
|
|
145
|
+
${serveCommand}
|
|
146
|
+
${mcpCommand}
|
|
147
|
+
\`\`\`
|
|
148
|
+
|
|
149
|
+
Use \`semantic_snapshot\` before choosing tap or type actions. Prefer selectors from accessibility identifiers, resource ids, labels, or exact text before coordinates. Export redacted traces before sharing artifacts.
|
|
150
|
+
|
|
151
|
+
## Failure Triage
|
|
152
|
+
|
|
153
|
+
\`\`\`bash
|
|
154
|
+
${explainCommand}
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
Use the JSON explanation before editing selectors. It includes the terminal status, partial visual-capture diagnostics, and the last useful failure context.
|
|
158
|
+
|
|
159
|
+
## Trace Sharing
|
|
160
|
+
|
|
161
|
+
\`\`\`bash
|
|
162
|
+
${exportCommand}
|
|
163
|
+
\`\`\`
|
|
164
|
+
|
|
165
|
+
Add \`--omit-screenshots\` when visual artifacts may contain sensitive data.
|
|
166
|
+
|
|
167
|
+
## Smoke Runs
|
|
168
|
+
|
|
169
|
+
\`\`\`bash
|
|
170
|
+
${directRuns.join("\n")}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
## Release Claims
|
|
174
|
+
|
|
175
|
+
${releaseClaims}
|
|
176
|
+
|
|
177
|
+
## ${appSectionTitle}
|
|
178
|
+
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
${appSectionCommands.join("\n")}
|
|
181
|
+
\`\`\`
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devClientReportCommand,
|
|
3
|
+
devClientRunCommand,
|
|
4
|
+
matrixCommand,
|
|
5
|
+
pilotGateCommand,
|
|
6
|
+
readinessCommand,
|
|
7
|
+
reliabilityCommand,
|
|
8
|
+
smokeReportCommand,
|
|
9
|
+
smokeRunCommand,
|
|
10
|
+
validateCommand,
|
|
11
|
+
} from "./commands.mjs";
|
|
12
|
+
|
|
13
|
+
export function appConfig(appId, { android = true, ios = true, androidShim = "", iosShim = "", expoDevClientScheme = "" } = {}) {
|
|
14
|
+
const androidCommand = smokeRunCommand({ platform: "android", androidShim });
|
|
15
|
+
const iosCommand = smokeRunCommand({ platform: "ios", iosShim });
|
|
16
|
+
const scripts = {
|
|
17
|
+
doctor: "zmr doctor --strict --json --config .zmr/config.json",
|
|
18
|
+
schemas: "zmr schemas --json",
|
|
19
|
+
validate: validateCommand({ android, ios, expoDevClientScheme }),
|
|
20
|
+
matrix: matrixCommand(),
|
|
21
|
+
pilotGate: pilotGateCommand({ android, ios, appId, iosShim }),
|
|
22
|
+
serve: "zmr serve --transport stdio --config .zmr/config.json --trace-dir traces/zmr-agent",
|
|
23
|
+
mcp: "zmr mcp --config .zmr/config.json --trace-dir traces/zmr-agent",
|
|
24
|
+
explain: "zmr explain traces/zmr-agent --json",
|
|
25
|
+
exportTrace: "zmr export traces/zmr-agent --out traces/zmr-agent-redacted.zmrtrace --redact",
|
|
26
|
+
};
|
|
27
|
+
if (android) {
|
|
28
|
+
scripts.android = androidCommand;
|
|
29
|
+
scripts.androidReport = smokeReportCommand({ platform: "android" });
|
|
30
|
+
scripts.androidReliability = reliabilityCommand({
|
|
31
|
+
scenario: ".zmr/android-smoke.json",
|
|
32
|
+
device: "emulator-5554",
|
|
33
|
+
appId,
|
|
34
|
+
androidShim,
|
|
35
|
+
traceRoot: "traces/zmr-android-reliability",
|
|
36
|
+
maxP95Ms: 30000,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (ios) {
|
|
40
|
+
scripts.ios = iosCommand;
|
|
41
|
+
scripts.iosReport = smokeReportCommand({ platform: "ios" });
|
|
42
|
+
scripts.iosReliability = reliabilityCommand({
|
|
43
|
+
scenario: ".zmr/ios-smoke.json",
|
|
44
|
+
platform: "ios",
|
|
45
|
+
device: "booted",
|
|
46
|
+
appId,
|
|
47
|
+
xcrun: "xcrun",
|
|
48
|
+
iosShim,
|
|
49
|
+
traceRoot: "traces/zmr-ios-reliability",
|
|
50
|
+
maxP95Ms: 45000,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (expoDevClientScheme) {
|
|
54
|
+
if (android) {
|
|
55
|
+
scripts.androidDevClient = devClientRunCommand({ platform: "android" });
|
|
56
|
+
scripts.androidDevClientReport = devClientReportCommand({ platform: "android" });
|
|
57
|
+
}
|
|
58
|
+
if (ios) {
|
|
59
|
+
scripts.iosDevClient = devClientRunCommand({ platform: "ios" });
|
|
60
|
+
scripts.iosDevClientReport = devClientReportCommand({ platform: "ios" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (android && ios) {
|
|
64
|
+
scripts.readiness = readinessCommand();
|
|
65
|
+
}
|
|
66
|
+
const config = {
|
|
67
|
+
schemaVersion: 1,
|
|
68
|
+
appId,
|
|
69
|
+
android: {
|
|
70
|
+
enabled: android,
|
|
71
|
+
defaultDevice: "emulator-5554",
|
|
72
|
+
smokeScenario: ".zmr/android-smoke.json",
|
|
73
|
+
traceDir: "traces/zmr-android",
|
|
74
|
+
},
|
|
75
|
+
ios: {
|
|
76
|
+
enabled: ios,
|
|
77
|
+
defaultDevice: "booted",
|
|
78
|
+
smokeScenario: ".zmr/ios-smoke.json",
|
|
79
|
+
traceDir: "traces/zmr-ios",
|
|
80
|
+
},
|
|
81
|
+
artifacts: {
|
|
82
|
+
screenshots: true,
|
|
83
|
+
hierarchy: true,
|
|
84
|
+
logs: true,
|
|
85
|
+
screenRecording: false,
|
|
86
|
+
},
|
|
87
|
+
scripts,
|
|
88
|
+
};
|
|
89
|
+
if (androidShim || iosShim) {
|
|
90
|
+
config.tools = {};
|
|
91
|
+
if (androidShim) config.tools.androidShimPath = androidShim;
|
|
92
|
+
if (iosShim) config.tools.iosShimPath = iosShim;
|
|
93
|
+
}
|
|
94
|
+
return config;
|
|
95
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
const out = path.join(root, "zig-out", "bin", process.platform === "win32" ? "zmr.exe" : "zmr");
|
|
9
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
10
|
+
|
|
11
|
+
const args = ["build-exe", "src/main.zig", "-O", "ReleaseSafe", `-femit-bin=${out}`];
|
|
12
|
+
if (process.platform === "darwin" && process.arch === "arm64") {
|
|
13
|
+
args.splice(2, 0, "-target", "aarch64-macos.15.0");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = spawnSync("zig", args, { cwd: root, stdio: "inherit" });
|
|
17
|
+
if (result.error) {
|
|
18
|
+
console.error(result.error.message);
|
|
19
|
+
process.exit(127);
|
|
20
|
+
}
|
|
21
|
+
process.exit(result.status ?? 1);
|
package/npm/commands.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export function smokeRunCommand({ platform, androidShim = "", iosShim = "" }) {
|
|
2
|
+
if (platform === "android") {
|
|
3
|
+
const args = ["zmr", "run", ".zmr/android-smoke.json", "--device", "emulator-5554", "--trace-dir", "traces/zmr-android"];
|
|
4
|
+
if (androidShim) args.push("--android-shim", androidShim);
|
|
5
|
+
return shellJoin(args);
|
|
6
|
+
}
|
|
7
|
+
if (platform === "ios") {
|
|
8
|
+
const args = ["zmr", "run", ".zmr/ios-smoke.json", "--platform", "ios", "--device", "booted", "--trace-dir", "traces/zmr-ios"];
|
|
9
|
+
if (iosShim) args.push("--ios-shim", iosShim);
|
|
10
|
+
return shellJoin(args);
|
|
11
|
+
}
|
|
12
|
+
throw new Error(`unsupported smoke run platform: ${platform}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function smokeReportCommand({ platform }) {
|
|
16
|
+
if (platform === "android") return "zmr report traces/zmr-android --out traces/zmr-android/report.html";
|
|
17
|
+
if (platform === "ios") return "zmr report traces/zmr-ios --out traces/zmr-ios/report.html";
|
|
18
|
+
throw new Error(`unsupported smoke report platform: ${platform}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateCommand({ android = true, ios = true, expoDevClientScheme = "" } = {}) {
|
|
22
|
+
const scenarios = [];
|
|
23
|
+
if (android) scenarios.push(".zmr/android-smoke.json");
|
|
24
|
+
if (ios) scenarios.push(".zmr/ios-smoke.json");
|
|
25
|
+
if (expoDevClientScheme && android) scenarios.push(".zmr/android-dev-client-smoke.json");
|
|
26
|
+
if (expoDevClientScheme && ios) scenarios.push(".zmr/ios-dev-client-open-link.json");
|
|
27
|
+
return scenarios.map((scenario) => shellJoin(["zmr", "validate", "--json", scenario])).join(" && ");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function pilotGateCommand({ android, ios, appId, iosShim = "" }) {
|
|
31
|
+
const args = ["zmr-pilot-gate"];
|
|
32
|
+
if (android) args.push("--android");
|
|
33
|
+
if (ios) args.push("--ios");
|
|
34
|
+
if (android) args.push("--android-app-root", ".", "--android-app-id", appId, "--android-device", "emulator-5554");
|
|
35
|
+
if (ios) {
|
|
36
|
+
args.push("--ios-app-root", ".", "--ios-app-path", "./build/Debug-iphonesimulator/Sample.app", "--ios-app-id", appId, "--ios-device", "booted");
|
|
37
|
+
if (iosShim) args.push("--ios-shim", iosShim);
|
|
38
|
+
}
|
|
39
|
+
args.push("--runs", "20", "--min-pass-rate", "100", "--max-failures", "0", "--evidence-out", "traces/zmr-pilots/evidence.jsonl");
|
|
40
|
+
return shellJoin(args);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function matrixCommand() {
|
|
44
|
+
return "ZMR_BIN=${ZMR_BIN:-zmr} zmr-device-matrix --matrix .zmr/device-matrix.json --trace-root traces/zmr-matrix --min-pass-rate 100 --max-failures 0";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readinessCommand() {
|
|
48
|
+
return "zmr-release-readiness --evidence traces/zmr-pilots/evidence.jsonl --target production --json";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function reliabilityCommand({ scenario, platform = "", device, appId, xcrun = "", androidShim = "", iosShim = "", traceRoot, maxP95Ms }) {
|
|
52
|
+
const args = [
|
|
53
|
+
"zmr-benchmark",
|
|
54
|
+
"--zmr",
|
|
55
|
+
scenario,
|
|
56
|
+
];
|
|
57
|
+
if (platform) args.push("--platform", platform);
|
|
58
|
+
args.push("--device", device, "--app-id", appId);
|
|
59
|
+
if (xcrun) args.push("--xcrun", xcrun);
|
|
60
|
+
if (androidShim) args.push("--android-shim", androidShim);
|
|
61
|
+
if (iosShim) args.push("--ios-shim", iosShim);
|
|
62
|
+
args.push(
|
|
63
|
+
"--runs",
|
|
64
|
+
"20",
|
|
65
|
+
"--trace-root",
|
|
66
|
+
traceRoot,
|
|
67
|
+
"--min-pass-rate",
|
|
68
|
+
"100",
|
|
69
|
+
"--max-failures",
|
|
70
|
+
"0",
|
|
71
|
+
"--max-p95-ms",
|
|
72
|
+
String(maxP95Ms),
|
|
73
|
+
);
|
|
74
|
+
return `export ZMR_BIN="\${ZMR_BIN:-zmr}"; ${shellJoin(args)} && "$ZMR_BIN" report ${shellQuote(traceRoot)} --out ${shellQuote(`${traceRoot}/report.html`)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function devClientRunCommand({ platform }) {
|
|
78
|
+
if (platform === "android") {
|
|
79
|
+
return "zmr run .zmr/android-dev-client-smoke.json --device emulator-5554 --trace-dir traces/zmr-android-dev-client";
|
|
80
|
+
}
|
|
81
|
+
if (platform === "ios") {
|
|
82
|
+
return "zmr run .zmr/ios-dev-client-open-link.json --platform ios --device booted --trace-dir traces/zmr-ios-dev-client";
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`unsupported dev-client platform: ${platform}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function devClientReportCommand({ platform }) {
|
|
88
|
+
if (platform === "android") return "zmr report traces/zmr-android-dev-client --out traces/zmr-android-dev-client/report.html";
|
|
89
|
+
if (platform === "ios") return "zmr report traces/zmr-ios-dev-client --out traces/zmr-ios-dev-client/report.html";
|
|
90
|
+
throw new Error(`unsupported dev-client report platform: ${platform}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function shellJoin(args) {
|
|
94
|
+
return args.map((arg, index) => {
|
|
95
|
+
if (index === 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(arg)) return arg;
|
|
96
|
+
return shellQuote(arg);
|
|
97
|
+
}).join(" ");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function shellQuote(value) {
|
|
101
|
+
const text = String(value);
|
|
102
|
+
if (/^[A-Za-z0-9_./:=@%+,-]+$/.test(text)) return text;
|
|
103
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
104
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { applyPackageScripts } from "./package-scripts.mjs";
|
|
4
|
+
|
|
5
|
+
export function ensureTraceIgnore(root, { cwd = process.cwd() } = {}) {
|
|
6
|
+
const file = path.join(root, ".gitignore");
|
|
7
|
+
const existing = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
8
|
+
if (/^traces\/$/m.test(existing)) return null;
|
|
9
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
10
|
+
fs.writeFileSync(file, `${existing}${prefix}${existing.length > 0 ? "\n" : ""}# ZMR local run artifacts\ntraces/\n`);
|
|
11
|
+
return path.relative(cwd, file);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function writeJsonFile(file, value, { overwrite = false, cwd = process.cwd() } = {}) {
|
|
15
|
+
return writeGeneratedFile(file, `${JSON.stringify(value, null, 2)}\n`, { overwrite, cwd });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function writeTextFile(file, value, { overwrite = false, cwd = process.cwd() } = {}) {
|
|
19
|
+
return writeGeneratedFile(file, value, { overwrite, cwd });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function writeScaffoldFiles(root, files, { cwd = process.cwd() } = {}) {
|
|
23
|
+
const results = [];
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const fullPath = path.join(root, file.path);
|
|
26
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
27
|
+
const result = file.kind === "json"
|
|
28
|
+
? writeJsonFile(fullPath, file.value, { overwrite: file.overwrite, cwd })
|
|
29
|
+
: writeTextFile(fullPath, file.value, { overwrite: file.overwrite, cwd });
|
|
30
|
+
if (result) results.push(result);
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function writePackageScripts(root, config, { android = true, ios = true, cwd = process.cwd() } = {}) {
|
|
36
|
+
const file = path.join(root, "package.json");
|
|
37
|
+
const pkg = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {};
|
|
38
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
39
|
+
return writeJsonFile(file, applyPackageScripts(pkg, config, { android, ios }), { overwrite: true, cwd });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeGeneratedFile(file, value, { overwrite = false, cwd = process.cwd() } = {}) {
|
|
43
|
+
const existed = fs.existsSync(file);
|
|
44
|
+
if (existed && !overwrite) return null;
|
|
45
|
+
fs.writeFileSync(file, value);
|
|
46
|
+
return {
|
|
47
|
+
path: path.relative(cwd, file),
|
|
48
|
+
status: existed ? "updated" : "created",
|
|
49
|
+
};
|
|
50
|
+
}
|
package/npm/index.mjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
|
|
9
|
+
export function rootDir() {
|
|
10
|
+
return packageRoot;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveBinary(env = process.env, platform = process.platform, arch = process.arch) {
|
|
14
|
+
if (env.ZMR_BIN) return env.ZMR_BIN;
|
|
15
|
+
|
|
16
|
+
const exe = platform === "win32" ? "zmr.exe" : "zmr";
|
|
17
|
+
const candidates = [
|
|
18
|
+
path.join(packageRoot, "prebuilds", `${platform}-${arch}`, exe),
|
|
19
|
+
path.join(packageRoot, "zig-out", "bin", exe),
|
|
20
|
+
path.join(packageRoot, "dist", `zmr-${platform}-${arch}`, exe),
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
if (isExecutable(candidate)) return candidate;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function spawnZmr(args = [], options = {}) {
|
|
31
|
+
const binary = resolveBinary(options.env ?? process.env);
|
|
32
|
+
if (!binary) {
|
|
33
|
+
throw new Error(missingBinaryMessage());
|
|
34
|
+
}
|
|
35
|
+
return spawn(binary, args, {
|
|
36
|
+
stdio: options.stdio ?? "inherit",
|
|
37
|
+
cwd: options.cwd,
|
|
38
|
+
env: options.env,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function runZmr(args = [], options = {}) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let child;
|
|
45
|
+
try {
|
|
46
|
+
child = spawnZmr(args, options);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
reject(error);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
child.on("error", reject);
|
|
52
|
+
child.on("exit", (code, signal) => {
|
|
53
|
+
if (code === 0) resolve({ code, signal });
|
|
54
|
+
else reject(new Error(`zmr exited with ${signal ?? code}`));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function missingBinaryMessage() {
|
|
60
|
+
return [
|
|
61
|
+
"Could not find a zmr binary.",
|
|
62
|
+
"Set ZMR_BIN=/path/to/zmr, run `npm run build:zmr`, or install a release package that includes prebuilt binaries.",
|
|
63
|
+
`Package root: ${packageRoot}`,
|
|
64
|
+
`Host: ${os.platform()} ${os.arch()}`,
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isExecutable(candidate) {
|
|
69
|
+
try {
|
|
70
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
package/npm/init-app.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
appInitOutput,
|
|
5
|
+
ensureTraceIgnore,
|
|
6
|
+
nextStepCommands,
|
|
7
|
+
packageScripts,
|
|
8
|
+
parseScaffoldArgs,
|
|
9
|
+
scaffoldPlan,
|
|
10
|
+
writePackageScripts,
|
|
11
|
+
writeScaffoldFiles,
|
|
12
|
+
} from "./scaffold.mjs";
|
|
13
|
+
|
|
14
|
+
const options = parseArgs(process.argv.slice(2));
|
|
15
|
+
const {
|
|
16
|
+
dir,
|
|
17
|
+
appId,
|
|
18
|
+
android,
|
|
19
|
+
androidShim,
|
|
20
|
+
ios,
|
|
21
|
+
iosShim,
|
|
22
|
+
expoDevClientScheme,
|
|
23
|
+
packageJson,
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
if (!appId) {
|
|
27
|
+
console.error("--app-id cannot be empty");
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const targetDir = path.resolve(dir, ".zmr");
|
|
32
|
+
const appRoot = path.resolve(dir);
|
|
33
|
+
const plan = scaffoldPlan(appId, { android, ios, androidShim, iosShim, expoDevClientScheme, packageScripts: packageJson });
|
|
34
|
+
const { config, files } = plan;
|
|
35
|
+
writeScaffoldFiles(targetDir, files);
|
|
36
|
+
ensureTraceIgnore(appRoot);
|
|
37
|
+
if (packageJson) {
|
|
38
|
+
const result = writePackageScripts(appRoot, config, { android, ios, cwd: appRoot });
|
|
39
|
+
if (result && !options.json) console.log(`${result.status} ${result.path}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options.json) {
|
|
43
|
+
process.stdout.write(`${JSON.stringify(appInitOutput(appRoot, appId, plan, { packageScripts: packageJson }))}\n`);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`created ${path.relative(appRoot, targetDir)}`);
|
|
48
|
+
console.log("");
|
|
49
|
+
console.log("Next steps");
|
|
50
|
+
printNextSteps(config.scripts, { packageScripts: packageJson });
|
|
51
|
+
console.log("");
|
|
52
|
+
if (!packageJson) {
|
|
53
|
+
console.log("Add scripts like:");
|
|
54
|
+
console.log(JSON.stringify(packageScripts(config), null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function usage() {
|
|
58
|
+
console.log("Usage: zmr-init [--dir <app-root>] [--app-id <bundle-or-application-id>] [--android] [--android-shim <path>] [--ios] [--ios-shim <path>] [--expo-dev-client-scheme <scheme>] [--package-json] [--json]");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseArgs(args) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = parseScaffoldArgs(args, { packageJson: true });
|
|
64
|
+
if (parsed.help) {
|
|
65
|
+
usage();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(error.message);
|
|
71
|
+
usage();
|
|
72
|
+
process.exit(2);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function printNextSteps(scripts, { packageScripts: usePackageScripts = false } = {}) {
|
|
77
|
+
for (const step of nextStepCommands({ scripts }, { android, ios, packageScripts: usePackageScripts })) {
|
|
78
|
+
console.log(` ${step.command}`);
|
|
79
|
+
}
|
|
80
|
+
}
|