create-better-fullstack 1.7.1 → 1.8.1

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/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __reExport } from "./chunk-CCII7kTE.mjs";
3
- import { a as DEFAULT_CONFIG, c as getDefaultConfig, i as getLatestCLIVersion, l as getUserPkgManager, n as updateBtsConfig, o as DEFAULT_UI_LIBRARY_BY_FRONTEND, r as writeBtsConfig, t as readBtsConfig } from "./bts-config-bOXo9tbL.mjs";
4
- import { _ as setIsFirstPrompt$1, a as canPromptInteractively, c as CLIError, d as exitWithError, f as handleError, g as runWithContextAsync, h as isSilent, l as UserCancelledError, m as isFirstPrompt, o as getPackageExecutionArgs, p as didLastPromptShowUI, s as addPackageDependency, t as setupAddons, u as exitCancelled, v as setLastPromptShownUI } from "./addons-setup-CGhYT2qC.mjs";
3
+ import { a as DEFAULT_CONFIG, c as getDefaultConfig, i as getLatestCLIVersion, l as getUserPkgManager, n as updateBtsConfig, o as DEFAULT_UI_LIBRARY_BY_FRONTEND, r as writeBtsConfig, t as readBtsConfig } from "./bts-config-YcroedMK.mjs";
4
+ import { _ as setIsFirstPrompt$1, a as canPromptInteractively, c as CLIError, d as exitWithError, f as handleError, g as runWithContextAsync, h as isSilent, l as UserCancelledError, m as isFirstPrompt, o as getPackageExecutionArgs, p as didLastPromptShowUI, s as addPackageDependency, t as setupAddons, u as exitCancelled, v as setLastPromptShownUI } from "./addons-setup-CUmA_nra.mjs";
5
5
  import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
6
6
  import { createRouterClient, os } from "@orpc/server";
7
7
  import pc from "picocolors";
@@ -363,7 +363,7 @@ const CreateCommandOptionsSchema = z.object({
363
363
  yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
364
364
  verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
365
365
  dryRun: z.boolean().optional().default(false).describe("Preview generated file tree without writing to disk"),
366
- ecosystem: types_exports.EcosystemSchema.optional().describe("Language ecosystem (typescript, rust, python, go, or java)"),
366
+ ecosystem: types_exports.EcosystemSchema.optional().describe("Language ecosystem (typescript, react-native, rust, python, go, java, or elixir)"),
367
367
  database: types_exports.DatabaseSchema.optional(),
368
368
  orm: types_exports.ORMSchema.optional(),
369
369
  auth: types_exports.AuthSchema.optional(),
@@ -388,6 +388,13 @@ const CreateCommandOptionsSchema = z.object({
388
388
  i18n: types_exports.I18nSchema.optional().describe("Internationalization (i18n) library"),
389
389
  search: types_exports.SearchSchema.optional().describe("Search engine solution"),
390
390
  fileStorage: types_exports.FileStorageSchema.optional().describe("File storage solution (S3, R2)"),
391
+ mobileNavigation: types_exports.MobileNavigationSchema.optional().describe("Mobile navigation (expo-router, react-navigation)"),
392
+ mobileUI: types_exports.MobileUISchema.optional().describe("Mobile UI (tamagui, gluestack-ui, uniwind, unistyles)"),
393
+ mobileStorage: types_exports.MobileStorageSchema.optional().describe("Mobile storage (mmkv)"),
394
+ mobileTesting: types_exports.MobileTestingSchema.optional().describe("Mobile testing (maestro, react-native-testing-library)"),
395
+ mobilePush: types_exports.MobilePushSchema.optional().describe("Mobile push notifications (expo-notifications)"),
396
+ mobileOTA: types_exports.MobileOTASchema.optional().describe("Mobile OTA updates (expo-updates)"),
397
+ mobileDeepLinking: types_exports.MobileDeepLinkingSchema.optional().describe("Mobile deep linking (expo-linking)"),
391
398
  frontend: z.array(types_exports.FrontendSchema).optional(),
392
399
  astroIntegration: types_exports.AstroIntegrationSchema.optional().describe("Astro UI framework integration (react, vue, svelte, solid)"),
393
400
  addons: z.array(types_exports.AddonsSchema).optional(),
@@ -446,6 +453,21 @@ const CreateCommandOptionsSchema = z.object({
446
453
  javaAuth: types_exports.JavaAuthSchema.optional().describe("Java auth (spring-security)"),
447
454
  javaLibraries: z.array(types_exports.JavaLibrariesSchema).optional().describe("Java application libraries"),
448
455
  javaTestingLibraries: z.array(types_exports.JavaTestingLibrariesSchema).optional().describe("Java testing libraries"),
456
+ elixirWebFramework: types_exports.ElixirWebFrameworkSchema.optional().describe("Elixir web framework (phoenix, phoenix-live-view, none)"),
457
+ elixirOrm: types_exports.ElixirOrmSchema.optional().describe("Elixir ORM/database (ecto, ecto-sql, none)"),
458
+ elixirAuth: types_exports.ElixirAuthSchema.optional().describe("Elixir auth (phx-gen-auth, ueberauth, guardian, none)"),
459
+ elixirApi: types_exports.ElixirApiSchema.optional().describe("Elixir API layer (rest, absinthe, none)"),
460
+ elixirRealtime: types_exports.ElixirRealtimeSchema.optional().describe("Elixir realtime (channels, presence, pubsub, live-view-streams, none)"),
461
+ elixirJobs: types_exports.ElixirJobsSchema.optional().describe("Elixir jobs (oban, quantum, none)"),
462
+ elixirValidation: types_exports.ElixirValidationSchema.optional().describe("Elixir validation (ecto-changesets, nimble-options, none)"),
463
+ elixirHttp: types_exports.ElixirHttpSchema.optional().describe("Elixir HTTP client (req, finch, none)"),
464
+ elixirJson: types_exports.ElixirJsonSchema.optional().describe("Elixir JSON library (jason, none)"),
465
+ elixirEmail: types_exports.ElixirEmailSchema.optional().describe("Elixir email library (swoosh, none)"),
466
+ elixirCaching: types_exports.ElixirCachingSchema.optional().describe("Elixir caching (cachex, nebulex, none)"),
467
+ elixirObservability: types_exports.ElixirObservabilitySchema.optional().describe("Elixir observability (telemetry, opentelemetry, prom_ex, none)"),
468
+ elixirTesting: types_exports.ElixirTestingSchema.optional().describe("Elixir testing (ex_unit, mox, bypass, wallaby, none)"),
469
+ elixirQuality: types_exports.ElixirQualitySchema.optional().describe("Elixir code quality (credo, dialyxir, sobelow, none)"),
470
+ elixirDeploy: types_exports.ElixirDeploySchema.optional().describe("Elixir deploy target (docker, fly, gigalixir, mix-release, none)"),
449
471
  aiDocs: z.array(types_exports.AiDocsSchema).optional().describe("AI documentation files (claude-md, agents-md, cursorrules)")
450
472
  });
451
473
  const CreateCommandInputSchema = z.tuple([types_exports.ProjectNameSchema.optional(), CreateCommandOptionsSchema]);
@@ -534,6 +556,7 @@ function ensureSingleWebAndNative(frontends) {
534
556
  }
535
557
  const FULLSTACK_FRONTENDS$1 = [
536
558
  "next",
559
+ "vinext",
537
560
  "tanstack-start",
538
561
  "astro",
539
562
  "nuxt",
@@ -545,11 +568,11 @@ function validateSelfBackendCompatibility(providedFlags, options, config) {
545
568
  const frontends = config.frontend || options.frontend || [];
546
569
  if (backend === "self") {
547
570
  const { web, native } = splitFrontends$1(frontends);
548
- if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) exitWithError("Backend 'self' (fullstack) only supports Next.js, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, or --frontend solid-start.");
571
+ if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) exitWithError("Backend 'self' (fullstack) only supports Next.js, Vinext, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend vinext, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, or --frontend solid-start.");
549
572
  if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
550
573
  }
551
574
  const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
552
- if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) only supports Next.js, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, --frontend solid-start, or choose a different backend.");
575
+ if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) only supports Next.js, Vinext, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend vinext, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, --frontend solid-start, or choose a different backend.");
553
576
  }
554
577
  const WORKERS_COMPATIBLE_BACKENDS = [
555
578
  "hono",
@@ -647,11 +670,11 @@ function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime,
647
670
  };
648
671
  if (ecosystem === "typescript" && !hasDockerComposeCompatibleFrontend(frontend)) return {
649
672
  isCompatible: false,
650
- reason: "Docker Compose currently supports Next.js, TanStack Router, React Router, React Vite, Solid, or Astro"
673
+ reason: "Docker Compose currently supports Next.js, Vinext, TanStack Router, React Router, React Vite, Solid, or Astro"
651
674
  };
652
- if (ecosystem === "typescript" && backend === "self" && !frontend.includes("next")) return {
675
+ if (ecosystem === "typescript" && backend === "self" && !frontend.includes("next") && !frontend.includes("vinext")) return {
653
676
  isCompatible: false,
654
- reason: "Docker Compose self-backend support currently requires Next.js"
677
+ reason: "Docker Compose self-backend support currently requires Next.js or Vinext"
655
678
  };
656
679
  if (ecosystem === "rust" && rustFrontend && rustFrontend !== "none") return {
657
680
  isCompatible: false,
@@ -1093,6 +1116,10 @@ function getAddonDisplay(addon) {
1093
1116
  label = "Storybook";
1094
1117
  hint = "Component development and testing workshop";
1095
1118
  break;
1119
+ case "swr":
1120
+ label = "SWR";
1121
+ hint = "React Hooks for data fetching and caching";
1122
+ break;
1096
1123
  case "tanstack-query":
1097
1124
  label = "TanStack Query";
1098
1125
  hint = "Powerful async state management & data fetching";
@@ -1146,6 +1173,7 @@ const ADDON_GROUPS = {
1146
1173
  ],
1147
1174
  Integrations: ["msw", "storybook"],
1148
1175
  "AI Agents": ["mcp", "skills"],
1176
+ "Data Fetching": ["swr"],
1149
1177
  TanStack: [
1150
1178
  "tanstack-query",
1151
1179
  "tanstack-table",
@@ -1154,6 +1182,12 @@ const ADDON_GROUPS = {
1154
1182
  "tanstack-pacer"
1155
1183
  ]
1156
1184
  };
1185
+ function createGroupedAddonOptions() {
1186
+ return Object.fromEntries(Object.keys(ADDON_GROUPS).map((group$1) => [group$1, []]));
1187
+ }
1188
+ function getAddonGroup(addon) {
1189
+ return Object.entries(ADDON_GROUPS).find(([, addons]) => addons.includes(addon))?.[0];
1190
+ }
1157
1191
  function validateAddonCompatibilityForPrompt(addon, frontends, auth, backend, runtime) {
1158
1192
  return validateAddonCompatibility$1(addon, frontends, auth, backend, runtime);
1159
1193
  }
@@ -1163,14 +1197,7 @@ function getCompatibleAddonsForPrompt(allAddons, frontends, existingAddons = [],
1163
1197
  async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
1164
1198
  if (addons !== void 0) return addons;
1165
1199
  const allAddons = types_exports.AddonsSchema.options.filter((addon) => addon !== "none");
1166
- const groupedOptions = {
1167
- Tooling: [],
1168
- Documentation: [],
1169
- Extensions: [],
1170
- Integrations: [],
1171
- "AI Agents": [],
1172
- TanStack: []
1173
- };
1200
+ const groupedOptions = createGroupedAddonOptions();
1174
1201
  const frontendsArray = frontends || [];
1175
1202
  for (const addon of allAddons) {
1176
1203
  const { isCompatible } = validateAddonCompatibilityForPrompt(addon, frontendsArray, auth, backend, runtime);
@@ -1181,12 +1208,8 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
1181
1208
  label,
1182
1209
  hint
1183
1210
  };
1184
- if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
1185
- else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
1186
- else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
1187
- else if (ADDON_GROUPS.Integrations.includes(addon)) groupedOptions.Integrations.push(option);
1188
- else if (ADDON_GROUPS["AI Agents"].includes(addon)) groupedOptions["AI Agents"].push(option);
1189
- else if (ADDON_GROUPS.TanStack.includes(addon)) groupedOptions.TanStack.push(option);
1211
+ const group$1 = getAddonGroup(addon);
1212
+ if (group$1) groupedOptions[group$1].push(option);
1190
1213
  }
1191
1214
  Object.keys(groupedOptions).forEach((group$1) => {
1192
1215
  if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
@@ -1207,14 +1230,7 @@ async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
1207
1230
  return response;
1208
1231
  }
1209
1232
  async function getAddonsToAdd(frontend, existingAddons = [], auth, backend, runtime) {
1210
- const groupedOptions = {
1211
- Tooling: [],
1212
- Documentation: [],
1213
- Extensions: [],
1214
- Integrations: [],
1215
- "AI Agents": [],
1216
- TanStack: []
1217
- };
1233
+ const groupedOptions = createGroupedAddonOptions();
1218
1234
  const frontendArray = frontend || [];
1219
1235
  const compatibleAddons = getCompatibleAddonsForPrompt(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth, backend, runtime);
1220
1236
  for (const addon of compatibleAddons) {
@@ -1224,12 +1240,8 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth, backend, runt
1224
1240
  label,
1225
1241
  hint
1226
1242
  };
1227
- if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
1228
- else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
1229
- else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
1230
- else if (ADDON_GROUPS.Integrations.includes(addon)) groupedOptions.Integrations.push(option);
1231
- else if (ADDON_GROUPS["AI Agents"].includes(addon)) groupedOptions["AI Agents"].push(option);
1232
- else if (ADDON_GROUPS.TanStack.includes(addon)) groupedOptions.TanStack.push(option);
1243
+ const group$1 = getAddonGroup(addon);
1244
+ if (group$1) groupedOptions[group$1].push(option);
1233
1245
  }
1234
1246
  Object.keys(groupedOptions).forEach((group$1) => {
1235
1247
  if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
@@ -1513,6 +1525,24 @@ async function runGradleTests({ projectDir }) {
1513
1525
  if (error instanceof Error) consola.error(pc.red(`Gradle test error: ${error.message}`));
1514
1526
  }
1515
1527
  }
1528
+ async function runMixCompile({ projectDir }) {
1529
+ const s = spinner();
1530
+ try {
1531
+ s.start("Running mix deps.get and mix compile...");
1532
+ await $({
1533
+ cwd: projectDir,
1534
+ stderr: "inherit"
1535
+ })`mix deps.get`;
1536
+ await $({
1537
+ cwd: projectDir,
1538
+ stderr: "inherit"
1539
+ })`mix compile`;
1540
+ s.stop("Elixir dependencies installed and project compiled");
1541
+ } catch (error) {
1542
+ s.stop(pc.red("mix compile failed"));
1543
+ if (error instanceof Error) consola.error(pc.red(`Mix error: ${error.message}`));
1544
+ }
1545
+ }
1516
1546
 
1517
1547
  //#endregion
1518
1548
  //#region src/helpers/core/add-handler.ts
@@ -1817,6 +1847,7 @@ function resolveAnimationPrompt(context = {}) {
1817
1847
  "react-vite",
1818
1848
  "tanstack-start",
1819
1849
  "next",
1850
+ "vinext",
1820
1851
  "redwood"
1821
1852
  ].includes(f));
1822
1853
  const isFresh = web.includes("fresh");
@@ -2054,6 +2085,7 @@ async function getAuthChoice(auth, backend, frontend, ecosystem = "typescript")
2054
2085
  //#region src/prompts/backend.ts
2055
2086
  const FULLSTACK_FRONTENDS = [
2056
2087
  "next",
2088
+ "vinext",
2057
2089
  "tanstack-start",
2058
2090
  "astro",
2059
2091
  "nuxt",
@@ -2179,6 +2211,12 @@ const CACHING_PROMPT_OPTIONS = [{
2179
2211
  hint: "Skip caching layer setup"
2180
2212
  }];
2181
2213
  function resolveCachingPrompt(context = {}) {
2214
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
2215
+ shouldPrompt: false,
2216
+ mode: "single",
2217
+ options: [],
2218
+ autoValue: "none"
2219
+ };
2182
2220
  if (context.ecosystem && context.ecosystem !== "typescript") return context.caching !== void 0 ? {
2183
2221
  shouldPrompt: false,
2184
2222
  mode: "single",
@@ -2247,6 +2285,11 @@ const CMS_PROMPT_OPTIONS = [
2247
2285
  label: "TinaCMS",
2248
2286
  hint: "Git-backed headless CMS with visual editing"
2249
2287
  },
2288
+ {
2289
+ value: "directus",
2290
+ label: "Directus",
2291
+ hint: "Open data platform and headless CMS for SQL databases"
2292
+ },
2250
2293
  {
2251
2294
  value: "none",
2252
2295
  label: "None",
@@ -2579,6 +2622,317 @@ async function getDBSetupChoice(databaseType, dbSetup, _orm, backend, runtime) {
2579
2622
  return response;
2580
2623
  }
2581
2624
 
2625
+ //#endregion
2626
+ //#region src/prompts/elixir-ecosystem.ts
2627
+ function makeChoice(message, options, defaultValue, value) {
2628
+ const resolution = createStaticSinglePromptResolution(options, defaultValue, value);
2629
+ if (!resolution.shouldPrompt) return Promise.resolve(resolution.autoValue ?? defaultValue);
2630
+ return navigableSelect({
2631
+ message,
2632
+ options: resolution.options,
2633
+ initialValue: resolution.initialValue
2634
+ }).then((response) => isCancel$1(response) ? exitCancelled("Operation cancelled") : response);
2635
+ }
2636
+ const WEB_FRAMEWORK_OPTIONS = [
2637
+ {
2638
+ value: "phoenix",
2639
+ label: "Phoenix",
2640
+ hint: "Conventional Phoenix web application"
2641
+ },
2642
+ {
2643
+ value: "phoenix-live-view",
2644
+ label: "Phoenix LiveView",
2645
+ hint: "Server-rendered realtime UI"
2646
+ },
2647
+ {
2648
+ value: "none",
2649
+ label: "None",
2650
+ hint: "No Elixir web framework"
2651
+ }
2652
+ ];
2653
+ const ORM_OPTIONS = [
2654
+ {
2655
+ value: "ecto-sql",
2656
+ label: "Ecto SQL",
2657
+ hint: "Ecto plus SQL adapters and migrations"
2658
+ },
2659
+ {
2660
+ value: "ecto",
2661
+ label: "Ecto",
2662
+ hint: "Ecto schemas and changesets without SQL repo wiring"
2663
+ },
2664
+ {
2665
+ value: "none",
2666
+ label: "None",
2667
+ hint: "No database layer"
2668
+ }
2669
+ ];
2670
+ const AUTH_OPTIONS = [
2671
+ {
2672
+ value: "phx-gen-auth",
2673
+ label: "phx.gen.auth",
2674
+ hint: "Phoenix account/session scaffold"
2675
+ },
2676
+ {
2677
+ value: "ueberauth",
2678
+ label: "Ueberauth",
2679
+ hint: "OAuth strategy foundation"
2680
+ },
2681
+ {
2682
+ value: "guardian",
2683
+ label: "Guardian",
2684
+ hint: "JWT authentication foundation"
2685
+ },
2686
+ {
2687
+ value: "none",
2688
+ label: "None",
2689
+ hint: "No auth layer"
2690
+ }
2691
+ ];
2692
+ const API_OPTIONS = [
2693
+ {
2694
+ value: "rest",
2695
+ label: "Phoenix REST",
2696
+ hint: "Controllers and JSON endpoints"
2697
+ },
2698
+ {
2699
+ value: "absinthe",
2700
+ label: "Absinthe GraphQL",
2701
+ hint: "GraphQL schema and resolvers"
2702
+ },
2703
+ {
2704
+ value: "none",
2705
+ label: "None",
2706
+ hint: "No API layer"
2707
+ }
2708
+ ];
2709
+ const REALTIME_OPTIONS = [
2710
+ {
2711
+ value: "channels",
2712
+ label: "Phoenix Channels",
2713
+ hint: "WebSocket channel endpoint"
2714
+ },
2715
+ {
2716
+ value: "presence",
2717
+ label: "Phoenix Presence",
2718
+ hint: "Presence tracking over PubSub"
2719
+ },
2720
+ {
2721
+ value: "pubsub",
2722
+ label: "Phoenix PubSub",
2723
+ hint: "PubSub foundation only"
2724
+ },
2725
+ {
2726
+ value: "live-view-streams",
2727
+ label: "LiveView Streams",
2728
+ hint: "Realtime LiveView stream demo"
2729
+ },
2730
+ {
2731
+ value: "none",
2732
+ label: "None",
2733
+ hint: "No realtime feature"
2734
+ }
2735
+ ];
2736
+ const JOB_OPTIONS = [
2737
+ {
2738
+ value: "oban",
2739
+ label: "Oban",
2740
+ hint: "PostgreSQL-backed jobs and workers"
2741
+ },
2742
+ {
2743
+ value: "quantum",
2744
+ label: "Quantum",
2745
+ hint: "Cron-like scheduler"
2746
+ },
2747
+ {
2748
+ value: "none",
2749
+ label: "None",
2750
+ hint: "No jobs layer"
2751
+ }
2752
+ ];
2753
+ const VALIDATION_OPTIONS = [
2754
+ {
2755
+ value: "ecto-changesets",
2756
+ label: "Ecto Changesets",
2757
+ hint: "Data validation with Ecto"
2758
+ },
2759
+ {
2760
+ value: "nimble-options",
2761
+ label: "NimbleOptions",
2762
+ hint: "Declarative option validation"
2763
+ },
2764
+ {
2765
+ value: "none",
2766
+ label: "None",
2767
+ hint: "No extra validation helper"
2768
+ }
2769
+ ];
2770
+ const HTTP_OPTIONS = [
2771
+ {
2772
+ value: "req",
2773
+ label: "Req",
2774
+ hint: "High-level HTTP client"
2775
+ },
2776
+ {
2777
+ value: "finch",
2778
+ label: "Finch",
2779
+ hint: "Pooled HTTP client"
2780
+ },
2781
+ {
2782
+ value: "none",
2783
+ label: "None",
2784
+ hint: "No HTTP client"
2785
+ }
2786
+ ];
2787
+ const JSON_OPTIONS = [{
2788
+ value: "jason",
2789
+ label: "Jason",
2790
+ hint: "Phoenix default JSON library"
2791
+ }, {
2792
+ value: "none",
2793
+ label: "None",
2794
+ hint: "No JSON library"
2795
+ }];
2796
+ const EMAIL_OPTIONS = [{
2797
+ value: "swoosh",
2798
+ label: "Swoosh",
2799
+ hint: "Phoenix email library"
2800
+ }, {
2801
+ value: "none",
2802
+ label: "None",
2803
+ hint: "No email library"
2804
+ }];
2805
+ const CACHING_OPTIONS = [
2806
+ {
2807
+ value: "cachex",
2808
+ label: "Cachex",
2809
+ hint: "In-memory cache"
2810
+ },
2811
+ {
2812
+ value: "nebulex",
2813
+ label: "Nebulex",
2814
+ hint: "Cache abstraction"
2815
+ },
2816
+ {
2817
+ value: "none",
2818
+ label: "None",
2819
+ hint: "No cache layer"
2820
+ }
2821
+ ];
2822
+ const OBSERVABILITY_OPTIONS = [
2823
+ {
2824
+ value: "telemetry",
2825
+ label: "Telemetry",
2826
+ hint: "Phoenix telemetry metrics"
2827
+ },
2828
+ {
2829
+ value: "opentelemetry",
2830
+ label: "OpenTelemetry",
2831
+ hint: "Distributed tracing foundation"
2832
+ },
2833
+ {
2834
+ value: "prom_ex",
2835
+ label: "PromEx",
2836
+ hint: "Prometheus metrics for Phoenix"
2837
+ },
2838
+ {
2839
+ value: "none",
2840
+ label: "None",
2841
+ hint: "No observability add-on"
2842
+ }
2843
+ ];
2844
+ const TESTING_OPTIONS = [
2845
+ {
2846
+ value: "ex_unit",
2847
+ label: "ExUnit",
2848
+ hint: "Standard Elixir tests"
2849
+ },
2850
+ {
2851
+ value: "mox",
2852
+ label: "Mox",
2853
+ hint: "Concurrent-safe mocks"
2854
+ },
2855
+ {
2856
+ value: "bypass",
2857
+ label: "Bypass",
2858
+ hint: "External HTTP service fakes"
2859
+ },
2860
+ {
2861
+ value: "wallaby",
2862
+ label: "Wallaby",
2863
+ hint: "Browser acceptance testing"
2864
+ },
2865
+ {
2866
+ value: "none",
2867
+ label: "None",
2868
+ hint: "No extra test library"
2869
+ }
2870
+ ];
2871
+ const QUALITY_OPTIONS = [
2872
+ {
2873
+ value: "credo",
2874
+ label: "Credo",
2875
+ hint: "Static code analysis"
2876
+ },
2877
+ {
2878
+ value: "dialyxir",
2879
+ label: "Dialyxir",
2880
+ hint: "Dialyzer integration"
2881
+ },
2882
+ {
2883
+ value: "sobelow",
2884
+ label: "Sobelow",
2885
+ hint: "Phoenix security analysis"
2886
+ },
2887
+ {
2888
+ value: "none",
2889
+ label: "None",
2890
+ hint: "No code quality tool"
2891
+ }
2892
+ ];
2893
+ const DEPLOY_OPTIONS = [
2894
+ {
2895
+ value: "docker",
2896
+ label: "Docker",
2897
+ hint: "Dockerfile for Phoenix releases"
2898
+ },
2899
+ {
2900
+ value: "fly",
2901
+ label: "Fly.io",
2902
+ hint: "Fly.io release config"
2903
+ },
2904
+ {
2905
+ value: "gigalixir",
2906
+ label: "Gigalixir",
2907
+ hint: "Gigalixir Procfile and notes"
2908
+ },
2909
+ {
2910
+ value: "mix-release",
2911
+ label: "Mix Release",
2912
+ hint: "Release-ready runtime config"
2913
+ },
2914
+ {
2915
+ value: "none",
2916
+ label: "None",
2917
+ hint: "No deploy files"
2918
+ }
2919
+ ];
2920
+ const getElixirWebFrameworkChoice = (value) => makeChoice("Select Elixir web framework", WEB_FRAMEWORK_OPTIONS, "phoenix", value);
2921
+ const getElixirOrmChoice = (value) => makeChoice("Select Elixir database layer", ORM_OPTIONS, "ecto-sql", value);
2922
+ const getElixirAuthChoice = (value) => makeChoice("Select Elixir auth", AUTH_OPTIONS, "none", value);
2923
+ const getElixirApiChoice = (value) => makeChoice("Select Elixir API layer", API_OPTIONS, "rest", value);
2924
+ const getElixirRealtimeChoice = (value) => makeChoice("Select Elixir realtime feature", REALTIME_OPTIONS, "channels", value);
2925
+ const getElixirJobsChoice = (value) => makeChoice("Select Elixir jobs layer", JOB_OPTIONS, "none", value);
2926
+ const getElixirValidationChoice = (value) => makeChoice("Select Elixir validation", VALIDATION_OPTIONS, "ecto-changesets", value);
2927
+ const getElixirHttpChoice = (value) => makeChoice("Select Elixir HTTP client", HTTP_OPTIONS, "req", value);
2928
+ const getElixirJsonChoice = (value) => makeChoice("Select Elixir JSON library", JSON_OPTIONS, "jason", value);
2929
+ const getElixirEmailChoice = (value) => makeChoice("Select Elixir email library", EMAIL_OPTIONS, "none", value);
2930
+ const getElixirCachingChoice = (value) => makeChoice("Select Elixir caching", CACHING_OPTIONS, "none", value);
2931
+ const getElixirObservabilityChoice = (value) => makeChoice("Select Elixir observability", OBSERVABILITY_OPTIONS, "telemetry", value);
2932
+ const getElixirTestingChoice = (value) => makeChoice("Select Elixir testing", TESTING_OPTIONS, "ex_unit", value);
2933
+ const getElixirQualityChoice = (value) => makeChoice("Select Elixir code quality", QUALITY_OPTIONS, "credo", value);
2934
+ const getElixirDeployChoice = (value) => makeChoice("Select Elixir deploy target", DEPLOY_OPTIONS, "none", value);
2935
+
2582
2936
  //#endregion
2583
2937
  //#region src/prompts/ecosystem.ts
2584
2938
  async function getEcosystemChoice(ecosystem) {
@@ -2589,7 +2943,12 @@ async function getEcosystemChoice(ecosystem) {
2589
2943
  {
2590
2944
  value: "typescript",
2591
2945
  label: "TypeScript",
2592
- hint: "Full-stack TypeScript with React, Vue, Svelte, and more"
2946
+ hint: "Full-stack TypeScript web with React, Vue, Svelte, and more"
2947
+ },
2948
+ {
2949
+ value: "react-native",
2950
+ label: "React Native",
2951
+ hint: "Expo and React Native mobile apps with native integrations"
2593
2952
  },
2594
2953
  {
2595
2954
  value: "rust",
@@ -2605,6 +2964,11 @@ async function getEcosystemChoice(ecosystem) {
2605
2964
  value: "go",
2606
2965
  label: "Go",
2607
2966
  hint: "Go ecosystem with Gin, Echo, GORM, and more"
2967
+ },
2968
+ {
2969
+ value: "java",
2970
+ label: "Java",
2971
+ hint: "Java ecosystem with Spring Boot, Maven, Gradle, and more"
2608
2972
  }
2609
2973
  ],
2610
2974
  initialValue: "typescript"
@@ -2693,6 +3057,12 @@ const EMAIL_PROMPT_OPTIONS = [
2693
3057
  ];
2694
3058
  const NON_TYPESCRIPT_EMAIL_PROMPT_OPTIONS = EMAIL_PROMPT_OPTIONS.filter((option) => option.value === "resend" || option.value === "none");
2695
3059
  function resolveEmailPrompt(context = {}) {
3060
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
3061
+ shouldPrompt: false,
3062
+ mode: "single",
3063
+ options: [],
3064
+ autoValue: "none"
3065
+ };
2696
3066
  const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_EMAIL_PROMPT_OPTIONS : EMAIL_PROMPT_OPTIONS;
2697
3067
  if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
2698
3068
  shouldPrompt: false,
@@ -2774,6 +3144,11 @@ async function getFileStorageChoice(fileStorage, backend) {
2774
3144
  label: "Cloudflare R2",
2775
3145
  hint: "S3-compatible storage with zero egress fees"
2776
3146
  },
3147
+ {
3148
+ value: "cloudinary",
3149
+ label: "Cloudinary",
3150
+ hint: "Image and media storage with transformations"
3151
+ },
2777
3152
  {
2778
3153
  value: "none",
2779
3154
  label: "None",
@@ -2866,6 +3241,7 @@ function resolveFormsPrompt(context = {}) {
2866
3241
  "react-vite",
2867
3242
  "tanstack-start",
2868
3243
  "next",
3244
+ "vinext",
2869
3245
  "redwood"
2870
3246
  ].includes(f));
2871
3247
  const isSolid = web.includes("solid");
@@ -2949,6 +3325,11 @@ const WEB_FRONTEND_PROMPT_OPTIONS = [
2949
3325
  label: "Next.js",
2950
3326
  hint: "The React Framework for the Web"
2951
3327
  },
3328
+ {
3329
+ value: "vinext",
3330
+ label: "Vinext",
3331
+ hint: "The Vite Compiler for Next.js"
3332
+ },
2952
3333
  {
2953
3334
  value: "nuxt",
2954
3335
  label: "Nuxt",
@@ -3003,8 +3384,8 @@ const WEB_FRONTEND_PROMPT_OPTIONS = [
3003
3384
  const NATIVE_FRONTEND_PROMPT_OPTIONS = [
3004
3385
  {
3005
3386
  value: "native-bare",
3006
- label: "Bare",
3007
- hint: "Bare Expo without styling library"
3387
+ label: "StyleSheet",
3388
+ hint: "Expo with StyleSheet (no styling library)"
3008
3389
  },
3009
3390
  {
3010
3391
  value: "native-uniwind",
@@ -3101,6 +3482,17 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
3101
3482
  return result;
3102
3483
  }
3103
3484
  }
3485
+ async function getNativeFrontendChoice(frontendOptions) {
3486
+ if (frontendOptions !== void 0) return frontendOptions.filter((frontend) => frontend.startsWith("native-"));
3487
+ const nativeFramework = await navigableSelect({
3488
+ message: "Choose React Native app type",
3489
+ options: NATIVE_FRONTEND_PROMPT_OPTIONS,
3490
+ initialValue: "native-bare"
3491
+ });
3492
+ if (isGoBack(nativeFramework)) return GO_BACK_SYMBOL;
3493
+ if (isCancel$1(nativeFramework)) return exitCancelled("Operation cancelled");
3494
+ return [nativeFramework];
3495
+ }
3104
3496
 
3105
3497
  //#endregion
3106
3498
  //#region src/prompts/git.ts
@@ -3421,6 +3813,18 @@ async function getinstallChoice(install, ecosystem, javaBuildTool) {
3421
3813
  if (isCancel$1(response$1)) return exitCancelled("Operation cancelled");
3422
3814
  return response$1;
3423
3815
  }
3816
+ if (ecosystem === "elixir") {
3817
+ if (!await commandExists("mix")) {
3818
+ log.warn("Mix is not installed. Please install Elixir from https://elixir-lang.org/install.html");
3819
+ return false;
3820
+ }
3821
+ const response$1 = await navigableConfirm({
3822
+ message: "Run mix deps.get and mix compile?",
3823
+ initialValue: DEFAULT_CONFIG.install
3824
+ });
3825
+ if (isCancel$1(response$1)) return exitCancelled("Operation cancelled");
3826
+ return response$1;
3827
+ }
3424
3828
  const response = await navigableConfirm({
3425
3829
  message: "Install dependencies?",
3426
3830
  initialValue: DEFAULT_CONFIG.install
@@ -3787,6 +4191,11 @@ const LOGGING_PROMPT_OPTIONS = [
3787
4191
  label: "Winston",
3788
4192
  hint: "Flexible logging library with multiple transports"
3789
4193
  },
4194
+ {
4195
+ value: "evlog",
4196
+ label: "evlog",
4197
+ hint: "Typed event-based logging for TypeScript"
4198
+ },
3790
4199
  {
3791
4200
  value: "none",
3792
4201
  label: "None",
@@ -3827,6 +4236,143 @@ async function getLoggingChoice(logging, backend) {
3827
4236
  return response;
3828
4237
  }
3829
4238
 
4239
+ //#endregion
4240
+ //#region src/prompts/mobile.ts
4241
+ const MOBILE_NAVIGATION_OPTIONS = [
4242
+ {
4243
+ value: "expo-router",
4244
+ label: "Expo Router",
4245
+ hint: "File-based routing for Expo apps"
4246
+ },
4247
+ {
4248
+ value: "react-navigation",
4249
+ label: "React Navigation",
4250
+ hint: "Code-defined native stacks and tabs"
4251
+ },
4252
+ {
4253
+ value: "none",
4254
+ label: "None",
4255
+ hint: "Skip navigation setup"
4256
+ }
4257
+ ];
4258
+ const MOBILE_UI_OPTIONS = [
4259
+ {
4260
+ value: "none",
4261
+ label: "None",
4262
+ hint: "Use React Native primitives"
4263
+ },
4264
+ {
4265
+ value: "tamagui",
4266
+ label: "Tamagui",
4267
+ hint: "Universal themed UI primitives"
4268
+ },
4269
+ {
4270
+ value: "gluestack-ui",
4271
+ label: "Gluestack UI",
4272
+ hint: "Accessible cross-platform components"
4273
+ },
4274
+ {
4275
+ value: "uniwind",
4276
+ label: "Uniwind",
4277
+ hint: "Tailwind-style React Native styling"
4278
+ },
4279
+ {
4280
+ value: "unistyles",
4281
+ label: "Unistyles",
4282
+ hint: "Type-safe React Native stylesheets"
4283
+ }
4284
+ ];
4285
+ const MOBILE_STORAGE_OPTIONS = [{
4286
+ value: "none",
4287
+ label: "None",
4288
+ hint: "Skip device storage helpers"
4289
+ }, {
4290
+ value: "mmkv",
4291
+ label: "MMKV",
4292
+ hint: "Fast encrypted key-value storage"
4293
+ }];
4294
+ const MOBILE_TESTING_OPTIONS = [
4295
+ {
4296
+ value: "none",
4297
+ label: "None",
4298
+ hint: "Skip mobile testing setup"
4299
+ },
4300
+ {
4301
+ value: "maestro",
4302
+ label: "Maestro",
4303
+ hint: "Mobile E2E flow files"
4304
+ },
4305
+ {
4306
+ value: "react-native-testing-library",
4307
+ label: "React Native Testing Library",
4308
+ hint: "Unit tests for native components"
4309
+ },
4310
+ {
4311
+ value: "maestro-react-native-testing-library",
4312
+ label: "Maestro + RN Testing Library",
4313
+ hint: "Mobile E2E flows and unit tests"
4314
+ }
4315
+ ];
4316
+ const MOBILE_PUSH_OPTIONS = [{
4317
+ value: "none",
4318
+ label: "None",
4319
+ hint: "Skip push notification setup"
4320
+ }, {
4321
+ value: "expo-notifications",
4322
+ label: "Expo Notifications",
4323
+ hint: "Expo push token helper"
4324
+ }];
4325
+ const MOBILE_OTA_OPTIONS = [{
4326
+ value: "none",
4327
+ label: "None",
4328
+ hint: "Skip OTA update setup"
4329
+ }, {
4330
+ value: "expo-updates",
4331
+ label: "Expo Updates",
4332
+ hint: "Runtime version and update helper"
4333
+ }];
4334
+ const MOBILE_DEEP_LINKING_OPTIONS = [{
4335
+ value: "expo-linking",
4336
+ label: "Expo Linking",
4337
+ hint: "Scheme config and redirect URI helpers"
4338
+ }, {
4339
+ value: "none",
4340
+ label: "None",
4341
+ hint: "Skip deep link helpers"
4342
+ }];
4343
+ async function promptMobileOption(options, defaultValue, selected, message) {
4344
+ const resolution = createStaticSinglePromptResolution(options, defaultValue, selected);
4345
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? defaultValue;
4346
+ const response = await navigableSelect({
4347
+ message,
4348
+ options: resolution.options,
4349
+ initialValue: resolution.initialValue
4350
+ });
4351
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4352
+ return response;
4353
+ }
4354
+ function getMobileNavigationChoice(mobileNavigation) {
4355
+ return promptMobileOption(MOBILE_NAVIGATION_OPTIONS, "expo-router", mobileNavigation, "Select mobile navigation");
4356
+ }
4357
+ function getMobileUIChoice(mobileUI) {
4358
+ return promptMobileOption(MOBILE_UI_OPTIONS, "none", mobileUI, "Select mobile UI");
4359
+ }
4360
+ function getMobileStorageChoice(mobileStorage) {
4361
+ return promptMobileOption(MOBILE_STORAGE_OPTIONS, "none", mobileStorage, "Select mobile storage");
4362
+ }
4363
+ function getMobileTestingChoice(mobileTesting) {
4364
+ return promptMobileOption(MOBILE_TESTING_OPTIONS, "none", mobileTesting, "Select mobile testing");
4365
+ }
4366
+ function getMobilePushChoice(mobilePush) {
4367
+ return promptMobileOption(MOBILE_PUSH_OPTIONS, "none", mobilePush, "Select mobile push");
4368
+ }
4369
+ function getMobileOTAChoice(mobileOTA) {
4370
+ return promptMobileOption(MOBILE_OTA_OPTIONS, "none", mobileOTA, "Select mobile OTA updates");
4371
+ }
4372
+ function getMobileDeepLinkingChoice(mobileDeepLinking) {
4373
+ return promptMobileOption(MOBILE_DEEP_LINKING_OPTIONS, "expo-linking", mobileDeepLinking, "Select mobile deep linking");
4374
+ }
4375
+
3830
4376
  //#endregion
3831
4377
  //#region src/prompts/navigable-group.ts
3832
4378
  /**
@@ -3910,6 +4456,12 @@ const OBSERVABILITY_PROMPT_OPTIONS = [
3910
4456
  ];
3911
4457
  const NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS = OBSERVABILITY_PROMPT_OPTIONS.filter((option) => option.value === "sentry" || option.value === "none");
3912
4458
  function resolveObservabilityPrompt(context = {}) {
4459
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
4460
+ shouldPrompt: false,
4461
+ mode: "single",
4462
+ options: [],
4463
+ autoValue: "none"
4464
+ };
3913
4465
  const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS : OBSERVABILITY_PROMPT_OPTIONS;
3914
4466
  if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
3915
4467
  shouldPrompt: false,
@@ -5006,6 +5558,12 @@ const SEARCH_PROMPT_OPTIONS = [
5006
5558
  ];
5007
5559
  const NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS = SEARCH_PROMPT_OPTIONS.filter((option) => option.value === "meilisearch" || option.value === "none");
5008
5560
  function resolveSearchPrompt(context = {}) {
5561
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
5562
+ shouldPrompt: false,
5563
+ mode: "single",
5564
+ options: [],
5565
+ autoValue: "none"
5566
+ };
5009
5567
  const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS : SEARCH_PROMPT_OPTIONS;
5010
5568
  if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
5011
5569
  shouldPrompt: false,
@@ -5479,6 +6037,7 @@ function resolveStateManagementPrompt(context = {}) {
5479
6037
  "react-vite",
5480
6038
  "tanstack-start",
5481
6039
  "next",
6040
+ "vinext",
5482
6041
  "redwood"
5483
6042
  ].includes(f));
5484
6043
  const isFresh = web.includes("fresh");
@@ -5604,6 +6163,10 @@ const UI_LIBRARY_OPTIONS = {
5604
6163
  label: "shadcn/ui",
5605
6164
  hint: "Beautifully designed components built with Radix UI and Tailwind CSS"
5606
6165
  },
6166
+ "shadcn-svelte": {
6167
+ label: "shadcn-svelte",
6168
+ hint: "Svelte component collection styled with Tailwind CSS"
6169
+ },
5607
6170
  daisyui: {
5608
6171
  label: "daisyUI",
5609
6172
  hint: "Tailwind CSS component library with semantic class names"
@@ -5771,6 +6334,7 @@ const WEB_FRAMEWORKS = [
5771
6334
  "react-vite",
5772
6335
  "tanstack-start",
5773
6336
  "next",
6337
+ "vinext",
5774
6338
  "nuxt",
5775
6339
  "svelte",
5776
6340
  "solid",
@@ -5830,6 +6394,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5830
6394
  const result = await navigableGroup({
5831
6395
  ecosystem: () => getEcosystemChoice(flags.ecosystem),
5832
6396
  frontend: ({ results }) => {
6397
+ if (results.ecosystem === "react-native") return getNativeFrontendChoice(flags.frontend);
5833
6398
  if (results.ecosystem !== "typescript") return Promise.resolve([]);
5834
6399
  return getFrontendChoice(flags.frontend, flags.backend, flags.auth);
5835
6400
  },
@@ -5882,6 +6447,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5882
6447
  },
5883
6448
  auth: ({ results }) => {
5884
6449
  if (results.ecosystem === "typescript") return getAuthChoice(flags.auth, results.backend, results.frontend, "typescript");
6450
+ if (results.ecosystem === "react-native") return Promise.resolve(flags.auth ?? "none");
5885
6451
  if (results.ecosystem === "go") return getAuthChoice(flags.auth, void 0, void 0, "go");
5886
6452
  return Promise.resolve("none");
5887
6453
  },
@@ -5890,6 +6456,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5890
6456
  return getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend);
5891
6457
  },
5892
6458
  email: ({ results }) => {
6459
+ if (results.ecosystem === "react-native" || results.ecosystem === "elixir") return Promise.resolve("none");
5893
6460
  return getEmailChoice(flags.email, results.backend, results.ecosystem);
5894
6461
  },
5895
6462
  effect: ({ results }) => {
@@ -5961,6 +6528,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5961
6528
  return getLoggingChoice(flags.logging, results.backend);
5962
6529
  },
5963
6530
  observability: ({ results }) => {
6531
+ if (results.ecosystem === "react-native" || results.ecosystem === "elixir") return Promise.resolve("none");
5964
6532
  return getObservabilityChoice(flags.observability, results.backend, results.ecosystem);
5965
6533
  },
5966
6534
  featureFlags: ({ results }) => {
@@ -5976,6 +6544,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5976
6544
  return getCMSChoice(flags.cms, results.backend);
5977
6545
  },
5978
6546
  caching: ({ results }) => {
6547
+ if (results.ecosystem === "react-native" || results.ecosystem === "elixir") return Promise.resolve("none");
5979
6548
  return getCachingChoice(flags.caching, results.backend, results.ecosystem);
5980
6549
  },
5981
6550
  i18n: ({ results }) => {
@@ -5983,12 +6552,50 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5983
6552
  return getI18nChoice(flags.i18n, results.frontend);
5984
6553
  },
5985
6554
  search: ({ results }) => {
6555
+ if (results.ecosystem === "react-native" || results.ecosystem === "elixir") return Promise.resolve("none");
5986
6556
  return getSearchChoice(flags.search, results.backend, results.ecosystem);
5987
6557
  },
5988
6558
  fileStorage: ({ results }) => {
5989
6559
  if (results.ecosystem !== "typescript") return Promise.resolve("none");
5990
6560
  return getFileStorageChoice(flags.fileStorage, results.backend);
5991
6561
  },
6562
+ mobileNavigation: ({ results }) => {
6563
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6564
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6565
+ return getMobileNavigationChoice(flags.mobileNavigation);
6566
+ },
6567
+ mobileUI: ({ results }) => {
6568
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6569
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6570
+ if (results.frontend.includes("native-uniwind")) return Promise.resolve("uniwind");
6571
+ if (results.frontend.includes("native-unistyles")) return Promise.resolve("unistyles");
6572
+ return getMobileUIChoice(flags.mobileUI);
6573
+ },
6574
+ mobileStorage: ({ results }) => {
6575
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6576
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6577
+ return getMobileStorageChoice(flags.mobileStorage);
6578
+ },
6579
+ mobileTesting: ({ results }) => {
6580
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6581
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6582
+ return getMobileTestingChoice(flags.mobileTesting);
6583
+ },
6584
+ mobilePush: ({ results }) => {
6585
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6586
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6587
+ return getMobilePushChoice(flags.mobilePush);
6588
+ },
6589
+ mobileOTA: ({ results }) => {
6590
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6591
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6592
+ return getMobileOTAChoice(flags.mobileOTA);
6593
+ },
6594
+ mobileDeepLinking: ({ results }) => {
6595
+ if (results.ecosystem !== "typescript" && results.ecosystem !== "react-native") return Promise.resolve("none");
6596
+ if (!results.frontend?.some((frontend) => frontend.startsWith("native-"))) return Promise.resolve("none");
6597
+ return getMobileDeepLinkingChoice(flags.mobileDeepLinking);
6598
+ },
5992
6599
  rustWebFramework: ({ results }) => {
5993
6600
  if (results.ecosystem !== "rust") return Promise.resolve("none");
5994
6601
  return getRustWebFrameworkChoice(flags.rustWebFramework);
@@ -6118,10 +6725,70 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
6118
6725
  if (results.javaBuildTool === "none") return Promise.resolve([]);
6119
6726
  return getJavaTestingLibrariesChoice(flags.javaTestingLibraries);
6120
6727
  },
6728
+ elixirWebFramework: ({ results }) => {
6729
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6730
+ return getElixirWebFrameworkChoice(flags.elixirWebFramework);
6731
+ },
6732
+ elixirOrm: ({ results }) => {
6733
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6734
+ return getElixirOrmChoice(flags.elixirOrm);
6735
+ },
6736
+ elixirAuth: ({ results }) => {
6737
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6738
+ return getElixirAuthChoice(flags.elixirAuth);
6739
+ },
6740
+ elixirApi: ({ results }) => {
6741
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6742
+ return getElixirApiChoice(flags.elixirApi);
6743
+ },
6744
+ elixirRealtime: ({ results }) => {
6745
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6746
+ return getElixirRealtimeChoice(flags.elixirRealtime);
6747
+ },
6748
+ elixirJobs: ({ results }) => {
6749
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6750
+ return getElixirJobsChoice(flags.elixirJobs);
6751
+ },
6752
+ elixirValidation: ({ results }) => {
6753
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6754
+ return getElixirValidationChoice(flags.elixirValidation);
6755
+ },
6756
+ elixirHttp: ({ results }) => {
6757
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6758
+ return getElixirHttpChoice(flags.elixirHttp);
6759
+ },
6760
+ elixirJson: ({ results }) => {
6761
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6762
+ return getElixirJsonChoice(flags.elixirJson);
6763
+ },
6764
+ elixirEmail: ({ results }) => {
6765
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6766
+ return getElixirEmailChoice(flags.elixirEmail);
6767
+ },
6768
+ elixirCaching: ({ results }) => {
6769
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6770
+ return getElixirCachingChoice(flags.elixirCaching);
6771
+ },
6772
+ elixirObservability: ({ results }) => {
6773
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6774
+ return getElixirObservabilityChoice(flags.elixirObservability);
6775
+ },
6776
+ elixirTesting: ({ results }) => {
6777
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6778
+ return getElixirTestingChoice(flags.elixirTesting);
6779
+ },
6780
+ elixirQuality: ({ results }) => {
6781
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6782
+ return getElixirQualityChoice(flags.elixirQuality);
6783
+ },
6784
+ elixirDeploy: ({ results }) => {
6785
+ if (results.ecosystem !== "elixir") return Promise.resolve("none");
6786
+ return getElixirDeployChoice(flags.elixirDeploy);
6787
+ },
6121
6788
  aiDocs: () => getAiDocsChoice(flags.aiDocs),
6122
6789
  git: () => getGitChoice(flags.git),
6123
6790
  packageManager: ({ results }) => {
6124
- if (results.ecosystem === "rust" || results.ecosystem === "python" || results.ecosystem === "go" || results.ecosystem === "java") return Promise.resolve(flags.packageManager ?? getUserPkgManager());
6791
+ if (results.ecosystem === "rust" || results.ecosystem === "python" || results.ecosystem === "go" || results.ecosystem === "java" || results.ecosystem === "elixir") return Promise.resolve(flags.packageManager ?? getUserPkgManager());
6125
6792
  return getPackageManagerChoice(flags.packageManager);
6126
6793
  },
6127
6794
  install: ({ results }) => getinstallChoice(flags.install, results.ecosystem, results.javaBuildTool)
@@ -6170,6 +6837,13 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
6170
6837
  i18n: result.i18n,
6171
6838
  search: result.search,
6172
6839
  fileStorage: result.fileStorage,
6840
+ mobileNavigation: result.mobileNavigation,
6841
+ mobileUI: result.mobileUI,
6842
+ mobileStorage: result.mobileStorage,
6843
+ mobileTesting: result.mobileTesting,
6844
+ mobilePush: result.mobilePush,
6845
+ mobileOTA: result.mobileOTA,
6846
+ mobileDeepLinking: result.mobileDeepLinking,
6173
6847
  ecosystem: result.ecosystem,
6174
6848
  rustWebFramework: result.rustWebFramework,
6175
6849
  rustFrontend: result.rustFrontend,
@@ -6202,6 +6876,21 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
6202
6876
  javaAuth: result.javaAuth,
6203
6877
  javaLibraries: result.javaLibraries,
6204
6878
  javaTestingLibraries: result.javaTestingLibraries,
6879
+ elixirWebFramework: result.elixirWebFramework,
6880
+ elixirOrm: result.elixirOrm,
6881
+ elixirAuth: result.elixirAuth,
6882
+ elixirApi: result.elixirApi,
6883
+ elixirRealtime: result.elixirRealtime,
6884
+ elixirJobs: result.elixirJobs,
6885
+ elixirValidation: result.elixirValidation,
6886
+ elixirHttp: result.elixirHttp,
6887
+ elixirJson: result.elixirJson,
6888
+ elixirEmail: result.elixirEmail,
6889
+ elixirCaching: result.elixirCaching,
6890
+ elixirObservability: result.elixirObservability,
6891
+ elixirTesting: result.elixirTesting,
6892
+ elixirQuality: result.elixirQuality,
6893
+ elixirDeploy: result.elixirDeploy,
6205
6894
  aiDocs: result.aiDocs
6206
6895
  };
6207
6896
  }
@@ -6358,7 +7047,17 @@ function displayConfig(config) {
6358
7047
  if (config.jobQueue !== void 0) configDisplay.push(`${pc.blue("Job Queue:")} ${String(config.jobQueue)}`);
6359
7048
  if (config.logging !== void 0) configDisplay.push(`${pc.blue("Logging:")} ${String(config.logging)}`);
6360
7049
  if (config.observability !== void 0) configDisplay.push(`${pc.blue("Observability:")} ${String(config.observability)}`);
7050
+ if (config.featureFlags !== void 0) configDisplay.push(`${pc.blue("Feature Flags:")} ${String(config.featureFlags)}`);
7051
+ if (config.analytics !== void 0) configDisplay.push(`${pc.blue("Analytics:")} ${String(config.analytics)}`);
7052
+ if (config.mobileNavigation !== void 0) configDisplay.push(`${pc.blue("Mobile Navigation:")} ${String(config.mobileNavigation)}`);
7053
+ if (config.mobileUI !== void 0) configDisplay.push(`${pc.blue("Mobile UI:")} ${String(config.mobileUI)}`);
7054
+ if (config.mobileStorage !== void 0) configDisplay.push(`${pc.blue("Mobile Storage:")} ${String(config.mobileStorage)}`);
7055
+ if (config.mobileTesting !== void 0) configDisplay.push(`${pc.blue("Mobile Testing:")} ${String(config.mobileTesting)}`);
7056
+ if (config.mobilePush !== void 0) configDisplay.push(`${pc.blue("Mobile Push:")} ${String(config.mobilePush)}`);
7057
+ if (config.mobileOTA !== void 0) configDisplay.push(`${pc.blue("Mobile OTA:")} ${String(config.mobileOTA)}`);
7058
+ if (config.mobileDeepLinking !== void 0) configDisplay.push(`${pc.blue("Mobile Deep Linking:")} ${String(config.mobileDeepLinking)}`);
6361
7059
  if (config.caching !== void 0) configDisplay.push(`${pc.blue("Caching:")} ${String(config.caching)}`);
7060
+ if (config.i18n !== void 0) configDisplay.push(`${pc.blue("i18n:")} ${String(config.i18n)}`);
6362
7061
  if (config.cms !== void 0) configDisplay.push(`${pc.blue("CMS:")} ${String(config.cms)}`);
6363
7062
  if (config.search !== void 0) configDisplay.push(`${pc.blue("Search:")} ${String(config.search)}`);
6364
7063
  if (config.fileStorage !== void 0) configDisplay.push(`${pc.blue("File Storage:")} ${String(config.fileStorage)}`);
@@ -6372,6 +7071,11 @@ function displayConfig(config) {
6372
7071
  const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
6373
7072
  configDisplay.push(`${pc.blue("Examples:")} ${examplesText}`);
6374
7073
  }
7074
+ if (config.aiDocs !== void 0) {
7075
+ const aiDocs = Array.isArray(config.aiDocs) ? config.aiDocs : [config.aiDocs];
7076
+ const aiDocsText = aiDocs.length > 0 && aiDocs[0] !== void 0 ? aiDocs.join(", ") : "none";
7077
+ configDisplay.push(`${pc.blue("AI Docs:")} ${aiDocsText}`);
7078
+ }
6375
7079
  if (config.git !== void 0) {
6376
7080
  const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
6377
7081
  configDisplay.push(`${pc.blue("Git Init:")} ${gitText}`);
@@ -6465,6 +7169,13 @@ function getTypeScriptFlags(config) {
6465
7169
  flags.push(`--cms ${config.cms}`);
6466
7170
  flags.push(`--search ${config.search}`);
6467
7171
  flags.push(`--file-storage ${config.fileStorage}`);
7172
+ flags.push(`--mobile-navigation ${config.mobileNavigation}`);
7173
+ flags.push(`--mobile-ui ${config.mobileUI}`);
7174
+ flags.push(`--mobile-storage ${config.mobileStorage}`);
7175
+ flags.push(`--mobile-testing ${config.mobileTesting}`);
7176
+ flags.push(`--mobile-push ${config.mobilePush}`);
7177
+ flags.push(`--mobile-ota ${config.mobileOTA}`);
7178
+ flags.push(`--mobile-deep-linking ${config.mobileDeepLinking}`);
6468
7179
  if (config.addons && config.addons.length > 0) flags.push(`--addons ${config.addons.join(" ")}`);
6469
7180
  else flags.push("--addons none");
6470
7181
  if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
@@ -6475,6 +7186,21 @@ function getTypeScriptFlags(config) {
6475
7186
  appendCommonFlags(flags, config);
6476
7187
  return flags;
6477
7188
  }
7189
+ function getReactNativeFlags(config) {
7190
+ const flags = ["--ecosystem react-native"];
7191
+ if (config.frontend && config.frontend.length > 0) flags.push(`--frontend ${config.frontend.join(" ")}`);
7192
+ else flags.push("--frontend native-bare");
7193
+ flags.push(`--auth ${config.auth}`);
7194
+ flags.push(`--mobile-navigation ${config.mobileNavigation}`);
7195
+ flags.push(`--mobile-ui ${config.mobileUI}`);
7196
+ flags.push(`--mobile-storage ${config.mobileStorage}`);
7197
+ flags.push(`--mobile-testing ${config.mobileTesting}`);
7198
+ flags.push(`--mobile-push ${config.mobilePush}`);
7199
+ flags.push(`--mobile-ota ${config.mobileOTA}`);
7200
+ flags.push(`--mobile-deep-linking ${config.mobileDeepLinking}`);
7201
+ appendCommonFlags(flags, config);
7202
+ return flags;
7203
+ }
6478
7204
  function getRustFlags(config) {
6479
7205
  const flags = ["--ecosystem rust"];
6480
7206
  flags.push(`--rust-web-framework ${config.rustWebFramework}`);
@@ -6531,9 +7257,32 @@ function getJavaFlags(config) {
6531
7257
  appendCommonFlags(flags, config);
6532
7258
  return flags;
6533
7259
  }
7260
+ function getElixirFlags(config) {
7261
+ const flags = ["--ecosystem elixir"];
7262
+ flags.push(`--elixir-web-framework ${config.elixirWebFramework}`);
7263
+ flags.push(`--elixir-orm ${config.elixirOrm}`);
7264
+ flags.push(`--elixir-auth ${config.elixirAuth}`);
7265
+ flags.push(`--elixir-api ${config.elixirApi}`);
7266
+ flags.push(`--elixir-realtime ${config.elixirRealtime}`);
7267
+ flags.push(`--elixir-jobs ${config.elixirJobs}`);
7268
+ flags.push(`--elixir-validation ${config.elixirValidation}`);
7269
+ flags.push(`--elixir-http ${config.elixirHttp}`);
7270
+ flags.push(`--elixir-json ${config.elixirJson}`);
7271
+ flags.push(`--elixir-email ${config.elixirEmail}`);
7272
+ flags.push(`--elixir-caching ${config.elixirCaching}`);
7273
+ flags.push(`--elixir-observability ${config.elixirObservability}`);
7274
+ flags.push(`--elixir-testing ${config.elixirTesting}`);
7275
+ flags.push(`--elixir-quality ${config.elixirQuality}`);
7276
+ flags.push(`--elixir-deploy ${config.elixirDeploy}`);
7277
+ appendCommonFlags(flags, config);
7278
+ return flags;
7279
+ }
6534
7280
  function generateReproducibleCommand(config) {
6535
7281
  let flags;
6536
7282
  switch (config.ecosystem) {
7283
+ case "react-native":
7284
+ flags = getReactNativeFlags(config);
7285
+ break;
6537
7286
  case "rust":
6538
7287
  flags = getRustFlags(config);
6539
7288
  break;
@@ -6546,6 +7295,9 @@ function generateReproducibleCommand(config) {
6546
7295
  case "java":
6547
7296
  flags = getJavaFlags(config);
6548
7297
  break;
7298
+ case "elixir":
7299
+ flags = getElixirFlags(config);
7300
+ break;
6549
7301
  case "typescript":
6550
7302
  default:
6551
7303
  flags = getTypeScriptFlags(config);
@@ -6768,7 +7520,7 @@ const PEER_DEPENDENCY_CONFLICTS = [
6768
7520
  }],
6769
7521
  conflictsWithOptions: [{
6770
7522
  optionKey: "frontend",
6771
- values: ["next"]
7523
+ values: ["next", "vinext"]
6772
7524
  }]
6773
7525
  },
6774
7526
  {
@@ -6832,7 +7584,7 @@ const PEER_DEPENDENCY_CONFLICTS = [
6832
7584
  }],
6833
7585
  conflictsWithOptions: [{
6834
7586
  optionKey: "frontend",
6835
- values: ["next"]
7587
+ values: ["next", "vinext"]
6836
7588
  }]
6837
7589
  }
6838
7590
  ];
@@ -7173,6 +7925,18 @@ function validateBackendConstraints(config, providedFlags, options) {
7173
7925
  function validateFrontendConstraints(config, providedFlags) {
7174
7926
  const { frontend } = config;
7175
7927
  if (frontend && frontend.length > 0) {
7928
+ if (config.ecosystem === "react-native" && frontend.some((item) => !item.startsWith("native-") && item !== "none")) incompatibilityError({
7929
+ message: "React Native ecosystem only supports native Expo frontends.",
7930
+ provided: {
7931
+ ecosystem: "react-native",
7932
+ frontend: frontend.join(" ")
7933
+ },
7934
+ suggestions: [
7935
+ "Use --frontend native-bare",
7936
+ "Use --frontend native-uniwind",
7937
+ "Use --frontend native-unistyles"
7938
+ ]
7939
+ });
7176
7940
  ensureSingleWebAndNative(frontend);
7177
7941
  if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) validateApiFrontendCompatibility(config.api, frontend, config.astroIntegration);
7178
7942
  }
@@ -7216,6 +7980,142 @@ function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set
7216
7980
  suggestions: ["Use --java-build-tool maven or --java-build-tool gradle to enable JUnit/Mockito/Testcontainers", "Set --java-testing-libraries none for a source-only plain Java scaffold"]
7217
7981
  });
7218
7982
  }
7983
+ function validateElixirConstraints(config) {
7984
+ if (config.ecosystem !== "elixir") return;
7985
+ const hasPhoenix = config.elixirWebFramework !== "none";
7986
+ const hasEcto = config.elixirOrm !== "none";
7987
+ const unsupportedSelections = [
7988
+ {
7989
+ flag: "elixir-orm",
7990
+ value: config.elixirOrm,
7991
+ unsupported: ["ecto"],
7992
+ message: "Plain Ecto without SQL Repo wiring is not generated yet.",
7993
+ suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-orm none"]
7994
+ },
7995
+ {
7996
+ flag: "elixir-auth",
7997
+ value: config.elixirAuth,
7998
+ unsupported: ["ueberauth", "guardian"],
7999
+ message: "Only phx.gen.auth currently generates Phoenix auth files.",
8000
+ suggestions: ["Use --elixir-auth phx-gen-auth", "Use --elixir-auth none"]
8001
+ },
8002
+ {
8003
+ flag: "elixir-validation",
8004
+ value: config.elixirValidation,
8005
+ unsupported: ["nimble-options"],
8006
+ message: "NimbleOptions is not generated yet.",
8007
+ suggestions: ["Use --elixir-validation ecto-changesets", "Use --elixir-validation none"]
8008
+ },
8009
+ {
8010
+ flag: "elixir-caching",
8011
+ value: config.elixirCaching,
8012
+ unsupported: ["nebulex"],
8013
+ message: "Nebulex cache modules are not generated yet.",
8014
+ suggestions: ["Use --elixir-caching cachex", "Use --elixir-caching none"]
8015
+ },
8016
+ {
8017
+ flag: "elixir-observability",
8018
+ value: config.elixirObservability,
8019
+ unsupported: ["opentelemetry", "prom_ex"],
8020
+ message: "OpenTelemetry and PromEx setup are not generated yet.",
8021
+ suggestions: ["Use --elixir-observability telemetry", "Use --elixir-observability none"]
8022
+ },
8023
+ {
8024
+ flag: "elixir-testing",
8025
+ value: config.elixirTesting,
8026
+ unsupported: [
8027
+ "mox",
8028
+ "bypass",
8029
+ "wallaby"
8030
+ ],
8031
+ message: "Generated Phoenix projects currently include ExUnit tests only.",
8032
+ suggestions: ["Use --elixir-testing ex_unit"]
8033
+ },
8034
+ {
8035
+ flag: "elixir-deploy",
8036
+ value: config.elixirDeploy,
8037
+ unsupported: ["fly", "gigalixir"],
8038
+ message: "Fly.io and Gigalixir config files are not generated yet.",
8039
+ suggestions: ["Use --elixir-deploy docker", "Use --elixir-deploy mix-release"]
8040
+ }
8041
+ ];
8042
+ for (const selection of unsupportedSelections) if (selection.value && selection.unsupported.includes(selection.value)) incompatibilityError({
8043
+ message: selection.message,
8044
+ provided: { [selection.flag]: selection.value },
8045
+ suggestions: selection.suggestions
8046
+ });
8047
+ if (!hasPhoenix) {
8048
+ const phoenixOnlySelections = [
8049
+ {
8050
+ flag: "elixir-auth",
8051
+ value: config.elixirAuth,
8052
+ message: "Elixir auth scaffolds require Phoenix."
8053
+ },
8054
+ {
8055
+ flag: "elixir-api",
8056
+ value: config.elixirApi,
8057
+ message: "Elixir API scaffolds require Phoenix."
8058
+ },
8059
+ {
8060
+ flag: "elixir-realtime",
8061
+ value: config.elixirRealtime,
8062
+ message: "Elixir realtime scaffolds require Phoenix."
8063
+ }
8064
+ ];
8065
+ for (const selection of phoenixOnlySelections) {
8066
+ if (!selection.value || selection.value === "none") continue;
8067
+ incompatibilityError({
8068
+ message: selection.message,
8069
+ provided: {
8070
+ "elixir-web-framework": config.elixirWebFramework ?? "none",
8071
+ [selection.flag]: selection.value
8072
+ },
8073
+ suggestions: [
8074
+ "Use --elixir-web-framework phoenix",
8075
+ "Use --elixir-web-framework phoenix-live-view",
8076
+ `Use --${selection.flag} none`
8077
+ ]
8078
+ });
8079
+ }
8080
+ }
8081
+ if (hasPhoenix && config.elixirJson === "none") incompatibilityError({
8082
+ message: "Phoenix JSON scaffolds require Jason.",
8083
+ provided: { "elixir-json": "none" },
8084
+ suggestions: ["Use --elixir-json jason"]
8085
+ });
8086
+ if (config.elixirAuth === "phx-gen-auth" && !hasEcto) incompatibilityError({
8087
+ message: "phx.gen.auth requires Ecto in the generated Phoenix scaffold.",
8088
+ provided: {
8089
+ "elixir-auth": "phx-gen-auth",
8090
+ "elixir-orm": config.elixirOrm ?? "none"
8091
+ },
8092
+ suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-auth none"]
8093
+ });
8094
+ if (config.elixirJobs === "oban" && config.elixirOrm !== "ecto-sql") incompatibilityError({
8095
+ message: "Oban requires Ecto SQL with PostgreSQL in the generated Phoenix scaffold.",
8096
+ provided: {
8097
+ "elixir-jobs": "oban",
8098
+ "elixir-orm": config.elixirOrm ?? "none"
8099
+ },
8100
+ suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-jobs none"]
8101
+ });
8102
+ if (config.elixirRealtime === "live-view-streams" && config.elixirWebFramework !== "phoenix-live-view") incompatibilityError({
8103
+ message: "LiveView Streams require Phoenix LiveView.",
8104
+ provided: {
8105
+ "elixir-realtime": "live-view-streams",
8106
+ "elixir-web-framework": config.elixirWebFramework ?? "none"
8107
+ },
8108
+ suggestions: ["Use --elixir-web-framework phoenix-live-view", "Use --elixir-realtime channels"]
8109
+ });
8110
+ if (config.elixirApi === "absinthe" && !hasEcto) incompatibilityError({
8111
+ message: "Absinthe GraphQL requires Ecto in the current generated Phoenix scaffold.",
8112
+ provided: {
8113
+ "elixir-api": "absinthe",
8114
+ "elixir-orm": config.elixirOrm ?? "none"
8115
+ },
8116
+ suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-api rest"]
8117
+ });
8118
+ }
7219
8119
  function validateEmailConstraints(config) {
7220
8120
  if (!config.email || config.email === "none") return;
7221
8121
  if (config.ecosystem !== "typescript" && config.email !== "resend") incompatibilityError({
@@ -7340,6 +8240,7 @@ function validateFullConfig(config, providedFlags, options) {
7340
8240
  validateCachingConstraints(config);
7341
8241
  validateSearchConstraints(config);
7342
8242
  validateJavaConstraints(config, providedFlags);
8243
+ validateElixirConstraints(config);
7343
8244
  validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
7344
8245
  validateSelfBackendCompatibility(providedFlags, options, config);
7345
8246
  validateWorkersCompatibility(providedFlags, options, config);
@@ -7381,6 +8282,7 @@ function validateConfigForProgrammaticUse(config) {
7381
8282
  validateCachingConstraints(config);
7382
8283
  validateSearchConstraints(config);
7383
8284
  validateJavaConstraints(config);
8285
+ validateElixirConstraints(config);
7384
8286
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
7385
8287
  if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime, config.ecosystem, config.rustFrontend, config.javaWebFramework, config.database);
7386
8288
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
@@ -8753,6 +9655,10 @@ async function displayPostInstallInstructions(config) {
8753
9655
  displayPythonInstructions(config);
8754
9656
  return;
8755
9657
  }
9658
+ if (ecosystem === "elixir") {
9659
+ displayElixirInstructions(config);
9660
+ return;
9661
+ }
8756
9662
  const isConvex = backend === "convex";
8757
9663
  const isBackendSelf = backend === "self";
8758
9664
  const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : packageManager === "yarn" ? "yarn" : "bun run";
@@ -8916,7 +9822,7 @@ function getBunWebNativeWarning() {
8916
9822
  }
8917
9823
  function getClerkInstructions(backend, frontend) {
8918
9824
  if (backend === "convex") return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
8919
- if (backend === "self" && (frontend.includes("next") || frontend.includes("tanstack-start"))) {
9825
+ if (backend === "self" && (frontend.includes("next") || frontend.includes("vinext") || frontend.includes("tanstack-start"))) {
8920
9826
  const publishableKeyVar = frontend.includes("next") ? "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY" : "VITE_CLERK_PUBLISHABLE_KEY";
8921
9827
  return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Create an application in ${pc.underline("https://dashboard.clerk.com/")}\n${pc.cyan("•")} Set ${publishableKeyVar} in ${pc.white("apps/web/.env")}\n${pc.cyan("•")} Set CLERK_SECRET_KEY in ${pc.white("apps/web/.env")}\n${pc.cyan("•")} Clerk middleware and a protected dashboard route are already generated`;
8922
9828
  }
@@ -9303,6 +10209,36 @@ function displayPythonInstructions(config) {
9303
10209
  output += pc.dim("Your star helps other developers discover the project.");
9304
10210
  consola$1.box(output);
9305
10211
  }
10212
+ function displayElixirInstructions(config) {
10213
+ const { relativePath, depsInstalled, elixirWebFramework, elixirOrm, elixirApi, elixirRealtime, elixirJobs, elixirAuth, elixirDeploy } = config;
10214
+ let output = `${pc.bold("Project created successfully!")}\n\n`;
10215
+ output += `${pc.bold("Next steps:")}\n`;
10216
+ output += `${pc.cyan("1.")} cd ${relativePath}\n`;
10217
+ if (!depsInstalled) {
10218
+ output += `${pc.cyan("2.")} mix deps.get\n`;
10219
+ output += `${pc.cyan("3.")} mix ecto.setup\n`;
10220
+ output += `${pc.cyan("4.")} mix phx.server\n`;
10221
+ } else {
10222
+ output += `${pc.cyan("2.")} mix ecto.setup\n`;
10223
+ output += `${pc.cyan("3.")} mix phx.server\n`;
10224
+ }
10225
+ output += `\n${pc.bold("Selected Elixir stack:")}\n`;
10226
+ output += `${pc.cyan("•")} Web: ${elixirWebFramework}\n`;
10227
+ output += `${pc.cyan("•")} Database: ${elixirOrm}\n`;
10228
+ output += `${pc.cyan("•")} API: ${elixirApi}\n`;
10229
+ output += `${pc.cyan("•")} Realtime: ${elixirRealtime}\n`;
10230
+ output += `${pc.cyan("•")} Jobs: ${elixirJobs}\n`;
10231
+ output += `${pc.cyan("•")} Auth: ${elixirAuth}\n`;
10232
+ output += `${pc.cyan("•")} Deploy: ${elixirDeploy}\n`;
10233
+ output += `\n${pc.bold("Common Mix commands:")}\n`;
10234
+ output += `${pc.cyan("•")} Run: mix phx.server\n`;
10235
+ output += `${pc.cyan("•")} Test: mix test\n`;
10236
+ output += `${pc.cyan("•")} Format: mix format\n`;
10237
+ output += `${pc.cyan("•")} Compile: mix compile\n`;
10238
+ output += `\n${pc.bold("Your Phoenix app will be available at:")}\n`;
10239
+ output += `${pc.cyan("•")} Web: http://localhost:4000\n`;
10240
+ consola$1.box(output);
10241
+ }
9306
10242
 
9307
10243
  //#endregion
9308
10244
  //#region src/helpers/core/create-project.ts
@@ -9325,7 +10261,7 @@ async function createProject(options, cliInput = {}) {
9325
10261
  await writeBtsConfig(options);
9326
10262
  await formatProject(projectDir);
9327
10263
  if (!isSilent()) log.success("Project template successfully scaffolded!");
9328
- if (options.install && options.ecosystem === "typescript") await installDependencies({
10264
+ if (options.install && (options.ecosystem === "typescript" || options.ecosystem === "react-native")) await installDependencies({
9329
10265
  projectDir,
9330
10266
  packageManager: options.packageManager
9331
10267
  });
@@ -9334,6 +10270,7 @@ async function createProject(options, cliInput = {}) {
9334
10270
  if (options.install && options.ecosystem === "go") await runGoModTidy({ projectDir });
9335
10271
  if (options.install && options.ecosystem === "java" && options.javaBuildTool !== "none") if (options.javaBuildTool === "gradle") await runGradleTests({ projectDir });
9336
10272
  else await runMavenTests({ projectDir });
10273
+ if (options.install && options.ecosystem === "elixir") await runMixCompile({ projectDir });
9337
10274
  await initializeGit(projectDir, options.git);
9338
10275
  if (!isSilent()) await displayPostInstallInstructions({
9339
10276
  ...options,
@@ -9374,6 +10311,51 @@ async function ensurePackageManagerProjectFiles(projectDir, packageManager) {
9374
10311
 
9375
10312
  //#endregion
9376
10313
  //#region src/helpers/core/command-handlers.ts
10314
+ function getYesBaseConfig(flagConfig) {
10315
+ const baseConfig = getDefaultConfig();
10316
+ if (flagConfig.ecosystem !== "react-native") return baseConfig;
10317
+ return {
10318
+ ...baseConfig,
10319
+ backend: "none",
10320
+ runtime: "none",
10321
+ frontend: ["native-bare"],
10322
+ addons: [],
10323
+ examples: [],
10324
+ auth: "none",
10325
+ payments: "none",
10326
+ email: "none",
10327
+ fileUpload: "none",
10328
+ effect: "none",
10329
+ dbSetup: "none",
10330
+ api: "none",
10331
+ webDeploy: "none",
10332
+ serverDeploy: "none",
10333
+ cssFramework: "none",
10334
+ uiLibrary: "none",
10335
+ stateManagement: "none",
10336
+ forms: "none",
10337
+ testing: "none",
10338
+ realtime: "none",
10339
+ jobQueue: "none",
10340
+ animation: "none",
10341
+ logging: "none",
10342
+ observability: "none",
10343
+ featureFlags: "none",
10344
+ analytics: "none",
10345
+ cms: "none",
10346
+ caching: "none",
10347
+ i18n: "none",
10348
+ search: "none",
10349
+ fileStorage: "none",
10350
+ mobileNavigation: "expo-router",
10351
+ mobileUI: "none",
10352
+ mobileStorage: "none",
10353
+ mobileTesting: "none",
10354
+ mobilePush: "none",
10355
+ mobileOTA: "none",
10356
+ mobileDeepLinking: "none"
10357
+ };
10358
+ }
9377
10359
  function shouldPromptForVersionChannel(input) {
9378
10360
  if (input.yes || input.versionChannel !== void 0 || isSilent()) return false;
9379
10361
  return canPromptInteractively();
@@ -9476,6 +10458,13 @@ async function createProjectHandler(input, options = {}) {
9476
10458
  featureFlags: "none",
9477
10459
  analytics: "none",
9478
10460
  fileStorage: "none",
10461
+ mobileNavigation: "none",
10462
+ mobileUI: "none",
10463
+ mobileStorage: "none",
10464
+ mobileTesting: "none",
10465
+ mobilePush: "none",
10466
+ mobileOTA: "none",
10467
+ mobileDeepLinking: "none",
9479
10468
  pythonWebFramework: "none",
9480
10469
  pythonOrm: "none",
9481
10470
  pythonValidation: "none",
@@ -9497,6 +10486,21 @@ async function createProjectHandler(input, options = {}) {
9497
10486
  javaAuth: "none",
9498
10487
  javaLibraries: [],
9499
10488
  javaTestingLibraries: [],
10489
+ elixirWebFramework: "none",
10490
+ elixirOrm: "none",
10491
+ elixirAuth: "none",
10492
+ elixirApi: "none",
10493
+ elixirRealtime: "none",
10494
+ elixirJobs: "none",
10495
+ elixirValidation: "none",
10496
+ elixirHttp: "none",
10497
+ elixirJson: "none",
10498
+ elixirEmail: "none",
10499
+ elixirCaching: "none",
10500
+ elixirObservability: "none",
10501
+ elixirTesting: "none",
10502
+ elixirQuality: "none",
10503
+ elixirDeploy: "none",
9500
10504
  aiDocs: []
9501
10505
  },
9502
10506
  reproducibleCommand: "",
@@ -9541,7 +10545,7 @@ async function createProjectHandler(input, options = {}) {
9541
10545
  if (cliInput.yes) {
9542
10546
  const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
9543
10547
  config = {
9544
- ...getDefaultConfig(),
10548
+ ...getYesBaseConfig(flagConfig),
9545
10549
  ...flagConfig,
9546
10550
  projectName: finalBaseName,
9547
10551
  projectDir: finalResolvedPath,
@@ -9938,17 +10942,21 @@ async function history(options) {
9938
10942
  */
9939
10943
  async function createVirtual(options) {
9940
10944
  try {
10945
+ const ecosystem = options.ecosystem || "typescript";
10946
+ const isReactNative = ecosystem === "react-native";
10947
+ const frontend = options.frontend || (isReactNative ? ["native-bare"] : ["tanstack-router"]);
10948
+ const hasNativeFrontend = frontend.some((item) => item === "native-bare" || item === "native-uniwind" || item === "native-unistyles");
9941
10949
  const result = await generateVirtualProject$1({
9942
10950
  config: {
10951
+ ecosystem,
9943
10952
  projectName: options.projectName || "my-project",
9944
10953
  projectDir: "/virtual",
9945
10954
  relativePath: "./virtual",
9946
- ecosystem: options.ecosystem || "typescript",
9947
10955
  database: options.database || "none",
9948
10956
  orm: options.orm || "none",
9949
- backend: options.backend || "hono",
9950
- runtime: options.runtime || "bun",
9951
- frontend: options.frontend || ["tanstack-router"],
10957
+ backend: options.backend || (isReactNative ? "none" : "hono"),
10958
+ runtime: options.runtime || (isReactNative ? "none" : "bun"),
10959
+ frontend,
9952
10960
  addons: options.addons || [],
9953
10961
  examples: options.examples || [],
9954
10962
  auth: options.auth || "none",
@@ -9961,11 +10969,11 @@ async function createVirtual(options) {
9961
10969
  versionChannel: options.versionChannel || "stable",
9962
10970
  install: false,
9963
10971
  dbSetup: options.dbSetup || "none",
9964
- api: options.api || "trpc",
10972
+ api: options.api || (isReactNative ? "none" : "trpc"),
9965
10973
  webDeploy: options.webDeploy || "none",
9966
10974
  serverDeploy: options.serverDeploy || "none",
9967
- cssFramework: options.cssFramework || "tailwind",
9968
- uiLibrary: options.uiLibrary || "shadcn-ui",
10975
+ cssFramework: options.cssFramework || (isReactNative ? "none" : "tailwind"),
10976
+ uiLibrary: options.uiLibrary || (isReactNative ? "none" : "shadcn-ui"),
9969
10977
  shadcnBase: options.shadcnBase ?? "radix",
9970
10978
  shadcnStyle: options.shadcnStyle ?? "nova",
9971
10979
  shadcnIconLibrary: options.shadcnIconLibrary ?? "lucide",
@@ -9975,8 +10983,8 @@ async function createVirtual(options) {
9975
10983
  shadcnRadius: options.shadcnRadius ?? "default",
9976
10984
  ai: options.ai || "none",
9977
10985
  stateManagement: options.stateManagement || "none",
9978
- forms: options.forms || "react-hook-form",
9979
- testing: options.testing || "vitest",
10986
+ forms: options.forms || (isReactNative ? "none" : "react-hook-form"),
10987
+ testing: options.testing || (isReactNative ? "none" : "vitest"),
9980
10988
  validation: options.validation || "zod",
9981
10989
  realtime: options.realtime || "none",
9982
10990
  jobQueue: options.jobQueue || "none",
@@ -9985,6 +10993,13 @@ async function createVirtual(options) {
9985
10993
  observability: options.observability || "none",
9986
10994
  featureFlags: options.featureFlags || "none",
9987
10995
  analytics: options.analytics || "none",
10996
+ mobileNavigation: options.mobileNavigation || (hasNativeFrontend ? "expo-router" : "none"),
10997
+ mobileUI: options.mobileUI || "none",
10998
+ mobileStorage: options.mobileStorage || "none",
10999
+ mobileTesting: options.mobileTesting || "none",
11000
+ mobilePush: options.mobilePush || "none",
11001
+ mobileOTA: options.mobileOTA || "none",
11002
+ mobileDeepLinking: options.mobileDeepLinking || (hasNativeFrontend ? "expo-linking" : "none"),
9988
11003
  cms: options.cms || "none",
9989
11004
  caching: options.caching || "none",
9990
11005
  i18n: options.i18n || "none",
@@ -10021,6 +11036,21 @@ async function createVirtual(options) {
10021
11036
  javaAuth: options.javaAuth || "none",
10022
11037
  javaLibraries: options.javaLibraries || [],
10023
11038
  javaTestingLibraries: options.javaTestingLibraries || (options.ecosystem === "java" ? ["junit5"] : []),
11039
+ elixirWebFramework: options.elixirWebFramework || (options.ecosystem === "elixir" ? "phoenix" : "none"),
11040
+ elixirOrm: options.elixirOrm || (options.ecosystem === "elixir" ? "ecto-sql" : "none"),
11041
+ elixirAuth: options.elixirAuth || "none",
11042
+ elixirApi: options.elixirApi || (options.ecosystem === "elixir" ? "rest" : "none"),
11043
+ elixirRealtime: options.elixirRealtime || (options.ecosystem === "elixir" ? "channels" : "none"),
11044
+ elixirJobs: options.elixirJobs || "none",
11045
+ elixirValidation: options.elixirValidation || (options.ecosystem === "elixir" ? "ecto-changesets" : "none"),
11046
+ elixirHttp: options.elixirHttp || (options.ecosystem === "elixir" ? "req" : "none"),
11047
+ elixirJson: options.elixirJson || (options.ecosystem === "elixir" ? "jason" : "none"),
11048
+ elixirEmail: options.elixirEmail || "none",
11049
+ elixirCaching: options.elixirCaching || "none",
11050
+ elixirObservability: options.elixirObservability || (options.ecosystem === "elixir" ? "telemetry" : "none"),
11051
+ elixirTesting: options.elixirTesting || (options.ecosystem === "elixir" ? "ex_unit" : "none"),
11052
+ elixirQuality: options.elixirQuality || (options.ecosystem === "elixir" ? "credo" : "none"),
11053
+ elixirDeploy: options.elixirDeploy || "none",
10024
11054
  aiDocs: options.aiDocs || ["claude-md"]
10025
11055
  },
10026
11056
  templates: EMBEDDED_TEMPLATES$1