create-100x-mobile 0.5.1 → 0.5.3

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
File without changes
@@ -18,6 +18,7 @@ exports.writeInstantEnvironment = writeInstantEnvironment;
18
18
  exports.initializeConvex = initializeConvex;
19
19
  exports.ensureExpoPublicConvexUrl = ensureExpoPublicConvexUrl;
20
20
  exports.setConvexClerkEnv = setConvexClerkEnv;
21
+ exports.setupCloudflare = setupCloudflare;
21
22
  exports.initializeGit = initializeGit;
22
23
  exports.runHealthChecks = runHealthChecks;
23
24
  const node_path_1 = require("node:path");
@@ -328,6 +329,7 @@ async function promptClerkSetup(options) {
328
329
  async function promptInstantSetup(options, authMode) {
329
330
  let appId = options.instantAppId?.trim() ?? "";
330
331
  if ((0, scaffold_1.isMagicCodeInstantAuthMode)(authMode)) {
332
+ let adminToken = "";
331
333
  if (options.interactive) {
332
334
  prompts_1.log.info("");
333
335
  prompts_1.log.info(picocolors_1.default.bold((0, scaffold_1.isCloudflareInstantAuthMode)(authMode)
@@ -335,7 +337,7 @@ async function promptInstantSetup(options, authMode) {
335
337
  : "InstantDB Magic Code Setup"));
336
338
  prompts_1.log.info(picocolors_1.default.dim("Create an app id with: npx instant-cli init-without-files --title my-app"));
337
339
  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."));
340
+ prompts_1.log.info(picocolors_1.default.dim("Cloudflare Worker, R2, and D1 files will be generated and deployed automatically."));
339
341
  }
340
342
  if (!appId) {
341
343
  prompts_1.log.info(picocolors_1.default.dim("Press Enter to skip and configure later."));
@@ -348,8 +350,21 @@ async function promptInstantSetup(options, authMode) {
348
350
  appId = appIdInput.trim();
349
351
  }
350
352
  }
353
+ if ((0, scaffold_1.isCloudflareInstantAuthMode)(authMode) && appId) {
354
+ prompts_1.log.info("");
355
+ prompts_1.log.info(picocolors_1.default.dim("The admin token lets the Cloudflare Worker sync upload metadata to InstantDB."));
356
+ prompts_1.log.info(picocolors_1.default.dim("Get it from: https://instantdb.com/dash → Your App → Admin Tokens"));
357
+ const adminTokenInput = await (0, prompts_1.text)({
358
+ message: "InstantDB Admin Token",
359
+ placeholder: "your-instant-admin-token",
360
+ defaultValue: "",
361
+ });
362
+ if (!(0, prompts_1.isCancel)(adminTokenInput) && adminTokenInput) {
363
+ adminToken = adminTokenInput.trim();
364
+ }
365
+ }
351
366
  }
352
- return { authMode, appId };
367
+ return { authMode, appId, adminToken };
353
368
  }
354
369
  let clerkPublishableKey = options.clerkPublishableKey?.trim() ?? "";
355
370
  let clerkClientName = options.instantClerkClientName?.trim() || "clerk";
@@ -444,7 +459,8 @@ async function writeInstantEnvironment(projectDir, instant) {
444
459
  }
445
460
  if ((0, scaffold_1.isCloudflareInstantAuthMode)(instant.authMode)) {
446
461
  envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "INSTANT_APP_ID", instant.appId);
447
- envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "INSTANT_ADMIN_TOKEN", "");
462
+ const adminToken = "adminToken" in instant ? instant.adminToken : "";
463
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "INSTANT_ADMIN_TOKEN", adminToken);
448
464
  envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:8080,http://localhost:8081,http://localhost:19006");
449
465
  envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "MAX_UPLOAD_BYTES", "26214400");
450
466
  envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "USER_STORAGE_LIMIT_BYTES", "524288000");
@@ -524,6 +540,61 @@ async function setConvexClerkEnv(projectDir, clerkDomain) {
524
540
  return false;
525
541
  }
526
542
  }
543
+ async function setupCloudflare(projectDir) {
544
+ const result = {
545
+ configured: false,
546
+ loggedIn: false,
547
+ deployed: false,
548
+ workerUrl: "",
549
+ };
550
+ // Step 1: cloudflare:configure (alchemy configure)
551
+ try {
552
+ await (0, run_1.run)("bun", ["run", "cloudflare:configure"], { cwd: projectDir });
553
+ result.configured = true;
554
+ }
555
+ catch (error) {
556
+ prompts_1.log.warn(`Cloudflare configure failed: ${toErrorMessage(error)}`);
557
+ prompts_1.log.info(picocolors_1.default.dim("Run manually: bun run cloudflare:configure"));
558
+ return result;
559
+ }
560
+ // Step 2: cloudflare:login (alchemy login)
561
+ try {
562
+ await (0, run_1.run)("bun", ["run", "cloudflare:login"], { cwd: projectDir });
563
+ result.loggedIn = true;
564
+ }
565
+ catch (error) {
566
+ prompts_1.log.warn(`Cloudflare login failed: ${toErrorMessage(error)}`);
567
+ prompts_1.log.info(picocolors_1.default.dim("Run manually: bun run cloudflare:login"));
568
+ return result;
569
+ }
570
+ // Step 3: cloudflare:deploy (alchemy deploy) — capture output for worker URL
571
+ try {
572
+ const deployOutput = await (0, run_1.runCapture)("bun", ["run", "cloudflare:deploy"], { cwd: projectDir });
573
+ result.deployed = true;
574
+ // Try to extract the storageWorkerUrl from the deploy output
575
+ const urlMatch = deployOutput.match(/storageWorkerUrl[:\s]*["']?(https?:\/\/[^\s"']+)["']?/i);
576
+ if (urlMatch?.[1]) {
577
+ result.workerUrl = urlMatch[1];
578
+ // Write the worker URL to .env.local
579
+ const envLocalPath = (0, node_path_1.join)(projectDir, ".env.local");
580
+ let envContents = "";
581
+ try {
582
+ envContents = await (0, fs_1.readTextFile)(envLocalPath);
583
+ }
584
+ catch {
585
+ // .env.local may not exist yet
586
+ }
587
+ envContents = (0, dotenv_1.upsertDotenvVar)(envContents, "EXPO_PUBLIC_STORAGE_WORKER_URL", result.workerUrl);
588
+ await (0, fs_1.writeTextFile)(envLocalPath, envContents);
589
+ }
590
+ }
591
+ catch (error) {
592
+ prompts_1.log.warn(`Cloudflare deploy failed: ${toErrorMessage(error)}`);
593
+ prompts_1.log.info(picocolors_1.default.dim("Run manually: bun run cloudflare:deploy"));
594
+ return result;
595
+ }
596
+ return result;
597
+ }
527
598
  async function initializeGit(projectDir) {
528
599
  try {
529
600
  await (0, run_1.run)("git", ["init"], { cwd: projectDir });
@@ -257,9 +257,11 @@ async function cmdNew(rawArgs) {
257
257
  prompts_1.log.info(picocolors_1.default.dim(`Client name: ${instant.clerkClientName}`));
258
258
  prompts_1.log.info(picocolors_1.default.dim(`Clerk publishable key: ${instant.clerkPublishableKey}`));
259
259
  }
260
+ const isCloudflareMode = backend === "instantdb" && (0, scaffold_1.isCloudflareInstantAuthMode)(instantAuthMode);
260
261
  const totalSteps = 1 + // project generation
261
262
  (options.installDependencies ? 2 : 0) +
262
263
  (backend === "convex" ? 2 + (clerk.domain ? 1 : 0) : 1) +
264
+ (isCloudflareMode && options.installDependencies ? 1 : 0) + // cloudflare setup
263
265
  (options.initializeGit ? 1 : 0) +
264
266
  1; // health check
265
267
  let currentStep = 0;
@@ -319,6 +321,7 @@ async function cmdNew(rawArgs) {
319
321
  else {
320
322
  prompts_1.log.info(picocolors_1.default.dim("Skipping dependency installation (--no-install)."));
321
323
  }
324
+ let cloudflareSetup = null;
322
325
  if (backend === "convex") {
323
326
  currentStep++;
324
327
  prompts_1.log.step(`Setting up Convex ${stepLabel()}`);
@@ -348,6 +351,23 @@ async function cmdNew(rawArgs) {
348
351
  }
349
352
  }
350
353
  }
354
+ if (isCloudflareMode && options.installDependencies) {
355
+ currentStep++;
356
+ prompts_1.log.step(`Setting up Cloudflare storage ${stepLabel()}`);
357
+ cloudflareSetup = await (0, steps_1.setupCloudflare)(projectDir);
358
+ if (cloudflareSetup.deployed) {
359
+ if (cloudflareSetup.workerUrl) {
360
+ prompts_1.log.success(`Cloudflare deployed. Worker URL: ${picocolors_1.default.cyan(cloudflareSetup.workerUrl)}`);
361
+ }
362
+ else {
363
+ prompts_1.log.success("Cloudflare deployed.");
364
+ prompts_1.log.info(picocolors_1.default.dim("Add the storageWorkerUrl to EXPO_PUBLIC_STORAGE_WORKER_URL in .env.local."));
365
+ }
366
+ }
367
+ else {
368
+ prompts_1.log.warn("Cloudflare setup did not complete fully.");
369
+ }
370
+ }
351
371
  if (options.initializeGit) {
352
372
  currentStep++;
353
373
  prompts_1.log.step(`Initializing git repository ${stepLabel()}`);
@@ -372,17 +392,19 @@ async function cmdNew(rawArgs) {
372
392
  }
373
393
  prompts_1.log.info("");
374
394
  if (backend === "instantdb") {
375
- const isCloudflareMode = instant?.authMode && (0, scaffold_1.isCloudflareInstantAuthMode)(instant.authMode);
376
395
  const isMagicCodeMode = instant?.authMode && (0, scaffold_1.isMagicCodeInstantAuthMode)(instant.authMode);
377
396
  const instantReady = isMagicCodeMode
378
397
  ? Boolean(instant?.appId)
379
398
  : Boolean(instant?.authMode === "clerk" &&
380
399
  instant.appId &&
381
400
  instant.clerkPublishableKey);
401
+ const cloudflareFullyDeployed = cloudflareSetup?.deployed && cloudflareSetup?.workerUrl;
382
402
  if (instantReady) {
383
- prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green(isCloudflareMode
384
- ? "Your app is ready! Deploy Cloudflare storage next."
385
- : "Your app is ready!")));
403
+ prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green(isCloudflareMode && cloudflareFullyDeployed
404
+ ? "Your app is ready! Cloudflare storage deployed."
405
+ : isCloudflareMode
406
+ ? "Your app is ready! Deploy Cloudflare storage next."
407
+ : "Your app is ready!")));
386
408
  }
387
409
  else if (isMagicCodeMode) {
388
410
  prompts_1.log.success(picocolors_1.default.bold(picocolors_1.default.green(isCloudflareMode
@@ -402,15 +424,36 @@ async function cmdNew(rawArgs) {
402
424
  prompts_1.log.info(` ${picocolors_1.default.cyan("bun install")} ${picocolors_1.default.dim("# or npm install")}`);
403
425
  prompts_1.log.info(` ${picocolors_1.default.cyan("bunx expo start")}`);
404
426
  }
405
- if (isCloudflareMode) {
427
+ if (isCloudflareMode && !cloudflareFullyDeployed) {
406
428
  prompts_1.log.info("");
407
- prompts_1.log.info(picocolors_1.default.dim(" Cloudflare setup:"));
408
- prompts_1.log.info(picocolors_1.default.dim(" INSTANT_APP_ID is written to .env.local for Alchemy deploys."));
409
- prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:configure")}`);
410
- prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:login")}`);
411
- prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:deploy")}`);
429
+ if (!cloudflareSetup || !cloudflareSetup.configured) {
430
+ // Nothing ran show all steps
431
+ prompts_1.log.info(picocolors_1.default.dim(" Cloudflare setup (run manually):"));
432
+ prompts_1.log.info(picocolors_1.default.dim(" INSTANT_APP_ID is written to .env.local for Alchemy deploys."));
433
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:configure")}`);
434
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:login")}`);
435
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:deploy")}`);
436
+ }
437
+ else if (!cloudflareSetup.loggedIn) {
438
+ prompts_1.log.info(picocolors_1.default.dim(" Cloudflare setup (continue from login):"));
439
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:login")}`);
440
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:deploy")}`);
441
+ }
442
+ else if (!cloudflareSetup.deployed) {
443
+ prompts_1.log.info(picocolors_1.default.dim(" Cloudflare setup (deploy remaining):"));
444
+ prompts_1.log.info(` ${picocolors_1.default.cyan("bun run cloudflare:deploy")}`);
445
+ }
446
+ else {
447
+ // deployed but worker URL not extracted
448
+ prompts_1.log.info(picocolors_1.default.dim(" Cloudflare deployed but worker URL not detected."));
449
+ }
412
450
  prompts_1.log.info(picocolors_1.default.dim(" Add the printed storageWorkerUrl to EXPO_PUBLIC_STORAGE_WORKER_URL in .env.local."));
413
451
  }
452
+ else if (isCloudflareMode && cloudflareFullyDeployed) {
453
+ prompts_1.log.info("");
454
+ prompts_1.log.info(` ${picocolors_1.default.green("✓")} Cloudflare Worker URL: ${picocolors_1.default.cyan(cloudflareSetup.workerUrl)}`);
455
+ prompts_1.log.info(picocolors_1.default.dim(" EXPO_PUBLIC_STORAGE_WORKER_URL set in .env.local."));
456
+ }
414
457
  if (isMagicCodeMode) {
415
458
  if (!instantReady) {
416
459
  prompts_1.log.info("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-100x-mobile",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Scaffold a full-stack mobile app with Expo + Convex + Clerk in seconds",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "typecheck": "tsc --noEmit",
14
14
  "test": "vitest run",
15
15
  "check": "npm run typecheck && npm test && npm pack --dry-run",
16
- "prepublishOnly": "npm run check"
16
+ "prepublishOnly": "npm run build && npm run check"
17
17
  },
18
18
  "files": [
19
19
  "dist"