@ceraph/react-native-mcp 0.3.2 → 0.4.5
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 +41 -15
- 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 _0x50ee6e=_0x5795;(function(_0x5a6590,_0x496257){const _0x163059=_0x5795,_0x161787=_0x5a6590();while(!![]){try{const _0x1c9096=parseInt(_0x163059(0x122))/0x1*(parseInt(_0x163059(0x127))/0x2)+parseInt(_0x163059(0x12f))/0x3*(parseInt(_0x163059(0x13a))/0x4)+-parseInt(_0x163059(0x14a))/0x5*(parseInt(_0x163059(0x147))/0x6)+-parseInt(_0x163059(0x132))/0x7*(-parseInt(_0x163059(0x13f))/0x8)+-parseInt(_0x163059(0x150))/0x9*(-parseInt(_0x163059(0x13e))/0xa)+parseInt(_0x163059(0x129))/0xb*(-parseInt(_0x163059(0x139))/0xc)+-parseInt(_0x163059(0x14e))/0xd;if(_0x1c9096===_0x496257)break;else _0x161787['push'](_0x161787['shift']());}catch(_0x5a8118){_0x161787['push'](_0x161787['shift']());}}}(_0x36b0,0xd4b3e));var __defProp=Object['defineProperty'],__getOwnPropDesc=Object[_0x50ee6e(0x12b)],__getOwnPropNames=Object['getOwnPropertyNames'],__hasOwnProp=Object['prototype']['hasOwnProperty'],__export=(_0x47c36e,_0xacd9a0)=>{for(var _0x246753 in _0xacd9a0)__defProp(_0x47c36e,_0x246753,{'get':_0xacd9a0[_0x246753],'enumerable':!![]});},__copyProps=(_0x2c7d0f,_0x46475c,_0x2d5b69,_0x2bc0f3)=>{const _0x1ebfec=_0x50ee6e;if(_0x46475c&&typeof _0x46475c===_0x1ebfec(0x12e)||typeof _0x46475c==='function'){for(let _0x1accfe of __getOwnPropNames(_0x46475c))if(!__hasOwnProp['call'](_0x2c7d0f,_0x1accfe)&&_0x1accfe!==_0x2d5b69)__defProp(_0x2c7d0f,_0x1accfe,{'get':()=>_0x46475c[_0x1accfe],'enumerable':!(_0x2bc0f3=__getOwnPropDesc(_0x46475c,_0x1accfe))||_0x2bc0f3[_0x1ebfec(0x143)]});}return _0x2c7d0f;},__toCommonJS=_0x572501=>__copyProps(__defProp({},_0x50ee6e(0x130),{'value':!![]}),_0x572501),babel_plugin_exports={};__export(babel_plugin_exports,{'default':()=>ceraphTestIdPlugin}),module[_0x50ee6e(0x123)]=__toCommonJS(babel_plugin_exports);function _0x5795(_0x13a3b2,_0x49637f){_0x13a3b2=_0x13a3b2-0x119;const _0x36b0c0=_0x36b0();let _0x579523=_0x36b0c0[_0x13a3b2];return _0x579523;}var CERAPH_TESTID_PREFIX=_0x50ee6e(0x11e);function normalizeRelPath(_0x463f72){const _0x3b33c9=_0x50ee6e;return _0x463f72[_0x3b33c9(0x144)](/\\/g,'/')['replace'](/^\.\//,'')['replace'](/^\/+/,'');}function slug(_0x2fd314){const _0x2a3421=_0x50ee6e,_0x18c32c=_0x2fd314[_0x2a3421(0x144)](/\.[jt]sx?$/i,''),_0x3ce966=_0x18c32c[_0x2a3421(0x119)]()['replace'](/[^a-z0-9]+/g,'-')['replace'](/-+/g,'-')[_0x2a3421(0x144)](/^-+|-+$/g,'');return _0x3ce966[_0x2a3421(0x128)]>0x28?_0x3ce966['slice'](_0x3ce966[_0x2a3421(0x128)]-0x28)[_0x2a3421(0x144)](/^-+/,''):_0x3ce966;}function fnv1a64Hex(_0x468eb7){const _0x7cc731=_0x50ee6e;let _0x5a409d=0xcbf29ce484222325n;const _0x5c0299=0x100000001b3n,_0x3f243a=0xffffffffffffffffn;for(let _0x511134=0x0;_0x511134<_0x468eb7['length'];_0x511134++){_0x5a409d^=BigInt(_0x468eb7[_0x7cc731(0x149)](_0x511134)),_0x5a409d=_0x5a409d*_0x5c0299&_0x3f243a;}return _0x5a409d['toString'](0x10)[_0x7cc731(0x151)](0x10,'0');}function deriveTestId(_0x33a262){const _0x47a438=_0x50ee6e,_0x46a09b=normalizeRelPath(_0x33a262[_0x47a438(0x11d)]),_0x41a098=_0x46a09b+'|'+_0x33a262['line']+'|'+_0x33a262['col']+'|'+(_0x33a262['tag']??''),_0x177402=fnv1a64Hex(_0x41a098)['slice'](0x0,0xa);return''+CERAPH_TESTID_PREFIX+slug(_0x46a09b)+'_'+_0x33a262[_0x47a438(0x121)]+'_'+_0x33a262['col']+'_'+_0x177402;}var INTERACTIVE_TAGS=new Set(['Pressable',_0x50ee6e(0x133),_0x50ee6e(0x126),_0x50ee6e(0x13b),_0x50ee6e(0x13d),'Button','Link','IconButton',_0x50ee6e(0x135),'ListItem',_0x50ee6e(0x136)]),INPUT_TAGS=new Set(['TextInput',_0x50ee6e(0x131),'Switch','Picker','Slider',_0x50ee6e(0x120),_0x50ee6e(0x138),'SearchBar']),PRESS_PROPS=new Set(['onPress',_0x50ee6e(0x154),_0x50ee6e(0x11b),'onClick']),INPUT_PROPS=new Set(['onChangeText',_0x50ee6e(0x14f),'onChange']);function baseTag(_0x57ae16){const _0x4c414b=_0x50ee6e;return _0x57ae16[_0x4c414b(0x14b)]('.')[0x0];}function isTargetableJsx(_0x40cf78,_0x5430e4){const _0x438277=_0x50ee6e,_0x218b45=baseTag(_0x40cf78);if(INTERACTIVE_TAGS['has'](_0x40cf78)||INTERACTIVE_TAGS['has'](_0x218b45)||INPUT_TAGS['has'](_0x40cf78)||INPUT_TAGS[_0x438277(0x137)](_0x218b45)){if(_0x438277(0x156)==='mWNeP'){for(var _0x3b190f in _0x5555a8)_0x3adf1a(_0x5b4398,_0x3b190f,{'get':_0x10d94d[_0x3b190f],'enumerable':!![]});}else return!![];}for(const _0x5dde3d of _0x5430e4){if(PRESS_PROPS[_0x438277(0x137)](_0x5dde3d)||INPUT_PROPS[_0x438277(0x137)](_0x5dde3d))return!![];}return![];}var import_node_path=require('path');function jsxTagName(_0xc0a1eb,_0x33a3e2){const _0x88a60c=_0x50ee6e;if(_0x33a3e2[_0x88a60c(0x12c)](_0xc0a1eb))return _0xc0a1eb['name'];if(_0x33a3e2['isJSXMemberExpression'](_0xc0a1eb)){const _0x517b49=[];let _0x493310=_0xc0a1eb;while(_0x33a3e2[_0x88a60c(0x141)](_0x493310)){if('taevR'===_0x88a60c(0x13c))_0x517b49['unshift'](_0x493310[_0x88a60c(0x155)]['name']),_0x493310=_0x493310['object'];else return _0x3247f4[_0x88a60c(0x144)](/\\/g,'/')[_0x88a60c(0x144)](/^\.\//,'')['replace'](/^\/+/,'');}if(_0x33a3e2['isJSXIdentifier'](_0x493310))_0x517b49['unshift'](_0x493310['name']);return _0x517b49['join']('.');}if(_0x33a3e2['isJSXNamespacedName'](_0xc0a1eb))return _0xc0a1eb[_0x88a60c(0x12a)][_0x88a60c(0x14d)]+':'+_0xc0a1eb[_0x88a60c(0x14d)]['name'];return'';}function attrName(_0x285a14,_0x5a504c){return _0x5a504c['isJSXIdentifier'](_0x285a14['name'])?_0x285a14['name']['name']:void 0x0;}function attrNames(_0x2b6e53,_0x2bd02b){const _0x57cd95=_0x50ee6e,_0x3ab3bb=[];for(const _0x1555bd of _0x2b6e53[_0x57cd95(0x11a)]){if(_0x2bd02b[_0x57cd95(0x140)](_0x1555bd)){const _0x325525=attrName(_0x1555bd,_0x2bd02b);if(_0x325525)_0x3ab3bb['push'](_0x325525);}}return _0x3ab3bb;}function hasTestId(_0x453417){const _0x5486dc=_0x50ee6e;return _0x453417['includes'](_0x5486dc(0x125));}function _0x36b0(){const _0x2a2381=['NODE_ENV','testID','TouchableHighlight','39692XjjltL','length','11XPffLt','namespace','getOwnPropertyDescriptor','isJSXIdentifier','sep','object','230634wYTizs','__esModule','TextField','63SbfyJr','TouchableOpacity','node','MenuItem','Chip','has','SegmentedControl','13727100eLoQrr','32nMDZaq','TouchableWithoutFeedback','taevR','TouchableNativeFeedback','677180IrcQtP','338176OesRRs','isJSXAttribute','isJSXMemberExpression','env','enumerable','replace','loc','filename','323694HDHvDR','production','charCodeAt','25ocZVQw','split','unshift','name','14411670vrWNXp','onValueChange','171XiZKHK','padStart','file','enabled','onPressIn','property','mckJC','toLowerCase','attributes','onLongPress','opts','relPath','cer_','posix','Checkbox','line','56BOzvTT','exports'];_0x36b0=function(){return _0x2a2381;};return _0x36b0();}function toRelPath(_0xd27745,_0x5cf877){const _0x5e6331=_0x50ee6e,_0x42a636=_0xd27745??'',_0x391b90=_0x42a636?(0x0,import_node_path['relative'])(_0x42a636,_0x5cf877):_0x5cf877;return import_node_path[_0x5e6331(0x12d)]==='/'?_0x391b90:_0x391b90[_0x5e6331(0x14b)](import_node_path['sep'])['join'](import_node_path[_0x5e6331(0x11f)][_0x5e6331(0x12d)]);}function defaultEnabled(){const _0xf9ca39=_0x50ee6e,_0xc1dae8=globalThis['process']?.[_0xf9ca39(0x142)];return _0xc1dae8?.[_0xf9ca39(0x124)]!==_0xf9ca39(0x148);}function ceraphTestIdPlugin(_0x44d89e,_0x202768={}){const _0x191f8a=_0x50ee6e,_0x1be2e8=_0x44d89e['types'],_0x5ede7d=_0x202768[_0x191f8a(0x153)]??defaultEnabled();return{'name':'ceraph-testid-injector','visitor':{'JSXOpeningElement'(_0x401cc6,_0x46418c){const _0x2096fc=_0x191f8a;if(!_0x5ede7d)return;const _0x502b01=_0x401cc6[_0x2096fc(0x134)],_0xa9467d=_0x502b01[_0x2096fc(0x145)];if(!_0xa9467d)return;const _0x5a4267=_0x46418c['file'][_0x2096fc(0x11c)][_0x2096fc(0x146)];if(!_0x5a4267)return;const _0x66e81a=jsxTagName(_0x502b01['name'],_0x1be2e8),_0x3d8a36=attrNames(_0x502b01,_0x1be2e8);if(hasTestId(_0x3d8a36))return;if(!isTargetableJsx(_0x66e81a,_0x3d8a36))return;const _0x48c8c8=toRelPath(_0x46418c[_0x2096fc(0x152)]['opts']['root']??null,_0x5a4267),_0x4e49d7=deriveTestId({'relPath':_0x48c8c8,'line':_0xa9467d['start'][_0x2096fc(0x121)],'col':_0xa9467d['start']['column']+0x1,'tag':_0x66e81a});_0x502b01[_0x2096fc(0x11a)][_0x2096fc(0x14c)](_0x1be2e8['jsxAttribute'](_0x1be2e8['jsxIdentifier'](_0x2096fc(0x125)),_0x1be2e8['stringLiteral'](_0x4e49d7)));}}};}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const _0x48071e=_0x11dd;(function(_0x37284e,_0x178610){const _0x2ef483=_0x11dd,_0x35aac9=_0x37284e();while(!![]){try{const _0xb6fc8f=-parseInt(_0x2ef483(0x122))/0x1+-parseInt(_0x2ef483(0x118))/0x2*(-parseInt(_0x2ef483(0x124))/0x3)+-parseInt(_0x2ef483(0x107))/0x4+-parseInt(_0x2ef483(0x11f))/0x5+-parseInt(_0x2ef483(0x126))/0x6+-parseInt(_0x2ef483(0x131))/0x7+parseInt(_0x2ef483(0x134))/0x8*(parseInt(_0x2ef483(0x133))/0x9);if(_0xb6fc8f===_0x178610)break;else _0x35aac9['push'](_0x35aac9['shift']());}catch(_0x20c413){_0x35aac9['push'](_0x35aac9['shift']());}}}(_0x2df0,0x773a2));var CERAPH_TESTID_PREFIX=_0x48071e(0x117);function normalizeRelPath(_0x13d913){const _0x144bb1=_0x48071e;return _0x13d913[_0x144bb1(0x130)](/\\/g,'/')['replace'](/^\.\//,'')['replace'](/^\/+/,'');}function slug(_0x2fe056){const _0x371ccc=_0x48071e,_0x5c5b60=_0x2fe056['replace'](/\.[jt]sx?$/i,''),_0x5d8904=_0x5c5b60[_0x371ccc(0x10b)]()[_0x371ccc(0x130)](/[^a-z0-9]+/g,'-')['replace'](/-+/g,'-')['replace'](/^-+|-+$/g,'');return _0x5d8904['length']>0x28?_0x5d8904['slice'](_0x5d8904['length']-0x28)['replace'](/^-+/,''):_0x5d8904;}function fnv1a64Hex(_0x24046f){const _0x3f9ee2=_0x48071e;let _0x2f42da=0xcbf29ce484222325n;const _0x256409=0x100000001b3n,_0x580cdf=0xffffffffffffffffn;for(let _0x1638bd=0x0;_0x1638bd<_0x24046f['length'];_0x1638bd++){_0x2f42da^=BigInt(_0x24046f['charCodeAt'](_0x1638bd)),_0x2f42da=_0x2f42da*_0x256409&_0x580cdf;}return _0x2f42da['toString'](0x10)[_0x3f9ee2(0x12a)](0x10,'0');}function deriveTestId(_0x43872f){const _0x3747fc=_0x48071e,_0x24b2fd=normalizeRelPath(_0x43872f[_0x3747fc(0x113)]),_0x5f5a2b=_0x24b2fd+'|'+_0x43872f['line']+'|'+_0x43872f['col']+'|'+(_0x43872f['tag']??''),_0x22d904=fnv1a64Hex(_0x5f5a2b)['slice'](0x0,0xa);return''+CERAPH_TESTID_PREFIX+slug(_0x24b2fd)+'_'+_0x43872f['line']+'_'+_0x43872f['col']+'_'+_0x22d904;}var INTERACTIVE_TAGS=new Set([_0x48071e(0x112),'TouchableOpacity',_0x48071e(0x11d),'TouchableWithoutFeedback','TouchableNativeFeedback',_0x48071e(0x121),_0x48071e(0x11b),_0x48071e(0x109),_0x48071e(0x12b),'ListItem',_0x48071e(0x135)]),INPUT_TAGS=new Set([_0x48071e(0x127),'TextField','Switch',_0x48071e(0x132),'Slider','Checkbox','SegmentedControl',_0x48071e(0x11c)]),PRESS_PROPS=new Set(['onPress','onPressIn',_0x48071e(0x12d),'onClick']),INPUT_PROPS=new Set(['onChangeText','onValueChange',_0x48071e(0x12c)]);function baseTag(_0x460781){return _0x460781['split']('.')[0x0];}function isTargetableJsx(_0x37b280,_0x549f15){const _0x357815=_0x48071e,_0x147ee4=baseTag(_0x37b280);if(INTERACTIVE_TAGS[_0x357815(0x111)](_0x37b280)||INTERACTIVE_TAGS['has'](_0x147ee4)||INPUT_TAGS['has'](_0x37b280)||INPUT_TAGS['has'](_0x147ee4))return!![];for(const _0x1b1aab of _0x549f15){if(PRESS_PROPS[_0x357815(0x111)](_0x1b1aab)||INPUT_PROPS['has'](_0x1b1aab))return!![];}return![];}import{posix,relative,sep}from'path';function _0x11dd(_0x129193,_0x34ab21){_0x129193=_0x129193-0x107;const _0x2df0b6=_0x2df0();let _0x11dd46=_0x2df0b6[_0x129193];return _0x11dd46;}function jsxTagName(_0x1596b6,_0xe52562){const _0x270f0e=_0x48071e;if(_0xe52562['isJSXIdentifier'](_0x1596b6))return _0x1596b6['name'];if(_0xe52562[_0x270f0e(0x12e)](_0x1596b6)){const _0x3cdf0c=[];let _0xbe6e82=_0x1596b6;while(_0xe52562[_0x270f0e(0x12e)](_0xbe6e82)){_0x3cdf0c['unshift'](_0xbe6e82['property']['name']),_0xbe6e82=_0xbe6e82[_0x270f0e(0x125)];}if(_0xe52562[_0x270f0e(0x10c)](_0xbe6e82))_0x3cdf0c['unshift'](_0xbe6e82[_0x270f0e(0x10e)]);return _0x3cdf0c['join']('.');}if(_0xe52562['isJSXNamespacedName'](_0x1596b6))return _0x1596b6[_0x270f0e(0x115)][_0x270f0e(0x10e)]+':'+_0x1596b6['name'][_0x270f0e(0x10e)];return'';}function _0x2df0(){const _0x85ecfa=['node','column','has','Pressable','relPath','includes','namespace','jsxAttribute','cer_','318cYnzGk','jsxIdentifier','isJSXAttribute','Link','SearchBar','TouchableHighlight','root','3639835NxlrxF','stringLiteral','Button','793522ZKgRcl','testID','2553JDnSAW','object','1997394gHvSlv','TextInput','opts','process','padStart','MenuItem','onChange','onLongPress','isJSXMemberExpression','env','replace','6085380TSNgEH','Picker','15291zWELzi','17688TsPoHV','Chip','2718864RHtVzj','file','IconButton','start','toLowerCase','isJSXIdentifier','attributes','name'];_0x2df0=function(){return _0x85ecfa;};return _0x2df0();}function attrName(_0x4ce20b,_0x615cf6){const _0x565919=_0x48071e;return _0x615cf6['isJSXIdentifier'](_0x4ce20b['name'])?_0x4ce20b[_0x565919(0x10e)][_0x565919(0x10e)]:void 0x0;}function attrNames(_0x82e263,_0x12cc32){const _0x44d579=_0x48071e,_0x265831=[];for(const _0x248b21 of _0x82e263[_0x44d579(0x10d)]){if(_0x12cc32[_0x44d579(0x11a)](_0x248b21)){const _0x1357ff=attrName(_0x248b21,_0x12cc32);if(_0x1357ff)_0x265831['push'](_0x1357ff);}}return _0x265831;}function hasTestId(_0x452e92){const _0xf9c51e=_0x48071e;return _0x452e92[_0xf9c51e(0x114)](_0xf9c51e(0x123));}function toRelPath(_0x550e32,_0x555104){const _0x5c99d6=_0x550e32??'',_0x5668b4=_0x5c99d6?relative(_0x5c99d6,_0x555104):_0x555104;return sep==='/'?_0x5668b4:_0x5668b4['split'](sep)['join'](posix['sep']);}function defaultEnabled(){const _0x4a65ba=_0x48071e,_0x36e400=globalThis[_0x4a65ba(0x129)]?.[_0x4a65ba(0x12f)];return _0x36e400?.['NODE_ENV']!=='production';}function ceraphTestIdPlugin(_0x1ac91c,_0x53230f={}){const _0x1fc548=_0x1ac91c['types'],_0x7a7cdb=_0x53230f['enabled']??defaultEnabled();return{'name':'ceraph-testid-injector','visitor':{'JSXOpeningElement'(_0x55e12d,_0x4d6190){const _0x38d5ee=_0x11dd;if(!_0x7a7cdb)return;const _0x1bab15=_0x55e12d[_0x38d5ee(0x10f)],_0x583a2b=_0x1bab15['loc'];if(!_0x583a2b)return;const _0x18de79=_0x4d6190[_0x38d5ee(0x108)][_0x38d5ee(0x128)]['filename'];if(!_0x18de79)return;const _0x14a1a3=jsxTagName(_0x1bab15[_0x38d5ee(0x10e)],_0x1fc548),_0x41c877=attrNames(_0x1bab15,_0x1fc548);if(hasTestId(_0x41c877))return;if(!isTargetableJsx(_0x14a1a3,_0x41c877))return;const _0xf7eb6a=toRelPath(_0x4d6190['file']['opts'][_0x38d5ee(0x11e)]??null,_0x18de79),_0x3337f3=deriveTestId({'relPath':_0xf7eb6a,'line':_0x583a2b[_0x38d5ee(0x10a)]['line'],'col':_0x583a2b['start'][_0x38d5ee(0x110)]+0x1,'tag':_0x14a1a3});_0x1bab15['attributes']['unshift'](_0x1fc548[_0x38d5ee(0x116)](_0x1fc548[_0x38d5ee(0x119)]('testID'),_0x1fc548[_0x38d5ee(0x120)](_0x3337f3)));}}};}export{ceraphTestIdPlugin as default};
|
package/dist/cli.d.ts
CHANGED