create-gufran-expo-app 2.0.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/LICENSE +21 -0
- package/README.md +413 -0
- package/bin/cli.js +36 -0
- package/index.js +4 -0
- package/lib/createApp.js +324 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Gufran Gaury
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# š @gufran/expo-boilerplate
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**Ultimate Expo React Native Boilerplate** - A professionally configured Expo environment designed for production-ready applications. Skip the tedious setup and dive straight into building amazing features!
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⨠Why Choose This Boilerplate?
|
|
12
|
+
|
|
13
|
+
This boilerplate provides a complete, production-ready setup with enterprise-grade features:
|
|
14
|
+
|
|
15
|
+
- š„ **Firebase Integration** - Authentication, Push Notifications, Analytics
|
|
16
|
+
- šØ **Professional UI** - Pre-built components and screens
|
|
17
|
+
- šļø **Clean Architecture** - Organized folder structure that scales
|
|
18
|
+
- āļø **Cloud Storage** - Azure Blob Storage for file uploads
|
|
19
|
+
- š **Authentication Flow** - Complete auth screens and navigation
|
|
20
|
+
- š± **Native Features** - Camera, Image Picker, Permissions
|
|
21
|
+
- š **Push Notifications** - Notifee and Firebase Messaging
|
|
22
|
+
- š **API Management** - TanStack Query configured
|
|
23
|
+
- š **State Management** - Zustand for efficient state handling
|
|
24
|
+
- šÆ **TypeScript** - Full type safety
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## š Quick Start
|
|
29
|
+
|
|
30
|
+
Create a new project with a single command:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @gufran/expo-boilerplate my-awesome-app
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or use npm directly:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm create @gufran/expo-boilerplate my-awesome-app
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With yarn:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
yarn create @gufran/expo-boilerplate my-awesome-app
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Interactive Mode
|
|
49
|
+
|
|
50
|
+
Simply run without a project name to enter interactive mode (will prompt for project name and bundle ID):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @gufran/expo-boilerplate
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## š Command Options
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx @gufran/expo-boilerplate [project-name] [options]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Options:
|
|
65
|
+
|
|
66
|
+
| Option | Description |
|
|
67
|
+
|--------|-------------|
|
|
68
|
+
| `-b, --bundle-id <bundleId>` | Bundle identifier (e.g., com.myapp) |
|
|
69
|
+
| `--skip-install` | Skip automatic dependency installation |
|
|
70
|
+
| `--skip-git` | Skip git initialization |
|
|
71
|
+
| `--npm` | Use npm instead of yarn |
|
|
72
|
+
| `-h, --help` | Display help information |
|
|
73
|
+
| `-V, --version` | Display version number |
|
|
74
|
+
|
|
75
|
+
### Examples:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Create project with custom bundle ID
|
|
79
|
+
npx @gufran/expo-boilerplate my-app -b com.myapp
|
|
80
|
+
|
|
81
|
+
# Create project with npm
|
|
82
|
+
npx @gufran/expo-boilerplate my-app --npm
|
|
83
|
+
|
|
84
|
+
# Skip installation and git init
|
|
85
|
+
npx @gufran/expo-boilerplate my-app --skip-install --skip-git
|
|
86
|
+
|
|
87
|
+
# Full command with all options
|
|
88
|
+
npx @gufran/expo-boilerplate my-app -b com.myapp --npm
|
|
89
|
+
|
|
90
|
+
# Interactive mode (prompts for name and bundle ID)
|
|
91
|
+
npx @gufran/expo-boilerplate
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## š¦ What's Included?
|
|
97
|
+
|
|
98
|
+
### šÆ Core Technologies
|
|
99
|
+
|
|
100
|
+
- **Expo SDK 54** - Latest Expo framework
|
|
101
|
+
- **React Native 0.81** - Latest stable RN version
|
|
102
|
+
- **TypeScript** - Full type safety
|
|
103
|
+
- **React Navigation 7** - Native stack navigation
|
|
104
|
+
|
|
105
|
+
### š„ Firebase Services
|
|
106
|
+
|
|
107
|
+
- Firebase App & Messaging
|
|
108
|
+
- Push Notifications with Notifee
|
|
109
|
+
- Cloud Messaging
|
|
110
|
+
- Analytics ready
|
|
111
|
+
|
|
112
|
+
### š¾ State & Data Management
|
|
113
|
+
|
|
114
|
+
- **Zustand** - Lightweight state management
|
|
115
|
+
- **TanStack Query** - Server state management
|
|
116
|
+
- **MMKV** - Fast local storage
|
|
117
|
+
- **React Native Gesture Handler** - Smooth gestures
|
|
118
|
+
|
|
119
|
+
### šØ UI & Media
|
|
120
|
+
|
|
121
|
+
- Image Picker & Camera
|
|
122
|
+
- Image Zoom capabilities
|
|
123
|
+
- SVG support
|
|
124
|
+
- Reanimated animations
|
|
125
|
+
- Keyboard-aware scrolling
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
### āļø Cloud Services
|
|
129
|
+
|
|
130
|
+
- Azure Blob Storage integration
|
|
131
|
+
- Background file upload
|
|
132
|
+
- Progress tracking
|
|
133
|
+
|
|
134
|
+
### š Authentication
|
|
135
|
+
|
|
136
|
+
- Complete auth flow
|
|
137
|
+
- Context-based auth management
|
|
138
|
+
- Secure storage
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## š Project Structure
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
my-app/
|
|
146
|
+
āāā src/
|
|
147
|
+
ā āāā assets/ # Images, fonts, icons
|
|
148
|
+
ā āāā components/ # Reusable UI components
|
|
149
|
+
ā ā āāā common/ # Common components
|
|
150
|
+
ā āāā config/ # App configuration
|
|
151
|
+
ā āāā constants/ # Constants, themes, strings
|
|
152
|
+
ā āāā contexts/ # React contexts (Auth, etc.)
|
|
153
|
+
ā āāā hooks/ # Custom React hooks
|
|
154
|
+
ā āāā navigation/ # Navigation setup
|
|
155
|
+
ā ā āāā AuthStack.tsx
|
|
156
|
+
ā ā āāā MainStack.tsx
|
|
157
|
+
ā ā āāā RootNavigator.tsx
|
|
158
|
+
ā āāā screens/ # App screens
|
|
159
|
+
ā ā āāā auth/ # Authentication screens
|
|
160
|
+
ā ā āāā chat/ # Chat features
|
|
161
|
+
ā ā āāā clubs/ # Club management
|
|
162
|
+
ā ā āāā events/ # Events
|
|
163
|
+
ā ā āāā ...
|
|
164
|
+
ā āāā services/ # API services
|
|
165
|
+
ā āāā stores/ # Zustand stores
|
|
166
|
+
ā āāā types/ # TypeScript types
|
|
167
|
+
ā āāā utils/ # Utility functions
|
|
168
|
+
āāā android/ # Android native code
|
|
169
|
+
āāā ios/ # iOS native code
|
|
170
|
+
āāā FirebaseFiles/ # Firebase configuration files
|
|
171
|
+
āāā assets/ # Root assets
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## š ļø Setup Steps
|
|
177
|
+
|
|
178
|
+
### 1. Create Your Project
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Interactive mode (recommended)
|
|
182
|
+
npx @gufran/expo-boilerplate
|
|
183
|
+
|
|
184
|
+
# Or specify project name and bundle ID
|
|
185
|
+
npx @gufran/expo-boilerplate my-app -b com.myapp
|
|
186
|
+
|
|
187
|
+
cd my-app
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The CLI will automatically:
|
|
191
|
+
- Create project directory
|
|
192
|
+
- Copy all template files (no git clone needed!)
|
|
193
|
+
- Update `package.json` with your project name
|
|
194
|
+
- Update `app.json` with your project name and bundle IDs
|
|
195
|
+
- Install dependencies (unless --skip-install)
|
|
196
|
+
- Initialize git repository (unless --skip-git)
|
|
197
|
+
|
|
198
|
+
### 2. Configure Firebase
|
|
199
|
+
|
|
200
|
+
Add your Firebase configuration files:
|
|
201
|
+
|
|
202
|
+
**For Android:**
|
|
203
|
+
```bash
|
|
204
|
+
# Add google-services.json to:
|
|
205
|
+
android/app/google-services.json
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**For iOS:**
|
|
209
|
+
```bash
|
|
210
|
+
# Add GoogleService-Info.plist to:
|
|
211
|
+
ios/ClubYakka/GoogleService-Info.plist
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 3. Install iOS Dependencies
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
cd ios && pod install && cd ..
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 4. Verify Configuration
|
|
221
|
+
|
|
222
|
+
The CLI automatically updates `app.json` with your bundle IDs, but you can verify:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"expo": {
|
|
227
|
+
"name": "my-app",
|
|
228
|
+
"slug": "my-app",
|
|
229
|
+
"version": "1.0.0",
|
|
230
|
+
"ios": {
|
|
231
|
+
"bundleIdentifier": "com.myapp"
|
|
232
|
+
},
|
|
233
|
+
"android": {
|
|
234
|
+
"package": "com.myapp"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 5. Start Development
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Start Metro bundler
|
|
244
|
+
npm start
|
|
245
|
+
|
|
246
|
+
# Run on Android
|
|
247
|
+
npm run android
|
|
248
|
+
|
|
249
|
+
# Run on iOS
|
|
250
|
+
npm run ios
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## š§ Available Scripts
|
|
256
|
+
|
|
257
|
+
| Script | Description |
|
|
258
|
+
|--------|-------------|
|
|
259
|
+
| `npm start` | Start Metro bundler |
|
|
260
|
+
| `npm run android` | Run on Android device/emulator |
|
|
261
|
+
| `npm run ios` | Run on iOS device/simulator |
|
|
262
|
+
| `npm run web` | Run on web browser |
|
|
263
|
+
| `npm run pod` | Install iOS pods |
|
|
264
|
+
| `npm run clear` | Clear Metro cache |
|
|
265
|
+
| `npm run clean` | Clean Android build |
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## šØ Key Features Explained
|
|
270
|
+
|
|
271
|
+
### Authentication Flow
|
|
272
|
+
|
|
273
|
+
Complete authentication system with:
|
|
274
|
+
- Login/Register screens
|
|
275
|
+
- OTP verification
|
|
276
|
+
- Password reset
|
|
277
|
+
- Protected routes
|
|
278
|
+
- Auth context management
|
|
279
|
+
|
|
280
|
+
### Navigation Structure
|
|
281
|
+
|
|
282
|
+
Three-level navigation:
|
|
283
|
+
1. **RootNavigator** - Entry point
|
|
284
|
+
2. **AuthStack** - Unauthenticated screens
|
|
285
|
+
3. **MainStack** - Authenticated screens
|
|
286
|
+
|
|
287
|
+
### API Integration
|
|
288
|
+
|
|
289
|
+
Pre-configured with:
|
|
290
|
+
- Axios for HTTP requests
|
|
291
|
+
- TanStack Query for caching
|
|
292
|
+
- Error handling
|
|
293
|
+
- Request/response interceptors
|
|
294
|
+
|
|
295
|
+
### File Upload
|
|
296
|
+
|
|
297
|
+
Background upload with:
|
|
298
|
+
- Azure Blob Storage integration
|
|
299
|
+
- Progress tracking
|
|
300
|
+
- Multiple file support
|
|
301
|
+
- Error handling
|
|
302
|
+
|
|
303
|
+
### Push Notifications
|
|
304
|
+
|
|
305
|
+
Complete notification system:
|
|
306
|
+
- Firebase Cloud Messaging
|
|
307
|
+
- Notifee for local notifications
|
|
308
|
+
- Permission handling
|
|
309
|
+
- Deep linking support
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## š Environment Setup
|
|
314
|
+
|
|
315
|
+
Create `.env` file in root:
|
|
316
|
+
|
|
317
|
+
```env
|
|
318
|
+
API_BASE_URL=https://api.yourapp.com
|
|
319
|
+
AZURE_STORAGE_URL=your-azure-url
|
|
320
|
+
SQUARE_APPLICATION_ID=your-square-id
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## š± Platform-Specific Notes
|
|
326
|
+
|
|
327
|
+
### iOS
|
|
328
|
+
|
|
329
|
+
- Requires Xcode 14+
|
|
330
|
+
- Run `pod install` after installing dependencies
|
|
331
|
+
- Configure signing in Xcode
|
|
332
|
+
- Update `Info.plist` with required permissions
|
|
333
|
+
|
|
334
|
+
### Android
|
|
335
|
+
|
|
336
|
+
- Requires Android Studio
|
|
337
|
+
- Update `google-services.json`
|
|
338
|
+
- Configure signing in `android/app/build.gradle`
|
|
339
|
+
- Set up keystore for release builds
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## š§Ŗ Testing
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Run tests (when configured)
|
|
347
|
+
npm test
|
|
348
|
+
|
|
349
|
+
# Type checking
|
|
350
|
+
npx tsc --noEmit
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## š Documentation
|
|
356
|
+
|
|
357
|
+
For detailed documentation on specific features:
|
|
358
|
+
|
|
359
|
+
- [Firebase Setup](./docs/firebase.md)
|
|
360
|
+
- [Azure Storage](./docs/azure-storage.md)
|
|
361
|
+
- [Navigation Guide](./docs/navigation.md)
|
|
362
|
+
- [API Services](./docs/api-services.md)
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## š¤ Contributing
|
|
367
|
+
|
|
368
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
369
|
+
|
|
370
|
+
1. Fork the repository
|
|
371
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
372
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
373
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
374
|
+
5. Open a Pull Request
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## š Issues
|
|
379
|
+
|
|
380
|
+
Found a bug? Please [open an issue](https://github.com/GufranGaury1887/Boiler_Plat_Expo_-Gufran/issues) with a detailed description.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## š License
|
|
385
|
+
|
|
386
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## š¤ Author
|
|
391
|
+
|
|
392
|
+
**Gufran Gaury**
|
|
393
|
+
|
|
394
|
+
- GitHub: [@GufranGaury1887](https://github.com/GufranGaury1887)
|
|
395
|
+
- Repository: [Boiler_Plat_Expo_-Gufran](https://github.com/GufranGaury1887/Boiler_Plat_Expo_-Gufran)
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## š Show Your Support
|
|
400
|
+
|
|
401
|
+
Give a āļø if this project helped you!
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## š® Support
|
|
406
|
+
|
|
407
|
+
For support, email your-email@example.com or open an issue on GitHub.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
**Built with ā¤ļø by Gufran Gaury**
|
|
412
|
+
|
|
413
|
+
*Happy Coding! š*
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { createExpoApp } = require('../lib/createApp');
|
|
6
|
+
const packageJson = require('../package.json');
|
|
7
|
+
|
|
8
|
+
console.log(chalk.cyan.bold(`
|
|
9
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
10
|
+
ā ā
|
|
11
|
+
ā š Gufran's Expo Boilerplate Generator š ā
|
|
12
|
+
ā ā
|
|
13
|
+
ā Create production-ready Expo apps in seconds! ā
|
|
14
|
+
ā ā
|
|
15
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
16
|
+
`));
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.version(packageJson.version)
|
|
20
|
+
.description('Create a new Expo app using Gufran\'s professional boilerplate')
|
|
21
|
+
.argument('[project-name]', 'Name of your new project')
|
|
22
|
+
.option('-b, --bundle-id <bundleId>', 'Bundle identifier (e.g., com.company.appname)')
|
|
23
|
+
.option('-t, --template <template>', 'Template variant to use', 'default')
|
|
24
|
+
.option('--skip-install', 'Skip dependency installation')
|
|
25
|
+
.option('--skip-git', 'Skip git initialization')
|
|
26
|
+
.option('--npm', 'Use npm instead of yarn')
|
|
27
|
+
.action(async (projectName, options) => {
|
|
28
|
+
try {
|
|
29
|
+
await createExpoApp(projectName, options);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(chalk.red('\nā Error creating project:'), error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program.parse(process.argv);
|
package/index.js
ADDED
package/lib/createApp.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
const prompts = require('prompts');
|
|
7
|
+
const validateProjectName = require('validate-npm-package-name');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execute command with proper error handling
|
|
11
|
+
*/
|
|
12
|
+
function executeCommand(command, options = {}) {
|
|
13
|
+
try {
|
|
14
|
+
execSync(command, {
|
|
15
|
+
stdio: options.silent ? 'pipe' : 'inherit',
|
|
16
|
+
...options
|
|
17
|
+
});
|
|
18
|
+
return true;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (!options.ignoreError) {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if command exists
|
|
29
|
+
*/
|
|
30
|
+
function commandExists(command) {
|
|
31
|
+
try {
|
|
32
|
+
execSync(`command -v ${command}`, { stdio: 'pipe' });
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate project name
|
|
41
|
+
*/
|
|
42
|
+
function validateName(name) {
|
|
43
|
+
const validation = validateProjectName(name);
|
|
44
|
+
if (!validation.validForNewPackages) {
|
|
45
|
+
const errors = [
|
|
46
|
+
...(validation.errors || []),
|
|
47
|
+
...(validation.warnings || [])
|
|
48
|
+
];
|
|
49
|
+
throw new Error(`Invalid project name: ${errors.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generate bundle identifier
|
|
55
|
+
*/
|
|
56
|
+
function generateBundleId(projectName, bundleIdPrefix) {
|
|
57
|
+
const cleanName = projectName.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
58
|
+
return `${bundleIdPrefix}.${cleanName}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Copy directory recursively
|
|
63
|
+
*/
|
|
64
|
+
function copyDirectory(src, dest, excludeDirs = []) {
|
|
65
|
+
if (!fs.existsSync(dest)) {
|
|
66
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
70
|
+
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
const srcPath = path.join(src, entry.name);
|
|
73
|
+
const destPath = path.join(dest, entry.name);
|
|
74
|
+
|
|
75
|
+
// Skip excluded directories
|
|
76
|
+
if (excludeDirs.includes(entry.name)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (entry.isDirectory()) {
|
|
81
|
+
copyDirectory(srcPath, destPath, excludeDirs);
|
|
82
|
+
} else {
|
|
83
|
+
fs.copyFileSync(srcPath, destPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Copy template files and update configurations
|
|
90
|
+
*/
|
|
91
|
+
function copyTemplateFiles(templatePath, projectPath, appName, bundleId) {
|
|
92
|
+
// Directories to exclude from copying
|
|
93
|
+
const excludeDirs = [
|
|
94
|
+
'node_modules',
|
|
95
|
+
'.git',
|
|
96
|
+
'cli-package',
|
|
97
|
+
'build',
|
|
98
|
+
'ios/build',
|
|
99
|
+
'android/build',
|
|
100
|
+
'android/.gradle',
|
|
101
|
+
'.expo',
|
|
102
|
+
'dist',
|
|
103
|
+
'Pods',
|
|
104
|
+
'ios/Pods',
|
|
105
|
+
'.idea',
|
|
106
|
+
'ios/ClubYakka.xcworkspace/xcuserdata',
|
|
107
|
+
'ios/ClubYakka.xcodeproj/xcuserdata'
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
// Copy all files except excluded directories
|
|
111
|
+
copyDirectory(templatePath, projectPath, excludeDirs);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Main function to create the Expo app
|
|
116
|
+
*/
|
|
117
|
+
async function createExpoApp(projectName, options) {
|
|
118
|
+
let appName = projectName;
|
|
119
|
+
let bundleId = options.bundleId;
|
|
120
|
+
|
|
121
|
+
// Prompt for project name and bundle ID if not provided
|
|
122
|
+
if (!appName || !bundleId) {
|
|
123
|
+
const responses = await prompts([
|
|
124
|
+
{
|
|
125
|
+
type: 'text',
|
|
126
|
+
name: 'projectName',
|
|
127
|
+
message: 'What is your project name?',
|
|
128
|
+
initial: appName || 'my-expo-app',
|
|
129
|
+
validate: (value) => {
|
|
130
|
+
if (!value) return 'Project name is required';
|
|
131
|
+
const validation = validateProjectName(value);
|
|
132
|
+
if (!validation.validForNewPackages) {
|
|
133
|
+
const errors = [
|
|
134
|
+
...(validation.errors || []),
|
|
135
|
+
...(validation.warnings || [])
|
|
136
|
+
];
|
|
137
|
+
return errors.join(', ');
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'text',
|
|
144
|
+
name: 'bundleId',
|
|
145
|
+
message: 'What is your bundle identifier? (e.g., com.appname)',
|
|
146
|
+
initial: (prev) => bundleId || generateBundleId(prev, 'com'),
|
|
147
|
+
validate: (value) => {
|
|
148
|
+
if (!value) return 'Bundle ID is required';
|
|
149
|
+
if (!/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/i.test(value)) {
|
|
150
|
+
return 'Invalid bundle ID format. Use format: com.company.appname';
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
if (!responses.projectName || !responses.bundleId) {
|
|
158
|
+
console.log(chalk.red('\nā Project creation cancelled'));
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
appName = responses.projectName;
|
|
163
|
+
bundleId = responses.bundleId;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Validate project name
|
|
167
|
+
validateName(appName);
|
|
168
|
+
|
|
169
|
+
const projectPath = path.resolve(process.cwd(), appName);
|
|
170
|
+
|
|
171
|
+
// Check if directory already exists
|
|
172
|
+
if (fs.existsSync(projectPath)) {
|
|
173
|
+
console.log(chalk.red(`\nā Directory "${appName}" already exists!`));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(chalk.green(`\n⨠Creating a new Expo app: ${chalk.bold(appName)}`));
|
|
178
|
+
console.log(chalk.cyan(`š¦ Bundle ID: ${chalk.bold(bundleId)}\n`));
|
|
179
|
+
|
|
180
|
+
// Check for Node.js
|
|
181
|
+
let spinner = ora('Checking prerequisites...').start();
|
|
182
|
+
if (!commandExists('node')) {
|
|
183
|
+
spinner.fail('Node.js is not installed. Please install Node.js and try again.');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
spinner.succeed('Prerequisites check passed');
|
|
187
|
+
|
|
188
|
+
// Create project directory
|
|
189
|
+
spinner = ora('Creating project directory...').start();
|
|
190
|
+
try {
|
|
191
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
192
|
+
spinner.succeed('Project directory created');
|
|
193
|
+
} catch (error) {
|
|
194
|
+
spinner.fail('Failed to create project directory');
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Copy template files from current directory (the boilerplate itself)
|
|
199
|
+
spinner = ora('Copying template files...').start();
|
|
200
|
+
try {
|
|
201
|
+
const templatePath = path.resolve(__dirname, '../../');
|
|
202
|
+
copyTemplateFiles(templatePath, projectPath, appName, bundleId);
|
|
203
|
+
spinner.succeed('Template files copied successfully');
|
|
204
|
+
} catch (error) {
|
|
205
|
+
spinner.fail('Failed to copy template files');
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Update package.json
|
|
210
|
+
spinner = ora('Updating package.json...').start();
|
|
211
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
212
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
213
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
214
|
+
packageJson.name = appName;
|
|
215
|
+
packageJson.version = '1.0.0';
|
|
216
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
217
|
+
spinner.succeed('package.json updated');
|
|
218
|
+
} else {
|
|
219
|
+
spinner.warn('package.json not found, skipping update');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Update app.json
|
|
223
|
+
spinner = ora('Updating app.json...').start();
|
|
224
|
+
const appJsonPath = path.join(projectPath, 'app.json');
|
|
225
|
+
if (fs.existsSync(appJsonPath)) {
|
|
226
|
+
const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
|
|
227
|
+
if (appJson.expo) {
|
|
228
|
+
appJson.expo.name = appName;
|
|
229
|
+
appJson.expo.slug = appName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
230
|
+
|
|
231
|
+
// Update bundle identifiers
|
|
232
|
+
if (appJson.expo.ios) {
|
|
233
|
+
appJson.expo.ios.bundleIdentifier = bundleId;
|
|
234
|
+
} else {
|
|
235
|
+
appJson.expo.ios = { bundleIdentifier: bundleId };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (appJson.expo.android) {
|
|
239
|
+
appJson.expo.android.package = bundleId;
|
|
240
|
+
} else {
|
|
241
|
+
appJson.expo.android = { package: bundleId };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
fs.writeFileSync(appJsonPath, JSON.stringify(appJson, null, 2));
|
|
245
|
+
spinner.succeed('app.json updated with bundle IDs');
|
|
246
|
+
} else {
|
|
247
|
+
spinner.warn('app.json not found, skipping update');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Install dependencies
|
|
251
|
+
if (!options.skipInstall) {
|
|
252
|
+
const useYarn = !options.npm && commandExists('yarn');
|
|
253
|
+
const packageManager = useYarn ? 'yarn' : 'npm';
|
|
254
|
+
|
|
255
|
+
spinner = ora(`Installing dependencies with ${packageManager}...`).start();
|
|
256
|
+
spinner.text = `Installing dependencies... This might take a few minutes ā`;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
executeCommand(`cd "${projectPath}" && ${packageManager} install`);
|
|
260
|
+
spinner.succeed('Dependencies installed successfully');
|
|
261
|
+
} catch (error) {
|
|
262
|
+
spinner.fail('Failed to install dependencies');
|
|
263
|
+
console.log(chalk.yellow('\nYou can install dependencies later by running:'));
|
|
264
|
+
console.log(chalk.cyan(` cd ${appName} && ${packageManager} install\n`));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Initialize git
|
|
269
|
+
if (!options.skipGit) {
|
|
270
|
+
spinner = ora('Initializing git repository...').start();
|
|
271
|
+
try {
|
|
272
|
+
executeCommand(`cd "${projectPath}" && git init`, { silent: true });
|
|
273
|
+
executeCommand(`cd "${projectPath}" && git add -A`, { silent: true });
|
|
274
|
+
executeCommand(`cd "${projectPath}" && git commit -m "Initial commit from @gufran/expo-boilerplate"`, { silent: true });
|
|
275
|
+
spinner.succeed('Git repository initialized');
|
|
276
|
+
} catch (error) {
|
|
277
|
+
spinner.warn('Git initialization skipped');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Success message
|
|
282
|
+
console.log(chalk.green.bold('\nš Success! Your Expo app is ready!\n'));
|
|
283
|
+
console.log(chalk.cyan('š Project created at:'), chalk.bold(projectPath));
|
|
284
|
+
console.log(chalk.cyan('\nš Next steps:\n'));
|
|
285
|
+
console.log(chalk.white(` ${chalk.bold('1.')} Navigate to your project:`));
|
|
286
|
+
console.log(chalk.gray(` cd ${appName}\n`));
|
|
287
|
+
|
|
288
|
+
if (options.skipInstall) {
|
|
289
|
+
console.log(chalk.white(` ${chalk.bold('2.')} Install dependencies:`));
|
|
290
|
+
console.log(chalk.gray(` npm install\n`));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(chalk.white(` ${chalk.bold(options.skipInstall ? '3' : '2')}. Configure Firebase:`));
|
|
294
|
+
console.log(chalk.gray(` - Add your google-services.json to android/app/`));
|
|
295
|
+
console.log(chalk.gray(` - Add your GoogleService-Info.plist to ios/ClubYakka/\n`));
|
|
296
|
+
|
|
297
|
+
console.log(chalk.white(` ${chalk.bold(options.skipInstall ? '4' : '3')}. Install iOS dependencies:`));
|
|
298
|
+
console.log(chalk.gray(` cd ios && pod install && cd ..\n`));
|
|
299
|
+
|
|
300
|
+
console.log(chalk.white(` ${chalk.bold(options.skipInstall ? '5' : '4')}. Start the development server:`));
|
|
301
|
+
console.log(chalk.gray(` npm start\n`));
|
|
302
|
+
|
|
303
|
+
console.log(chalk.white(` ${chalk.bold(options.skipInstall ? '6' : '5')}. Run on device:`));
|
|
304
|
+
console.log(chalk.gray(` npm run android ${chalk.dim('# For Android')}`));
|
|
305
|
+
console.log(chalk.gray(` npm run ios ${chalk.dim('# For iOS')}\n`));
|
|
306
|
+
|
|
307
|
+
console.log(chalk.cyan('š Features included:'));
|
|
308
|
+
console.log(chalk.gray(' ā
React Navigation with Authentication Flow'));
|
|
309
|
+
console.log(chalk.gray(' ā
Firebase Integration (Messaging, Analytics)'));
|
|
310
|
+
console.log(chalk.gray(' ā
Azure Blob Storage Upload'));
|
|
311
|
+
console.log(chalk.gray(' ā
TanStack Query for API Management'));
|
|
312
|
+
console.log(chalk.gray(' ā
TypeScript Configuration'));
|
|
313
|
+
console.log(chalk.gray(' ā
Zustand State Management'));
|
|
314
|
+
console.log(chalk.gray(' ā
Image Picker & Camera Integration'));
|
|
315
|
+
console.log(chalk.gray(' ā
Push Notifications with Notifee\n'));
|
|
316
|
+
|
|
317
|
+
console.log(chalk.yellow('ā ļø Important:'));
|
|
318
|
+
console.log(chalk.gray(' Remember to update your app.json with your own configuration'));
|
|
319
|
+
console.log(chalk.gray(' and add your Firebase configuration files!\n'));
|
|
320
|
+
|
|
321
|
+
console.log(chalk.magenta('š” Happy coding! š\n'));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = { createExpoApp };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-gufran-expo-app",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "š Ultimate Expo React Native Boilerplate - Create production-ready Expo apps instantly with Firebase, Navigation, TypeScript, and more. No git clone needed, works offline!",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-gufran-expo-app": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"prepare": "chmod +x bin/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react-native",
|
|
15
|
+
"expo",
|
|
16
|
+
"boilerplate",
|
|
17
|
+
"template",
|
|
18
|
+
"firebase",
|
|
19
|
+
"typescript",
|
|
20
|
+
"react-navigation",
|
|
21
|
+
"azure-storage",
|
|
22
|
+
"starter-kit",
|
|
23
|
+
"gufran"
|
|
24
|
+
],
|
|
25
|
+
"author": "Gufran Gaury <your-email@example.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/GufranGaury1887/Boiler_Plat_Expo_-Gufran.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/GufranGaury1887/Boiler_Plat_Expo_-Gufran/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/GufranGaury1887/Boiler_Plat_Expo_-Gufran#readme",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "^4.1.2",
|
|
40
|
+
"commander": "^11.1.0",
|
|
41
|
+
"ora": "^5.4.1",
|
|
42
|
+
"prompts": "^2.4.2",
|
|
43
|
+
"validate-npm-package-name": "^5.0.0"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"bin",
|
|
47
|
+
"lib",
|
|
48
|
+
"template",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
]
|
|
52
|
+
}
|