create-ern-boilerplate 0.0.44 → 0.0.45

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/create.js CHANGED
@@ -79,8 +79,7 @@ async function main() {
79
79
  autoInstall = hasInstall;
80
80
  console.log(
81
81
  chalk.green(
82
- `📦 Creating project ${projectName}... (fast mode${
83
- autoInstall ? " + installing dependencies" : ""
82
+ `📦 Creating project ${projectName}... (fast mode${autoInstall ? " + installing dependencies" : ""
84
83
  })`
85
84
  )
86
85
  );
@@ -113,15 +112,28 @@ async function main() {
113
112
  await fs.move(gitignoreTemplatePath, gitignorePath);
114
113
  }
115
114
 
115
+
116
+ const toTitleCaseWithSpace = (name) => {
117
+ return name
118
+ // pisah camelCase
119
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
120
+ // pisah pascalCase
121
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2')
122
+ .trim()
123
+ .replace(/\s+/g, ' ')
124
+ .replace(/\b\w/g, (c) => c.toUpperCase());
125
+ };
126
+
116
127
  // === Update package.json ===
117
128
  const pkgPath = path.join(targetDir, "package.json");
118
129
  if (fs.existsSync(pkgPath)) {
119
130
  const pkg = await fs.readJson(pkgPath);
120
- pkg.name = projectName;
131
+ pkg.name = projectName.toLowerCase().replace(/\s+/g, "-");;
121
132
  pkg.description = description;
122
133
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
123
134
  }
124
135
 
136
+
125
137
  // === Update app.json ===
126
138
  const updateAppJson = async (file, env = null) => {
127
139
  const filePath = path.join(targetDir, file);
@@ -154,9 +166,64 @@ async function main() {
154
166
  await fs.writeJson(filePath, json, { spaces: 2 });
155
167
  };
156
168
 
157
- await updateAppJson("app.json");
158
- await updateAppJson("app.staging.json", "staging");
159
- await updateAppJson("app.prod.json", "production");
169
+ // === Update app.config.js ===
170
+ const updateAppConfigJs = async (filePath, projectName, env = null) => {
171
+ if (!fs.existsSync(filePath)) return;
172
+
173
+ let content = await fs.readFile(filePath, "utf8");
174
+
175
+ const slug = projectName.toLowerCase().replace(/\s+/g, "-");
176
+
177
+ const titleName = toTitleCaseWithSpace(projectName);
178
+
179
+ // --- Replace base fields ---
180
+ content = content
181
+ .replace(/name:\s*'[^']*'/, `name: '${titleName}'`)
182
+ .replace(/slug:\s*'[^']*'/, `slug: '${slug}'`)
183
+ .replace(/scheme:\s*'[^']*'/, `scheme: '${slug}'`);
184
+
185
+ // --- Replace iOS bundle ---
186
+ content = content.replace(
187
+ /bundleIdentifier:\s*'[^']*'/,
188
+ `bundleIdentifier: 'com.${slug}.app'`
189
+ );
190
+
191
+ // --- Replace Android pkg ---
192
+ content = content.replace(
193
+ /package:\s*'[^']*'/,
194
+ `package: 'com.${slug}.app'`
195
+ );
196
+
197
+ // --- Replace EAS projectId ---
198
+ content = content.replace(
199
+ /projectId:\s*'[^']*'/,
200
+ `projectId: ''`
201
+ );
202
+
203
+ // --- Replace APP_ENV if environment provided ---
204
+ if (env) {
205
+ content = content.replace(
206
+ /APP_ENV = process\.env\.APP_ENV \?\? '[^']*'/,
207
+ `APP_ENV = process.env.APP_ENV ?? '${env}'`
208
+ );
209
+ }
210
+
211
+ await fs.writeFile(filePath, content, "utf8");
212
+ };
213
+
214
+
215
+ // === Detect & update app.json OR app.config.js ===
216
+ const appConfigJsPath = path.join(targetDir, "app.config.js");
217
+
218
+ if (fs.existsSync(appConfigJsPath)) {
219
+ // update dynamic config
220
+ await updateAppConfigJs(appConfigJsPath, projectName);
221
+ } else {
222
+ // update JSON configs
223
+ await updateAppJson("app.json");
224
+ await updateAppJson("app.staging.json", "staging");
225
+ await updateAppJson("app.prod.json", "production");
226
+ }
160
227
 
161
228
  spinner.succeed(`✅ Template "${templateName}" has been created!`);
162
229
  } catch (err) {
@@ -182,17 +249,16 @@ async function main() {
182
249
  ${chalk.green("🎉 Done!")}
183
250
  The boilerplate has been successfully created for project: ${chalk.cyan(projectName)}
184
251
 
185
- ${
186
- autoInstall
187
- ? chalk.dim("📦 Dependencies have been installed automatically. ✅")
188
- : chalk.yellow(
189
- "📦 Before starting, review the dependencies in package.json to ensure they match your project requirements."
190
- )
191
- }
252
+ ${autoInstall
253
+ ? chalk.dim("📦 Dependencies have been installed automatically. ✅")
254
+ : chalk.yellow(
255
+ "📦 Before starting, review the dependencies in package.json to ensure they match your project requirements."
256
+ )
257
+ }
192
258
 
193
259
  ${chalk.cyan(
194
- "Before building any features, it is highly recommended to follow the reading order below to fully understand the project structure and AI workflow."
195
- )}
260
+ "Before building any features, it is highly recommended to follow the reading order below to fully understand the project structure and AI workflow."
261
+ )}
196
262
 
197
263
  ${chalk.bold("\n📖 RECOMMENDED READING ORDER:\n")}
198
264
 
@@ -221,8 +287,8 @@ ${chalk.bold("\n📖 RECOMMENDED READING ORDER:\n")}
221
287
  ${chalk.dim("README.md")}
222
288
 
223
289
  ${chalk.bold(
224
- "\n📖 Always maintain integration stability, architectural scalability, maintainability, testability, codebase consistency, and zero errors.\n"
225
- )}
290
+ "\n📖 Always maintain integration stability, architectural scalability, maintainability, testability, codebase consistency, and zero errors.\n"
291
+ )}
226
292
 
227
293
  ${chalk.green("Happy coding! 💻")}
228
294
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ern-boilerplate",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "description": "Expo React Native boilerplate generator",
5
5
  "bin": {
6
6
  "create-ern-boilerplate": "./create.js",
@@ -0,0 +1,99 @@
1
+ import withAndroidSplits from './app.plugins.js';
2
+
3
+ /**
4
+ * Best-practice Expo app.config.js
5
+ * Fully dynamic, schema-safe, ABI-split enabled
6
+ */
7
+ export default ({ config }) => {
8
+ // Ambil environment EAS (development, staging, production)
9
+ const APP_ENV = process.env.APP_ENV ?? 'development';
10
+
11
+ // API URL berdasarkan env
12
+ const API_MAP = {
13
+ development: 'http://localhost:3000',
14
+ staging: 'https://api.staging.com',
15
+ production: 'https://api.production.com',
16
+ };
17
+
18
+ const apiBaseUrl = API_MAP[APP_ENV] ?? API_MAP.development;;
19
+
20
+ // Base Expo config
21
+ const base = {
22
+ name: 'APP_NAME',
23
+ slug: 'APP_SLUG',
24
+ version: '1.0.0',
25
+ orientation: 'portrait',
26
+ icon: './src/assets/images/icon.png',
27
+ scheme: 'APP_SCHEME',
28
+ userInterfaceStyle: 'automatic',
29
+
30
+ splash: {
31
+ image: './src/assets/images/splash-icon.png',
32
+ resizeMode: 'contain',
33
+ backgroundColor: '#ffffff',
34
+ },
35
+
36
+ assetBundlePatterns: ['src/assets/**/*'],
37
+
38
+ ios: {
39
+ supportsTablet: true,
40
+ bundleIdentifier: 'com.builder.APP_SCHEME',
41
+ },
42
+
43
+ android: {
44
+ package: 'com.builder.APP_SCHEME',
45
+ adaptiveIcon: {
46
+ foregroundImage: './src/assets/images/adaptive-icon.png',
47
+ backgroundColor: '#ffffff',
48
+ },
49
+ minSdkVersion: 23,
50
+ },
51
+
52
+ web: {
53
+ bundler: 'metro',
54
+ output: 'static',
55
+ favicon: './src/assets/images/favicon.png',
56
+ },
57
+
58
+ plugins: [
59
+ './app.plugins.js',
60
+ 'expo-router',
61
+ [
62
+ 'expo-secure-store',
63
+ {
64
+ requireFullDiskAccess: false,
65
+ },
66
+ ],
67
+ ],
68
+
69
+ experiments: {
70
+ typedRoutes: true,
71
+ },
72
+
73
+ extra: {
74
+ eas: {
75
+ projectId: '3429d460-2085-4d18-8053-d2c5ec2cfeb3',
76
+ },
77
+ APP_ENV,
78
+ API_BASE_URL: apiBaseUrl,
79
+ },
80
+
81
+ updates: {
82
+ enabled: true,
83
+ checkAutomatically: 'ON_LOAD',
84
+ },
85
+
86
+ runtimeVersion: {
87
+ policy: 'sdkVersion',
88
+ },
89
+ };
90
+
91
+ // Merge config dari EAS + base
92
+ const merged = {
93
+ ...config,
94
+ ...base,
95
+ };
96
+
97
+ // Tambah ABI splits
98
+ return withAndroidSplits(merged);
99
+ };
@@ -0,0 +1,17 @@
1
+ module.exports = function withAndroidConfig(config) {
2
+ if (!config.android) config.android = {};
3
+
4
+ // 1. Split ABI
5
+ config.android.splits = { abi: true };
6
+
7
+ // 2. Aktifkan ProGuard + Shrink resources di release build
8
+ if (!config.modResults) config.modResults = {};
9
+
10
+ config.modResults = {
11
+ ...config.modResults,
12
+ enableProguardInReleaseBuilds: true,
13
+ enableShrinkResources: true,
14
+ };
15
+
16
+ return config;
17
+ };
@@ -17,6 +17,7 @@ module.exports = function (api) {
17
17
  '@types': './src/types',
18
18
  '@utils': './src/utils',
19
19
  '@contexts': './src/contexts',
20
+ '@store': './src/store',
20
21
  '@hooks': './src/hooks',
21
22
  '@theme': './src/theme',
22
23
  '@assets': './src/assets'
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "cli": {
3
- "version": ">= 3.0.0"
3
+ "version": ">= 3.0.0",
4
+ "appVersionSource": "remote"
4
5
  },
5
6
  "build": {
6
7
  "development": {
@@ -10,22 +11,33 @@
10
11
  "env": {
11
12
  "APP_ENV": "development",
12
13
  "API_BASE_URL": "http://localhost:3000"
14
+ },
15
+ "android": {
16
+ "buildType": "apk"
13
17
  }
14
18
  },
15
19
  "staging": {
16
20
  "distribution": "internal",
17
21
  "autoIncrement": true,
22
+ "developmentClient": false,
18
23
  "env": {
19
24
  "APP_ENV": "staging",
20
25
  "API_BASE_URL": "https://api.staging.com"
26
+ },
27
+ "android": {
28
+ "buildType": "apk"
21
29
  }
22
30
  },
23
31
  "production": {
24
32
  "distribution": "store",
25
33
  "autoIncrement": true,
34
+ "developmentClient": false,
26
35
  "env": {
27
36
  "APP_ENV": "production",
28
37
  "API_BASE_URL": "https://api.production.com"
38
+ },
39
+ "android": {
40
+ "buildType": "app-bundle"
29
41
  }
30
42
  }
31
43
  },
@@ -39,4 +51,4 @@
39
51
  }
40
52
  }
41
53
  }
42
- }
54
+ }
@@ -3,27 +3,39 @@
3
3
  "version": "1.0.0",
4
4
  "main": "expo-router/entry",
5
5
  "scripts": {
6
- "start": "expo start --clear",
7
- "start:staging": "expo start --clear --config app.staging.json",
8
- "start:prod": "expo start --clear --config app.prod.json",
6
+ "start": "EXPO_USE_DEV_SERVER=false expo start --clear --go",
7
+ "start:staging": "EXPO_USE_DEV_SERVER=false APP_ENV=staging expo start --clear --go",
8
+ "start:prod": "EXPO_USE_DEV_SERVER=false APP_ENV=production expo start --clear --go",
9
9
  "android": "expo start --android",
10
10
  "ios": "expo start --ios",
11
11
  "web": "expo start --web",
12
12
  "lint": "eslint .",
13
13
  "type-check": "tsc --noEmit",
14
- "build": "eas build --profile development --platform all --local",
15
- "build:staging": "eas build --profile staging --platform all --local --config app.staging.json",
16
- "build:prod": "eas build --profile production --platform all --local --config app.prod.json",
17
- "build:android": "eas build --profile development --platform android --local",
18
- "build:ios": "eas build --profile development --platform ios --local",
14
+ "build": "eas build --platform all --local --profile development",
15
+ "build:staging": "eas build --platform all --local --profile staging",
16
+ "build:prod": "eas build --platform all --local --profile production",
17
+
18
+ "build:android": "eas build --platform android --local --profile development",
19
+ "build:ios": "eas build --platform ios --local --profile development ",
20
+
21
+ "build:eas:ios:dev": "eas build --platform ios --profile development",
22
+ "build:eas:android:dev": "eas build --platform android --profile development",
23
+
24
+ "build:eas:ios:staging": "eas build --platform ios --profile staging",
25
+ "build:eas:android:staging": "eas build --platform android --profile staging",
26
+
27
+ "build:eas:ios:production": "eas build --platform ios --profile production",
28
+ "build:eas:android:production": "eas build --platform android --profile production",
29
+
19
30
  "prebuild": "expo prebuild --clean",
20
- "clean": "rm -rf node_modules && rm -rf .expo && rm -rf android && rm -rf ios && npm install"
31
+ "clean": "rm -rf node_modules && rm -rf .expo && rm -rf android && rm -rf ios && npm install",
32
+ "doctor": "npx expo-doctor --verbose"
21
33
  },
22
34
  "dependencies": {
23
35
  "@expo/vector-icons": "^15.0.2",
24
36
  "@lucide/lab": "^0.1.2",
25
- "@react-native-async-storage/async-storage": "^1.21.0",
26
- "@react-native-community/datetimepicker": "^8.2.0",
37
+ "@react-native-async-storage/async-storage": "2.2.0",
38
+ "@react-native-community/datetimepicker": "8.4.4",
27
39
  "@tanstack/react-query": "^5.51.0",
28
40
  "axios": "^1.7.2",
29
41
  "babel-preset-expo": "^54.0.5",
@@ -32,7 +44,9 @@
32
44
  "expo-background-fetch": "~14.0.7",
33
45
  "expo-blur": "~15.0.7",
34
46
  "expo-camera": "~17.0.8",
47
+ "expo-checkbox": "~5.0.7",
35
48
  "expo-constants": "~18.0.9",
49
+ "expo-dev-client": "~6.0.18",
36
50
  "expo-file-system": "~19.0.17",
37
51
  "expo-font": "~14.0.8",
38
52
  "expo-haptics": "~15.0.7",
@@ -40,12 +54,12 @@
40
54
  "expo-linear-gradient": "~15.0.7",
41
55
  "expo-linking": "~8.0.8",
42
56
  "expo-local-authentication": "~17.0.7",
43
- "expo-location": "~18.0.8",
57
+ "expo-location": "~19.0.7",
44
58
  "expo-navigation-bar": "~5.0.9",
45
59
  "expo-notifications": "~0.32.12",
46
60
  "expo-router": "~6.0.12",
47
61
  "expo-secure-store": "~15.0.7",
48
- "expo-sensors": "~13.0.8",
62
+ "expo-sensors": "~15.0.7",
49
63
  "expo-sharing": "~14.0.7",
50
64
  "expo-splash-screen": "~31.0.10",
51
65
  "expo-status-bar": "~3.0.8",
@@ -58,10 +72,10 @@
58
72
  "react": "19.1.0",
59
73
  "react-dom": "19.1.0",
60
74
  "react-hook-form": "^7.66.0",
61
- "react-native": "0.81.4",
75
+ "react-native": "0.81.5",
62
76
  "react-native-dotenv": "^3.4.11",
63
77
  "react-native-gesture-handler": "~2.28.0",
64
- "react-native-maps": "1.18.0",
78
+ "react-native-maps": "1.20.1",
65
79
  "react-native-modal": "^13.0.1",
66
80
  "react-native-paper": "^5.12.3",
67
81
  "react-native-reanimated": "~4.1.1",
@@ -74,14 +88,12 @@
74
88
  "react-native-web": "^0.21.0",
75
89
  "react-native-webview": "13.15.0",
76
90
  "react-native-worklets": "0.5.1",
77
- "zustand": "^5.0.0",
78
- "expo-checkbox": "~5.0.7"
91
+ "zustand": "^5.0.0"
79
92
  },
80
93
  "devDependencies": {
81
94
  "@babel/core": "^7.26.0",
82
95
  "@babel/plugin-transform-react-jsx": "^7.27.1",
83
96
  "@types/react": "~19.1.10",
84
- "@types/react-native": "^0.73.0",
85
97
  "babel-plugin-module-resolver": "^5.0.2",
86
98
  "eslint": "^9.12.0",
87
99
  "eslint-config-prettier": "^9.1.0",
@@ -90,5 +102,8 @@
90
102
  "tailwindcss": "^3.4.13",
91
103
  "typescript": "~5.9.2"
92
104
  },
105
+ "volta": {
106
+ "node": "20.19.5"
107
+ },
93
108
  "private": true
94
- }
109
+ }
@@ -12,6 +12,7 @@
12
12
  "@types/*": ["src/types/*"],
13
13
  "@utils/*": ["src/utils/*"],
14
14
  "@contexts/*": ["src/contexts/*"],
15
+ "@store/*": ["src/store/*"],
15
16
  "@hooks/*": ["src/hooks/*"],
16
17
  "@theme/*": ["src/theme/*"],
17
18
  "@assets/*": ["src/assets/*"]
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(tree:*)"
5
- ],
6
- "deny": [],
7
- "ask": []
8
- }
9
- }
@@ -1,58 +0,0 @@
1
- {
2
- "expo": {
3
- "name": "APP_NAME",
4
- "slug": "APP_SLUG",
5
- "version": "1.0.0",
6
- "orientation": "portrait",
7
- "icon": "./src/assets/images/icon.png",
8
- "scheme": "myapp",
9
- "userInterfaceStyle": "automatic",
10
- "splash": {
11
- "image": "./src/assets/images/splash-icon.png",
12
- "resizeMode": "contain",
13
- "backgroundColor": "#ffffff"
14
- },
15
- "assetBundlePatterns": ["**/*"],
16
- "ios": {
17
- "supportsTablet": true,
18
- "bundleIdentifier": "com.yourname.APP_SLUG"
19
- },
20
- "android": {
21
- "adaptiveIcon": {
22
- "foregroundImage": "./src/assets/images/adaptive-icon.png",
23
- "backgroundColor": "#ffffff"
24
- },
25
- "package": "com.yourname.APP_SLUG",
26
- "versionCode": 1
27
- },
28
- "web": {
29
- "bundler": "metro",
30
- "output": "static",
31
- "favicon": "./src/assets/images/favicon.png"
32
- },
33
- "plugins": [
34
- "expo-router",
35
- [
36
- "expo-secure-store",
37
- {
38
- "requireFullDiskAccess": false
39
- }
40
- ]
41
- ],
42
- "experiments": {
43
- "typedRoutes": true
44
- },
45
- "extra": {
46
- "eas": {
47
- "projectId": ""
48
- }
49
- },
50
- "updates": {
51
- "enabled": true,
52
- "checkAutomatically": "ON_LOAD"
53
- },
54
- "runtimeVersion": {
55
- "policy": "sdkVersion"
56
- }
57
- }
58
- }
@@ -1,60 +0,0 @@
1
- {
2
- "expo": {
3
- "name": "APP_NAME",
4
- "slug": "APP_SLUG",
5
- "version": "1.0.0",
6
- "orientation": "portrait",
7
- "icon": "./src/assets/images/icon.png",
8
- "scheme": "myapp",
9
- "userInterfaceStyle": "automatic",
10
- "splash": {
11
- "image": "./src/assets/images/splash-icon.png",
12
- "resizeMode": "contain",
13
- "backgroundColor": "#ffffff"
14
- },
15
- "assetBundlePatterns": ["**/*"],
16
- "ios": {
17
- "supportsTablet": true,
18
- "bundleIdentifier": "com.yourname.APP_SLUG"
19
- },
20
- "android": {
21
- "adaptiveIcon": {
22
- "foregroundImage": "./src/assets/images/adaptive-icon.png",
23
- "backgroundColor": "#ffffff"
24
- },
25
- "package": "com.yourname.APP_SLUG",
26
- "versionCode": 1
27
- },
28
- "web": {
29
- "bundler": "metro",
30
- "output": "static",
31
- "favicon": "./src/assets/images/favicon.png"
32
- },
33
- "plugins": [
34
- "expo-router",
35
- [
36
- "expo-secure-store",
37
- {
38
- "requireFullDiskAccess": false
39
- }
40
- ]
41
- ],
42
- "experiments": {
43
- "typedRoutes": true
44
- },
45
- "extra": {
46
- "eas": {
47
- "projectId": ""
48
- },
49
- "BASE_URL": "https://api.production.com",
50
- "ENV": "production"
51
- },
52
- "updates": {
53
- "enabled": true,
54
- "checkAutomatically": "ON_LOAD"
55
- },
56
- "runtimeVersion": {
57
- "policy": "sdkVersion"
58
- }
59
- }
60
- }
@@ -1,60 +0,0 @@
1
- {
2
- "expo": {
3
- "name": "APP_NAME",
4
- "slug": "APP_SLUG",
5
- "version": "1.0.0",
6
- "orientation": "portrait",
7
- "icon": "./src/assets/images/icon.png",
8
- "scheme": "myapp",
9
- "userInterfaceStyle": "automatic",
10
- "splash": {
11
- "image": "./src/assets/images/splash-icon.png",
12
- "resizeMode": "contain",
13
- "backgroundColor": "#ffffff"
14
- },
15
- "assetBundlePatterns": ["**/*"],
16
- "ios": {
17
- "supportsTablet": true,
18
- "bundleIdentifier": "com.yourname.APP_SLUG"
19
- },
20
- "android": {
21
- "adaptiveIcon": {
22
- "foregroundImage": "./src/assets/images/adaptive-icon.png",
23
- "backgroundColor": "#ffffff"
24
- },
25
- "package": "com.yourname.APP_SLUG",
26
- "versionCode": 1
27
- },
28
- "web": {
29
- "bundler": "metro",
30
- "output": "static",
31
- "favicon": "./src/assets/images/favicon.png"
32
- },
33
- "plugins": [
34
- "expo-router",
35
- [
36
- "expo-secure-store",
37
- {
38
- "requireFullDiskAccess": false
39
- }
40
- ]
41
- ],
42
- "experiments": {
43
- "typedRoutes": true
44
- },
45
- "extra": {
46
- "eas": {
47
- "projectId": ""
48
- },
49
- "BASE_URL": "https://api.staging.com",
50
- "ENV": "staging"
51
- },
52
- "updates": {
53
- "enabled": true,
54
- "checkAutomatically": "ON_LOAD"
55
- },
56
- "runtimeVersion": {
57
- "policy": "sdkVersion"
58
- }
59
- }
60
- }
@@ -1,11 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(cat:*)",
5
- "Bash(chmod:*)",
6
- "Bash(tree:*)"
7
- ],
8
- "deny": [],
9
- "ask": []
10
- }
11
- }