create-100x-mobile 0.4.3 → 0.4.5

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
@@ -16,11 +16,13 @@ Options:
16
16
  --yes Run without prompts (defaults name to "my-app")
17
17
  --no-install Skip dependency installation
18
18
  --skip-git Skip git init/commit
19
+ --backend <provider> Backend: "convex" or "instantdb"
19
20
  --sdk <value> Use "latest", "previous", or a specific version (e.g. 54.0.33)
20
21
  --clerk-pk <key> Pre-fill Clerk publishable key
21
22
  --clerk-domain <domain> Pre-fill Clerk issuer domain
23
+ --instant-app-id <id> Pre-fill InstantDB app id
22
24
 
23
- Scaffolds a full-stack mobile app with Expo + Convex + Clerk.
25
+ Scaffolds a mobile app with Expo + Convex/Clerk or Expo + InstantDB.
24
26
  `);
25
27
  }
26
28
  async function main() {
@@ -22,8 +22,10 @@ function parseNewCommandArgs(args) {
22
22
  installDependencies: true,
23
23
  initializeGit: true,
24
24
  sdk: null,
25
+ backend: null,
25
26
  clerkPublishableKey: null,
26
27
  clerkDomain: null,
28
+ instantAppId: null,
27
29
  };
28
30
  let projectName = null;
29
31
  for (let i = 0; i < args.length; i++) {
@@ -55,6 +57,16 @@ function parseNewCommandArgs(args) {
55
57
  i = nextIndex;
56
58
  continue;
57
59
  }
60
+ if (arg === "--backend" || arg.startsWith("--backend=")) {
61
+ const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
62
+ const normalized = value.toLowerCase();
63
+ if (normalized !== "convex" && normalized !== "instantdb") {
64
+ throw new Error(`Invalid --backend value "${value}". Use "convex" or "instantdb".`);
65
+ }
66
+ options.backend = normalized;
67
+ i = nextIndex;
68
+ continue;
69
+ }
58
70
  if (arg === "--clerk-pk" || arg.startsWith("--clerk-pk=")) {
59
71
  const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
60
72
  options.clerkPublishableKey = value;
@@ -67,6 +79,12 @@ function parseNewCommandArgs(args) {
67
79
  i = nextIndex;
68
80
  continue;
69
81
  }
82
+ if (arg === "--instant-app-id" || arg.startsWith("--instant-app-id=")) {
83
+ const { value, nextIndex } = getInlineOrNextValue(arg, args, i);
84
+ options.instantAppId = value;
85
+ i = nextIndex;
86
+ continue;
87
+ }
70
88
  throw new Error(`Unknown flag: ${arg}`);
71
89
  }
72
90
  if (!projectName && !options.interactive) {
@@ -7,16 +7,16 @@ const node_path_1 = require("node:path");
7
7
  const fs_1 = require("../../lib/fs");
8
8
  // 1x1 transparent PNG placeholder.
9
9
  const TRANSPARENT_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO7+P5kAAAAASUVORK5CYII=";
10
- async function createProjectDirectories(projectDir) {
11
- const dirs = [
12
- "app/(auth)",
10
+ async function createProjectDirectories(projectDir, backend) {
11
+ const commonDirs = [
13
12
  "app/(tabs)",
14
- "app/providers",
15
- "components",
16
- "convex",
17
13
  "hooks",
18
14
  "assets/images",
19
15
  ];
16
+ const backendDirs = backend === "convex"
17
+ ? ["app/(auth)", "app/providers", "components", "convex"]
18
+ : ["lib"];
19
+ const dirs = [...commonDirs, ...backendDirs];
20
20
  for (const dir of dirs) {
21
21
  await (0, fs_1.ensureDir)((0, node_path_1.join)(projectDir, dir));
22
22
  }
@@ -5,11 +5,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.normalizeClerkDomain = normalizeClerkDomain;
7
7
  exports.toErrorMessage = toErrorMessage;
8
+ exports.resolveBackendChoice = resolveBackendChoice;
8
9
  exports.resolveExpoVersion = resolveExpoVersion;
9
10
  exports.installDependencies = installDependencies;
10
11
  exports.resolveExpoDependencies = resolveExpoDependencies;
11
12
  exports.promptClerkSetup = promptClerkSetup;
13
+ exports.promptInstantSetup = promptInstantSetup;
12
14
  exports.writeAuthEnvironment = writeAuthEnvironment;
15
+ exports.writeInstantEnvironment = writeInstantEnvironment;
13
16
  exports.initializeConvex = initializeConvex;
14
17
  exports.ensureExpoPublicConvexUrl = ensureExpoPublicConvexUrl;
15
18
  exports.setConvexClerkEnv = setConvexClerkEnv;
@@ -58,6 +61,34 @@ function normalizeSdkArg(sdk) {
58
61
  }
59
62
  throw new Error(`Invalid --sdk value "${value}". Use "latest", "previous", or a version like "54.0.33".`);
60
63
  }
64
+ async function resolveBackendChoice(options) {
65
+ if (options.backend) {
66
+ return options.backend;
67
+ }
68
+ if (!options.interactive) {
69
+ return "convex";
70
+ }
71
+ const choice = await (0, prompts_1.select)({
72
+ message: "Which backend do you want?",
73
+ options: [
74
+ {
75
+ value: "convex",
76
+ label: "Convex + Clerk",
77
+ hint: "Auth + realtime backend",
78
+ },
79
+ {
80
+ value: "instantdb",
81
+ label: "InstantDB",
82
+ hint: "Client-first realtime backend",
83
+ },
84
+ ],
85
+ initialValue: "convex",
86
+ });
87
+ if ((0, prompts_1.isCancel)(choice)) {
88
+ return "convex";
89
+ }
90
+ return choice;
91
+ }
61
92
  async function resolveExpoVersion(options) {
62
93
  if (options.sdk) {
63
94
  const normalized = normalizeSdkArg(options.sdk);
@@ -144,24 +175,21 @@ async function installDependencies(projectDir) {
144
175
  }
145
176
  }
146
177
  }
147
- async function resolveExpoDependencies(projectDir) {
148
- const expoPackages = [
178
+ async function resolveExpoDependencies(projectDir, backend) {
179
+ const commonExpoPackages = [
149
180
  "react",
150
181
  "react-dom",
151
182
  "react-native",
152
183
  "expo-router",
153
184
  "@expo/vector-icons",
154
- "expo-auth-session",
155
185
  "expo-blur",
156
186
  "expo-constants",
157
187
  "expo-font",
158
188
  "expo-haptics",
159
189
  "expo-linking",
160
- "expo-secure-store",
161
190
  "expo-splash-screen",
162
191
  "expo-status-bar",
163
192
  "expo-system-ui",
164
- "expo-web-browser",
165
193
  "react-native-gesture-handler",
166
194
  "react-native-reanimated",
167
195
  "react-native-safe-area-context",
@@ -169,6 +197,10 @@ async function resolveExpoDependencies(projectDir) {
169
197
  "react-native-svg",
170
198
  "react-native-web",
171
199
  ];
200
+ const backendExpoPackages = backend === "convex"
201
+ ? ["expo-auth-session", "expo-secure-store", "expo-web-browser"]
202
+ : ["@react-native-async-storage/async-storage", "@react-native-community/netinfo"];
203
+ const expoPackages = [...commonExpoPackages, ...backendExpoPackages];
172
204
  try {
173
205
  await (0, run_1.run)("npx", ["expo", "install", ...expoPackages], { cwd: projectDir });
174
206
  return true;
@@ -240,6 +272,24 @@ async function promptClerkSetup(options) {
240
272
  }
241
273
  return { publishableKey, domain, jwtCreated };
242
274
  }
275
+ async function promptInstantSetup(options) {
276
+ let appId = options.instantAppId?.trim() ?? "";
277
+ if (!appId && options.interactive) {
278
+ prompts_1.log.info("");
279
+ prompts_1.log.info(picocolors_1.default.bold("InstantDB Setup"));
280
+ prompts_1.log.info(picocolors_1.default.dim("Create an app id with: npx instant-cli init-without-files --title my-app"));
281
+ prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
282
+ const appIdInput = await (0, prompts_1.text)({
283
+ message: "InstantDB App ID",
284
+ placeholder: "your-instant-app-id",
285
+ defaultValue: "",
286
+ });
287
+ if (!(0, prompts_1.isCancel)(appIdInput) && appIdInput) {
288
+ appId = appIdInput.trim();
289
+ }
290
+ }
291
+ return { appId };
292
+ }
243
293
  async function writeAuthEnvironment(projectDir, clerk) {
244
294
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
245
295
  let envContents = "";
@@ -265,6 +315,23 @@ async function writeAuthEnvironment(projectDir, clerk) {
265
315
  await (0, fs_1.writeTextFile)((0, node_path_1.join)(projectDir, "convex/auth.config.ts"), `export default {\n providers: [\n {\n domain: ${serializedDomain},\n applicationID: "convex",\n },\n ],\n};\n`);
266
316
  }
267
317
  }
318
+ async function writeInstantEnvironment(projectDir, instant) {
319
+ if (!instant.appId) {
320
+ return;
321
+ }
322
+ const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
323
+ let envContents = "";
324
+ try {
325
+ envContents = await (0, fs_1.readTextFile)(envLocalPath);
326
+ }
327
+ catch (error) {
328
+ if (isErrnoException(error) && error.code !== "ENOENT") {
329
+ prompts_1.log.warn(`Could not read .env.local: ${toErrorMessage(error)}`);
330
+ }
331
+ }
332
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_INSTANT_APP_ID", instant.appId);
333
+ await (0, fs_1.writeTextFile)(envLocalPath, envContents);
334
+ }
268
335
  async function initializeConvex(projectDir) {
269
336
  try {
270
337
  await (0, run_1.run)("bunx", ["convex", "dev", "--once"], { cwd: projectDir });
@@ -343,18 +410,23 @@ async function initializeGit(projectDir) {
343
410
  return false;
344
411
  }
345
412
  }
346
- async function runHealthChecks(projectDir, clerkPublishableKey) {
413
+ async function runHealthChecks(projectDir, backend, clerkPublishableKey) {
347
414
  const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
348
415
  const checks = [];
349
416
  const envExists = await (0, fs_1.pathExists)(envLocalPath);
350
417
  checks.push({ label: ".env.local exists", ok: envExists });
351
418
  if (!envExists) {
352
- checks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
353
- if (clerkPublishableKey) {
354
- checks.push({
355
- label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
356
- ok: false,
357
- });
419
+ if (backend === "instantdb") {
420
+ checks.push({ label: "EXPO_PUBLIC_INSTANT_APP_ID is set", ok: false });
421
+ }
422
+ else {
423
+ checks.push({ label: "EXPO_PUBLIC_CONVEX_URL is set", ok: false });
424
+ if (clerkPublishableKey) {
425
+ checks.push({
426
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
427
+ ok: false,
428
+ });
429
+ }
358
430
  }
359
431
  return checks;
360
432
  }
@@ -365,15 +437,23 @@ async function runHealthChecks(projectDir, clerkPublishableKey) {
365
437
  catch (error) {
366
438
  prompts_1.log.warn(`Could not read .env.local during health checks: ${toErrorMessage(error)}`);
367
439
  }
368
- checks.push({
369
- label: "EXPO_PUBLIC_CONVEX_URL is set",
370
- ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
371
- });
372
- if (clerkPublishableKey) {
440
+ if (backend === "instantdb") {
373
441
  checks.push({
374
- label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
375
- ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
442
+ label: "EXPO_PUBLIC_INSTANT_APP_ID is set",
443
+ ok: envContent.includes("EXPO_PUBLIC_INSTANT_APP_ID"),
376
444
  });
377
445
  }
446
+ else {
447
+ checks.push({
448
+ label: "EXPO_PUBLIC_CONVEX_URL is set",
449
+ ok: envContent.includes("EXPO_PUBLIC_CONVEX_URL"),
450
+ });
451
+ if (clerkPublishableKey) {
452
+ checks.push({
453
+ label: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY is set",
454
+ ok: envContent.includes("EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY"),
455
+ });
456
+ }
457
+ }
378
458
  return checks;
379
459
  }
@@ -34,9 +34,14 @@ const notFound_1 = require("../templates/app/notFound");
34
34
  const rootLayout_1 = require("../templates/app/rootLayout");
35
35
  const settingsScreen_1 = require("../templates/app/settingsScreen");
36
36
  const signIn_1 = require("../templates/app/signIn");
37
- const signUp_1 = require("../templates/app/signUp");
38
37
  const tabsLayout_1 = require("../templates/app/tabsLayout");
39
38
  const todosScreen_1 = require("../templates/app/todosScreen");
39
+ // Instant templates
40
+ const instantLib_1 = require("../templates/instant/instantLib");
41
+ const rootLayout_2 = require("../templates/instant/rootLayout");
42
+ const settingsScreen_2 = require("../templates/instant/settingsScreen");
43
+ const tabsLayout_2 = require("../templates/instant/tabsLayout");
44
+ const todosScreen_2 = require("../templates/instant/todosScreen");
40
45
  // Component templates
41
46
  const addTodoForm_1 = require("../templates/components/addTodoForm");
42
47
  const emptyState_1 = require("../templates/components/emptyState");
@@ -44,18 +49,42 @@ const filterTabs_1 = require("../templates/components/filterTabs");
44
49
  const todoItem_1 = require("../templates/components/todoItem");
45
50
  // Hook templates
46
51
  const useFrameworkReady_1 = require("../templates/hooks/useFrameworkReady");
47
- function buildTemplateFiles(projectName, expoVersion) {
52
+ function buildTemplateFiles(projectName, expoVersion, backend) {
53
+ if (backend === "instantdb") {
54
+ const files = [
55
+ // Config
56
+ ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend)],
57
+ ["app.json", (0, appJson_1.appJsonTemplate)(projectName)],
58
+ ["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
59
+ [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
60
+ [".env.example", (0, envExample_1.envExampleTemplate)(backend)],
61
+ ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
62
+ [".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
63
+ ["eas.json", (0, easJson_1.easJsonTemplate)()],
64
+ ["README.md", (0, readme_1.readmeTemplate)(projectName, backend)],
65
+ // Instant app
66
+ ["app/_layout.tsx", (0, rootLayout_2.instantRootLayoutTemplate)()],
67
+ ["app/+not-found.tsx", (0, notFound_1.notFoundTemplate)()],
68
+ ["app/(tabs)/_layout.tsx", (0, tabsLayout_2.instantTabsLayoutTemplate)()],
69
+ ["app/(tabs)/index.tsx", (0, todosScreen_2.instantTodosScreenTemplate)()],
70
+ ["app/(tabs)/settings.tsx", (0, settingsScreen_2.instantSettingsScreenTemplate)()],
71
+ ["lib/instant.ts", (0, instantLib_1.instantLibTemplate)()],
72
+ // Hooks
73
+ ["hooks/useFrameworkReady.ts", (0, useFrameworkReady_1.useFrameworkReadyTemplate)()],
74
+ ];
75
+ return files.map(([path, content]) => ({ path, content }));
76
+ }
48
77
  const files = [
49
78
  // Config
50
- ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion)],
79
+ ["package.json", (0, packageJson_1.packageJsonTemplate)(projectName, expoVersion, backend)],
51
80
  ["app.json", (0, appJson_1.appJsonTemplate)(projectName)],
52
81
  ["tsconfig.json", (0, tsconfig_1.tsconfigTemplate)()],
53
82
  [".gitignore", (0, gitignore_1.gitignoreTemplate)()],
54
- [".env.example", (0, envExample_1.envExampleTemplate)()],
83
+ [".env.example", (0, envExample_1.envExampleTemplate)(backend)],
55
84
  ["expo-env.d.ts", (0, tsconfig_1.expoEnvDtsTemplate)()],
56
85
  [".prettierrc", (0, prettierrc_1.prettierrcTemplate)()],
57
86
  ["eas.json", (0, easJson_1.easJsonTemplate)()],
58
- ["README.md", (0, readme_1.readmeTemplate)(projectName)],
87
+ ["README.md", (0, readme_1.readmeTemplate)(projectName, backend)],
59
88
  // Convex
60
89
  ["convex/schema.ts", (0, schema_1.schemaTemplate)()],
61
90
  ["convex/todos.ts", (0, todos_1.todosTemplate)()],
@@ -69,7 +98,6 @@ function buildTemplateFiles(projectName, expoVersion) {
69
98
  ["app/providers/AuthProvider.tsx", (0, authProvider_1.authProviderTemplate)()],
70
99
  ["app/(auth)/_layout.tsx", (0, authLayout_1.authLayoutTemplate)()],
71
100
  ["app/(auth)/sign-in.tsx", (0, signIn_1.signInTemplate)()],
72
- ["app/(auth)/sign-up.tsx", (0, signUp_1.signUpTemplate)()],
73
101
  ["app/(tabs)/_layout.tsx", (0, tabsLayout_1.tabsLayoutTemplate)()],
74
102
  ["app/(tabs)/index.tsx", (0, todosScreen_1.todosScreenTemplate)()],
75
103
  ["app/(tabs)/settings.tsx", (0, settingsScreen_1.settingsScreenTemplate)()],
@@ -130,14 +158,20 @@ async function cmdNew(rawArgs) {
130
158
  }
131
159
  await (0, fs_1.removeDir)(projectDir);
132
160
  }
133
- if (options.clerkDomain) {
161
+ const backend = await (0, steps_1.resolveBackendChoice)(options);
162
+ if (backend === "convex" && options.clerkDomain) {
134
163
  options.clerkDomain = (0, steps_1.normalizeClerkDomain)(options.clerkDomain);
135
164
  }
136
165
  const expoVersion = await (0, steps_1.resolveExpoVersion)(options);
137
- const clerk = await (0, steps_1.promptClerkSetup)(options);
138
- const totalSteps = 2 + // project generation + convex init
166
+ const clerk = backend === "convex"
167
+ ? await (0, steps_1.promptClerkSetup)(options)
168
+ : { publishableKey: "", domain: "", jwtCreated: false };
169
+ const instant = backend === "instantdb"
170
+ ? await (0, steps_1.promptInstantSetup)(options)
171
+ : { appId: "" };
172
+ const totalSteps = 1 + // project generation
139
173
  (options.installDependencies ? 2 : 0) +
140
- (clerk.domain ? 1 : 0) +
174
+ (backend === "convex" ? 2 + (clerk.domain ? 1 : 0) : 1) +
141
175
  (options.initializeGit ? 1 : 0) +
142
176
  1; // health check
143
177
  let currentStep = 0;
@@ -145,11 +179,26 @@ async function cmdNew(rawArgs) {
145
179
  const structureSpinner = (0, prompts_1.spinner)();
146
180
  currentStep++;
147
181
  structureSpinner.start(`Creating project structure ${stepLabel()}`);
148
- await (0, scaffold_1.createProjectDirectories)(projectDir);
149
- await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion));
182
+ await (0, scaffold_1.createProjectDirectories)(projectDir, backend);
183
+ await (0, scaffold_1.writeScaffoldFiles)(projectDir, buildTemplateFiles(projectName, expoVersion, backend));
150
184
  await (0, scaffold_1.writeDefaultAssetFiles)(projectDir);
151
- await (0, steps_1.writeAuthEnvironment)(projectDir, clerk);
152
185
  structureSpinner.stop("Project files created.");
186
+ currentStep++;
187
+ if (backend === "convex") {
188
+ prompts_1.log.step(`Configuring Convex + Clerk ${stepLabel()}`);
189
+ await (0, steps_1.writeAuthEnvironment)(projectDir, clerk);
190
+ prompts_1.log.success("Convex and Clerk environment configured.");
191
+ }
192
+ else {
193
+ prompts_1.log.step(`Configuring InstantDB ${stepLabel()}`);
194
+ await (0, steps_1.writeInstantEnvironment)(projectDir, instant);
195
+ if (instant.appId) {
196
+ prompts_1.log.success("InstantDB environment configured.");
197
+ }
198
+ else {
199
+ prompts_1.log.info(picocolors_1.default.dim("Set EXPO_PUBLIC_INSTANT_APP_ID in .env.local to connect InstantDB."));
200
+ }
201
+ }
153
202
  if (options.installDependencies) {
154
203
  currentStep++;
155
204
  prompts_1.log.step(`Installing dependencies ${stepLabel()}`);
@@ -162,7 +211,7 @@ async function cmdNew(rawArgs) {
162
211
  }
163
212
  currentStep++;
164
213
  prompts_1.log.step(`Installing Expo dependencies ${stepLabel()}`);
165
- const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir);
214
+ const resolved = await (0, steps_1.resolveExpoDependencies)(projectDir, backend);
166
215
  if (resolved) {
167
216
  prompts_1.log.success("Expo dependencies resolved.");
168
217
  }
@@ -170,31 +219,33 @@ async function cmdNew(rawArgs) {
170
219
  else {
171
220
  prompts_1.log.info(picocolors_1.default.dim("Skipping dependency installation (--no-install)."));
172
221
  }
173
- currentStep++;
174
- prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
175
- const convexInitialized = await (0, steps_1.initializeConvex)(projectDir);
176
- if (convexInitialized) {
177
- prompts_1.log.success("Convex initialized.");
178
- }
179
- else {
180
- prompts_1.log.info(picocolors_1.default.dim(`Run manually: cd ${projectName} && bunx convex dev --once`));
181
- }
182
- const convexUrlReady = await (0, steps_1.ensureExpoPublicConvexUrl)(projectDir);
183
- if (convexUrlReady) {
184
- prompts_1.log.success("Convex URL configured for Expo.");
185
- }
186
- else {
187
- prompts_1.log.info(picocolors_1.default.dim("Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
188
- }
189
- if (clerk.domain) {
222
+ if (backend === "convex") {
190
223
  currentStep++;
191
- prompts_1.log.step(`Setting Convex environment variable ${stepLabel()}`);
192
- const setEnvOk = await (0, steps_1.setConvexClerkEnv)(projectDir, clerk.domain);
193
- if (setEnvOk) {
194
- prompts_1.log.success("Convex environment variable set.");
224
+ prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
225
+ const convexInitialized = await (0, steps_1.initializeConvex)(projectDir);
226
+ if (convexInitialized) {
227
+ prompts_1.log.success("Convex initialized.");
195
228
  }
196
229
  else {
197
- prompts_1.log.info(picocolors_1.default.dim(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerk.domain}`));
230
+ prompts_1.log.info(picocolors_1.default.dim(`Run manually: cd ${projectName} && bunx convex dev --once`));
231
+ }
232
+ const convexUrlReady = await (0, steps_1.ensureExpoPublicConvexUrl)(projectDir);
233
+ if (convexUrlReady) {
234
+ prompts_1.log.success("Convex URL configured for Expo.");
235
+ }
236
+ else {
237
+ prompts_1.log.info(picocolors_1.default.dim("Add EXPO_PUBLIC_CONVEX_URL to .env.local with your Convex deployment URL."));
238
+ }
239
+ if (clerk.domain) {
240
+ currentStep++;
241
+ prompts_1.log.step(`Setting Convex environment variable ${stepLabel()}`);
242
+ const setEnvOk = await (0, steps_1.setConvexClerkEnv)(projectDir, clerk.domain);
243
+ if (setEnvOk) {
244
+ prompts_1.log.success("Convex environment variable set.");
245
+ }
246
+ else {
247
+ prompts_1.log.info(picocolors_1.default.dim(`Run manually: bunx convex env set CLERK_JWT_ISSUER_DOMAIN ${clerk.domain}`));
248
+ }
198
249
  }
199
250
  }
200
251
  if (options.initializeGit) {
@@ -210,7 +261,7 @@ async function cmdNew(rawArgs) {
210
261
  }
211
262
  currentStep++;
212
263
  prompts_1.log.step(`Running health check ${stepLabel()}`);
213
- const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, clerk.publishableKey);
264
+ const healthChecks = await (0, steps_1.runHealthChecks)(projectDir, backend, clerk.publishableKey);
214
265
  for (const check of healthChecks) {
215
266
  if (check.ok) {
216
267
  prompts_1.log.info(` ${picocolors_1.default.green("✓")} ${check.label}`);
@@ -220,7 +271,26 @@ async function cmdNew(rawArgs) {
220
271
  }
221
272
  }
222
273
  prompts_1.log.info("");
223
- if (clerk.publishableKey && clerk.domain) {
274
+ if (backend === "instantdb") {
275
+ prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Project scaffolded with InstantDB!")));
276
+ prompts_1.log.info("");
277
+ prompts_1.log.info(" Next steps:");
278
+ prompts_1.log.info(` ${picocolors_1.default.cyan("cd")} ${projectName}`);
279
+ if (options.installDependencies) {
280
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
281
+ }
282
+ else {
283
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun install")} ${picocolors_1.default.dim("# or npm install")}`);
284
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
285
+ }
286
+ if (!instant.appId) {
287
+ prompts_1.log.info("");
288
+ prompts_1.log.info(picocolors_1.default.dim(" Add EXPO_PUBLIC_INSTANT_APP_ID to .env.local first."));
289
+ prompts_1.log.info(picocolors_1.default.dim(" Create one with: npx instant-cli init-without-files --title " +
290
+ projectName));
291
+ }
292
+ }
293
+ else if (clerk.publishableKey && clerk.domain) {
224
294
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green("Your app is ready!")));
225
295
  prompts_1.log.info("");
226
296
  prompts_1.log.info(" Next steps:");
@@ -8,7 +8,6 @@ export default function AuthLayout() {
8
8
  return (
9
9
  <Stack screenOptions={{ headerShown: false }}>
10
10
  <Stack.Screen name="sign-in" />
11
- <Stack.Screen name="sign-up" />
12
11
  </Stack>
13
12
  );
14
13
  }
@@ -14,7 +14,7 @@ import {
14
14
  } from "react-native";
15
15
  import { SafeAreaView } from "react-native-safe-area-context";
16
16
  import { useOAuth } from "@clerk/clerk-expo";
17
- import { Link, useRouter } from "expo-router";
17
+ import { useRouter } from "expo-router";
18
18
  import { SquareCheck as CheckSquare } from "lucide-react-native";
19
19
  import Svg, { Path } from "react-native-svg";
20
20
  import * as WebBrowser from "expo-web-browser";
@@ -127,11 +127,6 @@ export default function SignInScreen() {
127
127
  <Text style={styles.terms}>
128
128
  By continuing, you agree to our Terms of Service and Privacy Policy
129
129
  </Text>
130
- <Link href="/(auth)/sign-up" asChild>
131
- <TouchableOpacity style={styles.emailLinkWrap} activeOpacity={0.7}>
132
- <Text style={styles.emailLinkText}>Use email and password instead</Text>
133
- </TouchableOpacity>
134
- </Link>
135
130
  </View>
136
131
  </SafeAreaView>
137
132
  );
@@ -219,15 +214,6 @@ const styles = StyleSheet.create({
219
214
  lineHeight: 20,
220
215
  paddingHorizontal: 16,
221
216
  },
222
- emailLinkWrap: {
223
- marginTop: 16,
224
- alignItems: "center",
225
- },
226
- emailLinkText: {
227
- fontSize: 14,
228
- fontWeight: "600",
229
- color: "#4B5563",
230
- },
231
217
  });
232
218
  `;
233
219
  }
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.envExampleTemplate = envExampleTemplate;
4
- function envExampleTemplate() {
4
+ function envExampleTemplate(backend) {
5
+ if (backend === "instantdb") {
6
+ return `# InstantDB
7
+ EXPO_PUBLIC_INSTANT_APP_ID=
8
+ `;
9
+ }
5
10
  return `# Convex
6
11
  EXPO_PUBLIC_CONVEX_URL=
7
12
 
@@ -1,7 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.packageJsonTemplate = packageJsonTemplate;
4
- function packageJsonTemplate(appName, expoVersion = "latest") {
4
+ function packageJsonTemplate(appName, expoVersion = "latest", backend = "convex") {
5
+ const backendDependencies = backend === "instantdb"
6
+ ? {
7
+ "@instantdb/react-native": "^0.20.0",
8
+ "@react-native-async-storage/async-storage": "^2.2.0",
9
+ "@react-native-community/netinfo": "^11.4.1",
10
+ "react-native-get-random-values": "^1.11.0",
11
+ }
12
+ : {
13
+ "@clerk/clerk-expo": "^2.14.24",
14
+ convex: "^1.26.1",
15
+ };
5
16
  const pkg = {
6
17
  name: appName,
7
18
  main: "expo-router/entry",
@@ -14,12 +25,11 @@ function packageJsonTemplate(appName, expoVersion = "latest") {
14
25
  format: "prettier --write .",
15
26
  },
16
27
  dependencies: {
17
- "@clerk/clerk-expo": "^2.14.24",
18
28
  "@react-navigation/bottom-tabs": "^7.3.10",
19
29
  "@react-navigation/native": "^7.1.6",
20
- convex: "^1.26.1",
21
30
  expo: expoVersion,
22
31
  "lucide-react-native": "^0.542.0",
32
+ ...backendDependencies,
23
33
  },
24
34
  devDependencies: {
25
35
  "@babel/core": "^7.25.2",
@@ -1,7 +1,51 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readmeTemplate = readmeTemplate;
4
- function readmeTemplate(projectName) {
4
+ function readmeTemplate(projectName, backend) {
5
+ if (backend === "instantdb") {
6
+ return `# ${projectName}
7
+
8
+ A mobile app built with **Expo** and **InstantDB**.
9
+
10
+ ## Tech Stack
11
+
12
+ - **[Expo](https://expo.dev)** — React Native framework
13
+ - **[InstantDB](https://instantdb.com)** — Real-time backend and sync
14
+ - **[Expo Router](https://docs.expo.dev/router/introduction/)** — File-based navigation
15
+
16
+ ## Getting Started
17
+
18
+ \`\`\`bash
19
+ # Install dependencies
20
+ bun install
21
+
22
+ # Add your Instant app id
23
+ cp .env.example .env.local
24
+
25
+ # Start Expo
26
+ bunx expo start
27
+ \`\`\`
28
+
29
+ Set \`EXPO_PUBLIC_INSTANT_APP_ID\` in \`.env.local\`. You can create an Instant app id via:
30
+
31
+ \`\`\`bash
32
+ npx instant-cli init-without-files --title ${projectName}
33
+ \`\`\`
34
+
35
+ ## Scripts
36
+
37
+ | Command | Description |
38
+ |---------|-------------|
39
+ | \`bun run dev\` | Start Expo dev server |
40
+ | \`bun run lint\` | Run ESLint |
41
+ | \`bun run format\` | Format code with Prettier |
42
+
43
+ ## Learn More
44
+
45
+ - [Expo Docs](https://docs.expo.dev)
46
+ - [InstantDB Docs](https://instantdb.com/docs)
47
+ `;
48
+ }
5
49
  return `# ${projectName}
6
50
 
7
51
  A full-stack mobile app built with **Expo**, **Convex**, and **Clerk**.