Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .cursor/rules/development-workflow.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
alwaysApply: true
---

# Development Workflow — Ketch React Native SDK

## Prerequisites

- **Node.js** >= 18
- **Yarn** 4.x (per `example/package.json` `packageManager`) or npm
- **Android**: Android SDK, `adb`, emulator or device
- **iOS** (macOS only): Xcode, CocoaPods (`pod`), iOS Simulator

## Setup

```bash
git clone git@github.com:ketch-com/ketch-react-native.git
cd ketch-react-native
```

To run the **in-repo example** (npm release or local `file:../package` — see skill):

```bash
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android # remote (default)
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android local # this checkout
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios local
```

## Common Commands

| Command | Purpose |
| --- | --- |
| `cd package && npm run all` | Lint, typecheck, and build the library |
| `cd package && npm test` | Run package unit tests |
| `cd example && npm run start` | Start Metro bundler |
| `cd example && npm run android` / `npm run ios` | Run example (Metro must be running) |
| `bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android local` | Full flow: configure, Metro, build, launch, logs |
| `cd example/ios && pod install` | Refresh iOS pods after native dependency changes |

## Validation Checklist

Before merging SDK changes:

1. `cd package && npm run lint && npm run typecheck` — **must pass**
2. `cd package && npm test` — **must pass**
3. `bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android local --build-only` — example must build
4. If iOS native code changed: `bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios local --build-only`

## Testing

- **Package tests**: Jest in `package/`
- **Integration tests**: `KETCH_INTEGRATION_TESTS=1 npm run test:integration` in `package/`
- **Example app**: manual verification via run-sample script or `npm run android` / `npm run ios`

## Code Quality

- Match existing **TypeScript** style under `package/src/`
- Keep public API exports intentional in `package/src/index.ts`
- Prefer **small, focused diffs**; do not reformat unrelated files

## SDK Health Dashboard (manual QA)

`example/` wires `KetchServiceProvider` callbacks in `App.tsx` into `DashboardContext`; `Main.tsx` renders structured panels above the existing controls.

| Section | What to verify |
| --- | --- |
| **Connection** | Init, status, org/property/env (editable), data center |
| **WebView / Experience** | Load, visibility, dismiss, WebView visibility; `ketch_att` on iOS |
| **ATT (iOS)** | Native ATT; **Request ATT** then **Reload WebView** |
| **Privacy / Consent** | Environment, jurisdiction, region, consent, US Privacy / TCF / GPP |
| **Headless** | Fetch Location / Bootstrap / Cold Start (`useKetchService()` headless APIs) |
| **Event log** | Timestamped callback trace |

Launch: `bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios local`

Example org/property: `ethansch061226` / `website_smart_tag`. See `ketch-react-native-run-sample` skill for prerequisites, launch, and smoke flow.

Provider uses `autoLoad={false}` so **Load** is an explicit manual step.

## Local tag URL overrides (non-prod scripts)

Pass `webResourceUrlOverrides` on `KetchServiceProvider` / `KetchMobile` — exact source URL → local replacement. Separate from `dataCenter` (boot.js base).

```tsx
<KetchServiceProvider
webResourceUrlOverrides={{
'https://cdn.uat.ketchjs.com/ketchtag/stable/v2.12/ketch-sdk.js': 'http://localhost:9000/ketch-sdk.js',
}}
/>
```

JS hook in `package/src/assets/index.ts` runs before boot.js. Sample: `DEV_URL_OVERRIDES_ENABLED = true` in `example/devUrlOverrides.ts`.
75 changes: 75 additions & 0 deletions .cursor/skills/ketch-react-native-run-sample/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
name: ketch-react-native-run-sample
description: Configures the in-repo React Native example for either the released @ketch-com/ketch-react-native npm package or the local file:../package link, starts Metro, builds, launches on android or ios, and streams filtered logs. Use when the user runs /ketch-react-native-run-sample.
---

# ketch-react-native-run-sample

## Instructions

When the user invokes **`/ketch-react-native-run-sample`** (or asks to run the RN example with production / local SDK):

1. `cd` to the `ketch-react-native` repository root.

2. Run the helper script with a **required** platform argument:

**Released package (default)** — rewrites `example/package.json` to use npm. Fetches the **latest version** via `npm view` unless `KETCH_RN_VERSION` is set.

```bash
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios
```

**Local package** — uses `file:../package`:

```bash
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android local
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios local
```

The script installs dependencies, starts Metro if needed, runs `pod install` on iOS when the dependency changes, launches via `react-native run-*`, and streams logs. Stop with `Ctrl-C`.

## Manual testing basics

**Needs:** Node + yarn (or npm), Metro (started by script), Android emulator + `adb` or iOS Simulator + Xcode/CocoaPods, network for CDN/headless steps.

**Launch:** `bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios local` or `... android local` from the `ketch-react-native` repo root.

**In the app:** **SDK Health Dashboard** is the first section in the example scroll view. Provider defaults: org `ethansch061226`, property `website_smart_tag`. Provider uses `autoLoad={false}` — tap **Load** explicitly.

**Smoke flow:** **Load** → dashboard rows update → **Show Consent** → WebView/Experience rows change → (iOS) **Request ATT** then **Reload WebView** → Headless **Fetch Bootstrap**.

## Overrides

```bash
KETCH_RN_VERSION=0.6.9 bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android
DEVICE_ID=emulator-5554 bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android local
SIMULATOR_NAME="iPhone 15 Pro" bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios local
```

## Other options

```bash
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android local --build-only
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh ios --full-system-logs
bash .cursor/skills/ketch-react-native-run-sample/scripts/run-sample-app.sh android --no-logs
```

## Manual QA checklist (SDK Health Dashboard)

After launch, verify on-screen panels in `example/Main.tsx` (SDK Health Dashboard section):

1. **Connection** — Init/status; org/property/env from editable fields; data center from API Region.
2. **Load** — Tap **Load** → Load row `loading`; status updates; provider callbacks fill privacy rows.
3. **WebView / Experience** — **Show Consent** → visibility/dismiss/WebView rows update via `App.tsx` callbacks.
4. **ATT (iOS)** — **Request ATT** then **Reload WebView**; `ketch_att` row updates.
5. **Headless** — **Fetch Location** / **Fetch Bootstrap** / **Cold Start** show inline results.
6. **Event log** — Timestamped callback trace at bottom of dashboard section.

Provider uses `autoLoad={false}` — **Load** is explicit.

## Notes

- Default remote mode needs **network access** for `npm view` when not pinning with `KETCH_RN_VERSION`.
- **iOS** requires CocoaPods (`pod`) and Xcode; **Android** requires `adb`.
- Example uses `packageManager: yarn@4.2.2`; script prefers **yarn** when available.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Switch example/package.json between local file: dependency and npm registry release."""

from __future__ import annotations

import os
import re
import subprocess
import sys
from pathlib import Path

PACKAGE_NAME = "@ketch-com/ketch-react-native"
LOCAL_SPEC = "file:../package"
LOCAL_RE = re.compile(
rf'"{re.escape(PACKAGE_NAME)}":\s*"file:\.\./package"'
)
REMOTE_RE = re.compile(
rf'"{re.escape(PACKAGE_NAME)}":\s*"[\^~]?\d+\.\d+\.\d+(?:-[\w.]+)?"'
)


def latest_npm_version() -> str:
proc = subprocess.run(
["npm", "view", PACKAGE_NAME, "version"],
check=False,
capture_output=True,
text=True,
)
if proc.returncode != 0:
raise SystemExit(
f"npm view failed ({proc.returncode}) for {PACKAGE_NAME!r}:\n{proc.stderr.strip()}"
)
version = proc.stdout.strip()
if not version:
raise SystemExit(f"No version returned for {PACKAGE_NAME!r}")
return version


def remote_version() -> tuple[str, str]:
pinned = os.environ.get("KETCH_RN_VERSION", "").strip()
if pinned:
return pinned, f"exactVersion={pinned!r}"
version = latest_npm_version()
return version, f"{version!r} (latest on npm)"


def target_spec(mode: str) -> tuple[str, str]:
if mode == "local":
return LOCAL_SPEC, "local file:../package"
version, summary = remote_version()
return version, f"npm ({summary})"


def replace_dependency_text(content: str, new_spec: str) -> str:
replacement = f'"{PACKAGE_NAME}": "{new_spec}"'
if LOCAL_RE.search(content):
return LOCAL_RE.sub(replacement, content, count=1)
if REMOTE_RE.search(content):
return REMOTE_RE.sub(replacement, content, count=1)
raise SystemExit(
f"No {PACKAGE_NAME} dependency found in package.json "
'(expected "file:../package" or a semver version).'
)


def main() -> None:
if len(sys.argv) != 3:
raise SystemExit(
"Usage: configure-sample-package.py <local|remote> <path-to-package.json>"
)

mode = sys.argv[1]
package_path = Path(sys.argv[2])

if mode not in ("local", "remote"):
raise SystemExit("mode must be local or remote")
if not package_path.is_file():
raise SystemExit(f"Missing package.json: {package_path}")

new_spec, summary = target_spec(mode)
original = package_path.read_text(encoding="utf-8")
updated = replace_dependency_text(original, new_spec)

if updated != original:
package_path.write_text(updated, encoding="utf-8")
print(f"Updated {package_path} to use {summary}.")
else:
print(f"No changes needed for {package_path} ({mode}).")


if __name__ == "__main__":
main()
Loading
Loading