@ceraph/react-native-mcp 0.3.3 → 0.4.6
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 +335 -68
- package/dist/babel-plugin/index.cjs +1 -0
- package/dist/babel-plugin/index.js +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +1 -47
- package/dist/index.d.ts +106 -1
- package/dist/index.js +2 -1651
- package/dist/shim/async-storage-ops.d.ts +26 -0
- package/dist/shim/async-storage-ops.js +1 -0
- package/dist/shim/boot.d.ts +9 -0
- package/dist/shim/boot.js +1 -141
- package/dist/shim/camera.js +1 -62
- package/dist/shim/command-poll.d.ts +18 -0
- package/dist/shim/command-poll.js +1 -0
- package/dist/shim/config.js +1 -56
- package/dist/shim/console-capture.d.ts +16 -0
- package/dist/shim/console-capture.js +1 -0
- package/dist/shim/deep-link.js +1 -25
- package/dist/shim/dev-guard.js +1 -3
- package/dist/shim/dev-host.d.ts +1 -0
- package/dist/shim/dev-host.js +1 -0
- package/dist/shim/error-handler.js +1 -66
- package/dist/shim/fetch-interceptor.js +1 -93
- package/dist/shim/index.d.ts +3 -0
- package/dist/shim/index.js +1 -6
- package/dist/shim/keep-awake.js +1 -118
- package/dist/shim/network-ownership.d.ts +4 -0
- package/dist/shim/network-ownership.js +1 -0
- package/dist/shim/optimistic-observer.d.ts +29 -0
- package/dist/shim/optimistic-observer.js +1 -0
- package/dist/shim/reload.js +1 -76
- package/dist/shim/reset.d.ts +30 -0
- package/dist/shim/reset.js +1 -0
- package/dist/shim/signal-capture.d.ts +8 -0
- package/dist/shim/signal-capture.js +1 -15
- package/dist/shim/signal-transport.d.ts +14 -1
- package/dist/shim/signal-transport.js +1 -43
- package/dist/shim/xhr-interceptor.d.ts +39 -0
- package/dist/shim/xhr-interceptor.js +1 -0
- package/package.json +40 -11
- package/dist/app-lifecycle.d.ts +0 -50
- package/dist/app-lifecycle.js +0 -487
- package/dist/camera-image-writer.d.ts +0 -43
- package/dist/camera-image-writer.js +0 -280
- package/dist/camera-registry-sync.d.ts +0 -18
- package/dist/camera-registry-sync.js +0 -117
- package/dist/device-autonomy.d.ts +0 -30
- package/dist/device-autonomy.js +0 -117
- package/dist/error-parser.d.ts +0 -51
- package/dist/error-parser.js +0 -275
- package/dist/expo-manager.d.ts +0 -62
- package/dist/expo-manager.js +0 -447
- package/dist/init/ast-camera.d.ts +0 -29
- package/dist/init/ast-camera.js +0 -267
- package/dist/init/ast-layout.d.ts +0 -15
- package/dist/init/ast-layout.js +0 -167
- package/dist/init/claude-hook-constants.d.ts +0 -9
- package/dist/init/claude-hook-constants.js +0 -91
- package/dist/init/lan-ip.d.ts +0 -11
- package/dist/init/lan-ip.js +0 -51
- package/dist/init/monorepo.d.ts +0 -13
- package/dist/init/monorepo.js +0 -185
- package/dist/init/oauth.d.ts +0 -52
- package/dist/init/oauth.js +0 -220
- package/dist/init/package-manager.d.ts +0 -11
- package/dist/init/package-manager.js +0 -60
- package/dist/init/prompt.d.ts +0 -12
- package/dist/init/prompt.js +0 -68
- package/dist/init/shell-profile.d.ts +0 -22
- package/dist/init/shell-profile.js +0 -85
- package/dist/init/steps.d.ts +0 -135
- package/dist/init/steps.js +0 -399
- package/dist/init/url-scheme.d.ts +0 -42
- package/dist/init/url-scheme.js +0 -187
- package/dist/init/walkthrough.d.ts +0 -76
- package/dist/init/walkthrough.js +0 -340
- package/dist/init.d.ts +0 -8
- package/dist/init.js +0 -395
- package/dist/iproxy-manager.d.ts +0 -32
- package/dist/iproxy-manager.js +0 -216
- package/dist/mac-caffeinate.d.ts +0 -10
- package/dist/mac-caffeinate.js +0 -56
- package/dist/permission-interceptor.d.ts +0 -29
- package/dist/permission-interceptor.js +0 -185
- package/dist/prebuild-detector.d.ts +0 -19
- package/dist/prebuild-detector.js +0 -174
- package/dist/preflight.d.ts +0 -34
- package/dist/preflight.js +0 -847
- package/dist/screen.d.ts +0 -184
- package/dist/screen.js +0 -931
- package/dist/signal-listener.d.ts +0 -27
- package/dist/signal-listener.js +0 -135
- package/dist/simulator-boot.d.ts +0 -52
- package/dist/simulator-boot.js +0 -227
- package/dist/target.d.ts +0 -48
- package/dist/target.js +0 -267
- package/dist/uninstall/cli-runner.d.ts +0 -32
- package/dist/uninstall/cli-runner.js +0 -223
- package/dist/uninstall/footprint.d.ts +0 -40
- package/dist/uninstall/footprint.js +0 -288
- package/dist/uninstall/mcp-tools.d.ts +0 -14
- package/dist/uninstall/mcp-tools.js +0 -175
- package/dist/uninstall/revert-auth.d.ts +0 -22
- package/dist/uninstall/revert-auth.js +0 -31
- package/dist/uninstall/revert-boot.d.ts +0 -24
- package/dist/uninstall/revert-boot.js +0 -242
- package/dist/uninstall/revert-camera.d.ts +0 -12
- package/dist/uninstall/revert-camera.js +0 -199
- package/dist/uninstall/revert-ceraph-dir.d.ts +0 -27
- package/dist/uninstall/revert-ceraph-dir.js +0 -38
- package/dist/uninstall/revert-claude-hooks.d.ts +0 -19
- package/dist/uninstall/revert-claude-hooks.js +0 -191
- package/dist/uninstall/revert-gitignore.d.ts +0 -17
- package/dist/uninstall/revert-gitignore.js +0 -43
- package/dist/uninstall/revert-mcp-clients.d.ts +0 -57
- package/dist/uninstall/revert-mcp-clients.js +0 -194
- package/dist/uninstall/revert-package.d.ts +0 -34
- package/dist/uninstall/revert-package.js +0 -98
- package/dist/uninstall/revert-scheme.d.ts +0 -36
- package/dist/uninstall/revert-scheme.js +0 -139
- package/dist/uninstall/revert-signal-host-env.d.ts +0 -31
- package/dist/uninstall/revert-signal-host-env.js +0 -61
- package/dist/uninstall/walkthrough.d.ts +0 -80
- package/dist/uninstall/walkthrough.js +0 -1244
- package/dist/utils/atomic-write.d.ts +0 -1
- package/dist/utils/atomic-write.js +0 -30
- package/dist/wait-for-device.d.ts +0 -68
- package/dist/wait-for-device.js +0 -368
- package/dist/wda-manager.d.ts +0 -38
- package/dist/wda-manager.js +0 -186
- package/dist/wda-simulator.d.ts +0 -28
- package/dist/wda-simulator.js +0 -257
package/README.md
CHANGED
|
@@ -1,46 +1,140 @@
|
|
|
1
1
|
# @ceraph/react-native-mcp
|
|
2
2
|
|
|
3
|
-
MCP server for React Native and Expo development. Automatic build error capture, console monitoring, reliable screen interactions, prebuild detection
|
|
3
|
+
MCP server for React Native and Expo development. Automatic build error capture, console monitoring, reliable screen interactions, and prebuild detection.
|
|
4
4
|
|
|
5
5
|
Works with any MCP client: Claude Code, Cursor, Codex, Windsurf, and others.
|
|
6
6
|
|
|
7
7
|
> ⚠️ **Ignore the `npm i` line above.** Run this once instead:
|
|
8
8
|
>
|
|
9
9
|
> ```bash
|
|
10
|
-
> npx @ceraph/react-native-mcp init
|
|
10
|
+
> npx @ceraph/react-native-mcp@latest init
|
|
11
11
|
> ```
|
|
12
12
|
|
|
13
|
+
## Platform support
|
|
14
|
+
|
|
15
|
+
This MCP is **iOS-only**. Both a real iOS device (the original supported configuration) and a booted iOS Simulator now work. The runtime path (devicectl / idb / WebDriverAgent / simctl) is Apple-specific.
|
|
16
|
+
|
|
17
|
+
| | Status | Notes |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| iOS device (USB) | Supported | WebDriverAgent installs through Xcode (one-time); WDA is reached at `localhost:8100`. Default — no env var needed. |
|
|
20
|
+
| iOS Simulator | Supported | `ceraph_start` installs the optional `appium-webdriveragent` dep and builds + launches the WDA automatically the first time you target a simulator. See [Simulator setup](#simulator-setup) below. |
|
|
21
|
+
| Android (device or emulator) | Not supported | |
|
|
22
|
+
| Linux / Windows host | Not supported | |
|
|
23
|
+
| Expo Go | Not supported | Needs dev client or prebuilt app. |
|
|
24
|
+
|
|
25
|
+
If you don't have a Mac with Xcode, stop here — nothing in this package will work for you yet.
|
|
26
|
+
|
|
27
|
+
### Choosing device vs simulator
|
|
28
|
+
|
|
29
|
+
By default the MCP auto-detects: if a real device is connected, it uses the device; otherwise it uses a booted simulator. Override explicitly:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export CERAPH_TARGET=device # always use real device
|
|
33
|
+
export CERAPH_TARGET=simulator # always use simulator
|
|
34
|
+
export CERAPH_TARGET=auto # default behavior (also: unset)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `rn_target_status` tool reports which target is currently active and the resolved WDA base URL.
|
|
38
|
+
|
|
13
39
|
## Requirements
|
|
14
40
|
|
|
15
41
|
- macOS with Xcode 14+ (Command Line Tools installed)
|
|
16
|
-
- iOS 16+ device
|
|
42
|
+
- iOS 16+ device connected to the Mac over USB and unlocked **OR** a booted iOS Simulator
|
|
17
43
|
- Node.js 18+
|
|
18
|
-
- [
|
|
44
|
+
- [WebDriverAgent](https://github.com/appium/WebDriverAgent) running and reachable. On a real device, this is `localhost:8100` (port-forwarded from the device by Xcode / `iproxy`). On a simulator, the port is captured at runtime from `rn_wda_start` — see [Simulator setup](#simulator-setup).
|
|
19
45
|
- Expo dev client or prebuilt app (Expo Go is not supported)
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
> **Multi-developer Macs:** The runtime signal listener binds to `localhost:8101` (WDA uses 8100, Metro uses 8081). Only one developer on a given Mac can run the MCP at a time without a port collision. If you share a Mac and need parallel sessions, override the port via `CERAPH_SIGNAL_PORT` when starting the MCP server.
|
|
48
|
+
|
|
49
|
+
### Environment variables
|
|
50
|
+
|
|
51
|
+
| Variable | Default | Purpose |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `CERAPH_SIGNAL_HOST` | auto-detected | Optional override for the host the on-device signal capture posts to. It auto-discovers the Mac's address from the Metro dev-server URL at runtime (`SourceCode.scriptURL` — `localhost` on a simulator, the LAN IP on a real device), and the MCP server re-detects the LAN IP at startup for its own listener binding. You only set this for unusual network setups where the dev-server host isn't reachable for the signal channel (e.g. `192.168.1.x`). Leave it unset for normal use. |
|
|
54
|
+
| `CERAPH_SIGNAL_PORT` | `8101` | Port the signal listener binds to on the Mac. Change when 8101 is taken. |
|
|
55
|
+
| `CERAPH_TARGET` | `auto` | Which iOS runtime to drive. `device` forces the real-device path even if a simulator is booted; `simulator` forces simctl/sim-WDA even if a device is connected; `auto` prefers device when both are available. |
|
|
56
|
+
|
|
57
|
+
## Simulator setup
|
|
58
|
+
|
|
59
|
+
On a simulator, the WDA needs the optional `appium-webdriveragent` package — `ceraph_start` (and `rn_wda_start`) install it automatically the first time you target a simulator, then build and launch the WDA for you. The first build is a one-time ~1 minute; later runs reuse the cache at `<project>/.ceraph/wda-derived/`. It's an optional dep, so device-only setups skip the ~200 MB download.
|
|
60
|
+
|
|
61
|
+
Boot any iPhone simulator (iOS ≥ 16) and run `ceraph_start`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
open -a Simulator
|
|
65
|
+
# or: xcrun simctl boot <UDID>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`xcrun simctl list devices booted` shows what's currently up.
|
|
69
|
+
|
|
70
|
+
Once WDA is up, every `screen_*`, `app_*`, and `rn_test_*` tool routes to the simulator automatically. Call `rn_wda_stop` to tear it down (or just exit the MCP — shutdown stops the xcodebuild child cleanly).
|
|
71
|
+
|
|
72
|
+
**Concurrent device + simulator:** The auto-detection prefers a connected device. To test on the simulator while a phone is plugged in, set `CERAPH_TARGET=simulator` in the MCP process environment.
|
|
22
73
|
|
|
23
74
|
## Quick Setup
|
|
24
75
|
|
|
25
76
|
Run this from your project root:
|
|
26
77
|
|
|
27
78
|
```bash
|
|
28
|
-
npx @ceraph/react-native-mcp init
|
|
79
|
+
npx @ceraph/react-native-mcp@latest init
|
|
29
80
|
```
|
|
30
81
|
|
|
31
|
-
|
|
82
|
+
This automatically:
|
|
83
|
+
- Configures MCP servers for all detected clients (Claude Code, Cursor, Codex, VS Code, Windsurf, Antigravity)
|
|
84
|
+
- Installs a Claude Code hook that injects runtime errors into your conversation automatically
|
|
85
|
+
- Adds `.rn-errors.json` to your `.gitignore`
|
|
86
|
+
- Signs you in (browser OAuth) so the autonomous flow tools can reach the Ceraph server
|
|
32
87
|
|
|
33
|
-
## Uninstalling
|
|
88
|
+
## Uninstalling Ceraph
|
|
34
89
|
|
|
35
90
|
```bash
|
|
36
91
|
npx @ceraph/react-native-mcp uninstall
|
|
37
92
|
```
|
|
38
93
|
|
|
39
|
-
|
|
94
|
+
Non-destructive by default: it reverses what `init` did and reports a per-step status, leaving your camera test images and OAuth token in place so you can re-install without losing state. Only `@ceraph/react-native-mcp` is removed from `package.json`.
|
|
95
|
+
|
|
96
|
+
### Flags
|
|
97
|
+
|
|
98
|
+
| Flag | Effect |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| `-y`, `--yes` | Skip confirmation prompts. |
|
|
101
|
+
| `--purge-images` | Also delete `.ceraph/` (camera images, design snapshots, run artefacts). |
|
|
102
|
+
| `--global` | Also delete `~/.ceraph/auth.json`, signing you out of every Ceraph install on the machine. |
|
|
103
|
+
| `--dry-run` | Preview the step list without writing anything. |
|
|
104
|
+
| `--project-dir <path>` | Operate against a specific subpackage. Required when a monorepo has more than one React Native app. |
|
|
105
|
+
|
|
106
|
+
Or have your assistant drive `ceraph_uninstall_status` / `ceraph_uninstall_run` — call with `dryRun: true` first to preview.
|
|
107
|
+
|
|
108
|
+
### Not auto-reverted
|
|
109
|
+
|
|
110
|
+
A few mutations are hand-edits the uninstaller can't safely make for you. Each surfaces as a `manual` step with the exact lines to remove:
|
|
111
|
+
|
|
112
|
+
- **Bare React Native `Info.plist` and `AndroidManifest.xml` scheme entries.**
|
|
113
|
+
- **Expo dynamic config (`app.config.js` / `app.config.ts`)** — the file path and scheme value to remove.
|
|
114
|
+
- **`useEffect` that contains `installCeraph()` alongside your own statements** — the file and line to edit.
|
|
115
|
+
|
|
116
|
+
## Automatic auto-lock (optional)
|
|
117
|
+
|
|
118
|
+
If your consumer app has `expo-keep-awake` (Expo SDK installs it by default) or `react-native-keep-awake`, Ceraph activates it in `__DEV__` so your iPhone screen stays awake during Ceraph runs. It subscribes to `AppState` and releases the keep-awake when the app backgrounds, then re-acquires on foreground. Production builds tree-shake the entire path.
|
|
119
|
+
|
|
120
|
+
To add the optional dep to an Expo app:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx expo install expo-keep-awake
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### USB bridge (real-device only)
|
|
127
|
+
|
|
128
|
+
Real iOS devices reach WebDriverAgent through `iproxy 8100 8100 -u <udid>` (libimobiledevice). The MCP's doctor auto-spawns this when:
|
|
129
|
+
|
|
130
|
+
1. WDA at `localhost:8100` is unreachable, AND
|
|
131
|
+
2. A real iOS device is connected (detected via `xcrun devicectl`)
|
|
132
|
+
|
|
133
|
+
If `iproxy` isn't installed, doctor surfaces the remediation: `brew install libimobiledevice`. The auto-spawned child is killed cleanly on MCP server SIGINT / SIGTERM, so it doesn't hang around between sessions. Already-running iproxy (from Xcode's automatic forwarding, or a prior session) is detected and reused, not re-launched.
|
|
40
134
|
|
|
41
135
|
## Manual Setup
|
|
42
136
|
|
|
43
|
-
If you prefer to configure manually
|
|
137
|
+
If you prefer to configure manually, follow the instructions for your client below.
|
|
44
138
|
|
|
45
139
|
### Claude Code
|
|
46
140
|
|
|
@@ -49,34 +143,34 @@ Add to `.mcp.json` in your project root:
|
|
|
49
143
|
```json
|
|
50
144
|
{
|
|
51
145
|
"mcpServers": {
|
|
52
|
-
"
|
|
53
|
-
|
|
146
|
+
"react-native-mcp": {
|
|
147
|
+
"command": "npx",
|
|
148
|
+
"args": ["-y", "@ceraph/react-native-mcp@latest"]
|
|
149
|
+
}
|
|
54
150
|
}
|
|
55
151
|
}
|
|
56
152
|
```
|
|
57
153
|
|
|
58
154
|
### Cursor
|
|
59
155
|
|
|
60
|
-
Add to `.cursor/mcp.json
|
|
156
|
+
Add to `.cursor/mcp.json` in your project root:
|
|
61
157
|
|
|
62
158
|
```json
|
|
63
159
|
{
|
|
64
160
|
"mcpServers": {
|
|
65
|
-
"
|
|
66
|
-
|
|
161
|
+
"react-native-mcp": {
|
|
162
|
+
"command": "npx",
|
|
163
|
+
"args": ["-y", "@ceraph/react-native-mcp@latest"]
|
|
164
|
+
}
|
|
67
165
|
}
|
|
68
166
|
}
|
|
69
167
|
```
|
|
70
168
|
|
|
71
169
|
### Codex
|
|
72
170
|
|
|
73
|
-
Add to `.codex/config.toml
|
|
171
|
+
Add to `.codex/config.toml` in your project root:
|
|
74
172
|
|
|
75
173
|
```toml
|
|
76
|
-
[mcp_servers.mobile-mcp]
|
|
77
|
-
command = "npx"
|
|
78
|
-
args = ["-y", "@mobilenext/mobile-mcp@latest"]
|
|
79
|
-
|
|
80
174
|
[mcp_servers.react-native-mcp]
|
|
81
175
|
command = "npx"
|
|
82
176
|
args = ["-y", "@ceraph/react-native-mcp@latest"]
|
|
@@ -84,13 +178,15 @@ args = ["-y", "@ceraph/react-native-mcp@latest"]
|
|
|
84
178
|
|
|
85
179
|
### VS Code / Copilot
|
|
86
180
|
|
|
87
|
-
Add to `.vscode/mcp.json
|
|
181
|
+
Add to `.vscode/mcp.json` in your project root:
|
|
88
182
|
|
|
89
183
|
```json
|
|
90
184
|
{
|
|
91
185
|
"mcpServers": {
|
|
92
|
-
"
|
|
93
|
-
|
|
186
|
+
"react-native-mcp": {
|
|
187
|
+
"command": "npx",
|
|
188
|
+
"args": ["-y", "@ceraph/react-native-mcp@latest"]
|
|
189
|
+
}
|
|
94
190
|
}
|
|
95
191
|
}
|
|
96
192
|
```
|
|
@@ -102,42 +198,49 @@ Add to `~/.codeium/windsurf/mcp_config.json` (or Settings → Advanced Settings
|
|
|
102
198
|
```json
|
|
103
199
|
{
|
|
104
200
|
"mcpServers": {
|
|
105
|
-
"
|
|
106
|
-
|
|
201
|
+
"react-native-mcp": {
|
|
202
|
+
"command": "npx",
|
|
203
|
+
"args": ["-y", "@ceraph/react-native-mcp@latest"]
|
|
204
|
+
}
|
|
107
205
|
}
|
|
108
206
|
}
|
|
109
207
|
```
|
|
110
208
|
|
|
111
209
|
### Antigravity
|
|
112
210
|
|
|
113
|
-
Add to `~/.gemini/antigravity/mcp_config.json
|
|
211
|
+
Add to `~/.gemini/antigravity/mcp_config.json` (or Manage MCP Servers → View raw config):
|
|
114
212
|
|
|
115
213
|
```json
|
|
116
214
|
{
|
|
117
215
|
"mcpServers": {
|
|
118
|
-
"
|
|
119
|
-
|
|
216
|
+
"react-native-mcp": {
|
|
217
|
+
"command": "npx",
|
|
218
|
+
"args": ["-y", "@ceraph/react-native-mcp@latest"]
|
|
219
|
+
}
|
|
120
220
|
}
|
|
121
221
|
}
|
|
122
222
|
```
|
|
123
223
|
|
|
124
224
|
### Cline / Roo Code (VS Code extensions)
|
|
125
225
|
|
|
126
|
-
VS Code Settings → Extensions → Cline (or Roo Code) → MCP Servers:
|
|
226
|
+
Open VS Code Settings → Extensions → Cline (or Roo Code) → MCP Servers, then add:
|
|
127
227
|
|
|
128
228
|
```json
|
|
129
229
|
{
|
|
130
|
-
"
|
|
131
|
-
|
|
230
|
+
"react-native-mcp": {
|
|
231
|
+
"command": "npx",
|
|
232
|
+
"args": ["-y", "@ceraph/react-native-mcp@latest"]
|
|
233
|
+
}
|
|
132
234
|
}
|
|
133
235
|
```
|
|
134
236
|
|
|
135
237
|
### JetBrains IDEs
|
|
136
238
|
|
|
137
|
-
Settings → Tools → MCP Servers → Add:
|
|
239
|
+
Settings → Tools → MCP Servers → Add, then enter:
|
|
138
240
|
|
|
139
|
-
- **Name:** `react-native-mcp`
|
|
140
|
-
-
|
|
241
|
+
- **Name:** `react-native-mcp`
|
|
242
|
+
- **Command:** `npx`
|
|
243
|
+
- **Args:** `-y @ceraph/react-native-mcp@latest`
|
|
141
244
|
|
|
142
245
|
## Tools
|
|
143
246
|
|
|
@@ -145,59 +248,223 @@ Settings → Tools → MCP Servers → Add:
|
|
|
145
248
|
|
|
146
249
|
| Tool | Description |
|
|
147
250
|
|---|---|
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
251
|
+
| `ceraph_start` | **Call this first.** Brings a connected iPhone to ready-to-test in one call: Metro, then build/install + foreground the app (keep-awake shim) and start WebDriverAgent concurrently, then verify on-device automation. Runs only what's missing, stops at the first failed gate with its remediation. Real device only. |
|
|
252
|
+
| `ceraph_doctor` | Diagnose readiness before flows run: WDA reachable, device connected + awake, app installed, required env vars present, plus camera-shim setup (unwrapped `<CameraView>`, missing or orphaned `imageKey`). Returns structured failing checks with their remediation. `ceraph_start` runs it automatically as its final verify stage. |
|
|
253
|
+
| `rn_build_ios` | Build the app with `expo run:ios`. Captures Xcode output and returns structured errors (file, line, message, type). Optionally runs `prebuild --clean` first. |
|
|
254
|
+
| `rn_start` | Start Metro dev server. Monitors console output for runtime errors, JS exceptions, and red screens. |
|
|
255
|
+
| `rn_get_errors` | Return all captured build and runtime errors without re-running anything. |
|
|
256
|
+
| `rn_get_console` | Return recent Metro console output, filtered by log level. |
|
|
257
|
+
| `rn_check_prebuild` | Detect if `prebuild --clean` is needed by diffing `package.json`, `app.json`, and `Podfile.lock` against the last successful build. |
|
|
153
258
|
| `rn_stop` | Kill all managed React Native processes. |
|
|
154
|
-
| `rn_reload` | Trigger a JS bundle reload via `DevSettings.reload()` on the running app. |
|
|
155
259
|
|
|
156
260
|
### Target & Simulator
|
|
157
261
|
|
|
158
262
|
| Tool | Description |
|
|
159
263
|
|---|---|
|
|
160
|
-
| `rn_target_status` | Report
|
|
161
|
-
| `rn_wda_start` | Build + launch WebDriverAgent
|
|
162
|
-
| `rn_wda_stop` | Stop the simulator WDA session
|
|
163
|
-
| `rn_boot_simulator` | Boot a simulator (defaults to newest available iPhone runtime). |
|
|
164
|
-
| `rn_wait_for_device` | Wait for a real iOS device to appear over USB (event-driven via usbmuxd, no timeout). |
|
|
264
|
+
| `rn_target_status` | Report which iOS target (device vs simulator) is currently selected, the resolved WDA base URL, and whether a simulator WDA session is running. |
|
|
265
|
+
| `rn_wda_start` | Build + launch WebDriverAgent against a booted iOS simulator (requires the optional `appium-webdriveragent` dep). Captures the WDA port and routes every subsequent `screen_*` / `app_*` / `rn_test_*` tool to the simulator automatically. Idempotent — re-calling returns the existing session. Real-device users don't need this tool. |
|
|
266
|
+
| `rn_wda_stop` | Stop the simulator WDA session started by `rn_wda_start`. Idempotent. Real-device WDA is unaffected. |
|
|
165
267
|
|
|
166
268
|
### Screen Interaction
|
|
167
269
|
|
|
168
270
|
| Tool | Description |
|
|
169
271
|
|---|---|
|
|
170
|
-
| `screen_tap` | Tap at coordinates with automatic pixel
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `screen_press_key` | Press `home`, `volumeUp`, `volumeDown`,
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `screen_wait_for` | Poll the source tree until an element appears or disappears. |
|
|
181
|
-
| `screen_assert_visible` | One-shot visibility check (`invert: true` for assert-not-visible). |
|
|
272
|
+
| `screen_tap` | Tap at coordinates with automatic pixel ratio correction. Screenshot coordinates are divided by the device pixel ratio (2x/3x) so taps land where you expect. |
|
|
273
|
+
| `screen_tap_and_verify` | Find an element by text/label/type, tap its center, and optionally assert a follow-up selector is visible — resolved against one accessibility snapshot in a single W3C action. The reliable way to tap: no coordinate guessing. |
|
|
274
|
+
| `screen_tap_chain` | Tap a sequence of elements as one W3C action, all resolved against a single snapshot (zero gestures sent if any query fails). |
|
|
275
|
+
| `screen_type_into_field` | Focus an input (by text/label/type) and type into it in one W3C action. Set `clearFirst: true` to clear the field before typing. |
|
|
276
|
+
| `screen_swipe` | Swipe up/down/left/right. Defaults to a 60%-of-axis swipe from screen center. |
|
|
277
|
+
| `screen_scroll_to` | Repeatedly swipe until a matching element appears in the accessibility tree (no tap). |
|
|
278
|
+
| `screen_long_press` | Long-press a matched element for a configurable duration. |
|
|
279
|
+
| `screen_press_key` | Press a hardware/system button: `home`, `volumeUp`, `volumeDown`, `lock`. |
|
|
280
|
+
| `screen_open_url` | Open a URL / deep link (e.g. `myapp://product/42`) to jump straight to a screen — useful for testing your app's deep-link routes. |
|
|
281
|
+
| `screen_screenshot` | Capture a screenshot of the current screen. Returns a viewable image block. |
|
|
282
|
+
| `screen_wait_for` | Poll the source tree until an element appears (or disappears). Pass `timeoutMs: 0` for a one-shot visible / not-visible check. |
|
|
182
283
|
|
|
183
284
|
### App Lifecycle & Device
|
|
184
285
|
|
|
185
286
|
| Tool | Description |
|
|
186
287
|
|---|---|
|
|
187
|
-
| `app_launch` | Launch by bundle ID
|
|
188
|
-
| `app_terminate` | Terminate by bundle ID. |
|
|
189
|
-
| `app_activate` |
|
|
190
|
-
| `app_list_installed` | List installed apps (cached 30s). |
|
|
191
|
-
| `app_active` |
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
288
|
+
| `app_launch` | Launch an app by bundle ID. Uses `xcrun devicectl` (Xcode 15+) on a real device, falls back to `idb`, then `simctl`. Terminates any existing instance first. |
|
|
289
|
+
| `app_terminate` | Terminate an app by bundle ID. |
|
|
290
|
+
| `app_activate` | Bring an app to foreground via WDA without a cold restart. |
|
|
291
|
+
| `app_list_installed` | List installed apps on the connected device (cached 30s). |
|
|
292
|
+
| `app_active` | Report the foreground app's bundleId, pid, and name. |
|
|
293
|
+
| `rn_reset_state` | Reset the running app's persisted state — `AsyncStorage.clear` plus any registered cleanup hooks — without a rebuild. Requires `installCeraph` active in the app; dev-only (no-ops in production). |
|
|
294
|
+
| `rn_reload` | Reload the app's JS bundle, wait for it to come back, then capture a screenshot. Requires `installCeraph` active in the app; dev-only. |
|
|
295
|
+
| `device_ensure_awake` | Ensure the device is awake and unlocked. Returns structured remediation when locked. |
|
|
296
|
+
| `device_set_orientation` | Set portrait or landscape. |
|
|
297
|
+
|
|
298
|
+
### Autonomous Flows
|
|
299
|
+
|
|
300
|
+
Server-planned tools that ask Ceraph which flows are relevant, then execute each one locally against the connected iOS device. These three require sign-in (`npx @ceraph/react-native-mcp@latest init` — the token is shared with your Ceraph account) and an active Pro subscription; non-Pro callers get a clear upgrade message pointing to the pricing page.
|
|
301
|
+
|
|
302
|
+
| Tool | Use when |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `rn_list_flows` | You want to preview which flows your working-tree diff (or specific files via `filePaths`) affects, without executing. Each row carries a `flowId` — the handle you pass to `rn_test_flows`. The order rows are returned in isn't significant; pass the `flowId`s you want. `totalCount` is the true matched count — rows are only ever clipped past a high bound, flagged with `truncated: true` plus a hint. The first step for "test my changes". |
|
|
305
|
+
| `rn_find_flows` | You want to find flows app-wide by describing them in natural language ("user logs in") — not scoped to a diff. Returns candidate flows with their `flowId`s; each match's `reason` carries the relevance. |
|
|
306
|
+
| `rn_test_flows` | You have one or more `flowId`s (from `rn_list_flows` / `rn_find_flows`) and want to execute exactly those flows — pass every preview `flowId` for comprehensive verification, batching across calls above the ~10-flowId-per-call cap. Related flows run in one device session. Returns screenshots of the moments that matter — errors, server failures, and the steps your change touches — as viewable `image` blocks, plus any runtime errors. Auth-gated app flows are handled with no credentials from you — Ceraph provisions the managed test users they require automatically. |
|
|
307
|
+
|
|
308
|
+
#### Example: test your uncommitted edits
|
|
309
|
+
|
|
310
|
+
```jsonc
|
|
311
|
+
// 1. Preview the affected flows
|
|
312
|
+
{
|
|
313
|
+
"name": "rn_list_flows",
|
|
314
|
+
"arguments": { "base": "main" }
|
|
315
|
+
}
|
|
316
|
+
// → { "flows": [ { "flowId": "login.home.a1b2c3", "name": "Login", ... } ], ... }
|
|
317
|
+
|
|
318
|
+
// 2. Execute the selection (all rows = comprehensive verification)
|
|
319
|
+
{
|
|
320
|
+
"name": "rn_test_flows",
|
|
321
|
+
"arguments": { "flowIds": ["login.home.a1b2c3"] }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Result shape (JSON summary block)
|
|
325
|
+
{
|
|
326
|
+
"runId": "run_01HX...",
|
|
327
|
+
"status": "planned",
|
|
328
|
+
"results": [
|
|
329
|
+
{
|
|
330
|
+
"flow": "Login",
|
|
331
|
+
"flowId": "login.home.a1b2c3",
|
|
332
|
+
"success": true,
|
|
333
|
+
"totalDurationMs": 8421,
|
|
334
|
+
"steps": [ /* per-step success + status, no base64 */ ],
|
|
335
|
+
"runtimeErrors": []
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Each result entry carries the same shape produced by the underlying flow runner — per-step `success`, status, and any new runtime errors that surfaced during that step's window. The captured screenshots do **not** ride inside this JSON: they come back as separate viewable `image` content blocks (the base64 is stripped out of the summary text to save tokens). Execution stops at the first failed flow and `failedAt` is the index of the flow that broke; surviving flows are not run. When the server has nothing to plan (clean tree, unknown file, missing flow), the result is `{ status: "no-flows", results: [], hint: "..." }`.
|
|
342
|
+
|
|
343
|
+
#### Testing a pull request
|
|
344
|
+
|
|
345
|
+
There is no dedicated PR tool — a PR is just a diff against a base branch, which `rn_list_flows({ base })` already expresses. Compose Ceraph with a GitHub MCP server (or the `gh` CLI) instead:
|
|
346
|
+
|
|
347
|
+
1. **Check out the PR** — `gh pr checkout <N>` (or the GitHub MCP equivalent). This puts the PR's code in your working tree; note its base branch.
|
|
348
|
+
2. **Rebuild and relaunch** — `rn_build_ios`, then `rn_start`, so the device runs the PR's code. Ceraph drives the *running app*, not source on disk: a JS-only change may hot-reload, but a native or dependency change needs a real rebuild before the run reflects the PR.
|
|
349
|
+
3. **Preview, then run** — `rn_list_flows({ base: "<pr-target-branch>" })` lists the flows the PR's changes affect; `rn_test_flows({ flowIds })` runs them (pass every id for full PR coverage, or a subset).
|
|
350
|
+
4. **Report back (optional)** — post the pass/fail summary as a PR comment through the GitHub MCP.
|
|
351
|
+
|
|
352
|
+
### Test users
|
|
353
|
+
|
|
354
|
+
Auth-gated flows are provisioned **automatically** — when `rn_test_flows` hits a login wall, Ceraph mints a managed test user against your auth provider (Clerk / Auth0 / Supabase / Firebase / Cognito) for that run, with no credentials from you. The tools below are for **on-demand** test users — when you want to provision or inspect one yourself (Pro tier):
|
|
355
|
+
|
|
356
|
+
| Tool | Description |
|
|
357
|
+
|---|---|
|
|
358
|
+
| `ceraph_test_data_provision` | Mint a managed test user on demand. Returns email + password (+ a session token when the provider exposes one); 24-hour TTL; optional `tag` to label it. |
|
|
359
|
+
| `ceraph_test_data_list` | List active on-demand test users for the project (excludes the one-run users auto-provisioned during a flow). |
|
|
360
|
+
| `ceraph_test_data_cleanup` | Delete on-demand test users by `id`, or `all: true` to wipe them. |
|
|
361
|
+
|
|
362
|
+
## Camera testing
|
|
363
|
+
|
|
364
|
+
iOS blocks external processes from injecting camera frames, so Ceraph ships a `__DEV__`-gated shim that returns a pre-configured image instead. It's a subpath import of this package, so testing camera-heavy RN flows takes two lines of code.
|
|
365
|
+
|
|
366
|
+
### Installation
|
|
367
|
+
|
|
368
|
+
Nothing extra. The shim ships in the same package — it is exposed via the `@ceraph/react-native-mcp/shim` subpath. `react`, `react-native`, and `expo-camera` are optional peer dependencies; bring whichever versions your app already uses.
|
|
369
|
+
|
|
370
|
+
### Setup
|
|
371
|
+
|
|
372
|
+
**1. Drop test images into `.ceraph/camera-images/`** in your repo root, one file per scenario, with descriptive lowercase filenames. The filename without the extension IS the `imageKey` you reference in code:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
.ceraph/camera-images/profile.jpg → imageKey="profile"
|
|
376
|
+
.ceraph/camera-images/id-card.png → imageKey="id-card"
|
|
377
|
+
.ceraph/camera-images/product.jpg → imageKey="product"
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Supported extensions: `.jpg`, `.jpeg`, `.png`, `.webp`, `.heic`. On collision the order above is the priority. These images are committed to your repo by default so test runs are reproducible across machines and CI.
|
|
381
|
+
|
|
382
|
+
**2. Generate the static registry.** Call the MCP tool `rn_sync_camera_registry` — it scans `.ceraph/camera-images/` and writes `_registry.ts` next to your images with one `require()` per file (Metro requires statically analysable paths, hence the codegen step). `ceraph_doctor` also runs this automatically, so you usually never have to call it by hand.
|
|
383
|
+
|
|
384
|
+
**3. Apply the registry at app boot, gated behind `__DEV__`:**
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
// App.tsx
|
|
388
|
+
import { applyCameraImageRegistry } from "./.ceraph/camera-images/_registry";
|
|
389
|
+
|
|
390
|
+
if (__DEV__) {
|
|
391
|
+
applyCameraImageRegistry();
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
You no longer call `configureTestImage` by hand — the generated registry handles it. The 1-arg `configureTestImage(require(...))` form still works for back-compat, but the recommended workflow is the codegen above.
|
|
396
|
+
|
|
397
|
+
**4. Replace `<CameraView>` with `<CeraphCamera>` and set `imageKey` per screen:**
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
import { CeraphCamera } from "@ceraph/react-native-mcp/shim";
|
|
401
|
+
|
|
402
|
+
export function ScanProfileScreen() {
|
|
403
|
+
return (
|
|
404
|
+
<CeraphCamera
|
|
405
|
+
imageKey="profile"
|
|
406
|
+
style={{ flex: 1 }}
|
|
407
|
+
facing="front"
|
|
408
|
+
ref={cameraRef}
|
|
409
|
+
/>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
The `imageKey` prop selects which image the shim returns when the shutter is triggered. The AI tool consuming the MCP picks the right key per screen at code-write time, based on what that camera screen captures conceptually (face → `"profile"`, ID document → `"id-card"`, etc.). `<CeraphCamera>` takes the same prop shape as `expo-camera`'s `CameraView` and forwards every other prop verbatim. In production builds, `__DEV__` is `false`, `applyCameraImageRegistry` is dead-stripped, the `imageKey` prop is a no-op, and `<CeraphCamera>` lazily requires `expo-camera` and renders the real `<CameraView>` — zero overhead.
|
|
415
|
+
|
|
416
|
+
### Adding test images via MCP
|
|
417
|
+
|
|
418
|
+
Typical loop: the dev pastes an image into chat and says "use this for the handwriting screen." The AI assistant calls `ceraph_add_camera_image` to write the bytes into `.ceraph/camera-images/` and regenerate `_registry.ts` in one step — no manual `cp`, no follow-up call to `rn_sync_camera_registry`.
|
|
419
|
+
|
|
420
|
+
```jsonc
|
|
421
|
+
// Tool call
|
|
422
|
+
{
|
|
423
|
+
"name": "ceraph_add_camera_image",
|
|
424
|
+
"arguments": {
|
|
425
|
+
"imageKey": "handwriting-sample",
|
|
426
|
+
"imageBase64": "iVBORw0KGgoAAAANSUhEUgAA...",
|
|
427
|
+
"contentType": "image/png" // optional hint
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Result shape
|
|
432
|
+
{
|
|
433
|
+
"written": ".ceraph/camera-images/handwriting-sample.png",
|
|
434
|
+
"registry": ".ceraph/camera-images/_registry.ts",
|
|
435
|
+
"registered": ["handwriting-sample", "id-card", "profile"],
|
|
436
|
+
"detectedContentType": "image/png",
|
|
437
|
+
"detectedExt": "png",
|
|
438
|
+
"replaced": false,
|
|
439
|
+
"bytes": 48213,
|
|
440
|
+
"registryState": "written",
|
|
441
|
+
"warnings": []
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Errors return `{ error, message, remediation }` with `isError: true` — e.g. an existing imageKey without `overwrite: true`, a payload over the 5 MB cap, or a stem that doesn't match the `[a-z0-9]+(?:-[a-z0-9]+)*` convention (same rule documented in [Setup](#setup) — the stem IS the `imageKey`).
|
|
446
|
+
|
|
447
|
+
Notes:
|
|
448
|
+
|
|
449
|
+
- The declared `contentType` is a hint only. The extension is chosen from magic-byte detection of JPEG/PNG/WebP/HEIC; on mismatch the tool writes using the detected type and surfaces a `content-type-mismatch` warning.
|
|
450
|
+
- `imageKey` and `subDir` are validated against a strict lowercase-hyphen regex (no `..`, no slashes, no NUL), and the resolved path is re-checked to stay under `.ceraph/camera-images/` — path traversal is blocked by construction.
|
|
451
|
+
- `overwrite: true` is required to replace an existing imageKey; if the new extension differs (e.g. `profile.jpg` → `profile.png`), the stale sibling is removed and reported as `removedSiblingPath`.
|
|
452
|
+
- `data:image/...;base64,...` URI prefixes are accepted and stripped automatically.
|
|
453
|
+
|
|
454
|
+
### Resolution order at runtime
|
|
455
|
+
|
|
456
|
+
When `<CeraphCamera>` renders in dev/test mode, the active test image is chosen in this order:
|
|
195
457
|
|
|
196
|
-
|
|
458
|
+
1. If the component received an `imageKey` prop, that key is selected.
|
|
459
|
+
2. Otherwise, the key previously selected via `selectTestImageKey` is used.
|
|
460
|
+
3. Otherwise, the `"default"` key is used (only when something registered it).
|
|
461
|
+
4. Otherwise, the shim falls through to the real `<CameraView>`.
|
|
197
462
|
|
|
198
|
-
|
|
463
|
+
### Production safety
|
|
199
464
|
|
|
200
|
-
|
|
465
|
+
- `__DEV__` is `false` in any production build, so `configureTestImage` is never called and the shim always falls through to the real camera.
|
|
466
|
+
- The configured test image is referenced inside the `__DEV__` branch only — Metro tree-shakes the `require` out of the production bundle, so the image is never shipped to end users.
|
|
467
|
+
- `expo-camera` is imported lazily via `require()` inside the production branch. Apps that do not yet use `expo-camera` can still install the shim without a missing-module error.
|
|
201
468
|
|
|
202
469
|
## License
|
|
203
470
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';const _0x21f2ef=_0x4906;(function(_0x5b2516,_0x552fd5){const _0x5e2d8b=_0x4906,_0x26527b=_0x5b2516();while(!![]){try{const _0x166e35=parseInt(_0x5e2d8b(0x204))/0x1*(parseInt(_0x5e2d8b(0x20b))/0x2)+-parseInt(_0x5e2d8b(0x1f7))/0x3+parseInt(_0x5e2d8b(0x1ef))/0x4*(-parseInt(_0x5e2d8b(0x20f))/0x5)+parseInt(_0x5e2d8b(0x1ff))/0x6+-parseInt(_0x5e2d8b(0x218))/0x7+-parseInt(_0x5e2d8b(0x1f5))/0x8*(parseInt(_0x5e2d8b(0x1fd))/0x9)+parseInt(_0x5e2d8b(0x201))/0xa;if(_0x166e35===_0x552fd5)break;else _0x26527b['push'](_0x26527b['shift']());}catch(_0x29fdf5){_0x26527b['push'](_0x26527b['shift']());}}}(_0x59ce,0xc82a3));var __defProp=Object['defineProperty'],__getOwnPropDesc=Object['getOwnPropertyDescriptor'],__getOwnPropNames=Object['getOwnPropertyNames'],__hasOwnProp=Object['prototype'][_0x21f2ef(0x1e9)],__export=(_0x1b2d97,_0x2c33d2)=>{for(var _0x4851f9 in _0x2c33d2)__defProp(_0x1b2d97,_0x4851f9,{'get':_0x2c33d2[_0x4851f9],'enumerable':!![]});},__copyProps=(_0x1f8637,_0x9277c,_0x35b760,_0x44b3bd)=>{const _0x1a5beb=_0x21f2ef;if(_0x9277c&&typeof _0x9277c===_0x1a5beb(0x216)||typeof _0x9277c===_0x1a5beb(0x1f2)){for(let _0x1c7567 of __getOwnPropNames(_0x9277c))if(!__hasOwnProp[_0x1a5beb(0x1eb)](_0x1f8637,_0x1c7567)&&_0x1c7567!==_0x35b760)__defProp(_0x1f8637,_0x1c7567,{'get':()=>_0x9277c[_0x1c7567],'enumerable':!(_0x44b3bd=__getOwnPropDesc(_0x9277c,_0x1c7567))||_0x44b3bd[_0x1a5beb(0x1f1)]});}return _0x1f8637;},__toCommonJS=_0x448aa8=>__copyProps(__defProp({},'__esModule',{'value':!![]}),_0x448aa8),babel_plugin_exports={};__export(babel_plugin_exports,{'default':()=>ceraphTestIdPlugin}),module[_0x21f2ef(0x200)]=__toCommonJS(babel_plugin_exports);var CERAPH_TESTID_PREFIX=_0x21f2ef(0x203);function normalizeRelPath(_0x1d6c1f){const _0x3def1b=_0x21f2ef;return _0x1d6c1f['replace'](/\\/g,'/')[_0x3def1b(0x1e7)](/^\.\//,'')['replace'](/^\/+/,'');}function slug(_0x3607d1){const _0x1b8d8a=_0x21f2ef,_0x1adca6=_0x3607d1[_0x1b8d8a(0x1e7)](/\.[jt]sx?$/i,''),_0x12aea3=_0x1adca6['toLowerCase']()['replace'](/[^a-z0-9]+/g,'-')['replace'](/-+/g,'-')[_0x1b8d8a(0x1e7)](/^-+|-+$/g,'');return _0x12aea3['length']>0x28?_0x12aea3[_0x1b8d8a(0x1ed)](_0x12aea3[_0x1b8d8a(0x1f3)]-0x28)[_0x1b8d8a(0x1e7)](/^-+/,''):_0x12aea3;}function fnv1a64Hex(_0x49cf15){const _0x50f366=_0x21f2ef;let _0x4f2b91=0xcbf29ce484222325n;const _0x5e65c1=0x100000001b3n,_0x20b9c7=0xffffffffffffffffn;for(let _0x462f41=0x0;_0x462f41<_0x49cf15[_0x50f366(0x1f3)];_0x462f41++){_0x4f2b91^=BigInt(_0x49cf15['charCodeAt'](_0x462f41)),_0x4f2b91=_0x4f2b91*_0x5e65c1&_0x20b9c7;}return _0x4f2b91['toString'](0x10)[_0x50f366(0x1e3)](0x10,'0');}function deriveTestId(_0x4d2c68){const _0x14b3f0=_0x21f2ef,_0x43f433=normalizeRelPath(_0x4d2c68['relPath']),_0x3ca73a=_0x43f433+'|'+_0x4d2c68[_0x14b3f0(0x206)]+'|'+_0x4d2c68[_0x14b3f0(0x1f6)]+'|'+(_0x4d2c68[_0x14b3f0(0x1f8)]??''),_0x552f81=fnv1a64Hex(_0x3ca73a)['slice'](0x0,0xa);return''+CERAPH_TESTID_PREFIX+slug(_0x43f433)+'_'+_0x4d2c68[_0x14b3f0(0x206)]+'_'+_0x4d2c68[_0x14b3f0(0x1f6)]+'_'+_0x552f81;}var INTERACTIVE_TAGS=new Set([_0x21f2ef(0x209),_0x21f2ef(0x1fe),'TouchableHighlight','TouchableWithoutFeedback',_0x21f2ef(0x214),_0x21f2ef(0x1f4),_0x21f2ef(0x1fb),'IconButton','MenuItem',_0x21f2ef(0x1f0),'Chip']),INPUT_TAGS=new Set(['TextInput','TextField',_0x21f2ef(0x20a),'Picker',_0x21f2ef(0x1f9),_0x21f2ef(0x20c),'SegmentedControl','SearchBar']),PRESS_PROPS=new Set(['onPress','onPressIn','onLongPress','onClick']),INPUT_PROPS=new Set(['onChangeText','onValueChange','onChange']);function baseTag(_0x815ba0){return _0x815ba0['split']('.')[0x0];}function _0x4906(_0x27ed9b,_0x41ff15){_0x27ed9b=_0x27ed9b-0x1e2;const _0x59ce4e=_0x59ce();let _0x4906d2=_0x59ce4e[_0x27ed9b];return _0x4906d2;}function _0x59ce(){const _0x2ec0c3=['isJSXMemberExpression','25895nCPeLy','env','column','file','namespace','TouchableNativeFeedback','unshift','object','sep','10986549zWceKj','NODE_ENV','padStart','split','has','testID','replace','push','hasOwnProperty','start','call','attributes','slice','isJSXAttribute','340wpFjTs','ListItem','enumerable','function','length','Button','8mdSeOc','col','2007636NGLfDP','tag','Slider','loc','Link','name','134163UIhRZO','TouchableOpacity','7762398gWElfZ','exports','14262200EAISuY','filename','cer_','41777wjRViV','opts','line','enabled','property','Pressable','Switch','38PxUITV','Checkbox','ceraph-testid-injector'];_0x59ce=function(){return _0x2ec0c3;};return _0x59ce();}function isTargetableJsx(_0x4492b2,_0x1c0059){const _0x4300fc=_0x21f2ef,_0x261e51=baseTag(_0x4492b2);if(INTERACTIVE_TAGS[_0x4300fc(0x1e5)](_0x4492b2)||INTERACTIVE_TAGS['has'](_0x261e51)||INPUT_TAGS[_0x4300fc(0x1e5)](_0x4492b2)||INPUT_TAGS['has'](_0x261e51))return!![];for(const _0x140184 of _0x1c0059){if(PRESS_PROPS[_0x4300fc(0x1e5)](_0x140184)||INPUT_PROPS[_0x4300fc(0x1e5)](_0x140184))return!![];}return![];}var import_node_path=require('path');function jsxTagName(_0x4eed95,_0x1c1631){const _0x4f21b8=_0x21f2ef;if(_0x1c1631['isJSXIdentifier'](_0x4eed95))return _0x4eed95[_0x4f21b8(0x1fc)];if(_0x1c1631[_0x4f21b8(0x20e)](_0x4eed95)){const _0x269421=[];let _0x298a88=_0x4eed95;while(_0x1c1631['isJSXMemberExpression'](_0x298a88)){_0x269421['unshift'](_0x298a88[_0x4f21b8(0x208)][_0x4f21b8(0x1fc)]),_0x298a88=_0x298a88[_0x4f21b8(0x216)];}if(_0x1c1631['isJSXIdentifier'](_0x298a88))_0x269421[_0x4f21b8(0x215)](_0x298a88[_0x4f21b8(0x1fc)]);return _0x269421['join']('.');}if(_0x1c1631['isJSXNamespacedName'](_0x4eed95))return _0x4eed95[_0x4f21b8(0x213)][_0x4f21b8(0x1fc)]+':'+_0x4eed95[_0x4f21b8(0x1fc)]['name'];return'';}function attrName(_0x25658f,_0x3cf082){const _0x592f5b=_0x21f2ef;return _0x3cf082['isJSXIdentifier'](_0x25658f['name'])?_0x25658f[_0x592f5b(0x1fc)][_0x592f5b(0x1fc)]:void 0x0;}function attrNames(_0x4f7d83,_0x5cd44b){const _0x2c548b=_0x21f2ef,_0x1c5401=[];for(const _0x483c21 of _0x4f7d83['attributes']){if(_0x5cd44b[_0x2c548b(0x1ee)](_0x483c21)){const _0x5a4f89=attrName(_0x483c21,_0x5cd44b);if(_0x5a4f89)_0x1c5401[_0x2c548b(0x1e8)](_0x5a4f89);}}return _0x1c5401;}function hasTestId(_0x31257d){const _0x3d5255=_0x21f2ef;return _0x31257d['includes'](_0x3d5255(0x1e6));}function toRelPath(_0x3d6ac7,_0x2c19db){const _0x56330d=_0x21f2ef,_0x88ed96=_0x3d6ac7??'',_0xc7f6d2=_0x88ed96?(0x0,import_node_path['relative'])(_0x88ed96,_0x2c19db):_0x2c19db;return import_node_path[_0x56330d(0x217)]==='/'?_0xc7f6d2:_0xc7f6d2[_0x56330d(0x1e4)](import_node_path['sep'])['join'](import_node_path['posix'][_0x56330d(0x217)]);}function defaultEnabled(){const _0x3ba803=_0x21f2ef,_0x28271e=globalThis['process']?.[_0x3ba803(0x210)];return _0x28271e?.[_0x3ba803(0x1e2)]!=='production';}function ceraphTestIdPlugin(_0x425062,_0x300af5={}){const _0x42372f=_0x21f2ef,_0x3c7664=_0x425062['types'],_0x4b4d5c=_0x300af5[_0x42372f(0x207)]??defaultEnabled();return{'name':_0x42372f(0x20d),'visitor':{'JSXOpeningElement'(_0x1b3be3,_0x2003df){const _0x437728=_0x42372f;if(!_0x4b4d5c)return;const _0x145895=_0x1b3be3['node'],_0x15a4f5=_0x145895[_0x437728(0x1fa)];if(!_0x15a4f5)return;const _0x372d5a=_0x2003df[_0x437728(0x212)][_0x437728(0x205)][_0x437728(0x202)];if(!_0x372d5a)return;const _0x46d4bb=jsxTagName(_0x145895['name'],_0x3c7664),_0x40e2fb=attrNames(_0x145895,_0x3c7664);if(hasTestId(_0x40e2fb))return;if(!isTargetableJsx(_0x46d4bb,_0x40e2fb))return;const _0x5458a9=toRelPath(_0x2003df[_0x437728(0x212)]['opts']['root']??null,_0x372d5a),_0x29cfae=deriveTestId({'relPath':_0x5458a9,'line':_0x15a4f5['start'][_0x437728(0x206)],'col':_0x15a4f5[_0x437728(0x1ea)][_0x437728(0x211)]+0x1,'tag':_0x46d4bb});_0x145895[_0x437728(0x1ec)]['unshift'](_0x3c7664['jsxAttribute'](_0x3c7664['jsxIdentifier']('testID'),_0x3c7664['stringLiteral'](_0x29cfae)));}}};}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const _0x20ecbd=_0x57cf;(function(_0x58d4d1,_0x100f73){const _0x5ae4c7=_0x57cf,_0x482b8f=_0x58d4d1();while(!![]){try{const _0x1b0909=-parseInt(_0x5ae4c7(0x133))/0x1+-parseInt(_0x5ae4c7(0x11a))/0x2+parseInt(_0x5ae4c7(0x137))/0x3+parseInt(_0x5ae4c7(0x115))/0x4*(parseInt(_0x5ae4c7(0x113))/0x5)+-parseInt(_0x5ae4c7(0x142))/0x6+parseInt(_0x5ae4c7(0x12d))/0x7*(parseInt(_0x5ae4c7(0x13a))/0x8)+-parseInt(_0x5ae4c7(0x13f))/0x9*(parseInt(_0x5ae4c7(0x11c))/0xa);if(_0x1b0909===_0x100f73)break;else _0x482b8f['push'](_0x482b8f['shift']());}catch(_0x4af8b1){_0x482b8f['push'](_0x482b8f['shift']());}}}(_0x45cd,0xdd4cc));var CERAPH_TESTID_PREFIX='cer_';function normalizeRelPath(_0x591053){const _0x4c2032=_0x57cf;return _0x591053['replace'](/\\/g,'/')['replace'](/^\.\//,'')[_0x4c2032(0x141)](/^\/+/,'');}function slug(_0x412217){const _0x4c46a4=_0x57cf,_0xf05090=_0x412217[_0x4c46a4(0x141)](/\.[jt]sx?$/i,''),_0x535b71=_0xf05090[_0x4c46a4(0x130)]()[_0x4c46a4(0x141)](/[^a-z0-9]+/g,'-')[_0x4c46a4(0x141)](/-+/g,'-')['replace'](/^-+|-+$/g,'');return _0x535b71['length']>0x28?_0x535b71['slice'](_0x535b71['length']-0x28)['replace'](/^-+/,''):_0x535b71;}function fnv1a64Hex(_0x65a5de){const _0x58598a=_0x57cf;let _0x13ecdb=0xcbf29ce484222325n;const _0x510bbf=0x100000001b3n,_0x19125e=0xffffffffffffffffn;for(let _0x368b81=0x0;_0x368b81<_0x65a5de[_0x58598a(0x126)];_0x368b81++){_0x13ecdb^=BigInt(_0x65a5de[_0x58598a(0x112)](_0x368b81)),_0x13ecdb=_0x13ecdb*_0x510bbf&_0x19125e;}return _0x13ecdb[_0x58598a(0x12b)](0x10)['padStart'](0x10,'0');}function deriveTestId(_0x4a8c41){const _0x142077=_0x57cf,_0x50a983=normalizeRelPath(_0x4a8c41['relPath']),_0x3467e9=_0x50a983+'|'+_0x4a8c41['line']+'|'+_0x4a8c41[_0x142077(0x125)]+'|'+(_0x4a8c41[_0x142077(0x123)]??''),_0x9bace7=fnv1a64Hex(_0x3467e9)['slice'](0x0,0xa);return''+CERAPH_TESTID_PREFIX+slug(_0x50a983)+'_'+_0x4a8c41['line']+'_'+_0x4a8c41[_0x142077(0x125)]+'_'+_0x9bace7;}var INTERACTIVE_TAGS=new Set(['Pressable',_0x20ecbd(0x116),_0x20ecbd(0x13d),'TouchableWithoutFeedback',_0x20ecbd(0x13b),'Button','Link',_0x20ecbd(0x119),_0x20ecbd(0x127),_0x20ecbd(0x124),'Chip']),INPUT_TAGS=new Set(['TextInput',_0x20ecbd(0x121),'Switch','Picker','Slider','Checkbox',_0x20ecbd(0x12c),'SearchBar']),PRESS_PROPS=new Set([_0x20ecbd(0x144),_0x20ecbd(0x11d),_0x20ecbd(0x114),_0x20ecbd(0x131)]),INPUT_PROPS=new Set([_0x20ecbd(0x140),_0x20ecbd(0x110),'onChange']);function baseTag(_0x3402c1){const _0x577ce7=_0x20ecbd;return _0x3402c1[_0x577ce7(0x128)]('.')[0x0];}function isTargetableJsx(_0x50be75,_0x61e4e7){const _0xa5a4ad=_0x20ecbd,_0x497df3=baseTag(_0x50be75);if(INTERACTIVE_TAGS[_0xa5a4ad(0x12a)](_0x50be75)||INTERACTIVE_TAGS['has'](_0x497df3)||INPUT_TAGS[_0xa5a4ad(0x12a)](_0x50be75)||INPUT_TAGS[_0xa5a4ad(0x12a)](_0x497df3))return!![];for(const _0x2d36ee of _0x61e4e7){if(PRESS_PROPS[_0xa5a4ad(0x12a)](_0x2d36ee)||INPUT_PROPS[_0xa5a4ad(0x12a)](_0x2d36ee))return!![];}return![];}import{posix,relative,sep}from'path';function jsxTagName(_0x2f22f0,_0x3c9cc4){const _0x3da31f=_0x20ecbd;if(_0x3c9cc4['isJSXIdentifier'](_0x2f22f0))return _0x2f22f0[_0x3da31f(0x135)];if(_0x3c9cc4[_0x3da31f(0x13e)](_0x2f22f0)){const _0x2948ad=[];let _0x302206=_0x2f22f0;while(_0x3c9cc4['isJSXMemberExpression'](_0x302206)){_0x2948ad[_0x3da31f(0x118)](_0x302206['property']['name']),_0x302206=_0x302206['object'];}if(_0x3c9cc4['isJSXIdentifier'](_0x302206))_0x2948ad['unshift'](_0x302206['name']);return _0x2948ad['join']('.');}if(_0x3c9cc4[_0x3da31f(0x139)](_0x2f22f0))return _0x2f22f0[_0x3da31f(0x11b)]['name']+':'+_0x2f22f0['name'][_0x3da31f(0x135)];return'';}function attrName(_0x33ca7d,_0x33b513){const _0x302411=_0x20ecbd;return _0x33b513[_0x302411(0x13c)](_0x33ca7d[_0x302411(0x135)])?_0x33ca7d['name']['name']:void 0x0;}function attrNames(_0x32b27d,_0x480f03){const _0x567b71=_0x20ecbd,_0x32ddfa=[];for(const _0x30c394 of _0x32b27d['attributes']){if(_0x567b71(0x129)!=='Cinml'){if(_0x480f03['isJSXAttribute'](_0x30c394)){const _0x170a09=attrName(_0x30c394,_0x480f03);if(_0x170a09)_0x32ddfa['push'](_0x170a09);}}else{const _0x8dba6c=[];for(const _0x366806 of _0x21d7e2['attributes']){if(_0x15e480[_0x567b71(0x12f)](_0x366806)){const _0x1af027=_0xd6fc94(_0x366806,_0x553278);if(_0x1af027)_0x8dba6c[_0x567b71(0x120)](_0x1af027);}}return _0x8dba6c;}}return _0x32ddfa;}function hasTestId(_0x5a9b05){return _0x5a9b05['includes']('testID');}function toRelPath(_0x2e313c,_0x588d72){const _0x1d3488=_0x20ecbd,_0x3af1d2=_0x2e313c??'',_0x33c012=_0x3af1d2?relative(_0x3af1d2,_0x588d72):_0x588d72;return sep==='/'?_0x33c012:_0x33c012[_0x1d3488(0x128)](sep)[_0x1d3488(0x136)](posix[_0x1d3488(0x111)]);}function _0x57cf(_0x1ba854,_0x2e3cf){_0x1ba854=_0x1ba854-0x10f;const _0x45cd08=_0x45cd();let _0x57cf4b=_0x45cd08[_0x1ba854];return _0x57cf4b;}function defaultEnabled(){const _0xc7a9f0=_0x20ecbd,_0x242457=globalThis[_0xc7a9f0(0x122)]?.['env'];return _0x242457?.['NODE_ENV']!==_0xc7a9f0(0x132);}function _0x45cd(){const _0x1cb0f9=['toLowerCase','onClick','production','1106456VeTTua','jsxAttribute','name','join','4697955vtTOED','start','isJSXNamespacedName','8728pABMYS','TouchableNativeFeedback','isJSXIdentifier','TouchableHighlight','isJSXMemberExpression','3983076PDLsbV','onChangeText','replace','10462344VbTsmX','loc','onPress','testID','filename','onValueChange','sep','charCodeAt','590FHuEsU','onLongPress','60776KqAfwZ','TouchableOpacity','jsxIdentifier','unshift','IconButton','1459276wWjUsC','namespace','10QaFasf','onPressIn','file','ceraph-testid-injector','push','TextField','process','tag','ListItem','col','length','MenuItem','split','igClQ','has','toString','SegmentedControl','10073jdbbbN','column','isJSXAttribute'];_0x45cd=function(){return _0x1cb0f9;};return _0x45cd();}function ceraphTestIdPlugin(_0xa92e70,_0x44b4ff={}){const _0x506e0a=_0x20ecbd,_0x16ec2f=_0xa92e70['types'],_0x3e138c=_0x44b4ff['enabled']??defaultEnabled();return{'name':_0x506e0a(0x11f),'visitor':{'JSXOpeningElement'(_0x1f6aae,_0x4ec0a5){const _0x592d0e=_0x506e0a;if(!_0x3e138c)return;const _0x32dfdf=_0x1f6aae['node'],_0x4c3c46=_0x32dfdf[_0x592d0e(0x143)];if(!_0x4c3c46)return;const _0x34b816=_0x4ec0a5[_0x592d0e(0x11e)]['opts'][_0x592d0e(0x10f)];if(!_0x34b816)return;const _0x36ab19=jsxTagName(_0x32dfdf['name'],_0x16ec2f),_0x3cb592=attrNames(_0x32dfdf,_0x16ec2f);if(hasTestId(_0x3cb592))return;if(!isTargetableJsx(_0x36ab19,_0x3cb592))return;const _0x3a342b=toRelPath(_0x4ec0a5['file']['opts']['root']??null,_0x34b816),_0x38f103=deriveTestId({'relPath':_0x3a342b,'line':_0x4c3c46[_0x592d0e(0x138)]['line'],'col':_0x4c3c46[_0x592d0e(0x138)][_0x592d0e(0x12e)]+0x1,'tag':_0x36ab19});_0x32dfdf['attributes'][_0x592d0e(0x118)](_0x16ec2f[_0x592d0e(0x134)](_0x16ec2f[_0x592d0e(0x117)](_0x592d0e(0x145)),_0x16ec2f['stringLiteral'](_0x38f103)));}}};}export{ceraphTestIdPlugin as default};
|
package/dist/cli.d.ts
CHANGED