create-better-fullstack 1.8.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,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-CSvxsFML.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-DqVFXnDv.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);
@@ -755,9 +750,10 @@ function validateAIFrontendCompatibility(ai, frontends = []) {
755
750
  */
756
751
  function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astroIntegration) {
757
752
  if (!uiLibrary || uiLibrary === "none") return;
753
+ const { web } = splitFrontends$1(frontends);
754
+ if (web.length === 0) return;
758
755
  const compatible = getCompatibleUILibraries(frontends, astroIntegration);
759
756
  if (!compatible.includes(uiLibrary)) {
760
- const { web } = splitFrontends$1(frontends);
761
757
  const isAstroNonReact = web.includes("astro") && astroIntegration !== "react";
762
758
  const supportsAstroReact = getCompatibleUILibraries(["astro"], "react").includes(uiLibrary);
763
759
  if (isAstroNonReact && supportsAstroReact) {
@@ -2935,42 +2931,48 @@ const getElixirDeployChoice = (value) => makeChoice("Select Elixir deploy target
2935
2931
 
2936
2932
  //#endregion
2937
2933
  //#region src/prompts/ecosystem.ts
2934
+ const ECOSYSTEM_PROMPT_OPTIONS = [
2935
+ {
2936
+ value: "typescript",
2937
+ label: "TypeScript",
2938
+ hint: "Full-stack TypeScript web with React, Vue, Svelte, and more"
2939
+ },
2940
+ {
2941
+ value: "react-native",
2942
+ label: "React Native",
2943
+ hint: "Expo and React Native mobile apps with native integrations"
2944
+ },
2945
+ {
2946
+ value: "rust",
2947
+ label: "Rust",
2948
+ hint: "Rust ecosystem with Axum, Leptos, and more"
2949
+ },
2950
+ {
2951
+ value: "python",
2952
+ label: "Python",
2953
+ hint: "Python ecosystem with FastAPI, Django, and AI/ML tools"
2954
+ },
2955
+ {
2956
+ value: "go",
2957
+ label: "Go",
2958
+ hint: "Go ecosystem with Gin, Echo, GORM, and more"
2959
+ },
2960
+ {
2961
+ value: "java",
2962
+ label: "Java",
2963
+ hint: "Java ecosystem with Spring Boot, Maven, Gradle, and more"
2964
+ },
2965
+ {
2966
+ value: "elixir",
2967
+ label: "Elixir",
2968
+ hint: "Elixir ecosystem with Phoenix, LiveView, Ecto, and more"
2969
+ }
2970
+ ];
2938
2971
  async function getEcosystemChoice(ecosystem) {
2939
2972
  if (ecosystem !== void 0) return ecosystem;
2940
2973
  const response = await navigableSelect({
2941
2974
  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
- ],
2975
+ options: ECOSYSTEM_PROMPT_OPTIONS,
2974
2976
  initialValue: "typescript"
2975
2977
  });
2976
2978
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
@@ -4343,6 +4345,7 @@ const MOBILE_DEEP_LINKING_OPTIONS = [{
4343
4345
  async function promptMobileOption(options, defaultValue, selected, message) {
4344
4346
  const resolution = createStaticSinglePromptResolution(options, defaultValue, selected);
4345
4347
  if (!resolution.shouldPrompt) return resolution.autoValue ?? defaultValue;
4348
+ if (!canPromptInteractively()) return defaultValue;
4346
4349
  const response = await navigableSelect({
4347
4350
  message,
4348
4351
  options: resolution.options,
@@ -4373,224 +4376,12 @@ function getMobileDeepLinkingChoice(mobileDeepLinking) {
4373
4376
  return promptMobileOption(MOBILE_DEEP_LINKING_OPTIONS, "expo-linking", mobileDeepLinking, "Select mobile deep linking");
4374
4377
  }
4375
4378
 
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
4379
  //#endregion
4591
4380
  //#region src/prompts/package-manager.ts
4592
4381
  async function getPackageManagerChoice(packageManager) {
4593
4382
  if (packageManager !== void 0) return packageManager;
4383
+ const detectedPackageManager = getUserPkgManager();
4384
+ if (!canPromptInteractively()) return detectedPackageManager;
4594
4385
  const response = await navigableSelect({
4595
4386
  message: "Choose package manager",
4596
4387
  options: [
@@ -4615,74 +4406,7 @@ async function getPackageManagerChoice(packageManager) {
4615
4406
  hint: "Yarn Berry (v4) with PnP or node_modules"
4616
4407
  }
4617
4408
  ],
4618
- initialValue: getUserPkgManager()
4619
- });
4620
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4621
- return response;
4622
- }
4623
-
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
4409
+ initialValue: detectedPackageManager
4686
4410
  });
4687
4411
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4688
4412
  return response;
@@ -5019,133 +4743,6 @@ async function getPythonQualityChoice(pythonQuality) {
5019
4743
  return response;
5020
4744
  }
5021
4745
 
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
4746
  //#endregion
5150
4747
  //#region src/prompts/rust-ecosystem.ts
5151
4748
  const RUST_WEB_FRAMEWORK_PROMPT_OPTIONS = [
@@ -5528,133 +5125,51 @@ async function getRustAuthChoice(rustAuth) {
5528
5125
  }
5529
5126
 
5530
5127
  //#endregion
5531
- //#region src/prompts/search.ts
5532
- const SEARCH_PROMPT_OPTIONS = [
5128
+ //#region src/prompts/shadcn-options.ts
5129
+ const BASE_OPTIONS = [{
5130
+ value: "radix",
5131
+ label: "Radix UI",
5132
+ hint: "Battle-tested headless primitives (130M+ monthly downloads)"
5133
+ }, {
5134
+ value: "base",
5135
+ label: "Base UI",
5136
+ hint: "MUI's headless library with cleaner APIs and native multi-select"
5137
+ }];
5138
+ const STYLE_OPTIONS = [
5533
5139
  {
5534
- value: "meilisearch",
5535
- label: "Meilisearch",
5536
- hint: "Lightning-fast search engine with typo tolerance"
5140
+ value: "vega",
5141
+ label: "Vega",
5142
+ hint: "Classic shadcn/ui look"
5537
5143
  },
5538
5144
  {
5539
- value: "typesense",
5540
- label: "Typesense",
5541
- hint: "Fast, typo-tolerant search with built-in vector search"
5145
+ value: "nova",
5146
+ label: "Nova",
5147
+ hint: "Compact layout with reduced padding"
5542
5148
  },
5543
5149
  {
5544
- value: "elasticsearch",
5545
- label: "Elasticsearch",
5546
- hint: "Distributed search and analytics engine with local and cloud deployments"
5150
+ value: "maia",
5151
+ label: "Maia",
5152
+ hint: "Soft, rounded with generous spacing"
5547
5153
  },
5548
5154
  {
5549
- value: "algolia",
5550
- label: "Algolia",
5551
- hint: "Hosted search API with instant results, typo tolerance, and analytics"
5155
+ value: "lyra",
5156
+ label: "Lyra",
5157
+ hint: "Boxy and sharp, pairs well with mono fonts"
5552
5158
  },
5553
5159
  {
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"
5160
+ value: "mira",
5161
+ label: "Mira",
5162
+ hint: "Dense, made for data-heavy interfaces"
5163
+ },
5164
+ {
5165
+ value: "luma",
5166
+ label: "Luma",
5167
+ hint: "Modern shadcn/ui v4 preset"
5168
+ },
5169
+ {
5170
+ value: "sera",
5171
+ label: "Sera",
5172
+ hint: "Modern shadcn/ui v4 preset"
5658
5173
  }
5659
5174
  ];
5660
5175
  const ICON_LIBRARY_OPTIONS = [
@@ -5941,14 +5456,28 @@ const SHADCN_DEFAULTS = {
5941
5456
  };
5942
5457
  async function getShadcnOptions(flags) {
5943
5458
  const fallback = (key) => isSilent() ? SHADCN_DEFAULTS[key] : void 0;
5459
+ const shadcnBase = flags.shadcnBase ?? fallback("shadcnBase") ?? await promptShadcnBase();
5460
+ if (isGoBack(shadcnBase)) return shadcnBase;
5461
+ const shadcnStyle = flags.shadcnStyle ?? fallback("shadcnStyle") ?? await promptShadcnStyle();
5462
+ if (isGoBack(shadcnStyle)) return shadcnStyle;
5463
+ const shadcnIconLibrary = flags.shadcnIconLibrary ?? fallback("shadcnIconLibrary") ?? await promptShadcnIconLibrary();
5464
+ if (isGoBack(shadcnIconLibrary)) return shadcnIconLibrary;
5465
+ const shadcnColorTheme = flags.shadcnColorTheme ?? fallback("shadcnColorTheme") ?? await promptShadcnColorTheme();
5466
+ if (isGoBack(shadcnColorTheme)) return shadcnColorTheme;
5467
+ const shadcnBaseColor = flags.shadcnBaseColor ?? fallback("shadcnBaseColor") ?? await promptShadcnBaseColor();
5468
+ if (isGoBack(shadcnBaseColor)) return shadcnBaseColor;
5469
+ const shadcnFont = flags.shadcnFont ?? fallback("shadcnFont") ?? await promptShadcnFont();
5470
+ if (isGoBack(shadcnFont)) return shadcnFont;
5471
+ const shadcnRadius = flags.shadcnRadius ?? fallback("shadcnRadius") ?? await promptShadcnRadius();
5472
+ if (isGoBack(shadcnRadius)) return shadcnRadius;
5944
5473
  return {
5945
- shadcnBase: flags.shadcnBase ?? fallback("shadcnBase") ?? await promptShadcnBase(),
5946
- shadcnStyle: flags.shadcnStyle ?? fallback("shadcnStyle") ?? await promptShadcnStyle(),
5947
- shadcnIconLibrary: flags.shadcnIconLibrary ?? fallback("shadcnIconLibrary") ?? await promptShadcnIconLibrary(),
5948
- shadcnColorTheme: flags.shadcnColorTheme ?? fallback("shadcnColorTheme") ?? await promptShadcnColorTheme(),
5949
- shadcnBaseColor: flags.shadcnBaseColor ?? fallback("shadcnBaseColor") ?? await promptShadcnBaseColor(),
5950
- shadcnFont: flags.shadcnFont ?? fallback("shadcnFont") ?? await promptShadcnFont(),
5951
- shadcnRadius: flags.shadcnRadius ?? fallback("shadcnRadius") ?? await promptShadcnRadius()
5474
+ shadcnBase,
5475
+ shadcnStyle,
5476
+ shadcnIconLibrary,
5477
+ shadcnColorTheme,
5478
+ shadcnBaseColor,
5479
+ shadcnFont,
5480
+ shadcnRadius
5952
5481
  };
5953
5482
  }
5954
5483
  async function promptShadcnBase() {
@@ -5957,6 +5486,7 @@ async function promptShadcnBase() {
5957
5486
  options: BASE_OPTIONS,
5958
5487
  initialValue: "radix"
5959
5488
  });
5489
+ if (isGoBack(selected)) return selected;
5960
5490
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5961
5491
  return selected;
5962
5492
  }
@@ -5966,6 +5496,7 @@ async function promptShadcnStyle() {
5966
5496
  options: STYLE_OPTIONS,
5967
5497
  initialValue: "nova"
5968
5498
  });
5499
+ if (isGoBack(selected)) return selected;
5969
5500
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5970
5501
  return selected;
5971
5502
  }
@@ -5975,6 +5506,7 @@ async function promptShadcnIconLibrary() {
5975
5506
  options: ICON_LIBRARY_OPTIONS,
5976
5507
  initialValue: "lucide"
5977
5508
  });
5509
+ if (isGoBack(selected)) return selected;
5978
5510
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5979
5511
  return selected;
5980
5512
  }
@@ -5984,6 +5516,7 @@ async function promptShadcnColorTheme() {
5984
5516
  options: COLOR_THEME_OPTIONS,
5985
5517
  initialValue: "neutral"
5986
5518
  });
5519
+ if (isGoBack(selected)) return selected;
5987
5520
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5988
5521
  return selected;
5989
5522
  }
@@ -5993,6 +5526,7 @@ async function promptShadcnBaseColor() {
5993
5526
  options: BASE_COLOR_OPTIONS,
5994
5527
  initialValue: "neutral"
5995
5528
  });
5529
+ if (isGoBack(selected)) return selected;
5996
5530
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5997
5531
  return selected;
5998
5532
  }
@@ -6002,6 +5536,7 @@ async function promptShadcnFont() {
6002
5536
  options: FONT_OPTIONS,
6003
5537
  initialValue: "inter"
6004
5538
  });
5539
+ if (isGoBack(selected)) return selected;
6005
5540
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
6006
5541
  return selected;
6007
5542
  }
@@ -6011,151 +5546,11 @@ async function promptShadcnRadius() {
6011
5546
  options: RADIUS_OPTIONS,
6012
5547
  initialValue: "default"
6013
5548
  });
5549
+ if (isGoBack(selected)) return selected;
6014
5550
  if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
6015
5551
  return selected;
6016
5552
  }
6017
5553
 
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
5554
  //#endregion
6160
5555
  //#region src/prompts/ui-library.ts
6161
5556
  const UI_LIBRARY_OPTIONS = {
@@ -6199,72 +5594,1096 @@ const UI_LIBRARY_OPTIONS = {
6199
5594
  label: "MUI",
6200
5595
  hint: "Popular React component library implementing Material Design"
6201
5596
  },
6202
- antd: {
6203
- label: "Ant Design",
6204
- hint: "Enterprise-class React UI component library"
5597
+ antd: {
5598
+ label: "Ant Design",
5599
+ hint: "Enterprise-class React UI component library"
5600
+ },
5601
+ "base-ui": {
5602
+ label: "Base UI",
5603
+ hint: "Unstyled, accessible components from MUI team (Radix successor)"
5604
+ },
5605
+ "ark-ui": {
5606
+ label: "Ark UI",
5607
+ hint: "Headless, accessible UI components for React, Vue, Solid, and Svelte"
5608
+ },
5609
+ "react-aria": {
5610
+ label: "React Aria",
5611
+ hint: "Adobe's accessible, unstyled UI components for React"
5612
+ },
5613
+ none: {
5614
+ label: "None",
5615
+ hint: "No UI component library"
5616
+ }
5617
+ };
5618
+ function resolveUILibraryPrompt(context = {}) {
5619
+ const { web } = splitFrontends$1(context.frontends);
5620
+ if (web.length === 0) return {
5621
+ shouldPrompt: false,
5622
+ mode: "single",
5623
+ options: [],
5624
+ autoValue: "none"
5625
+ };
5626
+ const compatibleLibraries = getCompatibleUILibraries$1(context.frontends, context.astroIntegration);
5627
+ if (context.uiLibrary !== void 0) return {
5628
+ shouldPrompt: false,
5629
+ mode: "single",
5630
+ options: compatibleLibraries.map((lib) => ({
5631
+ value: lib,
5632
+ label: UI_LIBRARY_OPTIONS[lib].label,
5633
+ hint: UI_LIBRARY_OPTIONS[lib].hint
5634
+ })),
5635
+ autoValue: compatibleLibraries.includes(context.uiLibrary) ? context.uiLibrary : compatibleLibraries[0]
5636
+ };
5637
+ const defaultLib = DEFAULT_UI_LIBRARY_BY_FRONTEND[web[0]];
5638
+ return {
5639
+ shouldPrompt: true,
5640
+ mode: "single",
5641
+ options: compatibleLibraries.map((lib) => ({
5642
+ value: lib,
5643
+ label: UI_LIBRARY_OPTIONS[lib].label,
5644
+ hint: UI_LIBRARY_OPTIONS[lib].hint
5645
+ })),
5646
+ initialValue: compatibleLibraries.includes(defaultLib) ? defaultLib : compatibleLibraries[0]
5647
+ };
5648
+ }
5649
+ async function getUILibraryChoice(uiLibrary, frontends, astroIntegration) {
5650
+ const resolution = resolveUILibraryPrompt({
5651
+ uiLibrary,
5652
+ frontends,
5653
+ astroIntegration
5654
+ });
5655
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
5656
+ const selected = await navigableSelect({
5657
+ message: "Select UI component library",
5658
+ options: resolution.options,
5659
+ initialValue: resolution.initialValue
5660
+ });
5661
+ if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
5662
+ return selected;
5663
+ }
5664
+
5665
+ //#endregion
5666
+ //#region src/utils/compatibility.ts
5667
+ const WEB_FRAMEWORKS = [
5668
+ "tanstack-router",
5669
+ "react-router",
5670
+ "react-vite",
5671
+ "tanstack-start",
5672
+ "next",
5673
+ "vinext",
5674
+ "nuxt",
5675
+ "svelte",
5676
+ "solid",
5677
+ "solid-start",
5678
+ "astro",
5679
+ "qwik",
5680
+ "angular",
5681
+ "redwood",
5682
+ "fresh"
5683
+ ];
5684
+
5685
+ //#endregion
5686
+ //#region src/prompts/web-deploy.ts
5687
+ function hasWebFrontend(frontends) {
5688
+ return frontends.some((f) => WEB_FRAMEWORKS.includes(f));
5689
+ }
5690
+ function getDeploymentDisplay(deployment) {
5691
+ if (deployment === "cloudflare") return {
5692
+ label: "Cloudflare",
5693
+ hint: "Deploy to Cloudflare Workers using Alchemy"
5694
+ };
5695
+ if (deployment === "vercel") return {
5696
+ label: "Vercel",
5697
+ hint: "Deploy to Vercel's edge network"
5698
+ };
5699
+ return {
5700
+ label: deployment,
5701
+ hint: `Add ${deployment} deployment`
5702
+ };
5703
+ }
5704
+ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
5705
+ if (deployment !== void 0) return deployment;
5706
+ if (!hasWebFrontend(frontend)) return "none";
5707
+ const response = await navigableSelect({
5708
+ message: "Select web deployment",
5709
+ options: [
5710
+ "cloudflare",
5711
+ "vercel",
5712
+ "none"
5713
+ ].map((deploy) => {
5714
+ const { label, hint } = getDeploymentDisplay(deploy);
5715
+ return {
5716
+ value: deploy,
5717
+ label,
5718
+ hint
5719
+ };
5720
+ }),
5721
+ initialValue: DEFAULT_CONFIG.webDeploy
5722
+ });
5723
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
5724
+ return response;
5725
+ }
5726
+
5727
+ //#endregion
5728
+ //#region src/prompts/multi-ecosystem-composer.ts
5729
+ async function getCompositionModeChoice() {
5730
+ const response = await navigableSelect({
5731
+ message: "Select project composition",
5732
+ options: [{
5733
+ value: "single",
5734
+ label: "Single ecosystem",
5735
+ hint: "Use the classic guided flow"
5736
+ }, {
5737
+ value: "multi",
5738
+ label: "Multi ecosystem",
5739
+ hint: "Compose a TypeScript frontend with another backend ecosystem"
5740
+ }],
5741
+ initialValue: "single"
5742
+ });
5743
+ if (isCancel$1(response) || isGoBack(response)) return exitCancelled("Operation cancelled");
5744
+ return response;
5745
+ }
5746
+ async function selectBackendEcosystem() {
5747
+ const response = await navigableSelect({
5748
+ message: "Select backend ecosystem",
5749
+ options: [
5750
+ {
5751
+ value: "go",
5752
+ label: "Go",
5753
+ hint: "Gin, Echo, Fiber, Chi"
5754
+ },
5755
+ {
5756
+ value: "rust",
5757
+ label: "Rust",
5758
+ hint: "Axum, Actix Web, Rocket"
5759
+ },
5760
+ {
5761
+ value: "python",
5762
+ label: "Python",
5763
+ hint: "FastAPI, Django, Flask"
5764
+ },
5765
+ {
5766
+ value: "java",
5767
+ label: "Java",
5768
+ hint: "Spring Boot, Quarkus"
5769
+ },
5770
+ {
5771
+ value: "elixir",
5772
+ label: "Elixir",
5773
+ hint: "Phoenix, LiveView"
5774
+ }
5775
+ ],
5776
+ initialValue: "go"
5777
+ });
5778
+ if (isCancel$1(response) || isGoBack(response)) return exitCancelled("Operation cancelled");
5779
+ return response;
5780
+ }
5781
+ async function selectServerDeployment(deployment) {
5782
+ if (deployment !== void 0) return deployment;
5783
+ const response = await navigableSelect({
5784
+ message: "Select server deployment",
5785
+ options: [
5786
+ {
5787
+ value: "none",
5788
+ label: "None",
5789
+ hint: "Skip server deployment setup"
5790
+ },
5791
+ {
5792
+ value: "railway",
5793
+ label: "Railway",
5794
+ hint: "Deploy a standalone backend service"
5795
+ },
5796
+ {
5797
+ value: "docker",
5798
+ label: "Docker",
5799
+ hint: "Containerize the backend service"
5800
+ },
5801
+ {
5802
+ value: "fly",
5803
+ label: "Fly",
5804
+ hint: "Deploy close to users"
5805
+ },
5806
+ {
5807
+ value: "vercel",
5808
+ label: "Vercel",
5809
+ hint: "Deploy from the backend workspace"
5810
+ }
5811
+ ],
5812
+ initialValue: "none"
5813
+ });
5814
+ if (isCancel$1(response) || isGoBack(response)) return exitCancelled("Operation cancelled");
5815
+ return response;
5816
+ }
5817
+ function promptValue(value) {
5818
+ if (isCancel$1(value) || isGoBack(value)) return exitCancelled("Operation cancelled");
5819
+ return value;
5820
+ }
5821
+ async function selectDatabaseConfig(flags) {
5822
+ const database = promptValue(await getDatabaseChoice(flags.database, "hono", "bun"));
5823
+ return {
5824
+ database,
5825
+ dbSetup: promptValue(await getDBSetupChoice(database, flags.dbSetup, "none", "none", "none"))
5826
+ };
5827
+ }
5828
+ async function gatherMultiEcosystemConfig(flags, projectName, projectDir, relativePath) {
5829
+ const baseConfig = getDefaultConfig();
5830
+ const frontend = promptValue(await navigableSelect({
5831
+ message: "Select TypeScript web frontend",
5832
+ options: WEB_FRONTEND_PROMPT_OPTIONS,
5833
+ initialValue: flags.frontend?.[0] ?? "next"
5834
+ }));
5835
+ const frontendList = [frontend];
5836
+ const astroIntegration = frontend === "astro" ? promptValue(await getAstroIntegrationChoice(flags.astroIntegration)) : void 0;
5837
+ const uiLibrary = hasWebStyling$1(frontendList) ? promptValue(await getUILibraryChoice(flags.uiLibrary, frontendList, astroIntegration)) : "none";
5838
+ const shadcnOptions = uiLibrary === "shadcn-ui" ? promptValue(await getShadcnOptions({
5839
+ shadcnBase: flags.shadcnBase,
5840
+ shadcnStyle: flags.shadcnStyle,
5841
+ shadcnIconLibrary: flags.shadcnIconLibrary,
5842
+ shadcnColorTheme: flags.shadcnColorTheme,
5843
+ shadcnBaseColor: flags.shadcnBaseColor,
5844
+ shadcnFont: flags.shadcnFont,
5845
+ shadcnRadius: flags.shadcnRadius
5846
+ })) : void 0;
5847
+ const cssFramework = hasWebStyling$1(frontendList) ? promptValue(await getCSSFrameworkChoice(flags.cssFramework, uiLibrary)) : "none";
5848
+ const backendEcosystem = await selectBackendEcosystem();
5849
+ const stackPartSpecs = [`frontend:typescript:${frontend}`];
5850
+ const backendChoices = {};
5851
+ let database = "none";
5852
+ let dbSetup = "none";
5853
+ if (backendEcosystem === "go") {
5854
+ const goWebFramework = promptValue(await getGoWebFrameworkChoice(flags.goWebFramework));
5855
+ if (goWebFramework !== "none") {
5856
+ const databaseConfig = await selectDatabaseConfig(flags);
5857
+ database = databaseConfig.database;
5858
+ dbSetup = databaseConfig.dbSetup;
5859
+ }
5860
+ const goOrm = database === "none" || goWebFramework === "none" ? "none" : promptValue(await getGoOrmChoice(flags.goOrm));
5861
+ const goApi = goWebFramework === "none" ? "none" : promptValue(await getGoApiChoice(flags.goApi));
5862
+ const goAuth = goWebFramework === "none" ? "none" : promptValue(await getGoAuthChoice(flags.goAuth));
5863
+ const goCli = goWebFramework === "none" ? "none" : promptValue(await getGoCliChoice(flags.goCli));
5864
+ const goLogging = goWebFramework === "none" ? "none" : promptValue(await getGoLoggingChoice(flags.goLogging));
5865
+ Object.assign(backendChoices, {
5866
+ goWebFramework,
5867
+ goOrm,
5868
+ goApi,
5869
+ goAuth,
5870
+ goCli,
5871
+ goLogging
5872
+ });
5873
+ if (goWebFramework !== "none") stackPartSpecs.push(`backend:go:${goWebFramework}`);
5874
+ if (goOrm !== "none") stackPartSpecs.push(`backend.orm:go:${goOrm}`);
5875
+ if (goApi !== "none") stackPartSpecs.push(`backend.api:go:${goApi}`);
5876
+ if (goAuth !== "none") stackPartSpecs.push(`backend.auth:go:${goAuth}`);
5877
+ }
5878
+ if (backendEcosystem === "rust") {
5879
+ const rustWebFramework = promptValue(await getRustWebFrameworkChoice(flags.rustWebFramework));
5880
+ if (rustWebFramework !== "none") {
5881
+ const databaseConfig = await selectDatabaseConfig(flags);
5882
+ database = databaseConfig.database;
5883
+ dbSetup = databaseConfig.dbSetup;
5884
+ }
5885
+ const rustOrm = database === "none" || rustWebFramework === "none" ? "none" : promptValue(await getRustOrmChoice(flags.rustOrm));
5886
+ const rustApi = rustWebFramework === "none" ? "none" : promptValue(await getRustApiChoice(flags.rustApi));
5887
+ const rustAuth = rustWebFramework === "none" ? "none" : promptValue(await getRustAuthChoice(flags.rustAuth));
5888
+ const rustFrontend = "none";
5889
+ const rustCli = rustWebFramework === "none" ? "none" : promptValue(await getRustCliChoice(flags.rustCli));
5890
+ const rustLibraries = rustWebFramework === "none" ? [] : promptValue(await getRustLibrariesChoice(flags.rustLibraries));
5891
+ const rustLogging = rustWebFramework === "none" ? "none" : promptValue(await getRustLoggingChoice(flags.rustLogging));
5892
+ const rustErrorHandling = promptValue(await getRustErrorHandlingChoice(flags.rustErrorHandling));
5893
+ const rustCaching = rustWebFramework === "none" ? "none" : promptValue(await getRustCachingChoice(flags.rustCaching));
5894
+ Object.assign(backendChoices, {
5895
+ rustWebFramework,
5896
+ rustOrm,
5897
+ rustApi,
5898
+ rustAuth,
5899
+ rustFrontend,
5900
+ rustCli,
5901
+ rustLibraries,
5902
+ rustLogging,
5903
+ rustErrorHandling,
5904
+ rustCaching
5905
+ });
5906
+ if (rustWebFramework !== "none") stackPartSpecs.push(`backend:rust:${rustWebFramework}`);
5907
+ if (rustOrm !== "none") stackPartSpecs.push(`backend.orm:rust:${rustOrm}`);
5908
+ if (rustApi !== "none") stackPartSpecs.push(`backend.api:rust:${rustApi}`);
5909
+ if (rustAuth !== "none") stackPartSpecs.push(`backend.auth:rust:${rustAuth}`);
5910
+ }
5911
+ if (backendEcosystem === "python") {
5912
+ const pythonWebFramework = promptValue(await getPythonWebFrameworkChoice(flags.pythonWebFramework));
5913
+ if (pythonWebFramework !== "none") {
5914
+ const databaseConfig = await selectDatabaseConfig(flags);
5915
+ database = databaseConfig.database;
5916
+ dbSetup = databaseConfig.dbSetup;
5917
+ }
5918
+ const pythonOrm = database === "none" || pythonWebFramework === "none" ? "none" : promptValue(await getPythonOrmChoice(flags.pythonOrm));
5919
+ const pythonValidation = pythonWebFramework === "none" ? "none" : promptValue(await getPythonValidationChoice(flags.pythonValidation));
5920
+ const pythonAi = pythonWebFramework === "none" ? [] : promptValue(await getPythonAiChoice(flags.pythonAi));
5921
+ const pythonAuth = pythonWebFramework === "none" ? "none" : promptValue(await getPythonAuthChoice(flags.pythonAuth));
5922
+ const pythonTaskQueue = pythonWebFramework === "none" ? "none" : promptValue(await getPythonTaskQueueChoice(flags.pythonTaskQueue));
5923
+ const pythonGraphql = pythonWebFramework === "none" ? "none" : promptValue(await getPythonGraphqlChoice(flags.pythonGraphql));
5924
+ const pythonQuality = pythonWebFramework === "none" ? "none" : promptValue(await getPythonQualityChoice(flags.pythonQuality));
5925
+ Object.assign(backendChoices, {
5926
+ pythonWebFramework,
5927
+ pythonOrm,
5928
+ pythonValidation,
5929
+ pythonAi,
5930
+ pythonAuth,
5931
+ pythonTaskQueue,
5932
+ pythonGraphql,
5933
+ pythonQuality
5934
+ });
5935
+ if (pythonWebFramework !== "none") stackPartSpecs.push(`backend:python:${pythonWebFramework}`);
5936
+ if (pythonOrm !== "none") stackPartSpecs.push(`backend.orm:python:${pythonOrm}`);
5937
+ if (pythonAuth !== "none") stackPartSpecs.push(`backend.auth:python:${pythonAuth}`);
5938
+ if (pythonTaskQueue !== "none") stackPartSpecs.push(`backend.jobQueue:python:${pythonTaskQueue}`);
5939
+ if (pythonGraphql !== "none") stackPartSpecs.push(`backend.api:python:${pythonGraphql}`);
5940
+ }
5941
+ if (backendEcosystem === "java") {
5942
+ const javaWebFramework = promptValue(await getJavaWebFrameworkChoice(flags.javaWebFramework));
5943
+ const javaBuildTool = promptValue(await getJavaBuildToolChoice(flags.javaBuildTool));
5944
+ if (javaWebFramework !== "none" && javaBuildTool !== "none") {
5945
+ const databaseConfig = await selectDatabaseConfig(flags);
5946
+ database = databaseConfig.database;
5947
+ dbSetup = databaseConfig.dbSetup;
5948
+ }
5949
+ const javaOrm = database === "none" || javaWebFramework !== "spring-boot" || javaBuildTool === "none" ? "none" : promptValue(await getJavaOrmChoice(flags.javaOrm));
5950
+ const javaAuth = javaWebFramework !== "spring-boot" || javaBuildTool === "none" ? "none" : promptValue(await getJavaAuthChoice(flags.javaAuth));
5951
+ const javaLibraries = javaWebFramework !== "spring-boot" || javaBuildTool === "none" ? [] : promptValue(await getJavaLibrariesChoice(flags.javaLibraries));
5952
+ const javaTestingLibraries = promptValue(await getJavaTestingLibrariesChoice(flags.javaTestingLibraries));
5953
+ Object.assign(backendChoices, {
5954
+ javaWebFramework,
5955
+ javaBuildTool,
5956
+ javaOrm,
5957
+ javaAuth,
5958
+ javaLibraries,
5959
+ javaTestingLibraries
5960
+ });
5961
+ if (javaWebFramework !== "none") stackPartSpecs.push(`backend:java:${javaWebFramework}`);
5962
+ if (javaOrm !== "none") stackPartSpecs.push(`backend.orm:java:${javaOrm}`);
5963
+ if (javaAuth !== "none") stackPartSpecs.push(`backend.auth:java:${javaAuth}`);
5964
+ }
5965
+ if (backendEcosystem === "elixir") {
5966
+ const elixirWebFramework = promptValue(await getElixirWebFrameworkChoice(flags.elixirWebFramework));
5967
+ if (elixirWebFramework !== "none") {
5968
+ const databaseConfig = await selectDatabaseConfig(flags);
5969
+ database = databaseConfig.database;
5970
+ dbSetup = databaseConfig.dbSetup;
5971
+ }
5972
+ const elixirOrm = database === "none" || elixirWebFramework === "none" ? "none" : promptValue(await getElixirOrmChoice(flags.elixirOrm));
5973
+ const elixirAuth = elixirWebFramework === "none" ? "none" : promptValue(await getElixirAuthChoice(flags.elixirAuth));
5974
+ const elixirApi = elixirWebFramework === "none" ? "none" : promptValue(await getElixirApiChoice(flags.elixirApi));
5975
+ const elixirRealtime = elixirWebFramework === "none" ? "none" : promptValue(await getElixirRealtimeChoice(flags.elixirRealtime));
5976
+ const elixirJobs = elixirWebFramework === "none" ? "none" : promptValue(await getElixirJobsChoice(flags.elixirJobs));
5977
+ const elixirValidation = elixirWebFramework === "none" ? "none" : promptValue(await getElixirValidationChoice(flags.elixirValidation));
5978
+ const elixirHttp = elixirWebFramework === "none" ? "none" : promptValue(await getElixirHttpChoice(flags.elixirHttp));
5979
+ const elixirJson = elixirWebFramework === "none" ? "none" : promptValue(await getElixirJsonChoice(flags.elixirJson));
5980
+ const elixirEmail = elixirWebFramework === "none" ? "none" : promptValue(await getElixirEmailChoice(flags.elixirEmail));
5981
+ const elixirCaching = elixirWebFramework === "none" ? "none" : promptValue(await getElixirCachingChoice(flags.elixirCaching));
5982
+ const elixirObservability = elixirWebFramework === "none" ? "none" : promptValue(await getElixirObservabilityChoice(flags.elixirObservability));
5983
+ const elixirTesting = elixirWebFramework === "none" ? "none" : promptValue(await getElixirTestingChoice(flags.elixirTesting));
5984
+ const elixirQuality = elixirWebFramework === "none" ? "none" : promptValue(await getElixirQualityChoice(flags.elixirQuality));
5985
+ const elixirDeploy = elixirWebFramework === "none" ? "none" : promptValue(await getElixirDeployChoice(flags.elixirDeploy));
5986
+ Object.assign(backendChoices, {
5987
+ elixirWebFramework,
5988
+ elixirOrm,
5989
+ elixirAuth,
5990
+ elixirApi,
5991
+ elixirRealtime,
5992
+ elixirJobs,
5993
+ elixirValidation,
5994
+ elixirHttp,
5995
+ elixirJson,
5996
+ elixirEmail,
5997
+ elixirCaching,
5998
+ elixirObservability,
5999
+ elixirTesting,
6000
+ elixirQuality,
6001
+ elixirDeploy
6002
+ });
6003
+ if (elixirWebFramework !== "none") stackPartSpecs.push(`backend:elixir:${elixirWebFramework}`);
6004
+ if (elixirOrm !== "none") stackPartSpecs.push(`backend.orm:elixir:${elixirOrm}`);
6005
+ if (elixirAuth !== "none") stackPartSpecs.push(`backend.auth:elixir:${elixirAuth}`);
6006
+ if (elixirApi !== "none") stackPartSpecs.push(`backend.api:elixir:${elixirApi}`);
6007
+ if (elixirRealtime !== "none") stackPartSpecs.push(`backend.api:elixir:${elixirRealtime}`);
6008
+ if (elixirJobs !== "none") stackPartSpecs.push(`backend.jobQueue:elixir:${elixirJobs}`);
6009
+ if (elixirEmail !== "none") stackPartSpecs.push(`backend.email:elixir:${elixirEmail}`);
6010
+ if (elixirCaching !== "none") stackPartSpecs.push(`backend.caching:elixir:${elixirCaching}`);
6011
+ if (elixirObservability !== "none") stackPartSpecs.push(`backend.observability:elixir:${elixirObservability}`);
6012
+ if (elixirTesting !== "none") stackPartSpecs.push(`backend.testing:elixir:${elixirTesting}`);
6013
+ if (elixirDeploy !== "none") stackPartSpecs.push(`backend.deploy:elixir:${elixirDeploy}`);
6014
+ }
6015
+ if (database !== "none") stackPartSpecs.push(`database:universal:${database}`);
6016
+ const stackParts = (0, types_exports.parseStackPartSpecs)(stackPartSpecs, "selected");
6017
+ const graphPartial = (0, types_exports.stackPartsToLegacyProjectConfigPartial)(stackParts);
6018
+ const addons = promptValue(await getAddonsChoice(flags.addons, frontendList, "none", "none", "bun"));
6019
+ const webDeploy = promptValue(await getDeploymentChoice(flags.webDeploy, "bun", "none", frontendList));
6020
+ const serverDeploy = await selectServerDeployment(flags.serverDeploy);
6021
+ const aiDocs = promptValue(await getAiDocsChoice(flags.aiDocs));
6022
+ const git = promptValue(await getGitChoice(flags.git));
6023
+ const packageManager = promptValue(await getPackageManagerChoice(flags.packageManager));
6024
+ const install = promptValue(await getinstallChoice(flags.install, "typescript", "none"));
6025
+ return {
6026
+ ...baseConfig,
6027
+ ...flags,
6028
+ ...graphPartial,
6029
+ ...backendChoices,
6030
+ projectName,
6031
+ projectDir,
6032
+ relativePath,
6033
+ ecosystem: "typescript",
6034
+ frontend: frontendList,
6035
+ backend: "none",
6036
+ runtime: "none",
6037
+ database,
6038
+ orm: "none",
6039
+ api: "none",
6040
+ auth: "none",
6041
+ astroIntegration,
6042
+ uiLibrary,
6043
+ ...shadcnOptions,
6044
+ cssFramework,
6045
+ addons,
6046
+ examples: [],
6047
+ dbSetup,
6048
+ webDeploy,
6049
+ serverDeploy,
6050
+ aiDocs,
6051
+ git,
6052
+ packageManager,
6053
+ install,
6054
+ stackParts
6055
+ };
6056
+ }
6057
+
6058
+ //#endregion
6059
+ //#region src/prompts/navigable-group.ts
6060
+ /**
6061
+ * Navigable group - a group of prompts that allows going back
6062
+ */
6063
+ /**
6064
+ * Define a group of prompts that supports going back to previous prompts.
6065
+ * Returns a result object with all the values, or handles cancel/go-back navigation.
6066
+ */
6067
+ async function navigableGroup(prompts, opts) {
6068
+ const results = {};
6069
+ const promptNames = Object.keys(prompts);
6070
+ let currentIndex = 0;
6071
+ let goingBack = false;
6072
+ while (currentIndex < promptNames.length) {
6073
+ const name = promptNames[currentIndex];
6074
+ const prompt = prompts[name];
6075
+ setIsFirstPrompt$1(currentIndex === 0);
6076
+ setLastPromptShownUI(false);
6077
+ const result = await prompt({ results })?.catch((e) => {
6078
+ throw e;
6079
+ });
6080
+ if (isGoBack(result)) {
6081
+ goingBack = true;
6082
+ if (currentIndex > 0) {
6083
+ const prevName = promptNames[currentIndex - 1];
6084
+ delete results[prevName];
6085
+ currentIndex--;
6086
+ continue;
6087
+ }
6088
+ goingBack = false;
6089
+ continue;
6090
+ }
6091
+ if (isCancel$1(result)) {
6092
+ if (typeof opts?.onCancel === "function") {
6093
+ results[name] = "canceled";
6094
+ opts.onCancel({ results });
6095
+ }
6096
+ setIsFirstPrompt$1(false);
6097
+ return results;
6098
+ }
6099
+ if (goingBack && !didLastPromptShowUI()) {
6100
+ if (currentIndex > 0) {
6101
+ const prevName = promptNames[currentIndex - 1];
6102
+ delete results[prevName];
6103
+ currentIndex--;
6104
+ continue;
6105
+ }
6106
+ }
6107
+ goingBack = false;
6108
+ results[name] = result;
6109
+ currentIndex++;
6110
+ }
6111
+ setIsFirstPrompt$1(false);
6112
+ return results;
6113
+ }
6114
+
6115
+ //#endregion
6116
+ //#region src/prompts/observability.ts
6117
+ const OBSERVABILITY_PROMPT_OPTIONS = [
6118
+ {
6119
+ value: "opentelemetry",
6120
+ label: "OpenTelemetry",
6121
+ hint: "Observability framework for traces, metrics, and logs"
6122
+ },
6123
+ {
6124
+ value: "sentry",
6125
+ label: "Sentry",
6126
+ hint: "Error tracking and performance monitoring"
6127
+ },
6128
+ {
6129
+ value: "grafana",
6130
+ label: "Grafana",
6131
+ hint: "Prometheus metrics for Grafana dashboards and alerting"
6132
+ },
6133
+ {
6134
+ value: "none",
6135
+ label: "None",
6136
+ hint: "Skip observability/tracing setup"
6137
+ }
6138
+ ];
6139
+ const NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS = OBSERVABILITY_PROMPT_OPTIONS.filter((option) => option.value === "sentry" || option.value === "none");
6140
+ function resolveObservabilityPrompt(context = {}) {
6141
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
6142
+ shouldPrompt: false,
6143
+ mode: "single",
6144
+ options: [],
6145
+ autoValue: "none"
6146
+ };
6147
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS : OBSERVABILITY_PROMPT_OPTIONS;
6148
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
6149
+ shouldPrompt: false,
6150
+ mode: "single",
6151
+ options: [],
6152
+ autoValue: "none"
6153
+ };
6154
+ return context.observability !== void 0 ? {
6155
+ shouldPrompt: false,
6156
+ mode: "single",
6157
+ options,
6158
+ autoValue: context.observability
6159
+ } : {
6160
+ shouldPrompt: true,
6161
+ mode: "single",
6162
+ options,
6163
+ initialValue: "none"
6164
+ };
6165
+ }
6166
+ async function getObservabilityChoice(observability, backend, ecosystem) {
6167
+ const resolution = resolveObservabilityPrompt({
6168
+ observability,
6169
+ backend,
6170
+ ecosystem
6171
+ });
6172
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6173
+ const response = await navigableSelect({
6174
+ message: "Select observability solution",
6175
+ options: resolution.options,
6176
+ initialValue: resolution.initialValue
6177
+ });
6178
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6179
+ return response;
6180
+ }
6181
+
6182
+ //#endregion
6183
+ //#region src/prompts/orm.ts
6184
+ const ormOptions = {
6185
+ prisma: {
6186
+ value: "prisma",
6187
+ label: "Prisma",
6188
+ hint: "Powerful, feature-rich ORM"
6189
+ },
6190
+ mongoose: {
6191
+ value: "mongoose",
6192
+ label: "Mongoose",
6193
+ hint: "Elegant object modeling tool"
6194
+ },
6195
+ drizzle: {
6196
+ value: "drizzle",
6197
+ label: "Drizzle",
6198
+ hint: "Lightweight and performant TypeScript ORM"
6199
+ },
6200
+ typeorm: {
6201
+ value: "typeorm",
6202
+ label: "TypeORM",
6203
+ hint: "Traditional ORM with Active Record/Data Mapper"
6204
+ },
6205
+ kysely: {
6206
+ value: "kysely",
6207
+ label: "Kysely",
6208
+ hint: "Type-safe SQL query builder"
6209
+ },
6210
+ mikroorm: {
6211
+ value: "mikroorm",
6212
+ label: "MikroORM",
6213
+ hint: "Data Mapper ORM for DDD"
6214
+ },
6215
+ sequelize: {
6216
+ value: "sequelize",
6217
+ label: "Sequelize",
6218
+ hint: "Mature ORM with wide adoption"
6219
+ }
6220
+ };
6221
+ function resolveORMPrompt(context) {
6222
+ if (context.backend === "convex" || !context.hasDatabase) return {
6223
+ shouldPrompt: false,
6224
+ mode: "single",
6225
+ options: [],
6226
+ autoValue: "none"
6227
+ };
6228
+ if (context.database === "edgedb" || context.database === "redis") return {
6229
+ shouldPrompt: false,
6230
+ mode: "single",
6231
+ options: [],
6232
+ autoValue: "none"
6233
+ };
6234
+ if (context.orm !== void 0) return {
6235
+ shouldPrompt: false,
6236
+ mode: "single",
6237
+ options: [],
6238
+ autoValue: context.orm
6239
+ };
6240
+ return {
6241
+ shouldPrompt: true,
6242
+ mode: "single",
6243
+ options: context.database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [
6244
+ ormOptions.drizzle,
6245
+ ormOptions.prisma,
6246
+ ormOptions.typeorm,
6247
+ ormOptions.kysely,
6248
+ ormOptions.mikroorm,
6249
+ ormOptions.sequelize
6250
+ ],
6251
+ initialValue: context.database === "mongodb" ? "prisma" : context.runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
6252
+ };
6253
+ }
6254
+ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
6255
+ const resolution = resolveORMPrompt({
6256
+ orm,
6257
+ hasDatabase,
6258
+ database,
6259
+ backend,
6260
+ runtime
6261
+ });
6262
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6263
+ const response = await navigableSelect({
6264
+ message: "Select ORM",
6265
+ options: resolution.options,
6266
+ initialValue: resolution.initialValue
6267
+ });
6268
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6269
+ return response;
6270
+ }
6271
+
6272
+ //#endregion
6273
+ //#region src/prompts/payments.ts
6274
+ function resolvePaymentsPrompt(context = {}) {
6275
+ if (context.payments !== void 0) return {
6276
+ shouldPrompt: false,
6277
+ mode: "single",
6278
+ options: [],
6279
+ autoValue: context.payments
6280
+ };
6281
+ if (context.backend === "none") return {
6282
+ shouldPrompt: false,
6283
+ mode: "single",
6284
+ options: [],
6285
+ autoValue: "none"
6286
+ };
6287
+ const isPolarCompatible = context.auth === "better-auth" && (context.frontends?.length === 0 || splitFrontends$1(context.frontends).web.length > 0);
6288
+ const options = [];
6289
+ if (isPolarCompatible) options.push({
6290
+ value: "polar",
6291
+ label: "Polar",
6292
+ hint: "Turn your software into a business. 6 lines of code."
6293
+ });
6294
+ options.push({
6295
+ value: "stripe",
6296
+ label: "Stripe",
6297
+ hint: "Payment processing platform for internet businesses."
6298
+ }, {
6299
+ value: "lemon-squeezy",
6300
+ label: "Lemon Squeezy",
6301
+ hint: "All-in-one platform for SaaS, digital products, and subscriptions."
6302
+ }, {
6303
+ value: "paddle",
6304
+ label: "Paddle",
6305
+ hint: "Complete payments infrastructure for SaaS."
6306
+ }, {
6307
+ value: "dodo",
6308
+ label: "Dodo Payments",
6309
+ hint: "Simple payment infrastructure for developers."
6310
+ }, {
6311
+ value: "none",
6312
+ label: "None",
6313
+ hint: "No payments integration"
6314
+ });
6315
+ return {
6316
+ shouldPrompt: true,
6317
+ mode: "single",
6318
+ options,
6319
+ initialValue: DEFAULT_CONFIG.payments
6320
+ };
6321
+ }
6322
+ async function getPaymentsChoice(payments, auth, backend, frontends) {
6323
+ const resolution = resolvePaymentsPrompt({
6324
+ payments,
6325
+ auth,
6326
+ backend,
6327
+ frontends
6328
+ });
6329
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6330
+ const response = await navigableSelect({
6331
+ message: "Select payments provider",
6332
+ options: resolution.options,
6333
+ initialValue: resolution.initialValue
6334
+ });
6335
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6336
+ return response;
6337
+ }
6338
+
6339
+ //#endregion
6340
+ //#region src/prompts/realtime.ts
6341
+ const REALTIME_PROMPT_OPTIONS = [
6342
+ {
6343
+ value: "socket-io",
6344
+ label: "Socket.IO",
6345
+ hint: "Real-time bidirectional communication with fallbacks"
6346
+ },
6347
+ {
6348
+ value: "partykit",
6349
+ label: "PartyKit",
6350
+ hint: "Edge-native multiplayer infrastructure on Cloudflare"
6351
+ },
6352
+ {
6353
+ value: "ably",
6354
+ label: "Ably",
6355
+ hint: "Real-time messaging platform with pub/sub and presence"
6356
+ },
6357
+ {
6358
+ value: "pusher",
6359
+ label: "Pusher",
6360
+ hint: "Real-time communication APIs with channels and events"
6361
+ },
6362
+ {
6363
+ value: "liveblocks",
6364
+ label: "Liveblocks",
6365
+ hint: "Collaboration infrastructure for multiplayer experiences"
6366
+ },
6367
+ {
6368
+ value: "yjs",
6369
+ label: "Y.js",
6370
+ hint: "CRDT library for real-time collaboration with conflict-free sync"
6371
+ },
6372
+ {
6373
+ value: "none",
6374
+ label: "None",
6375
+ hint: "Skip real-time/WebSocket integration"
6376
+ }
6377
+ ];
6378
+ function resolveRealtimePrompt(context = {}) {
6379
+ if (context.backend === "none" || context.backend === "convex") return {
6380
+ shouldPrompt: false,
6381
+ mode: "single",
6382
+ options: [],
6383
+ autoValue: "none"
6384
+ };
6385
+ return context.realtime !== void 0 ? {
6386
+ shouldPrompt: false,
6387
+ mode: "single",
6388
+ options: REALTIME_PROMPT_OPTIONS,
6389
+ autoValue: context.realtime
6390
+ } : {
6391
+ shouldPrompt: true,
6392
+ mode: "single",
6393
+ options: REALTIME_PROMPT_OPTIONS,
6394
+ initialValue: "none"
6395
+ };
6396
+ }
6397
+ async function getRealtimeChoice(realtime, backend) {
6398
+ const resolution = resolveRealtimePrompt({
6399
+ realtime,
6400
+ backend
6401
+ });
6402
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6403
+ const response = await navigableSelect({
6404
+ message: "Select real-time solution",
6405
+ options: resolution.options,
6406
+ initialValue: resolution.initialValue
6407
+ });
6408
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6409
+ return response;
6410
+ }
6411
+
6412
+ //#endregion
6413
+ //#region src/prompts/runtime.ts
6414
+ const RUNTIME_PROMPT_OPTIONS = [
6415
+ {
6416
+ value: "bun",
6417
+ label: "Bun",
6418
+ hint: "Fast all-in-one JavaScript runtime"
6419
+ },
6420
+ {
6421
+ value: "node",
6422
+ label: "Node.js",
6423
+ hint: "Traditional Node.js runtime"
6424
+ },
6425
+ {
6426
+ value: "workers",
6427
+ label: "Cloudflare Workers",
6428
+ hint: "Edge runtime on Cloudflare's global network"
6429
+ }
6430
+ ];
6431
+ function resolveRuntimePrompt(context = {}) {
6432
+ if (context.backend === "convex" || context.backend === "none" || context.backend === "self") return {
6433
+ shouldPrompt: false,
6434
+ mode: "single",
6435
+ options: [],
6436
+ autoValue: "none"
6437
+ };
6438
+ const options = RUNTIME_PROMPT_OPTIONS.filter((option) => option.value !== "workers" || context.backend === "hono");
6439
+ return context.runtime !== void 0 ? {
6440
+ shouldPrompt: false,
6441
+ mode: "single",
6442
+ options,
6443
+ autoValue: context.runtime
6444
+ } : {
6445
+ shouldPrompt: true,
6446
+ mode: "single",
6447
+ options,
6448
+ initialValue: DEFAULT_CONFIG.runtime
6449
+ };
6450
+ }
6451
+ async function getRuntimeChoice(runtime, backend) {
6452
+ const resolution = resolveRuntimePrompt({
6453
+ runtime,
6454
+ backend
6455
+ });
6456
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6457
+ const response = await navigableSelect({
6458
+ message: "Select runtime",
6459
+ options: resolution.options,
6460
+ initialValue: resolution.initialValue
6461
+ });
6462
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6463
+ return response;
6464
+ }
6465
+
6466
+ //#endregion
6467
+ //#region src/prompts/search.ts
6468
+ const SEARCH_PROMPT_OPTIONS = [
6469
+ {
6470
+ value: "meilisearch",
6471
+ label: "Meilisearch",
6472
+ hint: "Lightning-fast search engine with typo tolerance"
6473
+ },
6474
+ {
6475
+ value: "typesense",
6476
+ label: "Typesense",
6477
+ hint: "Fast, typo-tolerant search with built-in vector search"
6478
+ },
6479
+ {
6480
+ value: "elasticsearch",
6481
+ label: "Elasticsearch",
6482
+ hint: "Distributed search and analytics engine with local and cloud deployments"
6483
+ },
6484
+ {
6485
+ value: "algolia",
6486
+ label: "Algolia",
6487
+ hint: "Hosted search API with instant results, typo tolerance, and analytics"
6488
+ },
6489
+ {
6490
+ value: "none",
6491
+ label: "None",
6492
+ hint: "Skip search engine setup"
6493
+ }
6494
+ ];
6495
+ const NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS = SEARCH_PROMPT_OPTIONS.filter((option) => option.value === "meilisearch" || option.value === "none");
6496
+ function resolveSearchPrompt(context = {}) {
6497
+ if (context.ecosystem === "react-native" || context.ecosystem === "elixir") return {
6498
+ shouldPrompt: false,
6499
+ mode: "single",
6500
+ options: [],
6501
+ autoValue: "none"
6502
+ };
6503
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS : SEARCH_PROMPT_OPTIONS;
6504
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
6505
+ shouldPrompt: false,
6506
+ mode: "single",
6507
+ options: [],
6508
+ autoValue: "none"
6509
+ };
6510
+ return context.search !== void 0 ? {
6511
+ shouldPrompt: false,
6512
+ mode: "single",
6513
+ options,
6514
+ autoValue: context.search
6515
+ } : {
6516
+ shouldPrompt: true,
6517
+ mode: "single",
6518
+ options,
6519
+ initialValue: "none"
6520
+ };
6521
+ }
6522
+ async function getSearchChoice(search, backend, ecosystem) {
6523
+ const resolution = resolveSearchPrompt({
6524
+ search,
6525
+ backend,
6526
+ ecosystem
6527
+ });
6528
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6529
+ const response = await navigableSelect({
6530
+ message: "Select search engine",
6531
+ options: resolution.options,
6532
+ initialValue: resolution.initialValue
6533
+ });
6534
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6535
+ return response;
6536
+ }
6537
+
6538
+ //#endregion
6539
+ //#region src/prompts/server-deploy.ts
6540
+ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeploy) {
6541
+ if (deployment !== void 0) return deployment;
6542
+ if (backend === "none" || backend === "convex") return "none";
6543
+ if (backend !== "hono") return "none";
6544
+ if (runtime === "workers") return "cloudflare";
6545
+ return "none";
6546
+ }
6547
+
6548
+ //#endregion
6549
+ //#region src/prompts/state-management.ts
6550
+ function resolveStateManagementPrompt(context = {}) {
6551
+ if (context.stateManagement !== void 0) return {
6552
+ shouldPrompt: false,
6553
+ mode: "single",
6554
+ options: [],
6555
+ autoValue: context.stateManagement
6556
+ };
6557
+ const { web } = splitFrontends$1(context.frontends);
6558
+ if (web.length === 0) return {
6559
+ shouldPrompt: false,
6560
+ mode: "single",
6561
+ options: [],
6562
+ autoValue: "none"
6563
+ };
6564
+ const isReact = web.some((f) => [
6565
+ "tanstack-router",
6566
+ "react-router",
6567
+ "react-vite",
6568
+ "tanstack-start",
6569
+ "next",
6570
+ "vinext",
6571
+ "redwood"
6572
+ ].includes(f));
6573
+ const isFresh = web.includes("fresh");
6574
+ const options = [];
6575
+ if (isReact) options.push({
6576
+ value: "zustand",
6577
+ label: "Zustand",
6578
+ hint: "Lightweight state management with simple API"
6579
+ }, {
6580
+ value: "jotai",
6581
+ label: "Jotai",
6582
+ hint: "Primitive and flexible atomic state"
6583
+ }, {
6584
+ value: "redux-toolkit",
6585
+ label: "Redux Toolkit",
6586
+ hint: "Enterprise-standard state with excellent TS support"
6587
+ }, {
6588
+ value: "valtio",
6589
+ label: "Valtio",
6590
+ hint: "Proxy-based state management"
6591
+ }, {
6592
+ value: "legend-state",
6593
+ label: "Legend State",
6594
+ hint: "High-performance observable state for React"
6595
+ }, {
6596
+ value: "mobx",
6597
+ label: "MobX",
6598
+ hint: "Observable-based reactive state management"
6599
+ });
6600
+ if (!isFresh) options.push({
6601
+ value: "nanostores",
6602
+ label: "Nanostores",
6603
+ hint: "Tiny state manager (1KB) for all frameworks"
6604
+ }, {
6605
+ value: "xstate",
6606
+ label: "XState",
6607
+ hint: "State machines and statecharts for complex logic"
6608
+ }, {
6609
+ value: "tanstack-store",
6610
+ label: "TanStack Store",
6611
+ hint: "Framework-agnostic store powering TanStack ecosystem"
6612
+ });
6613
+ options.push({
6614
+ value: "none",
6615
+ label: "None",
6616
+ hint: "Skip state management setup"
6617
+ });
6618
+ return {
6619
+ shouldPrompt: true,
6620
+ mode: "single",
6621
+ options,
6622
+ initialValue: "none"
6623
+ };
6624
+ }
6625
+ async function getStateManagementChoice(stateManagement, frontends) {
6626
+ const resolution = resolveStateManagementPrompt({
6627
+ stateManagement,
6628
+ frontends
6629
+ });
6630
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6631
+ const response = await navigableSelect({
6632
+ message: "Select state management",
6633
+ options: resolution.options,
6634
+ initialValue: resolution.initialValue
6635
+ });
6636
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6637
+ return response;
6638
+ }
6639
+
6640
+ //#endregion
6641
+ //#region src/prompts/testing.ts
6642
+ const TESTING_PROMPT_OPTIONS = [
6643
+ {
6644
+ value: "vitest",
6645
+ label: "Vitest",
6646
+ hint: "Blazing fast Vite-native unit test framework"
6647
+ },
6648
+ {
6649
+ value: "vitest-playwright",
6650
+ label: "Vitest + Playwright",
6651
+ hint: "Both unit and E2E testing for complete coverage"
6205
6652
  },
6206
- "base-ui": {
6207
- label: "Base UI",
6208
- hint: "Unstyled, accessible components from MUI team (Radix successor)"
6653
+ {
6654
+ value: "playwright",
6655
+ label: "Playwright",
6656
+ hint: "End-to-end testing framework by Microsoft"
6209
6657
  },
6210
- "ark-ui": {
6211
- label: "Ark UI",
6212
- hint: "Headless, accessible UI components for React, Vue, Solid, and Svelte"
6658
+ {
6659
+ value: "jest",
6660
+ label: "Jest",
6661
+ hint: "Classic testing framework with wide ecosystem"
6213
6662
  },
6214
- "react-aria": {
6215
- label: "React Aria",
6216
- hint: "Adobe's accessible, unstyled UI components for React"
6663
+ {
6664
+ value: "cypress",
6665
+ label: "Cypress",
6666
+ hint: "E2E testing with time travel debugging"
6217
6667
  },
6218
- none: {
6668
+ {
6669
+ value: "none",
6219
6670
  label: "None",
6220
- hint: "No UI component library"
6671
+ hint: "Skip testing framework setup"
6221
6672
  }
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
- };
6673
+ ];
6674
+ function resolveTestingPrompt(testing) {
6675
+ return createStaticSinglePromptResolution(TESTING_PROMPT_OPTIONS, "vitest", testing);
6253
6676
  }
6254
- async function getUILibraryChoice(uiLibrary, frontends, astroIntegration) {
6255
- const resolution = resolveUILibraryPrompt({
6256
- uiLibrary,
6257
- frontends,
6258
- astroIntegration
6259
- });
6677
+ async function getTestingChoice(testing) {
6678
+ const resolution = resolveTestingPrompt(testing);
6260
6679
  if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
6261
- const selected = await navigableSelect({
6262
- message: "Select UI component library",
6680
+ const response = await navigableSelect({
6681
+ message: "Select testing framework",
6263
6682
  options: resolution.options,
6264
6683
  initialValue: resolution.initialValue
6265
6684
  });
6266
- if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
6267
- return selected;
6685
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6686
+ return response;
6268
6687
  }
6269
6688
 
6270
6689
  //#endregion
@@ -6326,71 +6745,12 @@ async function getValidationChoice(validation) {
6326
6745
  return response;
6327
6746
  }
6328
6747
 
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
6748
  //#endregion
6392
6749
  //#region src/prompts/config-prompts.ts
6393
6750
  async function gatherConfig(flags, projectName, projectDir, relativePath) {
6751
+ if (flags.ecosystem === void 0 && flags.stackParts === void 0) {
6752
+ if (await getCompositionModeChoice() === "multi") return gatherMultiEcosystemConfig(flags, projectName, projectDir, relativePath);
6753
+ }
6394
6754
  const result = await navigableGroup({
6395
6755
  ecosystem: () => getEcosystemChoice(flags.ecosystem),
6396
6756
  frontend: ({ results }) => {
@@ -7017,6 +7377,15 @@ async function trackProjectCreation(config, disableAnalytics = false) {
7017
7377
 
7018
7378
  //#endregion
7019
7379
  //#region src/utils/display-config.ts
7380
+ function getSelectedGraphPart(config, role, ownerPartId) {
7381
+ return config.stackParts?.find((part) => part.role === role && part.ownerPartId === ownerPartId && part.source !== "provided");
7382
+ }
7383
+ function getGraphDisplayValue(config, role) {
7384
+ const primaryBackend = getSelectedGraphPart(config, "backend");
7385
+ const part = role === "backend" ? primaryBackend : getSelectedGraphPart(config, role, primaryBackend?.id) ?? getSelectedGraphPart(config, role);
7386
+ if (!part) return null;
7387
+ return `${part.ecosystem}:${part.toolId}`;
7388
+ }
7020
7389
  function displayConfig(config) {
7021
7390
  const configDisplay = [];
7022
7391
  if (config.projectName) configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
@@ -7027,14 +7396,29 @@ function displayConfig(config) {
7027
7396
  }
7028
7397
  if (config.uiLibrary !== void 0) configDisplay.push(`${pc.blue("UI Library:")} ${String(config.uiLibrary)}`);
7029
7398
  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)}`);
7399
+ if (config.backend !== void 0) {
7400
+ const graphBackend = config.backend === "none" ? getGraphDisplayValue(config, "backend") : null;
7401
+ configDisplay.push(`${pc.blue("Backend:")} ${graphBackend ?? String(config.backend)}`);
7402
+ }
7031
7403
  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)}`);
7404
+ if (config.api !== void 0) {
7405
+ const graphApi = config.api === "none" ? getGraphDisplayValue(config, "api") : null;
7406
+ configDisplay.push(`${pc.blue("API:")} ${graphApi ?? String(config.api)}`);
7407
+ }
7033
7408
  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)}`);
7035
- if (config.auth !== void 0) configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`);
7409
+ if (config.orm !== void 0) {
7410
+ const graphOrm = config.orm === "none" ? getGraphDisplayValue(config, "orm") : null;
7411
+ configDisplay.push(`${pc.blue("ORM:")} ${graphOrm ?? String(config.orm)}`);
7412
+ }
7413
+ if (config.auth !== void 0) {
7414
+ const graphAuth = config.auth === "none" ? getGraphDisplayValue(config, "auth") : null;
7415
+ configDisplay.push(`${pc.blue("Auth:")} ${graphAuth ?? String(config.auth)}`);
7416
+ }
7036
7417
  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)}`);
7418
+ if (config.email !== void 0) {
7419
+ const graphEmail = config.email === "none" ? getGraphDisplayValue(config, "email") : null;
7420
+ configDisplay.push(`${pc.blue("Email:")} ${graphEmail ?? String(config.email)}`);
7421
+ }
7038
7422
  if (config.fileUpload !== void 0) configDisplay.push(`${pc.blue("File Upload:")} ${String(config.fileUpload)}`);
7039
7423
  if (config.effect !== void 0) configDisplay.push(`${pc.blue("Effect:")} ${String(config.effect)}`);
7040
7424
  if (config.ai !== void 0) configDisplay.push(`${pc.blue("AI:")} ${String(config.ai)}`);
@@ -7046,6 +7430,42 @@ function displayConfig(config) {
7046
7430
  if (config.realtime !== void 0) configDisplay.push(`${pc.blue("Realtime:")} ${String(config.realtime)}`);
7047
7431
  if (config.jobQueue !== void 0) configDisplay.push(`${pc.blue("Job Queue:")} ${String(config.jobQueue)}`);
7048
7432
  if (config.logging !== void 0) configDisplay.push(`${pc.blue("Logging:")} ${String(config.logging)}`);
7433
+ const graphBackendPart = getSelectedGraphPart(config, "backend");
7434
+ if (graphBackendPart?.ecosystem === "go") {
7435
+ if (config.goCli && config.goCli !== "none") configDisplay.push(`${pc.blue("Go CLI:")} ${String(config.goCli)}`);
7436
+ if (config.goLogging && config.goLogging !== "none") configDisplay.push(`${pc.blue("Go Logging:")} ${String(config.goLogging)}`);
7437
+ }
7438
+ if (graphBackendPart?.ecosystem === "rust") {
7439
+ if (config.rustCli && config.rustCli !== "none") configDisplay.push(`${pc.blue("Rust CLI:")} ${String(config.rustCli)}`);
7440
+ if (config.rustLibraries && config.rustLibraries.length > 0) configDisplay.push(`${pc.blue("Rust Libraries:")} ${config.rustLibraries.join(", ")}`);
7441
+ if (config.rustLogging && config.rustLogging !== "none") configDisplay.push(`${pc.blue("Rust Logging:")} ${String(config.rustLogging)}`);
7442
+ if (config.rustErrorHandling && config.rustErrorHandling !== "none") configDisplay.push(`${pc.blue("Rust Error Handling:")} ${String(config.rustErrorHandling)}`);
7443
+ if (config.rustCaching && config.rustCaching !== "none") configDisplay.push(`${pc.blue("Rust Caching:")} ${String(config.rustCaching)}`);
7444
+ }
7445
+ if (graphBackendPart?.ecosystem === "python") {
7446
+ if (config.pythonValidation && config.pythonValidation !== "none") configDisplay.push(`${pc.blue("Python Validation:")} ${String(config.pythonValidation)}`);
7447
+ if (config.pythonAi && config.pythonAi.length > 0) configDisplay.push(`${pc.blue("Python AI:")} ${config.pythonAi.join(", ")}`);
7448
+ if (config.pythonTaskQueue && config.pythonTaskQueue !== "none") configDisplay.push(`${pc.blue("Python Task Queue:")} ${String(config.pythonTaskQueue)}`);
7449
+ if (config.pythonGraphql && config.pythonGraphql !== "none") configDisplay.push(`${pc.blue("Python GraphQL:")} ${String(config.pythonGraphql)}`);
7450
+ if (config.pythonQuality && config.pythonQuality !== "none") configDisplay.push(`${pc.blue("Python Quality:")} ${String(config.pythonQuality)}`);
7451
+ }
7452
+ if (graphBackendPart?.ecosystem === "java") {
7453
+ if (config.javaBuildTool && config.javaBuildTool !== "none") configDisplay.push(`${pc.blue("Java Build Tool:")} ${String(config.javaBuildTool)}`);
7454
+ if (config.javaLibraries && config.javaLibraries.length > 0) configDisplay.push(`${pc.blue("Java Libraries:")} ${config.javaLibraries.join(", ")}`);
7455
+ if (config.javaTestingLibraries && config.javaTestingLibraries.length > 0) configDisplay.push(`${pc.blue("Java Testing Libraries:")} ${config.javaTestingLibraries.join(", ")}`);
7456
+ }
7457
+ if (graphBackendPart?.ecosystem === "elixir") {
7458
+ if (config.elixirRealtime && config.elixirRealtime !== "none") configDisplay.push(`${pc.blue("Elixir Realtime:")} ${String(config.elixirRealtime)}`);
7459
+ if (config.elixirJobs && config.elixirJobs !== "none") configDisplay.push(`${pc.blue("Elixir Jobs:")} ${String(config.elixirJobs)}`);
7460
+ if (config.elixirValidation && config.elixirValidation !== "none") configDisplay.push(`${pc.blue("Elixir Validation:")} ${String(config.elixirValidation)}`);
7461
+ if (config.elixirHttp && config.elixirHttp !== "none") configDisplay.push(`${pc.blue("Elixir HTTP:")} ${String(config.elixirHttp)}`);
7462
+ if (config.elixirJson && config.elixirJson !== "none") configDisplay.push(`${pc.blue("Elixir JSON:")} ${String(config.elixirJson)}`);
7463
+ if (config.elixirCaching && config.elixirCaching !== "none") configDisplay.push(`${pc.blue("Elixir Caching:")} ${String(config.elixirCaching)}`);
7464
+ if (config.elixirObservability && config.elixirObservability !== "none") configDisplay.push(`${pc.blue("Elixir Observability:")} ${String(config.elixirObservability)}`);
7465
+ if (config.elixirTesting && config.elixirTesting !== "none") configDisplay.push(`${pc.blue("Elixir Testing:")} ${String(config.elixirTesting)}`);
7466
+ if (config.elixirQuality && config.elixirQuality !== "none") configDisplay.push(`${pc.blue("Elixir Quality:")} ${String(config.elixirQuality)}`);
7467
+ if (config.elixirDeploy && config.elixirDeploy !== "none") configDisplay.push(`${pc.blue("Elixir Deploy:")} ${String(config.elixirDeploy)}`);
7468
+ }
7049
7469
  if (config.observability !== void 0) configDisplay.push(`${pc.blue("Observability:")} ${String(config.observability)}`);
7050
7470
  if (config.featureFlags !== void 0) configDisplay.push(`${pc.blue("Feature Flags:")} ${String(config.featureFlags)}`);
7051
7471
  if (config.analytics !== void 0) configDisplay.push(`${pc.blue("Analytics:")} ${String(config.analytics)}`);
@@ -7089,6 +7509,10 @@ function displayConfig(config) {
7089
7509
  if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
7090
7510
  if (config.webDeploy !== void 0) configDisplay.push(`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`);
7091
7511
  if (config.serverDeploy !== void 0) configDisplay.push(`${pc.blue("Server Deployment:")} ${String(config.serverDeploy)}`);
7512
+ if (config.stackParts?.length) {
7513
+ const stackParts = config.stackParts.filter((part) => part.source !== "provided").map((part) => formatStackPartSpec(part, config.stackParts ?? []));
7514
+ if (stackParts.length > 0) configDisplay.push(`${pc.blue("Stack Parts:")} ${stackParts.join(", ")}`);
7515
+ }
7092
7516
  if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
7093
7517
  return configDisplay.join("\n");
7094
7518
  }
@@ -7117,6 +7541,107 @@ function appendCommonFlags(flags, config) {
7117
7541
  if (config.versionChannel !== "stable") flags.push(`--version-channel ${config.versionChannel}`);
7118
7542
  flags.push(config.install ? "--install" : "--no-install");
7119
7543
  }
7544
+ function hasGraphPrimaryPart(config, role, ecosystem) {
7545
+ return config.stackParts?.some((part) => part.source !== "provided" && part.role === role && !part.ownerPartId && (!ecosystem || part.ecosystem === ecosystem));
7546
+ }
7547
+ function appendChangedStringFlag(flags, flag, value, defaultValue) {
7548
+ if (value !== defaultValue) flags.push(`--${flag} ${value}`);
7549
+ }
7550
+ function appendChangedArrayFlag(flags, flag, values, defaultValues) {
7551
+ if (values.length !== defaultValues.length || values.some((value, index) => value !== defaultValues[index])) flags.push(formatArrayFlag(flag, values));
7552
+ }
7553
+ function appendAstroIntegrationFlag(flags, config) {
7554
+ if (config.frontend.includes("astro") && config.astroIntegration !== "none") flags.push(`--astro-integration ${config.astroIntegration}`);
7555
+ }
7556
+ function appendGraphExtraFlags(flags, config) {
7557
+ appendChangedArrayFlag(flags, "addons", config.addons, ["turborepo"]);
7558
+ appendChangedArrayFlag(flags, "examples", config.examples, []);
7559
+ appendChangedStringFlag(flags, "db-setup", config.dbSetup, "none");
7560
+ appendChangedStringFlag(flags, "web-deploy", config.webDeploy, "none");
7561
+ appendChangedStringFlag(flags, "server-deploy", config.serverDeploy, "none");
7562
+ if (hasGraphPrimaryPart(config, "frontend", "typescript")) {
7563
+ appendAstroIntegrationFlag(flags, config);
7564
+ appendChangedStringFlag(flags, "css-framework", config.cssFramework, "tailwind");
7565
+ appendChangedStringFlag(flags, "ui-library", config.uiLibrary, "shadcn-ui");
7566
+ if (config.uiLibrary === "shadcn-ui") {
7567
+ appendChangedStringFlag(flags, "shadcn-base", config.shadcnBase ?? "radix", "radix");
7568
+ appendChangedStringFlag(flags, "shadcn-style", config.shadcnStyle ?? "nova", "nova");
7569
+ appendChangedStringFlag(flags, "shadcn-icon-library", config.shadcnIconLibrary ?? "lucide", "lucide");
7570
+ appendChangedStringFlag(flags, "shadcn-color-theme", config.shadcnColorTheme ?? "neutral", "neutral");
7571
+ appendChangedStringFlag(flags, "shadcn-base-color", config.shadcnBaseColor ?? "neutral", "neutral");
7572
+ appendChangedStringFlag(flags, "shadcn-font", config.shadcnFont ?? "inter", "inter");
7573
+ appendChangedStringFlag(flags, "shadcn-radius", config.shadcnRadius ?? "default", "default");
7574
+ }
7575
+ appendChangedStringFlag(flags, "state-management", config.stateManagement, "none");
7576
+ appendChangedStringFlag(flags, "forms", config.forms, "react-hook-form");
7577
+ appendChangedStringFlag(flags, "validation", config.validation, "zod");
7578
+ appendChangedStringFlag(flags, "testing", config.testing, "vitest");
7579
+ appendChangedStringFlag(flags, "animation", config.animation, "none");
7580
+ }
7581
+ if (hasGraphPrimaryPart(config, "frontend", "typescript") || hasGraphPrimaryPart(config, "backend", "typescript")) {
7582
+ appendChangedStringFlag(flags, "payments", config.payments, "none");
7583
+ appendChangedStringFlag(flags, "email", config.email, "none");
7584
+ appendChangedStringFlag(flags, "file-upload", config.fileUpload, "none");
7585
+ appendChangedStringFlag(flags, "effect", config.effect, "none");
7586
+ appendChangedStringFlag(flags, "ai", config.ai, "none");
7587
+ appendChangedStringFlag(flags, "realtime", config.realtime, "none");
7588
+ appendChangedStringFlag(flags, "job-queue", config.jobQueue, "none");
7589
+ appendChangedStringFlag(flags, "logging", config.logging, "none");
7590
+ appendChangedStringFlag(flags, "observability", config.observability, "none");
7591
+ appendChangedStringFlag(flags, "feature-flags", config.featureFlags, "none");
7592
+ appendChangedStringFlag(flags, "caching", config.caching, "none");
7593
+ appendChangedStringFlag(flags, "i18n", config.i18n, "none");
7594
+ appendChangedStringFlag(flags, "cms", config.cms, "none");
7595
+ appendChangedStringFlag(flags, "search", config.search, "none");
7596
+ appendChangedStringFlag(flags, "file-storage", config.fileStorage, "none");
7597
+ }
7598
+ if (hasGraphPrimaryPart(config, "mobile")) {
7599
+ appendChangedStringFlag(flags, "mobile-navigation", config.mobileNavigation, "expo-router");
7600
+ appendChangedStringFlag(flags, "mobile-ui", config.mobileUI, "none");
7601
+ appendChangedStringFlag(flags, "mobile-storage", config.mobileStorage, "none");
7602
+ appendChangedStringFlag(flags, "mobile-testing", config.mobileTesting, "none");
7603
+ appendChangedStringFlag(flags, "mobile-push", config.mobilePush, "none");
7604
+ appendChangedStringFlag(flags, "mobile-ota", config.mobileOTA, "none");
7605
+ appendChangedStringFlag(flags, "mobile-deep-linking", config.mobileDeepLinking, "none");
7606
+ }
7607
+ if (hasGraphPrimaryPart(config, "frontend", "rust")) appendChangedStringFlag(flags, "rust-frontend", config.rustFrontend, "none");
7608
+ if (hasGraphPrimaryPart(config, "backend", "rust")) {
7609
+ appendChangedStringFlag(flags, "rust-cli", config.rustCli, "none");
7610
+ appendChangedArrayFlag(flags, "rust-libraries", config.rustLibraries, []);
7611
+ appendChangedStringFlag(flags, "rust-logging", config.rustLogging, "tracing");
7612
+ appendChangedStringFlag(flags, "rust-error-handling", config.rustErrorHandling, "anyhow-thiserror");
7613
+ appendChangedStringFlag(flags, "rust-caching", config.rustCaching, "none");
7614
+ }
7615
+ if (hasGraphPrimaryPart(config, "backend", "python")) {
7616
+ appendChangedStringFlag(flags, "python-validation", config.pythonValidation, "none");
7617
+ appendChangedArrayFlag(flags, "python-ai", config.pythonAi, []);
7618
+ appendChangedStringFlag(flags, "python-task-queue", config.pythonTaskQueue, "none");
7619
+ appendChangedStringFlag(flags, "python-graphql", config.pythonGraphql, "none");
7620
+ appendChangedStringFlag(flags, "python-quality", config.pythonQuality, "none");
7621
+ }
7622
+ if (hasGraphPrimaryPart(config, "backend", "go")) {
7623
+ appendChangedStringFlag(flags, "go-cli", config.goCli, "none");
7624
+ appendChangedStringFlag(flags, "go-logging", config.goLogging, "none");
7625
+ }
7626
+ if (hasGraphPrimaryPart(config, "backend", "java")) {
7627
+ appendChangedStringFlag(flags, "java-build-tool", config.javaBuildTool, "maven");
7628
+ appendChangedArrayFlag(flags, "java-libraries", config.javaLibraries, []);
7629
+ appendChangedArrayFlag(flags, "java-testing-libraries", config.javaTestingLibraries, ["junit5"]);
7630
+ }
7631
+ if (hasGraphPrimaryPart(config, "backend", "elixir")) {
7632
+ appendChangedStringFlag(flags, "elixir-realtime", config.elixirRealtime, "channels");
7633
+ appendChangedStringFlag(flags, "elixir-jobs", config.elixirJobs, "none");
7634
+ appendChangedStringFlag(flags, "elixir-validation", config.elixirValidation, "ecto-changesets");
7635
+ appendChangedStringFlag(flags, "elixir-http", config.elixirHttp, "req");
7636
+ appendChangedStringFlag(flags, "elixir-json", config.elixirJson, "jason");
7637
+ if (!hasGraphPart(config, "email", "elixir")) appendChangedStringFlag(flags, "elixir-email", config.elixirEmail, "none");
7638
+ appendChangedStringFlag(flags, "elixir-caching", config.elixirCaching, "none");
7639
+ appendChangedStringFlag(flags, "elixir-observability", config.elixirObservability, "telemetry");
7640
+ appendChangedStringFlag(flags, "elixir-testing", config.elixirTesting, "ex_unit");
7641
+ appendChangedStringFlag(flags, "elixir-quality", config.elixirQuality, "credo");
7642
+ appendChangedStringFlag(flags, "elixir-deploy", config.elixirDeploy, "none");
7643
+ }
7644
+ }
7120
7645
  function appendSharedNonTypeScriptFlags(flags, config) {
7121
7646
  flags.push(`--email ${config.email}`);
7122
7647
  flags.push(`--observability ${config.observability}`);
@@ -7132,6 +7657,7 @@ function getTypeScriptFlags(config) {
7132
7657
  const flags = [];
7133
7658
  if (config.frontend && config.frontend.length > 0) flags.push(`--frontend ${config.frontend.join(" ")}`);
7134
7659
  else flags.push("--frontend none");
7660
+ appendAstroIntegrationFlag(flags, config);
7135
7661
  flags.push(`--backend ${config.backend}`);
7136
7662
  flags.push(`--runtime ${config.runtime}`);
7137
7663
  flags.push(`--database ${config.database}`);
@@ -7279,6 +7805,12 @@ function getElixirFlags(config) {
7279
7805
  }
7280
7806
  function generateReproducibleCommand(config) {
7281
7807
  let flags;
7808
+ if (config.stackParts && config.stackParts.length > 0) {
7809
+ flags = config.stackParts.filter((part) => part.source !== "provided").map((part) => `--part ${(0, types_exports.formatStackPartSpec)(part, config.stackParts ?? [])}`);
7810
+ appendGraphExtraFlags(flags, config);
7811
+ appendCommonFlags(flags, config);
7812
+ return `${getBaseCommand(config.packageManager)}${config.relativePath ? ` ${config.relativePath}` : ""} ${flags.join(" ")}`;
7813
+ }
7282
7814
  switch (config.ecosystem) {
7283
7815
  case "react-native":
7284
7816
  flags = getReactNativeFlags(config);
@@ -7306,6 +7838,98 @@ function generateReproducibleCommand(config) {
7306
7838
  return `${getBaseCommand(config.packageManager)}${config.relativePath ? ` ${config.relativePath}` : ""} ${flags.join(" ")}`;
7307
7839
  }
7308
7840
 
7841
+ //#endregion
7842
+ //#region src/utils/generated-checks.ts
7843
+ function getGraphTarget(config) {
7844
+ const backend = getPrimaryGraphPart(config, "backend");
7845
+ if (!backend || backend.ecosystem === "typescript" || backend.ecosystem === "react-native" || backend.ecosystem === "universal") return null;
7846
+ return {
7847
+ ecosystem: backend.ecosystem,
7848
+ projectDir: path.join(config.projectDir, backend.targetPath ?? "apps/server")
7849
+ };
7850
+ }
7851
+ function getSingleEcosystemTarget(config) {
7852
+ if (config.ecosystem === "typescript" || config.ecosystem === "react-native" || config.ecosystem === "java") return null;
7853
+ return {
7854
+ ecosystem: config.ecosystem,
7855
+ projectDir: config.projectDir
7856
+ };
7857
+ }
7858
+ async function runCommand(cwd, command, args) {
7859
+ await $({
7860
+ cwd,
7861
+ stdout: "inherit",
7862
+ stderr: "inherit"
7863
+ })`${command} ${args}`;
7864
+ }
7865
+ async function verifyTarget(target) {
7866
+ const s = spinner();
7867
+ const cwd = target.projectDir;
7868
+ switch (target.ecosystem) {
7869
+ case "go":
7870
+ s.start("Verifying generated Go server...");
7871
+ await runCommand(cwd, "go", ["mod", "tidy"]);
7872
+ await runCommand(cwd, "go", ["test", "./..."]);
7873
+ s.stop("Generated Go server checks passed");
7874
+ return;
7875
+ case "rust":
7876
+ s.start("Verifying generated Rust server...");
7877
+ await runCommand(cwd, "cargo", ["check"]);
7878
+ s.stop("Generated Rust server checks passed");
7879
+ return;
7880
+ case "python":
7881
+ s.start("Verifying generated Python server...");
7882
+ await runCommand(cwd, "uv", ["sync"]);
7883
+ await runCommand(cwd, "uv", [
7884
+ "run",
7885
+ "ruff",
7886
+ "check",
7887
+ "."
7888
+ ]);
7889
+ s.stop("Generated Python server checks passed");
7890
+ return;
7891
+ case "elixir":
7892
+ if (!await commandExists("mix")) {
7893
+ log.warn(pc.yellow("Skipping Elixir verification because mix is not on PATH"));
7894
+ return;
7895
+ }
7896
+ s.start("Verifying generated Elixir server...");
7897
+ await runCommand(cwd, "mix", ["deps.get"]);
7898
+ await runCommand(cwd, "mix", ["compile"]);
7899
+ s.stop("Generated Elixir server checks passed");
7900
+ return;
7901
+ default: log.warn(pc.yellow(`No generated checks are configured for ${target.ecosystem}`));
7902
+ }
7903
+ }
7904
+ async function runGeneratedChecks(config) {
7905
+ const target = getGraphTarget(config) ?? getSingleEcosystemTarget(config);
7906
+ if (!target) {
7907
+ log.warn(pc.yellow("No generated checks are configured for this stack"));
7908
+ return;
7909
+ }
7910
+ await verifyTarget(target);
7911
+ }
7912
+
7913
+ //#endregion
7914
+ //#region src/utils/preflight-display.ts
7915
+ function displayPreflightWarnings({ warnings }) {
7916
+ if (warnings.length === 0) return;
7917
+ const count = warnings.length;
7918
+ const lines = [pc.bold(pc.yellow(`${count} feature${count > 1 ? "s" : ""} will not generate templates:`)), ""];
7919
+ warnings.forEach((w, i) => {
7920
+ const selected = Array.isArray(w.selectedValue) ? w.selectedValue.join(", ") : w.selectedValue;
7921
+ lines.push(` ${pc.yellow(`${i + 1}.`)} ${pc.bold(w.featureDisplayName)} ${pc.dim(`(${selected})`)}`);
7922
+ lines.push(` ${w.reason}`);
7923
+ w.suggestions.forEach((s) => lines.push(` ${pc.green("•")} ${s}`));
7924
+ if (i < count - 1) lines.push("");
7925
+ });
7926
+ consola.box({
7927
+ title: pc.yellow("Pre-flight Check"),
7928
+ message: lines.join("\n"),
7929
+ style: { borderColor: "yellow" }
7930
+ });
7931
+ }
7932
+
7309
7933
  //#endregion
7310
7934
  //#region src/utils/project-directory.ts
7311
7935
  async function handleDirectoryConflict(currentPathInput) {
@@ -7656,6 +8280,10 @@ function validateDatabaseOrmAuth(cfg, flags) {
7656
8280
  const db = cfg.database;
7657
8281
  const orm = cfg.orm;
7658
8282
  const has = (k) => flags ? flags.has(k) : true;
8283
+ const hasGraphOrm = cfg.stackParts?.some((part) => part.role === "orm" && part.source !== "provided");
8284
+ const ecosystemOrm = getEcosystemOrm(cfg);
8285
+ const hasEcosystemOrm = ecosystemOrm !== void 0 && ecosystemOrm !== "none";
8286
+ const isNonTypeScriptSqliteDefault = cfg.ecosystem !== void 0 && cfg.ecosystem !== "typescript" && cfg.ecosystem !== "react-native" && db === "sqlite" && !hasEcosystemOrm;
7659
8287
  if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") incompatibilityError({
7660
8288
  message: "Mongoose ORM requires MongoDB database.",
7661
8289
  provided: {
@@ -7712,7 +8340,7 @@ function validateDatabaseOrmAuth(cfg, flags) {
7712
8340
  },
7713
8341
  suggestions: ["Use --orm mongoose", "Use --orm prisma"]
7714
8342
  });
7715
- if (has("database") && has("orm") && db && db !== "none" && db !== "edgedb" && db !== "redis" && orm === "none") missingRequirementError({
8343
+ if (has("database") && has("orm") && db && db !== "none" && db !== "edgedb" && db !== "redis" && orm === "none" && !hasGraphOrm && !hasEcosystemOrm && !isNonTypeScriptSqliteDefault) missingRequirementError({
7716
8344
  message: "Database selection requires an ORM.",
7717
8345
  provided: {
7718
8346
  database: db,
@@ -7754,6 +8382,26 @@ function validateDatabaseOrmAuth(cfg, flags) {
7754
8382
  ]
7755
8383
  });
7756
8384
  }
8385
+ function getEcosystemOrm(cfg) {
8386
+ switch (cfg.ecosystem) {
8387
+ case "rust": return cfg.rustOrm;
8388
+ case "python": return cfg.pythonOrm;
8389
+ case "go": return cfg.goOrm;
8390
+ case "java": return cfg.javaOrm;
8391
+ case "elixir": return cfg.elixirOrm;
8392
+ default: return;
8393
+ }
8394
+ }
8395
+ function getEcosystemBackend(cfg) {
8396
+ switch (cfg.ecosystem) {
8397
+ case "rust": return cfg.rustWebFramework && cfg.rustWebFramework !== "none" ? cfg.rustWebFramework : void 0;
8398
+ case "python": return cfg.pythonWebFramework && cfg.pythonWebFramework !== "none" ? cfg.pythonWebFramework : void 0;
8399
+ case "go": return cfg.goWebFramework && cfg.goWebFramework !== "none" ? cfg.goWebFramework : void 0;
8400
+ case "java": return cfg.javaWebFramework && cfg.javaWebFramework !== "none" ? cfg.javaWebFramework : void 0;
8401
+ case "elixir": return cfg.elixirWebFramework && cfg.elixirWebFramework !== "none" ? cfg.elixirWebFramework : void 0;
8402
+ default: return;
8403
+ }
8404
+ }
7757
8405
  function validateDatabaseSetup(config, providedFlags) {
7758
8406
  const { dbSetup, database, runtime } = config;
7759
8407
  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 +8527,9 @@ function validateConvexConstraints(config, providedFlags) {
7879
8527
  }
7880
8528
  function validateBackendNoneConstraints(config, providedFlags) {
7881
8529
  const { backend } = config;
7882
- if (backend !== "none") return;
8530
+ const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
8531
+ const hasEcosystemBackend = getEcosystemBackend(config) !== void 0;
8532
+ if (backend !== "none" || hasGraphBackend || hasEcosystemBackend) return;
7883
8533
  const has = (k) => providedFlags.has(k);
7884
8534
  if (has("runtime") && config.runtime !== "none") exitWithError("Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
7885
8535
  if (has("database") && config.database !== "none") exitWithError("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
@@ -8223,6 +8873,10 @@ function validatePythonApiConstraints(config) {
8223
8873
  });
8224
8874
  }
8225
8875
  function validateFullConfig(config, providedFlags, options) {
8876
+ if (config.stackParts && !options.yolo) {
8877
+ const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
8878
+ if (graphValidation.issues.length > 0) exitWithError(graphValidation.issues.map((issue) => issue.message).join("\n"));
8879
+ }
8226
8880
  validateEcosystemAuthCompatibility(config, providedFlags);
8227
8881
  validateDatabaseOrmAuth(config, providedFlags);
8228
8882
  validateDatabaseSetup(config, providedFlags);
@@ -8241,7 +8895,8 @@ function validateFullConfig(config, providedFlags, options) {
8241
8895
  validateSearchConstraints(config);
8242
8896
  validateJavaConstraints(config, providedFlags);
8243
8897
  validateElixirConstraints(config);
8244
- validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
8898
+ const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
8899
+ 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
8900
  validateSelfBackendCompatibility(providedFlags, options, config);
8246
8901
  validateWorkersCompatibility(providedFlags, options, config);
8247
8902
  if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'alchemy' for --server-deploy.");
@@ -8272,6 +8927,10 @@ function validateFullConfig(config, providedFlags, options) {
8272
8927
  }
8273
8928
  function validateConfigForProgrammaticUse(config) {
8274
8929
  try {
8930
+ if (config.stackParts) {
8931
+ const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
8932
+ if (graphValidation.issues.length > 0) throw new Error(graphValidation.issues.map((issue) => issue.message).join("\n"));
8933
+ }
8275
8934
  validateEcosystemAuthCompatibility(config);
8276
8935
  validateDatabaseOrmAuth(config);
8277
8936
  if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
@@ -8363,7 +9022,14 @@ function processAndValidateFlags(options, providedFlags, projectName) {
8363
9022
  return config;
8364
9023
  }
8365
9024
  function processProvidedFlagsWithoutValidation(options, projectName) {
8366
- if (!options.yolo) validateYesFlagCombination(options, getProvidedFlags(options));
9025
+ if (!options.yolo) {
9026
+ validateYesFlagCombination(options, getProvidedFlags(options));
9027
+ try {
9028
+ validateArrayOptions(options);
9029
+ } catch (error) {
9030
+ exitWithError(error instanceof Error ? error.message : String(error));
9031
+ }
9032
+ }
8367
9033
  const config = processFlags(options, projectName);
8368
9034
  const validatedProjectName = extractAndValidateProjectName(projectName, options.projectDirectory, true);
8369
9035
  if (validatedProjectName) config.projectName = validatedProjectName;
@@ -8375,26 +9041,6 @@ function validateConfigCompatibility(config, providedFlags, options) {
8375
9041
  else validateConfigForProgrammaticUse(config);
8376
9042
  }
8377
9043
 
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
9044
  //#endregion
8399
9045
  //#region src/utils/file-formatter.ts
8400
9046
  const formatOptions = {
@@ -9666,7 +10312,9 @@ async function displayPostInstallInstructions(config) {
9666
10312
  const hasHusky = addons?.includes("husky");
9667
10313
  const hasLefthook = addons?.includes("lefthook");
9668
10314
  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) : "";
10315
+ const graphOrmPart = getGraphPart(config, "orm");
10316
+ const databaseInstructions = !isConvex && database !== "none" && !graphOrmPart ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
10317
+ const graphDatabaseInstructions = !isConvex && database !== "none" && graphOrmPart ? getGraphDatabaseInstructions(database, graphOrmPart.ecosystem, graphOrmPart.toolId) : "";
9670
10318
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
9671
10319
  const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : "";
9672
10320
  const lefthookInstructions = hasLefthook ? getLefthookInstructions(packageManager) : "";
@@ -9680,14 +10328,24 @@ async function displayPostInstallInstructions(config) {
9680
10328
  const paymentSetupInstructions = getPaymentSetupInstructions(config.payments, backend);
9681
10329
  const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
9682
10330
  const vercelDeployInstructions = getVercelDeployInstructions(webDeploy, serverDeploy, backend);
10331
+ const graphBackendDeployInstructions = getGraphBackendDeployInstructions(config);
9683
10332
  const hasWeb = frontend?.some((f) => WEB_FRAMEWORKS.includes(f));
9684
10333
  const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
9685
10334
  const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
9686
- const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
10335
+ const noOrmWarning = !isConvex && database !== "none" && orm === "none" && !hasGraphPart(config, "orm") ? getNoOrmWarning() : "";
9687
10336
  const hasFresh = frontend?.includes("fresh");
9688
10337
  const webPort = String(getLocalWebDevPort(frontend ?? []));
9689
10338
  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`;
10339
+ const graphSummary = getGraphSummary(config);
10340
+ const graphBackendUrl = getGraphBackendUrl(config);
10341
+ const graphBackendPart = getGraphPart(config, "backend");
10342
+ const graphBackendNeedsSetup = Boolean(graphBackendUrl) && [
10343
+ "python",
10344
+ "go",
10345
+ "elixir"
10346
+ ].includes(graphBackendPart?.ecosystem ?? "");
10347
+ let output = graphSummary ? `${pc.bold("Generated:")} ${graphSummary}\n\n` : "";
10348
+ output += `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
9691
10349
  let stepCounter = 2;
9692
10350
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
9693
10351
  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`;
@@ -9696,6 +10354,13 @@ async function displayPostInstallInstructions(config) {
9696
10354
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
9697
10355
  output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
9698
10356
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
10357
+ } else if (graphBackendUrl) {
10358
+ if (graphBackendNeedsSetup) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} setup:server\n`;
10359
+ if (hasWeb) {
10360
+ output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:web\n`;
10361
+ output += `${pc.dim(" (run the backend in a second terminal)")}\n`;
10362
+ }
10363
+ output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:server\n`;
9699
10364
  } else if (isBackendSelf) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
9700
10365
  else {
9701
10366
  if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
@@ -9704,13 +10369,13 @@ async function displayPostInstallInstructions(config) {
9704
10369
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
9705
10370
  }
9706
10371
  }
9707
- const hasStandaloneBackend = backend !== "none";
10372
+ const hasStandaloneBackend = backend !== "none" || Boolean(graphBackendUrl);
9708
10373
  if (hasWeb || hasStandaloneBackend || addons?.includes("starlight") || addons?.includes("fumadocs")) {
9709
10374
  output += `${pc.bold("Your project will be available at:")}\n`;
9710
10375
  if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
9711
10376
  else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
9712
10377
  if (!isConvex && !isBackendSelf && hasStandaloneBackend) {
9713
- output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
10378
+ output += `${pc.cyan("•")} Backend API: ${graphBackendUrl ?? "http://localhost:3000"}\n`;
9714
10379
  if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
9715
10380
  }
9716
10381
  if (isBackendSelf && api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/api/rpc/api-reference\n`;
@@ -9719,6 +10384,7 @@ async function displayPostInstallInstructions(config) {
9719
10384
  }
9720
10385
  if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
9721
10386
  if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
10387
+ if (graphDatabaseInstructions) output += `\n${graphDatabaseInstructions.trim()}\n`;
9722
10388
  if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
9723
10389
  if (huskyInstructions) output += `\n${huskyInstructions.trim()}\n`;
9724
10390
  if (lefthookInstructions) output += `\n${lefthookInstructions.trim()}\n`;
@@ -9726,6 +10392,7 @@ async function displayPostInstallInstructions(config) {
9726
10392
  if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
9727
10393
  if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
9728
10394
  if (vercelDeployInstructions) output += `\n${vercelDeployInstructions.trim()}\n`;
10395
+ if (graphBackendDeployInstructions) output += `\n${graphBackendDeployInstructions.trim()}\n`;
9729
10396
  if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
9730
10397
  if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
9731
10398
  if (authSetupInstructions) output += `\n${authSetupInstructions.trim()}\n`;
@@ -9817,6 +10484,10 @@ function getStarlightInstructions(runCmd) {
9817
10484
  function getNoOrmWarning() {
9818
10485
  return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring\n database access (e.g., examples, auth) need manual setup.`;
9819
10486
  }
10487
+ function getGraphDatabaseInstructions(database, ecosystem, ormTool) {
10488
+ if (ecosystem === "java" && ormTool === "spring-data-jpa") return `${pc.bold("Database setup:")}\n${pc.cyan("•")} Selected database: ${database}\n${pc.cyan("•")} ORM: ${ecosystem}:${ormTool}\n${pc.cyan("•")} The generated Spring Data JPA example uses an embedded ${pc.white("H2")} dev database. Update ${pc.white("apps/server/src/main/resources/application.yml")} when switching to external ${database}.`;
10489
+ 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")}`;
10490
+ }
9820
10491
  function getBunWebNativeWarning() {
9821
10492
  return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
9822
10493
  }
@@ -10075,14 +10746,14 @@ function displayJavaInstructions(config) {
10075
10746
  }
10076
10747
  const effectiveJavaTestingLibraries = javaBuildTool === "none" ? [] : javaTestingLibraries.filter((library) => library !== "none");
10077
10748
  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;
10749
+ 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
10750
  const packageCommand = buildToolCommand ? javaBuildTool === "gradle" ? `${buildToolCommand} build` : `${buildToolCommand} package` : null;
10080
10751
  const sourceCompileCommand = buildToolCommand ? null : `javac -d out ${getJavaMainSourcePath(projectName)}`;
10081
10752
  const sourceRunCommand = buildToolCommand ? null : `java -cp out ${getJavaMainClass(projectName)}`;
10082
10753
  let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
10083
10754
  let stepCounter = 2;
10084
10755
  if (!depsInstalled && buildToolCommand && effectiveJavaTestingLibraries.length > 0) output += `${pc.cyan(`${stepCounter++}.`)} ${buildToolCommand} test\n`;
10085
- if (runCommand) output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand}\n`;
10756
+ if (runCommand$1) output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand$1}\n`;
10086
10757
  else if (sourceCompileCommand && sourceRunCommand) {
10087
10758
  output += `${pc.cyan(`${stepCounter++}.`)} ${sourceCompileCommand}\n`;
10088
10759
  output += `${pc.cyan(`${stepCounter++}.`)} ${sourceRunCommand}\n`;
@@ -10130,9 +10801,9 @@ function displayJavaInstructions(config) {
10130
10801
  output += `${pc.cyan("•")} Testing: ${testingList}\n`;
10131
10802
  }
10132
10803
  output += `\n${pc.bold("Common Java commands:")}\n`;
10133
- if (buildToolCommand && runCommand && packageCommand) {
10804
+ if (buildToolCommand && runCommand$1 && packageCommand) {
10134
10805
  if (effectiveJavaTestingLibraries.length > 0) output += `${pc.cyan("•")} Test: ${buildToolCommand} test\n`;
10135
- output += `${pc.cyan("•")} Run: ${runCommand}\n`;
10806
+ output += `${pc.cyan("•")} Run: ${runCommand$1}\n`;
10136
10807
  output += `${pc.cyan("•")} Package: ${packageCommand}\n`;
10137
10808
  } else if (sourceCompileCommand && sourceRunCommand) {
10138
10809
  output += `${pc.cyan("•")} Compile: ${sourceCompileCommand}\n`;
@@ -10150,14 +10821,14 @@ function displayJavaInstructions(config) {
10150
10821
  function displayPythonInstructions(config) {
10151
10822
  const { relativePath, depsInstalled, pythonWebFramework, pythonOrm, pythonValidation, pythonAi, pythonApi, pythonTaskQueue, pythonQuality } = config;
10152
10823
  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";
10824
+ let runCommand$1 = "uv run uvicorn app.main:app --reload";
10825
+ if (pythonWebFramework === "django") runCommand$1 = "uv run python manage.py runserver";
10826
+ else if (pythonWebFramework === "flask") runCommand$1 = "uv run flask --app app.main run --reload";
10827
+ else if (pythonWebFramework === "litestar") runCommand$1 = "litestar --app src.app.main:app run --reload --port 3001";
10157
10828
  let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
10158
10829
  let stepCounter = 2;
10159
10830
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} uv sync\n`;
10160
- output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand}\n`;
10831
+ output += `${pc.cyan(`${stepCounter++}.`)} ${runCommand$1}\n`;
10161
10832
  output += `\n${pc.bold("Your Python project includes:")}\n`;
10162
10833
  if (pythonWebFramework && pythonWebFramework !== "none") output += `${pc.cyan("•")} Web Framework: ${{
10163
10834
  fastapi: "FastAPI",
@@ -10195,7 +10866,7 @@ function displayPythonInstructions(config) {
10195
10866
  }[pythonQuality] || pythonQuality}\n`;
10196
10867
  output += `\n${pc.bold("Common Python commands:")}\n`;
10197
10868
  output += `${pc.cyan("•")} Install: uv sync\n`;
10198
- output += `${pc.cyan("•")} Run: ${runCommand}\n`;
10869
+ output += `${pc.cyan("•")} Run: ${runCommand$1}\n`;
10199
10870
  output += `${pc.cyan("•")} Test: uv run pytest\n`;
10200
10871
  if (pythonQuality === "ruff") {
10201
10872
  output += `${pc.cyan("•")} Format: uv run ruff format .\n`;
@@ -10357,7 +11028,7 @@ function getYesBaseConfig(flagConfig) {
10357
11028
  };
10358
11029
  }
10359
11030
  function shouldPromptForVersionChannel(input) {
10360
- if (input.yes || input.versionChannel !== void 0 || isSilent()) return false;
11031
+ if (input.yes || input.part?.length || input.versionChannel !== void 0 || isSilent()) return false;
10361
11032
  return canPromptInteractively();
10362
11033
  }
10363
11034
  async function createProjectHandler(input, options = {}) {
@@ -10542,7 +11213,7 @@ async function createProjectHandler(input, options = {}) {
10542
11213
  }
10543
11214
  }
10544
11215
  let config;
10545
- if (cliInput.yes) {
11216
+ if (cliInput.yes || cliInput.part?.length) {
10546
11217
  const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
10547
11218
  config = {
10548
11219
  ...getYesBaseConfig(flagConfig),
@@ -10571,6 +11242,7 @@ async function createProjectHandler(input, options = {}) {
10571
11242
  ...await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, currentPathInput),
10572
11243
  versionChannel
10573
11244
  };
11245
+ validateConfigCompatibility(config, providedFlags, cliInput);
10574
11246
  }
10575
11247
  const preflight = validatePreflightConfig(config);
10576
11248
  if (preflight.hasWarnings && !isSilent()) displayPreflightWarnings(preflight);
@@ -10617,6 +11289,7 @@ async function createProjectHandler(input, options = {}) {
10617
11289
  };
10618
11290
  }
10619
11291
  await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
11292
+ if (cliInput.verify ?? input.verify) await runGeneratedChecks(config);
10620
11293
  const reproducibleCommand = generateReproducibleCommand(config);
10621
11294
  if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
10622
11295
  await trackProjectCreation(config, input.disableAnalytics);
@@ -10946,113 +11619,116 @@ async function createVirtual(options) {
10946
11619
  const isReactNative = ecosystem === "react-native";
10947
11620
  const frontend = options.frontend || (isReactNative ? ["native-bare"] : ["tanstack-router"]);
10948
11621
  const hasNativeFrontend = frontend.some((item) => item === "native-bare" || item === "native-uniwind" || item === "native-unistyles");
11622
+ const config = {
11623
+ ecosystem,
11624
+ projectName: options.projectName || "my-project",
11625
+ projectDir: "/virtual",
11626
+ relativePath: "./virtual",
11627
+ database: options.database || "none",
11628
+ orm: options.orm || "none",
11629
+ backend: options.backend || (isReactNative ? "none" : "hono"),
11630
+ runtime: options.runtime || (isReactNative ? "none" : "bun"),
11631
+ frontend,
11632
+ addons: options.addons || [],
11633
+ examples: options.examples || [],
11634
+ auth: options.auth || "none",
11635
+ payments: options.payments || "none",
11636
+ email: options.email || "none",
11637
+ fileUpload: options.fileUpload || "none",
11638
+ effect: options.effect || "none",
11639
+ git: options.git ?? false,
11640
+ packageManager: options.packageManager || "bun",
11641
+ versionChannel: options.versionChannel || "stable",
11642
+ install: false,
11643
+ dbSetup: options.dbSetup || "none",
11644
+ api: options.api || (isReactNative ? "none" : "trpc"),
11645
+ webDeploy: options.webDeploy || "none",
11646
+ serverDeploy: options.serverDeploy || "none",
11647
+ astroIntegration: options.astroIntegration || "none",
11648
+ cssFramework: options.cssFramework || (isReactNative ? "none" : "tailwind"),
11649
+ uiLibrary: options.uiLibrary || (isReactNative ? "none" : "shadcn-ui"),
11650
+ shadcnBase: options.shadcnBase ?? "radix",
11651
+ shadcnStyle: options.shadcnStyle ?? "nova",
11652
+ shadcnIconLibrary: options.shadcnIconLibrary ?? "lucide",
11653
+ shadcnColorTheme: options.shadcnColorTheme ?? "neutral",
11654
+ shadcnBaseColor: options.shadcnBaseColor ?? "neutral",
11655
+ shadcnFont: options.shadcnFont ?? "inter",
11656
+ shadcnRadius: options.shadcnRadius ?? "default",
11657
+ ai: options.ai || "none",
11658
+ stateManagement: options.stateManagement || "none",
11659
+ forms: options.forms || (isReactNative ? "none" : "react-hook-form"),
11660
+ testing: options.testing || (isReactNative ? "none" : "vitest"),
11661
+ validation: options.validation || "zod",
11662
+ realtime: options.realtime || "none",
11663
+ jobQueue: options.jobQueue || "none",
11664
+ animation: options.animation || "none",
11665
+ logging: options.logging || "none",
11666
+ observability: options.observability || "none",
11667
+ featureFlags: options.featureFlags || "none",
11668
+ analytics: options.analytics || "none",
11669
+ mobileNavigation: options.mobileNavigation || (hasNativeFrontend ? "expo-router" : "none"),
11670
+ mobileUI: options.mobileUI || "none",
11671
+ mobileStorage: options.mobileStorage || "none",
11672
+ mobileTesting: options.mobileTesting || "none",
11673
+ mobilePush: options.mobilePush || "none",
11674
+ mobileOTA: options.mobileOTA || "none",
11675
+ mobileDeepLinking: options.mobileDeepLinking || (hasNativeFrontend ? "expo-linking" : "none"),
11676
+ cms: options.cms || "none",
11677
+ caching: options.caching || "none",
11678
+ i18n: options.i18n || "none",
11679
+ search: options.search || "none",
11680
+ fileStorage: options.fileStorage || "none",
11681
+ rustWebFramework: options.rustWebFramework || "none",
11682
+ rustFrontend: options.rustFrontend || "none",
11683
+ rustOrm: options.rustOrm || "none",
11684
+ rustApi: options.rustApi || "none",
11685
+ rustCli: options.rustCli || "none",
11686
+ rustLibraries: options.rustLibraries || [],
11687
+ rustLogging: options.rustLogging || (options.ecosystem === "rust" ? "tracing" : "none"),
11688
+ rustErrorHandling: options.rustErrorHandling || (options.ecosystem === "rust" ? "anyhow-thiserror" : "none"),
11689
+ rustCaching: options.rustCaching || "none",
11690
+ rustAuth: options.rustAuth || "none",
11691
+ pythonWebFramework: options.pythonWebFramework || "none",
11692
+ pythonOrm: options.pythonOrm || "none",
11693
+ pythonValidation: options.pythonValidation || "none",
11694
+ pythonAi: options.pythonAi || [],
11695
+ pythonAuth: options.pythonAuth || "none",
11696
+ pythonApi: options.pythonApi || "none",
11697
+ pythonTaskQueue: options.pythonTaskQueue || "none",
11698
+ pythonGraphql: options.pythonGraphql || "none",
11699
+ pythonQuality: options.pythonQuality || "none",
11700
+ goWebFramework: options.goWebFramework || "none",
11701
+ goOrm: options.goOrm || "none",
11702
+ goApi: options.goApi || "none",
11703
+ goCli: options.goCli || "none",
11704
+ goLogging: options.goLogging || "none",
11705
+ goAuth: options.goAuth || "none",
11706
+ javaWebFramework: options.javaWebFramework || (options.ecosystem === "java" ? "spring-boot" : "none"),
11707
+ javaBuildTool: options.javaBuildTool || (options.ecosystem === "java" ? "maven" : "none"),
11708
+ javaOrm: options.javaOrm || "none",
11709
+ javaAuth: options.javaAuth || "none",
11710
+ javaLibraries: options.javaLibraries || [],
11711
+ javaTestingLibraries: options.javaTestingLibraries || (options.ecosystem === "java" ? ["junit5"] : []),
11712
+ elixirWebFramework: options.elixirWebFramework || (options.ecosystem === "elixir" ? "phoenix" : "none"),
11713
+ elixirOrm: options.elixirOrm || (options.ecosystem === "elixir" ? "ecto-sql" : "none"),
11714
+ elixirAuth: options.elixirAuth || "none",
11715
+ elixirApi: options.elixirApi || (options.ecosystem === "elixir" ? "rest" : "none"),
11716
+ elixirRealtime: options.elixirRealtime || (options.ecosystem === "elixir" ? "channels" : "none"),
11717
+ elixirJobs: options.elixirJobs || "none",
11718
+ elixirValidation: options.elixirValidation || (options.ecosystem === "elixir" ? "ecto-changesets" : "none"),
11719
+ elixirHttp: options.elixirHttp || (options.ecosystem === "elixir" ? "req" : "none"),
11720
+ elixirJson: options.elixirJson || (options.ecosystem === "elixir" ? "jason" : "none"),
11721
+ elixirEmail: options.elixirEmail || "none",
11722
+ elixirCaching: options.elixirCaching || "none",
11723
+ elixirObservability: options.elixirObservability || (options.ecosystem === "elixir" ? "telemetry" : "none"),
11724
+ elixirTesting: options.elixirTesting || (options.ecosystem === "elixir" ? "ex_unit" : "none"),
11725
+ elixirQuality: options.elixirQuality || (options.ecosystem === "elixir" ? "credo" : "none"),
11726
+ elixirDeploy: options.elixirDeploy || "none",
11727
+ aiDocs: options.aiDocs || ["claude-md"]
11728
+ };
11729
+ if (options.stackParts) config.stackParts = options.stackParts;
10949
11730
  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
- },
11731
+ config,
11056
11732
  templates: EMBEDDED_TEMPLATES$1
11057
11733
  });
11058
11734
  if (result.success && result.tree) return {