Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9a515e5
feat: add create-react-native-brownfield scaffold for RN CLI
DanielPotorCK May 22, 2026
b168ad8
fix: make scaffold RN version resolution ESM-safe and project-root sc…
gedu Oct 1, 2026
c60796e
test: cover vanilla scaffold paths in podfile, framework files, and a…
gedu Oct 1, 2026
9746682
fix: make scaffold CLI run end-to-end on a real RN CLI app
gedu Oct 1, 2026
0314268
refactor: harden the RN CLI scaffold against review follow-ups
gedu Oct 1, 2026
4d14c1b
fix: emit AGP9-safe gradle from the RN CLI scaffold
gedu Oct 1, 2026
fe83b08
feat: install brownfield deps and generate brownfield.config.json in …
gedu Oct 1, 2026
53cec09
fix: pin scaffolded gradle plugin to a published version
gedu Oct 1, 2026
a60f3de
chore: wire create-react-native-brownfield into release and CI
gedu Oct 1, 2026
cf64f41
fix: address review advisories in scaffold file writes and CI smoke
gedu Oct 1, 2026
d402795
docs: document the automated RN CLI setup path
gedu Oct 1, 2026
9b15b0d
fix: keep the scaffold API off the react-native package entry
gedu Oct 2, 2026
d7d3c1a
fix: re-export ReactBrownfield from vanilla interface and honor ios.s…
gedu Oct 2, 2026
b0a3316
fix(ci): declare build outputs for create-react-native-brownfield
gedu Oct 2, 2026
39f4a90
revert: drop published gradle plugin version pin
gedu Oct 5, 2026
24c329d
refactor: scope create CLI package under @callstack
gedu Oct 5, 2026
0c5b2bb
docs: use PackageManagerTabs for the create command
gedu Oct 5, 2026
3983e1b
docs: trim create CLI README to point at the website
gedu Oct 5, 2026
bcaefc9
chore: align create-react-native-brownfield version with fixed group …
gedu Oct 5, 2026
dbd8098
chore: merge callstack main into feat/create-react-native-brownfield
gedu Oct 5, 2026
f047f0e
chore: align create-react-native-brownfield version with fixed group …
gedu Oct 5, 2026
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
3 changes: 2 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"@callstack/brownfield-navigation",
"@callstack/brownie",
"@callstack/brownfield-cli",
"@callstack/react-native-brownfield"
"@callstack/react-native-brownfield",
"@callstack/create-react-native-brownfield"
]
],
"linked": [],
Expand Down
6 changes: 6 additions & 0 deletions .changeset/plenty-owls-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@callstack/react-native-brownfield': minor
'@callstack/create-react-native-brownfield': minor
---

add @callstack/create-react-native-brownfield: scaffold Brownfield packaging targets in an existing React Native CLI project (`npm create @callstack/react-native-brownfield`), plus the `@callstack/react-native-brownfield/scaffold` subpath export it uses
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,32 @@ jobs:
run: |
yarn workspace @callstack/react-native-brownfield brownfield --version

scaffold-smoke-android:
name: Scaffold smoke (RNC CLI app - Android)
runs-on: ubuntu-latest
timeout-minutes: 60
needs: [filter, build-lint]
if: |
always() &&
needs.filter.outputs.packages == 'true' &&
(needs.build-lint.result == 'success' || needs.build-lint.result == 'skipped')

steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6

- name: Setup
uses: ./.github/actions/setup

- name: Prepare Android
uses: ./.github/actions/prepare-android

- name: Build packages
run: yarn build

- name: Scaffold a fresh RN CLI app and assemble the packaging module
run: bash ./scripts/ci-scaffold-smoke-android.sh

ios-native-tests:
name: iOS native tests
runs-on: macos-26
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ buck-out/
# generated by bob
lib/

# CLI build output
packages/create-react-native-brownfield/dist/

# Turbo
.turbo

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/docs/getting-started/_meta.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
["introduction", "quick-start", "ios", "android", "expo", "examples"]
["introduction", "quick-start", "rnc-cli", "ios", "android", "expo", "examples"]
4 changes: 4 additions & 0 deletions docs/docs/docs/getting-started/android.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { PackageManagerTabs, Tabs, Tab } from '@theme';

This guide walks you through packaging your React Native app as an **AAR** and integrating it into your native Android app.

:::tip Community CLI apps can skip steps 1-7
For a React Native Community CLI app, `npm create @callstack/react-native-brownfield@latest` creates the library module, wires the Gradle plugin and the React Native dependencies, writes the host manager, adds the build config fields and Maven publishing, and generates `brownfield.config.json`. See [React Native CLI Integration](/docs/getting-started/rnc-cli) and continue from step 8.
:::

## Prerequisites

- React Native app with `@callstack/react-native-brownfield` installed
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/docs/getting-started/ios.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { PackageManagerTabs } from '@theme';

This guide walks you through packaging your React Native app as an **XCFramework** and integrating it into your native iOS app.

:::tip Community CLI apps can skip steps 1-5
For a React Native Community CLI app, `npm create @callstack/react-native-brownfield@latest` creates the framework target and its build settings, updates the `Podfile`, copies the bundle script, writes the framework interface and generates `brownfield.config.json`. See [React Native CLI Integration](/docs/getting-started/rnc-cli), then check the [static linking requirement](#static-linking-requirement) in step 2 before you run `pod install`, and continue from step 6.
:::

## Prerequisites

- React Native app with `@callstack/react-native-brownfield` installed
Expand Down
11 changes: 11 additions & 0 deletions docs/docs/docs/getting-started/quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import { PackageManagerTabs } from '@theme';

The React Native Brownfield library is intended to be installed in a **React Native app** that is later consumed as a **framework artifact** by your native iOS or Android app.

:::tip React Native CLI app?
If your app uses the React Native Community CLI, one command performs this installation **and** sets up the packaging targets:

```bash
npm create @callstack/react-native-brownfield@latest
```

See [React Native CLI Integration](/docs/getting-started/rnc-cli) for what it changes and how to re-run it safely. The manual steps below apply to any setup, including Expo (see [Expo Integration](/docs/getting-started/expo)).
:::

In your React Native project run:

<PackageManagerTabs command="install @callstack/react-native-brownfield" />
Expand Down Expand Up @@ -70,6 +80,7 @@ The **Brownfield CLI automates all of this** with simple commands.

Now that you have the library installed, follow the platform-specific guides to package your React Native app and integrate it with your native app:

- [**React Native CLI Integration**](/docs/getting-started/rnc-cli) - Set up packaging targets automatically in a Community CLI app
- [**iOS Integration**](/docs/getting-started/ios) - Package as XCFramework and integrate with your iOS app
- [**Android Integration**](/docs/getting-started/android) - Package as AAR and integrate with your Android app

Expand Down
84 changes: 84 additions & 0 deletions docs/docs/docs/getting-started/rnc-cli.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { PackageManagerTabs } from '@theme';

# React Native CLI Integration

This guide walks you through packaging a **React Native Community CLI** app (non-Expo) as an **XCFramework** or an **AAR**.

If you use Expo, see [Expo Integration](/docs/getting-started/expo) instead — the Expo config plugin does this setup for you on every prebuild.

## Automated Setup

Run this command from the root of your React Native project:

<PackageManagerTabs command="create @callstack/react-native-brownfield@latest" />

The command wires up everything the [Brownfield CLI](/docs/cli/brownfield) needs to produce your artifacts, so you don't have to configure the packaging targets by hand.

### What it changes

| Area | Change |
| --- | --- |
| `android/build.gradle` | Adds the `brownfield-gradle-plugin` classpath |
| `android/settings.gradle` | Includes the new Android library module |
| `android/<androidModuleName>/` | New packaging module with `build.gradle.kts`, autolinking setup, and a `ReactNativeHostManager.kt` written for a non-Expo host |
| `ios/<project>.xcodeproj` | New framework target with the **Bundle React Native code and images** phase copied from your app target |
| `ios/Podfile` | Nested `target '<iosFrameworkName>'` block with `inherit! :complete` |
| `ios/<iosFrameworkName>/` | Framework sources (`<iosFrameworkName>.swift`, `Info.plist`) |
| `package.json` | Adds `@callstack/react-native-brownfield` and `@callstack/brownfield-cli` dependencies and the `package:ios` / `package:android` scripts |
| `brownfield.config.json` | Generated packaging settings: iOS scheme and configuration, Android module name and variant |

### Options

| Flag | Default | Purpose |
| --- | --- | --- |
| `--path <path>` | `.` | Project root to scaffold into |
| `--ios-framework-name <name>` | `BrownfieldLib` | iOS framework target name (also the framework directory name) |
| `--android-module-name <name>` | `brownfieldlib` | Android library module name |
| `--debug` | `false` | Verbose logging |

:::note `brownfield init` is not available yet
The same setup will eventually be exposed as an `init` subcommand of `@callstack/brownfield-cli`. Until then, `@callstack/create-react-native-brownfield` is the command to run.
:::

### Re-running the command

The command is **idempotent** — run it as many times as you like. It detects work it already did, so it does not duplicate targets, build phases, Podfile blocks, dependencies, or scripts. It overwrites `brownfield.config.json` with content derived from your framework and module names, so an unchanged re-run leaves the file byte-identical.

It does edit files you own, so review the result with `git diff` before committing. Existing dependency versions and existing `package:*` scripts are never overwritten.

:::tip If a run fails halfway
Re-run it after fixing the cause. Completed steps are detected and skipped, so you do not need to revert anything — inspect the partial state with `git diff`.
:::

### Prerequisites

- A React Native Community CLI project (Expo projects use the [config plugin](/docs/getting-started/expo) instead)
- Node.js 20 or newer
- CocoaPods and Xcode for iOS, Android SDK for Android

## Package Your App

After the setup command, install dependencies and run the packaging scripts it added:

<PackageManagerTabs command="install" />

<PackageManagerTabs command="run package:ios" />

<PackageManagerTabs command="run package:android" />

`package:ios` writes the XCFramework to `ios/.brownfield/package/build/`; `package:android` builds the Fat-AAR. Both read their settings from `brownfield.config.json`, so no flags are needed. See [Configuration files](/docs/api-reference/configuration) to customize them.

## Integrate With Your Native App

Embedding the artifact in your native app is a manual step, and the guides below cover it in detail — including the packaging setup itself, if you prefer to configure it by hand instead of running the command:

- [**iOS Integration**](/docs/getting-started/ios) — package as XCFramework and integrate into your iOS app. If you ran the automated setup, steps 1, 3, 4 and 5 are already done for you (framework target with the required build settings, bundle script, framework interface, and configuration file). Step 2 is done except for the optional static-linking snippet — check whether your `Podfile` needs it before you `pod install`. Then continue from step 6, **Create the XCFramework**.
- [**Android Integration**](/docs/getting-started/android) — package as AAR and integrate into your Android app. If you ran the automated setup, steps 1–7 are already done (library module, Gradle plugin, React Native dependencies, host manager, build config fields, Maven publishing, and configuration file). Continue from step 8, **Create the AAR**.

Both guides also describe the manual alternative in full, so you can use them to check the generated wiring or to customize it beyond what the command offers.

## Next Steps

- [iOS API Reference](/docs/api-reference/react-native-brownfield/swift)
- [Android API Reference](/docs/api-reference/react-native-brownfield/kotlin)
- [Guidelines](/docs/guides/guidelines)
17 changes: 17 additions & 0 deletions packages/create-react-native-brownfield/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# @callstack/create-react-native-brownfield

Scaffolds [React Native Brownfield](https://oss.callstack.com/react-native-brownfield/) packaging targets into an existing React Native Community CLI project (non-Expo).

## Usage

From the root of your React Native project:

```bash
npm create @callstack/react-native-brownfield@latest
```

Expo projects use the [Expo config plugin](https://oss.callstack.com/react-native-brownfield/docs/getting-started/expo) instead.

## Documentation

For what the command changes, its options, and the next steps, visit [our documentation](https://oss.callstack.com/react-native-brownfield/docs/getting-started/rnc-cli).
4 changes: 4 additions & 0 deletions packages/create-react-native-brownfield/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import eslintRnConfig from '../../eslint.config.rn.mjs';

/** @type {import('eslint').Linter.Config[]} */
export default eslintRnConfig;
49 changes: 49 additions & 0 deletions packages/create-react-native-brownfield/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@callstack/create-react-native-brownfield",
"version": "5.2.0",
"license": "MIT",
"author": "Callstack",
"bin": "dist/main.js",
"homepage": "https://github.com/callstack/react-native-brownfield",
"repository": {
"url": "git+https://github.com/callstack/react-native-brownfield.git"
},
"description": "Scaffold React Native Brownfield in an existing React Native CLI project",
"scripts": {
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.json",
"dev": "tsc -p tsconfig.json --watch"
},
"keywords": [
"react-native",
"brownfield",
"create",
"scaffold"
],
"files": [
"src",
"dist",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__",
"!**/.*",
"README.md"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"@callstack/react-native-brownfield": "workspace:^",
"commander": "^14.0.3"
},
"devDependencies": {
"@types/node": "^25.5.0",
"eslint": "^9.39.3",
"globals": "^17.3.0",
"typescript": "5.9.3"
},
"engines": {
"node": ">=20"
}
}
33 changes: 33 additions & 0 deletions packages/create-react-native-brownfield/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env node

import { Command } from 'commander';

import { scaffoldBrownfieldInRncCliProject } from '@callstack/react-native-brownfield/scaffold';

const program = new Command();

program
.name('create-react-native-brownfield')
.description(
'Scaffold React Native Brownfield packaging targets in an existing React Native CLI project.'
)
.option('-p, --path <path>', 'path to the React Native project', '.')
.option(
'--ios-framework-name <name>',
'iOS framework target name (default: BrownfieldLib)'
)
.option(
'--android-module-name <name>',
'Android library module name (default: brownfieldlib)'
)
.option('--debug', 'enable verbose logging', false)
.action(async (opts) => {
await scaffoldBrownfieldInRncCliProject({
projectRoot: opts.path,
iosFrameworkName: opts.iosFrameworkName,
androidModuleName: opts.androidModuleName,
debug: !!opts.debug,
});
});

program.parse(process.argv);
17 changes: 17 additions & 0 deletions packages/create-react-native-brownfield/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022",
"lib": ["ES2022"],
"types": ["node"],
"declaration": true,
"sourceMap": true,
"verbatimModuleSyntax": false,
"allowImportingTsExtensions": false
},
"include": ["src/**/*.ts"]
}
14 changes: 14 additions & 0 deletions packages/create-react-native-brownfield/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"inputs": [
"src/**/*",
"tsconfig*.json",
"package.json"
],
"outputs": ["dist/**"]
}
}
}
16 changes: 15 additions & 1 deletion packages/react-native-brownfield/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@
"default": "./lib/commonjs/expo-config-plugin/app.plugin.js"
}
},
"./scaffold": {
"source": "./src/scaffold/index.ts",
"import": {
"types": "./lib/typescript/module/src/scaffold/index.d.ts",
"default": "./lib/module/scaffold/index.js"
},
"require": {
"types": "./lib/typescript/commonjs/src/scaffold/index.d.ts",
"default": "./lib/commonjs/scaffold/index.js"
}
},
"./package.json": "./package.json"
},
"scripts": {
Expand Down Expand Up @@ -89,7 +100,10 @@
"@expo/config-plugins": ">=54 <100"
},
"dependencies": {
"@callstack/brownfield-cli": "workspace:^"
"@callstack/brownfield-cli": "workspace:^",
"@react-native-community/cli-config": "^20.0.0",
"@react-native-community/cli-types": "^20.0.0",
"xcode": "^3.0.1"
},
"devDependencies": {
"@babel/core": "^7.29.6",
Expand Down
Loading
Loading