Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions .claude/hooks/format-file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
#
# PostToolUse hook: formats a file with Prettier right after Claude Code writes
# or edits it, so formatting never has to be fixed up later (or caught by CI).
#
# It runs the workspace's pinned Prettier with `--ignore-unknown`, so it is a
# no-op on files Prettier can't format and it honors .prettierignore — meaning
# it is safe to run on every write regardless of file type.
set -euo pipefail

ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"

# The hook payload arrives as JSON on stdin; the edited path is tool_input.file_path.
FILE_PATH="$(node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(s);process.stdout.write(String((j.tool_input&&j.tool_input.file_path)||""))}catch{process.stdout.write("")}})' 2>/dev/null || true)"

# Nothing to do without a real, existing file.
[ -n "$FILE_PATH" ] && [ -f "$FILE_PATH" ] || exit 0

# Only format files inside the repo (skip scratchpad/other locations).
ABS_FILE="$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd)/$(basename "$FILE_PATH")" || exit 0
case "$ABS_FILE" in
"$ROOT"/*) ;;
*) exit 0 ;;
esac

# Use the workspace Prettier if dependencies are installed; otherwise skip.
PRETTIER="$ROOT/node_modules/.bin/prettier"
[ -x "$PRETTIER" ] || exit 0

# Never fail the tool call over formatting.
"$PRETTIER" --ignore-unknown --write "$ABS_FILE" >/dev/null 2>&1 || true
exit 0
74 changes: 74 additions & 0 deletions .claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
#
# SessionStart hook: bootstraps the repo so a fresh Claude Code worker can build,
# lint and test the Node.js tooling packages out of the box.
#
# It ensures the Node.js version pinned in .nvmrc (which matches CI), installs
# the pnpm workspace dependencies and builds the TypeScript project references.
#
# Native iOS/Android artifacts are intentionally NOT built here: they require the
# Android NDK / Apple toolchains which are not present on a generic Linux worker.
# See CLAUDE.md for how to build those when you actually need them.
set -euo pipefail

# Only bootstrap in remote (Claude Code on the web) workers. Local machines
# usually already have their own Node setup, so we don't want to interfere.
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}"

# --- Ensure the Node.js version from .nvmrc via nvm ---------------------------
# .nvmrc pins the Node version (matching CI). package.json's devEngines requires
# Node ^24 / pnpm ^10, and the package manager refuses to install with an older
# runtime. nvm ships on the remote workers, so use it to install and select the
# pinned version.
NVM_DIR="${NVM_DIR:-/opt/nvm}"
if [ ! -s "$NVM_DIR/nvm.sh" ] && [ -s "$HOME/.nvm/nvm.sh" ]; then
NVM_DIR="$HOME/.nvm"
fi

if [ -s "$NVM_DIR/nvm.sh" ]; then
export NVM_DIR
# nvm.sh dereferences unset variables, so relax `set -u` while it is active.
set +u
# shellcheck disable=SC1091
. "$NVM_DIR/nvm.sh"
# No version argument: nvm reads .nvmrc from the current directory.
nvm install >/dev/null
nvm use >/dev/null
# Resolve the bin dir for the pinned version explicitly. `nvm which current`
# can report the worker's default Node when it sits earlier on PATH, so ask
# for the .nvmrc version by name instead.
NVMRC_VERSION="$(cat "$PWD/.nvmrc" 2>/dev/null || echo default)"
NODE_BIN="$(dirname "$(nvm which "$NVMRC_VERSION")")"
set -u

export PATH="$NODE_BIN:$PATH"

# Persist the resolved Node toolchain on PATH for the rest of the session.
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
echo "export PATH=\"$NODE_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE"
fi
else
echo "warning: nvm not found; using system Node $(node -v). package.json requires Node ^24." >&2
fi

# --- Select pnpm via Corepack ------------------------------------------------
# The repo uses pnpm, pinned in package.json's "packageManager" field. Corepack
# ships with Node and provisions that exact pnpm version on demand, so we don't
# depend on whatever pnpm (if any) the worker image happens to have.
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
corepack enable >/dev/null 2>&1 || true

echo "Using Node $(node -v) / pnpm $(pnpm -v)"

# --- Install dependencies and build the TypeScript packages -------------------
# Plain `pnpm install` (not `--frozen-lockfile`) so the resolved node_modules can
# be cached between sessions; it stays deterministic thanks to the committed
# pnpm-lock.yaml.
pnpm install
pnpm run build

echo "Bootstrap complete: dependencies installed and TypeScript packages built."
25 changes: 25 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-file.sh"
}
]
}
]
}
}
79 changes: 5 additions & 74 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,8 @@
# Copilot Instructions for React Native Node-API

This is a **monorepo** that brings Node-API support to React Native, enabling native addons written in C/C++/Rust to run on React Native across iOS and Android.
The shared, tool-agnostic agent instructions for this repository now live in the
top-level [`AGENTS.md`](../AGENTS.md). Please read that file.

## Package-Specific Instructions

**IMPORTANT**: Before working on any package, always check for and read package-specific `copilot-instructions.md` files in the package directory. These contain critical preferences and patterns for that specific package.

## Architecture Overview

**Core Flow**: JS `require("./addon.node")` → Babel transform → `requireNodeAddon()` TurboModule call → native library loading → Node-API module initialization

### Package Architecture

See the [README.md](../README.md#packages) for detailed descriptions of each package and their roles in the system. Key packages include:

- `packages/host` - Core Node-API runtime and Babel plugin
- `packages/cmake-rn` - CMake wrapper for native builds
- `packages/cmake-file-api` - TypeScript wrapper for CMake File API with Zod validation
- `packages/ferric` - Rust/Cargo wrapper with napi-rs integration
- `packages/gyp-to-cmake` - Legacy binding.gyp compatibility
- `apps/test-app` - Integration testing harness

## Critical Build Dependencies

- **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377))
- **Prebuilt Binary Spec**: All tools must output to the exact naming scheme:
- Android: `*.android.node/` with jniLibs structure + `react-native-node-api-module` marker file
- iOS: `*.apple.node` (XCFramework renamed) + marker file

## Essential Workflows

### Development Setup

```bash
pnpm install && pnpm run build # Install deps and build all packages
pnpm run bootstrap # Build native components (weak-node-api, examples)
```

### Package Development

- **TypeScript project references**: Use `tsc --build` for incremental compilation
- **Workspace scripts**: Most build/test commands use pnpm workspaces (`--filter` flag), run in topological (dependency) order and fail fast
- **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code
- **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions.

## Key Patterns

### Babel Transformation

The core magic happens in `packages/host/src/node/babel-plugin/plugin.ts`:

```js
// Input: require("./addon.node")
// Output: require("react-native-node-api").requireNodeAddon("pkg-name--addon")
```

### CMake Integration

For linking against Node-API in CMakeLists.txt:

```cmake
include(${WEAK_NODE_API_CONFIG})
target_link_libraries(addon PRIVATE weak-node-api)
```

### Cross-Platform Naming

Library names use double-dash separation: `package-name--path-component--addon-name`

### Testing

- **Individual packages**: Some packages have VS Code test tasks and others have their own test scripts for focused iteration (e.g., `pnpm --filter cmake-rn run test`). Use the latter only if the former is missing.
- **Cross-package**: Use root-level `pnpm test` for cross-package testing once individual package tests pass
- **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed

**Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files.
**IMPORTANT**: Before working on any package, also check for and read
package-specific instruction files (`AGENTS.md` or `copilot-instructions.md`) in
the package directory.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lts/krypton
107 changes: 107 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# AGENTS.md

Instructions for AI coding agents working in the React Native Node-API repo.

This is a **monorepo** that brings Node-API support to React Native, enabling native addons written in C/C++/Rust to run on React Native across iOS and Android.

## Package-Specific Instructions

**IMPORTANT**: Before working on any package, always check for and read package-specific instruction files (`AGENTS.md` or `copilot-instructions.md`) in the package directory. These contain critical preferences and patterns for that specific package.

## Architecture Overview

**Core Flow**: JS `require("./addon.node")` → Babel transform → `requireNodeAddon()` TurboModule call → native library loading → Node-API module initialization

### Package Architecture

See the [README.md](README.md#packages) for detailed descriptions of each package and their roles in the system. Key packages include:

- `packages/host` - Core Node-API runtime and Babel plugin
- `packages/cmake-rn` - CMake wrapper for native builds
- `packages/cmake-file-api` - TypeScript wrapper for CMake File API with Zod validation
- `packages/ferric` - Rust/Cargo wrapper with napi-rs integration
- `packages/gyp-to-cmake` - Legacy binding.gyp compatibility
- `apps/test-app` - Integration testing harness

## Environment & Bootstrap

- **Node.js 24 is required** — pinned in `.nvmrc` as `lts/krypton` (matching CI); `package.json`'s `devEngines` requires Node `^24` and pnpm `^10`, and the package manager refuses to install with an older runtime. With nvm: `nvm install && nvm use`.
- **pnpm is the package manager** — pinned in `package.json`'s `packageManager` field. Let Corepack (bundled with Node) provide it: `corepack enable`, then use `pnpm`.
- Standard setup for the Node.js tooling packages:

```bash
pnpm install # Install workspace dependencies
pnpm run build # Incremental TypeScript build (tsc --build)
```

- On Claude Code on the web, `.claude/hooks/session-start.sh` performs the above automatically (selecting the `.nvmrc` version via nvm and pnpm via Corepack) at session start.
- **Native (iOS/Android) builds are not part of the default bootstrap.** `pnpm run bootstrap` and the native `bootstrap` scripts compile artifacts that require the Android NDK / Apple toolchains, which are absent on a generic Linux worker. Focus on the Node.js tooling packages; pass an explicit target (e.g. `pnpm exec ferric --apple`) only when the corresponding SDK is installed.

## Prefer an upstream fix over a local workaround

When a build/runtime failure looks like a known upstream bug, before writing a
patch or workaround:

1. Find where the fix actually landed and **verify at the source** — the
changelog, lockfile, or the dependency's own manifest/podspec for a _specific
installable version_, not the version list and not a related package's
timeline (a fork or platform variant may carry a fix on a line its upstream
never did).
2. If an installable version within our constraints contains the fix, prefer the
**smallest** bump that includes it (patch > minor > major) over a workaround.
3. Treat the upgrade as a hypothesis under test: say so, and be ready to revert —
every upgrade adds new unknown-bug surface. If it doesn't fix the issue, throw
it away rather than stacking a workaround on top of it.
4. If the bump is more than a patch, or widens scope/risk, check with me before
committing to it.
5. If no fixed version is reachable, a workaround is fine — but comment it with
the exact condition that makes it removable (e.g. "remove once dep ships
fmt ≥ 12.1"), and if you write that condition, verify it isn't already met.

## Critical Build Dependencies

- **Custom Hermes**: Currently depends on a patched Hermes with Node-API support (see [facebook/hermes#1377](https://github.com/facebook/hermes/pull/1377))
- **Prebuilt Binary Spec**: All tools must output to the exact naming scheme:
- Android: `*.android.node/` with jniLibs structure + `react-native-node-api-module` marker file
- iOS: `*.apple.node` (XCFramework renamed) + marker file

## Essential Workflows

### Package Development

- **TypeScript project references**: Use `tsc --build` for incremental compilation
- **Workspace scripts**: Most build/test commands use pnpm workspaces (`--filter` flag), run in topological (dependency) order and fail fast
- **Focus on Node.js packages**: AI development primarily targets the Node.js tooling packages rather than native mobile code
- **No TypeScript type asserts**: You have to ask explicitly and justify if you want to add `as` type assertions.

## Key Patterns

### Babel Transformation

The core magic happens in `packages/host/src/node/babel-plugin/plugin.ts`:

```js
// Input: require("./addon.node")
// Output: require("react-native-node-api").requireNodeAddon("pkg-name--addon")
```

### CMake Integration

For linking against Node-API in CMakeLists.txt:

```cmake
include(${WEAK_NODE_API_CONFIG})
target_link_libraries(addon PRIVATE weak-node-api)
```

### Cross-Platform Naming

Library names use double-dash separation: `package-name--path-component--addon-name`

### Testing

- **Individual packages**: Some packages have VS Code test tasks and others have their own `test` scripts for focused iteration (e.g., `pnpm --filter cmake-rn run test`). Use the latter only if the former is missing.
- **Cross-package**: Use root-level `pnpm test` for cross-package testing once individual package tests pass
- **Mobile integration**: Available but not the primary AI development focus - ask the developer to run those tests as needed

**Documentation**: Integration details, platform setup, and toolchain configuration are covered in existing repo documentation files.
29 changes: 29 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# CLAUDE.md

Guidance for Claude Code working in this repository.

The architecture overview, package layout, coding patterns, and
environment/bootstrap notes are shared with other AI tools and live in the
top-level, tool-agnostic instructions file — prefer editing that over
duplicating content here:

@AGENTS.md

## Claude Code on the web

A `SessionStart` hook (`.claude/hooks/session-start.sh`) bootstraps remote
workers automatically: it selects the Node.js version pinned in `.nvmrc` via
`nvm` (Node 24, as required by `package.json`'s `devEngines`) and pnpm via
Corepack, then runs `pnpm install` and `pnpm run build`, so a fresh session is
ready to build, lint, and test the Node.js tooling packages.

See the "Environment & Bootstrap" section of `AGENTS.md` for the manual steps and
for why native iOS/Android builds are not part of the default bootstrap.

## Formatting

A `PostToolUse` hook (`.claude/hooks/format-file.sh`) runs the workspace's
Prettier on every file Claude Code writes or edits inside the repo. It uses
`--ignore-unknown`, so it is a no-op on files Prettier can't format and it honors
`.prettierignore` — keeping the tree formatted without a separate `prettier:write`
pass. It's a convenience only; CI's `prettier:check` remains the source of truth.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copilot Instructions for cmake-file-api Package
# AGENTS.md — cmake-file-api Package

This package provides a TypeScript wrapper around the CMake File API using Zod schemas for validation.

Expand Down
Loading