create-better-fullstack 1.8.0 → 2.0.0

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