create-better-fullstack 1.8.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
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-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";
2
+ import { a as getGraphBackendUrl, c as getPrimaryGraphPart, d as getLatestCLIVersion, f as DEFAULT_CONFIG, g as getUserPkgManager, h as getDefaultConfig, i as getGraphBackendDeployInstructions, l as hasGraphPart, n as updateBtsConfig, o as getGraphPart, p as DEFAULT_UI_LIBRARY_BY_FRONTEND, r as writeBtsConfig, s as getGraphSummary, t as readBtsConfig, u as types_exports } from "./bts-config-BMniWIbd.mjs";
3
+ 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-DHoByttt.mjs";
5
4
  import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
6
5
  import { createRouterClient, os } from "@orpc/server";
7
6
  import pc from "picocolors";
@@ -10,7 +9,7 @@ import z from "zod";
10
9
  import envPaths from "env-paths";
11
10
  import fs from "fs-extra";
12
11
  import path from "node:path";
13
- import { allowedApisForFrontends, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasDockerComposeCompatibleFrontend, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
12
+ import { allowedApisForFrontends, formatStackPartSpec, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasDockerComposeCompatibleFrontend, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
14
13
  import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, checkAllVersions, generateCliReport, generateVirtualProject, generateVirtualProject as generateVirtualProject$1, listEcosystems, processAddonTemplates, processAddonsDeps, validatePreflightConfig } from "@better-fullstack/template-generator";
15
14
  import gradient from "gradient-string";
16
15
  import path$1 from "path";
@@ -349,20 +348,16 @@ function showEcosystems() {
349
348
  console.log(`\nUsage: update-deps --ecosystem <name>`);
350
349
  }
351
350
 
352
- //#endregion
353
- //#region src/types.ts
354
- var types_exports = {};
355
- import * as import__better_fullstack_types from "@better-fullstack/types";
356
- __reExport(types_exports, import__better_fullstack_types);
357
-
358
351
  //#endregion
359
352
  //#region src/create-command-input.ts
360
353
  const CreateCommandOptionsSchema = z.object({
361
354
  template: types_exports.TemplateSchema.optional().describe("Use a predefined template"),
362
355
  yes: z.boolean().optional().default(false).describe("Use default configuration"),
363
356
  yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
357
+ part: z.array(z.string()).optional().describe("Stack graph part binding, e.g. frontend:typescript:next or backend.orm:go:gorm"),
364
358
  verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
365
359
  dryRun: z.boolean().optional().default(false).describe("Preview generated file tree without writing to disk"),
360
+ verify: z.boolean().optional().default(false).describe("Run generated project checks after scaffolding without starting dev servers"),
366
361
  ecosystem: types_exports.EcosystemSchema.optional().describe("Language ecosystem (typescript, react-native, rust, python, go, java, or elixir)"),
367
362
  database: types_exports.DatabaseSchema.optional(),
368
363
  orm: types_exports.ORMSchema.optional(),
@@ -653,8 +648,8 @@ function requiresChatSdkVercelAI(backend, frontends = [], runtime) {
653
648
  function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
654
649
  if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
655
650
  }
656
- function validateServerDeployRequiresBackend(serverDeploy, backend) {
657
- if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
651
+ function validateServerDeployRequiresBackend(serverDeploy, backend, hasGraphBackend = false) {
652
+ if (serverDeploy && serverDeploy !== "none" && !hasGraphBackend && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
658
653
  }
659
654
  function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
660
655
  const baseCompatibility = validateAddonCompatibility(addon, frontend, _auth);
@@ -2935,42 +2930,48 @@ const getElixirDeployChoice = (value) => makeChoice("Select Elixir deploy target
2935
2930
 
2936
2931
  //#endregion
2937
2932
  //#region src/prompts/ecosystem.ts
2933
+ const ECOSYSTEM_PROMPT_OPTIONS = [
2934
+ {
2935
+ value: "typescript",
2936
+ label: "TypeScript",
2937
+ hint: "Full-stack TypeScript web with React, Vue, Svelte, and more"
2938
+ },
2939
+ {
2940
+ value: "react-native",
2941
+ label: "React Native",
2942
+ hint: "Expo and React Native mobile apps with native integrations"
2943
+ },
2944
+ {
2945
+ value: "rust",
2946
+ label: "Rust",
2947
+ hint: "Rust ecosystem with Axum, Leptos, and more"
2948
+ },
2949
+ {
2950
+ value: "python",
2951
+ label: "Python",
2952
+ hint: "Python ecosystem with FastAPI, Django, and AI/ML tools"
2953
+ },
2954
+ {
2955
+ value: "go",
2956
+ label: "Go",
2957
+ hint: "Go ecosystem with Gin, Echo, GORM, and more"
2958
+ },
2959
+ {
2960
+ value: "java",
2961
+ label: "Java",
2962
+ hint: "Java ecosystem with Spring Boot, Maven, Gradle, and more"
2963
+ },
2964
+ {
2965
+ value: "elixir",
2966
+ label: "Elixir",
2967
+ hint: "Elixir ecosystem with Phoenix, LiveView, Ecto, and more"
2968
+ }
2969
+ ];
2938
2970
  async function getEcosystemChoice(ecosystem) {
2939
2971
  if (ecosystem !== void 0) return ecosystem;
2940
2972
  const response = await navigableSelect({
2941
2973
  message: "Select ecosystem",
2942
- options: [
2943
- {
2944
- value: "typescript",
2945
- label: "TypeScript",
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"
2952
- },
2953
- {
2954
- value: "rust",
2955
- label: "Rust",
2956
- hint: "Rust ecosystem with Axum, Leptos, and more"
2957
- },
2958
- {
2959
- value: "python",
2960
- label: "Python",
2961
- hint: "Python ecosystem with FastAPI, Django, and AI/ML tools"
2962
- },
2963
- {
2964
- value: "go",
2965
- label: "Go",
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"
2972
- }
2973
- ],
2974
+ options: ECOSYSTEM_PROMPT_OPTIONS,
2974
2975
  initialValue: "typescript"
2975
2976
  });
2976
2977
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
@@ -4373,220 +4374,6 @@ function getMobileDeepLinkingChoice(mobileDeepLinking) {
4373
4374
  return promptMobileOption(MOBILE_DEEP_LINKING_OPTIONS, "expo-linking", mobileDeepLinking, "Select mobile deep linking");
4374
4375
  }
4375
4376
 
4376
- //#endregion
4377
- //#region src/prompts/navigable-group.ts
4378
- /**
4379
- * Navigable group - a group of prompts that allows going back
4380
- */
4381
- /**
4382
- * Define a group of prompts that supports going back to previous prompts.
4383
- * Returns a result object with all the values, or handles cancel/go-back navigation.
4384
- */
4385
- async function navigableGroup(prompts, opts) {
4386
- const results = {};
4387
- const promptNames = Object.keys(prompts);
4388
- let currentIndex = 0;
4389
- let goingBack = false;
4390
- while (currentIndex < promptNames.length) {
4391
- const name = promptNames[currentIndex];
4392
- const prompt = prompts[name];
4393
- setIsFirstPrompt$1(currentIndex === 0);
4394
- setLastPromptShownUI(false);
4395
- const result = await prompt({ results })?.catch((e) => {
4396
- throw e;
4397
- });
4398
- if (isGoBack(result)) {
4399
- goingBack = true;
4400
- if (currentIndex > 0) {
4401
- const prevName = promptNames[currentIndex - 1];
4402
- delete results[prevName];
4403
- currentIndex--;
4404
- continue;
4405
- }
4406
- goingBack = false;
4407
- continue;
4408
- }
4409
- if (isCancel$1(result)) {
4410
- if (typeof opts?.onCancel === "function") {
4411
- results[name] = "canceled";
4412
- opts.onCancel({ results });
4413
- }
4414
- setIsFirstPrompt$1(false);
4415
- return results;
4416
- }
4417
- if (goingBack && !didLastPromptShowUI()) {
4418
- if (currentIndex > 0) {
4419
- const prevName = promptNames[currentIndex - 1];
4420
- delete results[prevName];
4421
- currentIndex--;
4422
- continue;
4423
- }
4424
- }
4425
- goingBack = false;
4426
- results[name] = result;
4427
- currentIndex++;
4428
- }
4429
- setIsFirstPrompt$1(false);
4430
- return results;
4431
- }
4432
-
4433
- //#endregion
4434
- //#region src/prompts/observability.ts
4435
- const OBSERVABILITY_PROMPT_OPTIONS = [
4436
- {
4437
- value: "opentelemetry",
4438
- label: "OpenTelemetry",
4439
- hint: "Observability framework for traces, metrics, and logs"
4440
- },
4441
- {
4442
- value: "sentry",
4443
- label: "Sentry",
4444
- hint: "Error tracking and performance monitoring"
4445
- },
4446
- {
4447
- value: "grafana",
4448
- label: "Grafana",
4449
- hint: "Prometheus metrics for Grafana dashboards and alerting"
4450
- },
4451
- {
4452
- value: "none",
4453
- label: "None",
4454
- hint: "Skip observability/tracing setup"
4455
- }
4456
- ];
4457
- const NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS = OBSERVABILITY_PROMPT_OPTIONS.filter((option) => option.value === "sentry" || option.value === "none");
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
- };
4465
- const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS : OBSERVABILITY_PROMPT_OPTIONS;
4466
- if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
4467
- shouldPrompt: false,
4468
- mode: "single",
4469
- options: [],
4470
- autoValue: "none"
4471
- };
4472
- return context.observability !== void 0 ? {
4473
- shouldPrompt: false,
4474
- mode: "single",
4475
- options,
4476
- autoValue: context.observability
4477
- } : {
4478
- shouldPrompt: true,
4479
- mode: "single",
4480
- options,
4481
- initialValue: "none"
4482
- };
4483
- }
4484
- async function getObservabilityChoice(observability, backend, ecosystem) {
4485
- const resolution = resolveObservabilityPrompt({
4486
- observability,
4487
- backend,
4488
- ecosystem
4489
- });
4490
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
4491
- const response = await navigableSelect({
4492
- message: "Select observability solution",
4493
- options: resolution.options,
4494
- initialValue: resolution.initialValue
4495
- });
4496
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4497
- return response;
4498
- }
4499
-
4500
- //#endregion
4501
- //#region src/prompts/orm.ts
4502
- const ormOptions = {
4503
- prisma: {
4504
- value: "prisma",
4505
- label: "Prisma",
4506
- hint: "Powerful, feature-rich ORM"
4507
- },
4508
- mongoose: {
4509
- value: "mongoose",
4510
- label: "Mongoose",
4511
- hint: "Elegant object modeling tool"
4512
- },
4513
- drizzle: {
4514
- value: "drizzle",
4515
- label: "Drizzle",
4516
- hint: "Lightweight and performant TypeScript ORM"
4517
- },
4518
- typeorm: {
4519
- value: "typeorm",
4520
- label: "TypeORM",
4521
- hint: "Traditional ORM with Active Record/Data Mapper"
4522
- },
4523
- kysely: {
4524
- value: "kysely",
4525
- label: "Kysely",
4526
- hint: "Type-safe SQL query builder"
4527
- },
4528
- mikroorm: {
4529
- value: "mikroorm",
4530
- label: "MikroORM",
4531
- hint: "Data Mapper ORM for DDD"
4532
- },
4533
- sequelize: {
4534
- value: "sequelize",
4535
- label: "Sequelize",
4536
- hint: "Mature ORM with wide adoption"
4537
- }
4538
- };
4539
- function resolveORMPrompt(context) {
4540
- if (context.backend === "convex" || !context.hasDatabase) return {
4541
- shouldPrompt: false,
4542
- mode: "single",
4543
- options: [],
4544
- autoValue: "none"
4545
- };
4546
- if (context.database === "edgedb" || context.database === "redis") return {
4547
- shouldPrompt: false,
4548
- mode: "single",
4549
- options: [],
4550
- autoValue: "none"
4551
- };
4552
- if (context.orm !== void 0) return {
4553
- shouldPrompt: false,
4554
- mode: "single",
4555
- options: [],
4556
- autoValue: context.orm
4557
- };
4558
- return {
4559
- shouldPrompt: true,
4560
- mode: "single",
4561
- options: context.database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [
4562
- ormOptions.drizzle,
4563
- ormOptions.prisma,
4564
- ormOptions.typeorm,
4565
- ormOptions.kysely,
4566
- ormOptions.mikroorm,
4567
- ormOptions.sequelize
4568
- ],
4569
- initialValue: context.database === "mongodb" ? "prisma" : context.runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
4570
- };
4571
- }
4572
- async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
4573
- const resolution = resolveORMPrompt({
4574
- orm,
4575
- hasDatabase,
4576
- database,
4577
- backend,
4578
- runtime
4579
- });
4580
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
4581
- const response = await navigableSelect({
4582
- message: "Select ORM",
4583
- options: resolution.options,
4584
- initialValue: resolution.initialValue
4585
- });
4586
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4587
- return response;
4588
- }
4589
-
4590
4377
  //#endregion
4591
4378
  //#region src/prompts/package-manager.ts
4592
4379
  async function getPackageManagerChoice(packageManager) {
@@ -4621,73 +4408,6 @@ async function getPackageManagerChoice(packageManager) {
4621
4408
  return response;
4622
4409
  }
4623
4410
 
4624
- //#endregion
4625
- //#region src/prompts/payments.ts
4626
- function resolvePaymentsPrompt(context = {}) {
4627
- if (context.payments !== void 0) return {
4628
- shouldPrompt: false,
4629
- mode: "single",
4630
- options: [],
4631
- autoValue: context.payments
4632
- };
4633
- if (context.backend === "none") return {
4634
- shouldPrompt: false,
4635
- mode: "single",
4636
- options: [],
4637
- autoValue: "none"
4638
- };
4639
- const isPolarCompatible = context.auth === "better-auth" && (context.frontends?.length === 0 || splitFrontends$1(context.frontends).web.length > 0);
4640
- const options = [];
4641
- if (isPolarCompatible) options.push({
4642
- value: "polar",
4643
- label: "Polar",
4644
- hint: "Turn your software into a business. 6 lines of code."
4645
- });
4646
- options.push({
4647
- value: "stripe",
4648
- label: "Stripe",
4649
- hint: "Payment processing platform for internet businesses."
4650
- }, {
4651
- value: "lemon-squeezy",
4652
- label: "Lemon Squeezy",
4653
- hint: "All-in-one platform for SaaS, digital products, and subscriptions."
4654
- }, {
4655
- value: "paddle",
4656
- label: "Paddle",
4657
- hint: "Complete payments infrastructure for SaaS."
4658
- }, {
4659
- value: "dodo",
4660
- label: "Dodo Payments",
4661
- hint: "Simple payment infrastructure for developers."
4662
- }, {
4663
- value: "none",
4664
- label: "None",
4665
- hint: "No payments integration"
4666
- });
4667
- return {
4668
- shouldPrompt: true,
4669
- mode: "single",
4670
- options,
4671
- initialValue: DEFAULT_CONFIG.payments
4672
- };
4673
- }
4674
- async function getPaymentsChoice(payments, auth, backend, frontends) {
4675
- const resolution = resolvePaymentsPrompt({
4676
- payments,
4677
- auth,
4678
- backend,
4679
- frontends
4680
- });
4681
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
4682
- const response = await navigableSelect({
4683
- message: "Select payments provider",
4684
- options: resolution.options,
4685
- initialValue: resolution.initialValue
4686
- });
4687
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4688
- return response;
4689
- }
4690
-
4691
4411
  //#endregion
4692
4412
  //#region src/prompts/python-ecosystem.ts
4693
4413
  const PYTHON_WEB_FRAMEWORK_PROMPT_OPTIONS = [
@@ -5019,133 +4739,6 @@ async function getPythonQualityChoice(pythonQuality) {
5019
4739
  return response;
5020
4740
  }
5021
4741
 
5022
- //#endregion
5023
- //#region src/prompts/realtime.ts
5024
- const REALTIME_PROMPT_OPTIONS = [
5025
- {
5026
- value: "socket-io",
5027
- label: "Socket.IO",
5028
- hint: "Real-time bidirectional communication with fallbacks"
5029
- },
5030
- {
5031
- value: "partykit",
5032
- label: "PartyKit",
5033
- hint: "Edge-native multiplayer infrastructure on Cloudflare"
5034
- },
5035
- {
5036
- value: "ably",
5037
- label: "Ably",
5038
- hint: "Real-time messaging platform with pub/sub and presence"
5039
- },
5040
- {
5041
- value: "pusher",
5042
- label: "Pusher",
5043
- hint: "Real-time communication APIs with channels and events"
5044
- },
5045
- {
5046
- value: "liveblocks",
5047
- label: "Liveblocks",
5048
- hint: "Collaboration infrastructure for multiplayer experiences"
5049
- },
5050
- {
5051
- value: "yjs",
5052
- label: "Y.js",
5053
- hint: "CRDT library for real-time collaboration with conflict-free sync"
5054
- },
5055
- {
5056
- value: "none",
5057
- label: "None",
5058
- hint: "Skip real-time/WebSocket integration"
5059
- }
5060
- ];
5061
- function resolveRealtimePrompt(context = {}) {
5062
- if (context.backend === "none" || context.backend === "convex") return {
5063
- shouldPrompt: false,
5064
- mode: "single",
5065
- options: [],
5066
- autoValue: "none"
5067
- };
5068
- return context.realtime !== void 0 ? {
5069
- shouldPrompt: false,
5070
- mode: "single",
5071
- options: REALTIME_PROMPT_OPTIONS,
5072
- autoValue: context.realtime
5073
- } : {
5074
- shouldPrompt: true,
5075
- mode: "single",
5076
- options: REALTIME_PROMPT_OPTIONS,
5077
- initialValue: "none"
5078
- };
5079
- }
5080
- async function getRealtimeChoice(realtime, backend) {
5081
- const resolution = resolveRealtimePrompt({
5082
- realtime,
5083
- backend
5084
- });
5085
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
5086
- const response = await navigableSelect({
5087
- message: "Select real-time solution",
5088
- options: resolution.options,
5089
- initialValue: resolution.initialValue
5090
- });
5091
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
5092
- return response;
5093
- }
5094
-
5095
- //#endregion
5096
- //#region src/prompts/runtime.ts
5097
- const RUNTIME_PROMPT_OPTIONS = [
5098
- {
5099
- value: "bun",
5100
- label: "Bun",
5101
- hint: "Fast all-in-one JavaScript runtime"
5102
- },
5103
- {
5104
- value: "node",
5105
- label: "Node.js",
5106
- hint: "Traditional Node.js runtime"
5107
- },
5108
- {
5109
- value: "workers",
5110
- label: "Cloudflare Workers",
5111
- hint: "Edge runtime on Cloudflare's global network"
5112
- }
5113
- ];
5114
- function resolveRuntimePrompt(context = {}) {
5115
- if (context.backend === "convex" || context.backend === "none" || context.backend === "self") return {
5116
- shouldPrompt: false,
5117
- mode: "single",
5118
- options: [],
5119
- autoValue: "none"
5120
- };
5121
- const options = RUNTIME_PROMPT_OPTIONS.filter((option) => option.value !== "workers" || context.backend === "hono");
5122
- return context.runtime !== void 0 ? {
5123
- shouldPrompt: false,
5124
- mode: "single",
5125
- options,
5126
- autoValue: context.runtime
5127
- } : {
5128
- shouldPrompt: true,
5129
- mode: "single",
5130
- options,
5131
- initialValue: DEFAULT_CONFIG.runtime
5132
- };
5133
- }
5134
- async function getRuntimeChoice(runtime, backend) {
5135
- const resolution = resolveRuntimePrompt({
5136
- runtime,
5137
- backend
5138
- });
5139
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
5140
- const response = await navigableSelect({
5141
- message: "Select runtime",
5142
- options: resolution.options,
5143
- initialValue: resolution.initialValue
5144
- });
5145
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
5146
- return response;
5147
- }
5148
-
5149
4742
  //#endregion
5150
4743
  //#region src/prompts/rust-ecosystem.ts
5151
4744
  const RUST_WEB_FRAMEWORK_PROMPT_OPTIONS = [
@@ -5528,133 +5121,51 @@ async function getRustAuthChoice(rustAuth) {
5528
5121
  }
5529
5122
 
5530
5123
  //#endregion
5531
- //#region src/prompts/search.ts
5532
- const SEARCH_PROMPT_OPTIONS = [
5124
+ //#region src/prompts/shadcn-options.ts
5125
+ const BASE_OPTIONS = [{
5126
+ value: "radix",
5127
+ label: "Radix UI",
5128
+ hint: "Battle-tested headless primitives (130M+ monthly downloads)"
5129
+ }, {
5130
+ value: "base",
5131
+ label: "Base UI",
5132
+ hint: "MUI's headless library with cleaner APIs and native multi-select"
5133
+ }];
5134
+ const STYLE_OPTIONS = [
5533
5135
  {
5534
- value: "meilisearch",
5535
- label: "Meilisearch",
5536
- hint: "Lightning-fast search engine with typo tolerance"
5136
+ value: "vega",
5137
+ label: "Vega",
5138
+ hint: "Classic shadcn/ui look"
5537
5139
  },
5538
5140
  {
5539
- value: "typesense",
5540
- label: "Typesense",
5541
- hint: "Fast, typo-tolerant search with built-in vector search"
5141
+ value: "nova",
5142
+ label: "Nova",
5143
+ hint: "Compact layout with reduced padding"
5542
5144
  },
5543
5145
  {
5544
- value: "elasticsearch",
5545
- label: "Elasticsearch",
5546
- hint: "Distributed search and analytics engine with local and cloud deployments"
5146
+ value: "maia",
5147
+ label: "Maia",
5148
+ hint: "Soft, rounded with generous spacing"
5547
5149
  },
5548
5150
  {
5549
- value: "algolia",
5550
- label: "Algolia",
5551
- hint: "Hosted search API with instant results, typo tolerance, and analytics"
5151
+ value: "lyra",
5152
+ label: "Lyra",
5153
+ hint: "Boxy and sharp, pairs well with mono fonts"
5552
5154
  },
5553
5155
  {
5554
- value: "none",
5555
- label: "None",
5556
- hint: "Skip search engine setup"
5557
- }
5558
- ];
5559
- const NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS = SEARCH_PROMPT_OPTIONS.filter((option) => option.value === "meilisearch" || option.value === "none");
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
- };
5567
- const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS : SEARCH_PROMPT_OPTIONS;
5568
- if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
5569
- shouldPrompt: false,
5570
- mode: "single",
5571
- options: [],
5572
- autoValue: "none"
5573
- };
5574
- return context.search !== void 0 ? {
5575
- shouldPrompt: false,
5576
- mode: "single",
5577
- options,
5578
- autoValue: context.search
5579
- } : {
5580
- shouldPrompt: true,
5581
- mode: "single",
5582
- options,
5583
- initialValue: "none"
5584
- };
5585
- }
5586
- async function getSearchChoice(search, backend, ecosystem) {
5587
- const resolution = resolveSearchPrompt({
5588
- search,
5589
- backend,
5590
- ecosystem
5591
- });
5592
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
5593
- const response = await navigableSelect({
5594
- message: "Select search engine",
5595
- options: resolution.options,
5596
- initialValue: resolution.initialValue
5597
- });
5598
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
5599
- return response;
5600
- }
5601
-
5602
- //#endregion
5603
- //#region src/prompts/server-deploy.ts
5604
- async function getServerDeploymentChoice(deployment, runtime, backend, _webDeploy) {
5605
- if (deployment !== void 0) return deployment;
5606
- if (backend === "none" || backend === "convex") return "none";
5607
- if (backend !== "hono") return "none";
5608
- if (runtime === "workers") return "cloudflare";
5609
- return "none";
5610
- }
5611
-
5612
- //#endregion
5613
- //#region src/prompts/shadcn-options.ts
5614
- const BASE_OPTIONS = [{
5615
- value: "radix",
5616
- label: "Radix UI",
5617
- hint: "Battle-tested headless primitives (130M+ monthly downloads)"
5618
- }, {
5619
- value: "base",
5620
- label: "Base UI",
5621
- hint: "MUI's headless library with cleaner APIs and native multi-select"
5622
- }];
5623
- const STYLE_OPTIONS = [
5624
- {
5625
- value: "vega",
5626
- label: "Vega",
5627
- hint: "Classic shadcn/ui look"
5628
- },
5629
- {
5630
- value: "nova",
5631
- label: "Nova",
5632
- hint: "Compact layout with reduced padding"
5633
- },
5634
- {
5635
- value: "maia",
5636
- label: "Maia",
5637
- hint: "Soft, rounded with generous spacing"
5638
- },
5639
- {
5640
- value: "lyra",
5641
- label: "Lyra",
5642
- hint: "Boxy and sharp, pairs well with mono fonts"
5643
- },
5644
- {
5645
- value: "mira",
5646
- label: "Mira",
5647
- hint: "Dense, made for data-heavy interfaces"
5648
- },
5649
- {
5650
- value: "luma",
5651
- label: "Luma",
5652
- hint: "Modern shadcn/ui v4 preset"
5653
- },
5654
- {
5655
- value: "sera",
5656
- label: "Sera",
5657
- hint: "Modern shadcn/ui v4 preset"
5156
+ value: "mira",
5157
+ label: "Mira",
5158
+ hint: "Dense, made for data-heavy interfaces"
5159
+ },
5160
+ {
5161
+ value: "luma",
5162
+ label: "Luma",
5163
+ hint: "Modern shadcn/ui v4 preset"
5164
+ },
5165
+ {
5166
+ value: "sera",
5167
+ label: "Sera",
5168
+ hint: "Modern shadcn/ui v4 preset"
5658
5169
  }
5659
5170
  ];
5660
5171
  const ICON_LIBRARY_OPTIONS = [
@@ -6015,147 +5526,6 @@ async function promptShadcnRadius() {
6015
5526
  return selected;
6016
5527
  }
6017
5528
 
6018
- //#endregion
6019
- //#region src/prompts/state-management.ts
6020
- function resolveStateManagementPrompt(context = {}) {
6021
- if (context.stateManagement !== void 0) return {
6022
- shouldPrompt: false,
6023
- mode: "single",
6024
- options: [],
6025
- autoValue: context.stateManagement
6026
- };
6027
- const { web } = splitFrontends$1(context.frontends);
6028
- if (web.length === 0) return {
6029
- shouldPrompt: false,
6030
- mode: "single",
6031
- options: [],
6032
- autoValue: "none"
6033
- };
6034
- const isReact = web.some((f) => [
6035
- "tanstack-router",
6036
- "react-router",
6037
- "react-vite",
6038
- "tanstack-start",
6039
- "next",
6040
- "vinext",
6041
- "redwood"
6042
- ].includes(f));
6043
- const isFresh = web.includes("fresh");
6044
- const options = [];
6045
- if (isReact) options.push({
6046
- value: "zustand",
6047
- label: "Zustand",
6048
- hint: "Lightweight state management with simple API"
6049
- }, {
6050
- value: "jotai",
6051
- label: "Jotai",
6052
- hint: "Primitive and flexible atomic state"
6053
- }, {
6054
- value: "redux-toolkit",
6055
- label: "Redux Toolkit",
6056
- hint: "Enterprise-standard state with excellent TS support"
6057
- }, {
6058
- value: "valtio",
6059
- label: "Valtio",
6060
- hint: "Proxy-based state management"
6061
- }, {
6062
- value: "legend-state",
6063
- label: "Legend State",
6064
- hint: "High-performance observable state for React"
6065
- }, {
6066
- value: "mobx",
6067
- label: "MobX",
6068
- hint: "Observable-based reactive state management"
6069
- });
6070
- if (!isFresh) options.push({
6071
- value: "nanostores",
6072
- label: "Nanostores",
6073
- hint: "Tiny state manager (1KB) for all frameworks"
6074
- }, {
6075
- value: "xstate",
6076
- label: "XState",
6077
- hint: "State machines and statecharts for complex logic"
6078
- }, {
6079
- value: "tanstack-store",
6080
- label: "TanStack Store",
6081
- hint: "Framework-agnostic store powering TanStack ecosystem"
6082
- });
6083
- options.push({
6084
- value: "none",
6085
- label: "None",
6086
- hint: "Skip state management setup"
6087
- });
6088
- return {
6089
- shouldPrompt: true,
6090
- mode: "single",
6091
- options,
6092
- initialValue: "none"
6093
- };
6094
- }
6095
- async function getStateManagementChoice(stateManagement, frontends) {
6096
- const resolution = resolveStateManagementPrompt({
6097
- stateManagement,
6098
- frontends
6099
- });
6100
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6101
- const response = await navigableSelect({
6102
- message: "Select state management",
6103
- options: resolution.options,
6104
- initialValue: resolution.initialValue
6105
- });
6106
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6107
- return response;
6108
- }
6109
-
6110
- //#endregion
6111
- //#region src/prompts/testing.ts
6112
- const TESTING_PROMPT_OPTIONS = [
6113
- {
6114
- value: "vitest",
6115
- label: "Vitest",
6116
- hint: "Blazing fast Vite-native unit test framework"
6117
- },
6118
- {
6119
- value: "vitest-playwright",
6120
- label: "Vitest + Playwright",
6121
- hint: "Both unit and E2E testing for complete coverage"
6122
- },
6123
- {
6124
- value: "playwright",
6125
- label: "Playwright",
6126
- hint: "End-to-end testing framework by Microsoft"
6127
- },
6128
- {
6129
- value: "jest",
6130
- label: "Jest",
6131
- hint: "Classic testing framework with wide ecosystem"
6132
- },
6133
- {
6134
- value: "cypress",
6135
- label: "Cypress",
6136
- hint: "E2E testing with time travel debugging"
6137
- },
6138
- {
6139
- value: "none",
6140
- label: "None",
6141
- hint: "Skip testing framework setup"
6142
- }
6143
- ];
6144
- function resolveTestingPrompt(testing) {
6145
- return createStaticSinglePromptResolution(TESTING_PROMPT_OPTIONS, "vitest", testing);
6146
- }
6147
- async function getTestingChoice(testing) {
6148
- const resolution = resolveTestingPrompt(testing);
6149
- if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6150
- const response = await navigableSelect({
6151
- message: "Select testing framework",
6152
- options: resolution.options,
6153
- initialValue: resolution.initialValue
6154
- });
6155
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6156
- return response;
6157
- }
6158
-
6159
5529
  //#endregion
6160
5530
  //#region src/prompts/ui-library.ts
6161
5531
  const UI_LIBRARY_OPTIONS = {
@@ -6199,72 +5569,1096 @@ const UI_LIBRARY_OPTIONS = {
6199
5569
  label: "MUI",
6200
5570
  hint: "Popular React component library implementing Material Design"
6201
5571
  },
6202
- antd: {
6203
- label: "Ant Design",
6204
- hint: "Enterprise-class React UI component library"
5572
+ antd: {
5573
+ label: "Ant Design",
5574
+ hint: "Enterprise-class React UI component library"
5575
+ },
5576
+ "base-ui": {
5577
+ label: "Base UI",
5578
+ hint: "Unstyled, accessible components from MUI team (Radix successor)"
5579
+ },
5580
+ "ark-ui": {
5581
+ label: "Ark UI",
5582
+ hint: "Headless, accessible UI components for React, Vue, Solid, and Svelte"
5583
+ },
5584
+ "react-aria": {
5585
+ label: "React Aria",
5586
+ hint: "Adobe's accessible, unstyled UI components for React"
5587
+ },
5588
+ none: {
5589
+ label: "None",
5590
+ hint: "No UI component library"
5591
+ }
5592
+ };
5593
+ function resolveUILibraryPrompt(context = {}) {
5594
+ const { web } = splitFrontends$1(context.frontends);
5595
+ if (web.length === 0) return {
5596
+ shouldPrompt: false,
5597
+ mode: "single",
5598
+ options: [],
5599
+ autoValue: "none"
5600
+ };
5601
+ const compatibleLibraries = getCompatibleUILibraries$1(context.frontends, context.astroIntegration);
5602
+ if (context.uiLibrary !== void 0) return {
5603
+ shouldPrompt: false,
5604
+ mode: "single",
5605
+ options: compatibleLibraries.map((lib) => ({
5606
+ value: lib,
5607
+ label: UI_LIBRARY_OPTIONS[lib].label,
5608
+ hint: UI_LIBRARY_OPTIONS[lib].hint
5609
+ })),
5610
+ autoValue: compatibleLibraries.includes(context.uiLibrary) ? context.uiLibrary : compatibleLibraries[0]
5611
+ };
5612
+ const defaultLib = DEFAULT_UI_LIBRARY_BY_FRONTEND[web[0]];
5613
+ return {
5614
+ shouldPrompt: true,
5615
+ mode: "single",
5616
+ options: compatibleLibraries.map((lib) => ({
5617
+ value: lib,
5618
+ label: UI_LIBRARY_OPTIONS[lib].label,
5619
+ hint: UI_LIBRARY_OPTIONS[lib].hint
5620
+ })),
5621
+ initialValue: compatibleLibraries.includes(defaultLib) ? defaultLib : compatibleLibraries[0]
5622
+ };
5623
+ }
5624
+ async function getUILibraryChoice(uiLibrary, frontends, astroIntegration) {
5625
+ const resolution = resolveUILibraryPrompt({
5626
+ uiLibrary,
5627
+ frontends,
5628
+ astroIntegration
5629
+ });
5630
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
5631
+ const selected = await navigableSelect({
5632
+ message: "Select UI component library",
5633
+ options: resolution.options,
5634
+ initialValue: resolution.initialValue
5635
+ });
5636
+ if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5637
+ return selected;
5638
+ }
5639
+
5640
+ //#endregion
5641
+ //#region src/utils/compatibility.ts
5642
+ const WEB_FRAMEWORKS = [
5643
+ "tanstack-router",
5644
+ "react-router",
5645
+ "react-vite",
5646
+ "tanstack-start",
5647
+ "next",
5648
+ "vinext",
5649
+ "nuxt",
5650
+ "svelte",
5651
+ "solid",
5652
+ "solid-start",
5653
+ "astro",
5654
+ "qwik",
5655
+ "angular",
5656
+ "redwood",
5657
+ "fresh"
5658
+ ];
5659
+
5660
+ //#endregion
5661
+ //#region src/prompts/web-deploy.ts
5662
+ function hasWebFrontend(frontends) {
5663
+ return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
5664
+ }
5665
+ function getDeploymentDisplay(deployment) {
5666
+ if (deployment === "cloudflare") return {
5667
+ label: "Cloudflare",
5668
+ hint: "Deploy to Cloudflare Workers using Alchemy"
5669
+ };
5670
+ if (deployment === "vercel") return {
5671
+ label: "Vercel",
5672
+ hint: "Deploy to Vercel's edge network"
5673
+ };
5674
+ return {
5675
+ label: deployment,
5676
+ hint: `Add ${deployment} deployment`
5677
+ };
5678
+ }
5679
+ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
5680
+ if (deployment !== void 0) return deployment;
5681
+ if (!hasWebFrontend(frontend)) return "none";
5682
+ const response = await navigableSelect({
5683
+ message: "Select web deployment",
5684
+ options: [
5685
+ "cloudflare",
5686
+ "vercel",
5687
+ "none"
5688
+ ].map((deploy) => {
5689
+ const { label, hint } = getDeploymentDisplay(deploy);
5690
+ return {
5691
+ value: deploy,
5692
+ label,
5693
+ hint
5694
+ };
5695
+ }),
5696
+ initialValue: DEFAULT_CONFIG.webDeploy
5697
+ });
5698
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
5699
+ return response;
5700
+ }
5701
+
5702
+ //#endregion
5703
+ //#region src/prompts/multi-ecosystem-composer.ts
5704
+ async function getCompositionModeChoice() {
5705
+ const response = await navigableSelect({
5706
+ message: "Select project composition",
5707
+ options: [{
5708
+ value: "single",
5709
+ label: "Single ecosystem",
5710
+ hint: "Use the classic guided flow"
5711
+ }, {
5712
+ value: "multi",
5713
+ label: "Multi ecosystem",
5714
+ hint: "Compose a TypeScript frontend with another backend ecosystem"
5715
+ }],
5716
+ initialValue: "single"
5717
+ });
5718
+ if (isCancel$1(response) || isGoBack(response)) return exitCancelled("Operation cancelled");
5719
+ return response;
5720
+ }
5721
+ async function selectBackendEcosystem() {
5722
+ const response = await navigableSelect({
5723
+ message: "Select backend ecosystem",
5724
+ options: [
5725
+ {
5726
+ value: "go",
5727
+ label: "Go",
5728
+ hint: "Gin, Echo, Fiber, Chi"
5729
+ },
5730
+ {
5731
+ value: "rust",
5732
+ label: "Rust",
5733
+ hint: "Axum, Actix Web, Rocket"
5734
+ },
5735
+ {
5736
+ value: "python",
5737
+ label: "Python",
5738
+ hint: "FastAPI, Django, Flask"
5739
+ },
5740
+ {
5741
+ value: "java",
5742
+ label: "Java",
5743
+ hint: "Spring Boot, Quarkus"
5744
+ },
5745
+ {
5746
+ value: "elixir",
5747
+ label: "Elixir",
5748
+ hint: "Phoenix, LiveView"
5749
+ }
5750
+ ],
5751
+ initialValue: "go"
5752
+ });
5753
+ if (isCancel$1(response) || isGoBack(response)) return exitCancelled("Operation cancelled");
5754
+ return response;
5755
+ }
5756
+ async function selectServerDeployment(deployment) {
5757
+ if (deployment !== void 0) return deployment;
5758
+ const response = await navigableSelect({
5759
+ message: "Select server deployment",
5760
+ options: [
5761
+ {
5762
+ value: "none",
5763
+ label: "None",
5764
+ hint: "Skip server deployment setup"
5765
+ },
5766
+ {
5767
+ value: "railway",
5768
+ label: "Railway",
5769
+ hint: "Deploy a standalone backend service"
5770
+ },
5771
+ {
5772
+ value: "docker",
5773
+ label: "Docker",
5774
+ hint: "Containerize the backend service"
5775
+ },
5776
+ {
5777
+ value: "fly",
5778
+ label: "Fly",
5779
+ hint: "Deploy close to users"
5780
+ },
5781
+ {
5782
+ value: "vercel",
5783
+ label: "Vercel",
5784
+ hint: "Deploy from the backend workspace"
5785
+ }
5786
+ ],
5787
+ initialValue: "none"
5788
+ });
5789
+ if (isCancel$1(response) || isGoBack(response)) return exitCancelled("Operation cancelled");
5790
+ return response;
5791
+ }
5792
+ function promptValue(value) {
5793
+ if (isCancel$1(value) || isGoBack(value)) return exitCancelled("Operation cancelled");
5794
+ return value;
5795
+ }
5796
+ async function selectDatabaseConfig(flags) {
5797
+ const database = promptValue(await getDatabaseChoice(flags.database, "hono", "bun"));
5798
+ return {
5799
+ database,
5800
+ dbSetup: promptValue(await getDBSetupChoice(database, flags.dbSetup, "none", "none", "none"))
5801
+ };
5802
+ }
5803
+ async function gatherMultiEcosystemConfig(flags, projectName, projectDir, relativePath) {
5804
+ const baseConfig = getDefaultConfig();
5805
+ const frontend = promptValue(await navigableSelect({
5806
+ message: "Select TypeScript web frontend",
5807
+ options: WEB_FRONTEND_PROMPT_OPTIONS,
5808
+ initialValue: flags.frontend?.[0] ?? "next"
5809
+ }));
5810
+ const frontendList = [frontend];
5811
+ const astroIntegration = frontend === "astro" ? promptValue(await getAstroIntegrationChoice(flags.astroIntegration)) : void 0;
5812
+ const uiLibrary = hasWebStyling$1(frontendList) ? promptValue(await getUILibraryChoice(flags.uiLibrary, frontendList, astroIntegration)) : "none";
5813
+ const shadcnOptions = uiLibrary === "shadcn-ui" ? await getShadcnOptions({
5814
+ shadcnBase: flags.shadcnBase,
5815
+ shadcnStyle: flags.shadcnStyle,
5816
+ shadcnIconLibrary: flags.shadcnIconLibrary,
5817
+ shadcnColorTheme: flags.shadcnColorTheme,
5818
+ shadcnBaseColor: flags.shadcnBaseColor,
5819
+ shadcnFont: flags.shadcnFont,
5820
+ shadcnRadius: flags.shadcnRadius
5821
+ }) : void 0;
5822
+ const cssFramework = hasWebStyling$1(frontendList) ? promptValue(await getCSSFrameworkChoice(flags.cssFramework, uiLibrary)) : "none";
5823
+ const backendEcosystem = await selectBackendEcosystem();
5824
+ const stackPartSpecs = [`frontend:typescript:${frontend}`];
5825
+ const backendChoices = {};
5826
+ let database = "none";
5827
+ let dbSetup = "none";
5828
+ if (backendEcosystem === "go") {
5829
+ const goWebFramework = promptValue(await getGoWebFrameworkChoice(flags.goWebFramework));
5830
+ if (goWebFramework !== "none") {
5831
+ const databaseConfig = await selectDatabaseConfig(flags);
5832
+ database = databaseConfig.database;
5833
+ dbSetup = databaseConfig.dbSetup;
5834
+ }
5835
+ const goOrm = database === "none" || goWebFramework === "none" ? "none" : promptValue(await getGoOrmChoice(flags.goOrm));
5836
+ const goApi = goWebFramework === "none" ? "none" : promptValue(await getGoApiChoice(flags.goApi));
5837
+ const goAuth = goWebFramework === "none" ? "none" : promptValue(await getGoAuthChoice(flags.goAuth));
5838
+ const goCli = goWebFramework === "none" ? "none" : promptValue(await getGoCliChoice(flags.goCli));
5839
+ const goLogging = goWebFramework === "none" ? "none" : promptValue(await getGoLoggingChoice(flags.goLogging));
5840
+ Object.assign(backendChoices, {
5841
+ goWebFramework,
5842
+ goOrm,
5843
+ goApi,
5844
+ goAuth,
5845
+ goCli,
5846
+ goLogging
5847
+ });
5848
+ if (goWebFramework !== "none") stackPartSpecs.push(`backend:go:${goWebFramework}`);
5849
+ if (goOrm !== "none") stackPartSpecs.push(`backend.orm:go:${goOrm}`);
5850
+ if (goApi !== "none") stackPartSpecs.push(`backend.api:go:${goApi}`);
5851
+ if (goAuth !== "none") stackPartSpecs.push(`backend.auth:go:${goAuth}`);
5852
+ }
5853
+ if (backendEcosystem === "rust") {
5854
+ const rustWebFramework = promptValue(await getRustWebFrameworkChoice(flags.rustWebFramework));
5855
+ if (rustWebFramework !== "none") {
5856
+ const databaseConfig = await selectDatabaseConfig(flags);
5857
+ database = databaseConfig.database;
5858
+ dbSetup = databaseConfig.dbSetup;
5859
+ }
5860
+ const rustOrm = database === "none" || rustWebFramework === "none" ? "none" : promptValue(await getRustOrmChoice(flags.rustOrm));
5861
+ const rustApi = rustWebFramework === "none" ? "none" : promptValue(await getRustApiChoice(flags.rustApi));
5862
+ const rustAuth = rustWebFramework === "none" ? "none" : promptValue(await getRustAuthChoice(flags.rustAuth));
5863
+ const rustFrontend = "none";
5864
+ const rustCli = rustWebFramework === "none" ? "none" : promptValue(await getRustCliChoice(flags.rustCli));
5865
+ const rustLibraries = rustWebFramework === "none" ? [] : promptValue(await getRustLibrariesChoice(flags.rustLibraries));
5866
+ const rustLogging = rustWebFramework === "none" ? "none" : promptValue(await getRustLoggingChoice(flags.rustLogging));
5867
+ const rustErrorHandling = promptValue(await getRustErrorHandlingChoice(flags.rustErrorHandling));
5868
+ const rustCaching = rustWebFramework === "none" ? "none" : promptValue(await getRustCachingChoice(flags.rustCaching));
5869
+ Object.assign(backendChoices, {
5870
+ rustWebFramework,
5871
+ rustOrm,
5872
+ rustApi,
5873
+ rustAuth,
5874
+ rustFrontend,
5875
+ rustCli,
5876
+ rustLibraries,
5877
+ rustLogging,
5878
+ rustErrorHandling,
5879
+ rustCaching
5880
+ });
5881
+ if (rustWebFramework !== "none") stackPartSpecs.push(`backend:rust:${rustWebFramework}`);
5882
+ if (rustOrm !== "none") stackPartSpecs.push(`backend.orm:rust:${rustOrm}`);
5883
+ if (rustApi !== "none") stackPartSpecs.push(`backend.api:rust:${rustApi}`);
5884
+ if (rustAuth !== "none") stackPartSpecs.push(`backend.auth:rust:${rustAuth}`);
5885
+ }
5886
+ if (backendEcosystem === "python") {
5887
+ const pythonWebFramework = promptValue(await getPythonWebFrameworkChoice(flags.pythonWebFramework));
5888
+ if (pythonWebFramework !== "none") {
5889
+ const databaseConfig = await selectDatabaseConfig(flags);
5890
+ database = databaseConfig.database;
5891
+ dbSetup = databaseConfig.dbSetup;
5892
+ }
5893
+ const pythonOrm = database === "none" || pythonWebFramework === "none" ? "none" : promptValue(await getPythonOrmChoice(flags.pythonOrm));
5894
+ const pythonValidation = pythonWebFramework === "none" ? "none" : promptValue(await getPythonValidationChoice(flags.pythonValidation));
5895
+ const pythonAi = pythonWebFramework === "none" ? [] : promptValue(await getPythonAiChoice(flags.pythonAi));
5896
+ const pythonAuth = pythonWebFramework === "none" ? "none" : promptValue(await getPythonAuthChoice(flags.pythonAuth));
5897
+ const pythonTaskQueue = pythonWebFramework === "none" ? "none" : promptValue(await getPythonTaskQueueChoice(flags.pythonTaskQueue));
5898
+ const pythonGraphql = pythonWebFramework === "none" ? "none" : promptValue(await getPythonGraphqlChoice(flags.pythonGraphql));
5899
+ const pythonQuality = pythonWebFramework === "none" ? "none" : promptValue(await getPythonQualityChoice(flags.pythonQuality));
5900
+ Object.assign(backendChoices, {
5901
+ pythonWebFramework,
5902
+ pythonOrm,
5903
+ pythonValidation,
5904
+ pythonAi,
5905
+ pythonAuth,
5906
+ pythonTaskQueue,
5907
+ pythonGraphql,
5908
+ pythonQuality
5909
+ });
5910
+ if (pythonWebFramework !== "none") stackPartSpecs.push(`backend:python:${pythonWebFramework}`);
5911
+ if (pythonOrm !== "none") stackPartSpecs.push(`backend.orm:python:${pythonOrm}`);
5912
+ if (pythonAuth !== "none") stackPartSpecs.push(`backend.auth:python:${pythonAuth}`);
5913
+ if (pythonTaskQueue !== "none") stackPartSpecs.push(`backend.jobQueue:python:${pythonTaskQueue}`);
5914
+ if (pythonGraphql !== "none") stackPartSpecs.push(`backend.api:python:${pythonGraphql}`);
5915
+ }
5916
+ if (backendEcosystem === "java") {
5917
+ const javaWebFramework = promptValue(await getJavaWebFrameworkChoice(flags.javaWebFramework));
5918
+ const javaBuildTool = promptValue(await getJavaBuildToolChoice(flags.javaBuildTool));
5919
+ if (javaWebFramework !== "none" && javaBuildTool !== "none") {
5920
+ const databaseConfig = await selectDatabaseConfig(flags);
5921
+ database = databaseConfig.database;
5922
+ dbSetup = databaseConfig.dbSetup;
5923
+ }
5924
+ const javaOrm = database === "none" || javaWebFramework !== "spring-boot" || javaBuildTool === "none" ? "none" : promptValue(await getJavaOrmChoice(flags.javaOrm));
5925
+ const javaAuth = javaWebFramework !== "spring-boot" || javaBuildTool === "none" ? "none" : promptValue(await getJavaAuthChoice(flags.javaAuth));
5926
+ const javaLibraries = javaWebFramework !== "spring-boot" || javaBuildTool === "none" ? [] : promptValue(await getJavaLibrariesChoice(flags.javaLibraries));
5927
+ const javaTestingLibraries = promptValue(await getJavaTestingLibrariesChoice(flags.javaTestingLibraries));
5928
+ Object.assign(backendChoices, {
5929
+ javaWebFramework,
5930
+ javaBuildTool,
5931
+ javaOrm,
5932
+ javaAuth,
5933
+ javaLibraries,
5934
+ javaTestingLibraries
5935
+ });
5936
+ if (javaWebFramework !== "none") stackPartSpecs.push(`backend:java:${javaWebFramework}`);
5937
+ if (javaOrm !== "none") stackPartSpecs.push(`backend.orm:java:${javaOrm}`);
5938
+ if (javaAuth !== "none") stackPartSpecs.push(`backend.auth:java:${javaAuth}`);
5939
+ }
5940
+ if (backendEcosystem === "elixir") {
5941
+ const elixirWebFramework = promptValue(await getElixirWebFrameworkChoice(flags.elixirWebFramework));
5942
+ if (elixirWebFramework !== "none") {
5943
+ const databaseConfig = await selectDatabaseConfig(flags);
5944
+ database = databaseConfig.database;
5945
+ dbSetup = databaseConfig.dbSetup;
5946
+ }
5947
+ const elixirOrm = database === "none" || elixirWebFramework === "none" ? "none" : promptValue(await getElixirOrmChoice(flags.elixirOrm));
5948
+ const elixirAuth = elixirWebFramework === "none" ? "none" : promptValue(await getElixirAuthChoice(flags.elixirAuth));
5949
+ const elixirApi = elixirWebFramework === "none" ? "none" : promptValue(await getElixirApiChoice(flags.elixirApi));
5950
+ const elixirRealtime = elixirWebFramework === "none" ? "none" : promptValue(await getElixirRealtimeChoice(flags.elixirRealtime));
5951
+ const elixirJobs = elixirWebFramework === "none" ? "none" : promptValue(await getElixirJobsChoice(flags.elixirJobs));
5952
+ const elixirValidation = elixirWebFramework === "none" ? "none" : promptValue(await getElixirValidationChoice(flags.elixirValidation));
5953
+ const elixirHttp = elixirWebFramework === "none" ? "none" : promptValue(await getElixirHttpChoice(flags.elixirHttp));
5954
+ const elixirJson = elixirWebFramework === "none" ? "none" : promptValue(await getElixirJsonChoice(flags.elixirJson));
5955
+ const elixirEmail = elixirWebFramework === "none" ? "none" : promptValue(await getElixirEmailChoice(flags.elixirEmail));
5956
+ const elixirCaching = elixirWebFramework === "none" ? "none" : promptValue(await getElixirCachingChoice(flags.elixirCaching));
5957
+ const elixirObservability = elixirWebFramework === "none" ? "none" : promptValue(await getElixirObservabilityChoice(flags.elixirObservability));
5958
+ const elixirTesting = elixirWebFramework === "none" ? "none" : promptValue(await getElixirTestingChoice(flags.elixirTesting));
5959
+ const elixirQuality = elixirWebFramework === "none" ? "none" : promptValue(await getElixirQualityChoice(flags.elixirQuality));
5960
+ const elixirDeploy = elixirWebFramework === "none" ? "none" : promptValue(await getElixirDeployChoice(flags.elixirDeploy));
5961
+ Object.assign(backendChoices, {
5962
+ elixirWebFramework,
5963
+ elixirOrm,
5964
+ elixirAuth,
5965
+ elixirApi,
5966
+ elixirRealtime,
5967
+ elixirJobs,
5968
+ elixirValidation,
5969
+ elixirHttp,
5970
+ elixirJson,
5971
+ elixirEmail,
5972
+ elixirCaching,
5973
+ elixirObservability,
5974
+ elixirTesting,
5975
+ elixirQuality,
5976
+ elixirDeploy
5977
+ });
5978
+ if (elixirWebFramework !== "none") stackPartSpecs.push(`backend:elixir:${elixirWebFramework}`);
5979
+ if (elixirOrm !== "none") stackPartSpecs.push(`backend.orm:elixir:${elixirOrm}`);
5980
+ if (elixirAuth !== "none") stackPartSpecs.push(`backend.auth:elixir:${elixirAuth}`);
5981
+ if (elixirApi !== "none") stackPartSpecs.push(`backend.api:elixir:${elixirApi}`);
5982
+ if (elixirRealtime !== "none") stackPartSpecs.push(`backend.api:elixir:${elixirRealtime}`);
5983
+ if (elixirJobs !== "none") stackPartSpecs.push(`backend.jobQueue:elixir:${elixirJobs}`);
5984
+ if (elixirEmail !== "none") stackPartSpecs.push(`backend.email:elixir:${elixirEmail}`);
5985
+ if (elixirCaching !== "none") stackPartSpecs.push(`backend.caching:elixir:${elixirCaching}`);
5986
+ if (elixirObservability !== "none") stackPartSpecs.push(`backend.observability:elixir:${elixirObservability}`);
5987
+ if (elixirTesting !== "none") stackPartSpecs.push(`backend.testing:elixir:${elixirTesting}`);
5988
+ if (elixirDeploy !== "none") stackPartSpecs.push(`backend.deploy:elixir:${elixirDeploy}`);
5989
+ }
5990
+ if (database !== "none") stackPartSpecs.push(`database:universal:${database}`);
5991
+ const stackParts = (0, types_exports.parseStackPartSpecs)(stackPartSpecs, "selected");
5992
+ const graphPartial = (0, types_exports.stackPartsToLegacyProjectConfigPartial)(stackParts);
5993
+ const addons = promptValue(await getAddonsChoice(flags.addons, frontendList, "none", "none", "bun"));
5994
+ const webDeploy = promptValue(await getDeploymentChoice(flags.webDeploy, "bun", "none", frontendList));
5995
+ const serverDeploy = await selectServerDeployment(flags.serverDeploy);
5996
+ const aiDocs = promptValue(await getAiDocsChoice(flags.aiDocs));
5997
+ const git = promptValue(await getGitChoice(flags.git));
5998
+ const packageManager = promptValue(await getPackageManagerChoice(flags.packageManager));
5999
+ const install = promptValue(await getinstallChoice(flags.install, "typescript", "none"));
6000
+ return {
6001
+ ...baseConfig,
6002
+ ...flags,
6003
+ ...graphPartial,
6004
+ ...backendChoices,
6005
+ projectName,
6006
+ projectDir,
6007
+ relativePath,
6008
+ ecosystem: "typescript",
6009
+ frontend: frontendList,
6010
+ backend: "none",
6011
+ runtime: "none",
6012
+ database,
6013
+ orm: "none",
6014
+ api: "none",
6015
+ auth: "none",
6016
+ astroIntegration,
6017
+ uiLibrary,
6018
+ ...shadcnOptions,
6019
+ cssFramework,
6020
+ addons,
6021
+ examples: [],
6022
+ dbSetup,
6023
+ webDeploy,
6024
+ serverDeploy,
6025
+ aiDocs,
6026
+ git,
6027
+ packageManager,
6028
+ install,
6029
+ stackParts
6030
+ };
6031
+ }
6032
+
6033
+ //#endregion
6034
+ //#region src/prompts/navigable-group.ts
6035
+ /**
6036
+ * Navigable group - a group of prompts that allows going back
6037
+ */
6038
+ /**
6039
+ * Define a group of prompts that supports going back to previous prompts.
6040
+ * Returns a result object with all the values, or handles cancel/go-back navigation.
6041
+ */
6042
+ async function navigableGroup(prompts, opts) {
6043
+ const results = {};
6044
+ const promptNames = Object.keys(prompts);
6045
+ let currentIndex = 0;
6046
+ let goingBack = false;
6047
+ while (currentIndex < promptNames.length) {
6048
+ const name = promptNames[currentIndex];
6049
+ const prompt = prompts[name];
6050
+ setIsFirstPrompt$1(currentIndex === 0);
6051
+ setLastPromptShownUI(false);
6052
+ const result = await prompt({ results })?.catch((e) => {
6053
+ throw e;
6054
+ });
6055
+ if (isGoBack(result)) {
6056
+ goingBack = true;
6057
+ if (currentIndex > 0) {
6058
+ const prevName = promptNames[currentIndex - 1];
6059
+ delete results[prevName];
6060
+ currentIndex--;
6061
+ continue;
6062
+ }
6063
+ goingBack = false;
6064
+ continue;
6065
+ }
6066
+ if (isCancel$1(result)) {
6067
+ if (typeof opts?.onCancel === "function") {
6068
+ results[name] = "canceled";
6069
+ opts.onCancel({ results });
6070
+ }
6071
+ setIsFirstPrompt$1(false);
6072
+ return results;
6073
+ }
6074
+ if (goingBack && !didLastPromptShowUI()) {
6075
+ if (currentIndex > 0) {
6076
+ const prevName = promptNames[currentIndex - 1];
6077
+ delete results[prevName];
6078
+ currentIndex--;
6079
+ continue;
6080
+ }
6081
+ }
6082
+ goingBack = false;
6083
+ results[name] = result;
6084
+ currentIndex++;
6085
+ }
6086
+ setIsFirstPrompt$1(false);
6087
+ return results;
6088
+ }
6089
+
6090
+ //#endregion
6091
+ //#region src/prompts/observability.ts
6092
+ const OBSERVABILITY_PROMPT_OPTIONS = [
6093
+ {
6094
+ value: "opentelemetry",
6095
+ label: "OpenTelemetry",
6096
+ hint: "Observability framework for traces, metrics, and logs"
6097
+ },
6098
+ {
6099
+ value: "sentry",
6100
+ label: "Sentry",
6101
+ hint: "Error tracking and performance monitoring"
6102
+ },
6103
+ {
6104
+ value: "grafana",
6105
+ label: "Grafana",
6106
+ hint: "Prometheus metrics for Grafana dashboards and alerting"
6107
+ },
6108
+ {
6109
+ value: "none",
6110
+ label: "None",
6111
+ hint: "Skip observability/tracing setup"
6112
+ }
6113
+ ];
6114
+ const NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS = OBSERVABILITY_PROMPT_OPTIONS.filter((option) => option.value === "sentry" || option.value === "none");
6115
+ function resolveObservabilityPrompt(context = {}) {
6116
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
6117
+ shouldPrompt: false,
6118
+ mode: "single",
6119
+ options: [],
6120
+ autoValue: "none"
6121
+ };
6122
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS : OBSERVABILITY_PROMPT_OPTIONS;
6123
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
6124
+ shouldPrompt: false,
6125
+ mode: "single",
6126
+ options: [],
6127
+ autoValue: "none"
6128
+ };
6129
+ return context.observability !== void 0 ? {
6130
+ shouldPrompt: false,
6131
+ mode: "single",
6132
+ options,
6133
+ autoValue: context.observability
6134
+ } : {
6135
+ shouldPrompt: true,
6136
+ mode: "single",
6137
+ options,
6138
+ initialValue: "none"
6139
+ };
6140
+ }
6141
+ async function getObservabilityChoice(observability, backend, ecosystem) {
6142
+ const resolution = resolveObservabilityPrompt({
6143
+ observability,
6144
+ backend,
6145
+ ecosystem
6146
+ });
6147
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6148
+ const response = await navigableSelect({
6149
+ message: "Select observability solution",
6150
+ options: resolution.options,
6151
+ initialValue: resolution.initialValue
6152
+ });
6153
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6154
+ return response;
6155
+ }
6156
+
6157
+ //#endregion
6158
+ //#region src/prompts/orm.ts
6159
+ const ormOptions = {
6160
+ prisma: {
6161
+ value: "prisma",
6162
+ label: "Prisma",
6163
+ hint: "Powerful, feature-rich ORM"
6164
+ },
6165
+ mongoose: {
6166
+ value: "mongoose",
6167
+ label: "Mongoose",
6168
+ hint: "Elegant object modeling tool"
6169
+ },
6170
+ drizzle: {
6171
+ value: "drizzle",
6172
+ label: "Drizzle",
6173
+ hint: "Lightweight and performant TypeScript ORM"
6174
+ },
6175
+ typeorm: {
6176
+ value: "typeorm",
6177
+ label: "TypeORM",
6178
+ hint: "Traditional ORM with Active Record/Data Mapper"
6179
+ },
6180
+ kysely: {
6181
+ value: "kysely",
6182
+ label: "Kysely",
6183
+ hint: "Type-safe SQL query builder"
6184
+ },
6185
+ mikroorm: {
6186
+ value: "mikroorm",
6187
+ label: "MikroORM",
6188
+ hint: "Data Mapper ORM for DDD"
6189
+ },
6190
+ sequelize: {
6191
+ value: "sequelize",
6192
+ label: "Sequelize",
6193
+ hint: "Mature ORM with wide adoption"
6194
+ }
6195
+ };
6196
+ function resolveORMPrompt(context) {
6197
+ if (context.backend === "convex" || !context.hasDatabase) return {
6198
+ shouldPrompt: false,
6199
+ mode: "single",
6200
+ options: [],
6201
+ autoValue: "none"
6202
+ };
6203
+ if (context.database === "edgedb" || context.database === "redis") return {
6204
+ shouldPrompt: false,
6205
+ mode: "single",
6206
+ options: [],
6207
+ autoValue: "none"
6208
+ };
6209
+ if (context.orm !== void 0) return {
6210
+ shouldPrompt: false,
6211
+ mode: "single",
6212
+ options: [],
6213
+ autoValue: context.orm
6214
+ };
6215
+ return {
6216
+ shouldPrompt: true,
6217
+ mode: "single",
6218
+ options: context.database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [
6219
+ ormOptions.drizzle,
6220
+ ormOptions.prisma,
6221
+ ormOptions.typeorm,
6222
+ ormOptions.kysely,
6223
+ ormOptions.mikroorm,
6224
+ ormOptions.sequelize
6225
+ ],
6226
+ initialValue: context.database === "mongodb" ? "prisma" : context.runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
6227
+ };
6228
+ }
6229
+ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
6230
+ const resolution = resolveORMPrompt({
6231
+ orm,
6232
+ hasDatabase,
6233
+ database,
6234
+ backend,
6235
+ runtime
6236
+ });
6237
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6238
+ const response = await navigableSelect({
6239
+ message: "Select ORM",
6240
+ options: resolution.options,
6241
+ initialValue: resolution.initialValue
6242
+ });
6243
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6244
+ return response;
6245
+ }
6246
+
6247
+ //#endregion
6248
+ //#region src/prompts/payments.ts
6249
+ function resolvePaymentsPrompt(context = {}) {
6250
+ if (context.payments !== void 0) return {
6251
+ shouldPrompt: false,
6252
+ mode: "single",
6253
+ options: [],
6254
+ autoValue: context.payments
6255
+ };
6256
+ if (context.backend === "none") return {
6257
+ shouldPrompt: false,
6258
+ mode: "single",
6259
+ options: [],
6260
+ autoValue: "none"
6261
+ };
6262
+ const isPolarCompatible = context.auth === "better-auth" && (context.frontends?.length === 0 || splitFrontends$1(context.frontends).web.length > 0);
6263
+ const options = [];
6264
+ if (isPolarCompatible) options.push({
6265
+ value: "polar",
6266
+ label: "Polar",
6267
+ hint: "Turn your software into a business. 6 lines of code."
6268
+ });
6269
+ options.push({
6270
+ value: "stripe",
6271
+ label: "Stripe",
6272
+ hint: "Payment processing platform for internet businesses."
6273
+ }, {
6274
+ value: "lemon-squeezy",
6275
+ label: "Lemon Squeezy",
6276
+ hint: "All-in-one platform for SaaS, digital products, and subscriptions."
6277
+ }, {
6278
+ value: "paddle",
6279
+ label: "Paddle",
6280
+ hint: "Complete payments infrastructure for SaaS."
6281
+ }, {
6282
+ value: "dodo",
6283
+ label: "Dodo Payments",
6284
+ hint: "Simple payment infrastructure for developers."
6285
+ }, {
6286
+ value: "none",
6287
+ label: "None",
6288
+ hint: "No payments integration"
6289
+ });
6290
+ return {
6291
+ shouldPrompt: true,
6292
+ mode: "single",
6293
+ options,
6294
+ initialValue: DEFAULT_CONFIG.payments
6295
+ };
6296
+ }
6297
+ async function getPaymentsChoice(payments, auth, backend, frontends) {
6298
+ const resolution = resolvePaymentsPrompt({
6299
+ payments,
6300
+ auth,
6301
+ backend,
6302
+ frontends
6303
+ });
6304
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6305
+ const response = await navigableSelect({
6306
+ message: "Select payments provider",
6307
+ options: resolution.options,
6308
+ initialValue: resolution.initialValue
6309
+ });
6310
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6311
+ return response;
6312
+ }
6313
+
6314
+ //#endregion
6315
+ //#region src/prompts/realtime.ts
6316
+ const REALTIME_PROMPT_OPTIONS = [
6317
+ {
6318
+ value: "socket-io",
6319
+ label: "Socket.IO",
6320
+ hint: "Real-time bidirectional communication with fallbacks"
6321
+ },
6322
+ {
6323
+ value: "partykit",
6324
+ label: "PartyKit",
6325
+ hint: "Edge-native multiplayer infrastructure on Cloudflare"
6326
+ },
6327
+ {
6328
+ value: "ably",
6329
+ label: "Ably",
6330
+ hint: "Real-time messaging platform with pub/sub and presence"
6331
+ },
6332
+ {
6333
+ value: "pusher",
6334
+ label: "Pusher",
6335
+ hint: "Real-time communication APIs with channels and events"
6336
+ },
6337
+ {
6338
+ value: "liveblocks",
6339
+ label: "Liveblocks",
6340
+ hint: "Collaboration infrastructure for multiplayer experiences"
6341
+ },
6342
+ {
6343
+ value: "yjs",
6344
+ label: "Y.js",
6345
+ hint: "CRDT library for real-time collaboration with conflict-free sync"
6346
+ },
6347
+ {
6348
+ value: "none",
6349
+ label: "None",
6350
+ hint: "Skip real-time/WebSocket integration"
6351
+ }
6352
+ ];
6353
+ function resolveRealtimePrompt(context = {}) {
6354
+ if (context.backend === "none" || context.backend === "convex") return {
6355
+ shouldPrompt: false,
6356
+ mode: "single",
6357
+ options: [],
6358
+ autoValue: "none"
6359
+ };
6360
+ return context.realtime !== void 0 ? {
6361
+ shouldPrompt: false,
6362
+ mode: "single",
6363
+ options: REALTIME_PROMPT_OPTIONS,
6364
+ autoValue: context.realtime
6365
+ } : {
6366
+ shouldPrompt: true,
6367
+ mode: "single",
6368
+ options: REALTIME_PROMPT_OPTIONS,
6369
+ initialValue: "none"
6370
+ };
6371
+ }
6372
+ async function getRealtimeChoice(realtime, backend) {
6373
+ const resolution = resolveRealtimePrompt({
6374
+ realtime,
6375
+ backend
6376
+ });
6377
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6378
+ const response = await navigableSelect({
6379
+ message: "Select real-time solution",
6380
+ options: resolution.options,
6381
+ initialValue: resolution.initialValue
6382
+ });
6383
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6384
+ return response;
6385
+ }
6386
+
6387
+ //#endregion
6388
+ //#region src/prompts/runtime.ts
6389
+ const RUNTIME_PROMPT_OPTIONS = [
6390
+ {
6391
+ value: "bun",
6392
+ label: "Bun",
6393
+ hint: "Fast all-in-one JavaScript runtime"
6394
+ },
6395
+ {
6396
+ value: "node",
6397
+ label: "Node.js",
6398
+ hint: "Traditional Node.js runtime"
6399
+ },
6400
+ {
6401
+ value: "workers",
6402
+ label: "Cloudflare Workers",
6403
+ hint: "Edge runtime on Cloudflare's global network"
6404
+ }
6405
+ ];
6406
+ function resolveRuntimePrompt(context = {}) {
6407
+ if (context.backend === "convex" || context.backend === "none" || context.backend === "self") return {
6408
+ shouldPrompt: false,
6409
+ mode: "single",
6410
+ options: [],
6411
+ autoValue: "none"
6412
+ };
6413
+ const options = RUNTIME_PROMPT_OPTIONS.filter((option) => option.value !== "workers" || context.backend === "hono");
6414
+ return context.runtime !== void 0 ? {
6415
+ shouldPrompt: false,
6416
+ mode: "single",
6417
+ options,
6418
+ autoValue: context.runtime
6419
+ } : {
6420
+ shouldPrompt: true,
6421
+ mode: "single",
6422
+ options,
6423
+ initialValue: DEFAULT_CONFIG.runtime
6424
+ };
6425
+ }
6426
+ async function getRuntimeChoice(runtime, backend) {
6427
+ const resolution = resolveRuntimePrompt({
6428
+ runtime,
6429
+ backend
6430
+ });
6431
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6432
+ const response = await navigableSelect({
6433
+ message: "Select runtime",
6434
+ options: resolution.options,
6435
+ initialValue: resolution.initialValue
6436
+ });
6437
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6438
+ return response;
6439
+ }
6440
+
6441
+ //#endregion
6442
+ //#region src/prompts/search.ts
6443
+ const SEARCH_PROMPT_OPTIONS = [
6444
+ {
6445
+ value: "meilisearch",
6446
+ label: "Meilisearch",
6447
+ hint: "Lightning-fast search engine with typo tolerance"
6448
+ },
6449
+ {
6450
+ value: "typesense",
6451
+ label: "Typesense",
6452
+ hint: "Fast, typo-tolerant search with built-in vector search"
6453
+ },
6454
+ {
6455
+ value: "elasticsearch",
6456
+ label: "Elasticsearch",
6457
+ hint: "Distributed search and analytics engine with local and cloud deployments"
6458
+ },
6459
+ {
6460
+ value: "algolia",
6461
+ label: "Algolia",
6462
+ hint: "Hosted search API with instant results, typo tolerance, and analytics"
6463
+ },
6464
+ {
6465
+ value: "none",
6466
+ label: "None",
6467
+ hint: "Skip search engine setup"
6468
+ }
6469
+ ];
6470
+ const NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS = SEARCH_PROMPT_OPTIONS.filter((option) => option.value === "meilisearch" || option.value === "none");
6471
+ function resolveSearchPrompt(context = {}) {
6472
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
6473
+ shouldPrompt: false,
6474
+ mode: "single",
6475
+ options: [],
6476
+ autoValue: "none"
6477
+ };
6478
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS : SEARCH_PROMPT_OPTIONS;
6479
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
6480
+ shouldPrompt: false,
6481
+ mode: "single",
6482
+ options: [],
6483
+ autoValue: "none"
6484
+ };
6485
+ return context.search !== void 0 ? {
6486
+ shouldPrompt: false,
6487
+ mode: "single",
6488
+ options,
6489
+ autoValue: context.search
6490
+ } : {
6491
+ shouldPrompt: true,
6492
+ mode: "single",
6493
+ options,
6494
+ initialValue: "none"
6495
+ };
6496
+ }
6497
+ async function getSearchChoice(search, backend, ecosystem) {
6498
+ const resolution = resolveSearchPrompt({
6499
+ search,
6500
+ backend,
6501
+ ecosystem
6502
+ });
6503
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6504
+ const response = await navigableSelect({
6505
+ message: "Select search engine",
6506
+ options: resolution.options,
6507
+ initialValue: resolution.initialValue
6508
+ });
6509
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6510
+ return response;
6511
+ }
6512
+
6513
+ //#endregion
6514
+ //#region src/prompts/server-deploy.ts
6515
+ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeploy) {
6516
+ if (deployment !== void 0) return deployment;
6517
+ if (backend === "none" || backend === "convex") return "none";
6518
+ if (backend !== "hono") return "none";
6519
+ if (runtime === "workers") return "cloudflare";
6520
+ return "none";
6521
+ }
6522
+
6523
+ //#endregion
6524
+ //#region src/prompts/state-management.ts
6525
+ function resolveStateManagementPrompt(context = {}) {
6526
+ if (context.stateManagement !== void 0) return {
6527
+ shouldPrompt: false,
6528
+ mode: "single",
6529
+ options: [],
6530
+ autoValue: context.stateManagement
6531
+ };
6532
+ const { web } = splitFrontends$1(context.frontends);
6533
+ if (web.length === 0) return {
6534
+ shouldPrompt: false,
6535
+ mode: "single",
6536
+ options: [],
6537
+ autoValue: "none"
6538
+ };
6539
+ const isReact = web.some((f) => [
6540
+ "tanstack-router",
6541
+ "react-router",
6542
+ "react-vite",
6543
+ "tanstack-start",
6544
+ "next",
6545
+ "vinext",
6546
+ "redwood"
6547
+ ].includes(f));
6548
+ const isFresh = web.includes("fresh");
6549
+ const options = [];
6550
+ if (isReact) options.push({
6551
+ value: "zustand",
6552
+ label: "Zustand",
6553
+ hint: "Lightweight state management with simple API"
6554
+ }, {
6555
+ value: "jotai",
6556
+ label: "Jotai",
6557
+ hint: "Primitive and flexible atomic state"
6558
+ }, {
6559
+ value: "redux-toolkit",
6560
+ label: "Redux Toolkit",
6561
+ hint: "Enterprise-standard state with excellent TS support"
6562
+ }, {
6563
+ value: "valtio",
6564
+ label: "Valtio",
6565
+ hint: "Proxy-based state management"
6566
+ }, {
6567
+ value: "legend-state",
6568
+ label: "Legend State",
6569
+ hint: "High-performance observable state for React"
6570
+ }, {
6571
+ value: "mobx",
6572
+ label: "MobX",
6573
+ hint: "Observable-based reactive state management"
6574
+ });
6575
+ if (!isFresh) options.push({
6576
+ value: "nanostores",
6577
+ label: "Nanostores",
6578
+ hint: "Tiny state manager (1KB) for all frameworks"
6579
+ }, {
6580
+ value: "xstate",
6581
+ label: "XState",
6582
+ hint: "State machines and statecharts for complex logic"
6583
+ }, {
6584
+ value: "tanstack-store",
6585
+ label: "TanStack Store",
6586
+ hint: "Framework-agnostic store powering TanStack ecosystem"
6587
+ });
6588
+ options.push({
6589
+ value: "none",
6590
+ label: "None",
6591
+ hint: "Skip state management setup"
6592
+ });
6593
+ return {
6594
+ shouldPrompt: true,
6595
+ mode: "single",
6596
+ options,
6597
+ initialValue: "none"
6598
+ };
6599
+ }
6600
+ async function getStateManagementChoice(stateManagement, frontends) {
6601
+ const resolution = resolveStateManagementPrompt({
6602
+ stateManagement,
6603
+ frontends
6604
+ });
6605
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6606
+ const response = await navigableSelect({
6607
+ message: "Select state management",
6608
+ options: resolution.options,
6609
+ initialValue: resolution.initialValue
6610
+ });
6611
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6612
+ return response;
6613
+ }
6614
+
6615
+ //#endregion
6616
+ //#region src/prompts/testing.ts
6617
+ const TESTING_PROMPT_OPTIONS = [
6618
+ {
6619
+ value: "vitest",
6620
+ label: "Vitest",
6621
+ hint: "Blazing fast Vite-native unit test framework"
6622
+ },
6623
+ {
6624
+ value: "vitest-playwright",
6625
+ label: "Vitest + Playwright",
6626
+ hint: "Both unit and E2E testing for complete coverage"
6205
6627
  },
6206
- "base-ui": {
6207
- label: "Base UI",
6208
- hint: "Unstyled, accessible components from MUI team (Radix successor)"
6628
+ {
6629
+ value: "playwright",
6630
+ label: "Playwright",
6631
+ hint: "End-to-end testing framework by Microsoft"
6209
6632
  },
6210
- "ark-ui": {
6211
- label: "Ark UI",
6212
- hint: "Headless, accessible UI components for React, Vue, Solid, and Svelte"
6633
+ {
6634
+ value: "jest",
6635
+ label: "Jest",
6636
+ hint: "Classic testing framework with wide ecosystem"
6213
6637
  },
6214
- "react-aria": {
6215
- label: "React Aria",
6216
- hint: "Adobe's accessible, unstyled UI components for React"
6638
+ {
6639
+ value: "cypress",
6640
+ label: "Cypress",
6641
+ hint: "E2E testing with time travel debugging"
6217
6642
  },
6218
- none: {
6643
+ {
6644
+ value: "none",
6219
6645
  label: "None",
6220
- hint: "No UI component library"
6646
+ hint: "Skip testing framework setup"
6221
6647
  }
6222
- };
6223
- function resolveUILibraryPrompt(context = {}) {
6224
- const { web } = splitFrontends$1(context.frontends);
6225
- if (web.length === 0) return {
6226
- shouldPrompt: false,
6227
- mode: "single",
6228
- options: [],
6229
- autoValue: "none"
6230
- };
6231
- const compatibleLibraries = getCompatibleUILibraries$1(context.frontends, context.astroIntegration);
6232
- if (context.uiLibrary !== void 0) return {
6233
- shouldPrompt: false,
6234
- mode: "single",
6235
- options: compatibleLibraries.map((lib) => ({
6236
- value: lib,
6237
- label: UI_LIBRARY_OPTIONS[lib].label,
6238
- hint: UI_LIBRARY_OPTIONS[lib].hint
6239
- })),
6240
- autoValue: compatibleLibraries.includes(context.uiLibrary) ? context.uiLibrary : compatibleLibraries[0]
6241
- };
6242
- const defaultLib = DEFAULT_UI_LIBRARY_BY_FRONTEND[web[0]];
6243
- return {
6244
- shouldPrompt: true,
6245
- mode: "single",
6246
- options: compatibleLibraries.map((lib) => ({
6247
- value: lib,
6248
- label: UI_LIBRARY_OPTIONS[lib].label,
6249
- hint: UI_LIBRARY_OPTIONS[lib].hint
6250
- })),
6251
- initialValue: compatibleLibraries.includes(defaultLib) ? defaultLib : compatibleLibraries[0]
6252
- };
6648
+ ];
6649
+ function resolveTestingPrompt(testing) {
6650
+ return createStaticSinglePromptResolution(TESTING_PROMPT_OPTIONS, "vitest", testing);
6253
6651
  }
6254
- async function getUILibraryChoice(uiLibrary, frontends, astroIntegration) {
6255
- const resolution = resolveUILibraryPrompt({
6256
- uiLibrary,
6257
- frontends,
6258
- astroIntegration
6259
- });
6652
+ async function getTestingChoice(testing) {
6653
+ const resolution = resolveTestingPrompt(testing);
6260
6654
  if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6261
- const selected = await navigableSelect({
6262
- message: "Select UI component library",
6655
+ const response = await navigableSelect({
6656
+ message: "Select testing framework",
6263
6657
  options: resolution.options,
6264
6658
  initialValue: resolution.initialValue
6265
6659
  });
6266
- if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
6267
- return selected;
6660
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6661
+ return response;
6268
6662
  }
6269
6663
 
6270
6664
  //#endregion
@@ -6326,71 +6720,12 @@ async function getValidationChoice(validation) {
6326
6720
  return response;
6327
6721
  }
6328
6722
 
6329
- //#endregion
6330
- //#region src/utils/compatibility.ts
6331
- const WEB_FRAMEWORKS = [
6332
- "tanstack-router",
6333
- "react-router",
6334
- "react-vite",
6335
- "tanstack-start",
6336
- "next",
6337
- "vinext",
6338
- "nuxt",
6339
- "svelte",
6340
- "solid",
6341
- "solid-start",
6342
- "astro",
6343
- "qwik",
6344
- "angular",
6345
- "redwood",
6346
- "fresh"
6347
- ];
6348
-
6349
- //#endregion
6350
- //#region src/prompts/web-deploy.ts
6351
- function hasWebFrontend(frontends) {
6352
- return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
6353
- }
6354
- function getDeploymentDisplay(deployment) {
6355
- if (deployment === "cloudflare") return {
6356
- label: "Cloudflare",
6357
- hint: "Deploy to Cloudflare Workers using Alchemy"
6358
- };
6359
- if (deployment === "vercel") return {
6360
- label: "Vercel",
6361
- hint: "Deploy to Vercel's edge network"
6362
- };
6363
- return {
6364
- label: deployment,
6365
- hint: `Add ${deployment} deployment`
6366
- };
6367
- }
6368
- async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
6369
- if (deployment !== void 0) return deployment;
6370
- if (!hasWebFrontend(frontend)) return "none";
6371
- const response = await navigableSelect({
6372
- message: "Select web deployment",
6373
- options: [
6374
- "cloudflare",
6375
- "vercel",
6376
- "none"
6377
- ].map((deploy) => {
6378
- const { label, hint } = getDeploymentDisplay(deploy);
6379
- return {
6380
- value: deploy,
6381
- label,
6382
- hint
6383
- };
6384
- }),
6385
- initialValue: DEFAULT_CONFIG.webDeploy
6386
- });
6387
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6388
- return response;
6389
- }
6390
-
6391
6723
  //#endregion
6392
6724
  //#region src/prompts/config-prompts.ts
6393
6725
  async function gatherConfig(flags, projectName, projectDir, relativePath) {
6726
+ if (flags.ecosystem === void 0 && flags.stackParts === void 0) {
6727
+ if (await getCompositionModeChoice() === "multi") return gatherMultiEcosystemConfig(flags, projectName, projectDir, relativePath);
6728
+ }
6394
6729
  const result = await navigableGroup({
6395
6730
  ecosystem: () => getEcosystemChoice(flags.ecosystem),
6396
6731
  frontend: ({ results }) => {
@@ -7017,6 +7352,15 @@ async function trackProjectCreation(config, disableAnalytics = false) {
7017
7352
 
7018
7353
  //#endregion
7019
7354
  //#region src/utils/display-config.ts
7355
+ function getSelectedGraphPart(config, role, ownerPartId) {
7356
+ return config.stackParts?.find((part) => part.role === role && part.ownerPartId === ownerPartId && part.source !== "provided");
7357
+ }
7358
+ function getGraphDisplayValue(config, role) {
7359
+ const primaryBackend = getSelectedGraphPart(config, "backend");
7360
+ const part = role === "backend" ? primaryBackend : getSelectedGraphPart(config, role, primaryBackend?.id) ?? getSelectedGraphPart(config, role);
7361
+ if (!part) return null;
7362
+ return `${part.ecosystem}:${part.toolId}`;
7363
+ }
7020
7364
  function displayConfig(config) {
7021
7365
  const configDisplay = [];
7022
7366
  if (config.projectName) configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
@@ -7027,14 +7371,26 @@ function displayConfig(config) {
7027
7371
  }
7028
7372
  if (config.uiLibrary !== void 0) configDisplay.push(`${pc.blue("UI Library:")} ${String(config.uiLibrary)}`);
7029
7373
  if (config.cssFramework !== void 0) configDisplay.push(`${pc.blue("CSS Framework:")} ${String(config.cssFramework)}`);
7030
- if (config.backend !== void 0) configDisplay.push(`${pc.blue("Backend:")} ${String(config.backend)}`);
7374
+ if (config.backend !== void 0) {
7375
+ const graphBackend = config.backend === "none" ? getGraphDisplayValue(config, "backend") : null;
7376
+ configDisplay.push(`${pc.blue("Backend:")} ${graphBackend ?? String(config.backend)}`);
7377
+ }
7031
7378
  if (config.runtime !== void 0) configDisplay.push(`${pc.blue("Runtime:")} ${String(config.runtime)}`);
7032
- if (config.api !== void 0) configDisplay.push(`${pc.blue("API:")} ${String(config.api)}`);
7379
+ if (config.api !== void 0) {
7380
+ const graphApi = config.api === "none" ? getGraphDisplayValue(config, "api") : null;
7381
+ configDisplay.push(`${pc.blue("API:")} ${graphApi ?? String(config.api)}`);
7382
+ }
7033
7383
  if (config.database !== void 0) configDisplay.push(`${pc.blue("Database:")} ${String(config.database)}`);
7034
- if (config.orm !== void 0) configDisplay.push(`${pc.blue("ORM:")} ${String(config.orm)}`);
7384
+ if (config.orm !== void 0) {
7385
+ const graphOrm = config.orm === "none" ? getGraphDisplayValue(config, "orm") : null;
7386
+ configDisplay.push(`${pc.blue("ORM:")} ${graphOrm ?? String(config.orm)}`);
7387
+ }
7035
7388
  if (config.auth !== void 0) configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`);
7036
7389
  if (config.payments !== void 0) configDisplay.push(`${pc.blue("Payments:")} ${String(config.payments)}`);
7037
- if (config.email !== void 0) configDisplay.push(`${pc.blue("Email:")} ${String(config.email)}`);
7390
+ if (config.email !== void 0) {
7391
+ const graphEmail = config.email === "none" ? getGraphDisplayValue(config, "email") : null;
7392
+ configDisplay.push(`${pc.blue("Email:")} ${graphEmail ?? String(config.email)}`);
7393
+ }
7038
7394
  if (config.fileUpload !== void 0) configDisplay.push(`${pc.blue("File Upload:")} ${String(config.fileUpload)}`);
7039
7395
  if (config.effect !== void 0) configDisplay.push(`${pc.blue("Effect:")} ${String(config.effect)}`);
7040
7396
  if (config.ai !== void 0) configDisplay.push(`${pc.blue("AI:")} ${String(config.ai)}`);
@@ -7046,6 +7402,42 @@ function displayConfig(config) {
7046
7402
  if (config.realtime !== void 0) configDisplay.push(`${pc.blue("Realtime:")} ${String(config.realtime)}`);
7047
7403
  if (config.jobQueue !== void 0) configDisplay.push(`${pc.blue("Job Queue:")} ${String(config.jobQueue)}`);
7048
7404
  if (config.logging !== void 0) configDisplay.push(`${pc.blue("Logging:")} ${String(config.logging)}`);
7405
+ const graphBackendPart = getSelectedGraphPart(config, "backend");
7406
+ if (graphBackendPart?.ecosystem === "go") {
7407
+ if (config.goCli && config.goCli !== "none") configDisplay.push(`${pc.blue("Go CLI:")} ${String(config.goCli)}`);
7408
+ if (config.goLogging && config.goLogging !== "none") configDisplay.push(`${pc.blue("Go Logging:")} ${String(config.goLogging)}`);
7409
+ }
7410
+ if (graphBackendPart?.ecosystem === "rust") {
7411
+ if (config.rustCli && config.rustCli !== "none") configDisplay.push(`${pc.blue("Rust CLI:")} ${String(config.rustCli)}`);
7412
+ if (config.rustLibraries && config.rustLibraries.length > 0) configDisplay.push(`${pc.blue("Rust Libraries:")} ${config.rustLibraries.join(", ")}`);
7413
+ if (config.rustLogging && config.rustLogging !== "none") configDisplay.push(`${pc.blue("Rust Logging:")} ${String(config.rustLogging)}`);
7414
+ if (config.rustErrorHandling && config.rustErrorHandling !== "none") configDisplay.push(`${pc.blue("Rust Error Handling:")} ${String(config.rustErrorHandling)}`);
7415
+ if (config.rustCaching && config.rustCaching !== "none") configDisplay.push(`${pc.blue("Rust Caching:")} ${String(config.rustCaching)}`);
7416
+ }
7417
+ if (graphBackendPart?.ecosystem === "python") {
7418
+ if (config.pythonValidation && config.pythonValidation !== "none") configDisplay.push(`${pc.blue("Python Validation:")} ${String(config.pythonValidation)}`);
7419
+ if (config.pythonAi && config.pythonAi.length > 0) configDisplay.push(`${pc.blue("Python AI:")} ${config.pythonAi.join(", ")}`);
7420
+ if (config.pythonTaskQueue && config.pythonTaskQueue !== "none") configDisplay.push(`${pc.blue("Python Task Queue:")} ${String(config.pythonTaskQueue)}`);
7421
+ if (config.pythonGraphql && config.pythonGraphql !== "none") configDisplay.push(`${pc.blue("Python GraphQL:")} ${String(config.pythonGraphql)}`);
7422
+ if (config.pythonQuality && config.pythonQuality !== "none") configDisplay.push(`${pc.blue("Python Quality:")} ${String(config.pythonQuality)}`);
7423
+ }
7424
+ if (graphBackendPart?.ecosystem === "java") {
7425
+ if (config.javaBuildTool && config.javaBuildTool !== "none") configDisplay.push(`${pc.blue("Java Build Tool:")} ${String(config.javaBuildTool)}`);
7426
+ if (config.javaLibraries && config.javaLibraries.length > 0) configDisplay.push(`${pc.blue("Java Libraries:")} ${config.javaLibraries.join(", ")}`);
7427
+ if (config.javaTestingLibraries && config.javaTestingLibraries.length > 0) configDisplay.push(`${pc.blue("Java Testing Libraries:")} ${config.javaTestingLibraries.join(", ")}`);
7428
+ }
7429
+ if (graphBackendPart?.ecosystem === "elixir") {
7430
+ if (config.elixirRealtime && config.elixirRealtime !== "none") configDisplay.push(`${pc.blue("Elixir Realtime:")} ${String(config.elixirRealtime)}`);
7431
+ if (config.elixirJobs && config.elixirJobs !== "none") configDisplay.push(`${pc.blue("Elixir Jobs:")} ${String(config.elixirJobs)}`);
7432
+ if (config.elixirValidation && config.elixirValidation !== "none") configDisplay.push(`${pc.blue("Elixir Validation:")} ${String(config.elixirValidation)}`);
7433
+ if (config.elixirHttp && config.elixirHttp !== "none") configDisplay.push(`${pc.blue("Elixir HTTP:")} ${String(config.elixirHttp)}`);
7434
+ if (config.elixirJson && config.elixirJson !== "none") configDisplay.push(`${pc.blue("Elixir JSON:")} ${String(config.elixirJson)}`);
7435
+ if (config.elixirCaching && config.elixirCaching !== "none") configDisplay.push(`${pc.blue("Elixir Caching:")} ${String(config.elixirCaching)}`);
7436
+ if (config.elixirObservability && config.elixirObservability !== "none") configDisplay.push(`${pc.blue("Elixir Observability:")} ${String(config.elixirObservability)}`);
7437
+ if (config.elixirTesting && config.elixirTesting !== "none") configDisplay.push(`${pc.blue("Elixir Testing:")} ${String(config.elixirTesting)}`);
7438
+ if (config.elixirQuality && config.elixirQuality !== "none") configDisplay.push(`${pc.blue("Elixir Quality:")} ${String(config.elixirQuality)}`);
7439
+ if (config.elixirDeploy && config.elixirDeploy !== "none") configDisplay.push(`${pc.blue("Elixir Deploy:")} ${String(config.elixirDeploy)}`);
7440
+ }
7049
7441
  if (config.observability !== void 0) configDisplay.push(`${pc.blue("Observability:")} ${String(config.observability)}`);
7050
7442
  if (config.featureFlags !== void 0) configDisplay.push(`${pc.blue("Feature Flags:")} ${String(config.featureFlags)}`);
7051
7443
  if (config.analytics !== void 0) configDisplay.push(`${pc.blue("Analytics:")} ${String(config.analytics)}`);
@@ -7089,6 +7481,10 @@ function displayConfig(config) {
7089
7481
  if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
7090
7482
  if (config.webDeploy !== void 0) configDisplay.push(`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`);
7091
7483
  if (config.serverDeploy !== void 0) configDisplay.push(`${pc.blue("Server Deployment:")} ${String(config.serverDeploy)}`);
7484
+ if (config.stackParts?.length) {
7485
+ const stackParts = config.stackParts.filter((part) => part.source !== "provided").map((part) => formatStackPartSpec(part, config.stackParts ?? []));
7486
+ if (stackParts.length > 0) configDisplay.push(`${pc.blue("Stack Parts:")} ${stackParts.join(", ")}`);
7487
+ }
7092
7488
  if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
7093
7489
  return configDisplay.join("\n");
7094
7490
  }
@@ -7117,6 +7513,103 @@ function appendCommonFlags(flags, config) {
7117
7513
  if (config.versionChannel !== "stable") flags.push(`--version-channel ${config.versionChannel}`);
7118
7514
  flags.push(config.install ? "--install" : "--no-install");
7119
7515
  }
7516
+ function hasGraphPrimaryPart(config, role, ecosystem) {
7517
+ return config.stackParts?.some((part) => part.source !== "provided" && part.role === role && !part.ownerPartId && (!ecosystem || part.ecosystem === ecosystem));
7518
+ }
7519
+ function appendChangedStringFlag(flags, flag, value, defaultValue) {
7520
+ if (value !== defaultValue) flags.push(`--${flag} ${value}`);
7521
+ }
7522
+ function appendChangedArrayFlag(flags, flag, values, defaultValues) {
7523
+ if (values.length !== defaultValues.length || values.some((value, index) => value !== defaultValues[index])) flags.push(formatArrayFlag(flag, values));
7524
+ }
7525
+ function appendGraphExtraFlags(flags, config) {
7526
+ appendChangedArrayFlag(flags, "addons", config.addons, ["turborepo"]);
7527
+ appendChangedArrayFlag(flags, "examples", config.examples, []);
7528
+ appendChangedStringFlag(flags, "db-setup", config.dbSetup, "none");
7529
+ appendChangedStringFlag(flags, "web-deploy", config.webDeploy, "none");
7530
+ appendChangedStringFlag(flags, "server-deploy", config.serverDeploy, "none");
7531
+ if (hasGraphPrimaryPart(config, "frontend", "typescript")) {
7532
+ appendChangedStringFlag(flags, "css-framework", config.cssFramework, "tailwind");
7533
+ appendChangedStringFlag(flags, "ui-library", config.uiLibrary, "shadcn-ui");
7534
+ if (config.uiLibrary === "shadcn-ui") {
7535
+ appendChangedStringFlag(flags, "shadcn-base", config.shadcnBase ?? "radix", "radix");
7536
+ appendChangedStringFlag(flags, "shadcn-style", config.shadcnStyle ?? "nova", "nova");
7537
+ appendChangedStringFlag(flags, "shadcn-icon-library", config.shadcnIconLibrary ?? "lucide", "lucide");
7538
+ appendChangedStringFlag(flags, "shadcn-color-theme", config.shadcnColorTheme ?? "neutral", "neutral");
7539
+ appendChangedStringFlag(flags, "shadcn-base-color", config.shadcnBaseColor ?? "neutral", "neutral");
7540
+ appendChangedStringFlag(flags, "shadcn-font", config.shadcnFont ?? "inter", "inter");
7541
+ appendChangedStringFlag(flags, "shadcn-radius", config.shadcnRadius ?? "default", "default");
7542
+ }
7543
+ appendChangedStringFlag(flags, "state-management", config.stateManagement, "none");
7544
+ appendChangedStringFlag(flags, "forms", config.forms, "react-hook-form");
7545
+ appendChangedStringFlag(flags, "validation", config.validation, "zod");
7546
+ appendChangedStringFlag(flags, "testing", config.testing, "vitest");
7547
+ appendChangedStringFlag(flags, "animation", config.animation, "none");
7548
+ }
7549
+ if (hasGraphPrimaryPart(config, "frontend", "typescript") || hasGraphPrimaryPart(config, "backend", "typescript")) {
7550
+ appendChangedStringFlag(flags, "payments", config.payments, "none");
7551
+ appendChangedStringFlag(flags, "email", config.email, "none");
7552
+ appendChangedStringFlag(flags, "file-upload", config.fileUpload, "none");
7553
+ appendChangedStringFlag(flags, "effect", config.effect, "none");
7554
+ appendChangedStringFlag(flags, "ai", config.ai, "none");
7555
+ appendChangedStringFlag(flags, "realtime", config.realtime, "none");
7556
+ appendChangedStringFlag(flags, "job-queue", config.jobQueue, "none");
7557
+ appendChangedStringFlag(flags, "logging", config.logging, "none");
7558
+ appendChangedStringFlag(flags, "observability", config.observability, "none");
7559
+ appendChangedStringFlag(flags, "feature-flags", config.featureFlags, "none");
7560
+ appendChangedStringFlag(flags, "caching", config.caching, "none");
7561
+ appendChangedStringFlag(flags, "i18n", config.i18n, "none");
7562
+ appendChangedStringFlag(flags, "cms", config.cms, "none");
7563
+ appendChangedStringFlag(flags, "search", config.search, "none");
7564
+ appendChangedStringFlag(flags, "file-storage", config.fileStorage, "none");
7565
+ }
7566
+ if (hasGraphPrimaryPart(config, "mobile")) {
7567
+ appendChangedStringFlag(flags, "mobile-navigation", config.mobileNavigation, "expo-router");
7568
+ appendChangedStringFlag(flags, "mobile-ui", config.mobileUI, "none");
7569
+ appendChangedStringFlag(flags, "mobile-storage", config.mobileStorage, "none");
7570
+ appendChangedStringFlag(flags, "mobile-testing", config.mobileTesting, "none");
7571
+ appendChangedStringFlag(flags, "mobile-push", config.mobilePush, "none");
7572
+ appendChangedStringFlag(flags, "mobile-ota", config.mobileOTA, "none");
7573
+ appendChangedStringFlag(flags, "mobile-deep-linking", config.mobileDeepLinking, "none");
7574
+ }
7575
+ if (hasGraphPrimaryPart(config, "frontend", "rust")) appendChangedStringFlag(flags, "rust-frontend", config.rustFrontend, "none");
7576
+ if (hasGraphPrimaryPart(config, "backend", "rust")) {
7577
+ appendChangedStringFlag(flags, "rust-cli", config.rustCli, "none");
7578
+ appendChangedArrayFlag(flags, "rust-libraries", config.rustLibraries, []);
7579
+ appendChangedStringFlag(flags, "rust-logging", config.rustLogging, "tracing");
7580
+ appendChangedStringFlag(flags, "rust-error-handling", config.rustErrorHandling, "anyhow-thiserror");
7581
+ appendChangedStringFlag(flags, "rust-caching", config.rustCaching, "none");
7582
+ }
7583
+ if (hasGraphPrimaryPart(config, "backend", "python")) {
7584
+ appendChangedStringFlag(flags, "python-validation", config.pythonValidation, "none");
7585
+ appendChangedArrayFlag(flags, "python-ai", config.pythonAi, []);
7586
+ appendChangedStringFlag(flags, "python-task-queue", config.pythonTaskQueue, "none");
7587
+ appendChangedStringFlag(flags, "python-graphql", config.pythonGraphql, "none");
7588
+ appendChangedStringFlag(flags, "python-quality", config.pythonQuality, "none");
7589
+ }
7590
+ if (hasGraphPrimaryPart(config, "backend", "go")) {
7591
+ appendChangedStringFlag(flags, "go-cli", config.goCli, "none");
7592
+ appendChangedStringFlag(flags, "go-logging", config.goLogging, "none");
7593
+ }
7594
+ if (hasGraphPrimaryPart(config, "backend", "java")) {
7595
+ appendChangedStringFlag(flags, "java-build-tool", config.javaBuildTool, "maven");
7596
+ appendChangedArrayFlag(flags, "java-libraries", config.javaLibraries, []);
7597
+ appendChangedArrayFlag(flags, "java-testing-libraries", config.javaTestingLibraries, ["junit5"]);
7598
+ }
7599
+ if (hasGraphPrimaryPart(config, "backend", "elixir")) {
7600
+ appendChangedStringFlag(flags, "elixir-realtime", config.elixirRealtime, "channels");
7601
+ appendChangedStringFlag(flags, "elixir-jobs", config.elixirJobs, "none");
7602
+ appendChangedStringFlag(flags, "elixir-validation", config.elixirValidation, "ecto-changesets");
7603
+ appendChangedStringFlag(flags, "elixir-http", config.elixirHttp, "req");
7604
+ appendChangedStringFlag(flags, "elixir-json", config.elixirJson, "jason");
7605
+ if (!hasGraphPart(config, "email", "elixir")) appendChangedStringFlag(flags, "elixir-email", config.elixirEmail, "none");
7606
+ appendChangedStringFlag(flags, "elixir-caching", config.elixirCaching, "none");
7607
+ appendChangedStringFlag(flags, "elixir-observability", config.elixirObservability, "telemetry");
7608
+ appendChangedStringFlag(flags, "elixir-testing", config.elixirTesting, "ex_unit");
7609
+ appendChangedStringFlag(flags, "elixir-quality", config.elixirQuality, "credo");
7610
+ appendChangedStringFlag(flags, "elixir-deploy", config.elixirDeploy, "none");
7611
+ }
7612
+ }
7120
7613
  function appendSharedNonTypeScriptFlags(flags, config) {
7121
7614
  flags.push(`--email ${config.email}`);
7122
7615
  flags.push(`--observability ${config.observability}`);
@@ -7279,6 +7772,12 @@ function getElixirFlags(config) {
7279
7772
  }
7280
7773
  function generateReproducibleCommand(config) {
7281
7774
  let flags;
7775
+ if (config.stackParts && config.stackParts.length > 0) {
7776
+ flags = config.stackParts.filter((part) => part.source !== "provided").map((part) => `--part ${(0, types_exports.formatStackPartSpec)(part, config.stackParts ?? [])}`);
7777
+ appendGraphExtraFlags(flags, config);
7778
+ appendCommonFlags(flags, config);
7779
+ return `${getBaseCommand(config.packageManager)}${config.relativePath ? ` ${config.relativePath}` : ""} ${flags.join(" ")}`;
7780
+ }
7282
7781
  switch (config.ecosystem) {
7283
7782
  case "react-native":
7284
7783
  flags = getReactNativeFlags(config);
@@ -7306,6 +7805,98 @@ function generateReproducibleCommand(config) {
7306
7805
  return `${getBaseCommand(config.packageManager)}${config.relativePath ? ` ${config.relativePath}` : ""} ${flags.join(" ")}`;
7307
7806
  }
7308
7807
 
7808
+ //#endregion
7809
+ //#region src/utils/generated-checks.ts
7810
+ function getGraphTarget(config) {
7811
+ const backend = getPrimaryGraphPart(config, "backend");
7812
+ if (!backend || backend.ecosystem === "typescript" || backend.ecosystem === "react-native" || backend.ecosystem === "universal") return null;
7813
+ return {
7814
+ ecosystem: backend.ecosystem,
7815
+ projectDir: path.join(config.projectDir, backend.targetPath ?? "apps/server")
7816
+ };
7817
+ }
7818
+ function getSingleEcosystemTarget(config) {
7819
+ if (config.ecosystem === "typescript" || config.ecosystem === "react-native" || config.ecosystem === "java") return null;
7820
+ return {
7821
+ ecosystem: config.ecosystem,
7822
+ projectDir: config.projectDir
7823
+ };
7824
+ }
7825
+ async function runCommand(cwd, command, args) {
7826
+ await $({
7827
+ cwd,
7828
+ stdout: "inherit",
7829
+ stderr: "inherit"
7830
+ })`${command} ${args}`;
7831
+ }
7832
+ async function verifyTarget(target) {
7833
+ const s = spinner();
7834
+ const cwd = target.projectDir;
7835
+ switch (target.ecosystem) {
7836
+ case "go":
7837
+ s.start("Verifying generated Go server...");
7838
+ await runCommand(cwd, "go", ["mod", "tidy"]);
7839
+ await runCommand(cwd, "go", ["test", "./..."]);
7840
+ s.stop("Generated Go server checks passed");
7841
+ return;
7842
+ case "rust":
7843
+ s.start("Verifying generated Rust server...");
7844
+ await runCommand(cwd, "cargo", ["check"]);
7845
+ s.stop("Generated Rust server checks passed");
7846
+ return;
7847
+ case "python":
7848
+ s.start("Verifying generated Python server...");
7849
+ await runCommand(cwd, "uv", ["sync"]);
7850
+ await runCommand(cwd, "uv", [
7851
+ "run",
7852
+ "ruff",
7853
+ "check",
7854
+ "."
7855
+ ]);
7856
+ s.stop("Generated Python server checks passed");
7857
+ return;
7858
+ case "elixir":
7859
+ if (!await commandExists("mix")) {
7860
+ log.warn(pc.yellow("Skipping Elixir verification because mix is not on PATH"));
7861
+ return;
7862
+ }
7863
+ s.start("Verifying generated Elixir server...");
7864
+ await runCommand(cwd, "mix", ["deps.get"]);
7865
+ await runCommand(cwd, "mix", ["compile"]);
7866
+ s.stop("Generated Elixir server checks passed");
7867
+ return;
7868
+ default: log.warn(pc.yellow(`No generated checks are configured for ${target.ecosystem}`));
7869
+ }
7870
+ }
7871
+ async function runGeneratedChecks(config) {
7872
+ const target = getGraphTarget(config) ?? getSingleEcosystemTarget(config);
7873
+ if (!target) {
7874
+ log.warn(pc.yellow("No generated checks are configured for this stack"));
7875
+ return;
7876
+ }
7877
+ await verifyTarget(target);
7878
+ }
7879
+
7880
+ //#endregion
7881
+ //#region src/utils/preflight-display.ts
7882
+ function displayPreflightWarnings({ warnings }) {
7883
+ if (warnings.length === 0) return;
7884
+ const count = warnings.length;
7885
+ const lines = [pc.bold(pc.yellow(`${count} feature${count > 1 ? "s" : ""} will not generate templates:`)), ""];
7886
+ warnings.forEach((w, i) => {
7887
+ const selected = Array.isArray(w.selectedValue) ? w.selectedValue.join(", ") : w.selectedValue;
7888
+ lines.push(` ${pc.yellow(`${i + 1}.`)} ${pc.bold(w.featureDisplayName)} ${pc.dim(`(${selected})`)}`);
7889
+ lines.push(` ${w.reason}`);
7890
+ w.suggestions.forEach((s) => lines.push(` ${pc.green("•")} ${s}`));
7891
+ if (i < count - 1) lines.push("");
7892
+ });
7893
+ consola.box({
7894
+ title: pc.yellow("Pre-flight Check"),
7895
+ message: lines.join("\n"),
7896
+ style: { borderColor: "yellow" }
7897
+ });
7898
+ }
7899
+
7309
7900
  //#endregion
7310
7901
  //#region src/utils/project-directory.ts
7311
7902
  async function handleDirectoryConflict(currentPathInput) {
@@ -7656,6 +8247,10 @@ function validateDatabaseOrmAuth(cfg, flags) {
7656
8247
  const db = cfg.database;
7657
8248
  const orm = cfg.orm;
7658
8249
  const has = (k) => flags ? flags.has(k) : true;
8250
+ const hasGraphOrm = cfg.stackParts?.some((part) => part.role === "orm" && part.source !== "provided");
8251
+ const ecosystemOrm = getEcosystemOrm(cfg);
8252
+ const hasEcosystemOrm = ecosystemOrm !== void 0 && ecosystemOrm !== "none";
8253
+ const isNonTypeScriptSqliteDefault = cfg.ecosystem !== void 0 && cfg.ecosystem !== "typescript" && cfg.ecosystem !== "react-native" && db === "sqlite" && !hasEcosystemOrm;
7659
8254
  if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") incompatibilityError({
7660
8255
  message: "Mongoose ORM requires MongoDB database.",
7661
8256
  provided: {
@@ -7712,7 +8307,7 @@ function validateDatabaseOrmAuth(cfg, flags) {
7712
8307
  },
7713
8308
  suggestions: ["Use --orm mongoose", "Use --orm prisma"]
7714
8309
  });
7715
- if (has("database") && has("orm") && db && db !== "none" && db !== "edgedb" && db !== "redis" && orm === "none") missingRequirementError({
8310
+ if (has("database") && has("orm") && db && db !== "none" && db !== "edgedb" && db !== "redis" && orm === "none" && !hasGraphOrm && !hasEcosystemOrm && !isNonTypeScriptSqliteDefault) missingRequirementError({
7716
8311
  message: "Database selection requires an ORM.",
7717
8312
  provided: {
7718
8313
  database: db,
@@ -7754,6 +8349,26 @@ function validateDatabaseOrmAuth(cfg, flags) {
7754
8349
  ]
7755
8350
  });
7756
8351
  }
8352
+ function getEcosystemOrm(cfg) {
8353
+ switch (cfg.ecosystem) {
8354
+ case "rust": return cfg.rustOrm;
8355
+ case "python": return cfg.pythonOrm;
8356
+ case "go": return cfg.goOrm;
8357
+ case "java": return cfg.javaOrm;
8358
+ case "elixir": return cfg.elixirOrm;
8359
+ default: return;
8360
+ }
8361
+ }
8362
+ function getEcosystemBackend(cfg) {
8363
+ switch (cfg.ecosystem) {
8364
+ case "rust": return cfg.rustWebFramework && cfg.rustWebFramework !== "none" ? cfg.rustWebFramework : void 0;
8365
+ case "python": return cfg.pythonWebFramework && cfg.pythonWebFramework !== "none" ? cfg.pythonWebFramework : void 0;
8366
+ case "go": return cfg.goWebFramework && cfg.goWebFramework !== "none" ? cfg.goWebFramework : void 0;
8367
+ case "java": return cfg.javaWebFramework && cfg.javaWebFramework !== "none" ? cfg.javaWebFramework : void 0;
8368
+ case "elixir": return cfg.elixirWebFramework && cfg.elixirWebFramework !== "none" ? cfg.elixirWebFramework : void 0;
8369
+ default: return;
8370
+ }
8371
+ }
7757
8372
  function validateDatabaseSetup(config, providedFlags) {
7758
8373
  const { dbSetup, database, runtime } = config;
7759
8374
  if (providedFlags.has("dbSetup") && providedFlags.has("database") && dbSetup && dbSetup !== "none" && database === "none") exitWithError("Database setup requires a database. Please choose a database or set '--db-setup none'.");
@@ -7879,7 +8494,9 @@ function validateConvexConstraints(config, providedFlags) {
7879
8494
  }
7880
8495
  function validateBackendNoneConstraints(config, providedFlags) {
7881
8496
  const { backend } = config;
7882
- if (backend !== "none") return;
8497
+ const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
8498
+ const hasEcosystemBackend = getEcosystemBackend(config) !== void 0;
8499
+ if (backend !== "none" || hasGraphBackend || hasEcosystemBackend) return;
7883
8500
  const has = (k) => providedFlags.has(k);
7884
8501
  if (has("runtime") && config.runtime !== "none") exitWithError("Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
7885
8502
  if (has("database") && config.database !== "none") exitWithError("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
@@ -8223,6 +8840,10 @@ function validatePythonApiConstraints(config) {
8223
8840
  });
8224
8841
  }
8225
8842
  function validateFullConfig(config, providedFlags, options) {
8843
+ if (config.stackParts && !options.yolo) {
8844
+ const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
8845
+ if (graphValidation.issues.length > 0) exitWithError(graphValidation.issues.map((issue) => issue.message).join("\n"));
8846
+ }
8226
8847
  validateEcosystemAuthCompatibility(config, providedFlags);
8227
8848
  validateDatabaseOrmAuth(config, providedFlags);
8228
8849
  validateDatabaseSetup(config, providedFlags);
@@ -8241,7 +8862,8 @@ function validateFullConfig(config, providedFlags, options) {
8241
8862
  validateSearchConstraints(config);
8242
8863
  validateJavaConstraints(config, providedFlags);
8243
8864
  validateElixirConstraints(config);
8244
- validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
8865
+ const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
8866
+ if (!(providedFlags.has("serverDeploy") && !options.yes && !options.part?.length && options.ecosystem === void 0 && options.backend === void 0 && config.stackParts === void 0)) validateServerDeployRequiresBackend(config.serverDeploy, config.backend, Boolean(hasGraphBackend));
8245
8867
  validateSelfBackendCompatibility(providedFlags, options, config);
8246
8868
  validateWorkersCompatibility(providedFlags, options, config);
8247
8869
  if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'alchemy' for --server-deploy.");
@@ -8272,6 +8894,10 @@ function validateFullConfig(config, providedFlags, options) {
8272
8894
  }
8273
8895
  function validateConfigForProgrammaticUse(config) {
8274
8896
  try {
8897
+ if (config.stackParts) {
8898
+ const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
8899
+ if (graphValidation.issues.length > 0) throw new Error(graphValidation.issues.map((issue) => issue.message).join("\n"));
8900
+ }
8275
8901
  validateEcosystemAuthCompatibility(config);
8276
8902
  validateDatabaseOrmAuth(config);
8277
8903
  if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
@@ -8363,7 +8989,14 @@ function processAndValidateFlags(options, providedFlags, projectName) {
8363
8989
  return config;
8364
8990
  }
8365
8991
  function processProvidedFlagsWithoutValidation(options, projectName) {
8366
- if (!options.yolo) validateYesFlagCombination(options, getProvidedFlags(options));
8992
+ if (!options.yolo) {
8993
+ validateYesFlagCombination(options, getProvidedFlags(options));
8994
+ try {
8995
+ validateArrayOptions(options);
8996
+ } catch (error) {
8997
+ exitWithError(error instanceof Error ? error.message : String(error));
8998
+ }
8999
+ }
8367
9000
  const config = processFlags(options, projectName);
8368
9001
  const validatedProjectName = extractAndValidateProjectName(projectName, options.projectDirectory, true);
8369
9002
  if (validatedProjectName) config.projectName = validatedProjectName;
@@ -8375,26 +9008,6 @@ function validateConfigCompatibility(config, providedFlags, options) {
8375
9008
  else validateConfigForProgrammaticUse(config);
8376
9009
  }
8377
9010
 
8378
- //#endregion
8379
- //#region src/utils/preflight-display.ts
8380
- function displayPreflightWarnings({ warnings }) {
8381
- if (warnings.length === 0) return;
8382
- const count = warnings.length;
8383
- const lines = [pc.bold(pc.yellow(`${count} feature${count > 1 ? "s" : ""} will not generate templates:`)), ""];
8384
- warnings.forEach((w, i) => {
8385
- const selected = Array.isArray(w.selectedValue) ? w.selectedValue.join(", ") : w.selectedValue;
8386
- lines.push(` ${pc.yellow(`${i + 1}.`)} ${pc.bold(w.featureDisplayName)} ${pc.dim(`(${selected})`)}`);
8387
- lines.push(` ${w.reason}`);
8388
- w.suggestions.forEach((s) => lines.push(` ${pc.green("•")} ${s}`));
8389
- if (i < count - 1) lines.push("");
8390
- });
8391
- consola.box({
8392
- title: pc.yellow("Pre-flight Check"),
8393
- message: lines.join("\n"),
8394
- style: { borderColor: "yellow" }
8395
- });
8396
- }
8397
-
8398
9011
  //#endregion
8399
9012
  //#region src/utils/file-formatter.ts
8400
9013
  const formatOptions = {
@@ -9666,7 +10279,9 @@ async function displayPostInstallInstructions(config) {
9666
10279
  const hasHusky = addons?.includes("husky");
9667
10280
  const hasLefthook = addons?.includes("lefthook");
9668
10281
  const hasGitHooksOrLinting = addons?.includes("husky") || addons?.includes("biome") || addons?.includes("lefthook") || addons?.includes("oxlint");
9669
- const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
10282
+ const graphOrmPart = getGraphPart(config, "orm");
10283
+ const databaseInstructions = !isConvex && database !== "none" && !graphOrmPart ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
10284
+ const graphDatabaseInstructions = !isConvex && database !== "none" && graphOrmPart ? getGraphDatabaseInstructions(database, graphOrmPart.ecosystem, graphOrmPart.toolId) : "";
9670
10285
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
9671
10286
  const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : "";
9672
10287
  const lefthookInstructions = hasLefthook ? getLefthookInstructions(packageManager) : "";
@@ -9680,14 +10295,17 @@ async function displayPostInstallInstructions(config) {
9680
10295
  const paymentSetupInstructions = getPaymentSetupInstructions(config.payments, backend);
9681
10296
  const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
9682
10297
  const vercelDeployInstructions = getVercelDeployInstructions(webDeploy, serverDeploy, backend);
10298
+ const graphBackendDeployInstructions = getGraphBackendDeployInstructions(config);
9683
10299
  const hasWeb = frontend?.some((f) => WEB_FRAMEWORKS.includes(f));
9684
10300
  const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
9685
10301
  const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
9686
- const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
10302
+ const noOrmWarning = !isConvex && database !== "none" && orm === "none" && !hasGraphPart(config, "orm") ? getNoOrmWarning() : "";
9687
10303
  const hasFresh = frontend?.includes("fresh");
9688
10304
  const webPort = String(getLocalWebDevPort(frontend ?? []));
9689
10305
  const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager) : "";
9690
- let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
10306
+ const graphSummary = getGraphSummary(config);
10307
+ let output = graphSummary ? `${pc.bold("Generated:")} ${graphSummary}\n\n` : "";
10308
+ output += `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
9691
10309
  let stepCounter = 2;
9692
10310
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
9693
10311
  if (hasFresh) output += `${pc.yellow("NOTE:")} Fresh projects require ${pc.white("deno")} on your PATH.\n Install: ${pc.underline("https://docs.deno.com/runtime/getting_started/installation/")}\n`;
@@ -9704,13 +10322,14 @@ async function displayPostInstallInstructions(config) {
9704
10322
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
9705
10323
  }
9706
10324
  }
9707
- const hasStandaloneBackend = backend !== "none";
10325
+ const graphBackendUrl = getGraphBackendUrl(config);
10326
+ const hasStandaloneBackend = backend !== "none" || Boolean(graphBackendUrl);
9708
10327
  if (hasWeb || hasStandaloneBackend || addons?.includes("starlight") || addons?.includes("fumadocs")) {
9709
10328
  output += `${pc.bold("Your project will be available at:")}\n`;
9710
10329
  if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
9711
10330
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
9712
10331
  if (!isConvex && !isBackendSelf && hasStandaloneBackend) {
9713
- output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
10332
+ output += `${pc.cyan("•")} Backend API: ${graphBackendUrl ?? "http://localhost:3000"}\n`;
9714
10333
  if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
9715
10334
  }
9716
10335
  if (isBackendSelf && api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/api/rpc/api-reference\n`;
@@ -9719,6 +10338,7 @@ async function displayPostInstallInstructions(config) {
9719
10338
  }
9720
10339
  if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
9721
10340
  if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
10341
+ if (graphDatabaseInstructions) output += `\n${graphDatabaseInstructions.trim()}\n`;
9722
10342
  if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
9723
10343
  if (huskyInstructions) output += `\n${huskyInstructions.trim()}\n`;
9724
10344
  if (lefthookInstructions) output += `\n${lefthookInstructions.trim()}\n`;
@@ -9726,6 +10346,7 @@ async function displayPostInstallInstructions(config) {
9726
10346
  if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
9727
10347
  if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
9728
10348
  if (vercelDeployInstructions) output += `\n${vercelDeployInstructions.trim()}\n`;
10349
+ if (graphBackendDeployInstructions) output += `\n${graphBackendDeployInstructions.trim()}\n`;
9729
10350
  if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
9730
10351
  if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
9731
10352
  if (authSetupInstructions) output += `\n${authSetupInstructions.trim()}\n`;
@@ -9817,6 +10438,9 @@ function getStarlightInstructions(runCmd) {
9817
10438
  function getNoOrmWarning() {
9818
10439
  return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
9819
10440
  }
10441
+ function getGraphDatabaseInstructions(database, ecosystem, ormTool) {
10442
+ return `${pc.bold("Database setup:")}\n${pc.cyan("•")} Database: ${database}\n${pc.cyan("•")} ORM: ${ecosystem}:${ormTool}\n${pc.cyan("•")} Configure ${pc.white("DATABASE_URL")} in ${pc.white("apps/server/.env")}`;
10443
+ }
9820
10444
  function getBunWebNativeWarning() {
9821
10445
  return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
9822
10446
  }
@@ -10075,14 +10699,14 @@ function displayJavaInstructions(config) {
10075
10699
  }
10076
10700
  const effectiveJavaTestingLibraries = javaBuildTool === "none" ? [] : javaTestingLibraries.filter((library) => library !== "none");
10077
10701
  const buildToolCommand = javaBuildTool === "none" ? null : javaBuildTool === "gradle" ? process.platform === "win32" ? "gradlew.bat" : "./gradlew" : process.platform === "win32" ? "mvnw.cmd" : "./mvnw";
10078
- const runCommand = buildToolCommand ? isSpringBoot ? javaBuildTool === "gradle" ? `${buildToolCommand} bootRun` : `${buildToolCommand} spring-boot:run` : isQuarkus ? javaBuildTool === "gradle" ? `${buildToolCommand} quarkusDev` : `${buildToolCommand} quarkus:dev` : javaBuildTool === "gradle" ? `${buildToolCommand} run` : `${buildToolCommand} exec:java` : null;
10702
+ const runCommand$1 = buildToolCommand ? isSpringBoot ? javaBuildTool === "gradle" ? `${buildToolCommand} bootRun` : `${buildToolCommand} spring-boot:run` : isQuarkus ? javaBuildTool === "gradle" ? `${buildToolCommand} quarkusDev` : `${buildToolCommand} quarkus:dev` : javaBuildTool === "gradle" ? `${buildToolCommand} run` : `${buildToolCommand} exec:java` : null;
10079
10703
  const packageCommand = buildToolCommand ? javaBuildTool === "gradle" ? `${buildToolCommand} build` : `${buildToolCommand} package` : null;
10080
10704
  const sourceCompileCommand = buildToolCommand ? null : `javac -d out ${getJavaMainSourcePath(projectName)}`;
10081
10705
  const sourceRunCommand = buildToolCommand ? null : `java -cp out ${getJavaMainClass(projectName)}`;
10082
10706
  let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
10083
10707
  let stepCounter = 2;
10084
10708
  if (!depsInstalled && buildToolCommand && effectiveJavaTestingLibraries.length > 0) output += `${pc.cyan(`${stepCounter++}.`)} ${buildToolCommand} test\n`;
10085
- if (runCommand) output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand}\n`;
10709
+ if (runCommand$1) output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand$1}\n`;
10086
10710
  else if (sourceCompileCommand && sourceRunCommand) {
10087
10711
  output += `${pc.cyan(`${stepCounter++}.`)} ${sourceCompileCommand}\n`;
10088
10712
  output += `${pc.cyan(`${stepCounter++}.`)} ${sourceRunCommand}\n`;
@@ -10130,9 +10754,9 @@ function displayJavaInstructions(config) {
10130
10754
  output += `${pc.cyan("•")} Testing: ${testingList}\n`;
10131
10755
  }
10132
10756
  output += `\n${pc.bold("Common Java commands:")}\n`;
10133
- if (buildToolCommand && runCommand && packageCommand) {
10757
+ if (buildToolCommand && runCommand$1 && packageCommand) {
10134
10758
  if (effectiveJavaTestingLibraries.length > 0) output += `${pc.cyan("•")} Test: ${buildToolCommand} test\n`;
10135
- output += `${pc.cyan("•")} Run: ${runCommand}\n`;
10759
+ output += `${pc.cyan("•")} Run: ${runCommand$1}\n`;
10136
10760
  output += `${pc.cyan("•")} Package: ${packageCommand}\n`;
10137
10761
  } else if (sourceCompileCommand && sourceRunCommand) {
10138
10762
  output += `${pc.cyan("•")} Compile: ${sourceCompileCommand}\n`;
@@ -10150,14 +10774,14 @@ function displayJavaInstructions(config) {
10150
10774
  function displayPythonInstructions(config) {
10151
10775
  const { relativePath, depsInstalled, pythonWebFramework, pythonOrm, pythonValidation, pythonAi, pythonApi, pythonTaskQueue, pythonQuality } = config;
10152
10776
  const cdCmd = `cd ${relativePath}`;
10153
- let runCommand = "uv run uvicorn app.main:app --reload";
10154
- if (pythonWebFramework === "django") runCommand = "uv run python manage.py runserver";
10155
- else if (pythonWebFramework === "flask") runCommand = "uv run flask --app app.main run --reload";
10156
- else if (pythonWebFramework === "litestar") runCommand = "litestar --app src.app.main:app run --reload --port 3001";
10777
+ let runCommand$1 = "uv run uvicorn app.main:app --reload";
10778
+ if (pythonWebFramework === "django") runCommand$1 = "uv run python manage.py runserver";
10779
+ else if (pythonWebFramework === "flask") runCommand$1 = "uv run flask --app app.main run --reload";
10780
+ else if (pythonWebFramework === "litestar") runCommand$1 = "litestar --app src.app.main:app run --reload --port 3001";
10157
10781
  let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
10158
10782
  let stepCounter = 2;
10159
10783
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} uv sync\n`;
10160
- output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand}\n`;
10784
+ output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand$1}\n`;
10161
10785
  output += `\n${pc.bold("Your Python project includes:")}\n`;
10162
10786
  if (pythonWebFramework && pythonWebFramework !== "none") output += `${pc.cyan("•")} Web Framework: ${{
10163
10787
  fastapi: "FastAPI",
@@ -10195,7 +10819,7 @@ function displayPythonInstructions(config) {
10195
10819
  }[pythonQuality] || pythonQuality}\n`;
10196
10820
  output += `\n${pc.bold("Common Python commands:")}\n`;
10197
10821
  output += `${pc.cyan("•")} Install: uv sync\n`;
10198
- output += `${pc.cyan("•")} Run: ${runCommand}\n`;
10822
+ output += `${pc.cyan("•")} Run: ${runCommand$1}\n`;
10199
10823
  output += `${pc.cyan("•")} Test: uv run pytest\n`;
10200
10824
  if (pythonQuality === "ruff") {
10201
10825
  output += `${pc.cyan("•")} Format: uv run ruff format .\n`;
@@ -10357,7 +10981,7 @@ function getYesBaseConfig(flagConfig) {
10357
10981
  };
10358
10982
  }
10359
10983
  function shouldPromptForVersionChannel(input) {
10360
- if (input.yes || input.versionChannel !== void 0 || isSilent()) return false;
10984
+ if (input.yes || input.part?.length || input.versionChannel !== void 0 || isSilent()) return false;
10361
10985
  return canPromptInteractively();
10362
10986
  }
10363
10987
  async function createProjectHandler(input, options = {}) {
@@ -10542,7 +11166,7 @@ async function createProjectHandler(input, options = {}) {
10542
11166
  }
10543
11167
  }
10544
11168
  let config;
10545
- if (cliInput.yes) {
11169
+ if (cliInput.yes || cliInput.part?.length) {
10546
11170
  const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
10547
11171
  config = {
10548
11172
  ...getYesBaseConfig(flagConfig),
@@ -10571,6 +11195,7 @@ async function createProjectHandler(input, options = {}) {
10571
11195
  ...await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, currentPathInput),
10572
11196
  versionChannel
10573
11197
  };
11198
+ validateConfigCompatibility(config, providedFlags, cliInput);
10574
11199
  }
10575
11200
  const preflight = validatePreflightConfig(config);
10576
11201
  if (preflight.hasWarnings && !isSilent()) displayPreflightWarnings(preflight);
@@ -10617,6 +11242,7 @@ async function createProjectHandler(input, options = {}) {
10617
11242
  };
10618
11243
  }
10619
11244
  await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
11245
+ if (cliInput.verify ?? input.verify) await runGeneratedChecks(config);
10620
11246
  const reproducibleCommand = generateReproducibleCommand(config);
10621
11247
  if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
10622
11248
  await trackProjectCreation(config, input.disableAnalytics);
@@ -10946,113 +11572,116 @@ async function createVirtual(options) {
10946
11572
  const isReactNative = ecosystem === "react-native";
10947
11573
  const frontend = options.frontend || (isReactNative ? ["native-bare"] : ["tanstack-router"]);
10948
11574
  const hasNativeFrontend = frontend.some((item) => item === "native-bare" || item === "native-uniwind" || item === "native-unistyles");
11575
+ const config = {
11576
+ ecosystem,
11577
+ projectName: options.projectName || "my-project",
11578
+ projectDir: "/virtual",
11579
+ relativePath: "./virtual",
11580
+ database: options.database || "none",
11581
+ orm: options.orm || "none",
11582
+ backend: options.backend || (isReactNative ? "none" : "hono"),
11583
+ runtime: options.runtime || (isReactNative ? "none" : "bun"),
11584
+ frontend,
11585
+ addons: options.addons || [],
11586
+ examples: options.examples || [],
11587
+ auth: options.auth || "none",
11588
+ payments: options.payments || "none",
11589
+ email: options.email || "none",
11590
+ fileUpload: options.fileUpload || "none",
11591
+ effect: options.effect || "none",
11592
+ git: options.git ?? false,
11593
+ packageManager: options.packageManager || "bun",
11594
+ versionChannel: options.versionChannel || "stable",
11595
+ install: false,
11596
+ dbSetup: options.dbSetup || "none",
11597
+ api: options.api || (isReactNative ? "none" : "trpc"),
11598
+ webDeploy: options.webDeploy || "none",
11599
+ serverDeploy: options.serverDeploy || "none",
11600
+ astroIntegration: options.astroIntegration || "none",
11601
+ cssFramework: options.cssFramework || (isReactNative ? "none" : "tailwind"),
11602
+ uiLibrary: options.uiLibrary || (isReactNative ? "none" : "shadcn-ui"),
11603
+ shadcnBase: options.shadcnBase ?? "radix",
11604
+ shadcnStyle: options.shadcnStyle ?? "nova",
11605
+ shadcnIconLibrary: options.shadcnIconLibrary ?? "lucide",
11606
+ shadcnColorTheme: options.shadcnColorTheme ?? "neutral",
11607
+ shadcnBaseColor: options.shadcnBaseColor ?? "neutral",
11608
+ shadcnFont: options.shadcnFont ?? "inter",
11609
+ shadcnRadius: options.shadcnRadius ?? "default",
11610
+ ai: options.ai || "none",
11611
+ stateManagement: options.stateManagement || "none",
11612
+ forms: options.forms || (isReactNative ? "none" : "react-hook-form"),
11613
+ testing: options.testing || (isReactNative ? "none" : "vitest"),
11614
+ validation: options.validation || "zod",
11615
+ realtime: options.realtime || "none",
11616
+ jobQueue: options.jobQueue || "none",
11617
+ animation: options.animation || "none",
11618
+ logging: options.logging || "none",
11619
+ observability: options.observability || "none",
11620
+ featureFlags: options.featureFlags || "none",
11621
+ analytics: options.analytics || "none",
11622
+ mobileNavigation: options.mobileNavigation || (hasNativeFrontend ? "expo-router" : "none"),
11623
+ mobileUI: options.mobileUI || "none",
11624
+ mobileStorage: options.mobileStorage || "none",
11625
+ mobileTesting: options.mobileTesting || "none",
11626
+ mobilePush: options.mobilePush || "none",
11627
+ mobileOTA: options.mobileOTA || "none",
11628
+ mobileDeepLinking: options.mobileDeepLinking || (hasNativeFrontend ? "expo-linking" : "none"),
11629
+ cms: options.cms || "none",
11630
+ caching: options.caching || "none",
11631
+ i18n: options.i18n || "none",
11632
+ search: options.search || "none",
11633
+ fileStorage: options.fileStorage || "none",
11634
+ rustWebFramework: options.rustWebFramework || "none",
11635
+ rustFrontend: options.rustFrontend || "none",
11636
+ rustOrm: options.rustOrm || "none",
11637
+ rustApi: options.rustApi || "none",
11638
+ rustCli: options.rustCli || "none",
11639
+ rustLibraries: options.rustLibraries || [],
11640
+ rustLogging: options.rustLogging || (options.ecosystem === "rust" ? "tracing" : "none"),
11641
+ rustErrorHandling: options.rustErrorHandling || (options.ecosystem === "rust" ? "anyhow-thiserror" : "none"),
11642
+ rustCaching: options.rustCaching || "none",
11643
+ rustAuth: options.rustAuth || "none",
11644
+ pythonWebFramework: options.pythonWebFramework || "none",
11645
+ pythonOrm: options.pythonOrm || "none",
11646
+ pythonValidation: options.pythonValidation || "none",
11647
+ pythonAi: options.pythonAi || [],
11648
+ pythonAuth: options.pythonAuth || "none",
11649
+ pythonApi: options.pythonApi || "none",
11650
+ pythonTaskQueue: options.pythonTaskQueue || "none",
11651
+ pythonGraphql: options.pythonGraphql || "none",
11652
+ pythonQuality: options.pythonQuality || "none",
11653
+ goWebFramework: options.goWebFramework || "none",
11654
+ goOrm: options.goOrm || "none",
11655
+ goApi: options.goApi || "none",
11656
+ goCli: options.goCli || "none",
11657
+ goLogging: options.goLogging || "none",
11658
+ goAuth: options.goAuth || "none",
11659
+ javaWebFramework: options.javaWebFramework || (options.ecosystem === "java" ? "spring-boot" : "none"),
11660
+ javaBuildTool: options.javaBuildTool || (options.ecosystem === "java" ? "maven" : "none"),
11661
+ javaOrm: options.javaOrm || "none",
11662
+ javaAuth: options.javaAuth || "none",
11663
+ javaLibraries: options.javaLibraries || [],
11664
+ javaTestingLibraries: options.javaTestingLibraries || (options.ecosystem === "java" ? ["junit5"] : []),
11665
+ elixirWebFramework: options.elixirWebFramework || (options.ecosystem === "elixir" ? "phoenix" : "none"),
11666
+ elixirOrm: options.elixirOrm || (options.ecosystem === "elixir" ? "ecto-sql" : "none"),
11667
+ elixirAuth: options.elixirAuth || "none",
11668
+ elixirApi: options.elixirApi || (options.ecosystem === "elixir" ? "rest" : "none"),
11669
+ elixirRealtime: options.elixirRealtime || (options.ecosystem === "elixir" ? "channels" : "none"),
11670
+ elixirJobs: options.elixirJobs || "none",
11671
+ elixirValidation: options.elixirValidation || (options.ecosystem === "elixir" ? "ecto-changesets" : "none"),
11672
+ elixirHttp: options.elixirHttp || (options.ecosystem === "elixir" ? "req" : "none"),
11673
+ elixirJson: options.elixirJson || (options.ecosystem === "elixir" ? "jason" : "none"),
11674
+ elixirEmail: options.elixirEmail || "none",
11675
+ elixirCaching: options.elixirCaching || "none",
11676
+ elixirObservability: options.elixirObservability || (options.ecosystem === "elixir" ? "telemetry" : "none"),
11677
+ elixirTesting: options.elixirTesting || (options.ecosystem === "elixir" ? "ex_unit" : "none"),
11678
+ elixirQuality: options.elixirQuality || (options.ecosystem === "elixir" ? "credo" : "none"),
11679
+ elixirDeploy: options.elixirDeploy || "none",
11680
+ aiDocs: options.aiDocs || ["claude-md"]
11681
+ };
11682
+ if (options.stackParts) config.stackParts = options.stackParts;
10949
11683
  const result = await generateVirtualProject$1({
10950
- config: {
10951
- ecosystem,
10952
- projectName: options.projectName || "my-project",
10953
- projectDir: "/virtual",
10954
- relativePath: "./virtual",
10955
- database: options.database || "none",
10956
- orm: options.orm || "none",
10957
- backend: options.backend || (isReactNative ? "none" : "hono"),
10958
- runtime: options.runtime || (isReactNative ? "none" : "bun"),
10959
- frontend,
10960
- addons: options.addons || [],
10961
- examples: options.examples || [],
10962
- auth: options.auth || "none",
10963
- payments: options.payments || "none",
10964
- email: options.email || "none",
10965
- fileUpload: options.fileUpload || "none",
10966
- effect: options.effect || "none",
10967
- git: options.git ?? false,
10968
- packageManager: options.packageManager || "bun",
10969
- versionChannel: options.versionChannel || "stable",
10970
- install: false,
10971
- dbSetup: options.dbSetup || "none",
10972
- api: options.api || (isReactNative ? "none" : "trpc"),
10973
- webDeploy: options.webDeploy || "none",
10974
- serverDeploy: options.serverDeploy || "none",
10975
- cssFramework: options.cssFramework || (isReactNative ? "none" : "tailwind"),
10976
- uiLibrary: options.uiLibrary || (isReactNative ? "none" : "shadcn-ui"),
10977
- shadcnBase: options.shadcnBase ?? "radix",
10978
- shadcnStyle: options.shadcnStyle ?? "nova",
10979
- shadcnIconLibrary: options.shadcnIconLibrary ?? "lucide",
10980
- shadcnColorTheme: options.shadcnColorTheme ?? "neutral",
10981
- shadcnBaseColor: options.shadcnBaseColor ?? "neutral",
10982
- shadcnFont: options.shadcnFont ?? "inter",
10983
- shadcnRadius: options.shadcnRadius ?? "default",
10984
- ai: options.ai || "none",
10985
- stateManagement: options.stateManagement || "none",
10986
- forms: options.forms || (isReactNative ? "none" : "react-hook-form"),
10987
- testing: options.testing || (isReactNative ? "none" : "vitest"),
10988
- validation: options.validation || "zod",
10989
- realtime: options.realtime || "none",
10990
- jobQueue: options.jobQueue || "none",
10991
- animation: options.animation || "none",
10992
- logging: options.logging || "none",
10993
- observability: options.observability || "none",
10994
- featureFlags: options.featureFlags || "none",
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"),
11003
- cms: options.cms || "none",
11004
- caching: options.caching || "none",
11005
- i18n: options.i18n || "none",
11006
- search: options.search || "none",
11007
- fileStorage: options.fileStorage || "none",
11008
- rustWebFramework: options.rustWebFramework || "none",
11009
- rustFrontend: options.rustFrontend || "none",
11010
- rustOrm: options.rustOrm || "none",
11011
- rustApi: options.rustApi || "none",
11012
- rustCli: options.rustCli || "none",
11013
- rustLibraries: options.rustLibraries || [],
11014
- rustLogging: options.rustLogging || (options.ecosystem === "rust" ? "tracing" : "none"),
11015
- rustErrorHandling: options.rustErrorHandling || (options.ecosystem === "rust" ? "anyhow-thiserror" : "none"),
11016
- rustCaching: options.rustCaching || "none",
11017
- rustAuth: options.rustAuth || "none",
11018
- pythonWebFramework: options.pythonWebFramework || "none",
11019
- pythonOrm: options.pythonOrm || "none",
11020
- pythonValidation: options.pythonValidation || "none",
11021
- pythonAi: options.pythonAi || [],
11022
- pythonAuth: options.pythonAuth || "none",
11023
- pythonApi: options.pythonApi || "none",
11024
- pythonTaskQueue: options.pythonTaskQueue || "none",
11025
- pythonGraphql: options.pythonGraphql || "none",
11026
- pythonQuality: options.pythonQuality || "none",
11027
- goWebFramework: options.goWebFramework || "none",
11028
- goOrm: options.goOrm || "none",
11029
- goApi: options.goApi || "none",
11030
- goCli: options.goCli || "none",
11031
- goLogging: options.goLogging || "none",
11032
- goAuth: options.goAuth || "none",
11033
- javaWebFramework: options.javaWebFramework || (options.ecosystem === "java" ? "spring-boot" : "none"),
11034
- javaBuildTool: options.javaBuildTool || (options.ecosystem === "java" ? "maven" : "none"),
11035
- javaOrm: options.javaOrm || "none",
11036
- javaAuth: options.javaAuth || "none",
11037
- javaLibraries: options.javaLibraries || [],
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",
11054
- aiDocs: options.aiDocs || ["claude-md"]
11055
- },
11684
+ config,
11056
11685
  templates: EMBEDDED_TEMPLATES$1
11057
11686
  });
11058
11687
  if (result.success && result.tree) return {