@fadyshawky/react-native-magic 2.0.9 → 2.1.1
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/.vscode/settings.json +2 -1
- package/README.md +55 -214
- package/package.json +9 -90
- package/scripts/askPackageName.js +165 -0
- package/template/.env.example +12 -0
- package/template/App.tsx +20 -16
- package/template/CHANGELOG.md +25 -0
- package/template/android/app/build.gradle +3 -3
- package/template/android/app/src/main/java/com/reactnativemagic/MainActivity.kt +8 -2
- package/template/android/app/src/main/java/com/reactnativemagic/MainApplication.kt +12 -29
- package/template/android/build.gradle +5 -5
- package/template/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/template/docs/ARCHITECTURE.md +27 -0
- package/template/docs/BEST_PRACTICES.md +33 -0
- package/template/docs/CUSTOMIZATION.md +53 -0
- package/template/index.js +1 -0
- package/template/package.json +44 -89
- package/template/src/common/components/AppStatusBar.tsx +24 -0
- package/template/src/common/components/Cards.tsx +1 -1
- package/template/src/common/components/EmptyView.tsx +1 -1
- package/template/src/common/components/OTPInput.tsx +1 -1
- package/template/src/common/components/PrimaryButton.tsx +5 -5
- package/template/src/common/components/PrimaryTextInput.tsx +4 -4
- package/template/src/common/components/RadioButton.tsx +1 -1
- package/template/src/common/components/RadioIcon.tsx +4 -4
- package/template/src/common/components/SnackbarProvider.tsx +11 -0
- package/template/src/common/components/TryAgain.tsx +3 -3
- package/template/src/common/validations/errorValidations.ts +1 -1
- package/template/src/core/api/serverHeaders.ts +38 -9
- package/template/src/core/config/index.ts +13 -0
- package/template/src/core/store/store.tsx +3 -53
- package/template/src/core/theme/colors.ts +3 -0
- package/template/src/core/theme/commonSizes.ts +3 -0
- package/template/src/core/theme/fonts.ts +3 -0
- package/template/src/core/utils/stringUtils.ts +2 -45
- package/template/src/navigation/TabBar.tsx +5 -5
- package/template/src/screens/index.tsx +0 -15
- package/template/tsconfig.json +6 -1
- package/template.config.js +4 -2
package/.vscode/settings.json
CHANGED
package/README.md
CHANGED
|
@@ -1,266 +1,107 @@
|
|
|
1
1
|
# ReactNativeMagic
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Plug and play** – create your app and start developing without hassle.
|
|
4
|
+
|
|
5
|
+
A production-ready React Native template with TypeScript, Redux, React Navigation, and a scalable architecture (Uprise-style). Use it to bootstrap new apps with one command.
|
|
4
6
|
|
|
5
7
|
## Requirements
|
|
6
8
|
|
|
7
|
-
- Node.js >=
|
|
9
|
+
- **Node.js >= 20** ([Download](https://nodejs.org/en/download/))
|
|
8
10
|
- JDK >= 11 ([Download](https://www.oracle.com/java/technologies/downloads/))
|
|
9
|
-
- Ruby >= 2.7.5 (for iOS)
|
|
10
|
-
- Xcode (for iOS)
|
|
11
|
-
- Android Studio (for Android) ([Download](https://developer.android.com/studio))
|
|
11
|
+
- Ruby >= 2.7.5 (for iOS)
|
|
12
|
+
- Xcode (for iOS) / Android Studio (for Android)
|
|
12
13
|
|
|
13
|
-
##
|
|
14
|
+
## Quick start
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
17
|
npx @react-native-community/cli init YourAppName --template @fadyshawky/react-native-magic
|
|
17
18
|
cd YourAppName
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
Optional: set your bundle ID at creation:
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Project Structure
|
|
27
|
-
|
|
24
|
+
npx @react-native-community/cli init YourAppName --template @fadyshawky/react-native-magic --package-name com.yourcompany.yourapp
|
|
28
25
|
```
|
|
29
|
-
YourAppName/
|
|
30
|
-
├── src/
|
|
31
|
-
│ ├── components/ # Reusable UI components
|
|
32
|
-
│ ├── screens/ # Screen components
|
|
33
|
-
│ ├── navigation/ # Navigation configurations
|
|
34
|
-
│ ├── services/ # API services and other external services
|
|
35
|
-
│ ├── store/ # State management
|
|
36
|
-
│ │ ├── slices/ # Redux slices
|
|
37
|
-
│ │ └── index.ts # Store configuration
|
|
38
|
-
│ ├── theme/ # Theme configurations
|
|
39
|
-
│ ├── utils/ # Utility functions
|
|
40
|
-
│ └── types/ # TypeScript type definitions
|
|
41
|
-
├── android/
|
|
42
|
-
├── ios/
|
|
43
|
-
└── ...
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Features
|
|
47
|
-
|
|
48
|
-
### 1. Type Safety
|
|
49
|
-
|
|
50
|
-
- Full TypeScript support
|
|
51
|
-
- Pre-configured tsconfig.json
|
|
52
|
-
- Type definitions for all components
|
|
53
|
-
|
|
54
|
-
### 2. Navigation
|
|
55
|
-
|
|
56
|
-
- React Navigation v6
|
|
57
|
-
- Type-safe navigation
|
|
58
|
-
- Bottom tabs setup
|
|
59
|
-
- Stack navigation setup
|
|
60
|
-
|
|
61
|
-
Documentation: [React Navigation](https://reactnavigation.org/docs/getting-started)
|
|
62
|
-
|
|
63
|
-
### 3. State Management
|
|
64
|
-
|
|
65
|
-
- Redux Toolkit
|
|
66
|
-
- Async storage integration
|
|
67
|
-
- Predefined store setup
|
|
68
26
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
### 4. Environment Variables
|
|
72
|
-
|
|
73
|
-
- React Native Config integration
|
|
74
|
-
- Secure environment configuration
|
|
75
|
-
- Type-safe environment variables
|
|
76
|
-
|
|
77
|
-
Documentation: [React Native Config](https://github.com/luggit/react-native-config)
|
|
78
|
-
|
|
79
|
-
### 5. Styling
|
|
80
|
-
|
|
81
|
-
- React Native Paper
|
|
82
|
-
- Custom theming system
|
|
83
|
-
- Dark mode support
|
|
84
|
-
|
|
85
|
-
Documentation: [React Native Paper](https://callstack.github.io/react-native-paper/)
|
|
86
|
-
|
|
87
|
-
### 6. Testing
|
|
88
|
-
|
|
89
|
-
- Jest configuration
|
|
90
|
-
- React Native Testing Library
|
|
91
|
-
- Example tests included
|
|
92
|
-
|
|
93
|
-
Documentation:
|
|
94
|
-
|
|
95
|
-
- [Jest](https://jestjs.io/docs/getting-started)
|
|
96
|
-
- [React Native Testing Library](https://callstack.github.io/react-native-testing-library/)
|
|
97
|
-
|
|
98
|
-
## Available Scripts
|
|
27
|
+
For iOS, install pods:
|
|
99
28
|
|
|
100
29
|
```bash
|
|
101
|
-
|
|
102
|
-
npm start
|
|
103
|
-
|
|
104
|
-
# Run on iOS
|
|
105
|
-
npm run ios
|
|
106
|
-
|
|
107
|
-
# Run on Android
|
|
108
|
-
npm run android
|
|
109
|
-
|
|
110
|
-
# Run tests
|
|
111
|
-
npm test
|
|
112
|
-
|
|
113
|
-
# Lint code
|
|
114
|
-
npm run lint
|
|
115
|
-
|
|
116
|
-
# Type checking
|
|
117
|
-
npm run typescript
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Dependencies
|
|
121
|
-
|
|
122
|
-
### Production Dependencies
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"@react-navigation/bottom-tabs": "^6.x",
|
|
127
|
-
"@react-navigation/native": "^6.x",
|
|
128
|
-
"@react-navigation/native-stack": "^6.x",
|
|
129
|
-
"@reduxjs/toolkit": "^1.x",
|
|
130
|
-
"react-native-paper": "^5.x",
|
|
131
|
-
"react-native-safe-area-context": "^4.x",
|
|
132
|
-
"react-native-screens": "^3.x",
|
|
133
|
-
"@react-native-async-storage/async-storage": "^1.x",
|
|
134
|
-
"react-native-config": "^1.x"
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Development Dependencies
|
|
139
|
-
|
|
140
|
-
```json
|
|
141
|
-
{
|
|
142
|
-
"@testing-library/react-native": "^11.x",
|
|
143
|
-
"@types/jest": "^29.x",
|
|
144
|
-
"@types/react": "^18.x",
|
|
145
|
-
"typescript": "^4.x",
|
|
146
|
-
"jest": "^29.x"
|
|
147
|
-
}
|
|
30
|
+
cd ios && pod install && cd ..
|
|
148
31
|
```
|
|
149
32
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
### iOS Build Issues
|
|
153
|
-
|
|
154
|
-
1. Pod installation fails:
|
|
33
|
+
Then run:
|
|
155
34
|
|
|
156
35
|
```bash
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
pod install
|
|
36
|
+
npm start
|
|
37
|
+
npm run ios # or: npm run android
|
|
160
38
|
```
|
|
161
39
|
|
|
162
|
-
|
|
40
|
+
## First steps after creating your app
|
|
163
41
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
npm run ios
|
|
169
|
-
```
|
|
42
|
+
1. **App name & bundle ID** – Set at init (or you’ll be prompted for package name if you didn’t pass `--package-name`). See [CUSTOMIZATION.md](template/docs/CUSTOMIZATION.md#app-name-and-bundle-id).
|
|
43
|
+
2. **API** – Copy `.env.example` to `.env` and set `API_BASE_URL` (and other vars) for your backend.
|
|
44
|
+
3. **Theme** – Edit `src/core/theme/colors.ts` (and `fonts.ts`, `commonSizes.ts` if needed) for your brand.
|
|
45
|
+
4. **Config** – Optional: adjust `src/core/config/index.ts` for feature toggles or app-level constants.
|
|
170
46
|
|
|
171
|
-
|
|
47
|
+
## Documentation
|
|
172
48
|
|
|
173
|
-
|
|
49
|
+
In your generated project you’ll have:
|
|
174
50
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
cd ..
|
|
179
|
-
npm run android
|
|
180
|
-
```
|
|
51
|
+
- **[docs/ARCHITECTURE.md](template/docs/ARCHITECTURE.md)** – Layers, folder map, data flow, SOLID.
|
|
52
|
+
- **[docs/CUSTOMIZATION.md](template/docs/CUSTOMIZATION.md)** – App name, bundle ID, API, theme, adding a screen/slice/language.
|
|
53
|
+
- **[docs/BEST_PRACTICES.md](template/docs/BEST_PRACTICES.md)** – Code style, structure, testing, security, upgrades.
|
|
181
54
|
|
|
182
|
-
|
|
183
|
-
Create a `local.properties` file in the android folder with your SDK path:
|
|
55
|
+
## Project structure (in your app)
|
|
184
56
|
|
|
185
|
-
```properties
|
|
186
|
-
sdk.dir=/Users/USERNAME/Library/Android/sdk
|
|
187
57
|
```
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
```typescript
|
|
196
|
-
export const theme = {
|
|
197
|
-
colors: {
|
|
198
|
-
primary: "#YOUR_COLOR",
|
|
199
|
-
// ...
|
|
200
|
-
},
|
|
201
|
-
// ...
|
|
202
|
-
};
|
|
58
|
+
src/
|
|
59
|
+
├── common/ # Shared components, localization, helpers, validations, utils
|
|
60
|
+
├── core/ # Store (Redux), API, theme, config
|
|
61
|
+
├── navigation/ # Auth stack, main stack, tabs
|
|
62
|
+
├── screens/ # Feature screens
|
|
63
|
+
└── sheetManager/ # Action sheets
|
|
203
64
|
```
|
|
204
65
|
|
|
205
|
-
|
|
66
|
+
## Scripts
|
|
206
67
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
68
|
+
| Command | Description |
|
|
69
|
+
|---------|-------------|
|
|
70
|
+
| `npm start` | Start Metro bundler |
|
|
71
|
+
| `npm run ios` | Run on iOS |
|
|
72
|
+
| `npm run android` | Run on Android |
|
|
73
|
+
| `npm test` | Run tests |
|
|
74
|
+
| `npm run lint` | Lint code |
|
|
210
75
|
|
|
211
|
-
|
|
76
|
+
## Versioning
|
|
212
77
|
|
|
213
|
-
|
|
78
|
+
- **React Native**: ^0.84.x (current stable at release).
|
|
79
|
+
- **React**: ^19.2.x.
|
|
80
|
+
- **Node**: >= 20 (LTS).
|
|
214
81
|
|
|
215
|
-
|
|
216
|
-
API_URL=https://api.example.com
|
|
217
|
-
ENV=development
|
|
218
|
-
```
|
|
82
|
+
Dependencies use **caret (^)** so your app can get patch/minor updates independently.
|
|
219
83
|
|
|
220
|
-
|
|
84
|
+
## Common issues
|
|
221
85
|
|
|
222
|
-
|
|
223
|
-
import Config from "react-native-config";
|
|
86
|
+
**iOS – Pod install fails**
|
|
224
87
|
|
|
225
|
-
|
|
88
|
+
```bash
|
|
89
|
+
cd ios && pod deintegrate && pod install && cd ..
|
|
226
90
|
```
|
|
227
91
|
|
|
228
|
-
|
|
92
|
+
**Android – Gradle / SDK**
|
|
229
93
|
|
|
230
|
-
|
|
94
|
+
- Run `./gradlew clean` in `android/`.
|
|
95
|
+
- Ensure `android/local.properties` has `sdk.dir` set to your Android SDK path.
|
|
231
96
|
|
|
232
|
-
|
|
233
|
-
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
234
|
-
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
235
|
-
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
236
|
-
5. Open a Pull Request
|
|
97
|
+
**Upgrading React Native**
|
|
237
98
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
- [React Native Documentation](https://reactnative.dev/docs/getting-started)
|
|
241
|
-
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
|
|
242
|
-
- [React Navigation Documentation](https://reactnavigation.org/docs/getting-started)
|
|
243
|
-
- [Redux Toolkit Documentation](https://redux-toolkit.js.org/introduction/getting-started)
|
|
244
|
-
- [React Native Paper Documentation](https://callstack.github.io/react-native-paper/)
|
|
99
|
+
Use [React Native Upgrade Helper](https://react-native-community.github.io/upgrade-helper/) (select current → target version) and apply the suggested changes.
|
|
245
100
|
|
|
246
101
|
## License
|
|
247
102
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
## Support
|
|
251
|
-
|
|
252
|
-
If you find this template helpful, consider buying me a beer! 🍺
|
|
253
|
-
|
|
254
|
-
<a href="https://www.buymeacoffee.com/fadytshawke"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a beer&emoji=🍺&slug=fadytshawke&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" /></a>
|
|
103
|
+
MIT – see [LICENSE.md](LICENSE.md).
|
|
255
104
|
|
|
256
105
|
## Author
|
|
257
106
|
|
|
258
|
-
Fady Shawky
|
|
259
|
-
|
|
260
|
-
- GitHub: [@fadyshawky](https://github.com/fadyshawky)
|
|
261
|
-
|
|
262
|
-
## Acknowledgments
|
|
263
|
-
|
|
264
|
-
- React Native Team
|
|
265
|
-
- React Navigation Team
|
|
266
|
-
- All contributors who help maintain this template
|
|
107
|
+
Fady Shawky – [GitHub](https://github.com/fadyshawky)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fadyshawky/react-native-magic",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "Plug-and-play React Native template: TypeScript, Redux, React Navigation, scalable architecture. Init and start developing without hassle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native-magic",
|
|
7
7
|
"react-native",
|
|
@@ -19,97 +19,16 @@
|
|
|
19
19
|
"author": "Fady Shawky",
|
|
20
20
|
"type": "commonjs",
|
|
21
21
|
"main": "index.js",
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"@fontsource/poppins": "^5.1.0",
|
|
24
|
-
"@react-native-async-storage/async-storage": "^2.1.0",
|
|
25
|
-
"@react-native-camera-roll/camera-roll": "^7.9.0",
|
|
26
|
-
"@react-native-community/datetimepicker": "^8.2.0",
|
|
27
|
-
"@react-native-community/image-editor": "^4.2.1",
|
|
28
|
-
"@react-native-community/netinfo": "^11.4.1",
|
|
29
|
-
"@react-native-community/slider": "^4.5.5",
|
|
30
|
-
"@react-navigation/bottom-tabs": "^7.2.0",
|
|
31
|
-
"@react-navigation/drawer": "^7.1.1",
|
|
32
|
-
"@react-navigation/material-top-tabs": "^7.1.0",
|
|
33
|
-
"@react-navigation/native": "^7.0.14",
|
|
34
|
-
"@react-navigation/native-stack": "^7.2.0",
|
|
35
|
-
"@reduxjs/toolkit": "^2.5.0",
|
|
36
|
-
"@shopify/flash-list": "^1.7.2",
|
|
37
|
-
"@types/intl": "^1.2.2",
|
|
38
|
-
"@types/jest": "^29.5.14",
|
|
39
|
-
"@types/lodash": "^4.17.13",
|
|
40
|
-
"@types/react-native-vector-icons": "^6.4.18",
|
|
41
|
-
"@types/react-redux": "^7.1.34",
|
|
42
|
-
"axios": "^1.7.9",
|
|
43
|
-
"babel-plugin-transform-remove-console": "^6.9.4",
|
|
44
|
-
"dayjs": "^1.11.13",
|
|
45
|
-
"detox": "^20.28.0",
|
|
46
|
-
"intl": "^1.2.5",
|
|
47
|
-
"lodash": "^4.17.21",
|
|
48
|
-
"mime": "^4.0.6",
|
|
49
|
-
"patch-package": "^8.0.0",
|
|
50
|
-
"react": "^18.3.1",
|
|
51
|
-
"react-native": "^0.76.5",
|
|
52
|
-
"react-native-actions-sheet": "^0.9.7",
|
|
53
|
-
"react-native-animated-pagination-dots": "^0.1.73",
|
|
54
|
-
"react-native-calendars": "^1.1307.0",
|
|
55
|
-
"react-native-config": "^1.5.3",
|
|
56
|
-
"react-native-device-info": "^14.0.2",
|
|
57
|
-
"react-native-dropdown-select-list": "^2.0.5",
|
|
58
|
-
"react-native-gesture-handler": "^2.21.2",
|
|
59
|
-
"react-native-image-crop-picker": "^0.41.6",
|
|
60
|
-
"react-native-image-resource-generator": "^1.0.2",
|
|
61
|
-
"react-native-in-app-review": "^4.3.3",
|
|
62
|
-
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
|
63
|
-
"react-native-localization": "^2.3.2",
|
|
64
|
-
"react-native-mask-input": "^1.2.3",
|
|
65
|
-
"react-native-orientation-locker": "^1.7.0",
|
|
66
|
-
"react-native-pager-view": "^6.6.1",
|
|
67
|
-
"react-native-permissions": "^5.2.1",
|
|
68
|
-
"react-native-safe-area-context": "^5.0.0",
|
|
69
|
-
"react-native-screens": "^4.4.0",
|
|
70
|
-
"react-native-sfsymbols": "^1.2.2",
|
|
71
|
-
"react-native-share": "^12.0.3",
|
|
72
|
-
"react-native-snackbar": "^2.8.0",
|
|
73
|
-
"react-native-svg": "^15.10.1",
|
|
74
|
-
"react-native-tab-view": "^4.0.5",
|
|
75
|
-
"react-native-vector-icons": "^10.2.0",
|
|
76
|
-
"react-native-vision-camera": "^4.6.3",
|
|
77
|
-
"react-native-webview": "^13.12.5",
|
|
78
|
-
"react-redux": "^9.2.0",
|
|
79
|
-
"redux": "^5.0.1",
|
|
80
|
-
"redux-persist": "^6.0.0",
|
|
81
|
-
"redux-persist-transform-filter": "^0.0.22"
|
|
82
|
-
},
|
|
83
|
-
"devDependencies": {
|
|
84
|
-
"@babel/core": "^7.25.2",
|
|
85
|
-
"@babel/preset-env": "^7.26.0",
|
|
86
|
-
"@babel/runtime": "^7.26.0",
|
|
87
|
-
"@jest/globals": "^29.7.0",
|
|
88
|
-
"@react-native-community/cli": "15.0.1",
|
|
89
|
-
"@react-native-community/cli-platform-android": "15.0.1",
|
|
90
|
-
"@react-native-community/cli-platform-ios": "15.0.1",
|
|
91
|
-
"@react-native/babel-preset": "^0.76.5",
|
|
92
|
-
"@react-native/eslint-config": "^0.76.5",
|
|
93
|
-
"@react-native/metro-config": "^0.76.5",
|
|
94
|
-
"@react-native/typescript-config": "^0.76.5",
|
|
95
|
-
"@types/react": "^18.3.18",
|
|
96
|
-
"@types/react-test-renderer": "^18.3.1",
|
|
97
|
-
"babel-jest": "^29.7.0",
|
|
98
|
-
"eslint": "^8.57.1",
|
|
99
|
-
"eslint-plugin-import": "^2.31.0",
|
|
100
|
-
"eslint-plugin-jest": "^28.10.0",
|
|
101
|
-
"eslint-plugin-react": "^7.37.2",
|
|
102
|
-
"eslint-plugin-react-hooks": "^5.1.0",
|
|
103
|
-
"eslint-plugin-react-native": "^4.1.0",
|
|
104
|
-
"eslint-plugin-unused-imports": "^4.1.4",
|
|
105
|
-
"jest": "^29.7.0",
|
|
106
|
-
"prettier": "^2.8.8",
|
|
107
|
-
"react-test-renderer": "^18.3.1",
|
|
108
|
-
"typescript": "^5.0.4"
|
|
109
|
-
},
|
|
110
22
|
"scripts": {
|
|
111
23
|
"test": "exit 0"
|
|
112
24
|
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "^19.2.0",
|
|
27
|
+
"react-native": "^0.84.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
113
32
|
"repository": {
|
|
114
33
|
"type": "git",
|
|
115
34
|
"url": "https://github.com/fadyshawky/ReactNativeMagic.git"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-init script: if the user did not pass --package-name at init,
|
|
5
|
+
* prompt for a package name and apply it to .env and iOS project.
|
|
6
|
+
* Runs with cwd = the new project directory.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
const PACKAGE_NAME_REGEX = /^([a-zA-Z][a-zA-Z0-9_]*\.)+[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
14
|
+
|
|
15
|
+
function prompt(question) {
|
|
16
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
rl.question(question, (answer) => {
|
|
19
|
+
rl.close();
|
|
20
|
+
resolve((answer || '').trim());
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function validatePackageName(packageName) {
|
|
26
|
+
const parts = packageName.split('.');
|
|
27
|
+
if (parts.length < 2) {
|
|
28
|
+
return 'Package name must have at least two segments (e.g. com.app)';
|
|
29
|
+
}
|
|
30
|
+
if (!PACKAGE_NAME_REGEX.test(packageName)) {
|
|
31
|
+
return 'Package name can only contain letters, numbers, underscores and dots';
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ensureEnvHasPackageIds(envPath, packageName) {
|
|
37
|
+
const fullPath = path.join(process.cwd(), envPath);
|
|
38
|
+
let content = '';
|
|
39
|
+
if (fs.existsSync(fullPath)) {
|
|
40
|
+
content = fs.readFileSync(fullPath, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
const lines = content.split('\n');
|
|
43
|
+
const updated = lines.map((line) => {
|
|
44
|
+
const eq = line.indexOf('=');
|
|
45
|
+
if (eq === -1) return line;
|
|
46
|
+
const key = line.slice(0, eq).trim();
|
|
47
|
+
if (key === 'APP_ID' || key === 'ANDROID_APP_ID') {
|
|
48
|
+
return `${key}=${packageName}`;
|
|
49
|
+
}
|
|
50
|
+
return line;
|
|
51
|
+
});
|
|
52
|
+
const hasAppId = lines.some((l) => /^APP_ID=/.test(l) || /^ANDROID_APP_ID=/.test(l));
|
|
53
|
+
if (!hasAppId) {
|
|
54
|
+
if (updated.length && updated[updated.length - 1] !== '') {
|
|
55
|
+
updated.push('');
|
|
56
|
+
}
|
|
57
|
+
updated.push(`APP_ID=${packageName}`);
|
|
58
|
+
updated.push(`ANDROID_APP_ID=${packageName}`);
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(fullPath, updated.join('\n'), 'utf8');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getCurrentIosBundleId() {
|
|
64
|
+
const iosDir = path.join(process.cwd(), 'ios');
|
|
65
|
+
if (!fs.existsSync(iosDir)) return null;
|
|
66
|
+
const entries = fs.readdirSync(iosDir, { withFileTypes: true });
|
|
67
|
+
const pbxprojPaths = entries
|
|
68
|
+
.filter((d) => d.isDirectory() && d.name.endsWith('.xcodeproj'))
|
|
69
|
+
.map((d) => path.join(iosDir, d.name, 'project.pbxproj'))
|
|
70
|
+
.filter((p) => fs.existsSync(p));
|
|
71
|
+
const customPattern = /PRODUCT_BUNDLE_IDENTIFIER = "([^"]+)"/;
|
|
72
|
+
for (const pbx of pbxprojPaths) {
|
|
73
|
+
const content = fs.readFileSync(pbx, 'utf8');
|
|
74
|
+
const customMatch = content.match(customPattern);
|
|
75
|
+
if (customMatch) {
|
|
76
|
+
const id = customMatch[1];
|
|
77
|
+
if (!id.includes('$(')) return id;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function updateIosBundleId(packageName) {
|
|
84
|
+
const iosDir = path.join(process.cwd(), 'ios');
|
|
85
|
+
if (!fs.existsSync(iosDir)) return;
|
|
86
|
+
|
|
87
|
+
const entries = fs.readdirSync(iosDir, { withFileTypes: true });
|
|
88
|
+
const pbxprojPaths = entries
|
|
89
|
+
.filter((d) => d.isDirectory() && d.name.endsWith('.xcodeproj'))
|
|
90
|
+
.map((d) => path.join(iosDir, d.name, 'project.pbxproj'))
|
|
91
|
+
.filter((p) => fs.existsSync(p));
|
|
92
|
+
|
|
93
|
+
for (const pbx of pbxprojPaths) {
|
|
94
|
+
let content = fs.readFileSync(pbx, 'utf8');
|
|
95
|
+
const pattern = /PRODUCT_BUNDLE_IDENTIFIER = "org\.reactjs\.native\.example\.\$\(PRODUCT_NAME:rfc1034identifier\)"/g;
|
|
96
|
+
if (pattern.test(content)) {
|
|
97
|
+
content = content.replace(
|
|
98
|
+
/PRODUCT_BUNDLE_IDENTIFIER = "org\.reactjs\.native\.example\.\$\(PRODUCT_NAME:rfc1034identifier\)"/g,
|
|
99
|
+
`PRODUCT_BUNDLE_IDENTIFIER = "${packageName}"`
|
|
100
|
+
);
|
|
101
|
+
fs.writeFileSync(pbx, content, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function main() {
|
|
107
|
+
const existingBundleId = getCurrentIosBundleId();
|
|
108
|
+
if (existingBundleId) {
|
|
109
|
+
// User passed --package-name; sync to .env for Android
|
|
110
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
111
|
+
const examplePath = path.join(process.cwd(), '.env.example');
|
|
112
|
+
if (!fs.existsSync(envPath) && fs.existsSync(examplePath)) {
|
|
113
|
+
fs.copyFileSync(examplePath, envPath);
|
|
114
|
+
}
|
|
115
|
+
ensureEnvHasPackageIds('.env', existingBundleId);
|
|
116
|
+
for (const f of ['.env.development', '.env.staging', '.env.production']) {
|
|
117
|
+
if (fs.existsSync(path.join(process.cwd(), f))) {
|
|
118
|
+
ensureEnvHasPackageIds(f, existingBundleId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log(`\nSynced package name ${existingBundleId} to .env (Android).\n`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('\nYou did not pass --package-name. Set a bundle ID (Android applicationId / iOS PRODUCT_BUNDLE_IDENTIFIER) now.\n');
|
|
126
|
+
|
|
127
|
+
const answer = await prompt('Package name (e.g. com.company.app), or press Enter to skip: ');
|
|
128
|
+
|
|
129
|
+
if (!answer) {
|
|
130
|
+
console.log('\nSkipped. You can set APP_ID and ANDROID_APP_ID in .env later, and set the iOS bundle ID in Xcode or use react-native-rename.\n');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const err = validatePackageName(answer);
|
|
135
|
+
if (err) {
|
|
136
|
+
console.error('\nInvalid package name:', err);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const packageName = answer;
|
|
141
|
+
|
|
142
|
+
// Ensure .env exists
|
|
143
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
144
|
+
const examplePath = path.join(process.cwd(), '.env.example');
|
|
145
|
+
if (!fs.existsSync(envPath) && fs.existsSync(examplePath)) {
|
|
146
|
+
fs.copyFileSync(examplePath, envPath);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
ensureEnvHasPackageIds('.env', packageName);
|
|
150
|
+
const envFiles = ['.env.development', '.env.staging', '.env.production'];
|
|
151
|
+
for (const f of envFiles) {
|
|
152
|
+
if (fs.existsSync(path.join(process.cwd(), f))) {
|
|
153
|
+
ensureEnvHasPackageIds(f, packageName);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
updateIosBundleId(packageName);
|
|
158
|
+
|
|
159
|
+
console.log(`\nSet package name to ${packageName} in .env and iOS project.\n`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
main().catch((e) => {
|
|
163
|
+
console.error(e);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Copy this file to .env and set your values.
|
|
2
|
+
# Do not commit .env (it is in .gitignore).
|
|
3
|
+
|
|
4
|
+
ENV=development
|
|
5
|
+
API_BASE_URL=https://api.example.com
|
|
6
|
+
|
|
7
|
+
# Bundle ID (Android applicationId / iOS). Set at init with --package-name or when prompted.
|
|
8
|
+
APP_ID=com.yourcompany.yourapp
|
|
9
|
+
ANDROID_APP_ID=com.yourcompany.yourapp
|
|
10
|
+
|
|
11
|
+
ANDROID_VERSION_CODE=1
|
|
12
|
+
ANDROID_VERSION_NAME=1.0.0
|