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.
- package/dist/commands/new.js +81 -65
- package/dist/lib/run.js +19 -0
- package/dist/templates/app/settingsScreen.js +5 -5
- package/dist/templates/app/signIn.js +21 -13
- package/package.json +1 -1
package/dist/commands/new.js
CHANGED
|
@@ -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:
|
|
155
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
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,
|
|
227
|
-
prompts_1.log.success("Convex environment variable set.");
|
|
244
|
+
updatedEnv = await (0, fs_2.readTextFile)(envLocalPath);
|
|
228
245
|
}
|
|
229
246
|
catch {
|
|
230
|
-
|
|
247
|
+
updatedEnv = "";
|
|
231
248
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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", "
|
|
254
|
-
prompts_1.log.success("Convex
|
|
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.
|
|
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
|
|
263
|
-
//
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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",
|