create-100x-mobile 0.2.2 → 0.3.0

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.
@@ -19,6 +19,9 @@ const appJson_1 = require("../templates/config/appJson");
19
19
  const tsconfig_1 = require("../templates/config/tsconfig");
20
20
  const gitignore_1 = require("../templates/config/gitignore");
21
21
  const envExample_1 = require("../templates/config/envExample");
22
+ const prettierrc_1 = require("../templates/config/prettierrc");
23
+ const easJson_1 = require("../templates/config/easJson");
24
+ const readme_1 = require("../templates/config/readme");
22
25
  // Convex templates
23
26
  const schema_1 = require("../templates/convex/schema");
24
27
  const todos_1 = require("../templates/convex/todos");
@@ -84,9 +87,19 @@ async function cmdNew(args) {
84
87
  }
85
88
  await (0, fs_1.removeDir)(projectDir);
86
89
  }
87
- // ── Step 2: Create directories ─────────────────────────
90
+ // ── Determine total steps ────────────────────────────────
91
+ // Steps: 1=Project structure, 2=Dependencies, 3=Convex setup,
92
+ // 4=Convex env (if clerkDomain), 5=Git init, 6=Health check
93
+ // We'll calculate totalSteps after Clerk prompts, but we create the
94
+ // counter now. We'll adjust totalSteps after Clerk prompts.
95
+ let currentStep = 0;
96
+ let totalSteps = 5; // base: structure, deps, convex, git, health check
97
+ // totalSteps += 1 if clerkDomain (convex env step) — adjusted after Clerk prompts
98
+ const stepLabel = () => `(${currentStep}/${totalSteps})`;
99
+ // ── Step: Create directories ─────────────────────────
88
100
  const s = (0, prompts_1.spinner)();
89
- s.start("Creating project structure");
101
+ currentStep++;
102
+ s.start(`Creating project structure ${stepLabel()}`);
90
103
  const dirs = [
91
104
  "app/(auth)",
92
105
  "app/(tabs)",
@@ -108,6 +121,9 @@ async function cmdNew(args) {
108
121
  [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
109
122
  [".env.example", (0, envExample_1.envExampleTemplate)()],
110
123
  ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
124
+ [".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
125
+ ["eas.json", (0, easJson_1.easJsonTemplate)()],
126
+ ["README.md", (0, readme_1.readmeTemplate)(projectName)],
111
127
  // Convex
112
128
  ["convex/schema.ts", (0, schema_1.schemaTemplate)()],
113
129
  ["convex/todos.ts", (0, todos_1.todosTemplate)()],
@@ -136,8 +152,9 @@ async function cmdNew(args) {
136
152
  await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, filePath), content);
137
153
  }
138
154
  s.stop("Project files created.");
139
- // ── Step 4: Install dependencies ───────────────────────
140
- prompts_1.log.step("Installing dependencies");
155
+ // ── Step: Install dependencies ───────────────────────
156
+ currentStep++;
157
+ prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
141
158
  try {
142
159
  await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
143
160
  }
@@ -206,7 +223,11 @@ async function cmdNew(args) {
206
223
  }
207
224
  }
208
225
  }
209
- // ── Step 6: Write Clerk env vars to .env.local ─────────
226
+ // Adjust total steps if Clerk domain is configured
227
+ if (clerkDomain) {
228
+ totalSteps = 6; // adds convex env step
229
+ }
230
+ // ── Write Clerk env vars to .env.local ─────────
210
231
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
211
232
  let envContents = "";
212
233
  try {
@@ -224,10 +245,15 @@ async function cmdNew(args) {
224
245
  if (envContents) {
225
246
  await (0, fs_1.writeTextFile)(envLocalPath, envContents);
226
247
  }
227
- // ── Step 7: Initialize Convex (single run) ─────────────
228
- // Only runs once. Convex env vars (set in Step 8) are read at runtime,
229
- // so no re-deploy is needed after setting them.
230
- prompts_1.log.step("Setting up Convex");
248
+ // ── Step 6b: Hardcode domain in auth.config.ts ─────────
249
+ // Convex auth.config.ts is evaluated at deploy time, so process.env
250
+ // won't have the value yet (we set it after deploy). Hardcode it.
251
+ if (clerkDomain) {
252
+ await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, "convex/auth.config.ts"), `export default {\n providers: [\n {\n domain: "${clerkDomain}",\n applicationID: "convex",\n },\n ],\n};\n`);
253
+ }
254
+ // ── Step: Initialize Convex (single run) ─────────────
255
+ currentStep++;
256
+ prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
231
257
  try {
232
258
  await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
233
259
  prompts_1.log.success("Convex initialized.");
@@ -273,9 +299,10 @@ async function cmdNew(args) {
273
299
  catch {
274
300
  // Non-critical — user can set manually
275
301
  }
276
- // ── Step 9: Set Convex env var for Clerk ───────────────
302
+ // ── Step: Set Convex env var for Clerk ───────────────
277
303
  if (clerkDomain) {
278
- prompts_1.log.step("Setting Convex environment variable");
304
+ currentStep++;
305
+ prompts_1.log.step(`Setting Convex environment variable ${stepLabel()}`);
279
306
  try {
280
307
  await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
281
308
  prompts_1.log.success("Convex environment variable set.");
@@ -284,7 +311,65 @@ async function cmdNew(args) {
284
311
  prompts_1.log.info(picocolors_1.default.dim(` Note: Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerkDomain}`));
285
312
  }
286
313
  }
287
- // ── Step 10: Success message ───────────────────────────
314
+ // ── Step: Git init ───────────────────────────────────
315
+ currentStep++;
316
+ prompts_1.log.step(`Initializing git repository ${stepLabel()}`);
317
+ try {
318
+ await (0, run_1.run)("git", ["init"], { cwd: projectDir });
319
+ await (0, run_1.run)("git", ["add", "-A"], { cwd: projectDir });
320
+ await (0, run_1.run)("git", ["commit", "-m", "Initial commit from create-100x-mobile"], {
321
+ cwd: projectDir,
322
+ });
323
+ prompts_1.log.success("Git repository initialized.");
324
+ }
325
+ catch {
326
+ prompts_1.log.info(picocolors_1.default.dim(" Note: git is not available. You can initialize a repo later."));
327
+ }
328
+ // ── Step: Health check ──────────────────────────────
329
+ currentStep++;
330
+ prompts_1.log.step(`Running health check ${stepLabel()}`);
331
+ const healthChecks = [];
332
+ // Check .env.local exists
333
+ const envExists = await (0, fs_1.pathExists)(envLocalPath);
334
+ healthChecks.push({ label: ".env.local exists", ok: envExists });
335
+ // Check EXPO_PUBLIC_CONVEX_URL is set
336
+ if (envExists) {
337
+ let envContent = "";
338
+ try {
339
+ envContent = await (0, fs_2.readTextFile)(envLocalPath);
340
+ }
341
+ catch {
342
+ // ignore
343
+ }
344
+ healthChecks.push({
345
+ label: "EXPO_PUBLIC_CONVEX_URL is set",
346
+ ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
347
+ });
348
+ if (clerkKeyValue) {
349
+ healthChecks.push({
350
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
351
+ ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
352
+ });
353
+ }
354
+ }
355
+ else {
356
+ healthChecks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
357
+ if (clerkKeyValue) {
358
+ healthChecks.push({
359
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
360
+ ok: false,
361
+ });
362
+ }
363
+ }
364
+ for (const check of healthChecks) {
365
+ if (check.ok) {
366
+ prompts_1.log.info(` ${picocolors_1.default.green("✓")} ${check.label}`);
367
+ }
368
+ else {
369
+ prompts_1.log.info(` ${picocolors_1.default.yellow("⚠")} ${check.label}`);
370
+ }
371
+ }
372
+ // ── Success message ───────────────────────────────
288
373
  prompts_1.log.info("");
289
374
  if (clerkKeyValue && clerkDomain) {
290
375
  // Clerk is configured — app is ready
@@ -2,8 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.todosScreenTemplate = todosScreenTemplate;
4
4
  function todosScreenTemplate() {
5
- return `import React, { useState } from "react";
6
- import { View, Text, ScrollView, StyleSheet, StatusBar } from "react-native";
5
+ return `import React, { useState, useCallback } from "react";
6
+ import {
7
+ View,
8
+ Text,
9
+ FlatList,
10
+ KeyboardAvoidingView,
11
+ Platform,
12
+ StyleSheet,
13
+ StatusBar,
14
+ } from "react-native";
15
+ import Animated, { LinearTransition } from "react-native-reanimated";
7
16
  import { SafeAreaView } from "react-native-safe-area-context";
8
17
  import { AddTodoForm } from "@/components/AddTodoForm";
9
18
  import { TodoItem } from "@/components/TodoItem";
@@ -23,6 +32,8 @@ export interface Todo {
23
32
 
24
33
  type FilterType = "all" | "active" | "completed";
25
34
 
35
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList<Todo>);
36
+
26
37
  export default function TodosScreen() {
27
38
  const [filter, setFilter] = useState<FilterType>("all");
28
39
 
@@ -73,47 +84,53 @@ export default function TodosScreen() {
73
84
  (todo: Todo) => todo.completed
74
85
  ).length;
75
86
 
87
+ const renderItem = useCallback(
88
+ ({ item }: { item: Todo }) => (
89
+ <TodoItem todo={item} onToggle={toggleTodo} onDelete={deleteTodo} />
90
+ ),
91
+ [toggleTodo, deleteTodo]
92
+ );
93
+
94
+ const keyExtractor = useCallback((item: Todo) => item._id, []);
95
+
76
96
  return (
77
97
  <SafeAreaView style={styles.container}>
78
98
  <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
79
-
80
- <View style={styles.header}>
81
- <Text style={styles.title}>My Tasks</Text>
82
- <Text style={styles.subtitle}>
83
- {activeTodosCount} active, {completedTodosCount} completed
84
- </Text>
85
- </View>
86
-
87
- <AddTodoForm onAddTodo={addTodo} />
88
-
89
- <FilterTabs
90
- filter={filter}
91
- onFilterChange={setFilter}
92
- counts={{
93
- all: todos.length,
94
- active: activeTodosCount,
95
- completed: completedTodosCount,
96
- }}
97
- />
98
-
99
- <ScrollView
100
- style={styles.todosContainer}
101
- contentContainerStyle={styles.todosContent}
102
- showsVerticalScrollIndicator={false}
99
+ <KeyboardAvoidingView
100
+ style={styles.keyboardAvoidingView}
101
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
103
102
  >
104
- {filteredTodos.length === 0 ? (
105
- <EmptyState filter={filter} />
106
- ) : (
107
- filteredTodos.map((todo) => (
108
- <TodoItem
109
- key={todo._id}
110
- todo={todo}
111
- onToggle={toggleTodo}
112
- onDelete={deleteTodo}
113
- />
114
- ))
115
- )}
116
- </ScrollView>
103
+ <View style={styles.header}>
104
+ <Text style={styles.title}>My Tasks</Text>
105
+ <Text style={styles.subtitle}>
106
+ {activeTodosCount} active, {completedTodosCount} completed
107
+ </Text>
108
+ </View>
109
+
110
+ <AddTodoForm onAddTodo={addTodo} />
111
+
112
+ <FilterTabs
113
+ filter={filter}
114
+ onFilterChange={setFilter}
115
+ counts={{
116
+ all: todos.length,
117
+ active: activeTodosCount,
118
+ completed: completedTodosCount,
119
+ }}
120
+ />
121
+
122
+ <AnimatedFlatList
123
+ data={filteredTodos}
124
+ renderItem={renderItem}
125
+ keyExtractor={keyExtractor}
126
+ style={styles.todosContainer}
127
+ contentContainerStyle={styles.todosContent}
128
+ showsVerticalScrollIndicator={false}
129
+ itemLayoutAnimation={LinearTransition.springify()}
130
+ ListEmptyComponent={<EmptyState filter={filter} />}
131
+ keyboardShouldPersistTaps="handled"
132
+ />
133
+ </KeyboardAvoidingView>
117
134
  </SafeAreaView>
118
135
  );
119
136
  }
@@ -123,6 +140,9 @@ const styles = StyleSheet.create({
123
140
  flex: 1,
124
141
  backgroundColor: "#FFFFFF",
125
142
  },
143
+ keyboardAvoidingView: {
144
+ flex: 1,
145
+ },
126
146
  header: {
127
147
  paddingHorizontal: 16,
128
148
  paddingTop: 24,
@@ -4,6 +4,12 @@ exports.addTodoFormTemplate = addTodoFormTemplate;
4
4
  function addTodoFormTemplate() {
5
5
  return `import React, { useState } from "react";
6
6
  import { View, TextInput, TouchableOpacity, StyleSheet } from "react-native";
7
+ import Animated, {
8
+ useSharedValue,
9
+ useAnimatedStyle,
10
+ withSpring,
11
+ } from "react-native-reanimated";
12
+ import * as Haptics from "expo-haptics";
7
13
  import { Plus } from "lucide-react-native";
8
14
 
9
15
  interface AddTodoFormProps {
@@ -12,9 +18,18 @@ interface AddTodoFormProps {
12
18
 
13
19
  export function AddTodoForm({ onAddTodo }: AddTodoFormProps) {
14
20
  const [text, setText] = useState("");
21
+ const buttonScale = useSharedValue(1);
22
+
23
+ const buttonAnimatedStyle = useAnimatedStyle(() => ({
24
+ transform: [{ scale: buttonScale.value }],
25
+ }));
15
26
 
16
27
  const handleSubmit = () => {
17
28
  if (text.trim()) {
29
+ buttonScale.value = withSpring(0.9, { damping: 15 }, () => {
30
+ buttonScale.value = withSpring(1, { damping: 15 });
31
+ });
32
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
18
33
  onAddTodo(text);
19
34
  setText("");
20
35
  }
@@ -25,7 +40,7 @@ export function AddTodoForm({ onAddTodo }: AddTodoFormProps) {
25
40
  <View style={styles.inputContainer}>
26
41
  <TextInput
27
42
  style={styles.input}
28
- placeholder="Add a new task..."
43
+ placeholder="Add a new task\u2026"
29
44
  placeholderTextColor="#9CA3AF"
30
45
  value={text}
31
46
  onChangeText={setText}
@@ -33,18 +48,20 @@ export function AddTodoForm({ onAddTodo }: AddTodoFormProps) {
33
48
  returnKeyType="done"
34
49
  multiline={false}
35
50
  />
36
- <TouchableOpacity
37
- style={[styles.addButton, !text.trim() && styles.addButtonDisabled]}
38
- onPress={handleSubmit}
39
- disabled={!text.trim()}
40
- activeOpacity={0.7}
41
- >
42
- <Plus
43
- size={20}
44
- color={text.trim() ? "#FFFFFF" : "#9CA3AF"}
45
- strokeWidth={2.5}
46
- />
47
- </TouchableOpacity>
51
+ <Animated.View style={buttonAnimatedStyle}>
52
+ <TouchableOpacity
53
+ style={[styles.addButton, !text.trim() && styles.addButtonDisabled]}
54
+ onPress={handleSubmit}
55
+ disabled={!text.trim()}
56
+ activeOpacity={0.7}
57
+ >
58
+ <Plus
59
+ size={20}
60
+ color={text.trim() ? "#FFFFFF" : "#9CA3AF"}
61
+ strokeWidth={2.5}
62
+ />
63
+ </TouchableOpacity>
64
+ </Animated.View>
48
65
  </View>
49
66
  </View>
50
67
  );
@@ -4,6 +4,7 @@ exports.filterTabsTemplate = filterTabsTemplate;
4
4
  function filterTabsTemplate() {
5
5
  return `import React from "react";
6
6
  import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
7
+ import * as Haptics from "expo-haptics";
7
8
 
8
9
  type FilterType = "all" | "active" | "completed";
9
10
 
@@ -24,13 +25,18 @@ export function FilterTabs({ filter, onFilterChange, counts }: FilterTabsProps)
24
25
  { key: "completed", label: "Done" },
25
26
  ];
26
27
 
28
+ const handlePress = (key: FilterType) => {
29
+ Haptics.selectionAsync();
30
+ onFilterChange(key);
31
+ };
32
+
27
33
  return (
28
34
  <View style={styles.container}>
29
35
  {tabs.map((tab) => (
30
36
  <TouchableOpacity
31
37
  key={tab.key}
32
38
  style={[styles.tab, filter === tab.key && styles.tabActive]}
33
- onPress={() => onFilterChange(tab.key)}
39
+ onPress={() => handlePress(tab.key)}
34
40
  activeOpacity={0.7}
35
41
  >
36
42
  <Text
@@ -4,6 +4,13 @@ exports.todoItemTemplate = todoItemTemplate;
4
4
  function todoItemTemplate() {
5
5
  return `import React from "react";
6
6
  import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
7
+ import Animated, {
8
+ FadeInDown,
9
+ useSharedValue,
10
+ useAnimatedStyle,
11
+ withSpring,
12
+ } from "react-native-reanimated";
13
+ import * as Haptics from "expo-haptics";
7
14
  import { Check, X } from "lucide-react-native";
8
15
  import type { Todo } from "@/app/(tabs)/index";
9
16
  import { Id } from "../convex/_generated/dataModel";
@@ -15,38 +22,63 @@ interface TodoItemProps {
15
22
  }
16
23
 
17
24
  export function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
25
+ const checkboxScale = useSharedValue(1);
26
+
27
+ const checkboxAnimatedStyle = useAnimatedStyle(() => ({
28
+ transform: [{ scale: checkboxScale.value }],
29
+ }));
30
+
31
+ const handleToggle = () => {
32
+ checkboxScale.value = withSpring(0.85, { damping: 15 }, () => {
33
+ checkboxScale.value = withSpring(1, { damping: 15 });
34
+ });
35
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
36
+ onToggle(todo._id);
37
+ };
38
+
39
+ const handleDelete = () => {
40
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
41
+ onDelete(todo._id);
42
+ };
43
+
18
44
  return (
19
- <View style={styles.container}>
20
- <TouchableOpacity
21
- style={styles.checkboxHitArea}
22
- onPress={() => onToggle(todo._id)}
23
- activeOpacity={0.7}
24
- >
25
- <View
26
- style={[styles.checkbox, todo.completed && styles.checkboxCompleted]}
45
+ <Animated.View entering={FadeInDown.duration(300).springify()}>
46
+ <View style={styles.container}>
47
+ <TouchableOpacity
48
+ style={styles.checkboxHitArea}
49
+ onPress={handleToggle}
50
+ activeOpacity={0.7}
27
51
  >
28
- {todo.completed && (
29
- <Check size={14} color="#FFFFFF" strokeWidth={3} />
30
- )}
52
+ <Animated.View
53
+ style={[
54
+ styles.checkbox,
55
+ todo.completed && styles.checkboxCompleted,
56
+ checkboxAnimatedStyle,
57
+ ]}
58
+ >
59
+ {todo.completed && (
60
+ <Check size={14} color="#FFFFFF" strokeWidth={3} />
61
+ )}
62
+ </Animated.View>
63
+ </TouchableOpacity>
64
+
65
+ <View style={styles.content}>
66
+ <Text
67
+ style={[styles.text, todo.completed && styles.textCompleted]}
68
+ >
69
+ {todo.text}
70
+ </Text>
31
71
  </View>
32
- </TouchableOpacity>
33
72
 
34
- <View style={styles.content}>
35
- <Text
36
- style={[styles.text, todo.completed && styles.textCompleted]}
73
+ <TouchableOpacity
74
+ style={styles.deleteHitArea}
75
+ onPress={handleDelete}
76
+ activeOpacity={0.7}
37
77
  >
38
- {todo.text}
39
- </Text>
78
+ <X size={18} color="#9CA3AF" strokeWidth={2} />
79
+ </TouchableOpacity>
40
80
  </View>
41
-
42
- <TouchableOpacity
43
- style={styles.deleteHitArea}
44
- onPress={() => onDelete(todo._id)}
45
- activeOpacity={0.7}
46
- >
47
- <X size={18} color="#9CA3AF" strokeWidth={2} />
48
- </TouchableOpacity>
49
- </View>
81
+ </Animated.View>
50
82
  );
51
83
  }
52
84
 
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.easJsonTemplate = easJsonTemplate;
4
+ function easJsonTemplate() {
5
+ const config = {
6
+ cli: {
7
+ version: ">= 15.0.0",
8
+ },
9
+ build: {
10
+ development: {
11
+ developmentClient: true,
12
+ distribution: "internal",
13
+ },
14
+ preview: {
15
+ distribution: "internal",
16
+ },
17
+ production: {},
18
+ },
19
+ submit: {
20
+ production: {},
21
+ },
22
+ };
23
+ return JSON.stringify(config, null, 2) + "\n";
24
+ }
@@ -11,6 +11,7 @@ function packageJsonTemplate(appName) {
11
11
  dev: "EXPO_NO_TELEMETRY=1 expo start",
12
12
  "build:web": "expo export --platform web",
13
13
  lint: "expo lint",
14
+ format: "prettier --write .",
14
15
  },
15
16
  dependencies: {
16
17
  "@clerk/clerk-expo": "^2.14.24",
@@ -23,6 +24,7 @@ function packageJsonTemplate(appName) {
23
24
  "expo-blur": "~15.0.8",
24
25
  "expo-constants": "~18.0.13",
25
26
  "expo-font": "~14.0.10",
27
+ "expo-haptics": "~14.0.4",
26
28
  "expo-linking": "~8.0.11",
27
29
  "expo-router": "~6.0.21",
28
30
  "expo-secure-store": "~15.0.8",
@@ -46,6 +48,7 @@ function packageJsonTemplate(appName) {
46
48
  "@types/react": "~19.1.10",
47
49
  eslint: "^9.0.0",
48
50
  "eslint-config-expo": "~10.0.0",
51
+ prettier: "^3.4.2",
49
52
  typescript: "~5.9.2",
50
53
  },
51
54
  };
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prettierrcTemplate = prettierrcTemplate;
4
+ function prettierrcTemplate() {
5
+ const config = {
6
+ semi: true,
7
+ singleQuote: false,
8
+ tabWidth: 2,
9
+ trailingComma: "es5",
10
+ };
11
+ return JSON.stringify(config, null, 2) + "\n";
12
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readmeTemplate = readmeTemplate;
4
+ function readmeTemplate(projectName) {
5
+ return `# ${projectName}
6
+
7
+ A full-stack mobile app built with **Expo**, **Convex**, and **Clerk**.
8
+
9
+ ## Tech Stack
10
+
11
+ - **[Expo](https://expo.dev)** — React Native framework
12
+ - **[Convex](https://convex.dev)** — Backend & real-time database
13
+ - **[Clerk](https://clerk.com)** — Authentication (Google & Apple OAuth)
14
+ - **[Expo Router](https://docs.expo.dev/router/introduction/)** — File-based navigation
15
+ - **[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)** — Animations
16
+
17
+ ## Getting Started
18
+
19
+ \`\`\`bash
20
+ # Install dependencies
21
+ bun install
22
+
23
+ # Start the Convex dev server
24
+ bunx convex dev
25
+
26
+ # Start Expo (in a separate terminal)
27
+ bunx expo start
28
+ \`\`\`
29
+
30
+ ## Project Structure
31
+
32
+ \`\`\`
33
+ ├── app/
34
+ │ ├── _layout.tsx # Root layout with providers
35
+ │ ├── +not-found.tsx # 404 screen
36
+ │ ├── (auth)/ # Auth screens (sign-in)
37
+ │ ├── (tabs)/ # Tab navigation (todos, settings)
38
+ │ └── providers/ # Auth provider
39
+ ├── components/ # Reusable UI components
40
+ ├── convex/ # Backend functions & schema
41
+ ├── hooks/ # Custom React hooks
42
+ └── assets/ # Images and static assets
43
+ \`\`\`
44
+
45
+ ## Scripts
46
+
47
+ | Command | Description |
48
+ |---------|-------------|
49
+ | \`bun run dev\` | Start Expo dev server |
50
+ | \`bun run lint\` | Run ESLint |
51
+ | \`bun run format\` | Format code with Prettier |
52
+ | \`bunx convex dev\` | Start Convex dev server |
53
+
54
+ ## Deployment
55
+
56
+ ### EAS Build
57
+
58
+ \`\`\`bash
59
+ # Development build
60
+ bunx eas build --profile development --platform ios
61
+
62
+ # Production build
63
+ bunx eas build --profile production --platform all
64
+ \`\`\`
65
+
66
+ ### Convex
67
+
68
+ \`\`\`bash
69
+ bunx convex deploy
70
+ \`\`\`
71
+
72
+ ## Learn More
73
+
74
+ - [Expo Docs](https://docs.expo.dev)
75
+ - [Convex Docs](https://docs.convex.dev)
76
+ - [Clerk Docs](https://clerk.com/docs)
77
+ `;
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-100x-mobile",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffold a full-stack mobile app with Expo + Convex + Clerk in seconds",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {