create-better-fullstack 2.1.4 → 2.1.6

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,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { c as isSilent, g as updateBtsConfig, l as runWithContextAsync, m as readBtsConfig, n as UserCancelledError, t as CLIError, x as getDefaultConfig } from "./errors-Cyol8zbN.mjs";
2
+ import { a as updateBtsConfig, r as readBtsConfig, u as getDefaultConfig } from "./bts-config-BceXPcpI.mjs";
3
3
  import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
- import { t as setupAddons } from "./addons-setup-CyrP1IV-.mjs";
5
- import "./file-formatter-B3dsev2l.mjs";
6
- import { c as applyDependencyVersionChannel, t as installDependencies, u as getAddonsToAdd } from "./install-dependencies-CgNh-aOy.mjs";
7
- import { a as applyStackUpdate, o as planStackUpdate } from "./mcp-entry.mjs";
4
+ import { c as isSilent, l as runWithContextAsync, n as UserCancelledError, t as CLIError } from "./errors-ns_o2OKg.mjs";
5
+ import { t as setupAddons } from "./addons-setup-C_lxdJqU.mjs";
6
+ import "./file-formatter-XU6ti05V.mjs";
7
+ import { c as applyDependencyVersionChannel, t as installDependencies, u as getAddonsToAdd } from "./install-dependencies-DHoYa3P-.mjs";
8
+ import { o as applyStackUpdate, u as planStackUpdate } from "./mcp-entry.mjs";
8
9
  import { intro, log, outro } from "@clack/prompts";
9
10
  import pc from "picocolors";
10
11
  import fs from "fs-extra";
@@ -55,6 +56,13 @@ function logStackUpdateSummary(plan, dryRun) {
55
56
  const envCount = countEnvChanges(plan);
56
57
  if (envCount > 0) log.info(pc.dim(`Env vars: ${formatCount(envCount, "addition")}`));
57
58
  for (const adjustment of plan.compatibilityAdjustments) log.info(pc.dim(`Adjusted: ${adjustment}`));
59
+ if (plan.architectureChanges.length > 0) {
60
+ const swaps = plan.architectureChanges.map((change) => `${change.key}: ${change.from} -> ${change.to}`).join(", ");
61
+ log.warn(pc.yellow(`Architecture change: ${swaps}`));
62
+ log.info(pc.dim("Data and schema are NOT migrated automatically. Migration checklist:"));
63
+ for (const step of plan.migrationSteps) log.info(pc.dim(` - ${step}`));
64
+ if (dryRun && plan.requiresArchitectureAck) log.warn(pc.yellow("Re-run with --acknowledge-architecture-change to apply this architecture change."));
65
+ }
58
66
  for (const blocker of plan.manualReviewBlockers) log.warn(pc.yellow(`Manual review: ${blocker}`));
59
67
  }
60
68
  function buildAddonSetupConfig(projectDir, projectName, currentConfig, plan, addonsToSetup) {
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { c as isSilent, m as readBtsConfig, r as exitCancelled } from "./errors-Cyol8zbN.mjs";
2
+ import { r as readBtsConfig } from "./bts-config-BceXPcpI.mjs";
3
+ import { c as isSilent, r as exitCancelled } from "./errors-ns_o2OKg.mjs";
3
4
  import { autocompleteMultiselect, group, isCancel, log, multiselect, select, spinner } from "@clack/prompts";
4
5
  import pc from "picocolors";
5
6
  import fs from "fs-extra";
@@ -396,6 +397,12 @@ function getRecommendedMcpServers(config) {
396
397
  name: "polar",
397
398
  target: "https://mcp.polar.sh/mcp/polar-mcp"
398
399
  });
400
+ if (config.payments === "revenuecat") servers.push({
401
+ key: "revenuecat",
402
+ label: "RevenueCat",
403
+ name: "revenuecat",
404
+ target: "https://mcp.revenuecat.ai/mcp"
405
+ });
399
406
  return servers;
400
407
  }
401
408
  function filterAgentsForScope(scope) {
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import "./bts-config-BceXPcpI.mjs";
3
+ import "./errors-ns_o2OKg.mjs";
4
+ import { t as setupAddons } from "./addons-setup-C_lxdJqU.mjs";
5
+
6
+ export { setupAddons };
@@ -1,14 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __reExport } from "./chunk-CCII7kTE.mjs";
3
- import { cancel } from "@clack/prompts";
4
- import pc from "picocolors";
5
3
  import fs from "fs-extra";
6
4
  import path from "node:path";
7
5
  import * as JSONC from "jsonc-parser";
8
6
  import { createCliDefaultProjectConfigBase } from "@better-fullstack/types";
9
7
  import { fileURLToPath } from "node:url";
10
- import { AsyncLocalStorage } from "node:async_hooks";
11
- import consola from "consola";
12
8
 
13
9
  //#region src/types.ts
14
10
  var types_exports = {};
@@ -462,6 +458,7 @@ function buildBtsConfigForPersistence(projectConfig, metadata = {}) {
462
458
  testing: persistedConfig.testing,
463
459
  packageManager: persistedConfig.packageManager,
464
460
  versionChannel: persistedConfig.versionChannel,
461
+ workspaceShape: persistedConfig.workspaceShape === "single-app" ? "single-app" : void 0,
465
462
  dbSetup: persistedConfig.dbSetup,
466
463
  api: persistedConfig.api,
467
464
  webDeploy: persistedConfig.webDeploy,
@@ -617,6 +614,7 @@ async function writeBtsConfig(projectConfig, metadata = {}) {
617
614
  testing: btsConfig.testing,
618
615
  packageManager: btsConfig.packageManager,
619
616
  versionChannel: btsConfig.versionChannel,
617
+ workspaceShape: btsConfig.workspaceShape,
620
618
  dbSetup: btsConfig.dbSetup,
621
619
  api: btsConfig.api,
622
620
  webDeploy: btsConfig.webDeploy,
@@ -814,83 +812,4 @@ async function updateBtsConfig(projectDir, updates) {
814
812
  }
815
813
 
816
814
  //#endregion
817
- //#region src/utils/context.ts
818
- const cliStorage = new AsyncLocalStorage();
819
- function defaultContext() {
820
- return {
821
- navigation: {
822
- isFirstPrompt: false,
823
- lastPromptShownUI: false
824
- },
825
- silent: false
826
- };
827
- }
828
- function getContext() {
829
- const ctx = cliStorage.getStore();
830
- if (!ctx) return defaultContext();
831
- return ctx;
832
- }
833
- function tryGetContext() {
834
- return cliStorage.getStore();
835
- }
836
- function isSilent() {
837
- return getContext().silent;
838
- }
839
- function isFirstPrompt() {
840
- return getContext().navigation.isFirstPrompt;
841
- }
842
- function didLastPromptShowUI() {
843
- return getContext().navigation.lastPromptShownUI;
844
- }
845
- function setIsFirstPrompt(value) {
846
- const ctx = tryGetContext();
847
- if (ctx) ctx.navigation.isFirstPrompt = value;
848
- }
849
- function setLastPromptShownUI(value) {
850
- const ctx = tryGetContext();
851
- if (ctx) ctx.navigation.lastPromptShownUI = value;
852
- }
853
- async function runWithContextAsync(options, fn) {
854
- const ctx = {
855
- navigation: {
856
- isFirstPrompt: false,
857
- lastPromptShownUI: false
858
- },
859
- silent: options.silent ?? false
860
- };
861
- return cliStorage.run(ctx, fn);
862
- }
863
-
864
- //#endregion
865
- //#region src/utils/errors.ts
866
- var UserCancelledError = class extends Error {
867
- constructor(message = "Operation cancelled") {
868
- super(message);
869
- this.name = "UserCancelledError";
870
- }
871
- };
872
- var CLIError = class extends Error {
873
- constructor(message) {
874
- super(message);
875
- this.name = "CLIError";
876
- }
877
- };
878
- function exitWithError(message) {
879
- if (isSilent()) throw new CLIError(message);
880
- consola.error(pc.red(message));
881
- process.exit(1);
882
- }
883
- function exitCancelled(message = "Operation cancelled") {
884
- if (isSilent()) throw new UserCancelledError(message);
885
- cancel(pc.red(message));
886
- process.exit(1);
887
- }
888
- function handleError(error, fallbackMessage) {
889
- const message = error instanceof Error ? error.message : fallbackMessage || String(error);
890
- if (isSilent()) throw error instanceof Error ? error : new Error(message);
891
- consola.error(pc.red(message));
892
- process.exit(1);
893
- }
894
-
895
- //#endregion
896
- export { types_exports as A, getEffectiveStack as C, getGraphSummary as D, getGraphPart as E, getPrimaryGraphPart as O, getUserPkgManager as S, getGraphBackendUrl as T, writeBtsConfig as _, handleError as a, DEFAULT_UI_LIBRARY_BY_FRONTEND as b, isSilent as c, setLastPromptShownUI as d, buildBtsConfigForPersistence as f, updateBtsConfig as g, readBtsConfigFromFile as h, exitWithError as i, hasGraphPart as k, runWithContextAsync as l, readBtsConfig as m, UserCancelledError as n, didLastPromptShowUI as o, previewBtsConfigUpdate as p, exitCancelled as r, isFirstPrompt as s, CLIError as t, setIsFirstPrompt as u, getLatestCLIVersion as v, getGraphBackendDeployInstructions as w, getDefaultConfig as x, DEFAULT_CONFIG as y };
815
+ export { getPrimaryGraphPart as _, updateBtsConfig as a, DEFAULT_CONFIG as c, getUserPkgManager as d, getEffectiveStack as f, getGraphSummary as g, getGraphPart as h, readBtsConfigFromFile as i, DEFAULT_UI_LIBRARY_BY_FRONTEND as l, getGraphBackendUrl as m, previewBtsConfigUpdate as n, writeBtsConfig as o, getGraphBackendDeployInstructions as p, readBtsConfig as r, getLatestCLIVersion as s, buildBtsConfigForPersistence as t, getDefaultConfig as u, hasGraphPart as v, types_exports as y };
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-CsW3i66c.mjs").then((m) => m.startMcpServer());
4
- else import("./run-_cf_sFwM.mjs").then((m) => m.createBtsCli().run());
3
+ if (process.argv[2] === "mcp" && process.argv.length === 3) import("./mcp-D9O5zgAA.mjs").then((m) => m.startMcpServer());
4
+ else import("./run-3AkXloH1.mjs").then((m) => m.createBtsCli().run());
5
5
 
6
6
  //#endregion
7
7
  export { };
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { A as types_exports } from "./errors-Cyol8zbN.mjs";
2
+ import { y as types_exports } from "./bts-config-BceXPcpI.mjs";
3
+ import path from "node:path";
4
+ import { cliInputToProjectConfigPartial } from "@better-fullstack/types/stack-translation";
3
5
 
4
6
  //#region src/utils/generate-reproducible-command.ts
5
7
  function getBaseCommand(packageManager) {
@@ -21,6 +23,7 @@ function appendCommonFlags(flags, config) {
21
23
  else flags.push("--ai-docs none");
22
24
  flags.push(config.git ? "--git" : "--no-git");
23
25
  flags.push(`--package-manager ${config.packageManager}`);
26
+ if (config.workspaceShape && config.workspaceShape !== "monorepo") flags.push(`--workspace-shape ${config.workspaceShape}`);
24
27
  if (config.versionChannel !== "stable") flags.push(`--version-channel ${config.versionChannel}`);
25
28
  flags.push(config.install ? "--install" : "--no-install");
26
29
  }
@@ -326,6 +329,7 @@ function getGoFlags(config) {
326
329
  function getJavaFlags(config) {
327
330
  const flags = ["--ecosystem java"];
328
331
  flags.push(`--java-web-framework ${config.javaWebFramework}`);
332
+ if (config.javaLanguage === "kotlin") flags.push(`--java-language ${config.javaLanguage}`);
329
333
  flags.push(`--java-build-tool ${config.javaBuildTool}`);
330
334
  flags.push(`--java-orm ${config.javaOrm}`);
331
335
  flags.push(`--java-auth ${config.javaAuth}`);
@@ -460,6 +464,21 @@ const TEMPLATE_PRESETS = {
460
464
  webDeploy: "none",
461
465
  serverDeploy: "none"
462
466
  },
467
+ saas: {
468
+ database: "postgres",
469
+ orm: "drizzle",
470
+ backend: "self",
471
+ runtime: "none",
472
+ frontend: ["next"],
473
+ api: "trpc",
474
+ auth: "better-auth-organizations",
475
+ payments: "creem",
476
+ addons: ["turborepo"],
477
+ examples: ["none"],
478
+ dbSetup: "none",
479
+ webDeploy: "none",
480
+ serverDeploy: "none"
481
+ },
463
482
  uniwind: {
464
483
  database: "none",
465
484
  orm: "none",
@@ -488,10 +507,46 @@ function getTemplateDescription(template) {
488
507
  mern: "MongoDB + Express + React + Node.js - Classic MERN stack",
489
508
  pern: "PostgreSQL + Express + React + Node.js - Popular PERN stack",
490
509
  t3: "T3 Stack - Next.js + tRPC + Prisma + PostgreSQL + Better Auth",
510
+ saas: "SaaS Starter - Next.js + tRPC + Drizzle + PostgreSQL + Better Auth Organizations + Creem billing",
491
511
  uniwind: "Expo + Uniwind native app with no backend services",
492
512
  none: "No template - Full customization"
493
513
  }[template] || "";
494
514
  }
495
515
 
496
516
  //#endregion
497
- export { getTemplateDescription as n, generateReproducibleCommand as r, getTemplateConfig as t };
517
+ //#region src/utils/config-processing.ts
518
+ function deriveProjectName(projectName, projectDirectory) {
519
+ if (projectName) return projectName;
520
+ if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
521
+ return "";
522
+ }
523
+ function processFlags(options, projectName) {
524
+ const derivedName = deriveProjectName(projectName, options.projectDirectory);
525
+ return cliInputToProjectConfigPartial(options, projectName || derivedName);
526
+ }
527
+ function applyEffectBackendDefaults(config, providedFlags = /* @__PURE__ */ new Set()) {
528
+ if (config.backend !== "effect") return config;
529
+ if (!providedFlags.has("effect")) config.effect = "effect-full";
530
+ if (!providedFlags.has("validation")) config.validation = "effect-schema";
531
+ return config;
532
+ }
533
+ function getProvidedFlags(options) {
534
+ return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
535
+ }
536
+ function validateNoneExclusivity(options, optionName) {
537
+ if (!options || options.length === 0) return;
538
+ if (options.includes("none") && options.length > 1) throw new Error(`Cannot combine 'none' with other ${optionName}.`);
539
+ }
540
+ function validateArrayOptions(options) {
541
+ validateNoneExclusivity(options.frontend, "frontend options");
542
+ validateNoneExclusivity(options.addons, "addons");
543
+ validateNoneExclusivity(options.examples, "examples");
544
+ validateNoneExclusivity(options.aiDocs, "ai docs");
545
+ validateNoneExclusivity(options.rustLibraries, "rust libraries");
546
+ validateNoneExclusivity(options.pythonAi, "python ai libraries");
547
+ validateNoneExclusivity(options.javaLibraries, "java libraries");
548
+ validateNoneExclusivity(options.javaTestingLibraries, "java testing libraries");
549
+ }
550
+
551
+ //#endregion
552
+ export { getTemplateConfig as a, validateArrayOptions as i, getProvidedFlags as n, getTemplateDescription as o, processFlags as r, generateReproducibleCommand as s, applyEffectBackendDefaults as t };
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as handleError, m as readBtsConfig } from "./errors-Cyol8zbN.mjs";
2
+ import { r as readBtsConfig } from "./bts-config-BceXPcpI.mjs";
3
3
  import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
4
- import { t as runGeneratedChecks } from "./generated-checks-C8hn9w2i.mjs";
4
+ import { a as handleError } from "./errors-ns_o2OKg.mjs";
5
+ import { t as runGeneratedChecks } from "./generated-checks-Dt4Xqp1x.mjs";
5
6
  import { intro, log, spinner } from "@clack/prompts";
6
7
  import pc from "picocolors";
7
8
  import fs from "fs-extra";
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { cancel } from "@clack/prompts";
3
+ import pc from "picocolors";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ import consola from "consola";
6
+
7
+ //#region src/utils/context.ts
8
+ const cliStorage = new AsyncLocalStorage();
9
+ function defaultContext() {
10
+ return {
11
+ navigation: {
12
+ isFirstPrompt: false,
13
+ lastPromptShownUI: false
14
+ },
15
+ silent: false
16
+ };
17
+ }
18
+ function getContext() {
19
+ const ctx = cliStorage.getStore();
20
+ if (!ctx) return defaultContext();
21
+ return ctx;
22
+ }
23
+ function tryGetContext() {
24
+ return cliStorage.getStore();
25
+ }
26
+ function isSilent() {
27
+ return getContext().silent;
28
+ }
29
+ function isFirstPrompt() {
30
+ return getContext().navigation.isFirstPrompt;
31
+ }
32
+ function didLastPromptShowUI() {
33
+ return getContext().navigation.lastPromptShownUI;
34
+ }
35
+ function setIsFirstPrompt(value) {
36
+ const ctx = tryGetContext();
37
+ if (ctx) ctx.navigation.isFirstPrompt = value;
38
+ }
39
+ function setLastPromptShownUI(value) {
40
+ const ctx = tryGetContext();
41
+ if (ctx) ctx.navigation.lastPromptShownUI = value;
42
+ }
43
+ async function runWithContextAsync(options, fn) {
44
+ const ctx = {
45
+ navigation: {
46
+ isFirstPrompt: false,
47
+ lastPromptShownUI: false
48
+ },
49
+ silent: options.silent ?? false
50
+ };
51
+ return cliStorage.run(ctx, fn);
52
+ }
53
+
54
+ //#endregion
55
+ //#region src/utils/errors.ts
56
+ var UserCancelledError = class extends Error {
57
+ constructor(message = "Operation cancelled") {
58
+ super(message);
59
+ this.name = "UserCancelledError";
60
+ }
61
+ };
62
+ var CLIError = class extends Error {
63
+ constructor(message) {
64
+ super(message);
65
+ this.name = "CLIError";
66
+ }
67
+ };
68
+ function exitWithError(message) {
69
+ if (isSilent()) throw new CLIError(message);
70
+ consola.error(pc.red(message));
71
+ process.exit(1);
72
+ }
73
+ function exitCancelled(message = "Operation cancelled") {
74
+ if (isSilent()) throw new UserCancelledError(message);
75
+ cancel(pc.red(message));
76
+ process.exit(1);
77
+ }
78
+ function handleError(error, fallbackMessage) {
79
+ const message = error instanceof Error ? error.message : fallbackMessage || String(error);
80
+ if (isSilent()) throw error instanceof Error ? error : new Error(message);
81
+ consola.error(pc.red(message));
82
+ process.exit(1);
83
+ }
84
+
85
+ //#endregion
86
+ export { handleError as a, isSilent as c, setLastPromptShownUI as d, exitWithError as i, runWithContextAsync as l, UserCancelledError as n, didLastPromptShowUI as o, exitCancelled as r, isFirstPrompt as s, CLIError as t, setIsFirstPrompt as u };
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { A as types_exports, c as isSilent, i as exitWithError, t as CLIError } from "./errors-Cyol8zbN.mjs";
2
+ import { y as types_exports } from "./bts-config-BceXPcpI.mjs";
3
+ import { c as isSilent, i as exitWithError, t as CLIError } from "./errors-ns_o2OKg.mjs";
3
4
  import pc from "picocolors";
4
5
  import z from "zod";
5
6
  import fs from "fs-extra";
@@ -59,6 +60,7 @@ const CreateCommandOptionsSchema = z.object({
59
60
  examples: z.array(types_exports.ExamplesSchema).optional(),
60
61
  git: z.boolean().optional(),
61
62
  packageManager: types_exports.PackageManagerSchema.optional(),
63
+ workspaceShape: types_exports.WorkspaceShapeSchema.optional().describe("Workspace layout: monorepo (default) or single-app (flat root app; only for a thin self app)"),
62
64
  install: z.boolean().optional(),
63
65
  versionChannel: types_exports.VersionChannelSchema.optional().describe("Dependency version channel (stable, latest, beta)"),
64
66
  dbSetup: types_exports.DatabaseSetupSchema.optional(),
@@ -121,6 +123,7 @@ const CreateCommandOptionsSchema = z.object({
121
123
  goConfig: types_exports.GoConfigSchema.optional().describe("Go config management (viper, koanf)"),
122
124
  goObservability: types_exports.GoObservabilitySchema.optional().describe("Go observability (opentelemetry)"),
123
125
  javaWebFramework: types_exports.JavaWebFrameworkSchema.optional().describe("Java web framework (spring-boot, quarkus, none)"),
126
+ javaLanguage: types_exports.JavaLanguageSchema.optional().describe("JVM language (java, kotlin)"),
124
127
  javaBuildTool: types_exports.JavaBuildToolSchema.optional().describe("Java build tool (maven, gradle, none)"),
125
128
  javaOrm: types_exports.JavaOrmSchema.optional().describe("Java ORM/database (spring-data-jpa)"),
126
129
  javaAuth: types_exports.JavaAuthSchema.optional().describe("Java auth (spring-security)"),
@@ -426,6 +429,10 @@ function validatePaymentsCompatibility(payments, auth, _backend, frontends = [])
426
429
  const { web } = splitFrontends$1(frontends);
427
430
  if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
428
431
  }
432
+ if (payments === "revenuecat") {
433
+ const { native } = splitFrontends$1(frontends);
434
+ if (native.length === 0) exitWithError("RevenueCat payments requires a native frontend. Please select a native frontend or choose a different payments provider.");
435
+ }
429
436
  }
430
437
  function validateExamplesCompatibility(examples, backend, frontend, runtime, ai) {
431
438
  const examplesArr = examples ?? [];
@@ -677,6 +684,14 @@ function validatePeerDependencies(config) {
677
684
 
678
685
  //#endregion
679
686
  //#region src/utils/config-validation.ts
687
+ const INTLAYER_COMPATIBLE_FRONTENDS = new Set([
688
+ "next",
689
+ "vinext",
690
+ "tanstack-router",
691
+ "tanstack-start",
692
+ "react-router",
693
+ "react-vite"
694
+ ]);
680
695
  function validateDatabaseOrmAuth(cfg, flags) {
681
696
  const db = cfg.database;
682
697
  const orm = cfg.orm;
@@ -938,7 +953,8 @@ function validateBackendNoneConstraints(config, providedFlags) {
938
953
  if (has("database") && config.database !== "none") exitWithError("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
939
954
  if (has("orm") && config.orm !== "none") exitWithError("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
940
955
  if (has("api") && config.api !== "none") exitWithError("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
941
- if (has("payments") && config.payments !== "none") exitWithError("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
956
+ const isNativeRevenueCat = config.payments === "revenuecat" && splitFrontends$1(config.frontend ?? []).native.length > 0;
957
+ if (has("payments") && config.payments !== "none" && !isNativeRevenueCat) exitWithError("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
942
958
  if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
943
959
  if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
944
960
  }
@@ -1019,23 +1035,44 @@ function validateApiConstraints(config, _options) {
1019
1035
  });
1020
1036
  if (!config.backend || ![
1021
1037
  "hono",
1038
+ "effect",
1022
1039
  "express",
1023
1040
  "fastify",
1024
1041
  "elysia"
1025
1042
  ].includes(config.backend)) incompatibilityError({
1026
- message: `${apiDisplayName} currently supports Hono, Express, Fastify, and Elysia backends.`,
1043
+ message: `${apiDisplayName} currently supports Hono, Effect, Express, Fastify, and Elysia backends.`,
1027
1044
  provided: {
1028
1045
  api: config.api,
1029
1046
  backend: config.backend ?? "none"
1030
1047
  },
1031
1048
  suggestions: [
1032
1049
  "Use --backend hono",
1050
+ "Use --backend effect",
1033
1051
  "Use --backend express",
1034
1052
  "Use --backend fastify",
1035
1053
  "Use --backend elysia"
1036
1054
  ]
1037
1055
  });
1038
1056
  }
1057
+ function validateEffectBackendConstraints(config) {
1058
+ if (config.backend !== "effect") return;
1059
+ if (config.effect !== "effect-full") missingRequirementError({
1060
+ message: "Effect backend requires Effect Platform + SQL services.",
1061
+ provided: {
1062
+ backend: "effect",
1063
+ effect: config.effect ?? "none"
1064
+ },
1065
+ suggestions: ["Use --effect effect-full"]
1066
+ });
1067
+ if (config.validation !== "effect-schema") missingRequirementError({
1068
+ message: "Effect backend requires Effect Schema validation.",
1069
+ provided: {
1070
+ backend: "effect",
1071
+ validation: config.validation ?? "none"
1072
+ },
1073
+ suggestions: ["Use --validation effect-schema"]
1074
+ });
1075
+ }
1039
1076
  function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set()) {
1040
1077
  if (config.ecosystem !== "java") return;
1041
1078
  const hasSpringBoot = config.javaWebFramework === "spring-boot";
@@ -1043,7 +1080,8 @@ function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set
1043
1080
  const hasNoBuildTool = config.javaBuildTool === "none";
1044
1081
  const hasJavaLibraries = (config.javaLibraries ?? []).some((library) => library !== "none");
1045
1082
  const hasJavaTestingLibraries = (config.javaTestingLibraries ?? []).some((library) => library !== "none");
1046
- const hasSpringOnlyFeatures = config.javaOrm !== "none" || config.javaAuth !== "none" || hasJavaLibraries;
1083
+ const hasJavaApi = (config.javaApi ?? "none") !== "none";
1084
+ const hasSpringOnlyFeatures = config.javaOrm !== "none" || config.javaAuth !== "none" || hasJavaLibraries || hasJavaApi;
1047
1085
  if (hasNoBuildTool && hasJavaWebFramework) incompatibilityError({
1048
1086
  message: "Java web frameworks require Maven or Gradle in the Java scaffold.",
1049
1087
  provided: {
@@ -1059,9 +1097,10 @@ function validateJavaConstraints(config, providedFlags = /* @__PURE__ */ new Set
1059
1097
  "java-build-tool": config.javaBuildTool ?? "none",
1060
1098
  "java-orm": config.javaOrm ?? "none",
1061
1099
  "java-auth": config.javaAuth ?? "none",
1100
+ "java-api": config.javaApi ?? "none",
1062
1101
  "java-libraries": (config.javaLibraries ?? []).join(" ") || "none"
1063
1102
  },
1064
- 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"]
1103
+ suggestions: ["Use --java-web-framework spring-boot and a real build tool for Spring features", "Clear --java-orm, --java-auth, --java-api, and --java-libraries when using plain Java or Quarkus"]
1065
1104
  });
1066
1105
  if (hasNoBuildTool && hasJavaTestingLibraries) incompatibilityError({
1067
1106
  message: "Java testing libraries require Maven or Gradle to manage test dependencies.",
@@ -1241,7 +1280,11 @@ function validateRateLimitConstraints(config) {
1241
1280
  function validateSearchConstraints(config) {
1242
1281
  if (!config.search || config.search === "none") return;
1243
1282
  const ecosystem = config.ecosystem ?? "typescript";
1244
- if (ecosystem !== "typescript" && config.search !== "meilisearch") incompatibilityError({
1283
+ const allowedSearch = {
1284
+ go: ["meilisearch", "bleve"],
1285
+ python: ["meilisearch", "elasticsearch"]
1286
+ }[ecosystem] ?? ["meilisearch"];
1287
+ if (ecosystem !== "typescript" && !allowedSearch.includes(config.search)) incompatibilityError({
1245
1288
  message: "Only Meilisearch search is available for non-TypeScript ecosystems.",
1246
1289
  provided: {
1247
1290
  ecosystem,
@@ -1288,6 +1331,19 @@ function validatePythonApiConstraints(config) {
1288
1331
  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"]
1289
1332
  });
1290
1333
  }
1334
+ function validateI18nConstraints(config) {
1335
+ if (config.i18n !== "intlayer") return;
1336
+ const { web } = splitFrontends$1(config.frontend ?? []);
1337
+ const unsupportedFrontend = web.find((frontend) => !INTLAYER_COMPATIBLE_FRONTENDS.has(frontend));
1338
+ if (web.length === 0 || unsupportedFrontend) incompatibilityError({
1339
+ message: "Intlayer i18n is currently wired only for Next.js, Vinext, TanStack Router/Start, React Router, and React + Vite frontends.",
1340
+ provided: {
1341
+ frontend: web.join(" ") || "none",
1342
+ i18n: "intlayer"
1343
+ },
1344
+ suggestions: ["Use --frontend next, vinext, tanstack-router, tanstack-start, react-router, or react-vite", "Set --i18n none or choose another i18n provider for this frontend"]
1345
+ });
1346
+ }
1291
1347
  function validateFullConfig(config, providedFlags, options) {
1292
1348
  if (config.stackParts && !options.yolo) {
1293
1349
  const graphValidation = (0, types_exports.validateStackParts)(config.stackParts);
@@ -1302,6 +1358,7 @@ function validateFullConfig(config, providedFlags, options) {
1302
1358
  validateEncoreConstraints(config, providedFlags);
1303
1359
  validateAdonisJSConstraints(config, providedFlags);
1304
1360
  validateBackendConstraints(config, providedFlags, options);
1361
+ validateEffectBackendConstraints(config);
1305
1362
  validateFrontendConstraints(config, providedFlags);
1306
1363
  validateApiConstraints(config, options);
1307
1364
  validatePythonApiConstraints(config);
@@ -1312,6 +1369,7 @@ function validateFullConfig(config, providedFlags, options) {
1312
1369
  validateSearchConstraints(config);
1313
1370
  validateJavaConstraints(config, providedFlags);
1314
1371
  validateElixirConstraints(config);
1372
+ validateI18nConstraints(config);
1315
1373
  const hasGraphBackend = config.stackParts?.some((part) => part.role === "backend" && !part.ownerPartId && part.source !== "provided" && part.ecosystem !== "typescript" && part.ecosystem !== "react-native" && part.ecosystem !== "universal");
1316
1374
  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));
1317
1375
  validateSelfBackendCompatibility(providedFlags, options, config);
@@ -1368,6 +1426,7 @@ function validateConfigForProgrammaticUse(config) {
1368
1426
  }
1369
1427
  validateEcosystemAuthCompatibility(config);
1370
1428
  validateDatabaseOrmAuth(config);
1429
+ validateEffectBackendConstraints(config);
1371
1430
  if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
1372
1431
  validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
1373
1432
  validatePythonApiConstraints(config);
@@ -1378,6 +1437,7 @@ function validateConfigForProgrammaticUse(config) {
1378
1437
  validateSearchConstraints(config);
1379
1438
  validateJavaConstraints(config);
1380
1439
  validateElixirConstraints(config);
1440
+ validateI18nConstraints(config);
1381
1441
  validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
1382
1442
  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);
1383
1443
  validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);