create-100x-mobile 0.2.0 → 0.2.2

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.
@@ -42,7 +42,7 @@ const emptyState_1 = require("../templates/components/emptyState");
42
42
  const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
43
43
  async function cmdNew(args) {
44
44
  (0, prompts_1.intro)(picocolors_1.default.bold(picocolors_1.default.cyan("create-100x-mobile")));
45
- // Step 1: Parse & validate project name
45
+ // ── Step 1: Parse & validate project name ──────────────
46
46
  let projectName = args[0];
47
47
  if (!projectName) {
48
48
  const nameInput = await (0, prompts_1.text)({
@@ -84,7 +84,7 @@ async function cmdNew(args) {
84
84
  }
85
85
  await (0, fs_1.removeDir)(projectDir);
86
86
  }
87
- // Step 2: Create directories
87
+ // ── Step 2: Create directories ─────────────────────────
88
88
  const s = (0, prompts_1.spinner)();
89
89
  s.start("Creating project structure");
90
90
  const dirs = [
@@ -99,7 +99,7 @@ async function cmdNew(args) {
99
99
  for (const dir of dirs) {
100
100
  await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
101
101
  }
102
- // Step 3: Write all template files
102
+ // ── Step 3: Write all template files ───────────────────
103
103
  const files = [
104
104
  // Config
105
105
  ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName)],
@@ -136,7 +136,7 @@ async function cmdNew(args) {
136
136
  await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, filePath), content);
137
137
  }
138
138
  s.stop("Project files created.");
139
- // Step 4: Install dependencies
139
+ // ── Step 4: Install dependencies ───────────────────────
140
140
  prompts_1.log.step("Installing dependencies");
141
141
  try {
142
142
  await (0, run_1.run)("bun", ["install"], { cwd: projectDir });
@@ -151,16 +151,8 @@ async function cmdNew(args) {
151
151
  }
152
152
  }
153
153
  prompts_1.log.success("Dependencies installed.");
154
- // Step 5: Initialize Convex
155
- prompts_1.log.step("Setting up Convex");
156
- try {
157
- await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
158
- prompts_1.log.success("Convex initialized.");
159
- }
160
- catch {
161
- prompts_1.log.warn("Convex initialization failed. Run `bunx convex dev --once` in your project directory.");
162
- }
163
- // Step 6: Prompt for Clerk keys
154
+ // ── Step 5: Prompt for Clerk keys ──────────────────────
155
+ // Moved BEFORE Convex init so we only need a single `convex dev --once` run.
164
156
  prompts_1.log.info("");
165
157
  prompts_1.log.info(picocolors_1.default.bold("Clerk Authentication Setup"));
166
158
  prompts_1.log.info(picocolors_1.default.dim("Get your keys from https://dashboard.clerk.com → Your App → API Keys"));
@@ -172,7 +164,7 @@ async function cmdNew(args) {
172
164
  });
173
165
  const clerkKeyValue = (0, prompts_1.isCancel)(clerkKey) || !clerkKey ? "" : clerkKey.trim();
174
166
  let clerkDomain = "";
175
- let clerkSecretKey = "";
167
+ let jwtCreated = false;
176
168
  if (clerkKeyValue) {
177
169
  // Try to extract domain from publishable key
178
170
  const derivedDomain = (0, clerk_1.extractDomainFromPublishableKey)(clerkKeyValue);
@@ -189,19 +181,32 @@ async function cmdNew(args) {
189
181
  clerkDomain =
190
182
  (0, prompts_1.isCancel)(domainInput) || !domainInput ? "" : domainInput.trim();
191
183
  }
192
- // Ask for secret key to auto-create JWT template
184
+ // Secret key prompt (optional — only needed to auto-create JWT template)
193
185
  prompts_1.log.info("");
194
186
  prompts_1.log.info(picocolors_1.default.dim('Provide your Clerk Secret Key to auto-create the "convex" JWT template.'));
195
- prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip (you can create it manually later)."));
187
+ prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip if you already have one."));
196
188
  const secretInput = await (0, prompts_1.text)({
197
189
  message: "Clerk Secret Key",
198
190
  placeholder: "sk_test_...",
199
191
  defaultValue: "",
200
192
  });
201
- clerkSecretKey =
202
- (0, prompts_1.isCancel)(secretInput) || !secretInput ? "" : secretInput.trim();
193
+ const clerkSecretKey = (0, prompts_1.isCancel)(secretInput) || !secretInput ? "" : secretInput.trim();
194
+ if (clerkSecretKey) {
195
+ const jwtSpinner = (0, prompts_1.spinner)();
196
+ jwtSpinner.start('Creating "convex" JWT template in Clerk');
197
+ const result = await (0, clerk_1.createConvexJwtTemplate)(clerkSecretKey);
198
+ if (result.success) {
199
+ jwtSpinner.stop('JWT template "convex" created in Clerk.');
200
+ jwtCreated = true;
201
+ }
202
+ else {
203
+ jwtSpinner.stop("Could not create JWT template.");
204
+ prompts_1.log.warn(`Clerk API: ${result.error}`);
205
+ prompts_1.log.info(picocolors_1.default.dim('You may need to create a JWT template named "convex" in your Clerk dashboard.'));
206
+ }
207
+ }
203
208
  }
204
- // Step 7: Write env vars
209
+ // ── Step 6: Write Clerk env vars to .env.local ─────────
205
210
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
206
211
  let envContents = "";
207
212
  try {
@@ -219,68 +224,80 @@ async function cmdNew(args) {
219
224
  if (envContents) {
220
225
  await (0, fs_1.writeTextFile)(envLocalPath, envContents);
221
226
  }
222
- // Step 8: Set Convex env var for Clerk
223
- if (clerkDomain) {
224
- prompts_1.log.step("Setting Convex environment variable");
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");
231
+ try {
232
+ await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
233
+ prompts_1.log.success("Convex initialized.");
234
+ }
235
+ catch {
236
+ prompts_1.log.warn("Convex setup needs attention.");
237
+ prompts_1.log.info(picocolors_1.default.dim(" Run manually: cd " + projectName + " && bunx convex dev --once"));
238
+ }
239
+ // ── Step 8: Ensure EXPO_PUBLIC_CONVEX_URL is set ───────
240
+ // Convex CLI saves the URL as CONVEX_URL, but Expo needs EXPO_PUBLIC_CONVEX_URL.
241
+ try {
242
+ let updatedEnv = "";
225
243
  try {
226
- await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
227
- prompts_1.log.success("Convex environment variable set.");
244
+ updatedEnv = await (0, fs_2.readTextFile)(envLocalPath);
228
245
  }
229
246
  catch {
230
- prompts_1.log.warn(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerkDomain}`);
247
+ updatedEnv = "";
231
248
  }
232
- }
233
- // Step 8b: Auto-create JWT template if secret key provided
234
- let jwtCreated = false;
235
- if (clerkSecretKey) {
236
- const jwtSpinner = (0, prompts_1.spinner)();
237
- jwtSpinner.start('Creating "convex" JWT template in Clerk');
238
- const result = await (0, clerk_1.createConvexJwtTemplate)(clerkSecretKey);
239
- if (result.success) {
240
- jwtSpinner.stop('JWT template "convex" created in Clerk.');
241
- jwtCreated = true;
242
- }
243
- else {
244
- jwtSpinner.stop("Could not create JWT template.");
245
- prompts_1.log.warn(`Clerk API error: ${result.error}`);
246
- prompts_1.log.warn('Create a JWT template named "convex" manually in Clerk dashboard.');
249
+ if (updatedEnv && !updatedEnv.includes("EXPO_PUBLIC_CONVEX_URL")) {
250
+ const parsed = (0, dotenv_1.parseDotenv)(updatedEnv);
251
+ let convexUrl = parsed["CONVEX_URL"] || "";
252
+ if (!convexUrl) {
253
+ // Try the convex url command as fallback
254
+ try {
255
+ convexUrl = await (0, run_1.runCapture)("bunx", ["convex", "url"], {
256
+ cwd: projectDir,
257
+ });
258
+ }
259
+ catch {
260
+ // Non-critical
261
+ }
262
+ }
263
+ if (convexUrl) {
264
+ updatedEnv = (0, dotenv_1.upsertDotenvVar)(updatedEnv, "EXPO_PUBLIC_CONVEX_URL", convexUrl);
265
+ await (0, fs_1.writeTextFile)(envLocalPath, updatedEnv);
266
+ prompts_1.log.success(`Convex URL configured for Expo.`);
267
+ }
268
+ else {
269
+ prompts_1.log.info(picocolors_1.default.dim(" Note: Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
270
+ }
247
271
  }
248
272
  }
249
- // Step 9: Push Convex functions (if Clerk was configured)
250
- if (clerkKeyValue && clerkDomain) {
251
- prompts_1.log.step("Deploying Convex functions");
273
+ catch {
274
+ // Non-critical user can set manually
275
+ }
276
+ // ── Step 9: Set Convex env var for Clerk ───────────────
277
+ if (clerkDomain) {
278
+ prompts_1.log.step("Setting Convex environment variable");
252
279
  try {
253
- await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
254
- prompts_1.log.success("Convex functions deployed.");
280
+ await (0, run_1.run)("bunx", ["convex", "env", "set", "CLERK_JWT_ISSUER_DOMAIN", clerkDomain], { cwd: projectDir });
281
+ prompts_1.log.success("Convex environment variable set.");
255
282
  }
256
283
  catch {
257
- prompts_1.log.warn("Run `bunx convex dev --once` in your project directory.");
284
+ prompts_1.log.info(picocolors_1.default.dim(` Note: Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerkDomain}`));
258
285
  }
259
286
  }
260
- // Step 10: Success message
287
+ // ── Step 10: Success message ───────────────────────────
261
288
  prompts_1.log.info("");
262
- if (clerkKeyValue && clerkDomain && jwtCreated) {
263
- // Fully automatedeverything is set up
289
+ if (clerkKeyValue && clerkDomain) {
290
+ // Clerk is configured app is ready
264
291
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
265
292
  prompts_1.log.info("");
266
293
  prompts_1.log.info(" Next steps:");
267
294
  prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
268
295
  prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
269
296
  prompts_1.log.info("");
270
- prompts_1.log.info(picocolors_1.default.dim("Remember to enable Google and Apple OAuth providers in your Clerk dashboard."));
271
- }
272
- else if (clerkKeyValue && clerkDomain) {
273
- // Keys set but JWT template not auto-created
274
- prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Almost ready!")));
275
- prompts_1.log.info("");
276
- prompts_1.log.info(" One more step:");
277
- prompts_1.log.info(` 1. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
278
- prompts_1.log.info(picocolors_1.default.dim(" Dashboard → JWT Templates → New template → Name: convex"));
279
- prompts_1.log.info(` 2. Enable Google and Apple OAuth in Clerk dashboard`);
280
- prompts_1.log.info("");
281
- prompts_1.log.info(" Then start your app:");
282
- prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
283
- prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
297
+ if (!jwtCreated) {
298
+ prompts_1.log.info(picocolors_1.default.dim(' Note: Ensure a JWT template named "convex" exists in your Clerk dashboard.'));
299
+ }
300
+ prompts_1.log.info(picocolors_1.default.dim(" Remember to enable Google and Apple OAuth providers in your Clerk dashboard."));
284
301
  }
285
302
  else {
286
303
  // No Clerk keys — manual setup needed
@@ -296,7 +313,6 @@ async function cmdNew(args) {
296
313
  prompts_1.log.info(` 4. Set the Convex env var:`);
297
314
  prompts_1.log.info(` ${picocolors_1.default.dim("bunx convex env set CLERK_JWT_ISSUER_DOMAIN <domain>")}`);
298
315
  prompts_1.log.info(` 5. Create a JWT template named ${picocolors_1.default.bold('"convex"')} in Clerk dashboard`);
299
- prompts_1.log.info(` 6. Deploy Convex: ${picocolors_1.default.dim("bunx convex dev --once")}`);
300
316
  prompts_1.log.info("");
301
317
  prompts_1.log.info(" Then start your app:");
302
318
  prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
package/dist/lib/run.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.run = run;
4
+ exports.runCapture = runCapture;
4
5
  const node_child_process_1 = require("node:child_process");
5
6
  async function run(cmd, args, options = {}) {
6
7
  await new Promise((resolve, reject) => {
@@ -19,3 +20,21 @@ async function run(cmd, args, options = {}) {
19
20
  });
20
21
  });
21
22
  }
23
+ async function runCapture(cmd, args, options = {}) {
24
+ return new Promise((resolve, reject) => {
25
+ const child = (0, node_child_process_1.spawn)(cmd, args, {
26
+ cwd: options.cwd,
27
+ env: options.env ?? process.env,
28
+ stdio: ["ignore", "pipe", "pipe"],
29
+ shell: process.platform === "win32",
30
+ });
31
+ let stdout = "";
32
+ child.stdout?.on("data", (data) => (stdout += data));
33
+ child.on("error", reject);
34
+ child.on("exit", (code) => {
35
+ if (code === 0)
36
+ return resolve(stdout.trim());
37
+ reject(new Error(`${cmd} exited with code ${code}`));
38
+ });
39
+ });
40
+ }
@@ -53,11 +53,11 @@ export default function SettingsScreen() {
53
53
  } catch {}
54
54
 
55
55
  await signOut();
56
- } catch (error) {
57
- console.error("Sign out error:", error);
58
- Alert.alert("Error", "Failed to sign out. Please try again.");
59
- } finally {
60
- setIsSigningOut(false);
56
+ // Sign-out succeeded — navigation will happen automatically
57
+ } catch {
58
+ // After signOut(), the session is cleared and the app navigates away.
59
+ // This may cause errors as the component unmounts — that's expected.
60
+ // Only a real pre-signout failure would leave the user stuck here.
61
61
  }
62
62
  };
63
63
 
@@ -16,6 +16,7 @@ import { SafeAreaView } from "react-native-safe-area-context";
16
16
  import { useOAuth } from "@clerk/clerk-expo";
17
17
  import { useRouter } from "expo-router";
18
18
  import { SquareCheck as CheckSquare } from "lucide-react-native";
19
+ import Svg, { Path } from "react-native-svg";
19
20
  import * as WebBrowser from "expo-web-browser";
20
21
 
21
22
  WebBrowser.maybeCompleteAuthSession();
@@ -83,7 +84,14 @@ export default function SignInScreen() {
83
84
  <ActivityIndicator size={20} color="#1F2937" />
84
85
  ) : (
85
86
  <>
86
- <Text style={styles.googleIcon}>G</Text>
87
+ <View style={styles.iconWrap}>
88
+ <Svg width={20} height={20} viewBox="0 0 24 24">
89
+ <Path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4" />
90
+ <Path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
91
+ <Path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
92
+ <Path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
93
+ </Svg>
94
+ </View>
87
95
  <Text style={styles.googleText}>Continue with Google</Text>
88
96
  </>
89
97
  )}
@@ -103,7 +111,11 @@ export default function SignInScreen() {
103
111
  <ActivityIndicator size={20} color="#FFFFFF" />
104
112
  ) : (
105
113
  <>
106
- <Text style={styles.appleIcon}>\u{F8FF}</Text>
114
+ <View style={styles.iconWrap}>
115
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="#FFFFFF">
116
+ <Path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" />
117
+ </Svg>
118
+ </View>
107
119
  <Text style={styles.appleText}>Continue with Apple</Text>
108
120
  </>
109
121
  )}
@@ -172,12 +184,6 @@ const styles = StyleSheet.create({
172
184
  borderWidth: 1.5,
173
185
  borderColor: "#E5E7EB",
174
186
  },
175
- googleIcon: {
176
- fontSize: 20,
177
- fontWeight: "700",
178
- color: "#4285F4",
179
- marginRight: 12,
180
- },
181
187
  googleText: {
182
188
  fontSize: 16,
183
189
  fontWeight: "600",
@@ -186,16 +192,18 @@ const styles = StyleSheet.create({
186
192
  appleButton: {
187
193
  backgroundColor: "#000000",
188
194
  },
189
- appleIcon: {
190
- fontSize: 20,
191
- color: "#FFFFFF",
192
- marginRight: 12,
193
- },
194
195
  appleText: {
195
196
  fontSize: 16,
196
197
  fontWeight: "600",
197
198
  color: "#FFFFFF",
198
199
  },
200
+ iconWrap: {
201
+ width: 24,
202
+ height: 24,
203
+ alignItems: "center",
204
+ justifyContent: "center",
205
+ marginRight: 12,
206
+ },
199
207
  terms: {
200
208
  fontSize: 13,
201
209
  color: "#9CA3AF",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-100x-mobile",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Scaffold a full-stack mobile app with Expo + Convex + Clerk in seconds",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {