@varunindiit/create-rn-starter 1.0.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.
Files changed (243) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/bin/index.js +270 -0
  4. package/lib/prompt.js +63 -0
  5. package/lib/rename.js +239 -0
  6. package/lib/scaffold.js +110 -0
  7. package/lib/utils.js +122 -0
  8. package/package.json +38 -0
  9. package/template/.eslintrc.js +4 -0
  10. package/template/.prettierrc.js +5 -0
  11. package/template/.watchmanconfig +1 -0
  12. package/template/App.tsx +100 -0
  13. package/template/Gemfile +17 -0
  14. package/template/README.md +97 -0
  15. package/template/__tests__/App.test.tsx +13 -0
  16. package/template/_gitignore +75 -0
  17. package/template/android/app/build.gradle +119 -0
  18. package/template/android/app/debug.keystore +0 -0
  19. package/template/android/app/proguard-rules.pro +10 -0
  20. package/template/android/app/src/main/AndroidManifest.xml +45 -0
  21. package/template/android/app/src/main/assets/fonts/MonaSans-Black.ttf +0 -0
  22. package/template/android/app/src/main/assets/fonts/MonaSans-BlackItalic.ttf +0 -0
  23. package/template/android/app/src/main/assets/fonts/MonaSans-Bold.ttf +0 -0
  24. package/template/android/app/src/main/assets/fonts/MonaSans-BoldItalic.ttf +0 -0
  25. package/template/android/app/src/main/assets/fonts/MonaSans-ExtraBold.ttf +0 -0
  26. package/template/android/app/src/main/assets/fonts/MonaSans-ExtraBoldItalic.ttf +0 -0
  27. package/template/android/app/src/main/assets/fonts/MonaSans-ExtraLight.ttf +0 -0
  28. package/template/android/app/src/main/assets/fonts/MonaSans-ExtraLightItalic.ttf +0 -0
  29. package/template/android/app/src/main/assets/fonts/MonaSans-Italic.ttf +0 -0
  30. package/template/android/app/src/main/assets/fonts/MonaSans-Light.ttf +0 -0
  31. package/template/android/app/src/main/assets/fonts/MonaSans-LightItalic.ttf +0 -0
  32. package/template/android/app/src/main/assets/fonts/MonaSans-Medium.ttf +0 -0
  33. package/template/android/app/src/main/assets/fonts/MonaSans-MediumItalic.ttf +0 -0
  34. package/template/android/app/src/main/assets/fonts/MonaSans-Regular.ttf +0 -0
  35. package/template/android/app/src/main/assets/fonts/MonaSans-SemiBold.ttf +0 -0
  36. package/template/android/app/src/main/assets/fonts/MonaSans-SemiBoldItalic.ttf +0 -0
  37. package/template/android/app/src/main/java/com/awesomeproject/MainActivity.kt +22 -0
  38. package/template/android/app/src/main/java/com/awesomeproject/MainApplication.kt +27 -0
  39. package/template/android/app/src/main/res/drawable/launch_screen.png +0 -0
  40. package/template/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  41. package/template/android/app/src/main/res/layout/launch_screen.xml +12 -0
  42. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  43. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  44. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  45. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  46. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  47. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  48. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  49. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  50. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  51. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  52. package/template/android/app/src/main/res/values/colors.xml +3 -0
  53. package/template/android/app/src/main/res/values/strings.xml +3 -0
  54. package/template/android/app/src/main/res/values/styles.xml +11 -0
  55. package/template/android/build.gradle +21 -0
  56. package/template/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  57. package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  58. package/template/android/gradle.properties +44 -0
  59. package/template/android/gradlew +248 -0
  60. package/template/android/gradlew.bat +98 -0
  61. package/template/android/link-assets-manifest.json +69 -0
  62. package/template/android/settings.gradle +6 -0
  63. package/template/app.json +4 -0
  64. package/template/babel.config.js +4 -0
  65. package/template/declarations.d.ts +6 -0
  66. package/template/env.example +20 -0
  67. package/template/index.js +10 -0
  68. package/template/ios/.xcode.env +11 -0
  69. package/template/ios/.xcode.env.local +1 -0
  70. package/template/ios/AwesomeProject/AppDelegate.swift +60 -0
  71. package/template/ios/AwesomeProject/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  72. package/template/ios/AwesomeProject/Images.xcassets/Contents.json +6 -0
  73. package/template/ios/AwesomeProject/Images.xcassets/Splash.imageset/Contents.json +23 -0
  74. package/template/ios/AwesomeProject/Images.xcassets/Splash.imageset/Splash@1x.png +0 -0
  75. package/template/ios/AwesomeProject/Images.xcassets/Splash.imageset/Splash@2x.png +0 -0
  76. package/template/ios/AwesomeProject/Images.xcassets/Splash.imageset/Splash@3x.png +0 -0
  77. package/template/ios/AwesomeProject/Info.plist +89 -0
  78. package/template/ios/AwesomeProject/LaunchScreen.storyboard +40 -0
  79. package/template/ios/AwesomeProject/PrivacyInfo.xcprivacy +38 -0
  80. package/template/ios/AwesomeProject.xcodeproj/project.pbxproj +576 -0
  81. package/template/ios/AwesomeProject.xcodeproj/xcshareddata/xcschemes/AwesomeProject.xcscheme +88 -0
  82. package/template/ios/AwesomeProject.xcworkspace/contents.xcworkspacedata +10 -0
  83. package/template/ios/Podfile +68 -0
  84. package/template/ios/link-assets-manifest.json +69 -0
  85. package/template/jest.config.js +3 -0
  86. package/template/metro.config.js +24 -0
  87. package/template/package.json +68 -0
  88. package/template/react-native.config.js +7 -0
  89. package/template/src/assets/fonts/MonaSans-Black.ttf +0 -0
  90. package/template/src/assets/fonts/MonaSans-BlackItalic.ttf +0 -0
  91. package/template/src/assets/fonts/MonaSans-Bold.ttf +0 -0
  92. package/template/src/assets/fonts/MonaSans-BoldItalic.ttf +0 -0
  93. package/template/src/assets/fonts/MonaSans-ExtraBold.ttf +0 -0
  94. package/template/src/assets/fonts/MonaSans-ExtraBoldItalic.ttf +0 -0
  95. package/template/src/assets/fonts/MonaSans-ExtraLight.ttf +0 -0
  96. package/template/src/assets/fonts/MonaSans-ExtraLightItalic.ttf +0 -0
  97. package/template/src/assets/fonts/MonaSans-Italic.ttf +0 -0
  98. package/template/src/assets/fonts/MonaSans-Light.ttf +0 -0
  99. package/template/src/assets/fonts/MonaSans-LightItalic.ttf +0 -0
  100. package/template/src/assets/fonts/MonaSans-Medium.ttf +0 -0
  101. package/template/src/assets/fonts/MonaSans-MediumItalic.ttf +0 -0
  102. package/template/src/assets/fonts/MonaSans-Regular.ttf +0 -0
  103. package/template/src/assets/fonts/MonaSans-SemiBold.ttf +0 -0
  104. package/template/src/assets/fonts/MonaSans-SemiBoldItalic.ttf +0 -0
  105. package/template/src/assets/image/BackGroundAuth.png +0 -0
  106. package/template/src/assets/image/BackgroundVerification.png +0 -0
  107. package/template/src/assets/image/logo.png +0 -0
  108. package/template/src/assets/svg/add-circle.svg +5 -0
  109. package/template/src/assets/svg/airConditioning.svg +12 -0
  110. package/template/src/assets/svg/apple.svg +3 -0
  111. package/template/src/assets/svg/arrowDown.svg +3 -0
  112. package/template/src/assets/svg/back.svg +10 -0
  113. package/template/src/assets/svg/bag.svg +11 -0
  114. package/template/src/assets/svg/calender.svg +5 -0
  115. package/template/src/assets/svg/car.svg +10 -0
  116. package/template/src/assets/svg/carConfirm.svg +60 -0
  117. package/template/src/assets/svg/chatActive.svg +3 -0
  118. package/template/src/assets/svg/chatUnActive.svg +3 -0
  119. package/template/src/assets/svg/document-text.svg +6 -0
  120. package/template/src/assets/svg/gender.svg +11 -0
  121. package/template/src/assets/svg/google.svg +6 -0
  122. package/template/src/assets/svg/headphone.svg +3 -0
  123. package/template/src/assets/svg/homeActive.svg +3 -0
  124. package/template/src/assets/svg/homeUnActive.svg +3 -0
  125. package/template/src/assets/svg/logo.svg +18 -0
  126. package/template/src/assets/svg/logout.svg +5 -0
  127. package/template/src/assets/svg/maxBack.svg +4 -0
  128. package/template/src/assets/svg/message-text.svg +7 -0
  129. package/template/src/assets/svg/music.svg +5 -0
  130. package/template/src/assets/svg/noSmoking.svg +10 -0
  131. package/template/src/assets/svg/notification.svg +5 -0
  132. package/template/src/assets/svg/passenger.svg +4 -0
  133. package/template/src/assets/svg/phone.svg +3 -0
  134. package/template/src/assets/svg/rightArrow.svg +3 -0
  135. package/template/src/assets/svg/security-user.svg +5 -0
  136. package/template/src/assets/svg/star.svg +3 -0
  137. package/template/src/assets/svg/tick-circle.svg +4 -0
  138. package/template/src/assets/svg/trafficLight.svg +41 -0
  139. package/template/src/assets/svg/tripActive.svg +10 -0
  140. package/template/src/assets/svg/tripUnActive.svg +10 -0
  141. package/template/src/assets/svg/usbChargers.svg +3 -0
  142. package/template/src/assets/svg/user.svg +4 -0
  143. package/template/src/assets/svg/userActive.svg +3 -0
  144. package/template/src/assets/svg/userPlaceholder.svg +3 -0
  145. package/template/src/assets/svg/userUnActive.svg +3 -0
  146. package/template/src/components/AuthLayout/AuthLayout.tsx +170 -0
  147. package/template/src/components/AuthLayout/index.ts +1 -0
  148. package/template/src/components/BottomSheet/BottomSheet.tsx +73 -0
  149. package/template/src/components/BottomSheet/BottomSheetAlert.tsx +100 -0
  150. package/template/src/components/BottomSheet/CenterAlert.tsx +153 -0
  151. package/template/src/components/BottomSheet/index.ts +2 -0
  152. package/template/src/components/BottomTabBar/index.tsx +145 -0
  153. package/template/src/components/Button/RNButton.tsx +152 -0
  154. package/template/src/components/Button/index.ts +2 -0
  155. package/template/src/components/Common/Avatar.tsx +80 -0
  156. package/template/src/components/Common/Card.tsx +49 -0
  157. package/template/src/components/Common/CardBrandLogo.tsx +66 -0
  158. package/template/src/components/Common/Checkbox.tsx +65 -0
  159. package/template/src/components/Common/Chip.tsx +79 -0
  160. package/template/src/components/Common/CommonStyles.tsx +594 -0
  161. package/template/src/components/Common/Divider.tsx +33 -0
  162. package/template/src/components/Common/DriverTripCard.tsx +308 -0
  163. package/template/src/components/Common/Dropdown.tsx +161 -0
  164. package/template/src/components/Common/EmptyState.tsx +52 -0
  165. package/template/src/components/Common/FAB.tsx +68 -0
  166. package/template/src/components/Common/HeaderLocation.tsx +108 -0
  167. package/template/src/components/Common/Loader.tsx +23 -0
  168. package/template/src/components/Common/RatingStars.tsx +103 -0
  169. package/template/src/components/Common/RouteDots.tsx +98 -0
  170. package/template/src/components/Common/SegmentedControl.tsx +126 -0
  171. package/template/src/components/Common/SosButton.tsx +80 -0
  172. package/template/src/components/Common/SosSheet.tsx +344 -0
  173. package/template/src/components/Common/StarRating.tsx +58 -0
  174. package/template/src/components/Common/StatusBadge.tsx +56 -0
  175. package/template/src/components/Common/Toggle.tsx +66 -0
  176. package/template/src/components/Common/TripCard.tsx +247 -0
  177. package/template/src/components/Common/UploadBox.tsx +106 -0
  178. package/template/src/components/Container/MainContainer.tsx +76 -0
  179. package/template/src/components/Container/index.ts +1 -0
  180. package/template/src/components/Header/index.tsx +143 -0
  181. package/template/src/components/Icon/SvgIcons.tsx +1991 -0
  182. package/template/src/components/ImagePickerSheet/ImagePickerSheet.tsx +233 -0
  183. package/template/src/components/ImagePickerSheet/index.ts +2 -0
  184. package/template/src/components/Input/CountryDropdown.tsx +71 -0
  185. package/template/src/components/Input/OtpInput.tsx +117 -0
  186. package/template/src/components/Input/RNInput.tsx +138 -0
  187. package/template/src/components/Input/index.ts +4 -0
  188. package/template/src/components/Picker/DatePickerSheet.tsx +393 -0
  189. package/template/src/components/Picker/PassengerPickerSheet.tsx +237 -0
  190. package/template/src/components/Text/RNText.tsx +62 -0
  191. package/template/src/components/Text/index.ts +1 -0
  192. package/template/src/components/index.ts +44 -0
  193. package/template/src/hooks/useCurrentLocation.ts +72 -0
  194. package/template/src/localization/i18n.ts +29 -0
  195. package/template/src/localization/i18next.d.ts +11 -0
  196. package/template/src/localization/index.ts +4 -0
  197. package/template/src/localization/languageStorage.ts +27 -0
  198. package/template/src/localization/languages.ts +62 -0
  199. package/template/src/localization/resources/en.ts +703 -0
  200. package/template/src/localization/resources/fr.ts +703 -0
  201. package/template/src/localization/useLanguage.ts +42 -0
  202. package/template/src/navigation/AuthNavigation.tsx +23 -0
  203. package/template/src/navigation/BottomTabs.tsx +24 -0
  204. package/template/src/navigation/RootNavigation.tsx +27 -0
  205. package/template/src/navigation/RouteKey.ts +22 -0
  206. package/template/src/navigation/StackNavigation.tsx +52 -0
  207. package/template/src/navigation/paramLists.ts +25 -0
  208. package/template/src/redux/slice/app.ts +66 -0
  209. package/template/src/redux/slice/auth.ts +40 -0
  210. package/template/src/redux/slice/userProfile.ts +124 -0
  211. package/template/src/redux/store.ts +17 -0
  212. package/template/src/screen/auth/Login.tsx +69 -0
  213. package/template/src/screen/onboarding/LanguageSelection.tsx +231 -0
  214. package/template/src/screen/root/home/index.tsx +36 -0
  215. package/template/src/screen/root/profile/index.tsx +69 -0
  216. package/template/src/screen/shared/Chat.tsx +308 -0
  217. package/template/src/screen/shared/EditProfile.tsx +407 -0
  218. package/template/src/screen/shared/HelpSupport.tsx +678 -0
  219. package/template/src/screen/shared/LocationSearch.tsx +362 -0
  220. package/template/src/screen/shared/Messages.tsx +115 -0
  221. package/template/src/screen/shared/Notifications.tsx +86 -0
  222. package/template/src/screen/shared/PrivacyPolicy.tsx +297 -0
  223. package/template/src/screen/shared/Profile.tsx +118 -0
  224. package/template/src/screen/shared/Ratings.tsx +170 -0
  225. package/template/src/screen/shared/TermsConditions.tsx +315 -0
  226. package/template/src/screen/shared/profile/DriverProfile.tsx +262 -0
  227. package/template/src/screen/shared/profile/PassengerProfile.tsx +123 -0
  228. package/template/src/screen/shared/profile/ProfileParts.tsx +219 -0
  229. package/template/src/services/Config.ts +37 -0
  230. package/template/src/services/api.ts +37 -0
  231. package/template/src/services/index.ts +4 -0
  232. package/template/src/services/places.ts +320 -0
  233. package/template/src/services/storage.ts +33 -0
  234. package/template/src/theme/fonts.ts +30 -0
  235. package/template/src/theme/index.ts +3 -0
  236. package/template/src/theme/spacing.ts +66 -0
  237. package/template/src/theme/theme.ts +58 -0
  238. package/template/src/types/env.d.ts +8 -0
  239. package/template/src/types/index.ts +3 -0
  240. package/template/src/utils/card.ts +101 -0
  241. package/template/src/utils/constants.ts +39 -0
  242. package/template/src/utils/functions.ts +24 -0
  243. package/template/tsconfig.json +8 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 varunindiit
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,93 @@
1
+ # create-rn-starter
2
+
3
+ > Scaffold a **production-ready React Native CLI** app — fully renamed for Android & iOS — in seconds.
4
+
5
+ ```bash
6
+ npx @varunindiit/create-rn-starter
7
+ ```
8
+
9
+ The CLI asks for a **project name** and a **bundle/package identifier**, then
10
+ generates a working project: it copies the template, renames every reference
11
+ across JS / Android / iOS, sets up the environment files, installs
12
+ dependencies (and CocoaPods on macOS) and initialises git.
13
+
14
+ ```bash
15
+ # interactive
16
+ npx @varunindiit/create-rn-starter
17
+
18
+ # non-interactive
19
+ npx @varunindiit/create-rn-starter awesome-app --bundle-id com.acme.awesome -y
20
+ ```
21
+
22
+ ## What you get
23
+
24
+ A real, runnable RN **0.85** project (New Architecture) with:
25
+
26
+ - **Navigation** — React Navigation native-stack + bottom tabs, with a
27
+ declarative auth guard (`StackNavigation`) that swaps the Auth and App trees
28
+ off a single redux flag.
29
+ - **State** — Redux Toolkit store with `auth` / `app` / `userProfile` slices.
30
+ - **Dummy auth flow** — Login → Bottom Tabs (**Home** · **Profile**) → Logout,
31
+ persisted with **MMKV** so the session survives restarts.
32
+ - **Theming** — centralised `theme` (colours, spacing, fonts) + `react-native-size-matters`.
33
+ - **i18n** — `i18next` / `react-i18next` with `en` / `fr` resources and language storage.
34
+ - **SVG** — `react-native-svg` + `react-native-svg-transformer` wired in Metro.
35
+ - **API/services** — `axios` client, `Config` service, storage and places helpers.
36
+ - **Components** — a sizeable reusable library (buttons, inputs, sheets, headers,
37
+ pickers, common UI…).
38
+ - **Native setup** — splash screen, fonts/assets linking, permissions, and a
39
+ fully configured Android + iOS project.
40
+
41
+ ## CLI options
42
+
43
+ | Flag | Description |
44
+ | ------------------- | ---------------------------------------------------- |
45
+ | `[project-name]` | Positional. Skips the name prompt. |
46
+ | `--bundle-id <id>` | Reverse-DNS identifier, e.g. `com.acme.myapp`. |
47
+ | `--no-install` | Skip JS dependency installation. |
48
+ | `--no-pods` | Skip iOS CocoaPods (macOS only). |
49
+ | `--no-git` | Skip git initialisation. |
50
+ | `-y, --yes` | Accept all defaults, no prompts. |
51
+ | `-h, --help` | Show help. |
52
+ | `-v, --version` | Show version. |
53
+
54
+ ## How the rename works
55
+
56
+ The bundled `template/` is a genuine working app, so every name appears as a
57
+ concrete literal. On scaffold the CLI:
58
+
59
+ 1. **Token pass** — rewrites file contents, swapping the source identifiers
60
+ (`AwesomeProject`, `com.awesomeproject`, the default Xcode bundle id …) for
61
+ your values across all text files (TS/JS, gradle, Kotlin, Swift, plist,
62
+ pbxproj, schemes, Podfile…).
63
+ 2. **Native folder moves** — renames `ios/<App>`, `ios/<App>.xcodeproj`,
64
+ `ios/<App>.xcworkspace` and the shared scheme, and moves the Android
65
+ `java/com/awesomeproject` package directory to your bundle's path.
66
+ 3. **Targeted edits** — sets `package.json` name (npm slug), `app.json`,
67
+ Android `strings.xml` `app_name` and iOS `CFBundleDisplayName` to the
68
+ human display name.
69
+
70
+ ## Architecture
71
+
72
+ ```
73
+ bin/index.js CLI entry: arg parsing, prompts, orchestration
74
+ lib/utils.js logging + name normalisation (pascal/slug/lower/display/bundle)
75
+ lib/prompt.js zero-dependency readline prompts
76
+ lib/rename.js RN rename engine: token pass + native folder moves + targeted edits
77
+ lib/scaffold.js copy tree, env files, dependency/pod install, git init
78
+ template/ a full, working RN CLI app (the thing that gets cloned)
79
+ scripts/smoke.js end-to-end test: scaffold to a temp dir and assert the rename
80
+ ```
81
+
82
+ The package has **zero runtime dependencies** — only Node's standard library.
83
+
84
+ ## Development
85
+
86
+ ```bash
87
+ npm run lint # node --check every script
88
+ npm test # scaffold into a temp dir and assert JS/Android/iOS rename
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ const {
8
+ c,
9
+ log,
10
+ step,
11
+ ok,
12
+ warn,
13
+ err,
14
+ toPascalName,
15
+ toSlug,
16
+ toLowerName,
17
+ toDisplayName,
18
+ defaultBundleId,
19
+ isValidBundleId,
20
+ isValidSlug,
21
+ resolveTarget,
22
+ } = require("../lib/utils");
23
+ const { createPrompter } = require("../lib/prompt");
24
+ const {
25
+ copyDir,
26
+ restoreDotfiles,
27
+ prepareEnv,
28
+ detectPackageManager,
29
+ installDeps,
30
+ installPods,
31
+ gitInit,
32
+ } = require("../lib/scaffold");
33
+ const { renameProject } = require("../lib/rename");
34
+
35
+ const TEMPLATE_DIR = path.join(__dirname, "..", "template");
36
+
37
+ // ── arg parsing ──────────────────────────────────────────────────────────────
38
+ function parseArgs(argv) {
39
+ const opts = {
40
+ name: undefined,
41
+ bundleId: undefined,
42
+ install: undefined, // undefined = ask
43
+ pods: undefined,
44
+ git: undefined,
45
+ yes: false,
46
+ };
47
+ const positional = [];
48
+ for (let i = 0; i < argv.length; i++) {
49
+ const a = argv[i];
50
+ if (a === "--help" || a === "-h") opts.help = true;
51
+ else if (a === "--version" || a === "-v") opts.version = true;
52
+ else if (a === "--yes" || a === "-y") opts.yes = true;
53
+ else if (a === "--no-install") opts.install = false;
54
+ else if (a === "--install") opts.install = true;
55
+ else if (a === "--no-pods") opts.pods = false;
56
+ else if (a === "--pods") opts.pods = true;
57
+ else if (a === "--no-git") opts.git = false;
58
+ else if (a === "--git") opts.git = true;
59
+ else if (a === "--bundle-id") opts.bundleId = argv[++i];
60
+ else if (a.startsWith("--bundle-id=")) opts.bundleId = a.split("=")[1];
61
+ else if (a.startsWith("--")) warn(`Ignoring unknown flag: ${a}`);
62
+ else positional.push(a);
63
+ }
64
+ if (positional.length) opts.name = positional[0];
65
+ return opts;
66
+ }
67
+
68
+ function printHelp() {
69
+ log(`
70
+ ${c.bold("create-rn-starter")} — scaffold a production-ready React Native CLI app
71
+
72
+ ${c.bold("Usage")}
73
+ npx create-rn-starter ${c.dim("[project-name] [options]")}
74
+
75
+ ${c.bold("Options")}
76
+ --bundle-id <id> Reverse-DNS app identifier (e.g. com.acme.myapp)
77
+ --no-install Skip JS dependency installation
78
+ --no-pods Skip iOS CocoaPods installation (macOS only)
79
+ --no-git Skip git repository initialisation
80
+ -y, --yes Accept all defaults, no prompts
81
+ -h, --help Show this help
82
+ -v, --version Show version
83
+
84
+ ${c.bold("Example")}
85
+ npx create-rn-starter awesome-app --bundle-id com.acme.awesome
86
+ `);
87
+ }
88
+
89
+ async function main() {
90
+ const opts = parseArgs(process.argv.slice(2));
91
+
92
+ if (opts.help) return printHelp();
93
+ if (opts.version) {
94
+ const pkg = require("../package.json");
95
+ return log(pkg.version);
96
+ }
97
+
98
+ log("");
99
+ log(`${c.magenta(c.bold("◆ create-rn-starter"))}`);
100
+ log(c.dim(" React Native CLI · React Navigation · Redux Toolkit · TypeScript\n"));
101
+
102
+ if (!fs.existsSync(TEMPLATE_DIR)) {
103
+ err("Bundled template/ directory is missing — the package is corrupt.");
104
+ process.exit(1);
105
+ }
106
+
107
+ const prompter = createPrompter();
108
+
109
+ try {
110
+ // ── project name ──────────────────────────────────────────────────────
111
+ const projectName =
112
+ opts.name ||
113
+ (opts.yes
114
+ ? "My RN App"
115
+ : await prompter.ask("Project name", {
116
+ defaultValue: "My RN App",
117
+ validate: (v) =>
118
+ isValidSlug(toSlug(v))
119
+ ? true
120
+ : "Use letters, numbers, spaces or hyphens.",
121
+ }));
122
+
123
+ const slug = toSlug(projectName);
124
+ if (!isValidSlug(slug)) {
125
+ err(`Could not derive a valid project slug from "${projectName}".`);
126
+ process.exit(1);
127
+ }
128
+
129
+ const target = resolveTarget(process.cwd(), slug);
130
+ if (fs.existsSync(target) && fs.readdirSync(target).length > 0) {
131
+ err(`Directory "${slug}" already exists and is not empty.`);
132
+ process.exit(1);
133
+ }
134
+
135
+ // ── bundle id ─────────────────────────────────────────────────────────
136
+ const bundleDefault = defaultBundleId(projectName);
137
+ const bundleId =
138
+ opts.bundleId ||
139
+ (opts.yes
140
+ ? bundleDefault
141
+ : await prompter.ask("Bundle / package identifier", {
142
+ defaultValue: bundleDefault,
143
+ validate: (v) =>
144
+ isValidBundleId(v)
145
+ ? true
146
+ : "Must be reverse-DNS, e.g. com.acme.myapp",
147
+ }));
148
+
149
+ if (!isValidBundleId(bundleId)) {
150
+ err(`Invalid bundle identifier: ${bundleId}`);
151
+ process.exit(1);
152
+ }
153
+
154
+ const names = {
155
+ pascalName: toPascalName(projectName),
156
+ slug,
157
+ lowerName: toLowerName(projectName),
158
+ displayName: toDisplayName(projectName),
159
+ bundleId,
160
+ };
161
+
162
+ // ── install / pods / git decisions ────────────────────────────────────
163
+ const wantInstall =
164
+ opts.install !== undefined
165
+ ? opts.install
166
+ : opts.yes
167
+ ? true
168
+ : await prompter.confirm("Install JS dependencies now?", true);
169
+
170
+ const canPods = process.platform === "darwin";
171
+ const wantPods =
172
+ opts.pods !== undefined
173
+ ? opts.pods
174
+ : !canPods || !wantInstall
175
+ ? false
176
+ : opts.yes
177
+ ? true
178
+ : await prompter.confirm("Install iOS CocoaPods now?", true);
179
+
180
+ const wantGit =
181
+ opts.git !== undefined
182
+ ? opts.git
183
+ : opts.yes
184
+ ? true
185
+ : await prompter.confirm("Initialise a git repository?", true);
186
+
187
+ prompter.close();
188
+
189
+ // ── summary ───────────────────────────────────────────────────────────
190
+ log("");
191
+ log(c.bold(" Creating project with:"));
192
+ log(` ${c.dim("display name")} ${names.displayName}`);
193
+ log(` ${c.dim("app name ")} ${names.pascalName}`);
194
+ log(` ${c.dim("slug ")} ${names.slug}`);
195
+ log(` ${c.dim("bundle id ")} ${names.bundleId}`);
196
+ log(
197
+ ` ${c.dim("location ")} ${
198
+ path.relative(process.cwd(), target) || "."
199
+ }`
200
+ );
201
+ log("");
202
+
203
+ // ── scaffold ──────────────────────────────────────────────────────────
204
+ step("Copying template files…");
205
+ copyDir(TEMPLATE_DIR, target);
206
+
207
+ step("Renaming project everywhere (JS, Android & iOS)…");
208
+ const changed = renameProject(target, names);
209
+ restoreDotfiles(target);
210
+ ok(`Updated ${changed} file${changed === 1 ? "" : "s"} and native folders.`);
211
+
212
+ step("Setting up environment files…");
213
+ prepareEnv(target);
214
+ ok("Created .env from .env.example.");
215
+
216
+ // ── deps ──────────────────────────────────────────────────────────────
217
+ const pm = detectPackageManager();
218
+ if (wantInstall) {
219
+ step(`Installing dependencies with ${pm}… (this can take a minute)`);
220
+ if (installDeps(target, pm)) ok("Dependencies installed.");
221
+ else warn(`"${pm} install" failed — run it manually after.`);
222
+ } else {
223
+ warn("Skipped dependency installation.");
224
+ }
225
+
226
+ if (wantPods) {
227
+ step("Installing iOS CocoaPods…");
228
+ const podResult = installPods(target);
229
+ if (podResult === "ok") ok("CocoaPods installed.");
230
+ else if (podResult === "skipped")
231
+ warn("CocoaPods skipped (not macOS or pod not found).");
232
+ else warn("pod install failed — run it manually in ios/.");
233
+ }
234
+
235
+ // ── git ───────────────────────────────────────────────────────────────
236
+ if (wantGit) {
237
+ step("Initialising git repository…");
238
+ if (gitInit(target)) ok("Git repository initialised.");
239
+ else warn("git init failed or git is not installed — skipped.");
240
+ }
241
+
242
+ // ── next steps ────────────────────────────────────────────────────────
243
+ const rel = path.relative(process.cwd(), target) || ".";
244
+ log("");
245
+ log(c.green(c.bold(" ✔ Done! Your React Native app is ready.\n")));
246
+ log(c.bold(" Next steps:"));
247
+ log(` ${c.cyan("cd")} ${rel}`);
248
+ if (!wantInstall) log(` ${c.cyan(pm)} install`);
249
+ if (!wantPods && canPods)
250
+ log(` ${c.cyan("cd ios && ")}${c.cyan("pod install && cd ..")}`);
251
+ log(` ${c.dim("# edit .env / src/services/Config.ts with your API URL")}`);
252
+ log(` ${c.cyan(pm === "npm" ? "npm run" : pm)} android`);
253
+ log(` ${c.cyan(pm === "npm" ? "npm run" : pm)} ios`);
254
+ log("");
255
+ log(
256
+ c.dim(
257
+ " Dummy auth flow: Login → Bottom Tabs (Home · Profile) → Logout, persisted via MMKV."
258
+ )
259
+ );
260
+ log("");
261
+ } catch (e) {
262
+ try {
263
+ prompter.close();
264
+ } catch {}
265
+ err(e && e.message ? e.message : String(e));
266
+ process.exit(1);
267
+ }
268
+ }
269
+
270
+ main();
package/lib/prompt.js ADDED
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ const readline = require("readline");
4
+ const { c } = require("./utils");
5
+
6
+ /**
7
+ * Minimal zero-dependency prompt helpers built on Node's readline.
8
+ * Each returns a Promise; the shared interface is created lazily and closed
9
+ * by the caller via `close()`.
10
+ */
11
+ function createPrompter() {
12
+ const rl = readline.createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ });
16
+
17
+ const question = (q) => new Promise((resolve) => rl.question(q, resolve));
18
+
19
+ /**
20
+ * Ask for free text.
21
+ * @param {string} message
22
+ * @param {object} [opts] { defaultValue, validate(value) => true | string }
23
+ */
24
+ async function ask(message, opts = {}) {
25
+ const { defaultValue, validate } = opts;
26
+ const hint = defaultValue ? c.dim(` (${defaultValue})`) : "";
27
+ // eslint-disable-next-line no-constant-condition
28
+ while (true) {
29
+ const raw = (await question(`${c.cyan("?")} ${message}${hint}: `)).trim();
30
+ const value = raw || defaultValue || "";
31
+ if (!value) {
32
+ console.log(c.red(" Please enter a value."));
33
+ continue;
34
+ }
35
+ if (validate) {
36
+ const result = validate(value);
37
+ if (result !== true) {
38
+ console.log(c.red(` ${result}`));
39
+ continue;
40
+ }
41
+ }
42
+ return value;
43
+ }
44
+ }
45
+
46
+ /** Yes/no confirmation. */
47
+ async function confirm(message, defaultYes = true) {
48
+ const hint = defaultYes ? c.dim(" (Y/n)") : c.dim(" (y/N)");
49
+ const raw = (await question(`${c.cyan("?")} ${message}${hint}: `))
50
+ .trim()
51
+ .toLowerCase();
52
+ if (!raw) return defaultYes;
53
+ return raw === "y" || raw === "yes";
54
+ }
55
+
56
+ function close() {
57
+ rl.close();
58
+ }
59
+
60
+ return { ask, confirm, close };
61
+ }
62
+
63
+ module.exports = { createPrompter };
package/lib/rename.js ADDED
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { bundleIdToPath } = require("./utils");
6
+
7
+ // ── what the bundled template currently calls itself ─────────────────────────
8
+ // The template is a real, working RN CLI app, so every identifier appears as a
9
+ // concrete literal. Renaming = swapping these source literals for the user's.
10
+ const SOURCE = {
11
+ pascalName: "AwesomeProject",
12
+ lowerName: "awesomeproject",
13
+ bundleId: "com.awesomeproject",
14
+ // The default identifier `react-native init` bakes into the Xcode project.
15
+ iosDefaultBundleId: "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)",
16
+ };
17
+
18
+ // File extensions / names we treat as text and run token replacement on.
19
+ // Everything else (fonts, png, keystores, jars …) is copied verbatim.
20
+ const TEXT_EXT = new Set([
21
+ ".js", ".jsx", ".ts", ".tsx", ".json", ".md", ".txt", ".html", ".css",
22
+ ".yml", ".yaml", ".env", ".xml", ".plist", ".pbxproj", ".storyboard",
23
+ ".xcscheme", ".xcworkspacedata", ".gradle", ".properties", ".kt", ".java",
24
+ ".swift", ".h", ".m", ".mm", ".rb", ".pro", ".cfg", ".podspec", ".d.ts",
25
+ ".xcprivacy",
26
+ ]);
27
+
28
+ const TEXT_BASENAMES = new Set([
29
+ "_gitignore", ".gitignore", "Podfile", "Gemfile", "gradlew", "gradlew.bat",
30
+ ".watchmanconfig", "app.json",
31
+ ]);
32
+
33
+ function isTextFile(filePath) {
34
+ const base = path.basename(filePath);
35
+ if (TEXT_BASENAMES.has(base)) return true;
36
+ return TEXT_EXT.has(path.extname(filePath).toLowerCase());
37
+ }
38
+
39
+ function escapeRegExp(s) {
40
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
41
+ }
42
+
43
+ /** Walk every file under `dir`, yielding absolute paths. */
44
+ function walk(dir, out = []) {
45
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
46
+ const full = path.join(dir, entry.name);
47
+ if (entry.isDirectory()) walk(full, out);
48
+ else out.push(full);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /**
54
+ * Replace every source identifier with the user's values across all text
55
+ * files. Longer keys are applied first so `org.reactjs.native…` and
56
+ * `com.awesomeproject` are handled before the bare `awesomeproject`.
57
+ */
58
+ function applyTokenReplacements(root, names) {
59
+ const tokens = {
60
+ [SOURCE.iosDefaultBundleId]: names.bundleId,
61
+ [SOURCE.bundleId]: names.bundleId,
62
+ [SOURCE.pascalName]: names.pascalName,
63
+ [SOURCE.lowerName]: names.lowerName,
64
+ };
65
+ const ordered = Object.keys(tokens).sort((a, b) => b.length - a.length);
66
+ const patterns = ordered.map((key) => ({
67
+ re: new RegExp(escapeRegExp(key), "g"),
68
+ value: tokens[key],
69
+ }));
70
+
71
+ let changed = 0;
72
+ for (const file of walk(root)) {
73
+ if (!isTextFile(file)) continue;
74
+ let content;
75
+ try {
76
+ content = fs.readFileSync(file, "utf8");
77
+ } catch {
78
+ continue; // unreadable / binary disguised as text — skip safely
79
+ }
80
+ let next = content;
81
+ for (const { re, value } of patterns) next = next.replace(re, value);
82
+ if (next !== content) {
83
+ fs.writeFileSync(file, next);
84
+ changed += 1;
85
+ }
86
+ }
87
+ return changed;
88
+ }
89
+
90
+ function renameIfExists(from, to) {
91
+ if (fs.existsSync(from) && from !== to) {
92
+ fs.mkdirSync(path.dirname(to), { recursive: true });
93
+ fs.renameSync(from, to);
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+
99
+ /**
100
+ * Rename the iOS folder, Xcode project, workspace and shared scheme so they
101
+ * match the new app name. The token pass above already rewrote every internal
102
+ * reference inside their files, so the names line up after this move.
103
+ */
104
+ function renameIosDirs(root, names) {
105
+ const ios = path.join(root, "ios");
106
+ if (!fs.existsSync(ios)) return;
107
+ const P = names.pascalName;
108
+
109
+ renameIfExists(path.join(ios, SOURCE.pascalName), path.join(ios, P));
110
+ renameIfExists(
111
+ path.join(ios, `${SOURCE.pascalName}.xcodeproj`),
112
+ path.join(ios, `${P}.xcodeproj`)
113
+ );
114
+ renameIfExists(
115
+ path.join(ios, `${SOURCE.pascalName}.xcworkspace`),
116
+ path.join(ios, `${P}.xcworkspace`)
117
+ );
118
+
119
+ // The shared scheme lives inside the (now renamed) .xcodeproj.
120
+ const schemeDir = path.join(
121
+ ios,
122
+ `${P}.xcodeproj`,
123
+ "xcshareddata",
124
+ "xcschemes"
125
+ );
126
+ renameIfExists(
127
+ path.join(schemeDir, `${SOURCE.pascalName}.xcscheme`),
128
+ path.join(schemeDir, `${P}.xcscheme`)
129
+ );
130
+ }
131
+
132
+ /** Recursively remove a directory and any parents that become empty, up to `stopAt`. */
133
+ function pruneEmptyDirs(dir, stopAt) {
134
+ let cur = dir;
135
+ while (cur.startsWith(stopAt) && cur !== stopAt) {
136
+ if (fs.existsSync(cur) && fs.readdirSync(cur).length === 0) {
137
+ fs.rmdirSync(cur);
138
+ cur = path.dirname(cur);
139
+ } else {
140
+ break;
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Move the Android source from `…/java/com/awesomeproject` to the package path
147
+ * implied by the new bundle id (e.g. `…/java/com/acme/myapp`). The `package`
148
+ * declaration inside MainActivity/MainApplication was already rewritten by the
149
+ * token pass, so the folder layout just needs to follow.
150
+ */
151
+ function moveAndroidPackage(root, names) {
152
+ const javaRoot = path.join(root, "android", "app", "src", "main", "java");
153
+ const oldDir = path.join(javaRoot, ...bundleIdToPath(SOURCE.bundleId));
154
+ if (!fs.existsSync(oldDir)) return;
155
+
156
+ const newDir = path.join(javaRoot, ...bundleIdToPath(names.bundleId));
157
+ if (path.resolve(oldDir) === path.resolve(newDir)) return;
158
+
159
+ fs.mkdirSync(newDir, { recursive: true });
160
+ for (const entry of fs.readdirSync(oldDir)) {
161
+ fs.renameSync(path.join(oldDir, entry), path.join(newDir, entry));
162
+ }
163
+ pruneEmptyDirs(oldDir, javaRoot);
164
+ }
165
+
166
+ // ── targeted edits the token pass can't safely express ───────────────────────
167
+
168
+ function setJsonField(file, mutate) {
169
+ if (!fs.existsSync(file)) return;
170
+ const json = JSON.parse(fs.readFileSync(file, "utf8"));
171
+ mutate(json);
172
+ fs.writeFileSync(file, JSON.stringify(json, null, 2) + "\n");
173
+ }
174
+
175
+ function replaceInFile(file, re, value) {
176
+ if (!fs.existsSync(file)) return;
177
+ const content = fs.readFileSync(file, "utf8");
178
+ const next = content.replace(re, value);
179
+ if (next !== content) fs.writeFileSync(file, next);
180
+ }
181
+
182
+ /**
183
+ * Apply the human-facing values that differ from the canonical PascalCase name:
184
+ * • package.json `name` → npm slug (lowercase, hyphenated)
185
+ * • app.json name/displayName
186
+ * • Android strings.xml app_name → display name
187
+ * • iOS Info.plist CFBundleDisplayName → display name
188
+ */
189
+ function applyTargetedEdits(root, names) {
190
+ // package.json — npm names must be lowercase.
191
+ setJsonField(path.join(root, "package.json"), (pkg) => {
192
+ pkg.name = names.slug;
193
+ });
194
+
195
+ // app.json — `name` is the AppRegistry key (must equal getMainComponentName).
196
+ setJsonField(path.join(root, "app.json"), (app) => {
197
+ app.name = names.pascalName;
198
+ app.displayName = names.displayName;
199
+ });
200
+
201
+ // Android home-screen label.
202
+ replaceInFile(
203
+ path.join(root, "android/app/src/main/res/values/strings.xml"),
204
+ /(<string name="app_name">)[^<]*(<\/string>)/,
205
+ `$1${names.displayName}$2`
206
+ );
207
+
208
+ // iOS home-screen label.
209
+ replaceInFile(
210
+ path.join(root, "ios", names.pascalName, "Info.plist"),
211
+ /(<key>CFBundleDisplayName<\/key>\s*<string>)[^<]*(<\/string>)/,
212
+ `$1${names.displayName}$2`
213
+ );
214
+ }
215
+
216
+ /**
217
+ * Full rename pipeline. Order matters: rewrite file *contents* first (while the
218
+ * folders still sit at their source paths), then move the native folders, then
219
+ * apply the few targeted human-facing values.
220
+ *
221
+ * @returns {number} number of files whose contents changed in the token pass.
222
+ */
223
+ function renameProject(root, names) {
224
+ const changed = applyTokenReplacements(root, names);
225
+ renameIosDirs(root, names);
226
+ moveAndroidPackage(root, names);
227
+ applyTargetedEdits(root, names);
228
+ return changed;
229
+ }
230
+
231
+ module.exports = {
232
+ SOURCE,
233
+ isTextFile,
234
+ applyTokenReplacements,
235
+ renameIosDirs,
236
+ moveAndroidPackage,
237
+ applyTargetedEdits,
238
+ renameProject,
239
+ };