create-better-fullstack 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +4 -4
  2. package/dist/{add-handler-CE3XIs3y.mjs → add-handler-z4NCxoq6.mjs} +7 -6
  3. package/dist/addons-setup-COE1e1db.mjs +7 -0
  4. package/dist/{addons-setup-DR924M7b.mjs → addons-setup-DKtiNexf.mjs} +1 -1
  5. package/dist/{analytics-BXlOG6u6.mjs → analytics-BD8hZvIj.mjs} +1 -1
  6. package/dist/{bts-config-B_HhcMXL.mjs → bts-config-Bwd6GB7B.mjs} +29 -0
  7. package/dist/cli.mjs +2 -2
  8. package/dist/command-exists-DtX9ldL8.mjs +15 -0
  9. package/dist/{config-processing-DJisMeW-.mjs → config-processing-B-RT_mVE.mjs} +16 -3
  10. package/dist/{doctor-Cc7sLuow.mjs → doctor-LmNs-hDr.mjs} +4 -3
  11. package/dist/{file-formatter-BgbfpYf4.mjs → file-formatter-DxF56-mO.mjs} +109 -3
  12. package/dist/{gen-BZ0QCxCC.mjs → gen-CQfQU0uZ.mjs} +1 -1
  13. package/dist/{generated-checks-CbioGjCh.mjs → generated-checks-BUcI5VM2.mjs} +55 -22
  14. package/dist/index.d.mts +292 -40
  15. package/dist/index.mjs +19 -9
  16. package/dist/{install-dependencies-D__bmW4k.mjs → install-dependencies-CJiUklm3.mjs} +49 -8
  17. package/dist/{mcp-DNKAo8nO.mjs → mcp-BBammItk.mjs} +5 -5
  18. package/dist/mcp-entry.mjs +47 -19
  19. package/dist/{registry-Cm3D45oT.mjs → registry-D0JfLCoH.mjs} +1 -1
  20. package/dist/{run-DmN7QRt5.mjs → run-BAIeghcU.mjs} +819 -52
  21. package/dist/run-CAl4L4uU.mjs +16 -0
  22. package/dist/{scaffold-manifest-ByKtMhTd.mjs → scaffold-manifest-CQwvyttq.mjs} +1 -1
  23. package/dist/{update-B270pWHc.mjs → update-CnrBdWrh.mjs} +3 -3
  24. package/package.json +21 -4
  25. package/dist/addons-setup-Du2VSv8V.mjs +0 -7
  26. package/dist/run-C_S7ujfY.mjs +0 -15
  27. /package/dist/{scaffold-manifest-CNFz-lmH.mjs → scaffold-manifest-Dr3Anmlf.mjs} +0 -0
  28. /package/dist/{update-deps-aD-iQw4U.mjs → update-deps-G3FgqBlW.mjs} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Better Fullstack
2
2
 
3
- Scaffold production-ready fullstack apps in seconds. Pick your stack from 425 options — the CLI wires everything together.
3
+ Scaffold configurable fullstack apps across web, mobile, and backend ecosystems — the CLI wires your selected stack together.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -31,8 +31,8 @@ Configure your stack visually — pick every option from a UI, preview your choi
31
31
 
32
32
  ## Features
33
33
 
34
- - **425 options** — frontend, backend, database, auth, payments, AI, DevOps, and more
35
- - **7 ecosystems** — TypeScript, React Native, Rust, Python, Go, Java, Elixir
34
+ - **Broad stack catalog** — frontend, backend, database, auth, payments, AI, DevOps, and more
35
+ - **Multi-ecosystem projects** — compose web, mobile, and backend stacks across supported ecosystems
36
36
  - **Visual builder** — configure your stack in the browser
37
37
  - **Wired for you** — every picked integration is preconfigured and working out of the box
38
38
 
@@ -42,7 +42,7 @@ Configure your stack visually — pick every option from a UI, preview your choi
42
42
  --yes # Accept all defaults
43
43
  --yolo # Scaffold a random stack — good for exploring
44
44
  --template <name> # Use a preset (t3, mern, pern, uniwind)
45
- --ecosystem <lang> # Start in typescript, react-native, rust, python, go, java, or elixir mode
45
+ --ecosystem <lang> # Choose the primary project ecosystem
46
46
  --part <binding> # Add a multi-ecosystem stack part, e.g. frontend:typescript:next
47
47
  --version-channel # Dependency channel: stable, latest, beta
48
48
  --no-git # Skip git initialization
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { a as updateBtsConfig, r as readBtsConfig, v as getDefaultConfig } from "./bts-config-B_HhcMXL.mjs";
2
+ import { a as updateBtsConfig, r as readBtsConfig, v as getDefaultConfig } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
4
  import { c as isSilent, l as runWithContextAsync, n as UserCancelledError, t as CLIError } from "./errors-ns_o2OKg.mjs";
5
5
  import "./prompt-environment-BR0Kkw2W.mjs";
6
- import { i as trackEvent, r as maybeShowTelemetryNotice } from "./analytics-BXlOG6u6.mjs";
7
- import "./file-formatter-BgbfpYf4.mjs";
8
- import { c as applyDependencyVersionChannel, t as installDependencies, u as getAddonsToAdd } from "./install-dependencies-D__bmW4k.mjs";
9
- import "./scaffold-manifest-CNFz-lmH.mjs";
10
- import { t as setupAddons } from "./addons-setup-DR924M7b.mjs";
6
+ import { i as trackEvent, r as maybeShowTelemetryNotice } from "./analytics-BD8hZvIj.mjs";
7
+ import "./file-formatter-DxF56-mO.mjs";
8
+ import { c as applyDependencyVersionChannel, t as installDependencies, u as getAddonsToAdd } from "./install-dependencies-CJiUklm3.mjs";
9
+ import "./command-exists-DtX9ldL8.mjs";
10
+ import "./scaffold-manifest-Dr3Anmlf.mjs";
11
+ import { t as setupAddons } from "./addons-setup-DKtiNexf.mjs";
11
12
  import { p as planStackUpdate, s as applyStackUpdate } from "./mcp-entry.mjs";
12
13
  import { intro, log, outro } from "@clack/prompts";
13
14
  import pc from "picocolors";
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import "./bts-config-Bwd6GB7B.mjs";
3
+ import "./errors-ns_o2OKg.mjs";
4
+ import "./prompt-environment-BR0Kkw2W.mjs";
5
+ import { t as setupAddons } from "./addons-setup-DKtiNexf.mjs";
6
+
7
+ export { setupAddons };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as readBtsConfig } from "./bts-config-B_HhcMXL.mjs";
2
+ import { r as readBtsConfig } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import { c as isSilent, r as exitCancelled } from "./errors-ns_o2OKg.mjs";
4
4
  import { t as canPromptInteractively } from "./prompt-environment-BR0Kkw2W.mjs";
5
5
  import { autocompleteMultiselect, group, isCancel, log, multiselect, select, spinner } from "@clack/prompts";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { m as getLatestCLIVersion } from "./bts-config-B_HhcMXL.mjs";
2
+ import { m as getLatestCLIVersion } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import { t as canPromptInteractively } from "./prompt-environment-BR0Kkw2W.mjs";
4
4
  import { log } from "@clack/prompts";
5
5
  import pc from "picocolors";
@@ -40,6 +40,8 @@ function getDefaultConfig() {
40
40
  pythonAi: [...DEFAULT_CONFIG_BASE.pythonAi],
41
41
  javaLibraries: [...DEFAULT_CONFIG_BASE.javaLibraries],
42
42
  javaTestingLibraries: [...DEFAULT_CONFIG_BASE.javaTestingLibraries],
43
+ mobileLibraries: [...DEFAULT_CONFIG_BASE.mobileLibraries],
44
+ dotnetLibraries: [...DEFAULT_CONFIG_BASE.dotnetLibraries],
43
45
  aiDocs: [...DEFAULT_CONFIG_BASE.aiDocs]
44
46
  };
45
47
  }
@@ -301,6 +303,13 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
301
303
  normalized.pythonRealtime = "none";
302
304
  normalized.pythonObservability = "none";
303
305
  normalized.pythonCli = [];
306
+ normalized.pythonCloudSdk = "none";
307
+ normalized.pythonHttpClient = "none";
308
+ normalized.pythonData = [];
309
+ normalized.pythonMedia = "none";
310
+ normalized.pythonServer = "none";
311
+ normalized.pythonPackageManager = "uv";
312
+ normalized.pythonMessageQueue = "none";
304
313
  }
305
314
  if (!selectedEcosystems.has("go")) {
306
315
  normalized.goWebFramework = "none";
@@ -346,6 +355,7 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
346
355
  normalized.dotnetValidation = "none";
347
356
  normalized.dotnetCaching = "none";
348
357
  normalized.dotnetDeploy = "none";
358
+ normalized.dotnetLibraries = [];
349
359
  }
350
360
  if (!selectedEcosystems.has("elixir")) {
351
361
  normalized.elixirWebFramework = "none";
@@ -381,6 +391,7 @@ function normalizeGraphConfigForPersistence(projectConfig, stackParts) {
381
391
  normalized.mobilePush = "none";
382
392
  normalized.mobileOTA = "none";
383
393
  normalized.mobileDeepLinking = "none";
394
+ normalized.mobileLibraries = [];
384
395
  }
385
396
  return normalized;
386
397
  }
@@ -497,6 +508,7 @@ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
497
508
  mobilePush: persistedConfig.mobilePush,
498
509
  mobileOTA: persistedConfig.mobileOTA,
499
510
  mobileDeepLinking: persistedConfig.mobileDeepLinking,
511
+ mobileLibraries: persistedConfig.mobileLibraries,
500
512
  cms: persistedConfig.cms,
501
513
  caching: persistedConfig.caching,
502
514
  rateLimit: persistedConfig.rateLimit,
@@ -532,6 +544,13 @@ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
532
544
  pythonRealtime: persistedConfig.pythonRealtime,
533
545
  pythonObservability: persistedConfig.pythonObservability,
534
546
  pythonCli: persistedConfig.pythonCli,
547
+ pythonCloudSdk: persistedConfig.pythonCloudSdk,
548
+ pythonHttpClient: persistedConfig.pythonHttpClient,
549
+ pythonData: persistedConfig.pythonData,
550
+ pythonMedia: persistedConfig.pythonMedia,
551
+ pythonServer: persistedConfig.pythonServer,
552
+ pythonPackageManager: persistedConfig.pythonPackageManager,
553
+ pythonMessageQueue: persistedConfig.pythonMessageQueue,
535
554
  goWebFramework: persistedConfig.goWebFramework,
536
555
  goOrm: persistedConfig.goOrm,
537
556
  goApi: persistedConfig.goApi,
@@ -570,6 +589,7 @@ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
570
589
  dotnetValidation: persistedConfig.dotnetValidation,
571
590
  dotnetCaching: persistedConfig.dotnetCaching,
572
591
  dotnetDeploy: persistedConfig.dotnetDeploy,
592
+ dotnetLibraries: persistedConfig.dotnetLibraries,
573
593
  elixirWebFramework: persistedConfig.elixirWebFramework,
574
594
  elixirOrm: persistedConfig.elixirOrm,
575
595
  elixirAuth: persistedConfig.elixirAuth,
@@ -665,6 +685,7 @@ async function writeBtsConfig(projectConfig, metadata = {}) {
665
685
  mobilePush: btsConfig.mobilePush,
666
686
  mobileOTA: btsConfig.mobileOTA,
667
687
  mobileDeepLinking: btsConfig.mobileDeepLinking,
688
+ mobileLibraries: btsConfig.mobileLibraries,
668
689
  cms: btsConfig.cms,
669
690
  caching: btsConfig.caching,
670
691
  rateLimit: btsConfig.rateLimit,
@@ -700,6 +721,13 @@ async function writeBtsConfig(projectConfig, metadata = {}) {
700
721
  pythonRealtime: btsConfig.pythonRealtime,
701
722
  pythonObservability: btsConfig.pythonObservability,
702
723
  pythonCli: btsConfig.pythonCli,
724
+ pythonCloudSdk: btsConfig.pythonCloudSdk,
725
+ pythonHttpClient: btsConfig.pythonHttpClient,
726
+ pythonData: btsConfig.pythonData,
727
+ pythonMedia: btsConfig.pythonMedia,
728
+ pythonServer: btsConfig.pythonServer,
729
+ pythonPackageManager: btsConfig.pythonPackageManager,
730
+ pythonMessageQueue: btsConfig.pythonMessageQueue,
703
731
  goWebFramework: btsConfig.goWebFramework,
704
732
  goOrm: btsConfig.goOrm,
705
733
  goApi: btsConfig.goApi,
@@ -738,6 +766,7 @@ async function writeBtsConfig(projectConfig, metadata = {}) {
738
766
  dotnetValidation: btsConfig.dotnetValidation,
739
767
  dotnetCaching: btsConfig.dotnetCaching,
740
768
  dotnetDeploy: btsConfig.dotnetDeploy,
769
+ dotnetLibraries: btsConfig.dotnetLibraries,
741
770
  elixirWebFramework: btsConfig.elixirWebFramework,
742
771
  elixirOrm: btsConfig.elixirOrm,
743
772
  elixirAuth: btsConfig.elixirAuth,
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  //#region src/cli.ts
3
- if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-DNKAo8nO.mjs").then((m) => m.startMcpServer());
4
- else import("./run-C_S7ujfY.mjs").then((m) => m.createBtsCli().run());
3
+ if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-BBammItk.mjs").then((m) => m.startMcpServer());
4
+ else import("./run-CAl4L4uU.mjs").then((m) => m.createBtsCli().run());
5
5
 
6
6
  //#endregion
7
7
  export { };
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { $ } from "execa";
3
+
4
+ //#region src/utils/command-exists.ts
5
+ async function commandExists(command) {
6
+ try {
7
+ if (process.platform === "win32") return (await $({ reject: false })`where ${command}`).exitCode === 0;
8
+ return (await $({ reject: false })`which ${command}`).exitCode === 0;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ //#endregion
15
+ export { commandExists as t };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { b as types_exports } from "./bts-config-B_HhcMXL.mjs";
2
+ import { b as types_exports } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import path from "node:path";
4
4
  import { cliInputToProjectConfigPartial } from "@better-fullstack/types/stack-translation";
5
5
 
@@ -13,7 +13,7 @@ function getBaseCommand(packageManager) {
13
13
  default: return "npx create-better-fullstack@latest";
14
14
  }
15
15
  }
16
- function formatArrayFlag(flag, values) {
16
+ function formatArrayFlag(flag, values = []) {
17
17
  const normalizedValues = values.filter((value) => value !== "none");
18
18
  if (normalizedValues.length === 0) return `--${flag} none`;
19
19
  return `--${flag} ${normalizedValues.join(" ")}`;
@@ -75,7 +75,7 @@ function hasAnyOwnedGraphArrayPart(config, ownerRole, role, ecosystem) {
75
75
  }
76
76
  function appendChangedOwnedGraphArrayFlag(flags, config, ownerRole, role, ecosystem, flag, values, defaultValues) {
77
77
  if (hasAnyOwnedGraphArrayPart(config, ownerRole, role, ecosystem)) return;
78
- appendChangedArrayFlag(flags, flag, values, defaultValues);
78
+ appendChangedArrayFlag(flags, flag, values ?? [], defaultValues);
79
79
  }
80
80
  function appendChangedArrayFlag(flags, flag, values, defaultValues) {
81
81
  if (values.length !== defaultValues.length || values.some((value, index) => value !== defaultValues[index])) flags.push(formatArrayFlag(flag, values));
@@ -141,6 +141,7 @@ function appendGraphExtraFlags(flags, config) {
141
141
  appendChangedOwnedGraphStringFlag(flags, config, "mobile", "push", "react-native", "mobile-push", config.mobilePush, "none");
142
142
  appendChangedOwnedGraphStringFlag(flags, config, "mobile", "ota", "react-native", "mobile-ota", config.mobileOTA, "none");
143
143
  appendChangedOwnedGraphStringFlag(flags, config, "mobile", "deepLinking", "react-native", "mobile-deep-linking", config.mobileDeepLinking, "none");
144
+ appendChangedOwnedGraphArrayFlag(flags, config, "mobile", "libraries", "react-native", "mobile-libraries", config.mobileLibraries, []);
144
145
  }
145
146
  if (hasGraphPrimaryPart(config, "frontend", "rust")) appendChangedStringFlag(flags, "rust-frontend", config.rustFrontend, "none");
146
147
  if (hasGraphPrimaryPart(config, "backend", "rust")) {
@@ -247,6 +248,7 @@ function getTypeScriptFlags(config) {
247
248
  flags.push(`--mobile-push ${config.mobilePush}`);
248
249
  flags.push(`--mobile-ota ${config.mobileOTA}`);
249
250
  flags.push(`--mobile-deep-linking ${config.mobileDeepLinking}`);
251
+ if (config.mobileLibraries) flags.push(formatArrayFlag("mobile-libraries", config.mobileLibraries));
250
252
  if (config.addons && config.addons.length > 0) flags.push(`--addons ${config.addons.join(" ")}`);
251
253
  else flags.push("--addons none");
252
254
  if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
@@ -269,6 +271,7 @@ function getReactNativeFlags(config) {
269
271
  flags.push(`--mobile-push ${config.mobilePush}`);
270
272
  flags.push(`--mobile-ota ${config.mobileOTA}`);
271
273
  flags.push(`--mobile-deep-linking ${config.mobileDeepLinking}`);
274
+ flags.push(formatArrayFlag("mobile-libraries", config.mobileLibraries));
272
275
  appendCommonFlags(flags, config);
273
276
  return flags;
274
277
  }
@@ -308,6 +311,13 @@ function getPythonFlags(config) {
308
311
  flags.push(`--python-realtime ${config.pythonRealtime}`);
309
312
  flags.push(`--python-observability ${config.pythonObservability}`);
310
313
  flags.push(formatArrayFlag("python-cli", config.pythonCli));
314
+ flags.push(`--python-cloud-sdk ${config.pythonCloudSdk}`);
315
+ flags.push(`--python-http-client ${config.pythonHttpClient}`);
316
+ flags.push(formatArrayFlag("python-data", config.pythonData));
317
+ flags.push(`--python-media ${config.pythonMedia}`);
318
+ flags.push(`--python-server ${config.pythonServer}`);
319
+ flags.push(`--python-package-manager ${config.pythonPackageManager}`);
320
+ flags.push(`--python-message-queue ${config.pythonMessageQueue}`);
311
321
  appendSharedNonTypeScriptFlags(flags, config);
312
322
  appendCommonFlags(flags, config);
313
323
  return flags;
@@ -365,6 +375,7 @@ function getDotnetFlags(config) {
365
375
  flags.push(`--dotnet-validation ${config.dotnetValidation}`);
366
376
  flags.push(`--dotnet-caching ${config.dotnetCaching}`);
367
377
  flags.push(`--dotnet-deploy ${config.dotnetDeploy}`);
378
+ if (config.dotnetLibraries) flags.push(formatArrayFlag("dotnet-libraries", config.dotnetLibraries));
368
379
  appendCommonFlags(flags, config);
369
380
  return flags;
370
381
  }
@@ -562,6 +573,8 @@ function validateArrayOptions(options) {
562
573
  validateNoneExclusivity(options.pythonAi, "python ai libraries");
563
574
  validateNoneExclusivity(options.javaLibraries, "java libraries");
564
575
  validateNoneExclusivity(options.javaTestingLibraries, "java testing libraries");
576
+ validateNoneExclusivity(options.mobileLibraries, "mobile libraries");
577
+ validateNoneExclusivity(options.dotnetLibraries, ".NET libraries");
565
578
  }
566
579
 
567
580
  //#endregion
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { r as readBtsConfig } from "./bts-config-B_HhcMXL.mjs";
2
+ import { r as readBtsConfig } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
4
  import { a as handleError } from "./errors-ns_o2OKg.mjs";
5
- import { t as runGeneratedChecks } from "./generated-checks-CbioGjCh.mjs";
5
+ import "./command-exists-DtX9ldL8.mjs";
6
+ import { t as runGeneratedChecks } from "./generated-checks-BUcI5VM2.mjs";
6
7
  import { intro, log, spinner } from "@clack/prompts";
7
8
  import pc from "picocolors";
8
9
  import fs from "fs-extra";
@@ -49,7 +50,7 @@ const NATIVE_LOCKFILES = {
49
50
  },
50
51
  python: {
51
52
  file: "uv.lock",
52
- hint: "uv sync"
53
+ hint: "uv sync --extra dev"
53
54
  },
54
55
  elixir: {
55
56
  file: "mix.lock",
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { b as types_exports } from "./bts-config-B_HhcMXL.mjs";
2
+ import { b as types_exports } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import { c as isSilent, i as exitWithError, t as CLIError } from "./errors-ns_o2OKg.mjs";
4
4
  import pc from "picocolors";
5
5
  import z from "zod";
@@ -54,6 +54,7 @@ const CreateCommandOptionsSchema = z.object({
54
54
  mobilePush: types_exports.MobilePushSchema.optional().describe("Mobile push notifications (expo-notifications)"),
55
55
  mobileOTA: types_exports.MobileOTASchema.optional().describe("Mobile OTA updates (expo-updates)"),
56
56
  mobileDeepLinking: types_exports.MobileDeepLinkingSchema.optional().describe("Mobile deep linking (expo-linking)"),
57
+ mobileLibraries: z.array(types_exports.MobileLibrariesSchema).optional().describe("Mobile application libraries"),
57
58
  frontend: z.array(types_exports.FrontendSchema).optional(),
58
59
  astroIntegration: types_exports.AstroIntegrationSchema.optional().describe("Astro UI framework integration (react, vue, svelte, solid)"),
59
60
  addons: z.array(types_exports.AddonsSchema).optional(),
@@ -110,6 +111,13 @@ const CreateCommandOptionsSchema = z.object({
110
111
  pythonRealtime: types_exports.PythonRealtimeSchema.optional().describe("Python realtime (python-socketio, websockets)"),
111
112
  pythonObservability: types_exports.PythonObservabilitySchema.optional().describe("Python observability (opentelemetry)"),
112
113
  pythonCli: z.array(types_exports.PythonCliSchema).optional().describe("Python CLI tooling (typer, click, rich)"),
114
+ pythonCloudSdk: types_exports.PythonCloudSdkSchema.optional().describe("Python cloud SDK (boto3)"),
115
+ pythonHttpClient: types_exports.PythonHttpClientSchema.optional().describe("Python HTTP client (requests)"),
116
+ pythonData: z.array(types_exports.PythonDataSchema).optional().describe("Python data and scientific libraries (numpy, pandas, scipy)"),
117
+ pythonMedia: types_exports.PythonMediaSchema.optional().describe("Python media library (pillow)"),
118
+ pythonServer: types_exports.PythonServerSchema.optional().describe("Python production server (gunicorn)"),
119
+ pythonPackageManager: types_exports.PythonPackageManagerSchema.optional().describe("Python package manager (uv, poetry)"),
120
+ pythonMessageQueue: types_exports.PythonMessageQueueSchema.optional().describe("Python message queue client (confluent-kafka)"),
113
121
  goWebFramework: types_exports.GoWebFrameworkSchema.optional().describe("Go web framework"),
114
122
  goOrm: types_exports.GoOrmSchema.optional().describe("Go ORM/database layer"),
115
123
  goApi: types_exports.GoApiSchema.optional().describe("Go API layer"),
@@ -148,6 +156,7 @@ const CreateCommandOptionsSchema = z.object({
148
156
  dotnetValidation: types_exports.DotnetValidationSchema.optional().describe(".NET validation (fluentvalidation, data-annotations)"),
149
157
  dotnetCaching: types_exports.DotnetCachingSchema.optional().describe(".NET caching (redis, memory-cache, none)"),
150
158
  dotnetDeploy: types_exports.DotnetDeploySchema.optional().describe(".NET deploy target (docker, azure, aws, none)"),
159
+ dotnetLibraries: z.array(types_exports.DotnetLibrariesSchema).optional().describe(".NET application libraries"),
151
160
  elixirWebFramework: types_exports.ElixirWebFrameworkSchema.optional().describe("Elixir web framework (phoenix, phoenix-live-view, none)"),
152
161
  elixirOrm: types_exports.ElixirOrmSchema.optional().describe("Elixir ORM/database (ecto, ecto-sql, none)"),
153
162
  elixirAuth: types_exports.ElixirAuthSchema.optional().describe("Elixir auth (phx-gen-auth, ueberauth, guardian, none)"),
@@ -427,9 +436,9 @@ function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime,
427
436
  isCompatible: false,
428
437
  reason: `${title} for Java currently requires Spring Boot`
429
438
  };
430
- if (ecosystem === "python" && database && database !== "none" && database !== "sqlite" && database !== "postgres") return {
439
+ if (ecosystem === "python" && database && database !== "none" && database !== "sqlite" && database !== "postgres" && database !== "mongodb") return {
431
440
  isCompatible: false,
432
- reason: `${title} for Python ORM projects currently supports SQLite defaults or Postgres`
441
+ reason: `${title} for Python ORM projects currently supports SQLite, Postgres, or MongoDB`
433
442
  };
434
443
  }
435
444
  return { isCompatible: true };
@@ -607,6 +616,15 @@ function validateRustExpansionCompatibility(config) {
607
616
  },
608
617
  suggestions: ["Use --rust-web-framework axum", "Choose --rust-auth openidconnect or none"]
609
618
  });
619
+ const orm = config.rustOrm ?? "none";
620
+ if (auth === "torii" && orm === "rusqlite") incompatibilityError({
621
+ message: "Torii's sqlx-based SQLite storage conflicts with rusqlite: both link the native sqlite3 library and cargo permits only one linker.",
622
+ provided: {
623
+ "rust-orm": orm,
624
+ "rust-auth": auth
625
+ },
626
+ suggestions: ["Use --rust-orm sqlx, sea-orm, or diesel with Torii", "Choose --rust-auth none"]
627
+ });
610
628
  }
611
629
 
612
630
  //#endregion
@@ -702,6 +720,7 @@ function buildCompatibilityInputFromConfig(config) {
702
720
  mobilePush: asString(config.mobilePush),
703
721
  mobileOTA: asString(config.mobileOTA),
704
722
  mobileDeepLinking: asString(config.mobileDeepLinking),
723
+ mobileLibraries: asStringArray(config.mobileLibraries),
705
724
  codeQuality,
706
725
  documentation,
707
726
  appPlatforms,
@@ -745,6 +764,13 @@ function buildCompatibilityInputFromConfig(config) {
745
764
  pythonRealtime: asString(config.pythonRealtime),
746
765
  pythonObservability: asString(config.pythonObservability),
747
766
  pythonCli: asStringArray(config.pythonCli),
767
+ pythonCloudSdk: asString(config.pythonCloudSdk),
768
+ pythonHttpClient: asString(config.pythonHttpClient),
769
+ pythonData: asStringArray(config.pythonData),
770
+ pythonMedia: asString(config.pythonMedia),
771
+ pythonServer: asString(config.pythonServer),
772
+ pythonPackageManager: asString(config.pythonPackageManager),
773
+ pythonMessageQueue: asString(config.pythonMessageQueue),
748
774
  goWebFramework: asString(config.goWebFramework),
749
775
  goOrm: asString(config.goOrm),
750
776
  goApi: asString(config.goApi),
@@ -783,6 +809,7 @@ function buildCompatibilityInputFromConfig(config) {
783
809
  dotnetValidation: asString(config.dotnetValidation),
784
810
  dotnetCaching: asString(config.dotnetCaching),
785
811
  dotnetDeploy: asString(config.dotnetDeploy),
812
+ dotnetLibraries: asStringArray(config.dotnetLibraries),
786
813
  elixirWebFramework: asString(config.elixirWebFramework),
787
814
  elixirOrm: asString(config.elixirOrm),
788
815
  elixirAuth: asString(config.elixirAuth),
@@ -1443,6 +1470,28 @@ function validateEffectBackendConstraints(config) {
1443
1470
  suggestions: ["Use --validation effect-schema"]
1444
1471
  });
1445
1472
  }
1473
+ function validateScopedLibraryFlags(config) {
1474
+ const mobileLibraries = (config.mobileLibraries ?? []).filter((lib) => lib !== "none");
1475
+ const hasNativeFrontend = (config.frontend ?? []).some((f) => String(f).startsWith("native-")) || config.stackParts?.some((part) => part.ecosystem === "react-native");
1476
+ if (mobileLibraries.length > 0 && !hasNativeFrontend) incompatibilityError({
1477
+ message: "Mobile libraries require a native Expo frontend.",
1478
+ provided: {
1479
+ frontend: (config.frontend ?? []).join(" ") || "none",
1480
+ "mobile-libraries": mobileLibraries.join(" ")
1481
+ },
1482
+ suggestions: ["Add a native frontend (native-bare, native-uniwind, native-unistyles)", "Remove --mobile-libraries for web-only stacks"]
1483
+ });
1484
+ const dotnetLibraries = (config.dotnetLibraries ?? []).filter((lib) => lib !== "none");
1485
+ const hasDotnetPart = config.ecosystem === "dotnet" || config.stackParts?.some((part) => part.ecosystem === "dotnet");
1486
+ if (dotnetLibraries.length > 0 && !hasDotnetPart) incompatibilityError({
1487
+ message: ".NET libraries require a .NET backend.",
1488
+ provided: {
1489
+ ecosystem: config.ecosystem ?? "none",
1490
+ "dotnet-libraries": dotnetLibraries.join(" ")
1491
+ },
1492
+ suggestions: ["Use --ecosystem dotnet (or add a .NET graph part) with --dotnet-libraries", "Remove --dotnet-libraries for non-.NET stacks"]
1493
+ });
1494
+ }
1446
1495
  function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set()) {
1447
1496
  if (config.ecosystem !== "java") return;
1448
1497
  const hasSpringBoot = config.javaWebFramework === "spring-boot";
@@ -1755,6 +1804,59 @@ function validatePythonApiConstraints(config) {
1755
1804
  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"]
1756
1805
  });
1757
1806
  }
1807
+ function validatePythonExpansionConstraints(config) {
1808
+ const pythonConfig = config.ecosystem === "python" ? config : config.stackParts?.some((part) => part.role === "backend" && part.ecosystem === "python" && part.source !== "provided") ? (0, types_exports.stackGraphToLegacyProjectConfigForEcosystem)(config, "python") : void 0;
1809
+ if (!pythonConfig) return;
1810
+ config = pythonConfig;
1811
+ if (config.pythonOrm === "pymongo" && config.database !== "mongodb") incompatibilityError({
1812
+ message: "PyMongo requires --database mongodb.",
1813
+ provided: {
1814
+ database: config.database || "none",
1815
+ "python-orm": "pymongo"
1816
+ },
1817
+ suggestions: ["Add --database mongodb when using --python-orm pymongo", "Choose another --python-orm for relational databases"]
1818
+ });
1819
+ if (config.database === "mongodb" && config.pythonOrm !== "pymongo" && config.pythonOrm !== "none" && config.pythonOrm !== void 0) incompatibilityError({
1820
+ message: "MongoDB requires --python-orm pymongo (relational ORMs cannot use it).",
1821
+ provided: {
1822
+ database: "mongodb",
1823
+ "python-orm": config.pythonOrm
1824
+ },
1825
+ suggestions: ["Use --python-orm pymongo with --database mongodb", "Choose a relational database (postgres, sqlite, mysql) for this ORM"]
1826
+ });
1827
+ if ((config.pythonWebFramework === "none" || config.pythonWebFramework === "aiohttp" || config.pythonWebFramework === "starlette" || config.pythonWebFramework === "streamlit") && config.pythonGraphql !== void 0 && config.pythonGraphql !== "none") incompatibilityError({
1828
+ message: "Python GraphQL is only wired for FastAPI, Django, Flask, and Litestar.",
1829
+ provided: {
1830
+ "python-web-framework": config.pythonWebFramework,
1831
+ "python-graphql": config.pythonGraphql
1832
+ },
1833
+ suggestions: ["Use FastAPI, Django, Flask, or Litestar with --python-graphql", "Set --python-graphql none"]
1834
+ });
1835
+ if ((config.pythonWebFramework === "none" || config.pythonWebFramework === "aiohttp" || config.pythonWebFramework === "starlette" || config.pythonWebFramework === "streamlit") && config.pythonAuth !== void 0 && config.pythonAuth !== "none") incompatibilityError({
1836
+ message: `Python auth routes are not wired for ${config.pythonWebFramework}.`,
1837
+ provided: {
1838
+ "python-web-framework": config.pythonWebFramework,
1839
+ "python-auth": config.pythonAuth
1840
+ },
1841
+ suggestions: ["Use FastAPI, Django, Flask, or Litestar with --python-auth", "Set --python-auth none"]
1842
+ });
1843
+ if ((config.pythonWebFramework === "streamlit" || config.pythonWebFramework === "none") && config.pythonObservability === "prometheus-client") incompatibilityError({
1844
+ message: "prometheus-client needs an HTTP server framework to expose /metrics.",
1845
+ provided: {
1846
+ "python-web-framework": config.pythonWebFramework,
1847
+ "python-observability": "prometheus-client"
1848
+ },
1849
+ suggestions: ["Use FastAPI, Django, Flask, Litestar, Starlette, or aiohttp", "Set --python-observability none"]
1850
+ });
1851
+ if (config.pythonServer === "gunicorn" && (config.pythonWebFramework === "streamlit" || config.pythonWebFramework === "none")) incompatibilityError({
1852
+ message: "Gunicorn requires a WSGI, ASGI, or aiohttp Python application.",
1853
+ provided: {
1854
+ "python-web-framework": config.pythonWebFramework,
1855
+ "python-server": "gunicorn"
1856
+ },
1857
+ suggestions: ["Use FastAPI, Django, Flask, Litestar, or aiohttp with --python-server gunicorn", "Set --python-server none for Streamlit or framework-free projects"]
1858
+ });
1859
+ }
1758
1860
  function validateI18nConstraints(config) {
1759
1861
  if (config.i18n !== "intlayer") return;
1760
1862
  const { web } = splitFrontends$1(config.frontend ?? []);
@@ -1786,6 +1888,7 @@ function validateFullConfig(config, providedFlags, options) {
1786
1888
  validateFrontendConstraints(config, providedFlags);
1787
1889
  validateApiConstraints(config, options);
1788
1890
  validatePythonApiConstraints(config);
1891
+ validatePythonExpansionConstraints(config);
1789
1892
  validateRustExpansionCompatibility(config);
1790
1893
  validateEmailConstraints(config);
1791
1894
  validateObservabilityConstraints(config);
@@ -1794,6 +1897,7 @@ function validateFullConfig(config, providedFlags, options) {
1794
1897
  validateSearchConstraints(config);
1795
1898
  validateJavaConstraints(config, providedFlags);
1796
1899
  validateElixirConstraints(config);
1900
+ validateScopedLibraryFlags(config);
1797
1901
  validateI18nConstraints(config);
1798
1902
  const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
1799
1903
  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));
@@ -1858,6 +1962,7 @@ function validateConfigForProgrammaticUse(config) {
1858
1962
  if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
1859
1963
  validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
1860
1964
  validatePythonApiConstraints(config);
1965
+ validatePythonExpansionConstraints(config);
1861
1966
  validateEmailConstraints(config);
1862
1967
  validateObservabilityConstraints(config);
1863
1968
  validateCachingConstraints(config);
@@ -1866,6 +1971,7 @@ function validateConfigForProgrammaticUse(config) {
1866
1971
  validateJavaConstraints(config);
1867
1972
  validateElixirConstraints(config);
1868
1973
  validateRustExpansionCompatibility(config);
1974
+ validateScopedLibraryFlags(config);
1869
1975
  validateI18nConstraints(config);
1870
1976
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
1871
1977
  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, config.api);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as readBtsConfig } from "./bts-config-B_HhcMXL.mjs";
2
+ import { r as readBtsConfig } from "./bts-config-Bwd6GB7B.mjs";
3
3
  import { log } from "@clack/prompts";
4
4
  import pc from "picocolors";
5
5
  import fs from "fs-extra";
@@ -1,35 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { f as getPrimaryGraphPart } from "./bts-config-B_HhcMXL.mjs";
2
+ import { b as types_exports, f as getPrimaryGraphPart } from "./bts-config-Bwd6GB7B.mjs";
3
+ import { t as commandExists } from "./command-exists-DtX9ldL8.mjs";
3
4
  import { log, spinner } from "@clack/prompts";
4
5
  import pc from "picocolors";
5
6
  import path from "node:path";
6
7
  import { $ } from "execa";
7
8
 
8
- //#region src/utils/command-exists.ts
9
- async function commandExists(command) {
10
- try {
11
- if (process.platform === "win32") return (await $({ reject: false })`where ${command}`).exitCode === 0;
12
- return (await $({ reject: false })`which ${command}`).exitCode === 0;
13
- } catch {
14
- return false;
15
- }
16
- }
17
-
18
- //#endregion
19
9
  //#region src/utils/generated-checks.ts
20
10
  function getGraphTarget(config) {
21
11
  const backend = getPrimaryGraphPart(config, "backend");
22
12
  if (!backend || backend.ecosystem === "typescript" || backend.ecosystem === "react-native" || backend.ecosystem === "universal") return null;
13
+ const projected = backend.ecosystem === "python" ? (0, types_exports.stackGraphToLegacyProjectConfigForEcosystem)(config, "python") : config;
23
14
  return {
24
15
  ecosystem: backend.ecosystem,
25
- projectDir: path.join(config.projectDir, backend.targetPath ?? "apps/server")
16
+ projectDir: path.join(config.projectDir, backend.targetPath ?? "apps/server"),
17
+ pythonPackageManager: projected.pythonPackageManager,
18
+ pythonQuality: projected.pythonQuality
26
19
  };
27
20
  }
28
21
  function getSingleEcosystemTarget(config) {
29
22
  if (config.ecosystem === "typescript" || config.ecosystem === "react-native" || config.ecosystem === "java") return null;
30
23
  return {
31
24
  ecosystem: config.ecosystem,
32
- projectDir: config.projectDir
25
+ projectDir: config.projectDir,
26
+ pythonPackageManager: config.pythonPackageManager,
27
+ pythonQuality: config.pythonQuality
33
28
  };
34
29
  }
35
30
  async function runCommand(cwd, command, args) {
@@ -39,6 +34,19 @@ async function runCommand(cwd, command, args) {
39
34
  stderr: "inherit"
40
35
  })`${command} ${args}`;
41
36
  }
37
+ function venvBin(tool) {
38
+ return process.platform === "win32" ? `.venv\\Scripts\\${tool}` : `.venv/bin/${tool}`;
39
+ }
40
+ async function runPythonQualityCheck(cwd, runner, quality) {
41
+ const run = (tool, args) => runner === "venv" ? runCommand(cwd, venvBin(tool), args) : runCommand(cwd, runner, [
42
+ "run",
43
+ tool,
44
+ ...args
45
+ ]);
46
+ if (quality === "ruff") await run("ruff", ["check", "."]);
47
+ else if (quality === "mypy") await run("mypy", []);
48
+ else if (quality === "pyright") await run("pyright", []);
49
+ }
42
50
  async function verifyTarget(target) {
43
51
  const s = spinner();
44
52
  const cwd = target.projectDir;
@@ -56,13 +64,38 @@ async function verifyTarget(target) {
56
64
  return;
57
65
  case "python":
58
66
  s.start("Verifying generated Python server...");
59
- await runCommand(cwd, "uv", ["sync"]);
60
- await runCommand(cwd, "uv", [
61
- "run",
62
- "ruff",
63
- "check",
64
- "."
65
- ]);
67
+ if (target.pythonPackageManager === "poetry") {
68
+ await runCommand(cwd, "poetry", [
69
+ "install",
70
+ "--extras",
71
+ "dev"
72
+ ]);
73
+ await runPythonQualityCheck(cwd, "poetry", target.pythonQuality);
74
+ } else if (target.pythonPackageManager === "none") {
75
+ await runCommand(cwd, await commandExists("python") ? "python" : "python3", [
76
+ "-m",
77
+ "venv",
78
+ ".venv"
79
+ ]);
80
+ await runCommand(cwd, venvBin("pip"), [
81
+ "install",
82
+ "-e",
83
+ ".[dev]"
84
+ ]);
85
+ await runCommand(cwd, venvBin("python"), [
86
+ "-m",
87
+ "compileall",
88
+ "src"
89
+ ]);
90
+ await runPythonQualityCheck(cwd, "venv", target.pythonQuality);
91
+ } else {
92
+ await runCommand(cwd, "uv", [
93
+ "sync",
94
+ "--extra",
95
+ "dev"
96
+ ]);
97
+ await runPythonQualityCheck(cwd, "uv", target.pythonQuality);
98
+ }
66
99
  s.stop("Generated Python server checks passed");
67
100
  return;
68
101
  case "elixir":
@@ -88,4 +121,4 @@ async function runGeneratedChecks(config) {
88
121
  }
89
122
 
90
123
  //#endregion
91
- export { commandExists as n, runGeneratedChecks as t };
124
+ export { runGeneratedChecks as t };