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
package/lib/creator.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { STARTER_DEPENDENCIES, STARTER_DEV_DEPENDENCIES } = require('./deps');
|
|
5
|
+
const { patchAllConfigs } = require('./patch-configs');
|
|
6
|
+
const { patchAllNative } = require('./patch-native');
|
|
7
|
+
const { copySrcFolder, copyPatchesFolder, copyEnvExample } = require('./copy-src');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execute command with stdout/stderr inherited
|
|
11
|
+
*/
|
|
12
|
+
function runCommand(cmd, cwd = process.cwd()) {
|
|
13
|
+
console.log(`\n🚀 Executing: ${cmd}`);
|
|
14
|
+
execSync(cmd, { stdio: 'inherit', cwd });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate project name
|
|
19
|
+
*/
|
|
20
|
+
function validateProjectName(name) {
|
|
21
|
+
if (!name || typeof name !== 'string') {
|
|
22
|
+
throw new Error('Project name must be provided.');
|
|
23
|
+
}
|
|
24
|
+
const cleanName = name.trim();
|
|
25
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(cleanName)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Invalid project name: "${cleanName}". Project name must start with a letter and contain only alphanumeric characters or underscores (e.g. MyProject or my_project).`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return cleanName;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Update package.json with starter dependencies and devDependencies
|
|
35
|
+
*/
|
|
36
|
+
function updatePackageJsonDependencies(projectDir) {
|
|
37
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
38
|
+
if (!fs.existsSync(pkgPath)) {
|
|
39
|
+
throw new Error(`package.json not found in ${projectDir}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
43
|
+
|
|
44
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
45
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
46
|
+
|
|
47
|
+
console.log('\n📦 Adding starter dependencies to package.json...');
|
|
48
|
+
|
|
49
|
+
for (const dep of STARTER_DEPENDENCIES) {
|
|
50
|
+
if (dep === 'react' || dep === 'react-native') {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
pkg.dependencies[dep] = 'latest';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const dep of STARTER_DEV_DEPENDENCIES) {
|
|
57
|
+
pkg.devDependencies[dep] = 'latest';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
61
|
+
console.log('✅ package.json dependencies updated.');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Main project creation orchestrator
|
|
66
|
+
*/
|
|
67
|
+
async function createProject({
|
|
68
|
+
projectName,
|
|
69
|
+
targetDir = process.cwd(),
|
|
70
|
+
starterRoot = path.resolve(__dirname, '../template'),
|
|
71
|
+
packageManager = 'yarn', // or 'npm'
|
|
72
|
+
skipInstall = false,
|
|
73
|
+
skipPods = false,
|
|
74
|
+
dryRun = false,
|
|
75
|
+
}) {
|
|
76
|
+
const name = validateProjectName(projectName);
|
|
77
|
+
const projectDir = path.join(targetDir, name);
|
|
78
|
+
|
|
79
|
+
console.log('\n============================================================');
|
|
80
|
+
console.log(`✨ Creating React Native Starter Project: ${name}`);
|
|
81
|
+
console.log(`📂 Destination: ${projectDir}`);
|
|
82
|
+
console.log(`🔧 Package Manager: ${packageManager}`);
|
|
83
|
+
console.log('⚛️ React Native: latest stable release');
|
|
84
|
+
console.log('⚡ Installing latest library versions directly from npm registry');
|
|
85
|
+
console.log('============================================================\n');
|
|
86
|
+
|
|
87
|
+
if (fs.existsSync(projectDir)) {
|
|
88
|
+
throw new Error(`Directory "${projectDir}" already exists. Please choose a different name or remove the existing directory.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!fs.existsSync(path.join(starterRoot, 'src'))) {
|
|
92
|
+
throw new Error(`Starter template not found at ${starterRoot}. Run "npm run sync-template" inside the cli folder first.`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (dryRun) {
|
|
96
|
+
console.log('🔍 [DRY RUN] Simulating steps without writing files:');
|
|
97
|
+
console.log(`1. Initialize bare React Native CLI project (${name}, latest stable React Native)`);
|
|
98
|
+
console.log('2. Copy src/ folder, patches/, and scripts');
|
|
99
|
+
console.log(`3. Install latest starter dependencies dynamically via ${packageManager} add`);
|
|
100
|
+
console.log('4. Apply JS/TS configurations (babel, metro, tsconfig, declarations, App.tsx)');
|
|
101
|
+
console.log('5. Apply Native configurations (Android build.gradle, MainActivity, AndroidManifest, iOS Info.plist)');
|
|
102
|
+
console.log('6. Link font assets (react-native-asset) and generate icons');
|
|
103
|
+
console.log('7. Run pod install (if on macOS)');
|
|
104
|
+
console.log('\n✅ Dry run completed successfully.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Step 1: Initialize bare React Native CLI project
|
|
109
|
+
console.log('\n📥 Step 1/7: Initializing bare React Native CLI project (latest stable React Native)...');
|
|
110
|
+
// Without --version, init uses react-native's "latest" dist-tag, which only points to stable releases (RCs go to "next").
|
|
111
|
+
const initCmd = `npx @react-native-community/cli@latest init ${name} --skip-install`;
|
|
112
|
+
runCommand(initCmd, targetDir);
|
|
113
|
+
|
|
114
|
+
// Step 2: Copy src directory and assets
|
|
115
|
+
console.log('\n📁 Step 2/7: Copying src/ directory, patches/, and assets...');
|
|
116
|
+
copySrcFolder(starterRoot, projectDir);
|
|
117
|
+
copyPatchesFolder(starterRoot, projectDir);
|
|
118
|
+
|
|
119
|
+
// Step 3: Install starter dependencies (always latest from registry)
|
|
120
|
+
if (!skipInstall) {
|
|
121
|
+
console.log(`\n📦 Step 3/7: Installing latest starter dependencies using ${packageManager}...`);
|
|
122
|
+
const depsString = STARTER_DEPENDENCIES.join(' ');
|
|
123
|
+
const devDepsString = STARTER_DEV_DEPENDENCIES.join(' ');
|
|
124
|
+
|
|
125
|
+
if (packageManager === 'yarn') {
|
|
126
|
+
runCommand(`yarn add ${depsString}`, projectDir);
|
|
127
|
+
runCommand(`yarn add -D ${devDepsString}`, projectDir);
|
|
128
|
+
} else {
|
|
129
|
+
runCommand(`npm install --legacy-peer-deps ${depsString}`, projectDir);
|
|
130
|
+
runCommand(`npm install -D --legacy-peer-deps ${devDepsString}`, projectDir);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.log('\n📝 Step 3/7: Adding starter dependencies to package.json (latest)...');
|
|
134
|
+
updatePackageJsonDependencies(projectDir);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Step 4: Apply JS/TS config patches
|
|
138
|
+
console.log('\n⚙️ Step 4/7: Applying Babel, Metro, TS, and App configurations...');
|
|
139
|
+
patchAllConfigs(projectDir);
|
|
140
|
+
copyEnvExample(starterRoot, projectDir);
|
|
141
|
+
|
|
142
|
+
// Apply dependency patches via patch-package if present
|
|
143
|
+
const patchesDir = path.join(projectDir, 'patches');
|
|
144
|
+
if (fs.existsSync(patchesDir) && !skipInstall) {
|
|
145
|
+
console.log('\n🩹 Applying dependency patches (patch-package)...');
|
|
146
|
+
try {
|
|
147
|
+
runCommand('npx patch-package', projectDir);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.log('⚠️ Note: patch-package completed with warnings:', e.message);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 5: Apply Native Android and iOS patches
|
|
154
|
+
console.log('\n📱 Step 5/7: Applying Android and iOS native configurations & permissions...');
|
|
155
|
+
patchAllNative(projectDir);
|
|
156
|
+
|
|
157
|
+
// Step 6: Asset linking & Icon generation
|
|
158
|
+
if (!skipInstall) {
|
|
159
|
+
console.log('\n🎨 Step 6/7: Linking font assets & generating icons...');
|
|
160
|
+
try {
|
|
161
|
+
runCommand('npx react-native-asset', projectDir);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.log('⚠️ Note: react-native-asset completed with warnings:', e.message);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
runCommand('node scripts/generate-icons.js', projectDir);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.log('⚠️ Note: Icon generation completed with warnings:', e.message);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
console.log('\n⏭️ Step 6/7: Skipped asset linking & icon generation (--skip-install).');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Step 7: iOS Pods installation (macOS only)
|
|
176
|
+
if (!skipPods && !skipInstall && process.platform === 'darwin') {
|
|
177
|
+
const iosDir = path.join(projectDir, 'ios');
|
|
178
|
+
if (fs.existsSync(iosDir)) {
|
|
179
|
+
console.log('\n🍎 Step 7/7: Installing iOS CocoaPods...');
|
|
180
|
+
try {
|
|
181
|
+
runCommand('pod install', iosDir);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.log('⚠️ CocoaPods installation encountered an issue. You can run "cd ios && pod install" manually.');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
console.log('\n⏭️ Step 7/7: Skipped CocoaPods installation.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Finished!
|
|
191
|
+
console.log('\n============================================================');
|
|
192
|
+
console.log(`🎉 Project ${name} is ready!`);
|
|
193
|
+
console.log('============================================================');
|
|
194
|
+
console.log('\nNext steps:');
|
|
195
|
+
console.log(` 1. cd ${name}`);
|
|
196
|
+
if (skipInstall) {
|
|
197
|
+
console.log(` 2. ${packageManager === 'yarn' ? 'yarn install' : 'npm install'}`);
|
|
198
|
+
if (process.platform === 'darwin') {
|
|
199
|
+
console.log(' 3. cd ios && pod install && cd ..');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
console.log(` ${skipInstall ? '4' : '2'}. ${packageManager === 'yarn' ? 'yarn' : 'npm run'} android (Run Android)`);
|
|
203
|
+
console.log(` ${skipInstall ? '5' : '3'}. ${packageManager === 'yarn' ? 'yarn' : 'npm run'} ios (Run iOS)`);
|
|
204
|
+
console.log('============================================================\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
createProject,
|
|
209
|
+
validateProjectName,
|
|
210
|
+
updatePackageJsonDependencies,
|
|
211
|
+
};
|
package/lib/deps.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated list of starter dependencies and devDependencies.
|
|
3
|
+
* No hardcoded version numbers - package manager (yarn/npm) dynamically
|
|
4
|
+
* installs the latest compatible versions directly from the npm registry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const STARTER_DEPENDENCIES = [
|
|
8
|
+
'@bam.tech/react-native-image-resizer',
|
|
9
|
+
'@d11/react-native-fast-image',
|
|
10
|
+
'@gorhom/bottom-sheet',
|
|
11
|
+
'@hookform/resolvers',
|
|
12
|
+
'@mhpdev/react-native-haptics',
|
|
13
|
+
'@react-native-community/datetimepicker',
|
|
14
|
+
'@react-native-community/netinfo',
|
|
15
|
+
'@react-native-documents/picker',
|
|
16
|
+
'@react-navigation/bottom-tabs',
|
|
17
|
+
'@react-navigation/native',
|
|
18
|
+
'@react-navigation/native-stack',
|
|
19
|
+
'@reduxjs/toolkit',
|
|
20
|
+
'@tanstack/react-query',
|
|
21
|
+
'axios',
|
|
22
|
+
'i18next',
|
|
23
|
+
'moment',
|
|
24
|
+
'react-hook-form',
|
|
25
|
+
'react-i18next',
|
|
26
|
+
'react-native-element-dropdown',
|
|
27
|
+
'react-native-gesture-handler',
|
|
28
|
+
'react-native-image-picker',
|
|
29
|
+
'rn-international-phone-number',
|
|
30
|
+
'react-native-linear-gradient',
|
|
31
|
+
'react-native-mmkv',
|
|
32
|
+
'react-native-nitro-modules',
|
|
33
|
+
'react-native-reanimated',
|
|
34
|
+
'react-native-reanimated-skeleton',
|
|
35
|
+
'react-native-safe-area-context',
|
|
36
|
+
'react-native-screens',
|
|
37
|
+
'react-native-svg',
|
|
38
|
+
'react-native-toast-message',
|
|
39
|
+
'react-native-walkthrough-tooltip',
|
|
40
|
+
'react-native-worklets',
|
|
41
|
+
'react-redux',
|
|
42
|
+
'redux-persist',
|
|
43
|
+
'yup',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const STARTER_DEV_DEPENDENCIES = [
|
|
47
|
+
'babel-plugin-module-resolver',
|
|
48
|
+
'babel-plugin-transform-inline-environment-variables',
|
|
49
|
+
'dotenv',
|
|
50
|
+
'react-native-svg-transformer',
|
|
51
|
+
'react-native-asset',
|
|
52
|
+
'patch-package',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
STARTER_DEPENDENCIES,
|
|
57
|
+
STARTER_DEV_DEPENDENCIES,
|
|
58
|
+
};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Patch or generate babel.config.js
|
|
6
|
+
*/
|
|
7
|
+
function patchBabelConfig(projectDir) {
|
|
8
|
+
const babelPath = path.join(projectDir, 'babel.config.js');
|
|
9
|
+
const babelContent = `const path = require('path');
|
|
10
|
+
const env =
|
|
11
|
+
require('dotenv').config({
|
|
12
|
+
path: path.resolve(__dirname, '.env'),
|
|
13
|
+
}).parsed || {};
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
presets: ['module:@react-native/babel-preset'],
|
|
17
|
+
|
|
18
|
+
//by own
|
|
19
|
+
plugins: [
|
|
20
|
+
[
|
|
21
|
+
'transform-inline-environment-variables',
|
|
22
|
+
{ include: Object.keys(env) },
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
'module-resolver',
|
|
26
|
+
{
|
|
27
|
+
root: ['./src'],
|
|
28
|
+
alias: {
|
|
29
|
+
'@': './src',
|
|
30
|
+
},
|
|
31
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
['react-native-worklets/plugin'],
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
`;
|
|
38
|
+
fs.writeFileSync(babelPath, babelContent, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ship a sample env file and keep real env files out of git.
|
|
43
|
+
*/
|
|
44
|
+
function patchEnvExample(projectDir) {
|
|
45
|
+
const envExample = [
|
|
46
|
+
'# Leave API_BASE_URL unset to use the built-in mock API (src/services/api/mock).',
|
|
47
|
+
'# Set it to your backend URL to call the real server.',
|
|
48
|
+
'# API_BASE_URL=https://api.example.com',
|
|
49
|
+
'',
|
|
50
|
+
].join('\n');
|
|
51
|
+
fs.writeFileSync(path.join(projectDir, '.env.example'), envExample, 'utf8');
|
|
52
|
+
|
|
53
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
54
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf8');
|
|
59
|
+
if (!/(^|\n)\.env(\n|$)/.test(gitignore)) {
|
|
60
|
+
fs.appendFileSync(
|
|
61
|
+
gitignorePath,
|
|
62
|
+
'\n# Environment\n.env\n.env.local\n',
|
|
63
|
+
'utf8',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Patch or generate metro.config.js
|
|
70
|
+
*/
|
|
71
|
+
function patchMetroConfig(projectDir) {
|
|
72
|
+
const metroPath = path.join(projectDir, 'metro.config.js');
|
|
73
|
+
const metroContent = `const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
|
74
|
+
const path = require('path');
|
|
75
|
+
|
|
76
|
+
//by own
|
|
77
|
+
const defaultConfig = getDefaultConfig(__dirname);
|
|
78
|
+
const { assetExts, sourceExts } = defaultConfig.resolver;
|
|
79
|
+
/**
|
|
80
|
+
* Metro configuration
|
|
81
|
+
* https://reactnative.dev/docs/metro
|
|
82
|
+
*
|
|
83
|
+
* @type {import('@react-native/metro-config').MetroConfig}
|
|
84
|
+
*/
|
|
85
|
+
const config = {
|
|
86
|
+
//by own
|
|
87
|
+
transformer: {
|
|
88
|
+
babelTransformerPath: require.resolve(
|
|
89
|
+
'react-native-svg-transformer/react-native',
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
resolver: {
|
|
93
|
+
assetExts: assetExts.filter(ext => ext !== 'svg'),
|
|
94
|
+
sourceExts: [...sourceExts, 'svg'],
|
|
95
|
+
extraNodeModules: {
|
|
96
|
+
'@': path.resolve(__dirname, 'src'),
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
|
|
102
|
+
`;
|
|
103
|
+
fs.writeFileSync(metroPath, metroContent, 'utf8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Remove // and /* *\/ comments and trailing commas from JSONC, leaving string contents
|
|
108
|
+
* untouched (globs like "**\/*.ts" contain comment-like sequences).
|
|
109
|
+
*/
|
|
110
|
+
function stripJsonComments(raw) {
|
|
111
|
+
let out = '';
|
|
112
|
+
let inString = false;
|
|
113
|
+
for (let i = 0; i < raw.length; i++) {
|
|
114
|
+
const ch = raw[i];
|
|
115
|
+
const next = raw[i + 1];
|
|
116
|
+
if (inString) {
|
|
117
|
+
out += ch;
|
|
118
|
+
if (ch === '\\') {
|
|
119
|
+
out += next ?? '';
|
|
120
|
+
i++;
|
|
121
|
+
} else if (ch === '"') {
|
|
122
|
+
inString = false;
|
|
123
|
+
}
|
|
124
|
+
} else if (ch === '"') {
|
|
125
|
+
inString = true;
|
|
126
|
+
out += ch;
|
|
127
|
+
} else if (ch === '/' && next === '/') {
|
|
128
|
+
while (i < raw.length && raw[i] !== '\n') i++;
|
|
129
|
+
out += '\n';
|
|
130
|
+
} else if (ch === '/' && next === '*') {
|
|
131
|
+
i += 2;
|
|
132
|
+
while (i < raw.length && !(raw[i] === '*' && raw[i + 1] === '/')) i++;
|
|
133
|
+
i++;
|
|
134
|
+
} else {
|
|
135
|
+
out += ch;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out.replace(/,(\s*[}\]])/g, '$1');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readJsonc(filePath) {
|
|
142
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(raw);
|
|
145
|
+
} catch {
|
|
146
|
+
return JSON.parse(stripJsonComments(raw));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Patch tsconfig.json
|
|
152
|
+
*/
|
|
153
|
+
function patchTsConfig(projectDir) {
|
|
154
|
+
const tsConfigPath = path.join(projectDir, 'tsconfig.json');
|
|
155
|
+
let tsConfig = { extends: '@react-native/typescript-config' };
|
|
156
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
157
|
+
try {
|
|
158
|
+
tsConfig = readJsonc(tsConfigPath);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
console.log('⚠️ Could not parse tsconfig.json, writing a default one:', e.message);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
tsConfig.compilerOptions = tsConfig.compilerOptions || {};
|
|
165
|
+
// `paths` resolves relative to tsconfig without `baseUrl`; TypeScript 6 rejects `baseUrl` as deprecated.
|
|
166
|
+
delete tsConfig.compilerOptions.baseUrl;
|
|
167
|
+
tsConfig.compilerOptions.paths = {
|
|
168
|
+
'@/*': ['./src/*'],
|
|
169
|
+
...(tsConfig.compilerOptions.paths || {}),
|
|
170
|
+
};
|
|
171
|
+
tsConfig.compilerOptions.skipLibCheck = true;
|
|
172
|
+
|
|
173
|
+
fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2) + '\n', 'utf8');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Generate react-native.config.js
|
|
178
|
+
*/
|
|
179
|
+
function patchReactNativeConfig(projectDir) {
|
|
180
|
+
const configPath = path.join(projectDir, 'react-native.config.js');
|
|
181
|
+
const content = `module.exports = {
|
|
182
|
+
project: {
|
|
183
|
+
ios: {},
|
|
184
|
+
android: {},
|
|
185
|
+
},
|
|
186
|
+
assets: [
|
|
187
|
+
'./src/assets/fonts/',
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
`;
|
|
191
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Generate declarations.d.ts
|
|
196
|
+
*/
|
|
197
|
+
function patchDeclarations(projectDir) {
|
|
198
|
+
const decPath = path.join(projectDir, 'declarations.d.ts');
|
|
199
|
+
const content = `declare module '*.svg' {
|
|
200
|
+
import React from 'react';
|
|
201
|
+
import { SvgProps } from 'react-native-svg';
|
|
202
|
+
const content: React.FC<SvgProps>;
|
|
203
|
+
export default content;
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
fs.writeFileSync(decPath, content, 'utf8');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Update App.tsx
|
|
211
|
+
*/
|
|
212
|
+
function patchAppTsx(projectDir) {
|
|
213
|
+
const appPath = path.join(projectDir, 'App.tsx');
|
|
214
|
+
const content = `/**
|
|
215
|
+
* React Native Starter Kit
|
|
216
|
+
*
|
|
217
|
+
* Main entry point - exports the App component from src/app
|
|
218
|
+
*
|
|
219
|
+
* @format
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
// Export the main App component from the src directory
|
|
223
|
+
export { default } from './src/app/App';
|
|
224
|
+
`;
|
|
225
|
+
fs.writeFileSync(appPath, content, 'utf8');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Copy or create scripts/generate-icons.js and update package.json scripts
|
|
230
|
+
*/
|
|
231
|
+
function patchScriptsAndPackageJson(projectDir) {
|
|
232
|
+
const scriptsDir = path.join(projectDir, 'scripts');
|
|
233
|
+
if (!fs.existsSync(scriptsDir)) {
|
|
234
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const iconScriptPath = path.join(scriptsDir, 'generate-icons.js');
|
|
238
|
+
const iconScriptContent = `const fs = require('fs');
|
|
239
|
+
const path = require('path');
|
|
240
|
+
|
|
241
|
+
const ICONS_DIR = path.resolve(__dirname, '../src/assets/icons');
|
|
242
|
+
const OUTPUT_FILE = path.join(ICONS_DIR, 'index.ts');
|
|
243
|
+
|
|
244
|
+
if (!fs.existsSync(ICONS_DIR)) {
|
|
245
|
+
console.log('⚠️ Icons directory not found:', ICONS_DIR);
|
|
246
|
+
process.exit(0);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const files = fs.readdirSync(ICONS_DIR).filter(file => file.endsWith('.svg'));
|
|
250
|
+
|
|
251
|
+
const toPascalCase = name =>
|
|
252
|
+
name.replace(/(^\\w|-\\w)/g, m => m.replace('-', '').toUpperCase());
|
|
253
|
+
|
|
254
|
+
const imports = files
|
|
255
|
+
.map(file => {
|
|
256
|
+
const name = toPascalCase(path.basename(file, '.svg'));
|
|
257
|
+
return \`import \${name} from './\${file}';\`;
|
|
258
|
+
})
|
|
259
|
+
.join('\\n');
|
|
260
|
+
|
|
261
|
+
const mappings = files
|
|
262
|
+
.map(file => {
|
|
263
|
+
const key = path.basename(file, '.svg');
|
|
264
|
+
const value = toPascalCase(key);
|
|
265
|
+
return \` \${key}: \${value},\`;
|
|
266
|
+
})
|
|
267
|
+
.join('\\n');
|
|
268
|
+
|
|
269
|
+
const content = \`\${imports}
|
|
270
|
+
|
|
271
|
+
export const Icons = {
|
|
272
|
+
\${mappings}
|
|
273
|
+
} as const;
|
|
274
|
+
|
|
275
|
+
export type IconName = keyof typeof Icons;
|
|
276
|
+
\`;
|
|
277
|
+
|
|
278
|
+
fs.writeFileSync(OUTPUT_FILE, content);
|
|
279
|
+
console.log('✅ Icons index.ts generated');
|
|
280
|
+
`;
|
|
281
|
+
fs.writeFileSync(iconScriptPath, iconScriptContent, 'utf8');
|
|
282
|
+
|
|
283
|
+
// Update target package.json
|
|
284
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
285
|
+
if (fs.existsSync(pkgPath)) {
|
|
286
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
287
|
+
pkg.scripts = pkg.scripts || {};
|
|
288
|
+
pkg.scripts['generate:icons'] = 'node scripts/generate-icons.js';
|
|
289
|
+
pkg.scripts['postinstall'] = 'patch-package';
|
|
290
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Run all JS/TS config patches
|
|
296
|
+
*/
|
|
297
|
+
function patchAllConfigs(projectDir) {
|
|
298
|
+
patchBabelConfig(projectDir);
|
|
299
|
+
patchEnvExample(projectDir);
|
|
300
|
+
patchMetroConfig(projectDir);
|
|
301
|
+
patchTsConfig(projectDir);
|
|
302
|
+
patchReactNativeConfig(projectDir);
|
|
303
|
+
patchDeclarations(projectDir);
|
|
304
|
+
patchAppTsx(projectDir);
|
|
305
|
+
patchScriptsAndPackageJson(projectDir);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
module.exports = {
|
|
309
|
+
patchAllConfigs,
|
|
310
|
+
patchBabelConfig,
|
|
311
|
+
patchEnvExample,
|
|
312
|
+
patchMetroConfig,
|
|
313
|
+
patchTsConfig,
|
|
314
|
+
patchReactNativeConfig,
|
|
315
|
+
patchDeclarations,
|
|
316
|
+
patchAppTsx,
|
|
317
|
+
patchScriptsAndPackageJson,
|
|
318
|
+
};
|