create-better-fullstack 2.1.0 → 2.1.2

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.
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { c as DEFAULT_CONFIG, d as getUserPkgManager, g as getGraphSummary, h as getGraphPart, i as readBtsConfigFromFile, l as DEFAULT_UI_LIBRARY_BY_FRONTEND, m as getGraphBackendUrl, o as writeBtsConfig, p as getGraphBackendDeployInstructions, s as getLatestCLIVersion, t as buildBtsConfigForPersistence, u as getDefaultConfig, v as hasGraphPart, y as types_exports } from "./bts-config-InNcw1aJ.mjs";
2
+ import { A as types_exports, D as getGraphSummary, E as getGraphPart, S as getUserPkgManager, T as getGraphBackendUrl, _ as writeBtsConfig, a as handleError, b as DEFAULT_UI_LIBRARY_BY_FRONTEND, c as isSilent, d as setLastPromptShownUI, f as buildBtsConfigForPersistence, h as readBtsConfigFromFile, i as exitWithError, k as hasGraphPart, l as runWithContextAsync, n as UserCancelledError, o as didLastPromptShowUI, r as exitCancelled, s as isFirstPrompt, t as CLIError, u as setIsFirstPrompt, v as getLatestCLIVersion, w as getGraphBackendDeployInstructions, x as getDefaultConfig, y as DEFAULT_CONFIG } from "./errors-D9yiiGVq.mjs";
3
3
  import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
- import { a as handleError, c as isSilent, d as setLastPromptShownUI, i as exitWithError, l as runWithContextAsync, n as UserCancelledError, o as didLastPromptShowUI, r as exitCancelled, s as isFirstPrompt, t as CLIError, u as setIsFirstPrompt } from "./errors-ns_o2OKg.mjs";
5
- import { i as canPromptInteractively, n as getPackageExecutionArgs, r as addPackageDependency, t as setupAddons } from "./addons-setup-uSvqHagW.mjs";
6
- import { A as validateAddonsAgainstFrontends, B as validateWorkersCompatibility, C as isExampleAIAllowed$1, D as requiresChatSdkVercelAI, E as isWebFrontend$1, F as validateServerDeployRequiresBackend, H as incompatibilityError, I as validateUILibraryCSSFrameworkCompatibility, L as validateUILibraryFrontendCompatibility, M as validateExamplesCompatibility, N as validatePaymentsCompatibility, O as splitFrontends$1, P as validateSelfBackendCompatibility, R as validateWebDeployFrontendTemplates, S as hasWebStyling$1, T as isFrontendAllowedWithBackend$1, U as missingRequirementError, V as constraintError, _ as isGoBack, a as runMavenTests, b as getCompatibleCSSFrameworks$1, c as applyDependencyVersionChannel, d as isCancel$1, f as navigableConfirm, g as GO_BACK_SYMBOL, h as setIsFirstPrompt$1, i as runGradleTests, j as validateApiFrontendCompatibility, k as validateAIFrontendCompatibility, l as getAddonsChoice, m as navigableSelect, n as runCargoBuild, o as runMixCompile, p as navigableMultiselect, r as runGoModTidy, s as runUvSync, t as installDependencies, v as allowedApisForFrontends$1, w as isExampleChatSdkAllowed$1, x as getCompatibleUILibraries$1, y as ensureSingleWebAndNative, z as validateWebDeployRequiresWebFrontend } from "./install-dependencies-CDjTNvIV.mjs";
7
- import { n as commandExists, t as runGeneratedChecks } from "./generated-checks-CUhFFfVo.mjs";
8
- import { n as getTemplateDescription, r as generateReproducibleCommand, t as getTemplateConfig } from "./templates-B351F1yX.mjs";
4
+ import { i as canPromptInteractively, n as getPackageExecutionArgs, r as addPackageDependency, t as setupAddons } from "./addons-setup-C_xrNtkL.mjs";
5
+ import { a as generateReproducibleCommand, i as getTemplateDescription, n as validateFullConfig, o as CreateCommandInputSchema, r as getTemplateConfig, t as validateConfigForProgrammaticUse } from "./config-validation-C4glouQh.mjs";
6
+ import { a as getCompatibleUILibraries$1, c as isExampleChatSdkAllowed$1, d as requiresChatSdkVercelAI, f as splitFrontends$1, i as getCompatibleCSSFrameworks$1, l as isFrontendAllowedWithBackend$1, o as hasWebStyling$1, s as isExampleAIAllowed$1, t as allowedApisForFrontends$1, u as isWebFrontend$1 } from "./compatibility-rules-D7zYNVjC.mjs";
7
+ import { _ as isGoBack, a as runMavenTests, c as applyDependencyVersionChannel, d as isCancel$1, f as navigableConfirm, g as GO_BACK_SYMBOL, h as setIsFirstPrompt$1, i as runGradleTests, l as getAddonsChoice, m as navigableSelect, n as runCargoBuild, o as runMixCompile, p as navigableMultiselect, r as runGoModTidy, s as runUvSync, t as installDependencies } from "./install-dependencies-DDGF-zDG.mjs";
8
+ import { n as commandExists, t as runGeneratedChecks } from "./generated-checks-DUvVXWId.mjs";
9
9
  import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
10
10
  import { createRouterClient, os } from "@orpc/server";
11
11
  import pc from "picocolors";
@@ -338,158 +338,6 @@ async function telemetryHandler(input) {
338
338
  log.message(pc.dim(`Change anytime: ${pc.cyan("create-better-fullstack telemetry enable")} / ${pc.cyan("create-better-fullstack telemetry disable")} (or set ${pc.cyan("BTS_TELEMETRY_DISABLED=1")}).`));
339
339
  }
340
340
 
341
- //#endregion
342
- //#region src/create-command-input.ts
343
- const CreateCommandOptionsSchema = z.object({
344
- template: types_exports.TemplateSchema.optional().describe("Use a predefined template"),
345
- fromHistory: z.number().optional().describe("Replay the stack of the Nth most-recent project from history (1 = most recent)"),
346
- config: z.string().optional().describe("Path to a bts.jsonc/JSON config file to use as the base stack"),
347
- yes: z.boolean().optional().default(false).describe("Use default configuration"),
348
- yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
349
- part: z.array(z.string()).optional().describe("Stack graph part binding, e.g. frontend:typescript:next or backend.orm:go:gorm"),
350
- verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
351
- dryRun: z.boolean().optional().default(false).describe("Preview generated file tree without writing to disk"),
352
- verify: z.boolean().optional().default(false).describe("Run generated project checks after scaffolding without starting dev servers"),
353
- ecosystem: types_exports.EcosystemSchema.optional().describe("Language ecosystem (typescript, react-native, rust, python, go, java, or elixir)"),
354
- database: types_exports.DatabaseSchema.optional(),
355
- orm: types_exports.ORMSchema.optional(),
356
- auth: types_exports.AuthSchema.optional(),
357
- payments: types_exports.PaymentsSchema.optional(),
358
- email: types_exports.EmailSchema.optional(),
359
- fileUpload: types_exports.FileUploadSchema.optional(),
360
- effect: types_exports.EffectSchema.optional(),
361
- stateManagement: types_exports.StateManagementSchema.optional(),
362
- validation: types_exports.ValidationSchema.optional(),
363
- forms: types_exports.FormsSchema.optional(),
364
- testing: types_exports.TestingSchema.optional(),
365
- ai: types_exports.AISchema.optional(),
366
- realtime: types_exports.RealtimeSchema.optional(),
367
- jobQueue: types_exports.JobQueueSchema.optional(),
368
- animation: types_exports.AnimationSchema.optional(),
369
- logging: types_exports.LoggingSchema.optional(),
370
- observability: types_exports.ObservabilitySchema.optional(),
371
- featureFlags: types_exports.FeatureFlagsSchema.optional().describe("Feature flags provider"),
372
- analytics: types_exports.AnalyticsSchema.optional().describe("Privacy-focused analytics"),
373
- cms: types_exports.CMSSchema.optional().describe("Headless CMS solution"),
374
- caching: types_exports.CachingSchema.optional().describe("Caching solution"),
375
- rateLimit: types_exports.RateLimitSchema.optional().describe("Rate limiting solution"),
376
- i18n: types_exports.I18nSchema.optional().describe("Internationalization (i18n) library"),
377
- search: types_exports.SearchSchema.optional().describe("Search engine solution"),
378
- vectorDb: types_exports.VectorDbSchema.optional().describe("Vector database for AI embeddings (pgvector, qdrant, chroma, pinecone)"),
379
- fileStorage: types_exports.FileStorageSchema.optional().describe("File storage solution (S3, R2)"),
380
- mobileNavigation: types_exports.MobileNavigationSchema.optional().describe("Mobile navigation (expo-router, react-navigation)"),
381
- mobileUI: types_exports.MobileUISchema.optional().describe("Mobile UI (tamagui, gluestack-ui, uniwind, unistyles)"),
382
- mobileStorage: types_exports.MobileStorageSchema.optional().describe("Mobile storage (mmkv)"),
383
- mobileTesting: types_exports.MobileTestingSchema.optional().describe("Mobile testing (maestro, react-native-testing-library)"),
384
- mobilePush: types_exports.MobilePushSchema.optional().describe("Mobile push notifications (expo-notifications)"),
385
- mobileOTA: types_exports.MobileOTASchema.optional().describe("Mobile OTA updates (expo-updates)"),
386
- mobileDeepLinking: types_exports.MobileDeepLinkingSchema.optional().describe("Mobile deep linking (expo-linking)"),
387
- frontend: z.array(types_exports.FrontendSchema).optional(),
388
- astroIntegration: types_exports.AstroIntegrationSchema.optional().describe("Astro UI framework integration (react, vue, svelte, solid)"),
389
- addons: z.array(types_exports.AddonsSchema).optional(),
390
- examples: z.array(types_exports.ExamplesSchema).optional(),
391
- git: z.boolean().optional(),
392
- packageManager: types_exports.PackageManagerSchema.optional(),
393
- install: z.boolean().optional(),
394
- versionChannel: types_exports.VersionChannelSchema.optional().describe("Dependency version channel (stable, latest, beta)"),
395
- dbSetup: types_exports.DatabaseSetupSchema.optional(),
396
- backend: types_exports.BackendSchema.optional(),
397
- runtime: types_exports.RuntimeSchema.optional(),
398
- api: types_exports.APISchema.optional(),
399
- cssFramework: types_exports.CSSFrameworkSchema.optional(),
400
- uiLibrary: types_exports.UILibrarySchema.optional(),
401
- shadcnBase: types_exports.ShadcnBaseSchema.optional().describe("shadcn/ui headless library (radix, base)"),
402
- shadcnStyle: types_exports.ShadcnStyleSchema.optional().describe("shadcn/ui visual style (vega, nova, maia, lyra, mira)"),
403
- shadcnIconLibrary: types_exports.ShadcnIconLibrarySchema.optional().describe("shadcn/ui icon library (lucide, tabler, hugeicons, phosphor, remixicon)"),
404
- shadcnColorTheme: types_exports.ShadcnColorThemeSchema.optional().describe("shadcn/ui color theme (neutral, blue, violet, etc.)"),
405
- shadcnBaseColor: types_exports.ShadcnBaseColorSchema.optional().describe("shadcn/ui base neutral color (neutral, stone, zinc, gray)"),
406
- shadcnFont: types_exports.ShadcnFontSchema.optional().describe("shadcn/ui font (inter, geist, figtree, etc.)"),
407
- shadcnRadius: types_exports.ShadcnRadiusSchema.optional().describe("shadcn/ui border radius (default, none, small, medium, large)"),
408
- webDeploy: types_exports.WebDeploySchema.optional(),
409
- serverDeploy: types_exports.ServerDeploySchema.optional(),
410
- directoryConflict: types_exports.DirectoryConflictSchema.optional(),
411
- renderTitle: z.boolean().optional(),
412
- disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
413
- manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup"),
414
- rustWebFramework: types_exports.RustWebFrameworkSchema.optional().describe("Rust web framework (axum, actix-web)"),
415
- rustFrontend: types_exports.RustFrontendSchema.optional().describe("Rust WASM frontend (leptos, dioxus)"),
416
- rustOrm: types_exports.RustOrmSchema.optional().describe("Rust ORM/database (sea-orm, sqlx)"),
417
- rustApi: types_exports.RustApiSchema.optional().describe("Rust API layer (tonic, async-graphql)"),
418
- rustCli: types_exports.RustCliSchema.optional().describe("Rust CLI tools (clap, ratatui)"),
419
- rustLibraries: z.array(types_exports.RustLibrariesSchema).optional().describe("Rust core libraries"),
420
- rustLogging: types_exports.RustLoggingSchema.optional().describe("Rust logging (tracing, env-logger)"),
421
- rustErrorHandling: types_exports.RustErrorHandlingSchema.optional().describe("Rust error handling (anyhow-thiserror, eyre)"),
422
- rustCaching: types_exports.RustCachingSchema.optional().describe("Rust caching (moka, redis)"),
423
- rustAuth: types_exports.RustAuthSchema.optional().describe("Rust auth (oauth2)"),
424
- rustRealtime: types_exports.RustRealtimeSchema.optional().describe("Rust realtime (tokio-tungstenite)"),
425
- rustMessageQueue: types_exports.RustMessageQueueSchema.optional().describe("Rust message queue (lapin)"),
426
- rustObservability: types_exports.RustObservabilitySchema.optional().describe("Rust observability (opentelemetry)"),
427
- rustTemplating: types_exports.RustTemplatingSchema.optional().describe("Rust templating (askama, tera)"),
428
- pythonWebFramework: types_exports.PythonWebFrameworkSchema.optional().describe("Python web framework (fastapi, django)"),
429
- pythonOrm: types_exports.PythonOrmSchema.optional().describe("Python ORM/database (sqlalchemy, sqlmodel)"),
430
- pythonValidation: types_exports.PythonValidationSchema.optional().describe("Python validation (pydantic)"),
431
- pythonAi: z.array(types_exports.PythonAiSchema).optional().describe("Python AI/ML frameworks"),
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)"),
434
- pythonTaskQueue: types_exports.PythonTaskQueueSchema.optional().describe("Python task queue (celery)"),
435
- pythonGraphql: types_exports.PythonGraphqlSchema.optional().describe("Python GraphQL framework (strawberry)"),
436
- pythonQuality: types_exports.PythonQualitySchema.optional().describe("Python code quality (ruff, mypy, pyright)"),
437
- pythonTesting: z.array(types_exports.PythonTestingSchema).optional().describe("Python testing libraries (pytest, hypothesis)"),
438
- pythonCaching: types_exports.PythonCachingSchema.optional().describe("Python caching (redis, aiocache)"),
439
- pythonRealtime: types_exports.PythonRealtimeSchema.optional().describe("Python realtime (python-socketio, websockets)"),
440
- pythonObservability: types_exports.PythonObservabilitySchema.optional().describe("Python observability (opentelemetry)"),
441
- pythonCli: z.array(types_exports.PythonCliSchema).optional().describe("Python CLI tooling (typer, click, rich)"),
442
- goWebFramework: types_exports.GoWebFrameworkSchema.optional().describe("Go web framework (gin, echo, fiber)"),
443
- goOrm: types_exports.GoOrmSchema.optional().describe("Go ORM/database (gorm, sqlc)"),
444
- goApi: types_exports.GoApiSchema.optional().describe("Go API layer (grpc-go)"),
445
- goCli: types_exports.GoCliSchema.optional().describe("Go CLI tools (cobra, bubbletea, urfave-cli)"),
446
- goLogging: types_exports.GoLoggingSchema.optional().describe("Go logging (zap, zerolog, slog)"),
447
- goAuth: types_exports.GoAuthSchema.optional().describe("Go auth (casbin, jwt, goth)"),
448
- goTesting: z.array(types_exports.GoTestingSchema).optional().describe("Go testing libraries (testify, gomock)"),
449
- goRealtime: types_exports.GoRealtimeSchema.optional().describe("Go realtime library (gorilla-websocket, centrifuge)"),
450
- goMessageQueue: types_exports.GoMessageQueueSchema.optional().describe("Go message queue (nats, watermill)"),
451
- goCaching: types_exports.GoCachingSchema.optional().describe("Go caching library (redis, ristretto)"),
452
- goConfig: types_exports.GoConfigSchema.optional().describe("Go config management (viper, koanf)"),
453
- goObservability: types_exports.GoObservabilitySchema.optional().describe("Go observability (opentelemetry)"),
454
- javaWebFramework: types_exports.JavaWebFrameworkSchema.optional().describe("Java web framework (spring-boot, quarkus, none)"),
455
- javaBuildTool: types_exports.JavaBuildToolSchema.optional().describe("Java build tool (maven, gradle, none)"),
456
- javaOrm: types_exports.JavaOrmSchema.optional().describe("Java ORM/database (spring-data-jpa)"),
457
- javaAuth: types_exports.JavaAuthSchema.optional().describe("Java auth (spring-security)"),
458
- javaApi: types_exports.JavaApiSchema.optional().describe("Java API layer (spring-graphql)"),
459
- javaLogging: types_exports.JavaLoggingSchema.optional().describe("Java logging (logback, log4j2)"),
460
- javaLibraries: z.array(types_exports.JavaLibrariesSchema).optional().describe("Java application libraries"),
461
- javaTestingLibraries: z.array(types_exports.JavaTestingLibrariesSchema).optional().describe("Java testing libraries"),
462
- dotnetWebFramework: types_exports.DotnetWebFrameworkSchema.optional().describe(".NET web framework (aspnet-minimal, aspnet-mvc, aspnet-blazor, none)"),
463
- dotnetOrm: types_exports.DotnetOrmSchema.optional().describe(".NET data access (ef-core, dapper, linq2db)"),
464
- dotnetAuth: types_exports.DotnetAuthSchema.optional().describe(".NET auth (aspnet-identity, duende-identityserver, auth0-aspnet, none)"),
465
- dotnetApi: types_exports.DotnetApiSchema.optional().describe(".NET API style (minimal-api, graphql-hotchocolate, grpc-dotnet, none)"),
466
- dotnetTesting: z.array(types_exports.DotnetTestingSchema).optional().describe(".NET testing libraries"),
467
- dotnetJobQueue: types_exports.DotnetJobQueueSchema.optional().describe(".NET jobs (hangfire, quartz-net, hosted-services, none)"),
468
- dotnetRealtime: types_exports.DotnetRealtimeSchema.optional().describe(".NET realtime (signalr, none)"),
469
- dotnetObservability: z.array(types_exports.DotnetObservabilitySchema).optional().describe(".NET observability/logging libraries"),
470
- dotnetValidation: types_exports.DotnetValidationSchema.optional().describe(".NET validation (fluentvalidation, data-annotations)"),
471
- dotnetCaching: types_exports.DotnetCachingSchema.optional().describe(".NET caching (redis, memory-cache, none)"),
472
- dotnetDeploy: types_exports.DotnetDeploySchema.optional().describe(".NET deploy target (docker, azure, aws, none)"),
473
- elixirWebFramework: types_exports.ElixirWebFrameworkSchema.optional().describe("Elixir web framework (phoenix, phoenix-live-view, none)"),
474
- elixirOrm: types_exports.ElixirOrmSchema.optional().describe("Elixir ORM/database (ecto, ecto-sql, none)"),
475
- elixirAuth: types_exports.ElixirAuthSchema.optional().describe("Elixir auth (phx-gen-auth, ueberauth, guardian, none)"),
476
- elixirApi: types_exports.ElixirApiSchema.optional().describe("Elixir API layer (rest, absinthe, none)"),
477
- elixirRealtime: types_exports.ElixirRealtimeSchema.optional().describe("Elixir realtime (channels, presence, pubsub, live-view-streams, none)"),
478
- elixirJobs: types_exports.ElixirJobsSchema.optional().describe("Elixir jobs (oban, quantum, none)"),
479
- elixirValidation: types_exports.ElixirValidationSchema.optional().describe("Elixir validation (ecto-changesets, nimble-options, none)"),
480
- elixirHttp: types_exports.ElixirHttpSchema.optional().describe("Elixir HTTP client (req, finch, none)"),
481
- elixirJson: types_exports.ElixirJsonSchema.optional().describe("Elixir JSON library (jason, none)"),
482
- elixirEmail: types_exports.ElixirEmailSchema.optional().describe("Elixir email library (swoosh, none)"),
483
- elixirCaching: types_exports.ElixirCachingSchema.optional().describe("Elixir caching (cachex, nebulex, none)"),
484
- elixirObservability: types_exports.ElixirObservabilitySchema.optional().describe("Elixir observability (telemetry, opentelemetry, prom_ex, none)"),
485
- elixirTesting: types_exports.ElixirTestingSchema.optional().describe("Elixir testing (ex_unit, mox, bypass, wallaby, none)"),
486
- elixirQuality: types_exports.ElixirQualitySchema.optional().describe("Elixir code quality (credo, dialyxir, sobelow, none)"),
487
- elixirDeploy: types_exports.ElixirDeploySchema.optional().describe("Elixir deploy target (docker, fly, gigalixir, mix-release, none)"),
488
- elixirLibraries: z.array(types_exports.ElixirLibrariesSchema).optional().describe("Elixir libraries (broadway, nx)"),
489
- aiDocs: z.array(types_exports.AiDocsSchema).optional().describe("AI documentation files (claude-md, agents-md, cursorrules)")
490
- });
491
- const CreateCommandInputSchema = z.tuple([types_exports.ProjectNameSchema.optional(), CreateCommandOptionsSchema]);
492
-
493
341
  //#endregion
494
342
  //#region src/prompts/prompt-contract.ts
495
343
  function createStaticSinglePromptResolution(options, initialValue, selectedValue) {
@@ -7764,927 +7612,6 @@ function validateArrayOptions(options) {
7764
7612
  validateNoneExclusivity(options.javaTestingLibraries, "java testing libraries");
7765
7613
  }
7766
7614
 
7767
- //#endregion
7768
- //#region src/utils/peer-dependency-conflicts.ts
7769
- /**
7770
- * Known peer dependency conflicts.
7771
- *
7772
- * Add new conflicts here as they are discovered.
7773
- * The validator will check these against the user's selected options.
7774
- */
7775
- const PEER_DEPENDENCY_CONFLICTS = [
7776
- {
7777
- id: "redux-toolkit-react19",
7778
- description: "redux-toolkit with React 19 may have peer dependency warnings",
7779
- packages: ["@reduxjs/toolkit", "react-redux"],
7780
- severity: "warning",
7781
- resolution: "Consider using zustand or jotai for React 19 projects, or ensure react-redux v9+",
7782
- triggeredBy: [{
7783
- optionKey: "stateManagement",
7784
- values: ["redux-toolkit"]
7785
- }],
7786
- conflictsWithOptions: [{
7787
- optionKey: "frontend",
7788
- values: ["next", "vinext"]
7789
- }]
7790
- },
7791
- {
7792
- id: "effect-schema-zod-overlap",
7793
- description: "effect/Schema (built into effect) and zod both provide validation - may cause confusion",
7794
- packages: ["effect", "zod"],
7795
- severity: "warning",
7796
- resolution: "Use --validation effect-schema with Effect, or --effect none with Zod",
7797
- triggeredBy: [{
7798
- optionKey: "effect",
7799
- values: ["effect-full"]
7800
- }],
7801
- conflictsWithOptions: [{
7802
- optionKey: "validation",
7803
- values: ["zod"]
7804
- }]
7805
- },
7806
- {
7807
- id: "prisma-d1-version",
7808
- description: "Prisma with D1 requires Prisma 5.x+ and specific adapter setup",
7809
- packages: ["prisma", "@prisma/adapter-d1"],
7810
- severity: "warning",
7811
- resolution: "Consider Drizzle ORM for simpler D1 integration",
7812
- triggeredBy: [{
7813
- optionKey: "orm",
7814
- values: ["prisma"]
7815
- }],
7816
- conflictsWithOptions: [{
7817
- optionKey: "dbSetup",
7818
- values: ["d1"]
7819
- }]
7820
- },
7821
- {
7822
- id: "biome-linting-overlap",
7823
- description: "Biome includes linting/formatting - other linting addons are redundant",
7824
- packages: [
7825
- "@biomejs/biome",
7826
- "eslint",
7827
- "prettier"
7828
- ],
7829
- severity: "warning",
7830
- resolution: "Choose either Biome (all-in-one) or other linting tools, not both",
7831
- triggeredBy: [{
7832
- optionKey: "addons",
7833
- values: ["biome"]
7834
- }],
7835
- conflictsWithOptions: [{
7836
- optionKey: "addons",
7837
- values: ["ultracite", "oxlint"]
7838
- }]
7839
- },
7840
- {
7841
- id: "framer-motion-react19",
7842
- description: "framer-motion versions <12 may have React 19 issues",
7843
- packages: ["framer-motion"],
7844
- severity: "warning",
7845
- resolution: "The CLI uses motion (framer-motion v12+) which supports React 19",
7846
- triggeredBy: [{
7847
- optionKey: "animation",
7848
- values: ["framer-motion"]
7849
- }],
7850
- conflictsWithOptions: [{
7851
- optionKey: "frontend",
7852
- values: ["next", "vinext"]
7853
- }]
7854
- }
7855
- ];
7856
-
7857
- //#endregion
7858
- //#region src/utils/peer-dependency-validator.ts
7859
- /**
7860
- * Peer dependency conflict validator.
7861
- *
7862
- * Checks project configuration against known conflicts and provides
7863
- * warnings or errors before project creation begins.
7864
- */
7865
- /**
7866
- * Checks if a config value matches any of the specified values.
7867
- */
7868
- function matchesValue(configValue, values) {
7869
- if (configValue === void 0 || configValue === null) return false;
7870
- if (Array.isArray(configValue)) return values.some((v) => configValue.includes(v));
7871
- return values.includes(configValue);
7872
- }
7873
- /**
7874
- * Checks the configuration against known peer dependency conflicts.
7875
- */
7876
- function checkPeerDependencyConflicts(config) {
7877
- const errors = [];
7878
- const warnings = [];
7879
- for (const conflict of PEER_DEPENDENCY_CONFLICTS) {
7880
- if (!conflict.triggeredBy.some((trigger) => {
7881
- const configValue = config[trigger.optionKey];
7882
- return matchesValue(configValue, trigger.values);
7883
- })) continue;
7884
- if (conflict.conflictsWithOptions.some((opt) => {
7885
- const configValue = config[opt.optionKey];
7886
- return matchesValue(configValue, opt.values);
7887
- })) if (conflict.severity === "error") errors.push(conflict);
7888
- else warnings.push(conflict);
7889
- }
7890
- return {
7891
- errors,
7892
- warnings
7893
- };
7894
- }
7895
- /**
7896
- * Prints a warning message for a dependency conflict.
7897
- */
7898
- function warnDependencyConflict(message, resolution) {
7899
- if (isSilent()) return;
7900
- consola.warn(pc.yellow(`Peer Dependency Warning: ${message}`));
7901
- consola.log(pc.dim(` → ${resolution}`));
7902
- }
7903
- /**
7904
- * Handles conflict results by printing warnings and exiting on errors.
7905
- */
7906
- function handleConflictResult(result) {
7907
- for (const warning of result.warnings) warnDependencyConflict(warning.description, warning.resolution);
7908
- if (result.errors.length > 0) exitWithError(`Peer Dependency Conflict:\n${result.errors.map((e) => `${e.description}\n → ${e.resolution}`).join("\n\n")}`);
7909
- }
7910
- /**
7911
- * Validates peer dependencies for the given configuration.
7912
- * Prints warnings and exits on errors.
7913
- */
7914
- function validatePeerDependencies(config) {
7915
- handleConflictResult(checkPeerDependencyConflicts(config));
7916
- }
7917
-
7918
- //#endregion
7919
- //#region src/utils/config-validation.ts
7920
- function validateDatabaseOrmAuth(cfg, flags) {
7921
- const db = cfg.database;
7922
- const orm = cfg.orm;
7923
- const has = (k) => flags ? flags.has(k) : true;
7924
- const hasGraphOrm = cfg.stackParts?.some((part) => part.role === "orm" && part.source !== "provided");
7925
- const ecosystemOrm = getEcosystemOrm(cfg);
7926
- const hasEcosystemOrm = ecosystemOrm !== void 0 && ecosystemOrm !== "none";
7927
- const isNonTypeScriptSqliteDefault = cfg.ecosystem !== void 0 && cfg.ecosystem !== "typescript" && cfg.ecosystem !== "react-native" && db === "sqlite" && !hasEcosystemOrm;
7928
- if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") incompatibilityError({
7929
- message: "Mongoose ORM requires MongoDB database.",
7930
- provided: {
7931
- orm: "mongoose",
7932
- database: db || "none"
7933
- },
7934
- suggestions: ["Use --database mongodb", "Choose a different ORM (drizzle, prisma)"]
7935
- });
7936
- if (has("orm") && has("database") && orm === "drizzle" && db === "mongodb") incompatibilityError({
7937
- message: "Drizzle ORM does not support MongoDB.",
7938
- provided: {
7939
- orm: "drizzle",
7940
- database: "mongodb"
7941
- },
7942
- suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
7943
- });
7944
- if (has("orm") && has("database") && orm === "typeorm" && db === "mongodb") incompatibilityError({
7945
- message: "TypeORM does not support MongoDB in Better Fullstack.",
7946
- provided: {
7947
- orm: "typeorm",
7948
- database: "mongodb"
7949
- },
7950
- suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
7951
- });
7952
- if (has("orm") && has("database") && orm === "kysely" && db === "mongodb") incompatibilityError({
7953
- message: "Kysely does not support MongoDB.",
7954
- provided: {
7955
- orm: "kysely",
7956
- database: "mongodb"
7957
- },
7958
- suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
7959
- });
7960
- if (has("orm") && has("database") && orm === "mikroorm" && db === "mongodb") incompatibilityError({
7961
- message: "MikroORM does not support MongoDB in Better Fullstack.",
7962
- provided: {
7963
- orm: "mikroorm",
7964
- database: "mongodb"
7965
- },
7966
- suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
7967
- });
7968
- if (has("orm") && has("database") && orm === "sequelize" && db === "mongodb") incompatibilityError({
7969
- message: "Sequelize does not support MongoDB.",
7970
- provided: {
7971
- orm: "sequelize",
7972
- database: "mongodb"
7973
- },
7974
- suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
7975
- });
7976
- if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") incompatibilityError({
7977
- message: "In Better-Fullstack, MongoDB is currently supported only with Mongoose or Prisma ORM.",
7978
- provided: {
7979
- database: "mongodb",
7980
- orm
7981
- },
7982
- suggestions: ["Use --orm mongoose", "Use --orm prisma"]
7983
- });
7984
- if (has("database") && has("orm") && db && db !== "none" && db !== "edgedb" && db !== "redis" && orm === "none" && !hasGraphOrm && !hasEcosystemOrm && !isNonTypeScriptSqliteDefault) missingRequirementError({
7985
- message: "Database selection requires an ORM.",
7986
- provided: {
7987
- database: db,
7988
- orm: "none"
7989
- },
7990
- suggestions: [
7991
- "Use --orm drizzle (recommended)",
7992
- "Use --orm prisma",
7993
- "Use --orm mongoose (MongoDB only)"
7994
- ]
7995
- });
7996
- if (has("database") && has("orm") && db === "edgedb" && orm && orm !== "none") incompatibilityError({
7997
- message: "EdgeDB has its own built-in query builder and does not require an ORM.",
7998
- provided: {
7999
- database: "edgedb",
8000
- orm
8001
- },
8002
- suggestions: ["Use --orm none with EdgeDB", "Choose a different database if you want to use an ORM"]
8003
- });
8004
- if (has("database") && has("orm") && db === "redis" && orm && orm !== "none") incompatibilityError({
8005
- message: "Redis is a key-value store and does not require an ORM.",
8006
- provided: {
8007
- database: "redis",
8008
- orm
8009
- },
8010
- suggestions: ["Use --orm none with Redis", "Choose a different database if you want to use an ORM"]
8011
- });
8012
- if (has("orm") && has("database") && orm && orm !== "none" && db === "none") missingRequirementError({
8013
- message: "ORM selection requires a database.",
8014
- provided: {
8015
- orm,
8016
- database: "none"
8017
- },
8018
- suggestions: [
8019
- "Use --database postgres",
8020
- "Use --database sqlite",
8021
- "Use --database mysql",
8022
- "Set --orm none"
8023
- ]
8024
- });
8025
- }
8026
- function getEcosystemOrm(cfg) {
8027
- switch (cfg.ecosystem) {
8028
- case "rust": return cfg.rustOrm;
8029
- case "python": return cfg.pythonOrm;
8030
- case "go": return cfg.goOrm;
8031
- case "java": return cfg.javaOrm;
8032
- case "dotnet": return cfg.dotnetOrm;
8033
- case "elixir": return cfg.elixirOrm;
8034
- default: return;
8035
- }
8036
- }
8037
- function getEcosystemBackend(cfg) {
8038
- switch (cfg.ecosystem) {
8039
- case "rust": return cfg.rustWebFramework && cfg.rustWebFramework !== "none" ? cfg.rustWebFramework : void 0;
8040
- case "python": return cfg.pythonWebFramework && cfg.pythonWebFramework !== "none" ? cfg.pythonWebFramework : void 0;
8041
- case "go": return cfg.goWebFramework && cfg.goWebFramework !== "none" ? cfg.goWebFramework : void 0;
8042
- case "java": return cfg.javaWebFramework && cfg.javaWebFramework !== "none" ? cfg.javaWebFramework : void 0;
8043
- case "dotnet": return cfg.dotnetWebFramework && cfg.dotnetWebFramework !== "none" ? cfg.dotnetWebFramework : void 0;
8044
- case "elixir": return cfg.elixirWebFramework && cfg.elixirWebFramework !== "none" ? cfg.elixirWebFramework : void 0;
8045
- default: return;
8046
- }
8047
- }
8048
- function validateDatabaseSetup(config, providedFlags) {
8049
- const { dbSetup, database, runtime } = config;
8050
- 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'.");
8051
- const setupValidations = {
8052
- turso: {
8053
- database: "sqlite",
8054
- errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup."
8055
- },
8056
- neon: {
8057
- database: "postgres",
8058
- errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
8059
- },
8060
- "prisma-postgres": {
8061
- database: "postgres",
8062
- errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
8063
- },
8064
- planetscale: { errorMessage: "PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup." },
8065
- "mongodb-atlas": {
8066
- database: "mongodb",
8067
- errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
8068
- },
8069
- upstash: {
8070
- database: "redis",
8071
- errorMessage: "Upstash setup requires Redis database. Please use '--database redis' or choose a different setup."
8072
- },
8073
- supabase: {
8074
- database: "postgres",
8075
- errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
8076
- },
8077
- d1: {
8078
- database: "sqlite",
8079
- runtime: "workers",
8080
- errorMessage: "Cloudflare D1 setup requires SQLite database and Cloudflare Workers runtime."
8081
- },
8082
- docker: { errorMessage: "In Better-Fullstack, Docker setup is currently not available with SQLite database or Cloudflare Workers runtime." },
8083
- none: { errorMessage: "" }
8084
- };
8085
- if (dbSetup && dbSetup !== "none") {
8086
- const validation = setupValidations[dbSetup];
8087
- if (dbSetup === "planetscale") {
8088
- if (database !== "postgres" && database !== "mysql") exitWithError(validation.errorMessage);
8089
- } else if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
8090
- if (validation.runtime && runtime !== validation.runtime) exitWithError(validation.errorMessage);
8091
- if (dbSetup === "docker") {
8092
- if (database === "sqlite") exitWithError("In Better-Fullstack, Docker setup is currently not available with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
8093
- if (runtime === "workers") exitWithError("In Better-Fullstack, Docker setup is currently not available with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
8094
- }
8095
- }
8096
- }
8097
- function validateEcosystemAuthCompatibility(config, providedFlags) {
8098
- const auth = config.auth;
8099
- if (!auth || auth === "none") return;
8100
- if ((auth === "better-auth" || auth === "better-auth-organizations") && config.orm && [
8101
- "typeorm",
8102
- "sequelize",
8103
- "mikroorm"
8104
- ].includes(config.orm)) {
8105
- config.auth = "none";
8106
- if (providedFlags?.has("auth") && !isSilent()) consola.warn(pc.yellow(`Unsupported auth selection '${auth}' with ${config.orm}: no Better Auth adapter exists. Falling back to '--auth none'.`));
8107
- return;
8108
- }
8109
- const normalized = (0, types_exports.normalizeCapabilitySelection)("auth", {
8110
- ecosystem: config.ecosystem,
8111
- backend: config.backend,
8112
- frontend: config.frontend
8113
- }, auth);
8114
- if (!normalized.normalized || normalized.value === auth) return;
8115
- config.auth = normalized.value;
8116
- if (providedFlags?.has("auth") && normalized.reason && !isSilent()) consola.warn(pc.yellow(`Unsupported auth selection '${auth}' for the current stack: ${normalized.reason}. Falling back to '--auth ${normalized.value}'.`));
8117
- }
8118
- function validateConvexConstraints(config, providedFlags) {
8119
- const { backend } = config;
8120
- if (backend !== "convex") return;
8121
- const has = (k) => providedFlags.has(k);
8122
- if (has("runtime") && config.runtime !== "none") constraintError({
8123
- message: "Convex backend manages its own runtime.",
8124
- provided: {
8125
- backend: "convex",
8126
- runtime: config.runtime || ""
8127
- },
8128
- suggestions: ["Remove --runtime flag", "Set --runtime none"]
8129
- });
8130
- if (has("database") && config.database !== "none") constraintError({
8131
- message: "Convex backend has its own built-in database.",
8132
- provided: {
8133
- backend: "convex",
8134
- database: config.database || ""
8135
- },
8136
- suggestions: ["Remove --database flag", "Set --database none"]
8137
- });
8138
- if (has("orm") && config.orm !== "none") constraintError({
8139
- message: "Convex backend has its own data layer (no ORM needed).",
8140
- provided: {
8141
- backend: "convex",
8142
- orm: config.orm || ""
8143
- },
8144
- suggestions: ["Remove --orm flag", "Set --orm none"]
8145
- });
8146
- if (has("api") && config.api !== "none") constraintError({
8147
- message: "Convex backend has its own built-in API layer.",
8148
- provided: {
8149
- backend: "convex",
8150
- api: config.api || ""
8151
- },
8152
- suggestions: ["Remove --api flag", "Set --api none"]
8153
- });
8154
- if (has("dbSetup") && config.dbSetup !== "none") constraintError({
8155
- message: "Convex backend manages its own database infrastructure.",
8156
- provided: {
8157
- backend: "convex",
8158
- "db-setup": config.dbSetup || ""
8159
- },
8160
- suggestions: ["Remove --db-setup flag", "Set --db-setup none"]
8161
- });
8162
- if (has("serverDeploy") && config.serverDeploy !== "none") constraintError({
8163
- message: "Convex backend has its own deployment platform.",
8164
- provided: {
8165
- backend: "convex",
8166
- "server-deploy": config.serverDeploy || ""
8167
- },
8168
- suggestions: ["Remove --server-deploy flag", "Set --server-deploy none"]
8169
- });
8170
- }
8171
- function validateBackendNoneConstraints(config, providedFlags) {
8172
- const { backend } = config;
8173
- const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
8174
- const hasEcosystemBackend = getEcosystemBackend(config) !== void 0;
8175
- if (backend !== "none" || hasGraphBackend || hasEcosystemBackend) return;
8176
- const has = (k) => providedFlags.has(k);
8177
- if (has("runtime") && config.runtime !== "none") exitWithError("Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
8178
- if (has("database") && config.database !== "none") exitWithError("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
8179
- if (has("orm") && config.orm !== "none") exitWithError("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
8180
- if (has("api") && config.api !== "none") exitWithError("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
8181
- if (has("payments") && config.payments !== "none") exitWithError("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
8182
- if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
8183
- if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
8184
- }
8185
- function validateSelfBackendConstraints(config, providedFlags) {
8186
- const { backend } = config;
8187
- if (backend !== "self") return;
8188
- const has = (k) => providedFlags.has(k);
8189
- if (has("runtime") && config.runtime !== "none") exitWithError("Backend 'self' (fullstack) requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.");
8190
- }
8191
- function validateEncoreConstraints(config, providedFlags) {
8192
- const { backend } = config;
8193
- if (backend !== "encore") return;
8194
- const has = (k) => providedFlags.has(k);
8195
- if (has("runtime") && config.runtime !== "none") exitWithError("Encore.ts backend requires '--runtime none'. Encore has its own runtime via the Encore CLI. Please remove the --runtime flag or set it to 'none'.");
8196
- if (has("api") && config.api !== "none") exitWithError("Encore.ts backend requires '--api none'. Encore has its own type-safe API system. Please remove the --api flag or set it to 'none'.");
8197
- if (has("database") && config.database !== "none") exitWithError("Encore.ts backend requires '--database none'. Encore manages databases through its infrastructure primitives. Please remove the --database flag or set it to 'none'.");
8198
- if (has("orm") && config.orm !== "none") exitWithError("Encore.ts backend requires '--orm none'. Encore has its own database abstractions. Please remove the --orm flag or set it to 'none'.");
8199
- if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Encore.ts backend requires '--db-setup none'. Encore manages infrastructure automatically. Please remove the --db-setup flag or set it to 'none'.");
8200
- if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Encore.ts backend requires '--server-deploy none'. Encore has its own deployment platform. Please remove the --server-deploy flag or set it to 'none'.");
8201
- }
8202
- function validateAdonisJSConstraints(config, providedFlags) {
8203
- const { backend } = config;
8204
- if (backend !== "adonisjs") return;
8205
- const has = (k) => providedFlags.has(k);
8206
- if (has("runtime") && config.runtime !== "node") exitWithError("AdonisJS backend requires '--runtime node'. AdonisJS currently only supports Node.js runtime. Please use '--runtime node' or remove the --runtime flag.");
8207
- }
8208
- function validateBackendConstraints(config, providedFlags, options) {
8209
- const { backend } = config;
8210
- if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self" && backend !== "encore") {
8211
- if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex', '--backend none', '--backend self', or '--backend encore'. Please choose 'bun', 'node', or remove the --runtime flag.");
8212
- }
8213
- if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
8214
- const incompatibleFrontends = options.frontend.filter((f) => ["solid", "astro"].includes(f));
8215
- if (incompatibleFrontends.length > 0) exitWithError(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
8216
- }
8217
- }
8218
- function validateFrontendConstraints(config, providedFlags) {
8219
- const { frontend } = config;
8220
- if (frontend && frontend.length > 0) {
8221
- if (config.ecosystem === "react-native" && frontend.some((item) => !item.startsWith("native-") && item !== "none")) incompatibilityError({
8222
- message: "React Native ecosystem only supports native Expo frontends.",
8223
- provided: {
8224
- ecosystem: "react-native",
8225
- frontend: frontend.join(" ")
8226
- },
8227
- suggestions: [
8228
- "Use --frontend native-bare",
8229
- "Use --frontend native-uniwind",
8230
- "Use --frontend native-unistyles"
8231
- ]
8232
- });
8233
- ensureSingleWebAndNative(frontend);
8234
- if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) validateApiFrontendCompatibility(config.api, frontend, config.astroIntegration);
8235
- }
8236
- const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend$1(f));
8237
- validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
8238
- validateWebDeployFrontendTemplates(config.webDeploy, frontend);
8239
- }
8240
- function validateApiConstraints(config, _options) {
8241
- if (config.api !== "openapi" && config.api !== "apollo-server") return;
8242
- const apiDisplayName = config.api === "apollo-server" ? "Apollo Server" : "OpenAPI";
8243
- const frontend = config.frontend ?? [];
8244
- if (frontend.some((item) => [
8245
- "native-bare",
8246
- "native-uniwind",
8247
- "native-unistyles"
8248
- ].includes(item))) incompatibilityError({
8249
- message: `${apiDisplayName} is currently available for web frontends, not React Native.`,
8250
- provided: {
8251
- api: config.api,
8252
- frontend
8253
- },
8254
- suggestions: [
8255
- "Use --api trpc",
8256
- "Use --api orpc",
8257
- "Use a web frontend"
8258
- ]
8259
- });
8260
- if (!config.backend || ![
8261
- "hono",
8262
- "express",
8263
- "fastify",
8264
- "elysia"
8265
- ].includes(config.backend)) incompatibilityError({
8266
- message: `${apiDisplayName} currently supports Hono, Express, Fastify, and Elysia backends.`,
8267
- provided: {
8268
- api: config.api,
8269
- backend: config.backend ?? "none"
8270
- },
8271
- suggestions: [
8272
- "Use --backend hono",
8273
- "Use --backend express",
8274
- "Use --backend fastify",
8275
- "Use --backend elysia"
8276
- ]
8277
- });
8278
- }
8279
- function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set()) {
8280
- if (config.ecosystem !== "java") return;
8281
- const hasSpringBoot = config.javaWebFramework === "spring-boot";
8282
- const hasJavaWebFramework = config.javaWebFramework !== "none";
8283
- const hasNoBuildTool = config.javaBuildTool === "none";
8284
- const hasJavaLibraries = (config.javaLibraries ?? []).some((library) => library !== "none");
8285
- const hasJavaTestingLibraries = (config.javaTestingLibraries ?? []).some((library) => library !== "none");
8286
- const hasSpringOnlyFeatures = config.javaOrm !== "none" || config.javaAuth !== "none" || hasJavaLibraries;
8287
- if (hasNoBuildTool && hasJavaWebFramework) incompatibilityError({
8288
- message: "Java web frameworks require Maven or Gradle in the Java scaffold.",
8289
- provided: {
8290
- "java-web-framework": config.javaWebFramework ?? "none",
8291
- "java-build-tool": config.javaBuildTool ?? "none"
8292
- },
8293
- 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"]
8294
- });
8295
- if ((!hasSpringBoot || hasNoBuildTool) && hasSpringOnlyFeatures) incompatibilityError({
8296
- message: "Spring-only Java features require the Spring Boot scaffold with Maven or Gradle.",
8297
- provided: {
8298
- "java-web-framework": config.javaWebFramework ?? "none",
8299
- "java-build-tool": config.javaBuildTool ?? "none",
8300
- "java-orm": config.javaOrm ?? "none",
8301
- "java-auth": config.javaAuth ?? "none",
8302
- "java-libraries": (config.javaLibraries ?? []).join(" ") || "none"
8303
- },
8304
- 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"]
8305
- });
8306
- if (hasNoBuildTool && hasJavaTestingLibraries) incompatibilityError({
8307
- message: "Java testing libraries require Maven or Gradle to manage test dependencies.",
8308
- provided: {
8309
- "java-build-tool": config.javaBuildTool ?? "none",
8310
- "java-testing-libraries": (config.javaTestingLibraries ?? []).join(" ") || "none"
8311
- },
8312
- 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"]
8313
- });
8314
- }
8315
- function validateElixirConstraints(config) {
8316
- if (config.ecosystem !== "elixir") return;
8317
- const hasPhoenix = config.elixirWebFramework !== "none";
8318
- const hasEcto = config.elixirOrm !== "none";
8319
- const unsupportedSelections = [
8320
- {
8321
- flag: "elixir-orm",
8322
- value: config.elixirOrm,
8323
- unsupported: ["ecto"],
8324
- message: "Plain Ecto without SQL Repo wiring is not generated yet.",
8325
- suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-orm none"]
8326
- },
8327
- {
8328
- flag: "elixir-auth",
8329
- value: config.elixirAuth,
8330
- unsupported: ["ueberauth", "guardian"],
8331
- message: "Only phx.gen.auth currently generates Phoenix auth files.",
8332
- suggestions: ["Use --elixir-auth phx-gen-auth", "Use --elixir-auth none"]
8333
- },
8334
- {
8335
- flag: "elixir-validation",
8336
- value: config.elixirValidation,
8337
- unsupported: ["nimble-options"],
8338
- message: "NimbleOptions is not generated yet.",
8339
- suggestions: ["Use --elixir-validation ecto-changesets", "Use --elixir-validation none"]
8340
- },
8341
- {
8342
- flag: "elixir-caching",
8343
- value: config.elixirCaching,
8344
- unsupported: ["nebulex"],
8345
- message: "Nebulex cache modules are not generated yet.",
8346
- suggestions: ["Use --elixir-caching cachex", "Use --elixir-caching none"]
8347
- },
8348
- {
8349
- flag: "elixir-observability",
8350
- value: config.elixirObservability,
8351
- unsupported: ["opentelemetry", "prom_ex"],
8352
- message: "OpenTelemetry and PromEx setup are not generated yet.",
8353
- suggestions: ["Use --elixir-observability telemetry", "Use --elixir-observability none"]
8354
- },
8355
- {
8356
- flag: "elixir-testing",
8357
- value: config.elixirTesting,
8358
- unsupported: [
8359
- "mox",
8360
- "bypass",
8361
- "wallaby"
8362
- ],
8363
- message: "Generated Phoenix projects currently include ExUnit tests only.",
8364
- suggestions: ["Use --elixir-testing ex_unit"]
8365
- },
8366
- {
8367
- flag: "elixir-deploy",
8368
- value: config.elixirDeploy,
8369
- unsupported: ["fly", "gigalixir"],
8370
- message: "Fly.io and Gigalixir config files are not generated yet.",
8371
- suggestions: ["Use --elixir-deploy docker", "Use --elixir-deploy mix-release"]
8372
- }
8373
- ];
8374
- for (const selection of unsupportedSelections) if (selection.value && selection.unsupported.includes(selection.value)) incompatibilityError({
8375
- message: selection.message,
8376
- provided: { [selection.flag]: selection.value },
8377
- suggestions: selection.suggestions
8378
- });
8379
- if (!hasPhoenix) {
8380
- const phoenixOnlySelections = [
8381
- {
8382
- flag: "elixir-auth",
8383
- value: config.elixirAuth,
8384
- message: "Elixir auth scaffolds require Phoenix."
8385
- },
8386
- {
8387
- flag: "elixir-api",
8388
- value: config.elixirApi,
8389
- message: "Elixir API scaffolds require Phoenix."
8390
- },
8391
- {
8392
- flag: "elixir-realtime",
8393
- value: config.elixirRealtime,
8394
- message: "Elixir realtime scaffolds require Phoenix."
8395
- }
8396
- ];
8397
- for (const selection of phoenixOnlySelections) {
8398
- if (!selection.value || selection.value === "none") continue;
8399
- incompatibilityError({
8400
- message: selection.message,
8401
- provided: {
8402
- "elixir-web-framework": config.elixirWebFramework ?? "none",
8403
- [selection.flag]: selection.value
8404
- },
8405
- suggestions: [
8406
- "Use --elixir-web-framework phoenix",
8407
- "Use --elixir-web-framework phoenix-live-view",
8408
- `Use --${selection.flag} none`
8409
- ]
8410
- });
8411
- }
8412
- }
8413
- if (hasPhoenix && config.elixirJson === "none") incompatibilityError({
8414
- message: "Phoenix JSON scaffolds require Jason.",
8415
- provided: { "elixir-json": "none" },
8416
- suggestions: ["Use --elixir-json jason"]
8417
- });
8418
- if (config.elixirAuth === "phx-gen-auth" && !hasEcto) incompatibilityError({
8419
- message: "phx.gen.auth requires Ecto in the generated Phoenix scaffold.",
8420
- provided: {
8421
- "elixir-auth": "phx-gen-auth",
8422
- "elixir-orm": config.elixirOrm ?? "none"
8423
- },
8424
- suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-auth none"]
8425
- });
8426
- if (config.elixirJobs === "oban" && config.elixirOrm !== "ecto-sql") incompatibilityError({
8427
- message: "Oban requires Ecto SQL with PostgreSQL in the generated Phoenix scaffold.",
8428
- provided: {
8429
- "elixir-jobs": "oban",
8430
- "elixir-orm": config.elixirOrm ?? "none"
8431
- },
8432
- suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-jobs none"]
8433
- });
8434
- if (config.elixirRealtime === "live-view-streams" && config.elixirWebFramework !== "phoenix-live-view") incompatibilityError({
8435
- message: "LiveView Streams require Phoenix LiveView.",
8436
- provided: {
8437
- "elixir-realtime": "live-view-streams",
8438
- "elixir-web-framework": config.elixirWebFramework ?? "none"
8439
- },
8440
- suggestions: ["Use --elixir-web-framework phoenix-live-view", "Use --elixir-realtime channels"]
8441
- });
8442
- if (config.elixirApi === "absinthe" && !hasEcto) incompatibilityError({
8443
- message: "Absinthe GraphQL requires Ecto in the current generated Phoenix scaffold.",
8444
- provided: {
8445
- "elixir-api": "absinthe",
8446
- "elixir-orm": config.elixirOrm ?? "none"
8447
- },
8448
- suggestions: ["Use --elixir-orm ecto-sql", "Use --elixir-api rest"]
8449
- });
8450
- }
8451
- function validateEmailConstraints(config) {
8452
- if (!config.email || config.email === "none") return;
8453
- if (config.ecosystem !== "typescript" && config.email !== "resend") incompatibilityError({
8454
- message: "Only Resend email is available for non-TypeScript ecosystems.",
8455
- provided: {
8456
- ecosystem: config.ecosystem ?? "typescript",
8457
- email: config.email
8458
- },
8459
- suggestions: ["Use --email resend", "Use --email none"]
8460
- });
8461
- if (config.ecosystem === "java" && config.email === "resend" && config.javaBuildTool === "none") incompatibilityError({
8462
- message: "Resend email for Java requires Maven or Gradle to manage the SDK dependency.",
8463
- provided: {
8464
- "java-build-tool": "none",
8465
- email: "resend"
8466
- },
8467
- suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
8468
- });
8469
- }
8470
- function validateObservabilityConstraints(config) {
8471
- if (!config.observability || config.observability === "none") return;
8472
- if (config.ecosystem !== "typescript" && config.observability !== "sentry") incompatibilityError({
8473
- message: "Only Sentry observability is available for non-TypeScript ecosystems.",
8474
- provided: {
8475
- ecosystem: config.ecosystem ?? "typescript",
8476
- observability: config.observability
8477
- },
8478
- suggestions: ["Use --observability sentry", "Use --observability none"]
8479
- });
8480
- if (config.ecosystem === "java" && config.observability === "sentry" && config.javaBuildTool === "none") incompatibilityError({
8481
- message: "Sentry observability for Java requires Maven or Gradle to manage the SDK dependency.",
8482
- provided: {
8483
- "java-build-tool": "none",
8484
- observability: "sentry"
8485
- },
8486
- suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
8487
- });
8488
- }
8489
- function validateCachingConstraints(config) {
8490
- if (!config.caching || config.caching === "none") return;
8491
- if (config.ecosystem !== "typescript" && config.caching !== "upstash-redis") incompatibilityError({
8492
- message: "Only Upstash Redis caching is available for non-TypeScript ecosystems.",
8493
- provided: {
8494
- ecosystem: config.ecosystem ?? "typescript",
8495
- caching: config.caching
8496
- },
8497
- suggestions: ["Use --caching upstash-redis", "Use --caching none"]
8498
- });
8499
- if (config.ecosystem === "java" && config.caching === "upstash-redis" && config.javaBuildTool === "none") incompatibilityError({
8500
- message: "Upstash Redis caching for Java requires Maven or Gradle to manage the Redis client dependency.",
8501
- provided: {
8502
- "java-build-tool": "none",
8503
- caching: "upstash-redis"
8504
- },
8505
- suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
8506
- });
8507
- }
8508
- function validateRateLimitConstraints(config) {
8509
- if (!config.rateLimit || config.rateLimit === "none") return;
8510
- if (config.ecosystem !== "typescript") incompatibilityError({
8511
- message: "Rate limiting helpers are currently available for TypeScript stacks only.",
8512
- provided: {
8513
- ecosystem: config.ecosystem ?? "typescript",
8514
- "rate-limit": config.rateLimit
8515
- },
8516
- suggestions: ["Use --rate-limit none"]
8517
- });
8518
- if (config.backend === "convex") incompatibilityError({
8519
- message: "Rate limiting helpers are not generated with Convex backend.",
8520
- provided: {
8521
- backend: "convex",
8522
- "rate-limit": config.rateLimit
8523
- },
8524
- suggestions: ["Use --rate-limit none"]
8525
- });
8526
- if (config.backend === "none") incompatibilityError({
8527
- message: "Rate limiting requires a backend.",
8528
- provided: {
8529
- backend: "none",
8530
- "rate-limit": config.rateLimit
8531
- },
8532
- suggestions: ["Use --backend hono", "Use --rate-limit none"]
8533
- });
8534
- }
8535
- function validateSearchConstraints(config) {
8536
- if (!config.search || config.search === "none") return;
8537
- const ecosystem = config.ecosystem ?? "typescript";
8538
- if (ecosystem !== "typescript" && config.search !== "meilisearch") incompatibilityError({
8539
- message: "Only Meilisearch search is available for non-TypeScript ecosystems.",
8540
- provided: {
8541
- ecosystem,
8542
- search: config.search
8543
- },
8544
- suggestions: ["Use --search meilisearch", "Use --search none"]
8545
- });
8546
- if (config.ecosystem === "java" && config.search === "meilisearch" && config.javaBuildTool === "none") incompatibilityError({
8547
- message: "Meilisearch search for Java requires Maven or Gradle to manage the SDK dependency.",
8548
- provided: {
8549
- "java-build-tool": "none",
8550
- search: "meilisearch"
8551
- },
8552
- suggestions: ["Use --java-build-tool maven", "Use --java-build-tool gradle"]
8553
- });
8554
- }
8555
- function validateShadcnConstraints(config, providedFlags) {
8556
- const shadcnFlagMap = {
8557
- shadcnBase: "--shadcn-base",
8558
- shadcnStyle: "--shadcn-style",
8559
- shadcnIconLibrary: "--shadcn-icon-library",
8560
- shadcnColorTheme: "--shadcn-color-theme",
8561
- shadcnBaseColor: "--shadcn-base-color",
8562
- shadcnFont: "--shadcn-font",
8563
- shadcnRadius: "--shadcn-radius"
8564
- };
8565
- const providedShadcnFlags = Object.keys(shadcnFlagMap).filter((f) => providedFlags.has(f));
8566
- if (providedShadcnFlags.length > 0 && config.uiLibrary !== "shadcn-ui") incompatibilityError({
8567
- message: "shadcn/ui customization flags require --ui-library shadcn-ui.",
8568
- provided: {
8569
- "ui-library": config.uiLibrary || "none",
8570
- ...Object.fromEntries(providedShadcnFlags.map((f) => [shadcnFlagMap[f], String(config[f] ?? "")]))
8571
- },
8572
- suggestions: ["Add --ui-library shadcn-ui to use shadcn customization flags", "Remove the --shadcn-* flags if not using shadcn/ui"]
8573
- });
8574
- }
8575
- function validatePythonApiConstraints(config) {
8576
- if (config.ecosystem === "python" && config.pythonApi && config.pythonApi !== "none" && config.pythonWebFramework !== "django") incompatibilityError({
8577
- message: "Python API frameworks require --python-web-framework django.",
8578
- provided: {
8579
- "python-web-framework": config.pythonWebFramework || "none",
8580
- "python-api": config.pythonApi
8581
- },
8582
- 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"]
8583
- });
8584
- }
8585
- function validateFullConfig(config, providedFlags, options) {
8586
- if (config.stackParts && !options.yolo) {
8587
- const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
8588
- if (graphValidation.issues.length > 0) exitWithError(graphValidation.issues.map((issue) => issue.message).join("\n"));
8589
- }
8590
- validateEcosystemAuthCompatibility(config, providedFlags);
8591
- validateDatabaseOrmAuth(config, providedFlags);
8592
- validateDatabaseSetup(config, providedFlags);
8593
- validateConvexConstraints(config, providedFlags);
8594
- validateBackendNoneConstraints(config, providedFlags);
8595
- validateSelfBackendConstraints(config, providedFlags);
8596
- validateEncoreConstraints(config, providedFlags);
8597
- validateAdonisJSConstraints(config, providedFlags);
8598
- validateBackendConstraints(config, providedFlags, options);
8599
- validateFrontendConstraints(config, providedFlags);
8600
- validateApiConstraints(config, options);
8601
- validatePythonApiConstraints(config);
8602
- validateEmailConstraints(config);
8603
- validateObservabilityConstraints(config);
8604
- validateCachingConstraints(config);
8605
- validateRateLimitConstraints(config);
8606
- validateSearchConstraints(config);
8607
- validateJavaConstraints(config, providedFlags);
8608
- validateElixirConstraints(config);
8609
- const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
8610
- 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));
8611
- validateSelfBackendCompatibility(providedFlags, options, config);
8612
- validateWorkersCompatibility(providedFlags, options, config);
8613
- if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'alchemy' for --server-deploy.");
8614
- if (providedFlags.has("serverDeploy") && config.serverDeploy === "cloudflare" && config.runtime !== "workers") exitWithError(`Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`);
8615
- if (config.serverDeploy === "vercel" && [
8616
- "nestjs",
8617
- "adonisjs",
8618
- "encore"
8619
- ].includes(config.backend)) incompatibilityError({
8620
- message: "Vercel serverless functions cannot host persistent-process backends",
8621
- provided: {
8622
- backend: config.backend,
8623
- serverDeploy: config.serverDeploy
8624
- },
8625
- suggestions: ["Use --server-deploy fly or --server-deploy railway for NestJS/AdonisJS", "Switch to a serverless-compatible backend like Hono or Express"]
8626
- });
8627
- if (config.serverDeploy === "netlify") {
8628
- if (config.backend !== "hono") incompatibilityError({
8629
- message: "Netlify Functions server deploy is currently supported only with Hono",
8630
- provided: {
8631
- backend: config.backend,
8632
- serverDeploy: config.serverDeploy
8633
- },
8634
- suggestions: ["Use --backend hono", "Use --server-deploy fly, railway, render, or docker for this backend"]
8635
- });
8636
- if (config.runtime !== "node") incompatibilityError({
8637
- message: "Netlify Functions server deploy requires Node.js runtime",
8638
- provided: {
8639
- runtime: config.runtime,
8640
- serverDeploy: config.serverDeploy
8641
- },
8642
- suggestions: ["Use --runtime node", "Choose a different server deployment target"]
8643
- });
8644
- }
8645
- if (config.addons && config.addons.length > 0) {
8646
- validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth, config.backend, config.runtime, config.ecosystem, config.rustFrontend, config.javaWebFramework, config.database);
8647
- config.addons = [...new Set(config.addons)];
8648
- }
8649
- validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
8650
- validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
8651
- validateAIFrontendCompatibility(config.ai, config.frontend ?? []);
8652
- validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
8653
- validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
8654
- validateShadcnConstraints(config, providedFlags);
8655
- validatePeerDependencies(config);
8656
- }
8657
- function validateConfigForProgrammaticUse(config) {
8658
- try {
8659
- if (config.stackParts) {
8660
- const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
8661
- if (graphValidation.issues.length > 0) throw new Error(graphValidation.issues.map((issue) => issue.message).join("\n"));
8662
- }
8663
- validateEcosystemAuthCompatibility(config);
8664
- validateDatabaseOrmAuth(config);
8665
- if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
8666
- validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
8667
- validatePythonApiConstraints(config);
8668
- validateEmailConstraints(config);
8669
- validateObservabilityConstraints(config);
8670
- validateCachingConstraints(config);
8671
- validateRateLimitConstraints(config);
8672
- validateSearchConstraints(config);
8673
- validateJavaConstraints(config);
8674
- validateElixirConstraints(config);
8675
- validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
8676
- 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);
8677
- validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
8678
- validateAIFrontendCompatibility(config.ai, config.frontend ?? []);
8679
- validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
8680
- validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
8681
- validatePeerDependencies(config);
8682
- } catch (error) {
8683
- if (error instanceof Error) throw error;
8684
- throw new Error(String(error), { cause: error });
8685
- }
8686
- }
8687
-
8688
7615
  //#endregion
8689
7616
  //#region src/validation.ts
8690
7617
  const CORE_STACK_FLAGS = new Set([
@@ -11341,7 +10268,7 @@ const router = os.router({
11341
10268
  packageManager: types_exports.PackageManagerSchema.optional().describe("Package manager to use"),
11342
10269
  projectDir: z.string().optional().describe("Project directory (defaults to current)")
11343
10270
  })).handler(async ({ input }) => {
11344
- const { addHandler } = await import("./add-handler-BjTmOwEN.mjs");
10271
+ const { addHandler } = await import("./add-handler-9F-AsGM-.mjs");
11345
10272
  await addHandler(input);
11346
10273
  }),
11347
10274
  history: os.meta({ description: "Show history of scaffolded projects with reproducible commands" }).input(z.object({
@@ -11390,7 +10317,7 @@ const router = os.router({
11390
10317
  json: z.boolean().optional().default(false).describe("Output the diagnosis as JSON")
11391
10318
  })])).handler(async ({ input }) => {
11392
10319
  const [projectDir, options] = input;
11393
- const { doctorCommand } = await import("./doctor-Bm9qvkya.mjs");
10320
+ const { doctorCommand } = await import("./doctor-BFSSbS-U.mjs");
11394
10321
  await doctorCommand({
11395
10322
  projectDir,
11396
10323
  ...options
@@ -11460,7 +10387,7 @@ async function builder() {
11460
10387
  return caller.builder();
11461
10388
  }
11462
10389
  async function add(input) {
11463
- const { addHandler } = await import("./add-handler-BjTmOwEN.mjs");
10390
+ const { addHandler } = await import("./add-handler-9F-AsGM-.mjs");
11464
10391
  return addHandler(input, { silent: true });
11465
10392
  }
11466
10393
  async function history(options) {