create-100x-mobile 0.4.10 → 0.5.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.
package/dist/cli.js CHANGED
@@ -17,13 +17,14 @@ Options:
17
17
  --no-install Skip dependency installation
18
18
  --skip-git Skip git init/commit
19
19
  --backend <provider> Backend: "convex" or "instantdb"
20
+ --instant-auth <mode> InstantDB auth: "clerk", "magic-code", or "magic-code-cloudflare"
20
21
  --sdk <value> Use "latest", "previous", or a specific version (e.g. 54.0.33)
21
22
  --clerk-pk <key> Pre-fill Clerk publishable key
22
23
  --clerk-domain <domain> Pre-fill Clerk issuer domain
23
24
  --instant-app-id <id> Pre-fill InstantDB app id
24
25
  --instant-clerk-client-name <name> Client name configured in Instant Auth tab
25
26
 
26
- Scaffolds a mobile app with Expo + Convex/Clerk or Expo + InstantDB.
27
+ Scaffolds a mobile app with Expo + Convex/Clerk, Expo + InstantDB, or InstantDB magic code + Cloudflare.
27
28
  `);
28
29
  }
29
30
  async function main() {
@@ -16,6 +16,21 @@ function getInlineOrNextValue(arg, args, i) {
16
16
  }
17
17
  return { value: nextValue.trim(), nextIndex: i + 1 };
18
18
  }
19
+ function parseInstantAuthMode(value) {
20
+ const normalized = value.trim().toLowerCase();
21
+ if (normalized === "clerk") {
22
+ return "clerk";
23
+ }
24
+ if (normalized === "magic-code" || normalized === "magic-link") {
25
+ return "magic-code";
26
+ }
27
+ if (normalized === "cloudflare" ||
28
+ normalized === "magic-code-cloudflare" ||
29
+ normalized === "magic-link-cloudflare") {
30
+ return "magic-code-cloudflare";
31
+ }
32
+ throw new Error(`Invalid --instant-auth value "${value}". Use "clerk", "magic-code", "magic-link", or "magic-code-cloudflare".`);
33
+ }
19
34
  function parseNewCommandArgs(args) {
20
35
  const options = {
21
36
  interactive: true,
@@ -23,6 +38,7 @@ function parseNewCommandArgs(args) {
23
38
  initializeGit: true,
24
39
  sdk: null,
25
40
  backend: null,
41
+ instantAuthMode: null,
26
42
  clerkPublishableKey: null,
27
43
  clerkDomain: null,
28
44
  instantAppId: null,
@@ -68,6 +84,12 @@ function parseNewCommandArgs(args) {
68
84
  i = nextIndex;
69
85
  continue;
70
86
  }
87
+ if (arg === "--instant-auth" || arg.startsWith("--instant-auth=")) {
88
+ const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
89
+ options.instantAuthMode = parseInstantAuthMode(value);
90
+ i = nextIndex;
91
+ continue;
92
+ }
71
93
  if (arg === "--clerk-pk" || arg.startsWith("--clerk-pk=")) {
72
94
  const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
73
95
  options.clerkPublishableKey = value;
@@ -98,5 +120,8 @@ function parseNewCommandArgs(args) {
98
120
  if (!projectName && !options.interactive) {
99
121
  projectName = "my-app";
100
122
  }
123
+ if (options.backend === "instantdb" && !options.instantAuthMode) {
124
+ options.instantAuthMode = "clerk";
125
+ }
101
126
  return { projectName, options };
102
127
  }
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isCloudflareInstantAuthMode = isCloudflareInstantAuthMode;
4
+ exports.isMagicCodeInstantAuthMode = isMagicCodeInstantAuthMode;
3
5
  exports.createProjectDirectories = createProjectDirectories;
4
6
  exports.writeScaffoldFiles = writeScaffoldFiles;
5
7
  exports.writeDefaultAssetFiles = writeDefaultAssetFiles;
@@ -7,16 +9,24 @@ const node_path_1 = require("node:path");
7
9
  const fs_1 = require("../../lib/fs");
8
10
  // 1x1 transparent PNG placeholder.
9
11
  const TRANSPARENT_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7+P5kAAAAASUVORK5CYII=";
10
- async function createProjectDirectories(projectDir, backend) {
11
- const commonDirs = [
12
- "app/(tabs)",
13
- "hooks",
14
- "assets/images",
15
- ];
12
+ function isCloudflareInstantAuthMode(instantAuthMode) {
13
+ return instantAuthMode === "magic-code-cloudflare";
14
+ }
15
+ function isMagicCodeInstantAuthMode(instantAuthMode) {
16
+ return (instantAuthMode === "magic-code" ||
17
+ instantAuthMode === "magic-code-cloudflare");
18
+ }
19
+ async function createProjectDirectories(projectDir, backend, instantAuthMode = "clerk") {
20
+ const commonDirs = ["app/(tabs)", "hooks", "assets/images"];
16
21
  const backendDirs = backend === "convex"
17
22
  ? ["app/(auth)", "app/providers", "components", "convex"]
18
- : ["app/(auth)", "app/providers", "lib"];
19
- const dirs = [...commonDirs, ...backendDirs];
23
+ : isMagicCodeInstantAuthMode(instantAuthMode)
24
+ ? ["app/(auth)", "lib"]
25
+ : ["app/(auth)", "app/providers", "lib"];
26
+ const cloudflareDirs = isCloudflareInstantAuthMode(instantAuthMode)
27
+ ? ["worker/src", "worker/migrations"]
28
+ : [];
29
+ const dirs = [...commonDirs, ...backendDirs, ...cloudflareDirs];
20
30
  for (const dir of dirs) {
21
31
  await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
22
32
  }
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.normalizeClerkDomain = normalizeClerkDomain;
7
7
  exports.toErrorMessage = toErrorMessage;
8
8
  exports.resolveBackendChoice = resolveBackendChoice;
9
+ exports.resolveInstantAuthMode = resolveInstantAuthMode;
9
10
  exports.resolveExpoVersion = resolveExpoVersion;
10
11
  exports.installDependencies = installDependencies;
12
+ exports.buildExpoInstallPackages = buildExpoInstallPackages;
11
13
  exports.resolveExpoDependencies = resolveExpoDependencies;
12
14
  exports.promptClerkSetup = promptClerkSetup;
13
15
  exports.promptInstantSetup = promptInstantSetup;
@@ -25,6 +27,7 @@ const clerk_1 = require("../../lib/clerk");
25
27
  const dotenv_1 = require("../../lib/dotenv");
26
28
  const fs_1 = require("../../lib/fs");
27
29
  const run_1 = require("../../lib/run");
30
+ const scaffold_1 = require("./scaffold");
28
31
  function normalizeClerkDomain(rawDomain) {
29
32
  const trimmed = rawDomain.trim();
30
33
  if (!trimmed)
@@ -89,6 +92,39 @@ async function resolveBackendChoice(options) {
89
92
  }
90
93
  return choice;
91
94
  }
95
+ async function resolveInstantAuthMode(options) {
96
+ if (options.instantAuthMode) {
97
+ return options.instantAuthMode;
98
+ }
99
+ if (!options.interactive) {
100
+ return "clerk";
101
+ }
102
+ const choice = await (0, prompts_1.select)({
103
+ message: "Which InstantDB auth flow do you want?",
104
+ options: [
105
+ {
106
+ value: "clerk",
107
+ label: "Clerk",
108
+ hint: "Current InstantDB + Clerk flow",
109
+ },
110
+ {
111
+ value: "magic-code",
112
+ label: "Magic code",
113
+ hint: "Email code auth with InstantDB",
114
+ },
115
+ {
116
+ value: "magic-code-cloudflare",
117
+ label: "Magic code + Cloudflare",
118
+ hint: "InstantDB realtime, R2 storage, D1 metadata",
119
+ },
120
+ ],
121
+ initialValue: "clerk",
122
+ });
123
+ if ((0, prompts_1.isCancel)(choice)) {
124
+ return "clerk";
125
+ }
126
+ return choice;
127
+ }
92
128
  async function resolveExpoVersion(options) {
93
129
  if (options.sdk) {
94
130
  const normalized = normalizeSdkArg(options.sdk);
@@ -175,7 +211,7 @@ async function installDependencies(projectDir) {
175
211
  }
176
212
  }
177
213
  }
178
- async function resolveExpoDependencies(projectDir, backend) {
214
+ function buildExpoInstallPackages(backend, instantAuthMode = "clerk") {
179
215
  const commonExpoPackages = [
180
216
  "react",
181
217
  "react-dom",
@@ -200,14 +236,22 @@ async function resolveExpoDependencies(projectDir, backend) {
200
236
  ];
201
237
  const backendExpoPackages = backend === "convex"
202
238
  ? ["expo-auth-session", "expo-secure-store", "expo-web-browser"]
203
- : [
204
- "expo-auth-session",
205
- "expo-secure-store",
206
- "expo-web-browser",
207
- "@react-native-async-storage/async-storage",
208
- "@react-native-community/netinfo",
209
- ];
210
- const expoPackages = [...commonExpoPackages, ...backendExpoPackages];
239
+ : (0, scaffold_1.isMagicCodeInstantAuthMode)(instantAuthMode)
240
+ ? [
241
+ "@react-native-async-storage/async-storage",
242
+ "@react-native-community/netinfo",
243
+ ]
244
+ : [
245
+ "expo-auth-session",
246
+ "expo-secure-store",
247
+ "expo-web-browser",
248
+ "@react-native-async-storage/async-storage",
249
+ "@react-native-community/netinfo",
250
+ ];
251
+ return [...commonExpoPackages, ...backendExpoPackages];
252
+ }
253
+ async function resolveExpoDependencies(projectDir, backend, instantAuthMode = "clerk") {
254
+ const expoPackages = buildExpoInstallPackages(backend, instantAuthMode);
211
255
  try {
212
256
  await (0, run_1.run)("npx", ["expo", "install", ...expoPackages], { cwd: projectDir });
213
257
  return true;
@@ -220,7 +264,9 @@ async function resolveExpoDependencies(projectDir, backend) {
220
264
  }
221
265
  async function promptClerkSetup(options) {
222
266
  let publishableKey = options.clerkPublishableKey?.trim() ?? "";
223
- let domain = options.clerkDomain ? normalizeClerkDomain(options.clerkDomain) : "";
267
+ let domain = options.clerkDomain
268
+ ? normalizeClerkDomain(options.clerkDomain)
269
+ : "";
224
270
  let jwtCreated = false;
225
271
  if (!publishableKey && options.interactive) {
226
272
  prompts_1.log.info("");
@@ -279,8 +325,32 @@ async function promptClerkSetup(options) {
279
325
  }
280
326
  return { publishableKey, domain, jwtCreated };
281
327
  }
282
- async function promptInstantSetup(options) {
328
+ async function promptInstantSetup(options, authMode) {
283
329
  let appId = options.instantAppId?.trim() ?? "";
330
+ if ((0, scaffold_1.isMagicCodeInstantAuthMode)(authMode)) {
331
+ if (options.interactive) {
332
+ prompts_1.log.info("");
333
+ prompts_1.log.info(picocolors_1.default.bold((0, scaffold_1.isCloudflareInstantAuthMode)(authMode)
334
+ ? "InstantDB Magic Code + Cloudflare Setup"
335
+ : "InstantDB Magic Code Setup"));
336
+ prompts_1.log.info(picocolors_1.default.dim("Create an app id with: npx instant-cli init-without-files --title my-app"));
337
+ if ((0, scaffold_1.isCloudflareInstantAuthMode)(authMode)) {
338
+ prompts_1.log.info(picocolors_1.default.dim("Cloudflare Worker, R2, and D1 files will be generated. Run `bun run cloudflare:deploy` after installing dependencies."));
339
+ }
340
+ if (!appId) {
341
+ prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
342
+ const appIdInput = await (0, prompts_1.text)({
343
+ message: "InstantDB App ID",
344
+ placeholder: "your-instant-app-id",
345
+ defaultValue: "",
346
+ });
347
+ if (!(0, prompts_1.isCancel)(appIdInput) && appIdInput) {
348
+ appId = appIdInput.trim();
349
+ }
350
+ }
351
+ }
352
+ return { authMode, appId };
353
+ }
284
354
  let clerkPublishableKey = options.clerkPublishableKey?.trim() ?? "";
285
355
  let clerkClientName = options.instantClerkClientName?.trim() || "clerk";
286
356
  if (options.interactive) {
@@ -320,7 +390,7 @@ async function promptInstantSetup(options) {
320
390
  clerkClientName = clientNameInput.trim();
321
391
  }
322
392
  }
323
- return { appId, clerkPublishableKey, clerkClientName };
393
+ return { authMode: "clerk", appId, clerkPublishableKey, clerkClientName };
324
394
  }
325
395
  async function writeAuthEnvironment(projectDir, clerk) {
326
396
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
@@ -348,7 +418,15 @@ async function writeAuthEnvironment(projectDir, clerk) {
348
418
  }
349
419
  }
350
420
  async function writeInstantEnvironment(projectDir, instant) {
351
- if (!instant.appId && !instant.clerkPublishableKey && !instant.clerkClientName) {
421
+ if ((0, scaffold_1.isMagicCodeInstantAuthMode)(instant.authMode)) {
422
+ if (!instant.appId) {
423
+ return;
424
+ }
425
+ }
426
+ else if (instant.authMode === "clerk" &&
427
+ !instant.appId &&
428
+ !instant.clerkPublishableKey &&
429
+ !instant.clerkClientName) {
352
430
  return;
353
431
  }
354
432
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
@@ -364,10 +442,22 @@ async function writeInstantEnvironment(projectDir, instant) {
364
442
  if (instant.appId) {
365
443
  envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_INSTANT_APP_ID", instant.appId);
366
444
  }
367
- if (instant.clerkPublishableKey) {
368
- envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", instant.clerkPublishableKey);
445
+ if ((0, scaffold_1.isCloudflareInstantAuthMode)(instant.authMode)) {
446
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "INSTANT_APP_ID", instant.appId);
447
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "INSTANT_ADMIN_TOKEN", "");
448
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:8080,http://localhost:8081,http://localhost:19006");
449
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "MAX_UPLOAD_BYTES", "26214400");
450
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "USER_STORAGE_LIMIT_BYTES", "524288000");
451
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "DAILY_UPLOAD_LIMIT", "100");
452
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "ALLOWED_CONTENT_TYPES", "image/jpeg,image/png,image/webp,application/pdf,text/plain");
453
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_STORAGE_WORKER_URL", "");
454
+ }
455
+ if (instant.authMode === "clerk") {
456
+ if (instant.clerkPublishableKey) {
457
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", instant.clerkPublishableKey);
458
+ }
459
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME", instant.clerkClientName || "clerk");
369
460
  }
370
- envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME", instant.clerkClientName || "clerk");
371
461
  await (0, fs_1.writeTextFile)(envLocalPath, envContents);
372
462
  }
373
463
  async function initializeConvex(projectDir) {
@@ -448,7 +538,7 @@ async function initializeGit(projectDir) {
448
538
  return false;
449
539
  }
450
540
  }
451
- async function runHealthChecks(projectDir, backend, clerkPublishableKey) {
541
+ async function runHealthChecks(projectDir, backend, instantAuthMode, clerkPublishableKey) {
452
542
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
453
543
  const checks = [];
454
544
  const envExists = await (0, fs_1.pathExists)(envLocalPath);
@@ -456,14 +546,30 @@ async function runHealthChecks(projectDir, backend, clerkPublishableKey) {
456
546
  if (!envExists) {
457
547
  if (backend === "instantdb") {
458
548
  checks.push({ label: "EXPO_PUBLIC_INSTANT_APP_ID is set", ok: false });
459
- checks.push({
460
- label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
461
- ok: false,
462
- });
463
- checks.push({
464
- label: "EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME is set",
465
- ok: false,
466
- });
549
+ if (instantAuthMode === "clerk") {
550
+ checks.push({
551
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
552
+ ok: false,
553
+ });
554
+ checks.push({
555
+ label: "EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME is set",
556
+ ok: false,
557
+ });
558
+ }
559
+ if ((0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode)) {
560
+ checks.push({
561
+ label: "INSTANT_APP_ID is set for Cloudflare deploy",
562
+ ok: false,
563
+ });
564
+ checks.push({
565
+ label: "INSTANT_ADMIN_TOKEN is set for Cloudflare deploy",
566
+ ok: false,
567
+ });
568
+ checks.push({
569
+ label: "EXPO_PUBLIC_STORAGE_WORKER_URL is set",
570
+ ok: false,
571
+ });
572
+ }
467
573
  }
468
574
  else {
469
575
  checks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
@@ -488,14 +594,35 @@ async function runHealthChecks(projectDir, backend, clerkPublishableKey) {
488
594
  label: "EXPO_PUBLIC_INSTANT_APP_ID is set",
489
595
  ok: envContent.includes("EXPO_PUBLIC_INSTANT_APP_ID"),
490
596
  });
491
- checks.push({
492
- label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
493
- ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
494
- });
495
- checks.push({
496
- label: "EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME is set",
497
- ok: envContent.includes("EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME"),
498
- });
597
+ if (instantAuthMode === "clerk") {
598
+ checks.push({
599
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
600
+ ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
601
+ });
602
+ checks.push({
603
+ label: "EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME is set",
604
+ ok: envContent.includes("EXPO_PUBLIC_INSTANT_CLERK_CLIENT_NAME"),
605
+ });
606
+ }
607
+ if ((0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode)) {
608
+ const env = (0, dotenv_1.parseDotenv)(envContent);
609
+ checks.push({
610
+ label: "INSTANT_APP_ID is set for Cloudflare deploy",
611
+ ok: Boolean(env["INSTANT_APP_ID"]),
612
+ });
613
+ checks.push({
614
+ label: "INSTANT_ADMIN_TOKEN is set for Cloudflare deploy",
615
+ ok: Boolean(env["INSTANT_ADMIN_TOKEN"]),
616
+ });
617
+ checks.push({
618
+ label: "ALLOWED_ORIGINS is set for Cloudflare deploy",
619
+ ok: Boolean(env["ALLOWED_ORIGINS"]),
620
+ });
621
+ checks.push({
622
+ label: "EXPO_PUBLIC_STORAGE_WORKER_URL is set",
623
+ ok: Boolean(env["EXPO_PUBLIC_STORAGE_WORKER_URL"]),
624
+ });
625
+ }
499
626
  }
500
627
  else {
501
628
  checks.push({