agent-device 0.13.2 → 0.14.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/README.md +68 -63
- package/android-snapshot-helper/README.md +75 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.0.apk +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.0.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.0.manifest.json +17 -0
- package/bin/agent-device.mjs +6 -2
- package/dist/src/113.js +1 -1
- package/dist/src/1974.js +2 -2
- package/dist/src/221.js +4 -0
- package/dist/src/2301.js +1 -1
- package/dist/src/3918.js +29 -29
- package/dist/src/7847.js +1 -1
- package/dist/src/8161.js +3 -0
- package/dist/src/8656.js +1 -1
- package/dist/src/9152.js +1 -1
- package/dist/src/940.js +1 -0
- package/dist/src/9542.js +2 -2
- package/dist/src/9818.js +1 -1
- package/dist/src/989.js +1 -1
- package/dist/src/android-snapshot-helper.d.ts +181 -0
- package/dist/src/android-snapshot-helper.js +1 -0
- package/dist/src/index.d.ts +204 -1942
- package/dist/src/index.js +1 -1
- package/dist/src/internal/bin.js +440 -0
- package/dist/src/internal/companion-tunnel.js +1 -0
- package/dist/src/internal/daemon.js +45 -0
- package/dist/src/internal/update-check-entry.js +1 -0
- package/dist/src/metro.d.ts +5 -3
- package/dist/src/selectors.js +1 -1
- package/package.json +28 -24
- package/skills/agent-device/SKILL.md +20 -62
- package/skills/dogfood/SKILL.md +9 -168
- package/skills/react-devtools/SKILL.md +15 -31
- package/dist/src/4993.js +0 -1
- package/dist/src/5721.js +0 -1
- package/dist/src/7166.js +0 -1
- package/dist/src/8564.js +0 -3
- package/dist/src/9076.js +0 -1
- package/dist/src/backend.d.ts +0 -527
- package/dist/src/backend.js +0 -1
- package/dist/src/bin.js +0 -105
- package/dist/src/commands/index.d.ts +0 -1883
- package/dist/src/commands/index.js +0 -1
- package/dist/src/daemon.js +0 -43
- package/dist/src/metro-companion.js +0 -1
- package/dist/src/observability.d.ts +0 -91
- package/dist/src/observability.js +0 -1
- package/dist/src/testing/conformance.d.ts +0 -753
- package/dist/src/testing/conformance.js +0 -1
- package/dist/src/update-check-entry.js +0 -1
- package/skills/agent-device/references/bootstrap-install.md +0 -244
- package/skills/agent-device/references/coordinate-system.md +0 -28
- package/skills/agent-device/references/debugging.md +0 -138
- package/skills/agent-device/references/exploration.md +0 -362
- package/skills/agent-device/references/macos-desktop.md +0 -88
- package/skills/agent-device/references/remote-tenancy.md +0 -188
- package/skills/agent-device/references/verification.md +0 -134
- package/skills/dogfood/references/issue-taxonomy.md +0 -83
- package/skills/dogfood/templates/dogfood-report-template.md +0 -52
- package/skills/react-devtools/references/commands.md +0 -91
- package/skills/react-devtools/references/profiling.md +0 -74
- /package/dist/src/{bin.d.ts → internal/bin.d.ts} +0 -0
- /package/dist/src/{daemon.d.ts → internal/companion-tunnel.d.ts} +0 -0
- /package/dist/src/{metro-companion.d.ts → internal/daemon.d.ts} +0 -0
- /package/dist/src/{update-check-entry.d.ts → internal/update-check-entry.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<a href="https://www.callstack.com/open-source?utm_campaign=generic&utm_source=github&utm_medium=referral&utm_content=agent-device" align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<img alt="agent-device" src="website/docs/public/agent-device-banner.jpg">
|
|
3
|
+
<img alt="agent-device: device automation CLI for AI agents" src="website/docs/public/agent-device-banner.jpg">
|
|
4
4
|
</picture>
|
|
5
5
|
</a>
|
|
6
6
|
|
|
@@ -8,104 +8,109 @@
|
|
|
8
8
|
|
|
9
9
|
# agent-device
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[](https://www.npmjs.com/package/agent-device)
|
|
12
|
+
[](https://github.com/callstackincubator/agent-device/actions/workflows/ci.yml)
|
|
13
|
+
[](LICENSE)
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Device automation CLI for AI agents. Mobile, TV, and desktop apps.
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
`agent-device` lets coding agents run real apps, inspect UI state, interact with visible elements, and collect debugging evidence through one CLI.
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
It is built around token-efficient accessibility snapshots, not pixel-first screenshots. Agents read compact UI trees, locate elements through refs like `@e3`, perform touch and text actions, and capture screenshots, video, logs, network, perf, and React profiles only when evidence is needed.
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
- Keep automation flows token-efficient enough for real agent loops.
|
|
21
|
-
- Make common interactions reliable enough for repeated automation runs.
|
|
22
|
-
- Make debugging evidence easy to collect through logs, network inspection, and performance snapshots.
|
|
23
|
-
- Keep automation grounded in sessions, selectors, and replayable flows instead of one-off scripts.
|
|
21
|
+
## Agentic QA And Development
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
- **Quality Assurance**: dogfood flows, validate PR builds, check accessibility coverage, capture evidence, and turn stable explorations into `.ad` e2e tests.
|
|
24
|
+
- **Development**: build from specs, reproduce crashes and support issues, inspect logs/network/perf data, and iterate until the UI matches the work.
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
- Snapshots: inspect the current accessibility tree in a compact form and get current-screen refs for exploration.
|
|
29
|
-
- Refs vs selectors: use refs for discovery, use selectors for durable replay and assertions.
|
|
30
|
-
- Observability: collect session logs, inspect recent HTTP traffic with `network dump`, and sample CPU/memory with `perf`.
|
|
31
|
-
- Tests: run deterministic `.ad` scripts as a light e2e test suite.
|
|
32
|
-
- Replay scripts: save `.ad` flows with `--save-script`, replay one script with `replay`, or run a folder/glob as a serial suite with `test`.
|
|
33
|
-
`test` supports metadata-aware retries up to 3 additional attempts, per-test timeouts, flaky pass reporting, and runner-managed artifacts under `.agent-device/test-artifacts` by default. Each attempt writes `replay.ad` and `result.txt`; failed attempts also keep copied logs and artifacts when available.
|
|
34
|
-
- Human docs vs agent skills: docs explain the system for people; skills provide compact operating guidance for agents.
|
|
26
|
+
If you know Vercel's [agent-browser](https://github.com/vercel-labs/agent-browser), this is the same idea for apps and devices.
|
|
35
27
|
|
|
36
|
-
|
|
28
|
+

|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
## Quick Start
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
Install the CLI first:
|
|
41
33
|
|
|
42
34
|
```bash
|
|
43
|
-
agent-device
|
|
44
|
-
agent-device
|
|
45
|
-
agent-device
|
|
46
|
-
agent-device react-devtools profile stop
|
|
47
|
-
agent-device react-devtools profile slow --limit 5
|
|
35
|
+
npm install -g agent-device
|
|
36
|
+
agent-device --version
|
|
37
|
+
agent-device help workflow
|
|
48
38
|
```
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
The CLI help is the source of truth for agents and is shipped with the installed version. Skills are optional but recommended when your agent runtime supports them: they auto-route device, React DevTools, and dogfood tasks to the right `agent-device help <topic>` page and verify the CLI is new enough before acting.
|
|
51
41
|
|
|
52
|
-
|
|
42
|
+
If you install skills separately, keep the CLI on `agent-device >= 0.13.4`. Older CLIs do not include the workflow help topics that the router skills expect.
|
|
53
43
|
|
|
54
|
-
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g agent-device@latest
|
|
46
|
+
agent-device --version
|
|
47
|
+
agent-device help
|
|
48
|
+
```
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
`agent-device` performs a lightweight background upgrade check for interactive CLI runs and, when a newer package is available, suggests a global reinstall command. Updating the package also refreshes the bundled `skills/` shipped with the CLI.
|
|
57
51
|
|
|
58
|
-
|
|
52
|
+
Prerequisites: Node.js 22+, Xcode for iOS/tvOS/macOS targets, Android SDK + ADB for Android, and macOS Accessibility permission for desktop automation. See [Installation](https://incubator.callstack.com/agent-device/docs/installation).
|
|
53
|
+
|
|
54
|
+
Try the loop.
|
|
59
55
|
|
|
60
56
|
```bash
|
|
57
|
+
# Find the app.
|
|
61
58
|
agent-device apps --platform ios
|
|
59
|
+
|
|
60
|
+
# Start a session.
|
|
62
61
|
agent-device open SampleApp --platform ios
|
|
62
|
+
|
|
63
|
+
# Inspect the current screen. -i returns interactive elements only.
|
|
63
64
|
agent-device snapshot -i
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
# @e1 [heading] "Settings"
|
|
66
|
+
# @e2 [button] "Sign In"
|
|
67
|
+
# @e3 [text-field] "Email"
|
|
68
|
+
|
|
69
|
+
# Act, capture a screenshot, and close.
|
|
70
|
+
agent-device fill @e3 "test"
|
|
71
|
+
agent-device screenshot ./artifacts/settings.png
|
|
69
72
|
agent-device close
|
|
70
73
|
```
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
Snapshots assign refs like `@e1`, `@e2`, and `@e3` to current-screen elements. Refs from the default snapshot are immediately actionable; for hidden content, scroll and re-snapshot.
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
2. `open` a target app or URL.
|
|
76
|
-
3. `snapshot -i` to inspect the current screen.
|
|
77
|
-
4. `press`, `fill`, `scroll`, `get`, or `wait` using refs or selectors. On iOS and Android, default snapshot text follows the same visible-first contract: refs shown in default output are actionable now, while hidden content is surfaced as scroll/list discovery hints instead of tappable off-screen refs. If the target only appears in a hidden-content hint, use `scroll <direction>` and re-snapshot.
|
|
78
|
-
Use `rotate <orientation>` when a flow needs a deterministic portrait or landscape state on mobile targets.
|
|
79
|
-
5. `diff snapshot` or re-snapshot after UI changes.
|
|
80
|
-
6. `close` when the session is finished.
|
|
77
|
+
## Where To Run agent-device
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
| Path | Best for | Start with |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| Local | Exploration, debugging, and development loops on simulators, emulators, physical devices, macOS apps, and Linux desktop targets. | Follow the Quick Start. |
|
|
82
|
+
| CI/CD | Automated PR and merge validation with replay scripts and captured artifacts. | Start with the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml). GitHub Actions template coming soon. |
|
|
83
|
+
| Cloud | Linux runners, managed devices, and remote execution. | Use [Agent Device Cloud](https://agent-device.dev/cloud) or [contact Callstack](mailto:hello@callstack.com) for team-scale QA. |
|
|
83
84
|
|
|
84
|
-
##
|
|
85
|
+
## Capabilities
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
- **Platforms**: iOS, Android, tvOS, Android TV, macOS, and Linux. Real devices and simulators are supported.
|
|
88
|
+
- **Capture**: screenshots, video, logs, network traffic, performance data, accessibility snapshots, and React render profiles.
|
|
89
|
+
- **Produce**: replayable `.ad` scripts (recorded replay files that run locally or in CI), e2e test runs, snapshot and screenshot diffs, and debugging artifacts.
|
|
90
|
+
- **React Native and Expo**: component tree inspection, props/state/hooks, and render profiling.
|
|
91
|
+
- **License**: MIT. Free to use.
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
- [Docs](https://incubator.callstack.com/agent-device/docs/introduction)
|
|
93
|
+
## How It Works
|
|
90
94
|
|
|
91
|
-
|
|
95
|
+
`agent-device` runs session-aware commands through platform backends: XCTest for iOS and tvOS, ADB plus the Android snapshot helper for Android, a local helper for macOS desktop automation, and AT-SPI for Linux desktop targets. See [Introduction](https://incubator.callstack.com/agent-device/docs/introduction) and [Commands](https://incubator.callstack.com/agent-device/docs/commands) for platform details.
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
- [react-devtools skill](skills/react-devtools/SKILL.md)
|
|
95
|
-
- [dogfood skill](skills/dogfood/SKILL.md)
|
|
96
|
-
- [agent-device skill on ClawHub](https://clawhub.ai/okwasniewski/agent-device)
|
|
97
|
+
## Used By
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
Used by teams and developers at Callstack, Expensify, Shopify, Kindred, Total Wine & More, LegendList, HerLyfe, App & Flow, and more.
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
npm install -g agent-device
|
|
102
|
-
```
|
|
101
|
+
## Documentation
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
- [Installation](https://incubator.callstack.com/agent-device/docs/installation)
|
|
104
|
+
- [Commands](https://incubator.callstack.com/agent-device/docs/commands)
|
|
105
|
+
- [Replay & E2E](https://incubator.callstack.com/agent-device/docs/replay-e2e)
|
|
106
|
+
- [Known limitations](https://incubator.callstack.com/agent-device/docs/known-limitations)
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
Agent integration:
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
- [agent-device skill](skills/agent-device/SKILL.md)
|
|
111
|
+
- [react-devtools skill](skills/react-devtools/SKILL.md)
|
|
112
|
+
- [dogfood skill](skills/dogfood/SKILL.md)
|
|
113
|
+
- [agent-device skill on ClawHub](https://clawhub.ai/okwasniewski/agent-device)
|
|
109
114
|
|
|
110
115
|
## Contributing
|
|
111
116
|
|
|
@@ -113,4 +118,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
113
118
|
|
|
114
119
|
## Made at Callstack
|
|
115
120
|
|
|
116
|
-
agent-device is
|
|
121
|
+
agent-device is open source and MIT licensed. Try the [EAS workflow template](https://github.com/callstackincubator/eas-agent-device/blob/main/.eas/workflows/agent-qa-mobile.yml), use [Agent Device Cloud](https://agent-device.dev/cloud), or contact us at hello@callstack.com.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Android Snapshot Helper
|
|
2
|
+
|
|
3
|
+
Small instrumentation APK used to capture Android accessibility snapshots without relying on
|
|
4
|
+
`uiautomator dump`'s fixed idle wait behavior. The helper enables Android's interactive-window
|
|
5
|
+
retrieval flag and serializes every accessible window root returned by `UiAutomation.getWindows()`
|
|
6
|
+
so keyboards and system overlays can appear in the same snapshot. If interactive window roots are
|
|
7
|
+
unavailable, it falls back to the active-window root.
|
|
8
|
+
|
|
9
|
+
The helper is intentionally provider-neutral. Local `adb`, cloud ADB tunnels, and remote device
|
|
10
|
+
providers can all install and run the same APK as long as they can execute ADB-style operations.
|
|
11
|
+
Released helper APKs use the committed `debug.keystore`; do not rotate it casually, because Android
|
|
12
|
+
requires a stable signing certificate for `adb install -r` upgrades.
|
|
13
|
+
|
|
14
|
+
## Build
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
sh ./scripts/build-android-snapshot-helper.sh 0.13.3 .tmp/android-snapshot-helper
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The build uses Android SDK command-line tools directly. It expects `ANDROID_HOME` or
|
|
21
|
+
`ANDROID_SDK_ROOT` to point at an SDK with `platforms/android-36` and matching build tools.
|
|
22
|
+
`pnpm prepack` builds the npm-bundled helper into `android-snapshot-helper/dist`; npm users get
|
|
23
|
+
that APK in the package and the first helper-backed `snapshot` installs it automatically when
|
|
24
|
+
missing or outdated.
|
|
25
|
+
|
|
26
|
+
## Run
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
adb install -r -t .tmp/android-snapshot-helper/agent-device-android-snapshot-helper-0.13.3.apk
|
|
30
|
+
adb shell am instrument -w \
|
|
31
|
+
-e waitForIdleTimeoutMs 500 \
|
|
32
|
+
-e timeoutMs 8000 \
|
|
33
|
+
-e maxDepth 128 \
|
|
34
|
+
-e maxNodes 5000 \
|
|
35
|
+
com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`maxDepth` also caps recursive traversal depth inside the helper.
|
|
39
|
+
The `-t` install flag is required because the helper is a debuggable instrumentation/test APK.
|
|
40
|
+
Devices or providers that block test-package installs must allow this package before helper capture
|
|
41
|
+
can run.
|
|
42
|
+
|
|
43
|
+
## Output Contract
|
|
44
|
+
|
|
45
|
+
The APK emits instrumentation status records using
|
|
46
|
+
`agentDeviceProtocol=android-snapshot-helper-v1`.
|
|
47
|
+
|
|
48
|
+
Each XML chunk is sent with:
|
|
49
|
+
|
|
50
|
+
- `outputFormat=uiautomator-xml`
|
|
51
|
+
- `chunkIndex`
|
|
52
|
+
- `chunkCount`
|
|
53
|
+
- `payloadBase64`
|
|
54
|
+
|
|
55
|
+
The final instrumentation result includes:
|
|
56
|
+
|
|
57
|
+
- `ok=true`
|
|
58
|
+
- `helperApiVersion=1`
|
|
59
|
+
- `waitForIdleTimeoutMs`
|
|
60
|
+
- `timeoutMs`
|
|
61
|
+
- `maxDepth`
|
|
62
|
+
- `maxNodes`
|
|
63
|
+
- `rootPresent`
|
|
64
|
+
- `captureMode` (`interactive-windows` or `active-window`)
|
|
65
|
+
- `windowCount`
|
|
66
|
+
- `nodeCount`
|
|
67
|
+
- `truncated`
|
|
68
|
+
- `elapsedMs`
|
|
69
|
+
|
|
70
|
+
Failures return `ok=false`, `errorType`, and `message` in the final result.
|
|
71
|
+
|
|
72
|
+
The release manifest is a stable provider contract for the current helper protocol. Providers should
|
|
73
|
+
resolve the APK from `apkUrl`, verify `sha256`, install using `installArgs`, and run
|
|
74
|
+
`instrumentationRunner`. `installArgs` must start with `install`; extra arguments are limited to the
|
|
75
|
+
allowlisted adb install flags `-r`, `-t`, `-d`, and `-g`, and the consumer appends the APK path.
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0ecde3a1b5ca159a5921cc66ccff3741bfe316a54f2f1ecc91e9d013312a38b0 agent-device-android-snapshot-helper-0.14.0.apk
|
package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.14.0.manifest.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "android-snapshot-helper",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"releaseTag": "v0.14.0",
|
|
5
|
+
"assetName": "agent-device-android-snapshot-helper-0.14.0.apk",
|
|
6
|
+
"apkUrl": null,
|
|
7
|
+
"sha256": "0ecde3a1b5ca159a5921cc66ccff3741bfe316a54f2f1ecc91e9d013312a38b0",
|
|
8
|
+
"checksumName": "agent-device-android-snapshot-helper-0.14.0.apk.sha256",
|
|
9
|
+
"packageName": "com.callstack.agentdevice.snapshothelper",
|
|
10
|
+
"versionCode": 14000,
|
|
11
|
+
"instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
|
|
12
|
+
"minSdk": 23,
|
|
13
|
+
"targetSdk": 36,
|
|
14
|
+
"outputFormat": "uiautomator-xml",
|
|
15
|
+
"statusProtocol": "android-snapshot-helper-v1",
|
|
16
|
+
"installArgs": ["install", "-r", "-t"]
|
|
17
|
+
}
|
package/bin/agent-device.mjs
CHANGED
|
@@ -4,9 +4,13 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
|
|
6
6
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const
|
|
7
|
+
const distPaths = [
|
|
8
|
+
join(here, '..', 'dist', 'src', 'internal', 'bin.js'),
|
|
9
|
+
join(here, '..', 'dist', 'src', 'bin.js'),
|
|
10
|
+
];
|
|
11
|
+
const distPath = distPaths.find((candidate) => existsSync(candidate));
|
|
8
12
|
|
|
9
|
-
if (!
|
|
13
|
+
if (!distPath) {
|
|
10
14
|
process.stderr.write('Missing dist build. Run `pnpm build` before using the binary.\n');
|
|
11
15
|
process.exit(1);
|
|
12
16
|
}
|
package/dist/src/113.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import e from"node:fs";import t from"node:path";import{fileURLToPath as r}from"node:url";import{runCmdDetached as n}from"./9818.js";let i="agent-device",o="--agent-device-run-update-check";function a(a){var c,p,u,m,h,f,g;let k,v,y;if(!(!(!(c=a).command||"help"===c.command||"test"===c.command||c.flags.help||c.flags.version||c.flags.json||process.env.CI?.trim()||"test"===process.env.NODE_ENV||process.env.AGENT_DEVICE_NO_UPDATE_NOTIFIER?.trim())&&process.stderr.isTTY))return;let S=Date.now(),V=t.join(a.stateDir,"update-check.json"),w=s(V);p=w,u=a.currentVersion,!p.latestVersion||0>=l(p.latestVersion,u)||!0===p.prompted||(process.stderr.write(`Update available: ${i} ${a.currentVersion} -> ${w.latestVersion}. Run \`npm install -g ${i}@latest\` to upgrade the CLI and bundled skills.
|
|
2
|
-
`),d(V,{...w,prompted:!0})),m=w,h=S,(void 0===(k=function(e){if(!e)return;let t=Date.parse(e);return Number.isNaN(t)?void 0:t}(m.checkedAt))||h-k>=12096e5)&&(f=V,g=a.currentVersion,y=(v=function(){let n=r(import.meta.url),i=t.extname(n)||".js",o=t.join(t.dirname(n),`update-check-entry${i}`)
|
|
2
|
+
`),d(V,{...w,prompted:!0})),m=w,h=S,(void 0===(k=function(e){if(!e)return;let t=Date.parse(e);return Number.isNaN(t)?void 0:t}(m.checkedAt))||h-k>=12096e5)&&(f=V,g=a.currentVersion,y=(v=function(){let n=r(import.meta.url),i=t.extname(n)||".js",o=[t.join(t.dirname(n),`update-check-entry${i}`),t.join(t.dirname(n),"internal",`update-check-entry${i}`)].find(t=>e.existsSync(t));if(!o)throw Error("Update check entrypoint not found. Rebuild the package to include the update-check worker entry.");return o}()).endsWith(".ts")?["--experimental-strip-types"]:[],n(process.execPath,[...y,v,o,f,g]))}async function c(e){let t=Date.now(),r=s(e.cachePath);try{let n=await p()??void 0;if(!n||0>=l(n,e.currentVersion))return void d(e.cachePath,{checkedAt:new Date(t).toISOString()});d(e.cachePath,{checkedAt:new Date(t).toISOString(),latestVersion:n,prompted:r.latestVersion===n&&!0===r.prompted})}catch{d(e.cachePath,{...r,checkedAt:new Date(t).toISOString()})}}function s(t){try{let r=JSON.parse(e.readFileSync(t,"utf8"));return{checkedAt:"string"==typeof r.checkedAt?r.checkedAt:void 0,latestVersion:"string"==typeof r.latestVersion?r.latestVersion:void 0,prompted:!0===r.prompted}}catch{return{}}}function d(r,n){try{e.mkdirSync(t.dirname(r),{recursive:!0}),e.writeFileSync(r,`${JSON.stringify(n,null,2)}
|
|
3
3
|
`,"utf8")}catch{}}async function p(){let e=await fetch(`https://registry.npmjs.org/${i}/latest`,{signal:AbortSignal.timeout(3500),headers:{accept:"application/json"}});if(!e.ok)return;let t=await e.json();return"string"==typeof t.version&&t.version.trim().length>0?t.version.trim():void 0}function l(e,t){return e.localeCompare(t,void 0,{numeric:!0})}function u(e){if(e[0]!==o)return null;let t=e[1]?.trim(),r=e[2]?.trim();return t&&r?{cachePath:t,currentVersion:r}:null}export{a as maybeRunUpgradeNotifier,u as readUpdateCheckWorkerArgs,c as runUpdateCheckWorker};
|
package/dist/src/1974.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:fs";import r from"node:path";import{createHash as t}from"node:crypto";import{fileURLToPath as n}from"node:url";import{runCmdDetached as o,runCmdSync as i}from"./9818.js";import{readProcessCommand as a,waitForProcessExit as s,resolveRuntimeTransportHints as l,isProcessAlive as u,readProcessStartTime as c}from"./8656.js";import{normalizeBaseUrl as d,ENV_COMPANION_TUNNEL_BEARER_TOKEN as m,ENV_COMPANION_TUNNEL_SERVER_BASE_URL as p,ENV_COMPANION_TUNNEL_STATE_PATH as f,ENV_COMPANION_TUNNEL_LAUNCH_URL as h,ENV_COMPANION_TUNNEL_SCOPE_LEASE_ID as g,ENV_COMPANION_TUNNEL_SCOPE_TENANT_ID as y,ENV_COMPANION_TUNNEL_DEVICE_PORT as b,buildBundleUrl as S,METRO_COMPANION_RUN_ARG as w,ENV_COMPANION_TUNNEL_SESSION as P,ENV_COMPANION_TUNNEL_SCOPE_RUN_ID as v,ENV_COMPANION_TUNNEL_REGISTER_PATH as U,ENV_COMPANION_TUNNEL_LOCAL_BASE_URL as _,ENV_COMPANION_TUNNEL_UNREGISTER_PATH as I}from"./2301.js";import{AppError as E}from"./9152.js";import{resolveUserPath as M}from"./3267.js";import{sleep as k}from"./4829.js";function
|
|
2
|
-
`,"utf8")}function T(r){try{let t=e.readdirSync(r);0===t.length&&e.rmdirSync(r)}catch{}}function j(t,n){let o=r.dirname(t.statePath),i=r.dirname(t.logPath);var a=t.statePath;try{e.unlinkSync(a)}catch{}var s=t.logPath;try{e.unlinkSync(s)}catch{}T(o),i!==o&&T(i),r.basename(o)===n.slug&&T(r.dirname(o))}function C(e,r){return e.includes(r.runArg)}function B(e){return A(e.consumerKey)??A(e.profileKey)??null}function K(e,r){return!r||e.consumers.includes(r)?e:{...e,consumers:[...e.consumers,r]}}async function D(e,r){if(!u(e.pid))return;let t=a(e.pid);if(t&&C(t,r)){try{process.kill(e.pid,"SIGTERM")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}if(!await s(e.pid,1e3)){try{process.kill(e.pid,"SIGKILL")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}await s(e.pid,1e3)}}}async function H(t){let i=B(t),s=$(t.projectRoot,t.definition,t.profileKey,t.stateDir),l=R(s.statePath);if(l&&function(e,r){var t,n;if(!u(e.pid))return!1;if(e.startTime){let r=c(e.pid);if(!r||r!==e.startTime)return!1}let o=a(e.pid);return!!o&&!!C(o,r.definition)&&!!e.bridgeScope&&e.serverBaseUrl===d(r.serverBaseUrl)&&e.localBaseUrl===d(r.localBaseUrl)&&e.launchUrl===A(r.launchUrl)&&e.registerPath===A(r.registerPath)&&e.unregisterPath===A(r.unregisterPath)&&e.devicePort===r.devicePort&&e.session===A(r.session)&&(t=e.bridgeScope,n=r.bridgeScope,t.tenantId===n.tenantId&&t.runId===n.runId&&t.leaseId===n.leaseId)&&e.tokenHash===N(r.bearerToken)}(l,t)){let e=K(l,i);return e!==l&&x(s.statePath,e),{pid:l.pid,spawned:!1,statePath:s.statePath,logPath:s.logPath}}l&&(await D(l,t.definition),j(s,t.definition));let S=function(t,i){let s=function(t){let o=n(import.meta.url),i=r.extname(o)||".js",a=r.join(r.dirname(o),`metro-companion${i}`);if(!e.existsSync(a))throw Error(`${t.displayName} entrypoint not found at ${a}. Rebuild the package to include the companion worker entry.`);return a}(t.definition),l=s.endsWith(".ts")?["--experimental-strip-types"]:[];e.mkdirSync(r.dirname(i),{recursive:!0});let u=e.openSync(i,"a"),S=0;try{let e;S=o(process.execPath,[...l,s,t.definition.runArg],{env:((e={...t.env??process.env,[p]:d(t.serverBaseUrl),[m]:t.bearerToken,[_]:d(t.localBaseUrl),[f]:$(t.projectRoot,t.definition,t.profileKey,t.stateDir).statePath})[y]=t.bridgeScope.tenantId,e[v]=t.bridgeScope.runId,e[g]=t.bridgeScope.leaseId,t.launchUrl?.trim()?e[h]=t.launchUrl.trim():delete e[h],t.registerPath?.trim()?e[U]=t.registerPath.trim():delete e[U],t.unregisterPath?.trim()?e[I]=t.unregisterPath.trim():delete e[I],void 0!==t.devicePort?e[b]=String(t.devicePort):delete e[b],t.session?.trim()?e[P]=t.session.trim():delete e[P],e),stdio:["ignore",u,u]})}finally{e.closeSync(u)}if(!Number.isInteger(S)||S<=0)throw Error(`Failed to start ${t.definition.displayName} process.`);return{pid:S,startTime:c(S)??void 0,command:a(S)??void 0,serverBaseUrl:d(t.serverBaseUrl),localBaseUrl:d(t.localBaseUrl),launchUrl:A(t.launchUrl),registerPath:A(t.registerPath),unregisterPath:A(t.unregisterPath),devicePort:t.devicePort,session:A(t.session),bridgeScope:t.bridgeScope,tokenHash:N(t.bearerToken),consumers:[]}}(t,s.logPath);return x(s.statePath,K(S,i)),{pid:S.pid,spawned:!0,statePath:s.statePath,logPath:s.logPath}}async function O(e){let r=B(e),t=$(e.projectRoot,e.definition,e.profileKey,e.stateDir),n=R(t.statePath);if(!n)return j(t,e.definition),{stopped:!1,statePath:t.statePath};let o=r?{...n,consumers:n.consumers.filter(e=>e!==r)}:{...n,consumers:[]};return o.consumers.length>0?(x(t.statePath,o),{stopped:!1,statePath:t.statePath}):(await D(n,e.definition),j(t,e.definition),{stopped:!0,statePath:t.statePath})}let L={slug:"metro-companion",runArg:w,displayName:"Metro companion"};async function G(e){return await H({...e,definition:L,registerPath:e.registerPath??"/api/metro/companion/register"})}async function J(e){return await O({...e,definition:L})}function F(e){return"string"==typeof e&&e.trim()?d(e.trim()):""}function V(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function q(e,r,t){return M(e,{env:r,cwd:t})}function W(r){try{return e.accessSync(r,e.constants.F_OK),!0}catch{return!1}}function z(e,r,t){if(null==e||""===e)return r;let n=Number.parseInt(String(e),10);return Number.isInteger(n)?Math.max(n,t):r}function X(e,r){if(null==e||""===e)return r;let t=Number.parseInt(String(e),10);if(!Number.isInteger(t)||t<1||t>65535)throw new E("INVALID_ARGS",`Invalid Metro port: ${String(e)}. Use 1-65535.`);return t}function Y(e,r){return{platform:r,bundleUrl:S(e,r)}}function Q(e,r){return{platform:r,metroHost:V(e?.metro_host),metroPort:e?.metro_port,bundleUrl:V(e?.metro_bundle_url),launchUrl:V(e?.launch_url)}}async function Z(e){await k(e)}async function ee(e,r,t={}){try{let n=await fetch(e,{headers:t,signal:AbortSignal.timeout(r)});return{ok:n.ok,status:n.status,body:await n.text()}}catch(t){if(t instanceof Error&&"TimeoutError"===t.name)throw Error(`Timed out fetching ${e} after ${r}ms`);throw t}}async function er(e,r){try{let t=await ee(e,r);return t.ok&&t.body.includes("packager-status:running")}catch{return!1}}async function et(e){if(Number.isInteger(e)&&!(e<=0)){try{process.kill(e,"SIGTERM")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}if(!await s(e,1e3)){try{process.kill(e,"SIGKILL")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}await s(e,1e3)}}}function en(e,r){let t=Error(e);return t.retryable=r,t}function eo(e,r){return!!(e>=500||408===e||425===e||429===e||JSON.stringify(r).includes("Metro companion is not connected"))}function ei(e){return!!(e&&"object"==typeof e&&"retryable"in e&&!0===e.retryable)}async function ea(e){let r;try{var t,n;r=await fetch(`${e.baseUrl}/api/metro/bridge`,{method:"POST",headers:(t=e.baseUrl,n=e.bearerToken,{Authorization:`Bearer ${n}`,"Content-Type":"application/json",...t.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({...e.scope,...e.runtime?{ios_runtime:e.runtime}:{},timeout_ms:e.timeoutMs}),signal:AbortSignal.timeout(e.timeoutMs)})}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw en(`/api/metro/bridge timed out after ${e.timeoutMs}ms calling ${e.baseUrl}/api/metro/bridge`,!0);throw en(r instanceof Error?r.message:String(r),!0)}let o=function(e,r,t){if(!e)return{};try{let r=JSON.parse(e);if(!r||"object"!=typeof r||Array.isArray(r))throw Error("Expected a JSON object");return r}catch(i){let n=e.slice(0,200),o=i instanceof Error?i.message:String(i);throw en(`/api/metro/bridge returned invalid JSON (${r}) from ${t}: ${o}. body=${JSON.stringify(n)}`,eo(r,e))}}(await r.text(),r.status,e.baseUrl);if(!r.ok)throw en(`/api/metro/bridge failed (${r.status}): ${JSON.stringify(o)}`,eo(r.status,o));var i=o;let a=i.data??i;if(!a||"object"!=typeof a||Array.isArray(a))throw en("/api/metro/bridge returned malformed descriptor: Expected a JSON object.",!1);try{return{enabled:a.enabled,baseUrl:a.base_url,statusUrl:a.status_url??"",bundleUrl:a.bundle_url??"",iosRuntime:Q(a.ios_runtime,"ios"),androidRuntime:Q(a.android_runtime,"android"),upstream:{bundleUrl:a.upstream.bundle_url??"",host:a.upstream.host??"",port:a.upstream.port??0,statusUrl:a.upstream.status_url??""},probe:{reachable:a.probe.reachable,statusCode:a.probe.status_code,latencyMs:a.probe.latency_ms,detail:a.probe.detail}}}catch(e){throw en(`/api/metro/bridge returned malformed descriptor: ${e instanceof Error?e.message:String(e)}`,!1)}}function es(e,r,t,n,o){let i=[`Metro bridge is required for this run but could not be configured via ${e}/api/metro/bridge.`];return r&&i.push(`bridgeError=${r}`),t?.probe.reachable===!1&&i.push(`bridgeProbe=${t.probe.detail||`unreachable (status ${t.probe.statusCode||0})`}`),n&&n!==r&&i.push(`initialBridgeError=${n}`),o&&i.push(`metroCompanionLog=${o}`),i.join(" ")}async function el(e,r,t){let n=Date.now()+r;for(;Date.now()<n;){let r=Math.min(t,Math.max(n-Date.now(),1));if(await er(e,r))return!0;let o=Math.min(500,Math.max(n-Date.now(),0));o>0&&await Z(o)}return!1}async function eu(e){let r=Date.now()+e.startupTimeoutMs,t=null,n=null;for(;Date.now()<r;){try{let r=await ea({baseUrl:e.baseUrl,bearerToken:e.bearerToken,scope:e.scope,runtime:e.runtime,timeoutMs:e.probeTimeoutMs});if(!1!==r.probe.reachable)return r;t=r,n=null}catch(e){if(n=e instanceof Error?e.message:String(e),!ei(e))break}let o=Math.min(1e3,Math.max(r-Date.now(),0));o>0&&await Z(o)}throw Error(es(e.baseUrl,n,t,e.initialBridgeError,e.companionLogPath))}async function ec(t={}){let n=t.env??process.env,a=process.cwd(),s=q(t.projectRoot??a,n,a),l=function(t,n){if("auto"!==n)return n;let o=function(t){let n=r.join(t,"package.json");if(!W(n))throw new E("INVALID_ARGS",`package.json not found at ${n}`);return JSON.parse(e.readFileSync(n,"utf8"))}(t);return"string"==typeof({...o.dependencies??{},...o.devDependencies??{}}).expo?"expo":"react-native"}(s,t.kind??"auto"),u=X(t.metroPort??8081,8081),c=V(t.listenHost)??"0.0.0.0",d=V(t.statusHost)??"127.0.0.1",m=F(t.publicBaseUrl),p=z(t.startupTimeoutMs,18e4,3e4),f=z(t.probeTimeoutMs,1e4,1e3),h=t.reuseExisting??!0,g=t.installDependenciesIfNeeded??!0,y=t.runtimeFilePath?q(t.runtimeFilePath,n,a):null,b=q(t.logPath??r.join(s,".agent-device","metro.log"),n,a);if(!m&&!F(t.proxyBaseUrl))throw new E("INVALID_ARGS","metro prepare requires --public-base-url <url>.");let{proxyEnabled:S,proxyBaseUrl:w,proxyBearerToken:P}=function(e,r){if(e&&!r)throw new E("INVALID_ARGS","metro prepare requires proxy auth when --proxy-base-url is provided. Pass --bearer-token or set AGENT_DEVICE_PROXY_TOKEN.");if(!e&&r)throw new E("INVALID_ARGS","metro prepare requires --proxy-base-url when proxy auth is provided.");return{proxyEnabled:!!(e&&r),proxyBaseUrl:e,proxyBearerToken:r}}(F(t.proxyBaseUrl),V(t.proxyBearerToken)??""),v=S?function(e){if(!e?.tenantId||!e.runId||!e.leaseId)throw new E("INVALID_ARGS","metro prepare with proxy requires tenantId, runId, and leaseId bridge scope.");return e}(t.bridgeScope):null,U=g?function(t,n){if(function(r){try{return e.statSync(r).isDirectory()}catch{return!1}}(r.join(t,"node_modules")))return{installed:!1};let o=W(r.join(t,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:W(r.join(t,"yarn.lock"))?{command:"yarn",installArgs:["install"]}:{command:"npm",installArgs:["install"]};return i(o.command,o.installArgs,{cwd:t,env:n}),{installed:!0,packageManager:o.command}}(s,n):{installed:!1},_=`http://${d}:${u}/status`,I=!1,M=!1,k=0;if(h&&await er(_,f))M=!0;else if(I=!0,k=function(t,n,i,a,s,l){let u="expo"===n?{command:"npx",installArgs:["expo","start","--host","lan","--port",String(i)]}:{command:"npx",installArgs:["react-native","start","--host",a,"--port",String(i)]};e.mkdirSync(r.dirname(s),{recursive:!0});let c=e.openSync(s,"a"),d=0;try{d=o(u.command,u.installArgs,{cwd:t,env:l,stdio:["ignore",c,c]})}finally{e.closeSync(c)}if(!Number.isInteger(d)||d<=0)throw Error("Failed to start Metro. Expected a detached child PID.");return{pid:d}}(s,l,u,c,b,n).pid,!await el(_,p,f))throw await et(k).catch(()=>{}),Error(`Metro did not become ready at ${_} within ${p}ms. Check ${b}.`);let N=m?Y(m,"ios"):{platform:"ios"},A=m?Y(m,"android"):{platform:"android"},$=null,R=null;if(v)try{$=await ea({baseUrl:w,bearerToken:P,scope:v,timeoutMs:f})}catch(e){if(!ei(e))throw e;R=e instanceof Error?e.message:String(e)}if(v&&(!$||!1===$.probe.reachable)){let e;try{e=(await G({projectRoot:s,serverBaseUrl:w,bearerToken:P,bridgeScope:v,localBaseUrl:`http://${d}:${u}`,launchUrl:V(t.launchUrl),profileKey:V(t.companionProfileKey),consumerKey:V(t.companionConsumerKey),env:n})).logPath}catch(e){throw Error(es(w,e instanceof Error?e.message:String(e),$,R))}try{$=await eu({baseUrl:w,bearerToken:P,scope:v,probeTimeoutMs:f,startupTimeoutMs:p,initialBridgeError:R,companionLogPath:e})}catch(e){throw e instanceof Error?e:Error(String(e))}}v&&function(e,r){if(!r?.iosRuntime.bundleUrl)throw Error(es(e,"bridge descriptor is missing ios_runtime.metro_bundle_url",r))}(w,$);let x=$?.iosRuntime??N,T=$?.androidRuntime??A,j={projectRoot:s,kind:l,dependenciesInstalled:U.installed,packageManager:U.packageManager??null,started:I,reused:M,pid:k,logPath:b,statusUrl:_,runtimeFilePath:y,iosRuntime:x,androidRuntime:T,bridge:$};return y&&(e.mkdirSync(r.dirname(y),{recursive:!0}),e.writeFileSync(y,JSON.stringify(j,null,2))),j}async function ed(e={}){let r=z(e.timeoutMs,1e4,1e3),t=function(e){var r;let t,n=V(e.bundleUrl)??e.runtime?.bundleUrl,o=!!V(e.bundleUrl),i=!!V(n),a=l({metroHost:V(e.metroHost)??(o?void 0:V(e.runtime?.metroHost))??(i?void 0:"localhost"),metroPort:void 0!==e.metroPort?X(e.metroPort,8081):o?void 0:e.runtime?.metroPort??(i?void 0:8081),bundleUrl:n});if(!a)throw new E("INVALID_ARGS","Unable to resolve Metro host and port for reload.");return r=function(e){let r=V(e);if(!r)return"/reload";let t=new URL(r).pathname.replace(/\/+$/,"");return t.endsWith("/index.bundle")?`${t.slice(0,-13)}/reload`:"/reload"}(n),(t=new URL(`${a.scheme}://localhost`)).hostname=a.host,t.port=String(a.port),t.pathname=r,t.toString()}(e),n=await ee(t,r);if(!n.ok)throw new E("COMMAND_FAILED",`Metro reload failed (${n.status}).`,{reloadUrl:t,status:n.status,body:n.body,hint:"Verify Metro is running and the target React Native app is connected to this Metro instance."});return{reloaded:!0,reloadUrl:t,status:n.status,body:n.body}}export{Y as buildMetroRuntimeHints,H as ensureCompanionTunnel,G as ensureMetroCompanion,ec as prepareMetroRuntime,ed as reloadMetro,O as stopCompanionTunnel,J as stopMetroCompanion};
|
|
1
|
+
import e from"node:fs";import r from"node:path";import{createHash as t}from"node:crypto";import{fileURLToPath as n}from"node:url";import{runCmdDetached as o,runCmdSync as i}from"./9818.js";import{readProcessCommand as a,waitForProcessExit as s,resolveRuntimeTransportHints as l,isProcessAlive as u,readProcessStartTime as c}from"./8656.js";import{normalizeBaseUrl as d,ENV_COMPANION_TUNNEL_BEARER_TOKEN as m,ENV_COMPANION_TUNNEL_SERVER_BASE_URL as p,ENV_COMPANION_TUNNEL_STATE_PATH as f,ENV_COMPANION_TUNNEL_LAUNCH_URL as h,ENV_COMPANION_TUNNEL_SCOPE_LEASE_ID as g,ENV_COMPANION_TUNNEL_SCOPE_TENANT_ID as y,ENV_COMPANION_TUNNEL_DEVICE_PORT as b,buildBundleUrl as S,METRO_COMPANION_RUN_ARG as w,ENV_COMPANION_TUNNEL_SESSION as P,ENV_COMPANION_TUNNEL_SCOPE_RUN_ID as v,ENV_COMPANION_TUNNEL_REGISTER_PATH as U,ENV_COMPANION_TUNNEL_LOCAL_BASE_URL as _,ENV_COMPANION_TUNNEL_UNREGISTER_PATH as I}from"./2301.js";import{AppError as E}from"./9152.js";import{resolveUserPath as M}from"./3267.js";import{sleep as k}from"./4829.js";let N="companion-tunnel";function $(e){return t("sha256").update(e).digest("hex")}function A(e){return e?.trim()?e.trim():void 0}function R(e,t,n,o){let i=o??r.join(e,".agent-device");if(!n)return{statePath:r.join(i,`${t.slug}.json`),logPath:r.join(i,`${t.slug}.log`)};let a=$(n).slice(0,12),s=r.join(i,t.slug);return{statePath:r.join(s,`${t.slug}-${a}.json`),logPath:r.join(s,`${t.slug}-${a}.log`)}}function x(r){try{let t=JSON.parse(e.readFileSync(r,"utf8"));if(!Number.isInteger(t.pid)||0>=Number(t.pid)||"string"!=typeof t.serverBaseUrl||"string"!=typeof t.localBaseUrl||"string"!=typeof t.tokenHash||0===t.tokenHash.length)return null;let n=Array.isArray(t.consumers)?t.consumers.filter(e=>"string"==typeof e&&e.length>0):[];return{pid:Number(t.pid),startTime:"string"==typeof t.startTime?t.startTime:void 0,command:"string"==typeof t.command?t.command:void 0,serverBaseUrl:t.serverBaseUrl,localBaseUrl:t.localBaseUrl,launchUrl:A("string"==typeof t.launchUrl?t.launchUrl:void 0),registerPath:A("string"==typeof t.registerPath?t.registerPath:void 0),unregisterPath:A("string"==typeof t.unregisterPath?t.unregisterPath:void 0),devicePort:Number.isInteger(t.devicePort)?Number(t.devicePort):void 0,session:A("string"==typeof t.session?t.session:void 0),bridgeScope:function(e){if(!(!e||"object"!=typeof e||Array.isArray(e))&&"string"==typeof e.tenantId&&"string"==typeof e.runId&&"string"==typeof e.leaseId)return{tenantId:e.tenantId,runId:e.runId,leaseId:e.leaseId}}(t.bridgeScope),tokenHash:t.tokenHash,consumers:n}}catch{return null}}function T(t,n){e.mkdirSync(r.dirname(t),{recursive:!0}),e.writeFileSync(t,`${JSON.stringify(n,null,2)}
|
|
2
|
+
`,"utf8")}function j(r){try{let t=e.readdirSync(r);0===t.length&&e.rmdirSync(r)}catch{}}function C(t,n){let o=r.dirname(t.statePath),i=r.dirname(t.logPath);var a=t.statePath;try{e.unlinkSync(a)}catch{}var s=t.logPath;try{e.unlinkSync(s)}catch{}j(o),i!==o&&j(i),r.basename(o)===n.slug&&j(r.dirname(o))}function B(e,r){return e.includes(r.runArg)}function K(e){return A(e.consumerKey)??A(e.profileKey)??null}function D(e,r){return!r||e.consumers.includes(r)?e:{...e,consumers:[...e.consumers,r]}}async function H(e,r){if(!u(e.pid))return;let t=a(e.pid);if(t&&B(t,r)){try{process.kill(e.pid,"SIGTERM")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}if(!await s(e.pid,1e3)){try{process.kill(e.pid,"SIGKILL")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}await s(e.pid,1e3)}}}async function O(t){let i=K(t),s=R(t.projectRoot,t.definition,t.profileKey,t.stateDir),l=x(s.statePath);if(l&&function(e,r){var t,n;if(!u(e.pid))return!1;if(e.startTime){let r=c(e.pid);if(!r||r!==e.startTime)return!1}let o=a(e.pid);return!!o&&!!B(o,r.definition)&&!!e.bridgeScope&&e.serverBaseUrl===d(r.serverBaseUrl)&&e.localBaseUrl===d(r.localBaseUrl)&&e.launchUrl===A(r.launchUrl)&&e.registerPath===A(r.registerPath)&&e.unregisterPath===A(r.unregisterPath)&&e.devicePort===r.devicePort&&e.session===A(r.session)&&(t=e.bridgeScope,n=r.bridgeScope,t.tenantId===n.tenantId&&t.runId===n.runId&&t.leaseId===n.leaseId)&&e.tokenHash===$(r.bearerToken)}(l,t)){let e=D(l,i);return e!==l&&T(s.statePath,e),{pid:l.pid,spawned:!1,statePath:s.statePath,logPath:s.logPath}}l&&(await H(l,t.definition),C(s,t.definition));let S=function(t,i){let s=function(t){let o=n(import.meta.url),i=r.extname(o)||".js",a=[r.join(r.dirname(o),`${N}${i}`),r.join(r.dirname(o),"internal",`${N}${i}`)].find(r=>e.existsSync(r));if(!a)throw Error(`${t.displayName} entrypoint not found. Rebuild the package to include the companion worker entry.`);return a}(t.definition),l=s.endsWith(".ts")?["--experimental-strip-types"]:[];e.mkdirSync(r.dirname(i),{recursive:!0});let u=e.openSync(i,"a"),S=0;try{let e;S=o(process.execPath,[...l,s,t.definition.runArg],{env:((e={...t.env??process.env})[p]=d(t.serverBaseUrl),e[m]=t.bearerToken,e[_]=d(t.localBaseUrl),e[f]=R(t.projectRoot,t.definition,t.profileKey,t.stateDir).statePath,e[y]=t.bridgeScope.tenantId,e[v]=t.bridgeScope.runId,e[g]=t.bridgeScope.leaseId,t.launchUrl?.trim()?e[h]=t.launchUrl.trim():delete e[h],t.registerPath?.trim()?e[U]=t.registerPath.trim():delete e[U],t.unregisterPath?.trim()?e[I]=t.unregisterPath.trim():delete e[I],void 0!==t.devicePort?e[b]=String(t.devicePort):delete e[b],t.session?.trim()?e[P]=t.session.trim():delete e[P],e),stdio:["ignore",u,u]})}finally{e.closeSync(u)}if(!Number.isInteger(S)||S<=0)throw Error(`Failed to start ${t.definition.displayName} process.`);return{pid:S,startTime:c(S)??void 0,command:a(S)??void 0,serverBaseUrl:d(t.serverBaseUrl),localBaseUrl:d(t.localBaseUrl),launchUrl:A(t.launchUrl),registerPath:A(t.registerPath),unregisterPath:A(t.unregisterPath),devicePort:t.devicePort,session:A(t.session),bridgeScope:t.bridgeScope,tokenHash:$(t.bearerToken),consumers:[]}}(t,s.logPath);return T(s.statePath,D(S,i)),{pid:S.pid,spawned:!0,statePath:s.statePath,logPath:s.logPath}}async function L(e){let r=K(e),t=R(e.projectRoot,e.definition,e.profileKey,e.stateDir),n=x(t.statePath);if(!n)return C(t,e.definition),{stopped:!1,statePath:t.statePath};let o=r?{...n,consumers:n.consumers.filter(e=>e!==r)}:{...n,consumers:[]};return o.consumers.length>0?(T(t.statePath,o),{stopped:!1,statePath:t.statePath}):(await H(n,e.definition),C(t,e.definition),{stopped:!0,statePath:t.statePath})}let G={slug:"metro-companion",runArg:w,displayName:"Metro companion"};async function J(e){return await O({...e,definition:G,registerPath:e.registerPath??"/api/metro/companion/register"})}async function F(e){return await L({...e,definition:G})}function V(e){return"string"==typeof e&&e.trim()?d(e.trim()):""}function q(e){return"string"==typeof e&&e.trim()?e.trim():void 0}function W(e,r,t){return M(e,{env:r,cwd:t})}function z(r){try{return e.accessSync(r,e.constants.F_OK),!0}catch{return!1}}function X(e,r,t){if(null==e||""===e)return r;let n=Number.parseInt(String(e),10);return Number.isInteger(n)?Math.max(n,t):r}function Y(e,r){if(null==e||""===e)return r;let t=Number.parseInt(String(e),10);if(!Number.isInteger(t)||t<1||t>65535)throw new E("INVALID_ARGS",`Invalid Metro port: ${String(e)}. Use 1-65535.`);return t}function Q(e,r){return{platform:r,bundleUrl:S(e,r)}}function Z(e,r){return{platform:r,metroHost:q(e?.metro_host),metroPort:e?.metro_port,bundleUrl:q(e?.metro_bundle_url),launchUrl:q(e?.launch_url)}}async function ee(e){await k(e)}async function er(e,r,t={}){try{let n=await fetch(e,{headers:t,signal:AbortSignal.timeout(r)});return{ok:n.ok,status:n.status,body:await n.text()}}catch(t){if(t instanceof Error&&"TimeoutError"===t.name)throw Error(`Timed out fetching ${e} after ${r}ms`);throw t}}async function et(e,r){try{let t=await er(e,r);return t.ok&&t.body.includes("packager-status:running")}catch{return!1}}async function en(e){if(Number.isInteger(e)&&!(e<=0)){try{process.kill(e,"SIGTERM")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}if(!await s(e,1e3)){try{process.kill(e,"SIGKILL")}catch(r){let e=r.code;if("ESRCH"===e||"EPERM"===e)return;throw r}await s(e,1e3)}}}function eo(e,r){let t=Error(e);return t.retryable=r,t}function ei(e,r){return!!(e>=500||408===e||425===e||429===e||JSON.stringify(r).includes("Metro companion is not connected"))}function ea(e){return!!(e&&"object"==typeof e&&"retryable"in e&&!0===e.retryable)}async function es(e){let r;try{var t,n;r=await fetch(`${e.baseUrl}/api/metro/bridge`,{method:"POST",headers:(t=e.baseUrl,n=e.bearerToken,{Authorization:`Bearer ${n}`,"Content-Type":"application/json",...t.includes("ngrok")?{"ngrok-skip-browser-warning":"1"}:{}}),body:JSON.stringify({...e.scope,...e.runtime?{ios_runtime:e.runtime}:{},timeout_ms:e.timeoutMs}),signal:AbortSignal.timeout(e.timeoutMs)})}catch(r){if(r instanceof Error&&"TimeoutError"===r.name)throw eo(`/api/metro/bridge timed out after ${e.timeoutMs}ms calling ${e.baseUrl}/api/metro/bridge`,!0);throw eo(r instanceof Error?r.message:String(r),!0)}let o=function(e,r,t){if(!e)return{};try{let r=JSON.parse(e);if(!r||"object"!=typeof r||Array.isArray(r))throw Error("Expected a JSON object");return r}catch(i){let n=e.slice(0,200),o=i instanceof Error?i.message:String(i);throw eo(`/api/metro/bridge returned invalid JSON (${r}) from ${t}: ${o}. body=${JSON.stringify(n)}`,ei(r,e))}}(await r.text(),r.status,e.baseUrl);if(!r.ok)throw eo(`/api/metro/bridge failed (${r.status}): ${JSON.stringify(o)}`,ei(r.status,o));var i=o;let a=i.data??i;if(!a||"object"!=typeof a||Array.isArray(a))throw eo("/api/metro/bridge returned malformed descriptor: Expected a JSON object.",!1);try{return{enabled:a.enabled,baseUrl:a.base_url,statusUrl:a.status_url??"",bundleUrl:a.bundle_url??"",iosRuntime:Z(a.ios_runtime,"ios"),androidRuntime:Z(a.android_runtime,"android"),upstream:{bundleUrl:a.upstream.bundle_url??"",host:a.upstream.host??"",port:a.upstream.port??0,statusUrl:a.upstream.status_url??""},probe:{reachable:a.probe.reachable,statusCode:a.probe.status_code,latencyMs:a.probe.latency_ms,detail:a.probe.detail}}}catch(e){throw eo(`/api/metro/bridge returned malformed descriptor: ${e instanceof Error?e.message:String(e)}`,!1)}}function el(e,r,t,n,o){let i=[`Metro bridge is required for this run but could not be configured via ${e}/api/metro/bridge.`];return r&&i.push(`bridgeError=${r}`),t?.probe.reachable===!1&&i.push(`bridgeProbe=${t.probe.detail||`unreachable (status ${t.probe.statusCode||0})`}`),n&&n!==r&&i.push(`initialBridgeError=${n}`),o&&i.push(`metroCompanionLog=${o}`),i.join(" ")}async function eu(e,r,t){let n=Date.now()+r;for(;Date.now()<n;){let r=Math.min(t,Math.max(n-Date.now(),1));if(await et(e,r))return!0;let o=Math.min(500,Math.max(n-Date.now(),0));o>0&&await ee(o)}return!1}async function ec(e){let r=Date.now()+e.startupTimeoutMs,t=null,n=null;for(;Date.now()<r;){try{let r=await es({baseUrl:e.baseUrl,bearerToken:e.bearerToken,scope:e.scope,runtime:e.runtime,timeoutMs:e.probeTimeoutMs});if(!1!==r.probe.reachable)return r;t=r,n=null}catch(e){if(n=e instanceof Error?e.message:String(e),!ea(e))break}let o=Math.min(1e3,Math.max(r-Date.now(),0));o>0&&await ee(o)}throw Error(el(e.baseUrl,n,t,e.initialBridgeError,e.companionLogPath))}async function ed(t={}){let n=t.env??process.env,a=process.cwd(),s=W(t.projectRoot??a,n,a),l=function(t,n){if("auto"!==n)return n;let o=function(t){let n=r.join(t,"package.json");if(!z(n))throw new E("INVALID_ARGS",`package.json not found at ${n}`);return JSON.parse(e.readFileSync(n,"utf8"))}(t);return"string"==typeof({...o.dependencies??{},...o.devDependencies??{}}).expo?"expo":"react-native"}(s,t.kind??"auto"),u=Y(t.metroPort??8081,8081),c=q(t.listenHost)??"0.0.0.0",d=q(t.statusHost)??"127.0.0.1",m=V(t.publicBaseUrl),p=X(t.startupTimeoutMs,18e4,3e4),f=X(t.probeTimeoutMs,1e4,1e3),h=t.reuseExisting??!0,g=t.installDependenciesIfNeeded??!0,y=t.runtimeFilePath?W(t.runtimeFilePath,n,a):null,b=W(t.logPath??r.join(s,".agent-device","metro.log"),n,a);if(!m&&!V(t.proxyBaseUrl))throw new E("INVALID_ARGS","metro prepare requires --public-base-url <url>.");let{proxyEnabled:S,proxyBaseUrl:w,proxyBearerToken:P}=function(e,r){if(e&&!r)throw new E("INVALID_ARGS","metro prepare requires proxy auth when --proxy-base-url is provided. Pass --bearer-token or set AGENT_DEVICE_PROXY_TOKEN.");if(!e&&r)throw new E("INVALID_ARGS","metro prepare requires --proxy-base-url when proxy auth is provided.");return{proxyEnabled:!!(e&&r),proxyBaseUrl:e,proxyBearerToken:r}}(V(t.proxyBaseUrl),q(t.proxyBearerToken)??""),v=S?function(e){if(!e?.tenantId||!e.runId||!e.leaseId)throw new E("INVALID_ARGS","metro prepare with proxy requires tenantId, runId, and leaseId bridge scope.");return e}(t.bridgeScope):null,U=g?function(t,n){if(function(r){try{return e.statSync(r).isDirectory()}catch{return!1}}(r.join(t,"node_modules")))return{installed:!1};let o=z(r.join(t,"pnpm-lock.yaml"))?{command:"pnpm",installArgs:["install"]}:z(r.join(t,"yarn.lock"))?{command:"yarn",installArgs:["install"]}:{command:"npm",installArgs:["install"]};return i(o.command,o.installArgs,{cwd:t,env:n}),{installed:!0,packageManager:o.command}}(s,n):{installed:!1},_=`http://${d}:${u}/status`,I=!1,M=!1,k=0;if(h&&await et(_,f))M=!0;else if(I=!0,k=function(t,n,i,a,s,l){let u="expo"===n?{command:"npx",installArgs:["expo","start","--host","lan","--port",String(i)]}:{command:"npx",installArgs:["react-native","start","--host",a,"--port",String(i)]};e.mkdirSync(r.dirname(s),{recursive:!0});let c=e.openSync(s,"a"),d=0;try{d=o(u.command,u.installArgs,{cwd:t,env:l,stdio:["ignore",c,c]})}finally{e.closeSync(c)}if(!Number.isInteger(d)||d<=0)throw Error("Failed to start Metro. Expected a detached child PID.");return{pid:d}}(s,l,u,c,b,n).pid,!await eu(_,p,f))throw await en(k).catch(()=>{}),Error(`Metro did not become ready at ${_} within ${p}ms. Check ${b}.`);let N=m?Q(m,"ios"):{platform:"ios"},$=m?Q(m,"android"):{platform:"android"},A=null,R=null;if(v)try{A=await es({baseUrl:w,bearerToken:P,scope:v,timeoutMs:f})}catch(e){if(!ea(e))throw e;R=e instanceof Error?e.message:String(e)}if(v&&(!A||!1===A.probe.reachable)){let e;try{e=(await J({projectRoot:s,serverBaseUrl:w,bearerToken:P,bridgeScope:v,localBaseUrl:`http://${d}:${u}`,launchUrl:q(t.launchUrl),profileKey:q(t.companionProfileKey),consumerKey:q(t.companionConsumerKey),env:n})).logPath}catch(e){throw Error(el(w,e instanceof Error?e.message:String(e),A,R))}try{A=await ec({baseUrl:w,bearerToken:P,scope:v,probeTimeoutMs:f,startupTimeoutMs:p,initialBridgeError:R,companionLogPath:e})}catch(e){throw e instanceof Error?e:Error(String(e))}}v&&function(e,r){if(!r?.iosRuntime.bundleUrl)throw Error(el(e,"bridge descriptor is missing ios_runtime.metro_bundle_url",r))}(w,A);let x=A?.iosRuntime??N,T=A?.androidRuntime??$,j={projectRoot:s,kind:l,dependenciesInstalled:U.installed,packageManager:U.packageManager??null,started:I,reused:M,pid:k,logPath:b,statusUrl:_,runtimeFilePath:y,iosRuntime:x,androidRuntime:T,bridge:A};return y&&(e.mkdirSync(r.dirname(y),{recursive:!0}),e.writeFileSync(y,JSON.stringify(j,null,2))),j}async function em(e={}){let r=X(e.timeoutMs,1e4,1e3),t=function(e){var r;let t,n=q(e.bundleUrl)??e.runtime?.bundleUrl,o=!!q(e.bundleUrl),i=!!q(n),a=l({metroHost:q(e.metroHost)??(o?void 0:q(e.runtime?.metroHost))??(i?void 0:"localhost"),metroPort:void 0!==e.metroPort?Y(e.metroPort,8081):o?void 0:e.runtime?.metroPort??(i?void 0:8081),bundleUrl:n});if(!a)throw new E("INVALID_ARGS","Unable to resolve Metro host and port for reload.");return r=function(e){let r=q(e);if(!r)return"/reload";let t=new URL(r).pathname.replace(/\/+$/,"");return t.endsWith("/index.bundle")?`${t.slice(0,-13)}/reload`:"/reload"}(n),(t=new URL(`${a.scheme}://localhost`)).hostname=a.host,t.port=String(a.port),t.pathname=r,t.toString()}(e),n=await er(t,r);if(!n.ok)throw new E("COMMAND_FAILED",`Metro reload failed (${n.status}).`,{reloadUrl:t,status:n.status,body:n.body,hint:"Verify Metro is running and the target React Native app is connected to this Metro instance."});return{reloaded:!0,reloadUrl:t,status:n.status,body:n.body}}export{Q as buildMetroRuntimeHints,O as ensureCompanionTunnel,J as ensureMetroCompanion,ed as prepareMetroRuntime,em as reloadMetro,L as stopCompanionTunnel,F as stopMetroCompanion};
|
package/dist/src/221.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import e from"node:crypto";import t from"node:fs";import n from"node:fs/promises";import r from"node:os";import i from"node:path";import{AppError as a}from"./9152.js";let o="android-snapshot-helper",s="com.callstack.agentdevice.snapshothelper",l="com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",u="android-snapshot-helper-v1",d="uiautomator-xml",c=new Set(["-r","-t","-d","-g"]);async function h(e){let t=await w(e.apkPath);if(t!==e.manifest.sha256)throw new a("COMMAND_FAILED","Android snapshot helper APK checksum mismatch",{apkPath:e.apkPath,expectedSha256:e.manifest.sha256,actualSha256:t})}async function f(e){let t=e.fetch??fetch,o=await t(e.manifestUrl);if(!o.ok)throw new a("COMMAND_FAILED","Failed to download Android snapshot helper manifest",{manifestUrl:e.manifestUrl,status:o.status,statusText:o.statusText});let s=p(JSON.parse((await m(o,65536,"Android snapshot helper manifest")).toString("utf8")));if(!s.apkUrl)throw new a("COMMAND_FAILED","Android snapshot helper manifest does not include apkUrl",{manifestUrl:e.manifestUrl});let l=e.cacheDir??i.join(r.tmpdir(),`agent-device-android-snapshot-helper-${s.version}`),u=!e.cacheDir;await n.mkdir(l,{recursive:!0});let d=s.assetName??`agent-device-android-snapshot-helper-${s.version}.apk`,c=i.join(l,d),f=await t(s.apkUrl);if(!f.ok)throw new a("COMMAND_FAILED","Failed to download Android snapshot helper APK",{apkUrl:s.apkUrl,status:f.status,statusText:f.statusText});await n.writeFile(c,await m(f,0x1400000,"Android snapshot helper APK"));let A={apkPath:c,manifest:s};return await h(A),{...A,cleanup:async()=>{await n.rm(u?l:c,{recursive:u,force:!0})}}}function p(e){var t,n;if(!e||"object"!=typeof e||Array.isArray(e))throw new a("INVALID_ARGS","Android snapshot helper manifest must be an object.");return{name:b(e.name,"name",o),version:g(e.version,"version"),releaseTag:N(e.releaseTag),assetName:N(e.assetName),apkUrl:(t=e.apkUrl,n="apkUrl",null===t?null:g(t,n)),sha256:function(e){let t=g(e,"sha256").trim().toLowerCase();if(64!==t.length||!function(e){for(let t of e){let e=t.charCodeAt(0),n=e>=48&&e<=57,r=e>=97&&e<=102;if(!n&&!r)return!1}return!0}(t))throw new a("INVALID_ARGS","Android snapshot helper manifest sha256 must be a 64-character hex string.");return t}(e.sha256),checksumName:N(e.checksumName),packageName:g(e.packageName,"packageName"),versionCode:M(e.versionCode,"versionCode"),instrumentationRunner:g(e.instrumentationRunner,"instrumentationRunner"),minSdk:M(e.minSdk,"minSdk"),targetSdk:void 0===e.targetSdk?void 0:M(e.targetSdk,"targetSdk"),outputFormat:b(e.outputFormat,"outputFormat",d),statusProtocol:b(e.statusProtocol,"statusProtocol",u),installArgs:A(e.installArgs)}}async function m(e,t,n){let r=e.headers.get("content-length");if(null!==r){let e=Number(r);if(Number.isFinite(e)&&e>t)throw new a("COMMAND_FAILED",`${n} download exceeds size limit`,{contentLength:e,maxBytes:t})}if(!e.body){let r=Buffer.from(await e.arrayBuffer());if(r.length>t)throw new a("COMMAND_FAILED",`${n} download exceeds size limit`,{contentLength:r.length,maxBytes:t});return r}let i=e.body.getReader(),o=[],s=0;try{for(;;){let{done:e,value:r}=await i.read();if(e)break;if((s+=r.byteLength)>t)throw new a("COMMAND_FAILED",`${n} download exceeds size limit`,{contentLength:s,maxBytes:t});o.push(Buffer.from(r))}}finally{i.releaseLock()}return Buffer.concat(o,s)}function A(e){let t=function(e,t){if(!Array.isArray(e)||!e.every(e=>"string"==typeof e))throw new a("INVALID_ARGS",`Android snapshot helper manifest ${t} must be a string array.`);return e}(e,"installArgs");if("install"!==t[0])throw new a("INVALID_ARGS",'Android snapshot helper manifest installArgs must start with "install".');if(t.some(e=>e.includes("\0")))throw new a("INVALID_ARGS","Android snapshot helper manifest installArgs must not contain null bytes.");let n=t.slice(1).find(e=>{var t;return t=e,!c.has(t)});if(n)throw new a("INVALID_ARGS",`Android snapshot helper manifest installArgs contains unsupported install flag "${n}".`);return t}async function w(n){return await new Promise((r,i)=>{let a=e.createHash("sha256"),o=t.createReadStream(n);o.on("error",i),o.on("data",e=>a.update(e)),o.on("end",()=>r(a.digest("hex")))})}function g(e,t){if("string"!=typeof e||0===e.trim().length)throw new a("INVALID_ARGS",`Android snapshot helper manifest ${t} is required.`);return e}function N(e){return"string"==typeof e&&e.trim().length>0?e:void 0}function M(e,t){if("number"!=typeof e||!Number.isInteger(e))throw new a("INVALID_ARGS",`Android snapshot helper manifest ${t} must be an integer.`);return e}function b(e,t,n){if(e!==n)throw new a("INVALID_ARGS",`Android snapshot helper manifest ${t} must be "${n}".`);return n}function x(e){let t=`${e??""}`.toLowerCase();return t.includes("scroll")||t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")||t.includes("collectionview")||"table"===t}function v(e){return!!x(e.type)||`${e.role??""} ${e.subrole??""}`.toLowerCase().includes("scroll")}function I(e,t,n){let{sourceNodes:r,...i}=D(_(e),t,n);return i}function D(e,t,n){let r={nodes:[],sourceNodes:[],maxNodes:t,maxDepth:n.depth??1/0,options:n,analysis:function(e){let t=0,n=0,r=[...e.children];for(;r.length>0;){let e=r.pop();t+=1,n=Math.max(n,e.depth),r.push(...e.children)}return{rawNodeCount:t,maxDepth:n}}(e),interactiveDescendantMemo:new Map,truncated:!1},i=n.scope?function(e,t){let n=t.toLowerCase(),r=[...e.children],i=0;for(;i<r.length;){let e=r[i++],t=e.label?.toLowerCase()??"",a=e.value?.toLowerCase()??"",o=e.identifier?.toLowerCase()??"";if(t.includes(n)||a.includes(n)||o.includes(n))return e;r.push(...e.children)}return null}(e,n.scope):null;for(let t of i?[i]:e.children)if(function e(t,n,r,i,a=!1,o=!1){if(t.nodes.length>=t.maxNodes){t.truncated=!0;return}if(r>t.maxDepth)return;let s=t.options.raw||function(e,t,n,r,i){var a,o,s;let l=function(e){let t=T(e.type),n=!!(e.label&&e.label.trim().length>0),r=!!(e.identifier&&e.identifier.trim().length>0);return{type:t,hasMeaningfulText:n&&!O(e.label??""),hasMeaningfulId:r&&!O(e.identifier??""),isStructural:function(e){let t=e.split(".").pop()??e;return t.includes("layout")||"viewgroup"===t||"view"===t}(t),isVisual:"imageview"===t||"imagebutton"===t}}(e);return t.interactiveOnly?function(e,t,n,r,i){var a,o,s,l;return!!(e.hittable||x(t.type)&&r)||(a=t,o=n,s=r,l=i,(!!a.hasMeaningfulText||!!a.hasMeaningfulId)&&!a.isVisual&&(!a.isStructural||!!l)&&(o||s||l))}(e,l,n,r,i):t.compact?l.hasMeaningfulText||l.hasMeaningfulId||!!e.hittable:!l.isStructural&&!l.isVisual||(a=e,o=l,s=r,!!a.hittable||!!o.hasMeaningfulText||!!o.hasMeaningfulId&&!!s||s)}(n,t.options,a,function e(t,n){let r=t.interactiveDescendantMemo.get(n);if(void 0!==r)return r;for(let r of n.children)if(r.hittable||e(t,r))return t.interactiveDescendantMemo.set(n,!0),!0;return t.interactiveDescendantMemo.set(n,!1),!1}(t,n),o)?function(e,t,n,r){let i=e.nodes.length;return e.sourceNodes.push(t),e.nodes.push({index:i,type:t.type??void 0,label:t.label??void 0,value:t.value??void 0,identifier:t.identifier??void 0,rect:t.rect,enabled:t.enabled,hittable:t.hittable,depth:n,parentIndex:r,...t.hiddenContentAbove?{hiddenContentAbove:!0}:{},...t.hiddenContentBelow?{hiddenContentBelow:!0}:{}}),i}(t,n,r,i):i,l=a||!!n.hittable,u=o||function(e){if(!e)return!1;let t=T(e);return t.includes("recyclerview")||t.includes("listview")||t.includes("gridview")}(n.type);for(let i of n.children)if(e(t,i,r+1,s,l,u),t.truncated)return}(r,t,0),r.truncated)break;let a={nodes:r.nodes,sourceNodes:r.sourceNodes,analysis:r.analysis};return r.truncated?{...a,truncated:!0}:a}function S(e){let t=function(e){let t=new Map,n=e.indexOf(" "),r=e.lastIndexOf(">");if(n<0||r<=n)return t;let i=n;for(;i<r&&!((i=C(e,i,r))>=r);){var a;let n=e[i];if("/"===n||">"===n)break;let o=i;for(;i<r&&!("="===(a=e[i]??"")||"/"===a||">"===a||k(a));)i+=1;let s=e.slice(o,i);if(i=C(e,i,r),!s||"="!==e[i])break;i=C(e,i+1,r);let l=e[i];if('"'!==l&&"'"!==l)break;let u=i+=1;for(;i<r&&e[i]!==l;)i+=1;if(i>=r)break;t.set(s,function(e){let t="",n=0;for(;n<e.length;){let r=e.indexOf("&",n);if(r<0){t+=e.slice(n);break}t+=e.slice(n,r);let i=e.indexOf(";",r+1);if(i<0){t+=e.slice(r);break}t+=function(e){switch(e){case"amp":return"&";case"lt":return"<";case"gt":return">";case"quot":return'"';case"apos":return"'";default:return function(e){if(!e.startsWith("#"))return;let t=e[1]?.toLowerCase()==="x"?16:10,n=16===t?e.slice(2):e.slice(1);if(!n||!function(e,t){for(let n of e){let e=n.charCodeAt(0),r=e>=48&&e<=57;if(10===t){if(!r)return!1;continue}let i=e>=65&&e<=70,a=e>=97&&e<=102;if(!r&&!i&&!a)return!1}return!0}(n,t))return;let r=Number.parseInt(n,t);if(Number.isFinite(r))try{return String.fromCodePoint(r)}catch{return}}(e)}}(e.slice(r+1,i))??e.slice(r,i+1),n=i+1}return t}(e.slice(u,i))),i+=1}return t}(e),n=e=>{let n=y(t,e);if(null!==n)return"true"===n};return{text:y(t,"text"),desc:y(t,"content-desc"),resourceId:y(t,"resource-id"),className:y(t,"class"),bounds:y(t,"bounds"),clickable:n("clickable"),enabled:n("enabled"),focusable:n("focusable"),focused:n("focused")}}function C(e,t,n){for(;t<n&&k(e[t]??"");)t+=1;return t}function k(e){return" "===e||"\n"===e||"\r"===e||" "===e}function y(e,t){return e.get(t)??null}function L(e){if(!e)return;let t=/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(e);if(!t)return;let n=Number(t[1]),r=Number(t[2]);return{x:n,y:r,width:Math.max(0,Number(t[3])-n),height:Math.max(0,Number(t[4])-r)}}function _(e){let t={type:null,label:null,value:null,identifier:null,depth:-1,children:[]},n=[t],r=/<node\b[^>]*>|<\/node>/g,i=r.exec(e);for(;i;){let t=i[0];if(t.startsWith("</node")){n.length>1&&n.pop(),i=r.exec(e);continue}let a=S(t),o=L(a.bounds),s=n[n.length-1],l={type:a.className,label:a.text||a.desc,value:a.text,identifier:a.resourceId,rect:o,enabled:a.enabled,hittable:a.clickable??a.focusable,depth:s.depth+1,parentIndex:void 0,children:[]};s.children.push(l),t.endsWith("/>")||n.push(l),i=r.exec(e)}return t}function T(e){return e?e.toLowerCase():""}function O(e){let t=e.trim();return!!t&&/^[\w.]+:id\/[\w.-]+$/i.test(t)}async function E(e){let t,n=e.waitForIdleTimeoutMs??500,r=e.timeoutMs??8e3,i=e.commandTimeoutMs??r+5e3,o=e.maxDepth??128,l=e.maxNodes??5e3,u=e.packageName??s,d=e.instrumentationRunner??`${u}/.SnapshotInstrumentation`,c=["shell","am","instrument","-w","-e","waitForIdleTimeoutMs",String(n),"-e","timeoutMs",String(r),"-e","maxDepth",String(o),"-e","maxNodes",String(l),d],h=await e.adb(c,{allowFailure:!0,timeoutMs:i});try{t=F(`${h.stdout}
|
|
2
|
+
${h.stderr}`)}catch(e){throw new a("COMMAND_FAILED",0===h.exitCode?"Android snapshot helper output could not be parsed":"Android snapshot helper failed before returning parseable output",{stdout:h.stdout,stderr:h.stderr,exitCode:h.exitCode},e)}if(0!==h.exitCode)throw new a("COMMAND_FAILED","Android snapshot helper failed",{stdout:h.stdout,stderr:h.stderr,exitCode:h.exitCode,helper:t.metadata});return t}function F(e){var t,n;let r=function(e){var t;let n={status:[],results:[],currentStatus:null,currentResult:null};for(let t of e.split(/\r?\n/))!function(e,t){if(e.startsWith("INSTRUMENTATION_STATUS: ")){t.currentStatus??={},$(e.slice(24),t.currentStatus);return}if(e.startsWith("INSTRUMENTATION_STATUS_CODE: "))return P(t);if(e.startsWith("INSTRUMENTATION_RESULT: ")){t.currentResult??={},$(e.slice(24),t.currentResult);return}e.startsWith("INSTRUMENTATION_CODE: ")&&U(t)}(t,n);return P(t=n),U(t),{status:n.status,results:n.results}}(e),i=function(e){let t=e.find(e=>e.agentDeviceProtocol===u);if(!t)throw new a("COMMAND_FAILED","Android snapshot helper did not return a final result");if("true"!==t.ok){var n;throw new a("COMMAND_FAILED",(n=t).message&&"null"!==n.message?n.message:n.errorType||"Android snapshot helper returned an error",{errorType:t.errorType,helper:t})}return t}(r.results);return{xml:function(e,t){if(0===e.length)throw new a("COMMAND_FAILED","Android snapshot helper did not return XML chunks",{helper:t});let n=function(e){let t=e[0]?.count??e.length;if(t<1||e.length!==t||e.some(e=>e.count!==t))throw new a("COMMAND_FAILED","Android snapshot helper returned incomplete XML chunks",{expectedChunks:t,actualChunks:e.length});return t}(e),r=Buffer.concat(function(e,t){let n=[];for(let r=0;r<t;r+=1){let i=e.get(r);if(void 0===i)throw new a("COMMAND_FAILED","Android snapshot helper returned incomplete XML chunks",{missingChunkIndex:r,expectedChunks:t});n.push(Buffer.from(i,"base64"))}return n}(function(e,t){let n=new Map;for(let r of e){if(void 0===r.index||r.index<0||r.index>=t)throw new a("COMMAND_FAILED","Android snapshot helper returned invalid chunk index",{chunkIndex:r.index,expectedChunks:t});if(n.has(r.index))throw new a("COMMAND_FAILED","Android snapshot helper returned duplicate XML chunks",{chunkIndex:r.index});n.set(r.index,r.payloadBase64)}return n}(e,n),n)).toString("utf8");if(!r.includes("<hierarchy")||!r.includes("</hierarchy>"))throw new a("COMMAND_FAILED","Android snapshot helper output did not contain XML",{xml:r});return r}(r.status.filter(e=>e.agentDeviceProtocol===u&&e.outputFormat===d&&"string"==typeof e.payloadBase64).map(e=>({index:H(e.chunkIndex),count:H(e.chunkCount),payloadBase64:e.payloadBase64})),i),metadata:{helperApiVersion:(t=i).helperApiVersion,outputFormat:d,waitForIdleTimeoutMs:H(t.waitForIdleTimeoutMs),timeoutMs:H(t.timeoutMs),maxDepth:H(t.maxDepth),maxNodes:H(t.maxNodes),rootPresent:B(t.rootPresent),captureMode:"interactive-windows"===(n=t.captureMode)||"active-window"===n?n:void 0,windowCount:H(t.windowCount),nodeCount:H(t.nodeCount),truncated:B(t.truncated),elapsedMs:H(t.elapsedMs)}}}function R(e,t={outputFormat:d},n={},r=800){return{...I(e,r,n),metadata:t}}function P(e){e.currentStatus&&(e.status.push(e.currentStatus),e.currentStatus=null)}function U(e){e.currentResult&&(e.results.push(e.currentResult),e.currentResult=null)}function $(e,t){let n=e.indexOf("=");n<0||(t[e.slice(0,n)]=e.slice(n+1))}function H(e){if(void 0===e)return;let t=Number(e);return Number.isFinite(t)?t:void 0}function B(e){return"true"===e||"false"!==e&&void 0}async function V(e){var t,n,r;let{adb:i,artifact:o}=e,s=e.installPolicy??"missing-or-outdated",l=o.manifest.packageName,u=o.manifest.versionCode;if("never"===s)return{packageName:l,versionCode:u,installed:!1,reason:"skipped"};let d=await G(i,l,e.timeoutMs),c=(t=s,n=d,r=u,"never"===t?"skipped":"always"===t?"forced":void 0===n?"missing":n<r?"outdated":"current");if("current"===c)return{packageName:l,versionCode:u,installedVersionCode:d,installed:!1,reason:c};await h(o);let f=[...A(o.manifest.installArgs),o.apkPath],p=await W(i,f,{packageName:l,timeoutMs:e.timeoutMs});if(0!==p.exitCode)throw new a("COMMAND_FAILED","Failed to install Android snapshot helper",{packageName:l,versionCode:u,stdout:p.stdout,stderr:p.stderr,exitCode:p.exitCode});return{packageName:l,versionCode:u,installedVersionCode:d,installed:!0,reason:c}}async function G(e,t,n){let r=await e(["shell","cmd","package","list","packages","--show-versioncode",t],{allowFailure:!0,timeoutMs:n});if(0===r.exitCode){var i=`${r.stdout}
|
|
3
|
+
${r.stderr}`,a=t;let e=`package:${a}`;for(let t of i.split(/\r?\n/)){if(!t.startsWith(e)||t.length>e.length&&!/\s/.test(t[e.length]??""))continue;let n=/(?:^|\s)versionCode:(\d+)(?:\s|$)/.exec(t);if(n)return Number(n[1])}return}}async function W(e,t,n){var r;let i=await e(t,{allowFailure:!0,timeoutMs:n.timeoutMs});if(0===i.exitCode||(r=i,!`${r.stdout}
|
|
4
|
+
${r.stderr}`.includes("INSTALL_FAILED_UPDATE_INCOMPATIBLE")))return i;let a=await e(["uninstall",n.packageName],{allowFailure:!0,timeoutMs:n.timeoutMs}),o=await e(t,{allowFailure:!0,timeoutMs:n.timeoutMs});return 0===o.exitCode?o:{...o,stderr:[o.stderr,a.stderr?`Previous uninstall stderr after INSTALL_FAILED_UPDATE_INCOMPATIBLE: ${a.stderr}`:""].filter(Boolean).join("\n")}}export{o as ANDROID_SNAPSHOT_HELPER_NAME,d as ANDROID_SNAPSHOT_HELPER_OUTPUT_FORMAT,s as ANDROID_SNAPSHOT_HELPER_PACKAGE,u as ANDROID_SNAPSHOT_HELPER_PROTOCOL,l as ANDROID_SNAPSHOT_HELPER_RUNNER,D as buildUiHierarchySnapshot,E as captureAndroidSnapshotWithHelper,V as ensureAndroidSnapshotHelper,v as isScrollableNodeLike,x as isScrollableType,p as parseAndroidSnapshotHelperManifest,F as parseAndroidSnapshotHelperOutput,R as parseAndroidSnapshotHelperXml,L as parseBounds,I as parseUiHierarchy,_ as parseUiHierarchyTree,f as prepareAndroidSnapshotHelperArtifactFromManifestUrl,S as readNodeAttributes,h as verifyAndroidSnapshotHelperArtifact};
|
package/dist/src/2301.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let
|
|
1
|
+
let N="--agent-device-run-metro-companion",_="--agent-device-run-react-devtools-companion",E="AGENT_DEVICE_COMPANION_TUNNEL_SERVER_BASE_URL",O="AGENT_DEVICE_COMPANION_TUNNEL_BEARER_TOKEN",A="AGENT_DEVICE_COMPANION_TUNNEL_LOCAL_BASE_URL",T="AGENT_DEVICE_COMPANION_TUNNEL_LAUNCH_URL",C="AGENT_DEVICE_COMPANION_TUNNEL_STATE_PATH",I="AGENT_DEVICE_COMPANION_TUNNEL_SCOPE_TENANT_ID",P="AGENT_DEVICE_COMPANION_TUNNEL_SCOPE_RUN_ID",U="AGENT_DEVICE_COMPANION_TUNNEL_SCOPE_LEASE_ID",L="AGENT_DEVICE_COMPANION_TUNNEL_REGISTER_PATH",e="AGENT_DEVICE_COMPANION_TUNNEL_UNREGISTER_PATH",n="AGENT_DEVICE_COMPANION_TUNNEL_DEVICE_PORT",R="AGENT_DEVICE_COMPANION_TUNNEL_SESSION";class r extends Error{name="MissingCompanionEnvError"}function M(N){let _=N.length;for(;_>0&&47===N.charCodeAt(_-1);)_-=1;return _===N.length?N:N.slice(0,_)}function V(N,_){let E=new URL(`${M(N)}/index.bundle`);return E.searchParams.set("platform",_),E.searchParams.set("dev","true"),E.searchParams.set("minify","false"),E.toString()}export{O as ENV_COMPANION_TUNNEL_BEARER_TOKEN,n as ENV_COMPANION_TUNNEL_DEVICE_PORT,T as ENV_COMPANION_TUNNEL_LAUNCH_URL,A as ENV_COMPANION_TUNNEL_LOCAL_BASE_URL,L as ENV_COMPANION_TUNNEL_REGISTER_PATH,U as ENV_COMPANION_TUNNEL_SCOPE_LEASE_ID,P as ENV_COMPANION_TUNNEL_SCOPE_RUN_ID,I as ENV_COMPANION_TUNNEL_SCOPE_TENANT_ID,E as ENV_COMPANION_TUNNEL_SERVER_BASE_URL,R as ENV_COMPANION_TUNNEL_SESSION,C as ENV_COMPANION_TUNNEL_STATE_PATH,e as ENV_COMPANION_TUNNEL_UNREGISTER_PATH,N as METRO_COMPANION_RUN_ARG,r as MissingCompanionEnvError,_ as REACT_DEVTOOLS_COMPANION_RUN_ARG,V as buildBundleUrl,M as normalizeBaseUrl};
|