create-react-native-starter-kit 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +378 -2
- package/bin/create-react-native-starter-kit.js +95 -0
- package/lib/copy-src.js +127 -0
- package/lib/creator.js +211 -0
- package/lib/deps.js +58 -0
- package/lib/patch-configs.js +318 -0
- package/lib/patch-native.js +588 -0
- package/package.json +47 -4
- package/scripts/sync-template.js +52 -0
- package/template/.env.example +3 -0
- package/template/patches/react-native-element-dropdown+2.12.4.patch +67 -0
- package/template/src/app/App.tsx +25 -0
- package/template/src/app/navigation/root.tsx +24 -0
- package/template/src/app/navigation/stacks/app.stack.tsx +67 -0
- package/template/src/app/navigation/stacks/app.tab.tsx +72 -0
- package/template/src/app/navigation/stacks/auth.stack.tsx +56 -0
- package/template/src/app/providers.tsx +43 -0
- package/template/src/assets/fonts/InterTight-Black.ttf +0 -0
- package/template/src/assets/fonts/InterTight-BlackItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Bold.ttf +0 -0
- package/template/src/assets/fonts/InterTight-BoldItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraBold.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraBoldItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraLight.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraLightItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Italic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Light.ttf +0 -0
- package/template/src/assets/fonts/InterTight-LightItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Medium.ttf +0 -0
- package/template/src/assets/fonts/InterTight-MediumItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Regular.ttf +0 -0
- package/template/src/assets/fonts/InterTight-SemiBold.ttf +0 -0
- package/template/src/assets/fonts/InterTight-SemiBoldItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Thin.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ThinItalic.ttf +0 -0
- package/template/src/assets/icons/back.svg +3 -0
- package/template/src/assets/icons/calendar.svg +3 -0
- package/template/src/assets/icons/claim_upload.svg +3 -0
- package/template/src/assets/icons/clock.svg +1 -0
- package/template/src/assets/icons/downArrow.svg +3 -0
- package/template/src/assets/icons/edit.svg +3 -0
- package/template/src/assets/icons/eye.svg +4 -0
- package/template/src/assets/icons/eyeClose.svg +7 -0
- package/template/src/assets/icons/folder.svg +5 -0
- package/template/src/assets/icons/home.svg +4 -0
- package/template/src/assets/icons/index.ts +35 -0
- package/template/src/assets/icons/log_out.svg +4 -0
- package/template/src/assets/icons/setting.svg +4 -0
- package/template/src/assets/icons/tabs/home/filled_dark.svg +3 -0
- package/template/src/assets/icons/tabs/home/filled_light.svg +3 -0
- package/template/src/assets/icons/tabs/home/outline.svg +4 -0
- package/template/src/assets/icons/tabs/index.ts +31 -0
- package/template/src/assets/icons/tabs/profile/filled_dark.svg +6 -0
- package/template/src/assets/icons/tabs/profile/filled_light.svg +6 -0
- package/template/src/assets/icons/tabs/profile/outline.svg +3 -0
- package/template/src/assets/icons/tooltip_dark.svg +1 -0
- package/template/src/assets/icons/tooltip_light.svg +1 -0
- package/template/src/assets/icons/upArrow.svg +3 -0
- package/template/src/assets/images/back.png +0 -0
- package/template/src/assets/images/index.ts +7 -0
- package/template/src/assets/images/placeholder/profile.png +0 -0
- package/template/src/components/shared/AppImage.tsx +190 -0
- package/template/src/components/shared/AvatarPicker.tsx +175 -0
- package/template/src/components/shared/CustomButton.tsx +273 -0
- package/template/src/components/shared/DocumentPicker.tsx +86 -0
- package/template/src/components/shared/GlobalLoader.tsx +47 -0
- package/template/src/components/shared/Icon.tsx +28 -0
- package/template/src/components/shared/PressableIcon.tsx +104 -0
- package/template/src/components/shared/bottom-sheet/BaseBottomSheet.tsx +30 -0
- package/template/src/components/shared/bottom-sheet/BaseBottomSheetModal.tsx +50 -0
- package/template/src/components/shared/bottom-sheet/sheets/ImagePickerSheet.tsx +310 -0
- package/template/src/components/shared/bottom-sheet/types.ts +4 -0
- package/template/src/components/shared/form-input/FormInput.tsx +140 -0
- package/template/src/components/shared/form-input/FormInputRenderer.tsx +82 -0
- package/template/src/components/shared/form-input/FullNameInput.tsx +243 -0
- package/template/src/components/shared/form-input/formFieldStyles.ts +43 -0
- package/template/src/components/shared/form-input/type.ts +188 -0
- package/template/src/components/shared/form-input/variants/CountryPhoneVariant.tsx +289 -0
- package/template/src/components/shared/form-input/variants/DateTimeVariant.tsx +280 -0
- package/template/src/components/shared/form-input/variants/DropdownVariant.tsx +221 -0
- package/template/src/components/shared/form-input/variants/TextInputVariant.tsx +300 -0
- package/template/src/components/shared/form-input/variants/ToggleVariant.tsx +194 -0
- package/template/src/components/shared/modal/BaseModal.tsx +83 -0
- package/template/src/components/shared/modal/common/AlertModal.tsx +173 -0
- package/template/src/components/shared/navigation/AnimatedTabIcon.tsx +37 -0
- package/template/src/components/shared/navigation/HeaderActionButton.tsx +70 -0
- package/template/src/components/shared/navigation/HeaderBackButton.tsx +38 -0
- package/template/src/components/shared/navigation/StackHeader.tsx +105 -0
- package/template/src/components/shared/navigation/tabIconRender.tsx +14 -0
- package/template/src/components/shared/offline/OfflineQueryBoundary.tsx +25 -0
- package/template/src/components/shared/offline/OfflineState.tsx +229 -0
- package/template/src/components/shared/offline/index.ts +6 -0
- package/template/src/components/shared/offline/types.ts +15 -0
- package/template/src/components/shared/offline/wifi-icon-animation/BrokenWifiAnimation.tsx +116 -0
- package/template/src/components/shared/offline/wifi-icon-animation/WifiPiece.tsx +80 -0
- package/template/src/components/shared/offline/wifi-icon-animation/geometry.ts +163 -0
- package/template/src/components/shared/offline/wifi-icon-animation/useWifiIconAnimationActive.ts +37 -0
- package/template/src/components/shared/skelton/BaseSkelton.tsx +71 -0
- package/template/src/components/shared/skelton/ShimmerHolder.ts +6 -0
- package/template/src/components/shared/skelton/holders/HomeShimmer.tsx +25 -0
- package/template/src/components/shared/skelton/holders/index.ts +1 -0
- package/template/src/components/shared/skelton/layouts/avatar.layout.ts +18 -0
- package/template/src/components/shared/skelton/layouts/home.layout.ts +16 -0
- package/template/src/components/shared/skelton/layouts/index.ts +3 -0
- package/template/src/components/shared/skelton/layouts/text.layout.ts +18 -0
- package/template/src/components/shared/text/CustomText.tsx +85 -0
- package/template/src/components/shared/text/ShimmerText.tsx +159 -0
- package/template/src/components/shared/text/types.ts +40 -0
- package/template/src/constants/device.ts +12 -0
- package/template/src/constants/endpoint.ts +11 -0
- package/template/src/constants/keyExtractor.ts +6 -0
- package/template/src/constants/limits.ts +9 -0
- package/template/src/constants/queryKey.ts +3 -0
- package/template/src/constants/regex.ts +7 -0
- package/template/src/context/NetworkContext.tsx +102 -0
- package/template/src/context/ThemeContext.tsx +56 -0
- package/template/src/hooks/app/index.ts +2 -0
- package/template/src/hooks/app/useGetProfile.ts +38 -0
- package/template/src/hooks/app/useUpdateProfile.ts +21 -0
- package/template/src/hooks/useAppDispatch.ts +6 -0
- package/template/src/hooks/useAppSelector.ts +4 -0
- package/template/src/hooks/useDebounce.ts +15 -0
- package/template/src/hooks/useDocumentPicker.ts +102 -0
- package/template/src/hooks/useImagePicker.ts +83 -0
- package/template/src/i18n/i18next.d.ts +9 -0
- package/template/src/i18n/index.ts +16 -0
- package/template/src/i18n/locales/en.json +274 -0
- package/template/src/i18n/locales/ru.json +165 -0
- package/template/src/i18n/resources.ts +11 -0
- package/template/src/redux/selectors/auth.selectors.ts +4 -0
- package/template/src/redux/slice/auth.slice.ts +23 -0
- package/template/src/redux/slice/loader.slice.ts +25 -0
- package/template/src/redux/slice/settings.slice.ts +26 -0
- package/template/src/redux/slice/user.slice.ts +25 -0
- package/template/src/screens/app/main-tab/index.tsx +73 -0
- package/template/src/screens/app/main-tab/style.ts +0 -0
- package/template/src/screens/app/profile-setup/index.tsx +162 -0
- package/template/src/screens/app/profile-setup/styles.ts +3 -0
- package/template/src/screens/auth/forgot-password/index.tsx +59 -0
- package/template/src/screens/auth/forgot-password/styles.ts +0 -0
- package/template/src/screens/auth/index.ts +4 -0
- package/template/src/screens/auth/login/index.tsx +138 -0
- package/template/src/screens/auth/login/styles.ts +3 -0
- package/template/src/screens/auth/signup/index.tsx +111 -0
- package/template/src/screens/auth/signup/styles.ts +3 -0
- package/template/src/screens/auth/verify-email/index.tsx +15 -0
- package/template/src/screens/auth/verify-email/styles.ts +3 -0
- package/template/src/screens/tab/home/index.tsx +49 -0
- package/template/src/screens/tab/home/styles.ts +3 -0
- package/template/src/screens/tab/index.ts +2 -0
- package/template/src/screens/tab/profile/index.tsx +50 -0
- package/template/src/screens/tab/profile/styles.ts +3 -0
- package/template/src/services/api/auth/auth.api.ts +22 -0
- package/template/src/services/api/auth/auth.endpoints.ts +7 -0
- package/template/src/services/api/auth/auth.types.ts +9 -0
- package/template/src/services/api/auth/index.ts +3 -0
- package/template/src/services/api/axios/axios.instance.ts +15 -0
- package/template/src/services/api/axios/axios.interceptors.ts +43 -0
- package/template/src/services/api/axios/index.ts +2 -0
- package/template/src/services/api/mock/mockAdapter.ts +97 -0
- package/template/src/services/api/user/index.ts +2 -0
- package/template/src/services/api/user/user.api.ts +67 -0
- package/template/src/services/api/user/user.endpoints.ts +0 -0
- package/template/src/services/api/user/user.types.ts +0 -0
- package/template/src/services/errors/backendErrorCodes.ts +7 -0
- package/template/src/services/errors/errorHandler.ts +94 -0
- package/template/src/services/errors/firebaseErrorMap.ts +61 -0
- package/template/src/services/errors/httpStatusMap.ts +47 -0
- package/template/src/services/errors/index.ts +4 -0
- package/template/src/services/network/index.ts +1 -0
- package/template/src/services/network/onlineManager.ts +22 -0
- package/template/src/services/query/index.ts +1 -0
- package/template/src/services/query/queryClient.ts +23 -0
- package/template/src/services/toast/ToastCard.tsx +137 -0
- package/template/src/services/toast/ToastConfig.tsx +32 -0
- package/template/src/services/toast/index.ts +3 -0
- package/template/src/services/toast/toast.ts +68 -0
- package/template/src/storage/mmkvAdapter.ts +3 -0
- package/template/src/storage/mmkvInstance.ts +16 -0
- package/template/src/store/index.ts +15 -0
- package/template/src/store/mmkv.ts +6 -0
- package/template/src/store/persist.ts +10 -0
- package/template/src/store/rootReducer.ts +26 -0
- package/template/src/theme/dark.ts +40 -0
- package/template/src/theme/fonts.ts +20 -0
- package/template/src/theme/index.ts +6 -0
- package/template/src/theme/layout.ts +23 -0
- package/template/src/theme/light.ts +41 -0
- package/template/src/theme/palette.ts +32 -0
- package/template/src/theme/spacing.ts +7 -0
- package/template/src/theme/typography.ts +80 -0
- package/template/src/types/api.types.ts +34 -0
- package/template/src/types/env.d.ts +6 -0
- package/template/src/types/index.ts +2 -0
- package/template/src/types/navigation.types.ts +94 -0
- package/template/src/utils/clearAppSession.ts +10 -0
- package/template/src/utils/date.ts +19 -0
- package/template/src/utils/documentPickerHandler.ts +96 -0
- package/template/src/utils/getFileNameFromUri.ts +18 -0
- package/template/src/utils/getInitials.ts +13 -0
- package/template/src/utils/hindiText.ts +33 -0
- package/template/src/utils/imageCompress.ts +97 -0
- package/template/src/utils/imagePickerHandler.ts +200 -0
- package/template/src/utils/maskPhone.ts +17 -0
- package/template/src/utils/normalize.ts +12 -0
- package/template/src/validation/messages.ts +11 -0
- package/template/src/validation/rules.ts +44 -0
- package/template/src/validation/schema/auth.schema.ts +53 -0
- package/template/src/validation/schema/user.schema.ts +46 -0
- package/template/src/validation/tValError.ts +13 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find files recursively matching a pattern or name
|
|
7
|
+
*/
|
|
8
|
+
function findFile(dir, fileName) {
|
|
9
|
+
if (!fs.existsSync(dir)) return null;
|
|
10
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
const fullPath = path.join(dir, entry.name);
|
|
13
|
+
if (entry.isDirectory()) {
|
|
14
|
+
if (entry.name === 'node_modules' || entry.name === 'Pods' || entry.name === 'build') continue;
|
|
15
|
+
const found = findFile(fullPath, fileName);
|
|
16
|
+
if (found) return found;
|
|
17
|
+
} else if (entry.name === fileName) {
|
|
18
|
+
return fullPath;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Patch android/app/build.gradle
|
|
26
|
+
*/
|
|
27
|
+
function patchAndroidBuildGradle(projectDir) {
|
|
28
|
+
const gradlePath = path.join(projectDir, 'android', 'app', 'build.gradle');
|
|
29
|
+
if (!fs.existsSync(gradlePath)) {
|
|
30
|
+
console.log('⚠️ Could not find android/app/build.gradle');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let content = fs.readFileSync(gradlePath, 'utf8');
|
|
35
|
+
|
|
36
|
+
// Check if androidx.activity is already added
|
|
37
|
+
if (!content.includes('androidx.activity:activity')) {
|
|
38
|
+
const target = 'dependencies {';
|
|
39
|
+
const injection = `dependencies {
|
|
40
|
+
implementation("androidx.activity:activity:1.+") //by own`;
|
|
41
|
+
if (content.includes(target)) {
|
|
42
|
+
content = content.replace(target, injection);
|
|
43
|
+
fs.writeFileSync(gradlePath, content, 'utf8');
|
|
44
|
+
console.log('✅ Patched android/app/build.gradle');
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Patch MainActivity.kt
|
|
53
|
+
*/
|
|
54
|
+
function patchAndroidMainActivity(projectDir) {
|
|
55
|
+
const javaDir = path.join(projectDir, 'android', 'app', 'src', 'main', 'java');
|
|
56
|
+
const mainActivityPath = findFile(javaDir, 'MainActivity.kt');
|
|
57
|
+
|
|
58
|
+
if (!mainActivityPath) {
|
|
59
|
+
console.log('⚠️ Could not find MainActivity.kt');
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let content = fs.readFileSync(mainActivityPath, 'utf8');
|
|
64
|
+
|
|
65
|
+
const requiredImports = [
|
|
66
|
+
'android.os.Bundle',
|
|
67
|
+
'com.facebook.react.ReactApplication',
|
|
68
|
+
'com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory',
|
|
69
|
+
].filter(name => !new RegExp(`^import\\s+${name.replace(/\./g, '\\.')}\\s*$`, 'm').test(content));
|
|
70
|
+
|
|
71
|
+
if (requiredImports.length > 0) {
|
|
72
|
+
const importBlock = `//By Own\n${requiredImports.map(name => `import ${name}`).join('\n')}\n`;
|
|
73
|
+
// Match only the package line itself so the inserted block always ends on its own line.
|
|
74
|
+
const packageLineRegex = /^package\s+[\w.]+[ \t]*\r?\n/m;
|
|
75
|
+
if (packageLineRegex.test(content)) {
|
|
76
|
+
content = content.replace(packageLineRegex, match => `${match}\n${importBlock}`);
|
|
77
|
+
} else {
|
|
78
|
+
content = `${importBlock}\n${content}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const activityHooks = `
|
|
83
|
+
//By Own
|
|
84
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
85
|
+
supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
|
|
86
|
+
super.onCreate(savedInstanceState)
|
|
87
|
+
reloadIfDisplaySizeChanged()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Changing "Display size" recreates this activity but keeps the JS runtime alive, so sizes
|
|
92
|
+
* computed once at JS load (normalize(), screen-width constants) stay stale and layouts break.
|
|
93
|
+
* Reloading the bundle recomputes them for the new density.
|
|
94
|
+
*/
|
|
95
|
+
private fun reloadIfDisplaySizeChanged() {
|
|
96
|
+
val densityDpi = resources.configuration.densityDpi
|
|
97
|
+
if (lastDensityDpi != 0 && lastDensityDpi != densityDpi) {
|
|
98
|
+
(application as ReactApplication).reactHost?.reload("Display size changed")
|
|
99
|
+
}
|
|
100
|
+
lastDensityDpi = densityDpi
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//By Own
|
|
104
|
+
private companion object {
|
|
105
|
+
/** Survives activity recreation (same process); 0 means first launch. */
|
|
106
|
+
var lastDensityDpi = 0
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
if (!content.includes('reloadIfDisplaySizeChanged')) {
|
|
111
|
+
const classRegex = /(class\s+MainActivity\s*:\s*ReactActivity\(\)\s*\{)/;
|
|
112
|
+
if (classRegex.test(content)) {
|
|
113
|
+
content = content.replace(classRegex, `$1${activityHooks}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(mainActivityPath, content, 'utf8');
|
|
118
|
+
console.log('✅ Patched MainActivity.kt');
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Patch AndroidManifest.xml
|
|
124
|
+
*/
|
|
125
|
+
function patchAndroidManifest(projectDir) {
|
|
126
|
+
const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
127
|
+
if (!fs.existsSync(manifestPath)) {
|
|
128
|
+
console.log('⚠️ Could not find AndroidManifest.xml');
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let content = fs.readFileSync(manifestPath, 'utf8');
|
|
133
|
+
let modified = false;
|
|
134
|
+
|
|
135
|
+
if (!content.includes('xmlns:tools')) {
|
|
136
|
+
content = content.replace(
|
|
137
|
+
'<manifest xmlns:android="http://schemas.android.com/apk/res/android"',
|
|
138
|
+
'<manifest xmlns:android="http://schemas.android.com/apk/res/android"\n xmlns:tools="http://schemas.android.com/tools"',
|
|
139
|
+
);
|
|
140
|
+
modified = true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!content.includes('android.permission.CAMERA')) {
|
|
144
|
+
const permissionBlock = ` <uses-permission android:name="android.permission.CAMERA" />
|
|
145
|
+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
|
146
|
+
<uses-permission
|
|
147
|
+
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
148
|
+
android:maxSdkVersion="32" />
|
|
149
|
+
<!-- RN debug adds this for Metro over LAN; use adb reverse instead. -->
|
|
150
|
+
<uses-permission
|
|
151
|
+
android:name="android.permission.ACCESS_LOCAL_NETWORK"
|
|
152
|
+
tools:node="remove" />
|
|
153
|
+
|
|
154
|
+
`;
|
|
155
|
+
if (content.includes('<application')) {
|
|
156
|
+
content = content.replace('<application', `${permissionBlock}<application`);
|
|
157
|
+
modified = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!content.includes('enableOnBackInvokedCallback')) {
|
|
162
|
+
content = content.replace(
|
|
163
|
+
'<application',
|
|
164
|
+
'<application\n android:enableOnBackInvokedCallback="false"',
|
|
165
|
+
);
|
|
166
|
+
modified = true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
content.includes('<activity') &&
|
|
171
|
+
!content.includes('android:screenOrientation=')
|
|
172
|
+
) {
|
|
173
|
+
content = content.replace(
|
|
174
|
+
'<activity',
|
|
175
|
+
'<activity\n android:screenOrientation="portrait"',
|
|
176
|
+
);
|
|
177
|
+
modified = true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (modified) {
|
|
181
|
+
fs.writeFileSync(manifestPath, content, 'utf8');
|
|
182
|
+
console.log('✅ Patched AndroidManifest.xml');
|
|
183
|
+
}
|
|
184
|
+
return modified;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get all font files from src/assets/fonts
|
|
189
|
+
*/
|
|
190
|
+
function getFontFiles(projectDir) {
|
|
191
|
+
const fontsDir = path.join(projectDir, 'src', 'assets', 'fonts');
|
|
192
|
+
if (!fs.existsSync(fontsDir)) return [];
|
|
193
|
+
return fs.readdirSync(fontsDir).filter(f => f.endsWith('.ttf') || f.endsWith('.otf'));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Patch iOS Info.plist
|
|
198
|
+
*/
|
|
199
|
+
function patchIOSInfoPlist(projectDir) {
|
|
200
|
+
const iosDir = path.join(projectDir, 'ios');
|
|
201
|
+
const infoPlistPath = findFile(iosDir, 'Info.plist');
|
|
202
|
+
|
|
203
|
+
if (!infoPlistPath) {
|
|
204
|
+
console.log('⚠️ Could not find iOS Info.plist');
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let content = fs.readFileSync(infoPlistPath, 'utf8');
|
|
209
|
+
let modified = false;
|
|
210
|
+
|
|
211
|
+
// Add Photo & Camera Permissions
|
|
212
|
+
const permissionsToAdd = [];
|
|
213
|
+
if (!content.includes('NSPhotoLibraryUsageDescription')) {
|
|
214
|
+
permissionsToAdd.push(`\t<key>NSPhotoLibraryUsageDescription</key>
|
|
215
|
+
\t<string>We need access to your photo gallery to update your profile picture.</string>`);
|
|
216
|
+
}
|
|
217
|
+
if (!content.includes('NSCameraUsageDescription')) {
|
|
218
|
+
permissionsToAdd.push(`\t<key>NSCameraUsageDescription</key>
|
|
219
|
+
\t<string>We need access to your camera to take photos for your profile picture.</string>`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Add UIAppFonts
|
|
223
|
+
const fonts = getFontFiles(projectDir);
|
|
224
|
+
if (fonts.length > 0 && !content.includes('<key>UIAppFonts</key>')) {
|
|
225
|
+
const fontStrings = fonts.map(font => `\t\t<string>${font}</string>`).join('\n');
|
|
226
|
+
permissionsToAdd.push(`\t<key>UIAppFonts</key>
|
|
227
|
+
\t<array>
|
|
228
|
+
${fontStrings}
|
|
229
|
+
\t</array>`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (content.includes('UIInterfaceOrientationLandscapeLeft')) {
|
|
233
|
+
content = content.replace(
|
|
234
|
+
/<key>UISupportedInterfaceOrientations<\/key>\s*<array>[\s\S]*?<\/array>/,
|
|
235
|
+
`<key>UISupportedInterfaceOrientations</key>
|
|
236
|
+
<array>
|
|
237
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
238
|
+
</array>`,
|
|
239
|
+
);
|
|
240
|
+
modified = true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (permissionsToAdd.length > 0) {
|
|
244
|
+
const insertion = permissionsToAdd.join('\n') + '\n</dict>';
|
|
245
|
+
content = content.replace(/<\/dict>\s*<\/plist>/, `${insertion}\n</plist>`);
|
|
246
|
+
modified = true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (modified) {
|
|
250
|
+
fs.writeFileSync(infoPlistPath, content, 'utf8');
|
|
251
|
+
console.log('✅ Patched iOS Info.plist with permissions and custom fonts');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return modified;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const SCENE_MANIFEST = `\t<key>UIApplicationSceneManifest</key>
|
|
258
|
+
\t<dict>
|
|
259
|
+
\t\t<key>UIApplicationSupportsMultipleScenes</key>
|
|
260
|
+
\t\t<false/>
|
|
261
|
+
\t\t<key>UISceneConfigurations</key>
|
|
262
|
+
\t\t<dict>
|
|
263
|
+
\t\t\t<key>UIWindowSceneSessionRoleApplication</key>
|
|
264
|
+
\t\t\t<array>
|
|
265
|
+
\t\t\t\t<dict>
|
|
266
|
+
\t\t\t\t\t<key>UISceneConfigurationName</key>
|
|
267
|
+
\t\t\t\t\t<string>Default Configuration</string>
|
|
268
|
+
\t\t\t\t\t<key>UISceneDelegateClassName</key>
|
|
269
|
+
\t\t\t\t\t<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
|
270
|
+
\t\t\t\t</dict>
|
|
271
|
+
\t\t\t</array>
|
|
272
|
+
\t\t</dict>
|
|
273
|
+
\t</dict>`;
|
|
274
|
+
|
|
275
|
+
// Same shape as the current react-native-community/template, limited to APIs that exist in
|
|
276
|
+
// React Native 0.87 (no connectionOptions overload and no scene-based RCTLinkingManager helpers).
|
|
277
|
+
const APP_DELEGATE_SOURCE = `import UIKit
|
|
278
|
+
|
|
279
|
+
@main
|
|
280
|
+
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
281
|
+
func application(
|
|
282
|
+
_ application: UIApplication,
|
|
283
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
|
284
|
+
) -> Bool {
|
|
285
|
+
true
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
`;
|
|
289
|
+
|
|
290
|
+
const sceneDelegateSource = moduleName => `import React
|
|
291
|
+
import React_RCTAppDelegate
|
|
292
|
+
import ReactAppDependencyProvider
|
|
293
|
+
import UIKit
|
|
294
|
+
|
|
295
|
+
class SceneDelegate: RCTDefaultReactNativeFactoryDelegate, UIWindowSceneDelegate {
|
|
296
|
+
var window: UIWindow?
|
|
297
|
+
var reactNativeFactory: RCTReactNativeFactory?
|
|
298
|
+
|
|
299
|
+
func scene(
|
|
300
|
+
_ scene: UIScene,
|
|
301
|
+
willConnectTo session: UISceneSession,
|
|
302
|
+
options connectionOptions: UIScene.ConnectionOptions
|
|
303
|
+
) {
|
|
304
|
+
guard let windowScene = scene as? UIWindowScene else {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
dependencyProvider = RCTAppDependencyProvider()
|
|
309
|
+
reactNativeFactory = RCTReactNativeFactory(delegate: self)
|
|
310
|
+
window = UIWindow(windowScene: windowScene)
|
|
311
|
+
|
|
312
|
+
var launchOptions: [AnyHashable: Any] = [:]
|
|
313
|
+
if let url = connectionOptions.urlContexts.first?.url {
|
|
314
|
+
launchOptions[UIApplication.LaunchOptionsKey.url] = url
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
reactNativeFactory?.startReactNative(
|
|
318
|
+
withModuleName: "${moduleName}",
|
|
319
|
+
in: window,
|
|
320
|
+
launchOptions: launchOptions
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
|
325
|
+
guard let url = URLContexts.first?.url else {
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
RCTLinkingManager.application(UIApplication.shared, open: url, options: [:])
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
|
|
332
|
+
RCTLinkingManager.application(
|
|
333
|
+
UIApplication.shared,
|
|
334
|
+
continue: userActivity,
|
|
335
|
+
restorationHandler: { _ in }
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
|
340
|
+
self.bundleURL()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
override func bundleURL() -> URL? {
|
|
344
|
+
#if DEBUG
|
|
345
|
+
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
346
|
+
#else
|
|
347
|
+
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
348
|
+
#endif
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
`;
|
|
352
|
+
|
|
353
|
+
/** Xcode object IDs are 24 uppercase hex chars and only need to be unique within the pbxproj. */
|
|
354
|
+
function newPbxprojId(pbxproj, taken = []) {
|
|
355
|
+
for (;;) {
|
|
356
|
+
const id = crypto.randomBytes(12).toString('hex').toUpperCase();
|
|
357
|
+
if (!pbxproj.includes(id) && !taken.includes(id)) return id;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function findXcodeProject(iosDir) {
|
|
362
|
+
if (!fs.existsSync(iosDir)) return null;
|
|
363
|
+
const projectDir = fs
|
|
364
|
+
.readdirSync(iosDir)
|
|
365
|
+
.find(entry => entry.endsWith('.xcodeproj') && entry !== 'Pods.xcodeproj');
|
|
366
|
+
if (!projectDir) return null;
|
|
367
|
+
const pbxprojPath = path.join(iosDir, projectDir, 'project.pbxproj');
|
|
368
|
+
return fs.existsSync(pbxprojPath) ? pbxprojPath : null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Add SceneDelegate.swift next to every AppDelegate.swift entry (file reference, group, Sources
|
|
373
|
+
* phase). Returns null when the project does not have the layout the template generates.
|
|
374
|
+
*/
|
|
375
|
+
function addSceneDelegateToPbxproj(pbxproj, appFolder) {
|
|
376
|
+
if (pbxproj.includes('SceneDelegate.swift')) return pbxproj;
|
|
377
|
+
|
|
378
|
+
const buildFileLine = /^([ \t]*)(\w+) \/\* AppDelegate\.swift in Sources \*\/ = \{isa = PBXBuildFile; fileRef = (\w+) \/\* AppDelegate\.swift \*\/; \};$/m;
|
|
379
|
+
const buildFile = pbxproj.match(buildFileLine);
|
|
380
|
+
if (!buildFile) return null;
|
|
381
|
+
const [, indent, appBuildId, appRefId] = buildFile;
|
|
382
|
+
|
|
383
|
+
const fileRefLine = new RegExp(`^[ \\t]*${appRefId} /\\* AppDelegate\\.swift \\*/ = \\{isa = PBXFileReference;.*\\};$`, 'm');
|
|
384
|
+
const groupChild = new RegExp(`^([ \\t]*)${appRefId} /\\* AppDelegate\\.swift \\*/,$`, 'm');
|
|
385
|
+
const sourcesEntry = new RegExp(`^([ \\t]*)${appBuildId} /\\* AppDelegate\\.swift in Sources \\*/,$`, 'm');
|
|
386
|
+
if (!fileRefLine.test(pbxproj) || !groupChild.test(pbxproj) || !sourcesEntry.test(pbxproj)) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const sceneFileRefId = newPbxprojId(pbxproj);
|
|
391
|
+
const sceneBuildFileId = newPbxprojId(pbxproj, [sceneFileRefId]);
|
|
392
|
+
|
|
393
|
+
return pbxproj
|
|
394
|
+
.replace(
|
|
395
|
+
buildFileLine,
|
|
396
|
+
match =>
|
|
397
|
+
`${match}\n${indent}${sceneBuildFileId} /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ${sceneFileRefId} /* SceneDelegate.swift */; };`,
|
|
398
|
+
)
|
|
399
|
+
.replace(
|
|
400
|
+
fileRefLine,
|
|
401
|
+
match =>
|
|
402
|
+
`${match}\n${indent}${sceneFileRefId} /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = ${appFolder}/SceneDelegate.swift; sourceTree = "<group>"; };`,
|
|
403
|
+
)
|
|
404
|
+
.replace(groupChild, (match, childIndent) => `${match}\n${childIndent}${sceneFileRefId} /* SceneDelegate.swift */,`)
|
|
405
|
+
.replace(
|
|
406
|
+
sourcesEntry,
|
|
407
|
+
(match, entryIndent) => `${match}\n${entryIndent}${sceneBuildFileId} /* SceneDelegate.swift in Sources */,`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Bring an AppDelegate-only iOS project (React Native <= 0.87) to the scene-based layout of the
|
|
413
|
+
* current template: SceneDelegate.swift owns the window, AppDelegate is empty, Info.plist declares
|
|
414
|
+
* the scene. Newer projects that already ship SceneDelegate.swift are left as generated.
|
|
415
|
+
*/
|
|
416
|
+
function patchIOSSceneLifecycle(projectDir) {
|
|
417
|
+
const iosDir = path.join(projectDir, 'ios');
|
|
418
|
+
const appDelegatePath = findFile(iosDir, 'AppDelegate.swift');
|
|
419
|
+
if (!appDelegatePath) {
|
|
420
|
+
console.log('⚠️ Could not find AppDelegate.swift, skipped UIScene setup');
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const appDir = path.dirname(appDelegatePath);
|
|
425
|
+
const sceneDelegatePath = path.join(appDir, 'SceneDelegate.swift');
|
|
426
|
+
if (fs.existsSync(sceneDelegatePath)) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const appDelegate = fs.readFileSync(appDelegatePath, 'utf8');
|
|
431
|
+
const moduleName = appDelegate.match(/withModuleName:\s*"([^"]+)"/);
|
|
432
|
+
const infoPlistPath = path.join(appDir, 'Info.plist');
|
|
433
|
+
const pbxprojPath = findXcodeProject(iosDir);
|
|
434
|
+
const pbxproj = pbxprojPath && addSceneDelegateToPbxproj(fs.readFileSync(pbxprojPath, 'utf8'), path.basename(appDir));
|
|
435
|
+
|
|
436
|
+
if (!moduleName || !appDelegate.includes('RCTReactNativeFactory') || !fs.existsSync(infoPlistPath) || !pbxproj) {
|
|
437
|
+
console.log('⚠️ iOS project does not match the React Native template, skipped UIScene setup');
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let infoPlist = fs.readFileSync(infoPlistPath, 'utf8');
|
|
442
|
+
if (!infoPlist.includes('UIApplicationSceneManifest')) {
|
|
443
|
+
infoPlist = infoPlist.replace(/<\/dict>\s*<\/plist>\s*$/, `${SCENE_MANIFEST}\n</dict>\n</plist>\n`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
fs.writeFileSync(sceneDelegatePath, sceneDelegateSource(moduleName[1]), 'utf8');
|
|
447
|
+
fs.writeFileSync(appDelegatePath, APP_DELEGATE_SOURCE, 'utf8');
|
|
448
|
+
fs.writeFileSync(infoPlistPath, infoPlist, 'utf8');
|
|
449
|
+
fs.writeFileSync(pbxprojPath, pbxproj, 'utf8');
|
|
450
|
+
|
|
451
|
+
console.log('✅ Moved iOS app startup to SceneDelegate.swift (UIScene life cycle)');
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Keep CocoaPods on the same iOS minimum as the app target.
|
|
457
|
+
*/
|
|
458
|
+
function patchIOSPodfile(projectDir) {
|
|
459
|
+
const podfilePath = path.join(projectDir, 'ios', 'Podfile');
|
|
460
|
+
if (!fs.existsSync(podfilePath)) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let content = fs.readFileSync(podfilePath, 'utf8');
|
|
465
|
+
if (content.includes("min_ios = '15.1'")) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const marker = 'react_native_post_install(';
|
|
470
|
+
const start = content.indexOf(marker);
|
|
471
|
+
if (start === -1) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const callEnd = content.indexOf('\n )', start);
|
|
476
|
+
if (callEnd === -1) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const injection = `
|
|
481
|
+
# Force every pod (including resource bundles) to match the app's
|
|
482
|
+
# iOS 15.1 minimum. Without this, some pods stay on 9.0 and Xcode fails.
|
|
483
|
+
min_ios = '15.1'
|
|
484
|
+
installer.pods_project.build_configurations.each do |config|
|
|
485
|
+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = min_ios
|
|
486
|
+
end
|
|
487
|
+
installer.pods_project.targets.each do |target|
|
|
488
|
+
target.build_configurations.each do |config|
|
|
489
|
+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = min_ios
|
|
490
|
+
end
|
|
491
|
+
end`;
|
|
492
|
+
|
|
493
|
+
content =
|
|
494
|
+
content.slice(0, callEnd + '\n )'.length) +
|
|
495
|
+
injection +
|
|
496
|
+
content.slice(callEnd + '\n )'.length);
|
|
497
|
+
fs.writeFileSync(podfilePath, content, 'utf8');
|
|
498
|
+
console.log('✅ Patched ios/Podfile deployment target');
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* The wrapper default (10s) drops the Gradle distribution download on slow networks.
|
|
504
|
+
*/
|
|
505
|
+
function patchGradleWrapper(projectDir) {
|
|
506
|
+
const wrapperPath = path.join(
|
|
507
|
+
projectDir,
|
|
508
|
+
'android',
|
|
509
|
+
'gradle',
|
|
510
|
+
'wrapper',
|
|
511
|
+
'gradle-wrapper.properties',
|
|
512
|
+
);
|
|
513
|
+
if (!fs.existsSync(wrapperPath)) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const timeoutMs = 180000;
|
|
518
|
+
let content = fs.readFileSync(wrapperPath, 'utf8');
|
|
519
|
+
const match = content.match(/^networkTimeout=(\d+)\s*$/m);
|
|
520
|
+
|
|
521
|
+
if (match && Number(match[1]) >= timeoutMs) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (match) {
|
|
526
|
+
content = content.replace(
|
|
527
|
+
/^networkTimeout=\d+\s*$/m,
|
|
528
|
+
`networkTimeout=${timeoutMs}`,
|
|
529
|
+
);
|
|
530
|
+
} else if (content.includes('distributionUrl=')) {
|
|
531
|
+
content = content.replace(
|
|
532
|
+
/^(distributionUrl=.*)$/m,
|
|
533
|
+
`$1\nnetworkTimeout=${timeoutMs}`,
|
|
534
|
+
);
|
|
535
|
+
} else {
|
|
536
|
+
content = `${content.replace(/\s*$/, '')}\nnetworkTimeout=${timeoutMs}\n`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
fs.writeFileSync(wrapperPath, content, 'utf8');
|
|
540
|
+
console.log('✅ Patched gradle-wrapper.properties (networkTimeout)');
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Draw behind system bars. Screens already use safe area insets.
|
|
546
|
+
*/
|
|
547
|
+
function patchGradleProperties(projectDir) {
|
|
548
|
+
const propsPath = path.join(projectDir, 'android', 'gradle.properties');
|
|
549
|
+
if (!fs.existsSync(propsPath)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let content = fs.readFileSync(propsPath, 'utf8');
|
|
554
|
+
if (!content.includes('edgeToEdgeEnabled=false')) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
content = content.replace('edgeToEdgeEnabled=false', 'edgeToEdgeEnabled=true');
|
|
559
|
+
fs.writeFileSync(propsPath, content, 'utf8');
|
|
560
|
+
console.log('✅ Patched android/gradle.properties (edgeToEdgeEnabled)');
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Run all native patches
|
|
566
|
+
*/
|
|
567
|
+
function patchAllNative(projectDir) {
|
|
568
|
+
patchAndroidBuildGradle(projectDir);
|
|
569
|
+
patchAndroidMainActivity(projectDir);
|
|
570
|
+
patchAndroidManifest(projectDir);
|
|
571
|
+
patchGradleWrapper(projectDir);
|
|
572
|
+
patchGradleProperties(projectDir);
|
|
573
|
+
patchIOSInfoPlist(projectDir);
|
|
574
|
+
patchIOSSceneLifecycle(projectDir);
|
|
575
|
+
patchIOSPodfile(projectDir);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
module.exports = {
|
|
579
|
+
patchAllNative,
|
|
580
|
+
patchAndroidBuildGradle,
|
|
581
|
+
patchAndroidMainActivity,
|
|
582
|
+
patchAndroidManifest,
|
|
583
|
+
patchGradleWrapper,
|
|
584
|
+
patchGradleProperties,
|
|
585
|
+
patchIOSInfoPlist,
|
|
586
|
+
patchIOSSceneLifecycle,
|
|
587
|
+
patchIOSPodfile,
|
|
588
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-react-native-starter-kit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Create a React Native CLI app with a ready folder structure, shared components, theming, i18n, API layer, offline handling and native setup done for you",
|
|
5
|
+
"main": "bin/create-react-native-starter-kit.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-react-native-starter-kit": "bin/create-react-native-starter-kit.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"scripts",
|
|
13
|
+
"template",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"sync-template": "node scripts/sync-template.js",
|
|
19
|
+
"test": "node test-patchers.js",
|
|
20
|
+
"prepack": "node scripts/sync-template.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"react-native",
|
|
24
|
+
"react-native-cli",
|
|
25
|
+
"starter",
|
|
26
|
+
"boilerplate",
|
|
27
|
+
"generator",
|
|
28
|
+
"scaffolder",
|
|
29
|
+
"template",
|
|
30
|
+
"starter-kit",
|
|
31
|
+
"typescript",
|
|
32
|
+
"react-navigation",
|
|
33
|
+
"redux-toolkit",
|
|
34
|
+
"tanstack-query"
|
|
35
|
+
],
|
|
36
|
+
"author": "Satyam Narayan (https://github.com/satyam-narayan)",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/satyam-narayan/create-react-native-starter-kit.git"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/satyam-narayan/create-react-native-starter-kit#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/satyam-narayan/create-react-native-starter-kit/issues"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.13.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Refreshes cli/template from the starter repo that contains this cli folder.
|
|
5
|
+
* Runs automatically before `npm pack` / `npm publish`.
|
|
6
|
+
* When the cli folder is used on its own (no parent repo), the existing template is kept.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { copyDirectoryRecursive } = require('../lib/copy-src');
|
|
12
|
+
|
|
13
|
+
const CLI_ROOT = path.resolve(__dirname, '..');
|
|
14
|
+
const REPO_ROOT = path.resolve(CLI_ROOT, '..');
|
|
15
|
+
const TEMPLATE_DIR = path.join(CLI_ROOT, 'template');
|
|
16
|
+
|
|
17
|
+
const DIRECTORIES = ['src', 'patches'];
|
|
18
|
+
const FILES = ['.env.example'];
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
if (!fs.existsSync(path.join(REPO_ROOT, 'src'))) {
|
|
22
|
+
if (!fs.existsSync(path.join(TEMPLATE_DIR, 'src'))) {
|
|
23
|
+
console.error('❌ No starter src/ found next to cli/ and cli/template is empty.');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
console.log('ℹ️ Starter repo not found next to cli/. Keeping the existing template.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fs.rmSync(TEMPLATE_DIR, { recursive: true, force: true });
|
|
31
|
+
fs.mkdirSync(TEMPLATE_DIR, { recursive: true });
|
|
32
|
+
|
|
33
|
+
for (const dir of DIRECTORIES) {
|
|
34
|
+
const from = path.join(REPO_ROOT, dir);
|
|
35
|
+
if (fs.existsSync(from)) {
|
|
36
|
+
copyDirectoryRecursive(from, path.join(TEMPLATE_DIR, dir));
|
|
37
|
+
console.log(`✅ template/${dir}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const file of FILES) {
|
|
42
|
+
const from = path.join(REPO_ROOT, file);
|
|
43
|
+
if (fs.existsSync(from)) {
|
|
44
|
+
fs.copyFileSync(from, path.join(TEMPLATE_DIR, file));
|
|
45
|
+
console.log(`✅ template/${file}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('🎉 cli/template is up to date.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
main();
|