create-better-fullstack 1.6.3 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __reExport } from "./chunk-CCII7kTE.mjs";
3
- import { a as DEFAULT_CONFIG, c as getDefaultConfig, i as getLatestCLIVersion, l as getUserPkgManager, n as updateBtsConfig, o as DEFAULT_UI_LIBRARY_BY_FRONTEND, r as writeBtsConfig, t as readBtsConfig } from "./bts-config-B_rZ4_sj.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-DQa6TRrx.mjs";
3
+ import { a as DEFAULT_CONFIG, c as getDefaultConfig, i as getLatestCLIVersion, l as getUserPkgManager, n as updateBtsConfig, o as DEFAULT_UI_LIBRARY_BY_FRONTEND, r as writeBtsConfig, t as readBtsConfig } from "./bts-config-bOXo9tbL.mjs";
4
+ import { _ as setIsFirstPrompt$1, a as canPromptInteractively, c as CLIError, d as exitWithError, f as handleError, g as runWithContextAsync, h as isSilent, l as UserCancelledError, m as isFirstPrompt, o as getPackageExecutionArgs, p as didLastPromptShowUI, s as addPackageDependency, t as setupAddons, u as exitCancelled, v as setLastPromptShownUI } from "./addons-setup-CGhYT2qC.mjs";
5
5
  import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
6
6
  import { createRouterClient, os } from "@orpc/server";
7
7
  import pc from "picocolors";
@@ -10,7 +10,7 @@ import z from "zod";
10
10
  import envPaths from "env-paths";
11
11
  import fs from "fs-extra";
12
12
  import path from "node:path";
13
- import { allowedApisForFrontends, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasDockerComposeCompatibleFrontend, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
13
+ import { allowedApisForFrontends, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasDockerComposeCompatibleFrontend, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
14
14
  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
15
  import gradient from "gradient-string";
16
16
  import path$1 from "path";
@@ -18,6 +18,7 @@ import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-w
18
18
  import consola, { consola as consola$1 } from "consola";
19
19
  import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
20
20
  import { $, execa } from "execa";
21
+ import { cliInputToProjectConfigPartial } from "@better-fullstack/types/stack-translation";
21
22
  import os$1 from "node:os";
22
23
  import { format } from "oxfmt";
23
24
 
@@ -429,16 +430,17 @@ const CreateCommandOptionsSchema = z.object({
429
430
  pythonValidation: types_exports.PythonValidationSchema.optional().describe("Python validation (pydantic)"),
430
431
  pythonAi: z.array(types_exports.PythonAiSchema).optional().describe("Python AI/ML frameworks"),
431
432
  pythonAuth: types_exports.PythonAuthSchema.optional().describe("Python auth library (authlib, jwt)"),
433
+ pythonApi: types_exports.PythonApiSchema.optional().describe("Python API framework (django-rest-framework, django-ninja)"),
432
434
  pythonTaskQueue: types_exports.PythonTaskQueueSchema.optional().describe("Python task queue (celery)"),
433
435
  pythonGraphql: types_exports.PythonGraphqlSchema.optional().describe("Python GraphQL framework (strawberry)"),
434
- pythonQuality: types_exports.PythonQualitySchema.optional().describe("Python code quality (ruff)"),
436
+ pythonQuality: types_exports.PythonQualitySchema.optional().describe("Python code quality (ruff, mypy, pyright)"),
435
437
  goWebFramework: types_exports.GoWebFrameworkSchema.optional().describe("Go web framework (gin, echo, fiber)"),
436
438
  goOrm: types_exports.GoOrmSchema.optional().describe("Go ORM/database (gorm, sqlc)"),
437
439
  goApi: types_exports.GoApiSchema.optional().describe("Go API layer (grpc-go)"),
438
- goCli: types_exports.GoCliSchema.optional().describe("Go CLI tools (cobra, bubbletea)"),
440
+ goCli: types_exports.GoCliSchema.optional().describe("Go CLI tools (cobra, bubbletea, urfave-cli)"),
439
441
  goLogging: types_exports.GoLoggingSchema.optional().describe("Go logging (zap, zerolog, slog)"),
440
442
  goAuth: types_exports.GoAuthSchema.optional().describe("Go auth (casbin, jwt)"),
441
- javaWebFramework: types_exports.JavaWebFrameworkSchema.optional().describe("Java web framework (spring-boot, none)"),
443
+ javaWebFramework: types_exports.JavaWebFrameworkSchema.optional().describe("Java web framework (spring-boot, quarkus, none)"),
442
444
  javaBuildTool: types_exports.JavaBuildToolSchema.optional().describe("Java build tool (maven, gradle, none)"),
443
445
  javaOrm: types_exports.JavaOrmSchema.optional().describe("Java ORM/database (spring-data-jpa)"),
444
446
  javaAuth: types_exports.JavaAuthSchema.optional().describe("Java auth (spring-security)"),
@@ -602,74 +604,12 @@ function validateWorkersCompatibility(providedFlags, options, config) {
602
604
  });
603
605
  }
604
606
  function validateApiFrontendCompatibility(api, frontends = [], astroIntegration) {
605
- const includesNuxt = frontends.includes("nuxt");
606
- const includesSvelte = frontends.includes("svelte");
607
- const includesSolid = frontends.includes("solid");
608
- const includesAstro = frontends.includes("astro");
609
- const includesQwik = frontends.includes("qwik");
610
- const includesAngular = frontends.includes("angular");
611
- const includesRedwood = frontends.includes("redwood");
612
- const includesFresh = frontends.includes("fresh");
613
- const includesSolidStart = frontends.includes("solid-start");
614
- if ((includesNuxt || includesSvelte || includesSolid || includesSolidStart) && (api === "trpc" || api === "ts-rest" || api === "garph")) {
615
- const apiName = api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph";
616
- const incompatibleFrontend = includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "solid-start";
617
- incompatibilityError({
618
- message: `${apiName} API requires React-based frontends.`,
619
- provided: {
620
- api,
621
- frontend: incompatibleFrontend
622
- },
623
- suggestions: [
624
- "Use --api orpc (works with all frontends)",
625
- "Use --api none",
626
- "Choose next, react-router, react-vite, or tanstack-start"
627
- ]
628
- });
629
- }
630
- if (includesQwik && api && api !== "none") incompatibilityError({
631
- message: "Qwik has built-in server capabilities and doesn't support external APIs.",
632
- provided: {
633
- api,
634
- frontend: "qwik"
635
- },
636
- suggestions: ["Use --api none with Qwik"]
637
- });
638
- if (includesAngular && api && api !== "none") incompatibilityError({
639
- message: "Angular has built-in HttpClient and doesn't support external APIs.",
640
- provided: {
641
- api,
642
- frontend: "angular"
643
- },
644
- suggestions: ["Use --api none with Angular"]
645
- });
646
- if (includesRedwood && api && api !== "none") incompatibilityError({
647
- message: "RedwoodJS has built-in GraphQL API and doesn't support external APIs.",
648
- provided: {
649
- api,
650
- frontend: "redwood"
651
- },
652
- suggestions: ["Use --api none with RedwoodJS"]
653
- });
654
- if (includesFresh && api && api !== "none") incompatibilityError({
655
- message: "Fresh has built-in server capabilities and doesn't support external APIs.",
656
- provided: {
657
- api,
658
- frontend: "fresh"
659
- },
660
- suggestions: ["Use --api none with Fresh"]
661
- });
662
- if (includesAstro && astroIntegration && astroIntegration !== "react" && (api === "trpc" || api === "ts-rest" || api === "garph")) incompatibilityError({
663
- message: `${api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph"} API requires React integration with Astro.`,
664
- provided: {
665
- api,
666
- "astro-integration": astroIntegration
667
- },
668
- suggestions: [
669
- "Use --api orpc (works with all Astro integrations)",
670
- "Use --api none",
671
- "Use --astro-integration react"
672
- ]
607
+ const issue = getApiFrontendCompatibilityIssue(api, frontends, astroIntegration);
608
+ if (!issue) return;
609
+ incompatibilityError({
610
+ message: issue.message,
611
+ provided: issue.provided ?? {},
612
+ suggestions: issue.suggestions ?? []
673
613
  });
674
614
  }
675
615
  function isFrontendAllowedWithBackend$1(frontend, backend, auth) {
@@ -783,18 +723,9 @@ function validateExamplesCompatibility(examples, backend, frontend, runtime, ai)
783
723
  * Server-side @tanstack/ai core works anywhere, but client adapters only exist for React and Solid.
784
724
  */
785
725
  function validateAIFrontendCompatibility(ai, frontends = []) {
786
- if (!ai || ai !== "tanstack-ai") return;
787
- const compatibleFrontends = [
788
- "tanstack-router",
789
- "react-router",
790
- "react-vite",
791
- "tanstack-start",
792
- "next",
793
- "redwood",
794
- "solid",
795
- "solid-start"
796
- ];
797
- if (!frontends.some((f) => compatibleFrontends.includes(f))) exitWithError("TanStack AI requires React or Solid frontend (no Vue/Svelte/Angular adapter yet). Please use a React-based frontend (Next.js, TanStack Router, React Router, etc.) or Solid.");
726
+ const issue = getAIFrontendCompatibilityIssue(ai, frontends);
727
+ if (!issue) return;
728
+ exitWithError(issue.message);
798
729
  }
799
730
  /**
800
731
  * Validates that a UI library is compatible with the selected frontend(s)
@@ -1482,16 +1413,20 @@ async function applyDependencyVersionChannel(projectDir, channel) {
1482
1413
  //#endregion
1483
1414
  //#region src/helpers/core/install-dependencies.ts
1484
1415
  function getInstallEnvironment(packageManager) {
1485
- if (packageManager !== "yarn") return;
1486
- return {
1416
+ if (packageManager === "yarn") return {
1487
1417
  YARN_ENABLE_HARDENED_MODE: "0",
1488
1418
  YARN_ENABLE_IMMUTABLE_INSTALLS: "false"
1489
1419
  };
1490
1420
  }
1421
+ function getInstallArgs(packageManager) {
1422
+ if (packageManager === "pnpm") return ["install", "--dangerously-allow-all-builds"];
1423
+ return ["install"];
1424
+ }
1491
1425
  async function installDependencies({ projectDir, packageManager }) {
1492
1426
  const s = spinner();
1493
1427
  try {
1494
1428
  s.start(`Running ${packageManager} install...`);
1429
+ const installArgs = getInstallArgs(packageManager);
1495
1430
  await $({
1496
1431
  cwd: projectDir,
1497
1432
  env: {
@@ -1499,7 +1434,7 @@ async function installDependencies({ projectDir, packageManager }) {
1499
1434
  ...getInstallEnvironment(packageManager)
1500
1435
  },
1501
1436
  stderr: "inherit"
1502
- })`${packageManager} install`;
1437
+ })`${packageManager} ${installArgs}`;
1503
1438
  s.stop("Dependencies installed successfully");
1504
1439
  } catch (error) {
1505
1440
  s.stop(pc.red("Failed to install dependencies"));
@@ -2244,6 +2179,17 @@ const CACHING_PROMPT_OPTIONS = [{
2244
2179
  hint: "Skip caching layer setup"
2245
2180
  }];
2246
2181
  function resolveCachingPrompt(context = {}) {
2182
+ if (context.ecosystem && context.ecosystem !== "typescript") return context.caching !== void 0 ? {
2183
+ shouldPrompt: false,
2184
+ mode: "single",
2185
+ options: CACHING_PROMPT_OPTIONS,
2186
+ autoValue: context.caching
2187
+ } : {
2188
+ shouldPrompt: true,
2189
+ mode: "single",
2190
+ options: CACHING_PROMPT_OPTIONS,
2191
+ initialValue: "none"
2192
+ };
2247
2193
  if (context.backend === "none" || context.backend === "convex") return {
2248
2194
  shouldPrompt: false,
2249
2195
  mode: "single",
@@ -2262,10 +2208,11 @@ function resolveCachingPrompt(context = {}) {
2262
2208
  initialValue: "none"
2263
2209
  };
2264
2210
  }
2265
- async function getCachingChoice(caching, backend) {
2211
+ async function getCachingChoice(caching, backend, ecosystem) {
2266
2212
  const resolution = resolveCachingPrompt({
2267
2213
  caching,
2268
- backend
2214
+ backend,
2215
+ ecosystem
2269
2216
  });
2270
2217
  if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
2271
2218
  const response = await navigableSelect({
@@ -2744,8 +2691,10 @@ const EMAIL_PROMPT_OPTIONS = [
2744
2691
  hint: "No email integration"
2745
2692
  }
2746
2693
  ];
2694
+ const NON_TYPESCRIPT_EMAIL_PROMPT_OPTIONS = EMAIL_PROMPT_OPTIONS.filter((option) => option.value === "resend" || option.value === "none");
2747
2695
  function resolveEmailPrompt(context = {}) {
2748
- if (context.backend === "none" || context.backend === "convex") return {
2696
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_EMAIL_PROMPT_OPTIONS : EMAIL_PROMPT_OPTIONS;
2697
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
2749
2698
  shouldPrompt: false,
2750
2699
  mode: "single",
2751
2700
  options: [],
@@ -2754,19 +2703,20 @@ function resolveEmailPrompt(context = {}) {
2754
2703
  return context.email !== void 0 ? {
2755
2704
  shouldPrompt: false,
2756
2705
  mode: "single",
2757
- options: EMAIL_PROMPT_OPTIONS,
2706
+ options,
2758
2707
  autoValue: context.email
2759
2708
  } : {
2760
2709
  shouldPrompt: true,
2761
2710
  mode: "single",
2762
- options: EMAIL_PROMPT_OPTIONS,
2711
+ options,
2763
2712
  initialValue: DEFAULT_CONFIG.email ?? "none"
2764
2713
  };
2765
2714
  }
2766
- async function getEmailChoice(email, backend) {
2715
+ async function getEmailChoice(email, backend, ecosystem) {
2767
2716
  const resolution = resolveEmailPrompt({
2768
2717
  email,
2769
- backend
2718
+ backend,
2719
+ ecosystem
2770
2720
  });
2771
2721
  if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
2772
2722
  const response = await navigableSelect({
@@ -3235,6 +3185,11 @@ const GO_CLI_PROMPT_OPTIONS = [
3235
3185
  label: "Bubble Tea",
3236
3186
  hint: "Powerful TUI framework based on The Elm Architecture"
3237
3187
  },
3188
+ {
3189
+ value: "urfave-cli",
3190
+ label: "urfave/cli",
3191
+ hint: "Declarative CLI framework with commands, flags, and shell completion"
3192
+ },
3238
3193
  {
3239
3194
  value: "none",
3240
3195
  label: "None",
@@ -3257,6 +3212,11 @@ const GO_LOGGING_PROMPT_OPTIONS = [
3257
3212
  label: "slog",
3258
3213
  hint: "Go 1.21+ stdlib structured logging (no external dependency)"
3259
3214
  },
3215
+ {
3216
+ value: "logrus",
3217
+ label: "Logrus",
3218
+ hint: "Classic structured logger with hooks and formatter ecosystem"
3219
+ },
3260
3220
  {
3261
3221
  value: "none",
3262
3222
  label: "None",
@@ -3471,15 +3431,23 @@ async function getinstallChoice(install, ecosystem, javaBuildTool) {
3471
3431
 
3472
3432
  //#endregion
3473
3433
  //#region src/prompts/java-ecosystem.ts
3474
- const JAVA_WEB_FRAMEWORK_PROMPT_OPTIONS = [{
3475
- value: "spring-boot",
3476
- label: "Spring Boot",
3477
- hint: "Production-grade Java framework with embedded server and auto-configuration"
3478
- }, {
3479
- value: "none",
3480
- label: "None",
3481
- hint: "No Java web framework"
3482
- }];
3434
+ const JAVA_WEB_FRAMEWORK_PROMPT_OPTIONS = [
3435
+ {
3436
+ value: "spring-boot",
3437
+ label: "Spring Boot",
3438
+ hint: "Production-grade Java framework with embedded server and auto-configuration"
3439
+ },
3440
+ {
3441
+ value: "quarkus",
3442
+ label: "Quarkus",
3443
+ hint: "Cloud-native Java framework optimized for fast startup and lower memory use"
3444
+ },
3445
+ {
3446
+ value: "none",
3447
+ label: "None",
3448
+ hint: "No Java web framework"
3449
+ }
3450
+ ];
3483
3451
  const JAVA_BUILD_TOOL_PROMPT_OPTIONS = [
3484
3452
  {
3485
3453
  value: "maven",
@@ -3608,6 +3576,46 @@ const JAVA_LIBRARY_PROMPT_OPTIONS = [
3608
3576
  label: "Caffeine",
3609
3577
  hint: "High-performance in-memory caching through Spring Cache"
3610
3578
  },
3579
+ {
3580
+ value: "resilience4j",
3581
+ label: "Resilience4j",
3582
+ hint: "Fault tolerance patterns for retries, circuit breakers, and rate limiting"
3583
+ },
3584
+ {
3585
+ value: "spring-webflux",
3586
+ label: "Spring WebFlux",
3587
+ hint: "Reactive HTTP stack for Spring applications"
3588
+ },
3589
+ {
3590
+ value: "spring-batch",
3591
+ label: "Spring Batch",
3592
+ hint: "Batch processing framework for ETL and scheduled jobs"
3593
+ },
3594
+ {
3595
+ value: "spring-kafka",
3596
+ label: "Spring for Apache Kafka",
3597
+ hint: "Kafka producer, consumer, and listener integration"
3598
+ },
3599
+ {
3600
+ value: "spring-mail",
3601
+ label: "Spring Mail",
3602
+ hint: "Email support through Jakarta Mail and Spring abstractions"
3603
+ },
3604
+ {
3605
+ value: "spring-devtools",
3606
+ label: "Spring Boot DevTools",
3607
+ hint: "Developer-time restart and local productivity support"
3608
+ },
3609
+ {
3610
+ value: "micrometer-prometheus",
3611
+ label: "Micrometer Prometheus",
3612
+ hint: "Prometheus metrics registry for Micrometer and Actuator"
3613
+ },
3614
+ {
3615
+ value: "thymeleaf",
3616
+ label: "Thymeleaf",
3617
+ hint: "Server-rendered HTML templates for Spring MVC apps"
3618
+ },
3611
3619
  {
3612
3620
  value: "none",
3613
3621
  label: "None",
@@ -3900,8 +3908,10 @@ const OBSERVABILITY_PROMPT_OPTIONS = [
3900
3908
  hint: "Skip observability/tracing setup"
3901
3909
  }
3902
3910
  ];
3911
+ const NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS = OBSERVABILITY_PROMPT_OPTIONS.filter((option) => option.value === "sentry" || option.value === "none");
3903
3912
  function resolveObservabilityPrompt(context = {}) {
3904
- if (context.backend === "none" || context.backend === "convex") return {
3913
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_OBSERVABILITY_PROMPT_OPTIONS : OBSERVABILITY_PROMPT_OPTIONS;
3914
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
3905
3915
  shouldPrompt: false,
3906
3916
  mode: "single",
3907
3917
  options: [],
@@ -3910,19 +3920,20 @@ function resolveObservabilityPrompt(context = {}) {
3910
3920
  return context.observability !== void 0 ? {
3911
3921
  shouldPrompt: false,
3912
3922
  mode: "single",
3913
- options: OBSERVABILITY_PROMPT_OPTIONS,
3923
+ options,
3914
3924
  autoValue: context.observability
3915
3925
  } : {
3916
3926
  shouldPrompt: true,
3917
3927
  mode: "single",
3918
- options: OBSERVABILITY_PROMPT_OPTIONS,
3928
+ options,
3919
3929
  initialValue: "none"
3920
3930
  };
3921
3931
  }
3922
- async function getObservabilityChoice(observability, backend) {
3932
+ async function getObservabilityChoice(observability, backend, ecosystem) {
3923
3933
  const resolution = resolveObservabilityPrompt({
3924
3934
  observability,
3925
- backend
3935
+ backend,
3936
+ ecosystem
3926
3937
  });
3927
3938
  if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
3928
3939
  const response = await navigableSelect({
@@ -4220,6 +4231,11 @@ const PYTHON_AI_PROMPT_OPTIONS = [
4220
4231
  value: "crewai",
4221
4232
  label: "CrewAI",
4222
4233
  hint: "Multi-agent orchestration framework"
4234
+ },
4235
+ {
4236
+ value: "haystack",
4237
+ label: "Haystack",
4238
+ hint: "Composable LLM pipelines, RAG, and search applications"
4223
4239
  }
4224
4240
  ];
4225
4241
  const PYTHON_AUTH_PROMPT_OPTIONS = [
@@ -4239,33 +4255,89 @@ const PYTHON_AUTH_PROMPT_OPTIONS = [
4239
4255
  hint: "No authentication library"
4240
4256
  }
4241
4257
  ];
4242
- const PYTHON_TASK_QUEUE_PROMPT_OPTIONS = [{
4243
- value: "celery",
4244
- label: "Celery",
4245
- hint: "Distributed task queue for Python"
4246
- }, {
4247
- value: "none",
4248
- label: "None",
4249
- hint: "No task queue"
4250
- }];
4251
- const PYTHON_GRAPHQL_PROMPT_OPTIONS = [{
4252
- value: "strawberry",
4253
- label: "Strawberry",
4254
- hint: "Python GraphQL library using dataclasses and type hints"
4255
- }, {
4256
- value: "none",
4257
- label: "None",
4258
- hint: "No GraphQL framework"
4259
- }];
4260
- const PYTHON_QUALITY_PROMPT_OPTIONS = [{
4261
- value: "ruff",
4262
- label: "Ruff",
4263
- hint: "An extremely fast Python linter and formatter"
4264
- }, {
4265
- value: "none",
4266
- label: "None",
4267
- hint: "No code quality tools"
4268
- }];
4258
+ const PYTHON_API_PROMPT_OPTIONS = [
4259
+ {
4260
+ value: "django-rest-framework",
4261
+ label: "Django REST Framework",
4262
+ hint: "Mature, widely used toolkit for building Django REST APIs"
4263
+ },
4264
+ {
4265
+ value: "django-ninja",
4266
+ label: "Django Ninja",
4267
+ hint: "FastAPI-style Django APIs with type hints and automatic OpenAPI docs"
4268
+ },
4269
+ {
4270
+ value: "none",
4271
+ label: "None",
4272
+ hint: "No additional Python API framework"
4273
+ }
4274
+ ];
4275
+ const PYTHON_TASK_QUEUE_PROMPT_OPTIONS = [
4276
+ {
4277
+ value: "celery",
4278
+ label: "Celery",
4279
+ hint: "Distributed task queue for Python"
4280
+ },
4281
+ {
4282
+ value: "rq",
4283
+ label: "RQ",
4284
+ hint: "Simple Redis-backed job queue for Python"
4285
+ },
4286
+ {
4287
+ value: "dramatiq",
4288
+ label: "Dramatiq",
4289
+ hint: "Distributed task processing with Redis or RabbitMQ brokers"
4290
+ },
4291
+ {
4292
+ value: "huey",
4293
+ label: "Huey",
4294
+ hint: "Lightweight task queue with Redis-backed scheduling"
4295
+ },
4296
+ {
4297
+ value: "none",
4298
+ label: "None",
4299
+ hint: "No task queue"
4300
+ }
4301
+ ];
4302
+ const PYTHON_GRAPHQL_PROMPT_OPTIONS = [
4303
+ {
4304
+ value: "strawberry",
4305
+ label: "Strawberry",
4306
+ hint: "Python GraphQL library using dataclasses and type hints"
4307
+ },
4308
+ {
4309
+ value: "ariadne",
4310
+ label: "Ariadne",
4311
+ hint: "Schema-first GraphQL server library for Python"
4312
+ },
4313
+ {
4314
+ value: "none",
4315
+ label: "None",
4316
+ hint: "No GraphQL framework"
4317
+ }
4318
+ ];
4319
+ const PYTHON_QUALITY_PROMPT_OPTIONS = [
4320
+ {
4321
+ value: "ruff",
4322
+ label: "Ruff",
4323
+ hint: "An extremely fast Python linter and formatter"
4324
+ },
4325
+ {
4326
+ value: "mypy",
4327
+ label: "mypy",
4328
+ hint: "Static type checker for Python"
4329
+ },
4330
+ {
4331
+ value: "pyright",
4332
+ label: "Pyright",
4333
+ hint: "Fast Python type checker from Microsoft"
4334
+ },
4335
+ {
4336
+ value: "none",
4337
+ label: "None",
4338
+ hint: "No code quality tools"
4339
+ }
4340
+ ];
4269
4341
  function resolvePythonWebFrameworkPrompt(pythonWebFramework) {
4270
4342
  return createStaticSinglePromptResolution(PYTHON_WEB_FRAMEWORK_PROMPT_OPTIONS, "fastapi", pythonWebFramework);
4271
4343
  }
@@ -4338,6 +4410,20 @@ async function getPythonAuthChoice(pythonAuth) {
4338
4410
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4339
4411
  return response;
4340
4412
  }
4413
+ function resolvePythonApiPrompt(pythonApi) {
4414
+ return createStaticSinglePromptResolution(PYTHON_API_PROMPT_OPTIONS, "none", pythonApi);
4415
+ }
4416
+ async function getPythonApiChoice(pythonApi) {
4417
+ const resolution = resolvePythonApiPrompt(pythonApi);
4418
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
4419
+ const response = await navigableSelect({
4420
+ message: "Select Python API framework",
4421
+ options: resolution.options,
4422
+ initialValue: resolution.initialValue
4423
+ });
4424
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4425
+ return response;
4426
+ }
4341
4427
  function resolvePythonTaskQueuePrompt(pythonTaskQueue) {
4342
4428
  return createStaticSinglePromptResolution(PYTHON_TASK_QUEUE_PROMPT_OPTIONS, "none", pythonTaskQueue);
4343
4429
  }
@@ -4611,6 +4697,51 @@ const RUST_LIBRARIES_PROMPT_OPTIONS = [
4611
4697
  label: "Serde",
4612
4698
  hint: "Serialization framework for Rust"
4613
4699
  },
4700
+ {
4701
+ value: "uuid",
4702
+ label: "uuid",
4703
+ hint: "UUID generation and parsing with Serde support"
4704
+ },
4705
+ {
4706
+ value: "chrono",
4707
+ label: "Chrono",
4708
+ hint: "Date and time library with Serde support"
4709
+ },
4710
+ {
4711
+ value: "reqwest",
4712
+ label: "Reqwest",
4713
+ hint: "Ergonomic HTTP client with JSON and Rustls TLS support"
4714
+ },
4715
+ {
4716
+ value: "config",
4717
+ label: "config",
4718
+ hint: "Layered configuration from files, environment, and defaults"
4719
+ },
4720
+ {
4721
+ value: "dashmap",
4722
+ label: "DashMap",
4723
+ hint: "Concurrent hash map for shared mutable state"
4724
+ },
4725
+ {
4726
+ value: "parking-lot",
4727
+ label: "parking_lot",
4728
+ hint: "Compact, fast synchronization primitives"
4729
+ },
4730
+ {
4731
+ value: "secrecy",
4732
+ label: "Secrecy",
4733
+ hint: "Secret value wrapper that avoids accidental exposure"
4734
+ },
4735
+ {
4736
+ value: "tokio-util",
4737
+ label: "Tokio Util",
4738
+ hint: "Tokio utilities for codecs, cancellation tokens, and IO helpers"
4739
+ },
4740
+ {
4741
+ value: "utoipa",
4742
+ label: "utoipa",
4743
+ hint: "OpenAPI documentation generation from Rust types"
4744
+ },
4614
4745
  {
4615
4746
  value: "validator",
4616
4747
  label: "Validator",
@@ -4635,6 +4766,16 @@ const RUST_LIBRARIES_PROMPT_OPTIONS = [
4635
4766
  value: "mockall",
4636
4767
  label: "Mockall",
4637
4768
  hint: "Powerful mocking library for Rust"
4769
+ },
4770
+ {
4771
+ value: "proptest",
4772
+ label: "Proptest",
4773
+ hint: "Property-based testing for Rust"
4774
+ },
4775
+ {
4776
+ value: "insta",
4777
+ label: "Insta",
4778
+ hint: "Snapshot testing for Rust"
4638
4779
  }
4639
4780
  ];
4640
4781
  const RUST_LOGGING_PROMPT_OPTIONS = [
@@ -4836,39 +4977,65 @@ async function getRustAuthChoice(rustAuth) {
4836
4977
 
4837
4978
  //#endregion
4838
4979
  //#region src/prompts/search.ts
4839
- async function getSearchChoice(search, backend) {
4840
- if (search !== void 0) return search;
4841
- if (backend === "none" || backend === "convex") return "none";
4980
+ const SEARCH_PROMPT_OPTIONS = [
4981
+ {
4982
+ value: "meilisearch",
4983
+ label: "Meilisearch",
4984
+ hint: "Lightning-fast search engine with typo tolerance"
4985
+ },
4986
+ {
4987
+ value: "typesense",
4988
+ label: "Typesense",
4989
+ hint: "Fast, typo-tolerant search with built-in vector search"
4990
+ },
4991
+ {
4992
+ value: "elasticsearch",
4993
+ label: "Elasticsearch",
4994
+ hint: "Distributed search and analytics engine with local and cloud deployments"
4995
+ },
4996
+ {
4997
+ value: "algolia",
4998
+ label: "Algolia",
4999
+ hint: "Hosted search API with instant results, typo tolerance, and analytics"
5000
+ },
5001
+ {
5002
+ value: "none",
5003
+ label: "None",
5004
+ hint: "Skip search engine setup"
5005
+ }
5006
+ ];
5007
+ const NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS = SEARCH_PROMPT_OPTIONS.filter((option) => option.value === "meilisearch" || option.value === "none");
5008
+ function resolveSearchPrompt(context = {}) {
5009
+ const options = context.ecosystem && context.ecosystem !== "typescript" ? NON_TYPESCRIPT_SEARCH_PROMPT_OPTIONS : SEARCH_PROMPT_OPTIONS;
5010
+ if ((!context.ecosystem || context.ecosystem === "typescript") && (context.backend === "none" || context.backend === "convex")) return {
5011
+ shouldPrompt: false,
5012
+ mode: "single",
5013
+ options: [],
5014
+ autoValue: "none"
5015
+ };
5016
+ return context.search !== void 0 ? {
5017
+ shouldPrompt: false,
5018
+ mode: "single",
5019
+ options,
5020
+ autoValue: context.search
5021
+ } : {
5022
+ shouldPrompt: true,
5023
+ mode: "single",
5024
+ options,
5025
+ initialValue: "none"
5026
+ };
5027
+ }
5028
+ async function getSearchChoice(search, backend, ecosystem) {
5029
+ const resolution = resolveSearchPrompt({
5030
+ search,
5031
+ backend,
5032
+ ecosystem
5033
+ });
5034
+ if (!resolution.shouldPrompt) return resolution.autoValue ?? "none";
4842
5035
  const response = await navigableSelect({
4843
5036
  message: "Select search engine",
4844
- options: [
4845
- {
4846
- value: "meilisearch",
4847
- label: "Meilisearch",
4848
- hint: "Lightning-fast search engine with typo tolerance"
4849
- },
4850
- {
4851
- value: "typesense",
4852
- label: "Typesense",
4853
- hint: "Fast, typo-tolerant search with built-in vector search"
4854
- },
4855
- {
4856
- value: "elasticsearch",
4857
- label: "Elasticsearch",
4858
- hint: "Distributed search and analytics engine with local and cloud deployments"
4859
- },
4860
- {
4861
- value: "algolia",
4862
- label: "Algolia",
4863
- hint: "Hosted search API with instant results, typo tolerance, and analytics"
4864
- },
4865
- {
4866
- value: "none",
4867
- label: "None",
4868
- hint: "Skip search engine setup"
4869
- }
4870
- ],
4871
- initialValue: "none"
5037
+ options: resolution.options,
5038
+ initialValue: resolution.initialValue
4872
5039
  });
4873
5040
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
4874
5041
  return response;
@@ -4920,6 +5087,16 @@ const STYLE_OPTIONS = [
4920
5087
  value: "mira",
4921
5088
  label: "Mira",
4922
5089
  hint: "Dense, made for data-heavy interfaces"
5090
+ },
5091
+ {
5092
+ value: "luma",
5093
+ label: "Luma",
5094
+ hint: "Modern shadcn/ui v4 preset"
5095
+ },
5096
+ {
5097
+ value: "sera",
5098
+ label: "Sera",
5099
+ hint: "Modern shadcn/ui v4 preset"
4923
5100
  }
4924
5101
  ];
4925
5102
  const ICON_LIBRARY_OPTIONS = [
@@ -4947,6 +5124,16 @@ const ICON_LIBRARY_OPTIONS = [
4947
5124
  value: "remixicon",
4948
5125
  label: "Remix Icon",
4949
5126
  hint: "Open-source neutral style icons"
5127
+ },
5128
+ {
5129
+ value: "heroicons",
5130
+ label: "Heroicons",
5131
+ hint: "Tailwind Labs SVG icon set"
5132
+ },
5133
+ {
5134
+ value: "react-icons",
5135
+ label: "React Icons",
5136
+ hint: "Popular wrapper for many icon packs"
4950
5137
  }
4951
5138
  ];
4952
5139
  const COLOR_THEME_OPTIONS = [
@@ -5445,6 +5632,14 @@ const UI_LIBRARY_OPTIONS = {
5445
5632
  label: "Mantine",
5446
5633
  hint: "Full-featured React component library with 120+ components"
5447
5634
  },
5635
+ mui: {
5636
+ label: "MUI",
5637
+ hint: "Popular React component library implementing Material Design"
5638
+ },
5639
+ antd: {
5640
+ label: "Ant Design",
5641
+ hint: "Enterprise-class React UI component library"
5642
+ },
5448
5643
  "base-ui": {
5449
5644
  label: "Base UI",
5450
5645
  hint: "Unstyled, accessible components from MUI team (Radix successor)"
@@ -5695,8 +5890,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5695
5890
  return getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend);
5696
5891
  },
5697
5892
  email: ({ results }) => {
5698
- if (results.ecosystem !== "typescript") return Promise.resolve("none");
5699
- return getEmailChoice(flags.email, results.backend);
5893
+ return getEmailChoice(flags.email, results.backend, results.ecosystem);
5700
5894
  },
5701
5895
  effect: ({ results }) => {
5702
5896
  if (results.ecosystem !== "typescript") return Promise.resolve("none");
@@ -5767,8 +5961,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5767
5961
  return getLoggingChoice(flags.logging, results.backend);
5768
5962
  },
5769
5963
  observability: ({ results }) => {
5770
- if (results.ecosystem !== "typescript") return Promise.resolve("none");
5771
- return getObservabilityChoice(flags.observability, results.backend);
5964
+ return getObservabilityChoice(flags.observability, results.backend, results.ecosystem);
5772
5965
  },
5773
5966
  featureFlags: ({ results }) => {
5774
5967
  if (results.ecosystem !== "typescript") return Promise.resolve("none");
@@ -5783,16 +5976,14 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5783
5976
  return getCMSChoice(flags.cms, results.backend);
5784
5977
  },
5785
5978
  caching: ({ results }) => {
5786
- if (results.ecosystem !== "typescript") return Promise.resolve("none");
5787
- return getCachingChoice(flags.caching, results.backend);
5979
+ return getCachingChoice(flags.caching, results.backend, results.ecosystem);
5788
5980
  },
5789
5981
  i18n: ({ results }) => {
5790
5982
  if (results.ecosystem !== "typescript") return Promise.resolve("none");
5791
5983
  return getI18nChoice(flags.i18n, results.frontend);
5792
5984
  },
5793
5985
  search: ({ results }) => {
5794
- if (results.ecosystem !== "typescript") return Promise.resolve("none");
5795
- return getSearchChoice(flags.search, results.backend);
5986
+ return getSearchChoice(flags.search, results.backend, results.ecosystem);
5796
5987
  },
5797
5988
  fileStorage: ({ results }) => {
5798
5989
  if (results.ecosystem !== "typescript") return Promise.resolve("none");
@@ -5858,6 +6049,11 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5858
6049
  if (results.ecosystem !== "python") return Promise.resolve("none");
5859
6050
  return getPythonAuthChoice(flags.pythonAuth);
5860
6051
  },
6052
+ pythonApi: ({ results }) => {
6053
+ if (results.ecosystem !== "python") return Promise.resolve("none");
6054
+ if (results.pythonWebFramework !== "django") return Promise.resolve("none");
6055
+ return getPythonApiChoice(flags.pythonApi);
6056
+ },
5861
6057
  pythonTaskQueue: ({ results }) => {
5862
6058
  if (results.ecosystem !== "python") return Promise.resolve("none");
5863
6059
  return getPythonTaskQueueChoice(flags.pythonTaskQueue);
@@ -5990,6 +6186,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
5990
6186
  pythonValidation: result.pythonValidation,
5991
6187
  pythonAi: result.pythonAi,
5992
6188
  pythonAuth: result.pythonAuth,
6189
+ pythonApi: result.pythonApi,
5993
6190
  pythonTaskQueue: result.pythonTaskQueue,
5994
6191
  pythonGraphql: result.pythonGraphql,
5995
6192
  pythonQuality: result.pythonQuality,
@@ -6217,6 +6414,10 @@ function appendCommonFlags(flags, config) {
6217
6414
  flags.push(config.install ? "--install" : "--no-install");
6218
6415
  }
6219
6416
  function appendSharedNonTypeScriptFlags(flags, config) {
6417
+ flags.push(`--email ${config.email}`);
6418
+ flags.push(`--observability ${config.observability}`);
6419
+ flags.push(`--caching ${config.caching}`);
6420
+ flags.push(`--search ${config.search}`);
6220
6421
  flags.push(formatArrayFlag("addons", config.addons));
6221
6422
  flags.push(formatArrayFlag("examples", config.examples));
6222
6423
  flags.push(`--db-setup ${config.dbSetup}`);
@@ -6258,6 +6459,7 @@ function getTypeScriptFlags(config) {
6258
6459
  flags.push(`--job-queue ${config.jobQueue}`);
6259
6460
  flags.push(`--logging ${config.logging}`);
6260
6461
  flags.push(`--observability ${config.observability}`);
6462
+ flags.push(`--feature-flags ${config.featureFlags}`);
6261
6463
  flags.push(`--caching ${config.caching}`);
6262
6464
  flags.push(`--i18n ${config.i18n}`);
6263
6465
  flags.push(`--cms ${config.cms}`);
@@ -6296,6 +6498,7 @@ function getPythonFlags(config) {
6296
6498
  flags.push(`--python-validation ${config.pythonValidation}`);
6297
6499
  flags.push(formatArrayFlag("python-ai", config.pythonAi));
6298
6500
  flags.push(`--python-auth ${config.pythonAuth}`);
6501
+ flags.push(`--python-api ${config.pythonApi}`);
6299
6502
  flags.push(`--python-task-queue ${config.pythonTaskQueue}`);
6300
6503
  flags.push(`--python-graphql ${config.pythonGraphql}`);
6301
6504
  flags.push(`--python-quality ${config.pythonQuality}`);
@@ -6517,100 +6720,14 @@ function getTemplateDescription(template) {
6517
6720
 
6518
6721
  //#endregion
6519
6722
  //#region src/utils/config-processing.ts
6520
- function processArrayOption(options) {
6521
- if (!options || options.length === 0) return [];
6522
- if (options.includes("none")) return [];
6523
- return options.filter((item) => item !== "none");
6524
- }
6525
6723
  function deriveProjectName(projectName, projectDirectory) {
6526
6724
  if (projectName) return projectName;
6527
6725
  if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
6528
6726
  return "";
6529
6727
  }
6530
6728
  function processFlags(options, projectName) {
6531
- const config = {};
6532
- if (options.ecosystem) config.ecosystem = options.ecosystem;
6533
- if (options.api) config.api = options.api;
6534
- if (options.backend) config.backend = options.backend;
6535
- if (options.database) config.database = options.database;
6536
- if (options.orm) config.orm = options.orm;
6537
- if (options.auth !== void 0) config.auth = options.auth;
6538
- if (options.payments !== void 0) config.payments = options.payments;
6539
- if (options.email !== void 0) config.email = options.email;
6540
- if (options.effect !== void 0) config.effect = options.effect;
6541
- if (options.stateManagement !== void 0) config.stateManagement = options.stateManagement;
6542
- if (options.validation !== void 0) config.validation = options.validation;
6543
- if (options.realtime !== void 0) config.realtime = options.realtime;
6544
- if (options.jobQueue !== void 0) config.jobQueue = options.jobQueue;
6545
- if (options.animation !== void 0) config.animation = options.animation;
6546
- if (options.ai !== void 0) config.ai = options.ai;
6547
- if (options.forms !== void 0) config.forms = options.forms;
6548
- if (options.testing !== void 0) config.testing = options.testing;
6549
- if (options.logging !== void 0) config.logging = options.logging;
6550
- if (options.observability !== void 0) config.observability = options.observability;
6551
- if (options.cms !== void 0) config.cms = options.cms;
6552
- if (options.caching !== void 0) config.caching = options.caching;
6553
- if (options.i18n !== void 0) config.i18n = options.i18n;
6554
- if (options.search !== void 0) config.search = options.search;
6555
- if (options.fileStorage !== void 0) config.fileStorage = options.fileStorage;
6556
- if (options.analytics !== void 0) config.analytics = options.analytics;
6557
- if (options.featureFlags !== void 0) config.featureFlags = options.featureFlags;
6558
- if (options.fileUpload !== void 0) config.fileUpload = options.fileUpload;
6559
- if (options.git !== void 0) config.git = options.git;
6560
- if (options.install !== void 0) config.install = options.install;
6561
- if (options.runtime) config.runtime = options.runtime;
6562
- if (options.dbSetup) config.dbSetup = options.dbSetup;
6563
- if (options.packageManager) config.packageManager = options.packageManager;
6564
- if (options.versionChannel) config.versionChannel = options.versionChannel;
6565
- if (options.webDeploy) config.webDeploy = options.webDeploy;
6566
- if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
6567
6729
  const derivedName = deriveProjectName(projectName, options.projectDirectory);
6568
- if (derivedName) config.projectName = projectName || derivedName;
6569
- if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
6570
- if (options.astroIntegration) config.astroIntegration = options.astroIntegration;
6571
- if (options.cssFramework) config.cssFramework = options.cssFramework;
6572
- if (options.uiLibrary) config.uiLibrary = options.uiLibrary;
6573
- if (options.shadcnBase) config.shadcnBase = options.shadcnBase;
6574
- if (options.shadcnStyle) config.shadcnStyle = options.shadcnStyle;
6575
- if (options.shadcnIconLibrary) config.shadcnIconLibrary = options.shadcnIconLibrary;
6576
- if (options.shadcnColorTheme) config.shadcnColorTheme = options.shadcnColorTheme;
6577
- if (options.shadcnBaseColor) config.shadcnBaseColor = options.shadcnBaseColor;
6578
- if (options.shadcnFont) config.shadcnFont = options.shadcnFont;
6579
- if (options.shadcnRadius) config.shadcnRadius = options.shadcnRadius;
6580
- if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
6581
- if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
6582
- if (options.aiDocs !== void 0) config.aiDocs = processArrayOption(options.aiDocs);
6583
- if (options.rustWebFramework !== void 0) config.rustWebFramework = options.rustWebFramework;
6584
- if (options.rustFrontend !== void 0) config.rustFrontend = options.rustFrontend;
6585
- if (options.rustOrm !== void 0) config.rustOrm = options.rustOrm;
6586
- if (options.rustApi !== void 0) config.rustApi = options.rustApi;
6587
- if (options.rustCli !== void 0) config.rustCli = options.rustCli;
6588
- if (options.rustLibraries !== void 0) config.rustLibraries = processArrayOption(options.rustLibraries);
6589
- if (options.rustLogging !== void 0) config.rustLogging = options.rustLogging;
6590
- if (options.rustErrorHandling !== void 0) config.rustErrorHandling = options.rustErrorHandling;
6591
- if (options.rustCaching !== void 0) config.rustCaching = options.rustCaching;
6592
- if (options.rustAuth !== void 0) config.rustAuth = options.rustAuth;
6593
- if (options.pythonWebFramework !== void 0) config.pythonWebFramework = options.pythonWebFramework;
6594
- if (options.pythonOrm !== void 0) config.pythonOrm = options.pythonOrm;
6595
- if (options.pythonValidation !== void 0) config.pythonValidation = options.pythonValidation;
6596
- if (options.pythonAi !== void 0) config.pythonAi = processArrayOption(options.pythonAi);
6597
- if (options.pythonAuth !== void 0) config.pythonAuth = options.pythonAuth;
6598
- if (options.pythonTaskQueue !== void 0) config.pythonTaskQueue = options.pythonTaskQueue;
6599
- if (options.pythonGraphql !== void 0) config.pythonGraphql = options.pythonGraphql;
6600
- if (options.pythonQuality !== void 0) config.pythonQuality = options.pythonQuality;
6601
- if (options.goWebFramework !== void 0) config.goWebFramework = options.goWebFramework;
6602
- if (options.goOrm !== void 0) config.goOrm = options.goOrm;
6603
- if (options.goApi !== void 0) config.goApi = options.goApi;
6604
- if (options.goCli !== void 0) config.goCli = options.goCli;
6605
- if (options.goLogging !== void 0) config.goLogging = options.goLogging;
6606
- if (options.goAuth !== void 0) config.goAuth = options.goAuth;
6607
- if (options.javaWebFramework !== void 0) config.javaWebFramework = options.javaWebFramework;
6608
- if (options.javaBuildTool !== void 0) config.javaBuildTool = options.javaBuildTool;
6609
- if (options.javaOrm !== void 0) config.javaOrm = options.javaOrm;
6610
- if (options.javaAuth !== void 0) config.javaAuth = options.javaAuth;
6611
- if (options.javaLibraries !== void 0) config.javaLibraries = processArrayOption(options.javaLibraries);
6612
- if (options.javaTestingLibraries !== void 0) config.javaTestingLibraries = processArrayOption(options.javaTestingLibraries);
6613
- return config;
6730
+ return cliInputToProjectConfigPartial(options, projectName || derivedName);
6614
6731
  }
6615
6732
  function getProvidedFlags(options) {
6616
6733
  return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
@@ -7066,19 +7183,20 @@ function validateApiConstraints(_config, _options) {}
7066
7183
  function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set()) {
7067
7184
  if (config.ecosystem !== "java") return;
7068
7185
  const hasSpringBoot = config.javaWebFramework === "spring-boot";
7186
+ const hasJavaWebFramework = config.javaWebFramework !== "none";
7069
7187
  const hasNoBuildTool = config.javaBuildTool === "none";
7070
7188
  const hasJavaLibraries = (config.javaLibraries ?? []).some((library) => library !== "none");
7071
7189
  const hasJavaTestingLibraries = (config.javaTestingLibraries ?? []).some((library) => library !== "none");
7072
7190
  const hasSpringOnlyFeatures = config.javaOrm !== "none" || config.javaAuth !== "none" || hasJavaLibraries;
7073
- if (hasNoBuildTool && hasSpringBoot) incompatibilityError({
7074
- message: "Spring Boot requires Maven or Gradle in the Java scaffold.",
7191
+ if (hasNoBuildTool && hasJavaWebFramework) incompatibilityError({
7192
+ message: "Java web frameworks require Maven or Gradle in the Java scaffold.",
7075
7193
  provided: {
7076
7194
  "java-web-framework": config.javaWebFramework ?? "none",
7077
7195
  "java-build-tool": config.javaBuildTool ?? "none"
7078
7196
  },
7079
- suggestions: ["Use --java-build-tool maven or --java-build-tool gradle with Spring Boot", "Use --java-web-framework none for a plain Java source-only scaffold"]
7197
+ suggestions: ["Use --java-build-tool maven or --java-build-tool gradle with Java web frameworks", "Use --java-web-framework none for a plain Java source-only scaffold"]
7080
7198
  });
7081
- if ((config.javaWebFramework === "none" || hasNoBuildTool) && hasSpringOnlyFeatures) incompatibilityError({
7199
+ if ((!hasSpringBoot || hasNoBuildTool) && hasSpringOnlyFeatures) incompatibilityError({
7082
7200
  message: "Spring-only Java features require the Spring Boot scaffold with Maven or Gradle.",
7083
7201
  provided: {
7084
7202
  "java-web-framework": config.javaWebFramework ?? "none",
@@ -7087,7 +7205,7 @@ function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set
7087
7205
  "java-auth": config.javaAuth ?? "none",
7088
7206
  "java-libraries": (config.javaLibraries ?? []).join(" ") || "none"
7089
7207
  },
7090
- suggestions: ["Use --java-web-framework spring-boot and a real build tool for Spring features", "Clear --java-orm, --java-auth, and --java-libraries when using plain Java"]
7208
+ suggestions: ["Use --java-web-framework spring-boot and a real build tool for Spring features", "Clear --java-orm, --java-auth, and --java-libraries when using plain Java or Quarkus"]
7091
7209
  });
7092
7210
  if (hasNoBuildTool && hasJavaTestingLibraries) incompatibilityError({
7093
7211
  message: "Java testing libraries require Maven or Gradle to manage test dependencies.",
@@ -7098,6 +7216,82 @@ function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set
7098
7216
  suggestions: ["Use --java-build-tool maven or --java-build-tool gradle to enable JUnit/Mockito/Testcontainers", "Set --java-testing-libraries none for a source-only plain Java scaffold"]
7099
7217
  });
7100
7218
  }
7219
+ function validateEmailConstraints(config) {
7220
+ if (!config.email || config.email === "none") return;
7221
+ if (config.ecosystem !== "typescript" && config.email !== "resend") incompatibilityError({
7222
+ message: "Only Resend email is available for non-TypeScript ecosystems.",
7223
+ provided: {
7224
+ ecosystem: config.ecosystem ?? "typescript",
7225
+ email: config.email
7226
+ },
7227
+ suggestions: ["Use --email resend", "Use --email none"]
7228
+ });
7229
+ if (config.ecosystem === "java" && config.email === "resend" && config.javaBuildTool === "none") incompatibilityError({
7230
+ message: "Resend email for Java requires Maven or Gradle to manage the SDK dependency.",
7231
+ provided: {
7232
+ "java-build-tool": "none",
7233
+ email: "resend"
7234
+ },
7235
+ suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
7236
+ });
7237
+ }
7238
+ function validateObservabilityConstraints(config) {
7239
+ if (!config.observability || config.observability === "none") return;
7240
+ if (config.ecosystem !== "typescript" && config.observability !== "sentry") incompatibilityError({
7241
+ message: "Only Sentry observability is available for non-TypeScript ecosystems.",
7242
+ provided: {
7243
+ ecosystem: config.ecosystem ?? "typescript",
7244
+ observability: config.observability
7245
+ },
7246
+ suggestions: ["Use --observability sentry", "Use --observability none"]
7247
+ });
7248
+ if (config.ecosystem === "java" && config.observability === "sentry" && config.javaBuildTool === "none") incompatibilityError({
7249
+ message: "Sentry observability for Java requires Maven or Gradle to manage the SDK dependency.",
7250
+ provided: {
7251
+ "java-build-tool": "none",
7252
+ observability: "sentry"
7253
+ },
7254
+ suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
7255
+ });
7256
+ }
7257
+ function validateCachingConstraints(config) {
7258
+ if (!config.caching || config.caching === "none") return;
7259
+ if (config.ecosystem !== "typescript" && config.caching !== "upstash-redis") incompatibilityError({
7260
+ message: "Only Upstash Redis caching is available for non-TypeScript ecosystems.",
7261
+ provided: {
7262
+ ecosystem: config.ecosystem ?? "typescript",
7263
+ caching: config.caching
7264
+ },
7265
+ suggestions: ["Use --caching upstash-redis", "Use --caching none"]
7266
+ });
7267
+ if (config.ecosystem === "java" && config.caching === "upstash-redis" && config.javaBuildTool === "none") incompatibilityError({
7268
+ message: "Upstash Redis caching for Java requires Maven or Gradle to manage the Redis client dependency.",
7269
+ provided: {
7270
+ "java-build-tool": "none",
7271
+ caching: "upstash-redis"
7272
+ },
7273
+ suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
7274
+ });
7275
+ }
7276
+ function validateSearchConstraints(config) {
7277
+ if (!config.search || config.search === "none") return;
7278
+ if (config.ecosystem !== "typescript" && config.search !== "meilisearch") incompatibilityError({
7279
+ message: "Only Meilisearch search is available for non-TypeScript ecosystems.",
7280
+ provided: {
7281
+ ecosystem: config.ecosystem ?? "typescript",
7282
+ search: config.search
7283
+ },
7284
+ suggestions: ["Use --search meilisearch", "Use --search none"]
7285
+ });
7286
+ if (config.ecosystem === "java" && config.search === "meilisearch" && config.javaBuildTool === "none") incompatibilityError({
7287
+ message: "Meilisearch search for Java requires Maven or Gradle to manage the SDK dependency.",
7288
+ provided: {
7289
+ "java-build-tool": "none",
7290
+ search: "meilisearch"
7291
+ },
7292
+ suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
7293
+ });
7294
+ }
7101
7295
  function validateShadcnConstraints(config, providedFlags) {
7102
7296
  const shadcnFlagMap = {
7103
7297
  shadcnBase: "--shadcn-base",
@@ -7118,6 +7312,16 @@ function validateShadcnConstraints(config, providedFlags) {
7118
7312
  suggestions: ["Add --ui-library shadcn-ui to use shadcn customization flags", "Remove the --shadcn-* flags if not using shadcn/ui"]
7119
7313
  });
7120
7314
  }
7315
+ function validatePythonApiConstraints(config) {
7316
+ if (config.ecosystem === "python" && config.pythonApi && config.pythonApi !== "none" && config.pythonWebFramework !== "django") incompatibilityError({
7317
+ message: "Python API frameworks require --python-web-framework django.",
7318
+ provided: {
7319
+ "python-web-framework": config.pythonWebFramework || "none",
7320
+ "python-api": config.pythonApi
7321
+ },
7322
+ suggestions: ["Use --python-web-framework django with --python-api django-rest-framework or django-ninja", "Set --python-api none for FastAPI, Flask, Litestar, or no Python web framework"]
7323
+ });
7324
+ }
7121
7325
  function validateFullConfig(config, providedFlags, options) {
7122
7326
  validateEcosystemAuthCompatibility(config, providedFlags);
7123
7327
  validateDatabaseOrmAuth(config, providedFlags);
@@ -7130,6 +7334,11 @@ function validateFullConfig(config, providedFlags, options) {
7130
7334
  validateBackendConstraints(config, providedFlags, options);
7131
7335
  validateFrontendConstraints(config, providedFlags);
7132
7336
  /* @__PURE__ */ validateApiConstraints(config, options);
7337
+ validatePythonApiConstraints(config);
7338
+ validateEmailConstraints(config);
7339
+ validateObservabilityConstraints(config);
7340
+ validateCachingConstraints(config);
7341
+ validateSearchConstraints(config);
7133
7342
  validateJavaConstraints(config, providedFlags);
7134
7343
  validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
7135
7344
  validateSelfBackendCompatibility(providedFlags, options, config);
@@ -7166,6 +7375,11 @@ function validateConfigForProgrammaticUse(config) {
7166
7375
  validateDatabaseOrmAuth(config);
7167
7376
  if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
7168
7377
  validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
7378
+ validatePythonApiConstraints(config);
7379
+ validateEmailConstraints(config);
7380
+ validateObservabilityConstraints(config);
7381
+ validateCachingConstraints(config);
7382
+ validateSearchConstraints(config);
7169
7383
  validateJavaConstraints(config);
7170
7384
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
7171
7385
  if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime, config.ecosystem, config.rustFrontend, config.javaWebFramework, config.database);
@@ -8840,7 +9054,8 @@ function displayGoInstructions(config) {
8840
9054
  if (goApi && goApi !== "none") output += `${pc.cyan("•")} API: ${{ "grpc-go": "gRPC-Go" }[goApi] || goApi}\n`;
8841
9055
  if (goCli && goCli !== "none") output += `${pc.cyan("•")} CLI: ${{
8842
9056
  cobra: "Cobra",
8843
- bubbletea: "Bubble Tea"
9057
+ bubbletea: "Bubble Tea",
9058
+ "urfave-cli": "urfave/cli"
8844
9059
  }[goCli] || goCli}\n`;
8845
9060
  if (goLogging && goLogging !== "none") output += `${pc.cyan("•")} Logging: ${{
8846
9061
  zap: "Zap",
@@ -8942,6 +9157,7 @@ function displayJavaInstructions(config) {
8942
9157
  const { projectName, relativePath, depsInstalled, javaWebFramework, javaBuildTool, javaOrm, javaAuth, javaLibraries, javaTestingLibraries } = config;
8943
9158
  const cdCmd = `cd ${relativePath}`;
8944
9159
  const isSpringBoot = javaWebFramework === "spring-boot" && javaBuildTool !== "none";
9160
+ const isQuarkus = javaWebFramework === "quarkus" && javaBuildTool !== "none";
8945
9161
  const rawJavaLibraries = isSpringBoot ? javaLibraries.filter((library) => library !== "none") : [];
8946
9162
  const rawJavaLibrarySet = new Set(rawJavaLibraries);
8947
9163
  const jpaRequiredJavaLibraries = new Set(["flyway", "liquibase"]);
@@ -8953,7 +9169,7 @@ function displayJavaInstructions(config) {
8953
9169
  }
8954
9170
  const effectiveJavaTestingLibraries = javaBuildTool === "none" ? [] : javaTestingLibraries.filter((library) => library !== "none");
8955
9171
  const buildToolCommand = javaBuildTool === "none" ? null : javaBuildTool === "gradle" ? process.platform === "win32" ? "gradlew.bat" : "./gradlew" : process.platform === "win32" ? "mvnw.cmd" : "./mvnw";
8956
- const runCommand = buildToolCommand ? isSpringBoot ? javaBuildTool === "gradle" ? `${buildToolCommand} bootRun` : `${buildToolCommand} spring-boot:run` : javaBuildTool === "gradle" ? `${buildToolCommand} run` : `${buildToolCommand} exec:java` : null;
9172
+ 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;
8957
9173
  const packageCommand = buildToolCommand ? javaBuildTool === "gradle" ? `${buildToolCommand} build` : `${buildToolCommand} package` : null;
8958
9174
  const sourceCompileCommand = buildToolCommand ? null : `javac -d out ${getJavaMainSourcePath(projectName)}`;
8959
9175
  const sourceRunCommand = buildToolCommand ? null : `java -cp out ${getJavaMainClass(projectName)}`;
@@ -8966,7 +9182,10 @@ function displayJavaInstructions(config) {
8966
9182
  output += `${pc.cyan(`${stepCounter++}.`)} ${sourceRunCommand}\n`;
8967
9183
  } else output += `${pc.cyan(`${stepCounter++}.`)} Add Maven or Gradle, then run the app\n`;
8968
9184
  output += `\n${pc.bold("Your Java project includes:")}\n`;
8969
- if (isSpringBoot) output += `${pc.cyan("•")} Web Framework: ${{ "spring-boot": "Spring Boot" }[javaWebFramework] || javaWebFramework}\n`;
9185
+ if (isSpringBoot || isQuarkus) output += `${pc.cyan("•")} Web Framework: ${{
9186
+ "spring-boot": "Spring Boot",
9187
+ quarkus: "Quarkus"
9188
+ }[javaWebFramework] || javaWebFramework}\n`;
8970
9189
  else output += `${pc.cyan("•")} Scaffold: Plain Java\n`;
8971
9190
  if (javaBuildTool && javaBuildTool !== "none") output += `${pc.cyan("•")} Build Tool: ${{
8972
9191
  maven: "Maven Wrapper",
@@ -9023,7 +9242,7 @@ function displayJavaInstructions(config) {
9023
9242
  consola$1.box(output);
9024
9243
  }
9025
9244
  function displayPythonInstructions(config) {
9026
- const { relativePath, depsInstalled, pythonWebFramework, pythonOrm, pythonValidation, pythonAi, pythonTaskQueue, pythonQuality } = config;
9245
+ const { relativePath, depsInstalled, pythonWebFramework, pythonOrm, pythonValidation, pythonAi, pythonApi, pythonTaskQueue, pythonQuality } = config;
9027
9246
  const cdCmd = `cd ${relativePath}`;
9028
9247
  let runCommand = "uv run uvicorn app.main:app --reload";
9029
9248
  if (pythonWebFramework === "django") runCommand = "uv run python manage.py runserver";
@@ -9058,14 +9277,25 @@ function displayPythonInstructions(config) {
9058
9277
  const aiList = pythonAi.filter((ai) => ai !== "none").map((ai) => aiNames[ai] || ai).join(", ");
9059
9278
  output += `${pc.cyan("•")} AI: ${aiList}\n`;
9060
9279
  }
9280
+ if (pythonApi && pythonApi !== "none") output += `${pc.cyan("•")} API Framework: ${{
9281
+ "django-rest-framework": "Django REST Framework",
9282
+ "django-ninja": "Django Ninja"
9283
+ }[pythonApi] || pythonApi}\n`;
9061
9284
  if (pythonTaskQueue && pythonTaskQueue !== "none") output += `${pc.cyan("•")} Task Queue: ${{ celery: "Celery" }[pythonTaskQueue] || pythonTaskQueue}\n`;
9062
- if (pythonQuality && pythonQuality !== "none") output += `${pc.cyan("•")} Code Quality: ${{ ruff: "Ruff" }[pythonQuality] || pythonQuality}\n`;
9285
+ if (pythonQuality && pythonQuality !== "none") output += `${pc.cyan("•")} Code Quality: ${{
9286
+ ruff: "Ruff",
9287
+ mypy: "mypy",
9288
+ pyright: "Pyright"
9289
+ }[pythonQuality] || pythonQuality}\n`;
9063
9290
  output += `\n${pc.bold("Common Python commands:")}\n`;
9064
9291
  output += `${pc.cyan("•")} Install: uv sync\n`;
9065
9292
  output += `${pc.cyan("•")} Run: ${runCommand}\n`;
9066
9293
  output += `${pc.cyan("•")} Test: uv run pytest\n`;
9067
- output += `${pc.cyan("")} Format: uv run ruff format .\n`;
9068
- output += `${pc.cyan("•")} Lint: uv run ruff check .\n`;
9294
+ if (pythonQuality === "ruff") {
9295
+ output += `${pc.cyan("•")} Format: uv run ruff format .\n`;
9296
+ output += `${pc.cyan("•")} Lint: uv run ruff check .\n`;
9297
+ } else if (pythonQuality === "mypy") output += `${pc.cyan("•")} Type check: uv run mypy src/app tests\n`;
9298
+ else if (pythonQuality === "pyright") output += `${pc.cyan("•")} Type check: uv run pyright\n`;
9069
9299
  output += `\n${pc.bold("Your project will be available at:")}\n`;
9070
9300
  output += `${pc.cyan("•")} API: http://localhost:8000\n`;
9071
9301
  output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow — star the repo!\n`;
@@ -9251,6 +9481,7 @@ async function createProjectHandler(input, options = {}) {
9251
9481
  pythonValidation: "none",
9252
9482
  pythonAi: [],
9253
9483
  pythonAuth: "none",
9484
+ pythonApi: "none",
9254
9485
  pythonTaskQueue: "none",
9255
9486
  pythonGraphql: "none",
9256
9487
  pythonQuality: "none",
@@ -9774,6 +10005,7 @@ async function createVirtual(options) {
9774
10005
  pythonValidation: options.pythonValidation || "none",
9775
10006
  pythonAi: options.pythonAi || [],
9776
10007
  pythonAuth: options.pythonAuth || "none",
10008
+ pythonApi: options.pythonApi || "none",
9777
10009
  pythonTaskQueue: options.pythonTaskQueue || "none",
9778
10010
  pythonGraphql: options.pythonGraphql || "none",
9779
10011
  pythonQuality: options.pythonQuality || "none",