create-ern-boilerplate 0.0.40 ā 0.0.42
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 +103 -110
- package/package.json +1 -1
- package/templates/agent-generator/examples/screen.example.tsx +2 -3
- package/templates/agent-generator/examples/screens/detail-screen.example.tsx +3 -2
- package/templates/agent-generator/examples/screens/list-screen-no-auth.example.tsx +3 -2
- package/templates/agent-generator/examples/screens/list-screen-with-auth.example.tsx +3 -2
package/create.js
CHANGED
|
@@ -14,12 +14,11 @@ const __dirname = path.dirname(__filename);
|
|
|
14
14
|
async function main() {
|
|
15
15
|
console.log(chalk.bold.cyan("\nš Create Expo React Native Boilerplate\n"));
|
|
16
16
|
|
|
17
|
-
//
|
|
17
|
+
// === Read CLI arguments ===
|
|
18
18
|
const args = process.argv.slice(2);
|
|
19
19
|
const hasY = args.includes("-y") || args.includes("--yes");
|
|
20
|
-
const hasInstall = args.includes("--install");
|
|
20
|
+
const hasInstall = args.includes("--install");
|
|
21
21
|
|
|
22
|
-
// Ambil nilai argumen manual
|
|
23
22
|
const nameArg = args.find((a) => !a.startsWith("-"));
|
|
24
23
|
const descIndex = args.findIndex((a) => a === "--desc");
|
|
25
24
|
const templateIndex = args.findIndex((a) => a === "--template");
|
|
@@ -28,34 +27,34 @@ async function main() {
|
|
|
28
27
|
const templateArg = templateIndex !== -1 ? args[templateIndex + 1] : "";
|
|
29
28
|
|
|
30
29
|
let projectName = nameArg || "";
|
|
31
|
-
let description = descArg || "
|
|
32
|
-
let templateName = templateArg || "minimal";
|
|
30
|
+
let description = descArg || "Expo React Native project";
|
|
31
|
+
let templateName = templateArg || "minimal";
|
|
33
32
|
let autoInstall = false;
|
|
34
33
|
|
|
35
|
-
// ===
|
|
34
|
+
// === Interactive mode ===
|
|
36
35
|
if (!hasY) {
|
|
37
36
|
const answers = await inquirer.prompt([
|
|
38
37
|
{
|
|
39
38
|
name: "projectName",
|
|
40
|
-
message: "
|
|
39
|
+
message: "Project name:",
|
|
41
40
|
default: projectName || "my-expo-app",
|
|
42
41
|
},
|
|
43
42
|
{
|
|
44
43
|
name: "description",
|
|
45
|
-
message: "
|
|
44
|
+
message: "Short description:",
|
|
46
45
|
default: description,
|
|
47
46
|
},
|
|
48
47
|
{
|
|
49
48
|
type: "list",
|
|
50
49
|
name: "template",
|
|
51
|
-
message: "
|
|
50
|
+
message: "Choose a template:",
|
|
52
51
|
choices: await getTemplateChoices(),
|
|
53
52
|
default: templateName,
|
|
54
53
|
},
|
|
55
54
|
{
|
|
56
55
|
type: "confirm",
|
|
57
56
|
name: "autoInstall",
|
|
58
|
-
message: "
|
|
57
|
+
message: "Install dependencies automatically after creating the project?",
|
|
59
58
|
default: false,
|
|
60
59
|
},
|
|
61
60
|
]);
|
|
@@ -65,20 +64,23 @@ async function main() {
|
|
|
65
64
|
templateName = answers.template;
|
|
66
65
|
autoInstall = answers.autoInstall;
|
|
67
66
|
} else {
|
|
68
|
-
// ===
|
|
67
|
+
// === Fast mode ===
|
|
69
68
|
if (!projectName) {
|
|
70
|
-
console.log(chalk.red("ā
|
|
71
|
-
console.log(chalk.yellow("
|
|
69
|
+
console.log(chalk.red("ā You must provide a project name when using -y mode."));
|
|
70
|
+
console.log(chalk.yellow("Example:"));
|
|
72
71
|
console.log(
|
|
73
|
-
chalk.cyan(
|
|
72
|
+
chalk.cyan(
|
|
73
|
+
" npx create-ern-boilerplate my-app -y --desc 'My App' --template redux"
|
|
74
|
+
)
|
|
74
75
|
);
|
|
75
76
|
process.exit(1);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
autoInstall = hasInstall;
|
|
79
|
+
autoInstall = hasInstall;
|
|
79
80
|
console.log(
|
|
80
81
|
chalk.green(
|
|
81
|
-
`š¦
|
|
82
|
+
`š¦ Creating project ${projectName}... (fast mode${
|
|
83
|
+
autoInstall ? " + installing dependencies" : ""
|
|
82
84
|
})`
|
|
83
85
|
)
|
|
84
86
|
);
|
|
@@ -88,22 +90,22 @@ async function main() {
|
|
|
88
90
|
const templateDir = path.resolve(__dirname, "templates", templateName);
|
|
89
91
|
|
|
90
92
|
if (!(await fs.pathExists(templateDir))) {
|
|
91
|
-
console.log(chalk.red(`ā Template "${templateName}"
|
|
93
|
+
console.log(chalk.red(`ā Template "${templateName}" not found.`));
|
|
92
94
|
process.exit(1);
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
if (fs.existsSync(targetDir)) {
|
|
96
|
-
console.log(chalk.red(`ā Folder "${projectName}"
|
|
98
|
+
console.log(chalk.red(`ā Folder "${projectName}" already exists.`));
|
|
97
99
|
process.exit(1);
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
// === Copy template ===
|
|
101
|
-
const spinner = ora(`š
|
|
103
|
+
const spinner = ora(`š Copying template "${templateName}"...`).start();
|
|
102
104
|
|
|
103
105
|
try {
|
|
104
106
|
await fs.copy(templateDir, targetDir);
|
|
105
107
|
|
|
106
|
-
// === Rename .gitignore-template
|
|
108
|
+
// === Rename .gitignore-template ā .gitignore ===
|
|
107
109
|
const gitignoreTemplatePath = path.join(targetDir, ".gitignore-template");
|
|
108
110
|
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
109
111
|
|
|
@@ -121,121 +123,112 @@ async function main() {
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
// === Update app.json ===
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
}
|
|
126
|
+
const updateAppJson = async (file, env = null) => {
|
|
127
|
+
const filePath = path.join(targetDir, file);
|
|
128
|
+
if (!fs.existsSync(filePath)) return;
|
|
129
|
+
|
|
130
|
+
const json = await fs.readJson(filePath);
|
|
131
|
+
|
|
132
|
+
json.expo = json.expo || {};
|
|
133
|
+
json.expo.name = projectName;
|
|
134
|
+
json.expo.slug = projectName.toLowerCase().replace(/\s+/g, "-");
|
|
135
|
+
|
|
136
|
+
json.expo.ios = json.expo.ios || {};
|
|
137
|
+
json.expo.android = json.expo.android || {};
|
|
138
|
+
|
|
139
|
+
json.expo.ios.bundleIdentifier = `com.${projectName.toLowerCase()}.app`;
|
|
140
|
+
json.expo.android.package = `com.${projectName.toLowerCase()}.app`;
|
|
140
141
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
appJsonStaging.expo = appJsonStaging.expo || {};
|
|
146
|
-
appJsonStaging.expo.name = projectName;
|
|
147
|
-
appJsonStaging.expo.slug = projectName.toLowerCase().replace(/\s+/g, "-");
|
|
148
|
-
appJsonStaging.expo.ios = appJsonStaging.expo.ios || {};
|
|
149
|
-
appJsonStaging.expo.android = appJsonStaging.expo.android || {};
|
|
150
|
-
appJsonStaging.expo.ios.bundleIdentifier = `com.${projectName.toLowerCase()}.app`;
|
|
151
|
-
appJsonStaging.expo.android.package = `com.${projectName.toLowerCase()}.app`;
|
|
152
|
-
appJsonStaging.expo.scheme = projectName.toLowerCase();
|
|
153
|
-
if (appJsonStaging.expo.extra && appJsonStaging.expo.extra.eas) {
|
|
154
|
-
appJsonStaging.expo.extra.eas.projectId = "";
|
|
142
|
+
json.expo.scheme = projectName.toLowerCase();
|
|
143
|
+
|
|
144
|
+
if (json.expo.extra && json.expo.extra.eas) {
|
|
145
|
+
json.expo.extra.eas.projectId = "";
|
|
155
146
|
}
|
|
156
|
-
appJsonStaging.expo.extra.ENV = "staging";
|
|
157
|
-
appJsonStaging.expo.extra.BASE_URL = "";
|
|
158
|
-
await fs.writeJson(appJsonPathStaging, appJsonStaging, { spaces: 2 });
|
|
159
|
-
}
|
|
160
147
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
appJsonProd.expo = appJsonProd.expo || {};
|
|
166
|
-
appJsonProd.expo.name = projectName;
|
|
167
|
-
appJsonProd.expo.slug = projectName.toLowerCase().replace(/\s+/g, "-");
|
|
168
|
-
appJsonProd.expo.ios = appJsonProd.expo.ios || {};
|
|
169
|
-
appJsonProd.expo.android = appJsonProd.expo.android || {};
|
|
170
|
-
appJsonProd.expo.ios.bundleIdentifier = `com.${projectName.toLowerCase()}.app`;
|
|
171
|
-
appJsonProd.expo.android.package = `com.${projectName.toLowerCase()}.app`;
|
|
172
|
-
appJsonProd.expo.scheme = projectName.toLowerCase();
|
|
173
|
-
if (appJsonProd.expo.extra && appJsonProd.expo.extra.eas) {
|
|
174
|
-
appJsonProd.expo.extra.eas.projectId = "";
|
|
148
|
+
if (env) {
|
|
149
|
+
json.expo.extra = json.expo.extra || {};
|
|
150
|
+
json.expo.extra.ENV = env;
|
|
151
|
+
json.expo.extra.BASE_URL = "";
|
|
175
152
|
}
|
|
176
|
-
appJsonProd.expo.extra.ENV = "production";
|
|
177
|
-
appJsonProd.expo.extra.BASE_URL = "";
|
|
178
|
-
await fs.writeJson(appJsonPathProd, appJsonProd, { spaces: 2 });
|
|
179
|
-
}
|
|
180
153
|
|
|
181
|
-
|
|
154
|
+
await fs.writeJson(filePath, json, { spaces: 2 });
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
await updateAppJson("app.json");
|
|
158
|
+
await updateAppJson("app.staging.json", "staging");
|
|
159
|
+
await updateAppJson("app.prod.json", "production");
|
|
160
|
+
|
|
161
|
+
spinner.succeed(`ā
Template "${templateName}" has been created!`);
|
|
182
162
|
} catch (err) {
|
|
183
|
-
spinner.fail("ā
|
|
163
|
+
spinner.fail("ā An error occurred while creating the project.");
|
|
184
164
|
console.error(err);
|
|
185
165
|
process.exit(1);
|
|
186
166
|
}
|
|
187
167
|
|
|
188
|
-
// === Auto install
|
|
168
|
+
// === Auto install dependencies ===
|
|
189
169
|
if (autoInstall) {
|
|
190
|
-
const installSpinner = ora("š¦
|
|
170
|
+
const installSpinner = ora("š¦ Installing dependencies...").start();
|
|
191
171
|
try {
|
|
192
172
|
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
193
|
-
installSpinner.succeed("ā
Dependencies
|
|
173
|
+
installSpinner.succeed("ā
Dependencies installed successfully!");
|
|
194
174
|
} catch (err) {
|
|
195
|
-
installSpinner.fail("ā
|
|
175
|
+
installSpinner.fail("ā Failed to install dependencies.");
|
|
196
176
|
console.error(err);
|
|
197
177
|
}
|
|
198
178
|
}
|
|
199
179
|
|
|
180
|
+
// === Final output ===
|
|
200
181
|
console.log(`
|
|
201
|
-
${chalk.green("š
|
|
202
|
-
|
|
182
|
+
${chalk.green("š Done!")}
|
|
183
|
+
The boilerplate has been successfully created for project: ${chalk.cyan(projectName)}
|
|
184
|
+
|
|
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
|
+
}
|
|
203
192
|
|
|
204
|
-
${
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
193
|
+
${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
|
+
)}
|
|
196
|
+
|
|
197
|
+
${chalk.bold("\nš RECOMMENDED READING ORDER:\n")}
|
|
198
|
+
|
|
199
|
+
1. ā ${chalk.dim("# START HERE ā Choose template variant")}
|
|
200
|
+
${chalk.dim("TEMPLATE_VARIANTS.md")}
|
|
201
|
+
|
|
202
|
+
2. ā ${chalk.dim("# Complete AI onboarding guide & AI workflow explanation")}
|
|
203
|
+
${chalk.dim("AI_GUIDE.md")}
|
|
204
|
+
|
|
205
|
+
3. š ${chalk.dim("# Path mappings & AI context configuration")}
|
|
206
|
+
${chalk.dim("ai-context.json")}
|
|
207
|
+
|
|
208
|
+
4. š ${chalk.dim("# Code generation patterns and rules")}
|
|
209
|
+
${chalk.dim("GENERATE_RULES.md")}
|
|
208
210
|
|
|
209
|
-
${chalk.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
${chalk.dim("# Mock API & dummy data guide")}
|
|
225
|
-
7. š” examples/
|
|
226
|
-
${chalk.dim("# Contoh kode siap pakai")}
|
|
227
|
-
8. š README.md
|
|
228
|
-
${chalk.dim("# Gambaran umum project")}
|
|
229
|
-
|
|
230
|
-
${chalk.bold("\nš Pastikan untuk selalu menjaga stabilitas integrasi, skalabilitas arsitektur, maintainability, testability, konsistensi codebase, serta zero error.\n")}
|
|
211
|
+
5. š ${chalk.dim("# Coding standards and conventions")}
|
|
212
|
+
${chalk.dim("CONVENTIONS.md")}
|
|
213
|
+
|
|
214
|
+
6. š ${chalk.dim("# Mock API usage & dummy data guide")}
|
|
215
|
+
${chalk.dim("SERVER_GUIDE.md")}
|
|
216
|
+
|
|
217
|
+
7. š” ${chalk.dim("# Ready-to-use examples")}
|
|
218
|
+
${chalk.dim("examples/")}
|
|
219
|
+
|
|
220
|
+
8. š ${chalk.dim("# General overview of the project")}
|
|
221
|
+
${chalk.dim("README.md")}
|
|
222
|
+
|
|
223
|
+
${chalk.bold(
|
|
224
|
+
"\nš Always maintain integration stability, architectural scalability, maintainability, testability, codebase consistency, and zero errors.\n"
|
|
225
|
+
)}
|
|
231
226
|
|
|
232
227
|
${chalk.green("Happy coding! š»")}
|
|
233
228
|
`);
|
|
234
229
|
}
|
|
235
230
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// === Helper untuk menampilkan daftar template ===
|
|
231
|
+
// === Helper: list available templates ===
|
|
239
232
|
async function getTemplateChoices() {
|
|
240
233
|
const templatesDir = path.join(__dirname, "templates");
|
|
241
234
|
const folders = await fs.readdir(templatesDir);
|
|
@@ -245,6 +238,6 @@ async function getTemplateChoices() {
|
|
|
245
238
|
}
|
|
246
239
|
|
|
247
240
|
main().catch((err) => {
|
|
248
|
-
console.error(chalk.red("
|
|
241
|
+
console.error(chalk.red("An unexpected error occurred:"), err);
|
|
249
242
|
process.exit(1);
|
|
250
243
|
});
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import React, { useState, useEffect } from 'react';
|
|
13
13
|
import { View, Text, FlatList, RefreshControl, ActivityIndicator } from 'react-native';
|
|
14
14
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
15
|
-
import {
|
|
15
|
+
import { useTheme } from '@/hooks/useTheme';
|
|
16
16
|
import { Card } from '@/components/common/Card';
|
|
17
17
|
import { Button } from '@/components/common/Button';
|
|
18
18
|
|
|
@@ -25,8 +25,7 @@ interface NewsItem {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export default function NewsListScreen() {
|
|
28
|
-
const
|
|
29
|
-
const isDark = colorScheme === 'dark';
|
|
28
|
+
const { colors, isDark } = useTheme();
|
|
30
29
|
|
|
31
30
|
// State management
|
|
32
31
|
const [news, setNews] = useState<NewsItem[]>([]);
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import { useTheme } from '@/hooks/useTheme';
|
|
29
29
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
|
30
30
|
import Toast from 'react-native-toast-message';
|
|
31
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
31
32
|
|
|
32
33
|
// Example: Product interface
|
|
33
34
|
interface Product {
|
|
@@ -184,7 +185,7 @@ export default function ProductDetailScreen() {
|
|
|
184
185
|
|
|
185
186
|
// Main content
|
|
186
187
|
return (
|
|
187
|
-
<
|
|
188
|
+
<SafeAreaView className="flex-1" style={{ backgroundColor: isDark ? '#000' : '#F9FAFB' }}>
|
|
188
189
|
<ScrollView>
|
|
189
190
|
{/* Product Image */}
|
|
190
191
|
{product.imageUrl && (
|
|
@@ -282,6 +283,6 @@ export default function ProductDetailScreen() {
|
|
|
282
283
|
</View>
|
|
283
284
|
</View>
|
|
284
285
|
</ScrollView>
|
|
285
|
-
</
|
|
286
|
+
</SafeAreaView>
|
|
286
287
|
);
|
|
287
288
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from 'react-native';
|
|
27
27
|
import { useTheme } from '@/hooks/useTheme';
|
|
28
28
|
import { useRouter } from 'expo-router';
|
|
29
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
29
30
|
|
|
30
31
|
// Example: News interface
|
|
31
32
|
interface NewsItem {
|
|
@@ -142,7 +143,7 @@ export default function NewsListScreen() {
|
|
|
142
143
|
|
|
143
144
|
// Main content
|
|
144
145
|
return (
|
|
145
|
-
<
|
|
146
|
+
<SafeAreaView className="flex-1" style={{ backgroundColor: isDark ? '#000' : '#F9FAFB' }}>
|
|
146
147
|
<FlatList
|
|
147
148
|
data={news}
|
|
148
149
|
keyExtractor={(item) => item.id}
|
|
@@ -189,6 +190,6 @@ export default function NewsListScreen() {
|
|
|
189
190
|
)}
|
|
190
191
|
contentContainerStyle={{ paddingBottom: 16 }}
|
|
191
192
|
/>
|
|
192
|
-
</
|
|
193
|
+
</SafeAreaView>
|
|
193
194
|
);
|
|
194
195
|
}
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { useTheme } from '@/hooks/useTheme';
|
|
28
28
|
import { useAuthStore } from '@/store/authStore';
|
|
29
29
|
import { useRouter } from 'expo-router';
|
|
30
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
30
31
|
|
|
31
32
|
// Example: User's task interface
|
|
32
33
|
interface Task {
|
|
@@ -190,7 +191,7 @@ export default function TaskListScreen() {
|
|
|
190
191
|
|
|
191
192
|
// Main content
|
|
192
193
|
return (
|
|
193
|
-
<
|
|
194
|
+
<SafeAreaView className="flex-1" style={{ backgroundColor: isDark ? '#000' : '#F9FAFB' }}>
|
|
194
195
|
{/* Header with user info */}
|
|
195
196
|
<View className={`px-4 py-4 ${isDark ? 'bg-gray-800' : 'bg-white'}`}>
|
|
196
197
|
<Text className={`text-lg font-bold ${isDark ? 'text-white' : 'text-gray-900'}`}>
|
|
@@ -245,6 +246,6 @@ export default function TaskListScreen() {
|
|
|
245
246
|
)}
|
|
246
247
|
contentContainerStyle={{ paddingBottom: 16 }}
|
|
247
248
|
/>
|
|
248
|
-
</
|
|
249
|
+
</SafeAreaView>
|
|
249
250
|
);
|
|
250
251
|
}
|