create-react-native-airborne 0.1.0 → 0.2.0

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 CHANGED
@@ -12,6 +12,9 @@ bun create react-native-airborne my-app
12
12
 
13
13
  - `--skip-install`: skip `bun install --workspaces`
14
14
  - `--no-git`: skip `git init`
15
+ - `--nix`: include root-level `flake.nix`, `flake.lock`, and `.envrc`
16
+
17
+ If you use `--nix`, run `direnv allow` after `cd` into the generated project.
15
18
 
16
19
  ## Development
17
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-react-native-airborne",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold a react-native-airborne starter project",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { mkdir, readdir, readFile, writeFile, copyFile, stat } from "node:fs/promises";
2
+ import { mkdir, readdir, readFile, writeFile, copyFile, rm, stat } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { fileURLToPath } from "node:url";
@@ -9,7 +9,9 @@ const flags = new Set(args.filter((arg) => arg.startsWith("--")));
9
9
  const projectNameArg = args.find((arg) => !arg.startsWith("--"));
10
10
 
11
11
  if (!projectNameArg) {
12
- console.error("Usage: bun create react-native-airborne <project-name> [--skip-install] [--no-git]");
12
+ console.error(
13
+ "Usage: bun create react-native-airborne <project-name> [--skip-install] [--no-git] [--nix]",
14
+ );
13
15
  process.exit(1);
14
16
  }
15
17
 
@@ -18,6 +20,7 @@ const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replac
18
20
  const targetDir = path.resolve(process.cwd(), projectName);
19
21
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
22
  const templateDir = path.resolve(__dirname, "../template");
23
+ const enableNix = flags.has("--nix");
21
24
 
22
25
  async function exists(filePath) {
23
26
  try {
@@ -34,7 +37,8 @@ async function copyDirectory(src, dest) {
34
37
 
35
38
  for (const entry of entries) {
36
39
  const srcPath = path.join(src, entry.name);
37
- const destPath = path.join(dest, entry.name);
40
+ const destName = entry.name === "gitignore" ? ".gitignore" : entry.name;
41
+ const destPath = path.join(dest, destName);
38
42
 
39
43
  if (entry.isDirectory()) {
40
44
  await copyDirectory(srcPath, destPath);
@@ -80,6 +84,13 @@ if (await exists(targetDir)) {
80
84
  await copyDirectory(templateDir, targetDir);
81
85
  await replaceTokens(targetDir);
82
86
 
87
+ if (!enableNix) {
88
+ const optionalNixFiles = [".envrc", "flake.nix", "flake.lock"];
89
+ for (const file of optionalNixFiles) {
90
+ await rm(path.join(targetDir, file), { force: true });
91
+ }
92
+ }
93
+
83
94
  if (!flags.has("--skip-install")) {
84
95
  const install = spawnSync("bun", ["install", "--workspaces"], {
85
96
  cwd: targetDir,
@@ -100,4 +111,7 @@ console.log("\nNext steps:");
100
111
  console.log(` cd ${projectName}`);
101
112
  console.log(" cp client/.env.example client/.env");
102
113
  console.log(" cp server/.env.example server/.env");
114
+ if (enableNix) {
115
+ console.log(" direnv allow");
116
+ }
103
117
  console.log(" just dev");
@@ -0,0 +1 @@
1
+ use flake . --no-pure-eval
@@ -19,7 +19,7 @@ jobs:
19
19
  bun-version: "1.3.4"
20
20
 
21
21
  - name: "📦 Install dependencies"
22
- run: bun install --workspaces
22
+ run: bun install --workspaces --frozen-lockfile
23
23
 
24
24
  - name: "🔍 Lint"
25
25
  run: |
@@ -1,6 +1,8 @@
1
1
  # React Native Airborne
2
2
 
3
- Opinionated React Native starter for mobile-first apps with Expo + Convex.
3
+ React Native Airborne my opinionated mobile starter for folks who want to ship iOS/Android apps fast without repeating the same setup every project.
4
+
5
+ It includes a production-ready Expo client and a Convex backend.
4
6
 
5
7
  ## 🧰 Stack
6
8
 
@@ -11,17 +13,31 @@ Opinionated React Native starter for mobile-first apps with Expo + Convex.
11
13
  - Convex backend + `convex-test`
12
14
  - Zustand + MMKV persistence
13
15
  - Expo push notifications
16
+ - Strict ESLint + Prettier setup
14
17
 
15
- ## 🤝 Contributor Guide
18
+ ## 🎯 Opinionated Defaults
16
19
 
17
- Detailed implementation and maintenance notes for engineers/agents live in `AGENTS.md`.
20
+ - Mobile-only target (iOS/Android), no web target in starter scope.
21
+ - Expo prebuild supported for local native debugging, but `client/ios` and `client/android` are not committed.
22
+ - Clerk + Convex integration wired from day one.
23
+ - Theme system includes `light`, `dark`, and `system` with persisted preference.
24
+ - Auth tokens are kept in secure storage flows, not MMKV.
25
+
26
+ ## 🗂️ Project Layout
27
+
28
+ ```text
29
+ __APP_NAME__/
30
+ client/ # Expo app (Expo Router + Native Tabs)
31
+ server/ # Convex backend
32
+ ```
18
33
 
19
34
  ## ✅ Prerequisites
20
35
 
21
36
  - Bun `1.3.4+`
22
37
  - `just` command runner
23
38
  - Expo toolchain for iOS/Android simulators
24
- - Clerk app + Convex project
39
+ - Clerk app configured for native API
40
+ - Convex project/deployment
25
41
 
26
42
  ## ⚡ Quickstart
27
43
 
@@ -38,7 +54,7 @@ cd server
38
54
  bun run dev
39
55
  ```
40
56
 
41
- Then run both apps:
57
+ Run both client and server:
42
58
 
43
59
  ```bash
44
60
  just dev
@@ -53,7 +69,7 @@ just dev
53
69
  - `just prebuild`: generate local iOS/Android native folders
54
70
  - `just ios`: launch iOS app
55
71
  - `just android`: launch Android app
56
- - `just lint`: lint/type lint checks
72
+ - `just lint`: lint checks
57
73
  - `just typecheck`: TypeScript checks
58
74
  - `just test`: client + server tests
59
75
  - `just test-client`: client tests only
@@ -72,23 +88,26 @@ just prebuild
72
88
 
73
89
  ## 🔐 Environment Variables
74
90
 
75
- ### Client (`client/.env`)
91
+ Client (`client/.env`):
76
92
 
77
93
  - `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY`
78
94
  - `EXPO_PUBLIC_CONVEX_URL`
79
95
  - `EXPO_PUBLIC_EAS_PROJECT_ID` (optional)
80
96
 
81
- ### Server (`server/.env`)
97
+ Server (`server/.env`):
82
98
 
83
99
  - `CLERK_JWT_ISSUER_DOMAIN`
84
100
  - `EXPO_PUSH_ENDPOINT` (optional)
85
101
  - `EXPO_ACCESS_TOKEN` (optional)
86
102
 
103
+ ## 🤝 Contributor Guide
104
+
105
+ Detailed implementation and maintenance notes for engineers/agents live in `AGENTS.md`.
106
+
87
107
  ## 📝 Notes
88
108
 
89
- - Mobile-only target (iOS/Android).
90
- - Do not store sensitive auth tokens in MMKV.
91
109
  - Uniwind classes are enabled by `client/global.css` and `client/metro.config.js`.
92
110
  - `SafeAreaView` is wrapped with `withUniwind` in `client/src/components/screen.tsx` for className support.
93
111
  - `server/convex/_generated` ships with starter stubs so typecheck/tests pass before deployment setup.
94
- After connecting Convex, run `cd server && bun run codegen` to regenerate.
112
+ - After connecting Convex, run `cd server && bun run codegen` to regenerate server types.
113
+ - `.direnv/` is gitignored by default.
@@ -0,0 +1,43 @@
1
+ # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2
+
3
+ # dependencies
4
+ node_modules/
5
+
6
+ # Expo
7
+ .expo/
8
+ dist/
9
+ web-build/
10
+ expo-env.d.ts
11
+
12
+ # Native
13
+ .kotlin/
14
+ *.orig.*
15
+ *.jks
16
+ *.p8
17
+ *.p12
18
+ *.key
19
+ *.mobileprovision
20
+
21
+ # Metro
22
+ .metro-health-check*
23
+
24
+ # debug
25
+ npm-debug.*
26
+ yarn-debug.*
27
+ yarn-error.*
28
+
29
+ # macOS
30
+ .DS_Store
31
+ *.pem
32
+
33
+ # local env files
34
+ .env*.local
35
+
36
+ # typescript
37
+ *.tsbuildinfo
38
+
39
+ app-example
40
+
41
+ # generated native folders
42
+ /ios
43
+ /android
@@ -0,0 +1,104 @@
1
+ {
2
+ "nodes": {
3
+ "android-nixpkgs": {
4
+ "inputs": {
5
+ "devshell": "devshell",
6
+ "flake-utils": "flake-utils",
7
+ "nixpkgs": [
8
+ "nixpkgs"
9
+ ]
10
+ },
11
+ "locked": {
12
+ "lastModified": 1770842373,
13
+ "narHash": "sha256-uCxNWY8xGGtk8YwDJXsfpKUYw8GqR/3degPbIthtFTE=",
14
+ "owner": "tadfisher",
15
+ "repo": "android-nixpkgs",
16
+ "rev": "12878f0d54f923dff305b71d04c6d837df8ce5ab",
17
+ "type": "github"
18
+ },
19
+ "original": {
20
+ "owner": "tadfisher",
21
+ "repo": "android-nixpkgs",
22
+ "type": "github"
23
+ }
24
+ },
25
+ "devshell": {
26
+ "inputs": {
27
+ "nixpkgs": [
28
+ "android-nixpkgs",
29
+ "nixpkgs"
30
+ ]
31
+ },
32
+ "locked": {
33
+ "lastModified": 1768818222,
34
+ "narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
35
+ "owner": "numtide",
36
+ "repo": "devshell",
37
+ "rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
38
+ "type": "github"
39
+ },
40
+ "original": {
41
+ "owner": "numtide",
42
+ "repo": "devshell",
43
+ "type": "github"
44
+ }
45
+ },
46
+ "flake-utils": {
47
+ "inputs": {
48
+ "systems": "systems"
49
+ },
50
+ "locked": {
51
+ "lastModified": 1731533236,
52
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
53
+ "owner": "numtide",
54
+ "repo": "flake-utils",
55
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
56
+ "type": "github"
57
+ },
58
+ "original": {
59
+ "owner": "numtide",
60
+ "repo": "flake-utils",
61
+ "type": "github"
62
+ }
63
+ },
64
+ "nixpkgs": {
65
+ "locked": {
66
+ "lastModified": 1770843696,
67
+ "narHash": "sha256-LovWTGDwXhkfCOmbgLVA10bvsi/P8eDDpRudgk68HA8=",
68
+ "owner": "NixOS",
69
+ "repo": "nixpkgs",
70
+ "rev": "2343bbb58f99267223bc2aac4fc9ea301a155a16",
71
+ "type": "github"
72
+ },
73
+ "original": {
74
+ "owner": "NixOS",
75
+ "ref": "nixpkgs-unstable",
76
+ "repo": "nixpkgs",
77
+ "type": "github"
78
+ }
79
+ },
80
+ "root": {
81
+ "inputs": {
82
+ "android-nixpkgs": "android-nixpkgs",
83
+ "nixpkgs": "nixpkgs"
84
+ }
85
+ },
86
+ "systems": {
87
+ "locked": {
88
+ "lastModified": 1681028828,
89
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
90
+ "owner": "nix-systems",
91
+ "repo": "default",
92
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
93
+ "type": "github"
94
+ },
95
+ "original": {
96
+ "owner": "nix-systems",
97
+ "repo": "default",
98
+ "type": "github"
99
+ }
100
+ }
101
+ },
102
+ "root": "root",
103
+ "version": 7
104
+ }
@@ -0,0 +1,164 @@
1
+ {
2
+ inputs = {
3
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
4
+
5
+ android-nixpkgs = {
6
+ url = "github:tadfisher/android-nixpkgs";
7
+ inputs.nixpkgs.follows = "nixpkgs";
8
+ };
9
+ };
10
+
11
+ outputs =
12
+ {
13
+ self,
14
+ nixpkgs,
15
+ android-nixpkgs,
16
+ }:
17
+ let
18
+ systems = [
19
+ "aarch64-darwin"
20
+ "x86_64-linux"
21
+ ];
22
+ forAllSystems = nixpkgs.lib.genAttrs systems;
23
+
24
+ pkgsFor =
25
+ system:
26
+ import nixpkgs {
27
+ inherit system;
28
+ config = {
29
+ allowUnfree = true;
30
+ android_sdk.accept_license = true;
31
+ };
32
+ };
33
+
34
+ androidSdkFor =
35
+ system:
36
+ android-nixpkgs.sdk.${system} (
37
+ sdkPkgs: with sdkPkgs; [
38
+ cmdline-tools-latest
39
+ build-tools-35-0-0
40
+ build-tools-36-0-0
41
+ platform-tools
42
+ platforms-android-35
43
+ platforms-android-36
44
+ ndk-27-1-12297006
45
+ ndk-27-0-12077973
46
+ cmake-3-22-1
47
+ ]
48
+ );
49
+
50
+ darwinDerivations = {
51
+ xcode-wrapper =
52
+ pkgs:
53
+ pkgs.stdenv.mkDerivation {
54
+ name = "xcode-wrapper-16.4.0";
55
+ buildInputs = [ pkgs.darwin.cctools ];
56
+ buildCommand = ''
57
+ mkdir -p $out/bin
58
+
59
+ cat > $out/bin/xcodebuild << EOF
60
+ #!/bin/sh
61
+ exec /usr/bin/xcodebuild "\$@"
62
+ EOF
63
+
64
+ cat > $out/bin/xcrun << EOF
65
+ #!/bin/sh
66
+ exec /usr/bin/xcrun "\$@"
67
+ EOF
68
+
69
+ cat > $out/bin/xcode-select << EOF
70
+ #!/bin/sh
71
+ if [ "\$1" = "-p" ] && [ -n "\$DEVELOPER_DIR" ]; then
72
+ echo "\$DEVELOPER_DIR"
73
+ else
74
+ exec /usr/bin/xcode-select "\$@"
75
+ fi
76
+ EOF
77
+
78
+ cat > $out/bin/codesign << EOF
79
+ #!/bin/sh
80
+ exec /usr/bin/codesign "\$@"
81
+ EOF
82
+
83
+ cat > $out/bin/ld << EOF
84
+ #!/bin/sh
85
+ exec /usr/bin/ld "\$@"
86
+ EOF
87
+
88
+ cat > $out/bin/clang << EOF
89
+ #!/bin/sh
90
+ exec /usr/bin/clang "\$@"
91
+ EOF
92
+
93
+ chmod +x $out/bin/*
94
+
95
+ if [ -d "/Applications/Xcode.app" ]; then
96
+ DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
97
+ elif [ -d "/Applications/Xcode-16.4.0.app" ]; then
98
+ DEVELOPER_DIR="/Applications/Xcode-16.4.0.app/Contents/Developer"
99
+ else
100
+ echo "Error: Xcode not found"
101
+ exit 1
102
+ fi
103
+
104
+ echo "export DEVELOPER_DIR=\"$DEVELOPER_DIR\"" > $out/bin/env.sh
105
+ '';
106
+ };
107
+
108
+ };
109
+
110
+ mkShellFor =
111
+ system:
112
+ let
113
+ pkgs = pkgsFor system;
114
+ androidSdk = androidSdkFor system;
115
+ basePackages = with pkgs; [
116
+ androidSdk
117
+ bun
118
+ just
119
+ ];
120
+
121
+ darwinPackages = with pkgs; [
122
+ bundler
123
+ cocoapods
124
+ (darwinDerivations.xcode-wrapper pkgs)
125
+ ];
126
+
127
+ darwinHook = ''
128
+ export LC_ALL=en_US.UTF-8
129
+ export LANG=en_US.UTF-8
130
+ export JAVA_HOME="${pkgs.jdk17.home}"
131
+
132
+ unset SDKROOT
133
+
134
+ if [ -f "${darwinDerivations.xcode-wrapper pkgs}/bin/env.sh" ]; then
135
+ source "${darwinDerivations.xcode-wrapper pkgs}/bin/env.sh"
136
+ fi
137
+
138
+ export LD=/usr/bin/clang
139
+ export LD_FOR_TARGET=/usr/bin/clang
140
+
141
+ echo "iOS development environment:"
142
+ echo "DEVELOPER_DIR: $DEVELOPER_DIR"
143
+ xcodebuild -version
144
+ '';
145
+
146
+ linuxHook = ''
147
+ export LC_ALL=en_US.UTF-8
148
+ export LANG=en_US.UTF-8
149
+ export JAVA_HOME="${pkgs.jdk17.home}"
150
+ '';
151
+
152
+ in
153
+ pkgs.mkShellNoCC {
154
+ buildInputs = if system == "aarch64-darwin" then basePackages ++ darwinPackages else basePackages;
155
+
156
+ shellHook = if system == "aarch64-darwin" then darwinHook else linuxHook;
157
+ };
158
+ in
159
+ {
160
+ devShells = forAllSystems (system: {
161
+ default = mkShellFor system;
162
+ });
163
+ };
164
+ }
@@ -0,0 +1,28 @@
1
+ # dependencies
2
+ node_modules/
3
+
4
+ # logs
5
+ *.log
6
+
7
+ # expo
8
+ .expo/
9
+ .expo-shared/
10
+ client/.expo/
11
+
12
+ # native folders (generated locally via prebuild)
13
+ client/ios/
14
+ client/android/
15
+
16
+ # test/build artifacts
17
+ coverage/
18
+ *.tsbuildinfo
19
+
20
+ # env files
21
+ .env
22
+ .env.*
23
+ !.env.example
24
+
25
+ # macOS
26
+ .DS_Store
27
+ .npmrc
28
+ .direnv/
@@ -0,0 +1,2 @@
1
+
2
+ .env.local