@better-fullstack/types 1.3.17 → 1.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +198 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2349 -2
- package/dist/index.mjs.map +1 -0
- package/dist/json-schema.d.mts +574 -0
- package/dist/json-schema.d.mts.map +1 -1
- package/dist/json-schema.mjs +1 -1
- package/dist/{schemas-j7_vA7t5.mjs → schemas-CdFUciyW.mjs} +102 -3
- package/dist/schemas-CdFUciyW.mjs.map +1 -0
- package/dist/{schemas-Cuius1p4.d.mts → schemas-DGofeXE2.d.mts} +509 -5
- package/dist/schemas-DGofeXE2.d.mts.map +1 -0
- package/dist/schemas.d.mts +2 -2
- package/dist/schemas.mjs +2 -2
- package/package.json +1 -1
- package/dist/schemas-Cuius1p4.d.mts.map +0 -1
- package/dist/schemas-j7_vA7t5.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,2350 @@
|
|
|
1
|
-
import { $ as FrontendSchema, $t as SEARCH_VALUES, A as DATABASE_SETUP_VALUES, At as ProjectNameSchema, B as EcosystemSchema, Bt as RUST_CLI_VALUES, C as CLIInputSchema, Ct as PYTHON_QUALITY_VALUES, D as CSS_FRAMEWORK_VALUES, Dt as PackageManagerSchema, E as CSSFrameworkSchema, Et as PYTHON_WEB_FRAMEWORK_VALUES, F as DirectoryConflictSchema, Ft as PythonValidationSchema, G as FILE_STORAGE_VALUES, Gt as RealtimeSchema, H as EmailSchema, Ht as RUST_LIBRARIES_VALUES, I as ECOSYSTEM_VALUES, It as PythonWebFrameworkSchema, J as FRONTEND_VALUES, Jt as RustCliSchema, K as FILE_UPLOAD_VALUES, Kt as RuntimeSchema, L as EFFECT_VALUES, Lt as REALTIME_VALUES, M as DIRECTORY_CONFLICT_VALUES, Mt as PythonOrmSchema, N as DatabaseSchema, Nt as PythonQualitySchema, O as CachingSchema, Ot as PaymentsSchema, P as DatabaseSetupSchema, Pt as PythonTaskQueueSchema, Q as FormsSchema, Qt as RustWebFrameworkSchema, R as EMAIL_VALUES, Rt as RUNTIME_VALUES, S as CACHING_VALUES, St as PYTHON_ORM_VALUES, T as CMS_VALUES, Tt as PYTHON_VALIDATION_VALUES, U as ExamplesSchema, Ut as RUST_ORM_VALUES, V as EffectSchema, Vt as RUST_FRONTEND_VALUES, W as FEATURE_FLAGS_VALUES, Wt as RUST_WEB_FRAMEWORK_VALUES, X as FileStorageSchema, Xt as RustLibrariesSchema, Y as FeatureFlagsSchema, Yt as RustFrontendSchema, Z as FileUploadSchema, Zt as RustOrmSchema, _ as AuthSchema, _t as ORM_VALUES, a as ANALYTICS_VALUES, an as
|
|
1
|
+
import { $ as FrontendSchema, $t as SEARCH_VALUES, A as DATABASE_SETUP_VALUES, At as ProjectNameSchema, B as EcosystemSchema, Bt as RUST_CLI_VALUES, C as CLIInputSchema, Cn as UILibrarySchema, Ct as PYTHON_QUALITY_VALUES, D as CSS_FRAMEWORK_VALUES, Dn as WEB_DEPLOY_VALUES, Dt as PackageManagerSchema, E as CSSFrameworkSchema, En as ValidationSchema, Et as PYTHON_WEB_FRAMEWORK_VALUES, F as DirectoryConflictSchema, Ft as PythonValidationSchema, G as FILE_STORAGE_VALUES, Gt as RealtimeSchema, H as EmailSchema, Ht as RUST_LIBRARIES_VALUES, I as ECOSYSTEM_VALUES, It as PythonWebFrameworkSchema, J as FRONTEND_VALUES, Jt as RustCliSchema, K as FILE_UPLOAD_VALUES, Kt as RuntimeSchema, L as EFFECT_VALUES, Lt as REALTIME_VALUES, M as DIRECTORY_CONFLICT_VALUES, Mt as PythonOrmSchema, N as DatabaseSchema, Nt as PythonQualitySchema, O as CachingSchema, On as WebDeploySchema, Ot as PaymentsSchema, P as DatabaseSetupSchema, Pt as PythonTaskQueueSchema, Q as FormsSchema, Qt as RustWebFrameworkSchema, R as EMAIL_VALUES, Rt as RUNTIME_VALUES, S as CACHING_VALUES, Sn as TestingSchema, St as PYTHON_ORM_VALUES, T as CMS_VALUES, Tn as VALIDATION_VALUES, Tt as PYTHON_VALIDATION_VALUES, U as ExamplesSchema, Ut as RUST_ORM_VALUES, V as EffectSchema, Vt as RUST_FRONTEND_VALUES, W as FEATURE_FLAGS_VALUES, Wt as RUST_WEB_FRAMEWORK_VALUES, X as FileStorageSchema, Xt as RustLibrariesSchema, Y as FeatureFlagsSchema, Yt as RustFrontendSchema, Z as FileUploadSchema, Zt as RustOrmSchema, _ as AuthSchema, _n as ShadcnStyleSchema, _t as ORM_VALUES, a as ANALYTICS_VALUES, an as SHADCN_ICON_LIBRARY_VALUES, at as GoApiSchema, b as BetterTStackConfigFileSchema, bn as TESTING_VALUES, bt as PAYMENTS_VALUES, c as API_VALUES, cn as STATE_MANAGEMENT_VALUES, ct as GoOrmSchema, d as AddInputSchema, dn as ShadcnBaseColorSchema, dt as JOB_QUEUE_VALUES, en as SERVER_DEPLOY_VALUES, et as GO_API_VALUES, f as AddonsSchema, fn as ShadcnBaseSchema, ft as JobQueueSchema, g as AstroIntegrationSchema, gn as ShadcnRadiusSchema, gt as ORMSchema, h as AnimationSchema, hn as ShadcnIconLibrarySchema, ht as OBSERVABILITY_VALUES, i as AI_VALUES, in as SHADCN_FONT_VALUES, it as GO_WEB_FRAMEWORK_VALUES, j as DATABASE_VALUES, jt as PythonAiSchema, k as CreateInputSchema, kt as ProjectConfigSchema, l as ASTRO_INTEGRATION_VALUES, ln as SearchSchema, lt as GoWebFrameworkSchema, m as AnalyticsSchema, mn as ShadcnFontSchema, mt as LoggingSchema, n as AISchema, nn as SHADCN_BASE_VALUES, nt as GO_LOGGING_VALUES, o as ANIMATION_VALUES, on as SHADCN_RADIUS_VALUES, ot as GoCliSchema, p as AiDocsSchema, pn as ShadcnColorThemeSchema, pt as LOGGING_VALUES, q as FORMS_VALUES, qt as RustApiSchema, r as AI_DOCS_VALUES, rn as SHADCN_COLOR_THEME_VALUES, rt as GO_ORM_VALUES, s as APISchema, sn as SHADCN_STYLE_VALUES, st as GoLoggingSchema, t as ADDONS_VALUES, tn as SHADCN_BASE_COLOR_VALUES, tt as GO_CLI_VALUES, u as AUTH_VALUES, un as ServerDeploySchema, ut as InitResultSchema, v as BACKEND_VALUES, vn as StateManagementSchema, vt as ObservabilitySchema, w as CMSSchema, wn as UI_LIBRARY_VALUES, wt as PYTHON_TASK_QUEUE_VALUES, x as BetterTStackConfigSchema, xn as TemplateSchema, xt as PYTHON_AI_VALUES, y as BackendSchema, yn as TEMPLATE_VALUES, yt as PACKAGE_MANAGER_VALUES, z as EXAMPLES_VALUES, zt as RUST_API_VALUES } from "./schemas-CdFUciyW.mjs";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
//#region src/capabilities.ts
|
|
4
|
+
const CAPABILITY_DEFINITIONS = { auth: [
|
|
5
|
+
{
|
|
6
|
+
id: "better-auth",
|
|
7
|
+
label: "Better-Auth",
|
|
8
|
+
description: "The most comprehensive authentication framework for TypeScript",
|
|
9
|
+
promptHint: "comprehensive auth framework for TypeScript",
|
|
10
|
+
icon: "/icon/better-auth.svg",
|
|
11
|
+
color: "from-green-400 to-green-600",
|
|
12
|
+
default: true
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "go-better-auth",
|
|
16
|
+
label: "GoBetterAuth",
|
|
17
|
+
description: "Embedded auth routes for Go applications",
|
|
18
|
+
promptHint: "embedded auth routes for Go applications",
|
|
19
|
+
icon: "https://cdn.simpleicons.org/go/00ADD8",
|
|
20
|
+
color: "from-cyan-400 to-sky-600"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "clerk",
|
|
24
|
+
label: "Clerk",
|
|
25
|
+
description: "More than authentication, Complete User Management",
|
|
26
|
+
promptHint: "More than auth, Complete User Management",
|
|
27
|
+
icon: "https://cdn.simpleicons.org/clerk/6C47FF",
|
|
28
|
+
color: "from-blue-400 to-blue-600"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "nextauth",
|
|
32
|
+
label: "Auth.js (NextAuth)",
|
|
33
|
+
description: "Open source authentication for Next.js",
|
|
34
|
+
promptHint: "Authentication for Next.js (formerly NextAuth.js)",
|
|
35
|
+
icon: "/icon/nextauth.png",
|
|
36
|
+
color: "from-orange-400 to-orange-600"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "stack-auth",
|
|
40
|
+
label: "Stack Auth",
|
|
41
|
+
description: "Open-source Auth0/Clerk alternative with user management",
|
|
42
|
+
promptHint: "Open-source Auth0/Clerk alternative",
|
|
43
|
+
icon: "/icon/stack-auth.svg",
|
|
44
|
+
color: "from-purple-400 to-purple-600"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "supabase-auth",
|
|
48
|
+
label: "Supabase Auth",
|
|
49
|
+
description: "Open-source Auth with Supabase platform integration",
|
|
50
|
+
promptHint: "Auth with Supabase platform integration",
|
|
51
|
+
icon: "https://cdn.simpleicons.org/supabase/3FCF8E",
|
|
52
|
+
color: "from-emerald-400 to-emerald-600"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "auth0",
|
|
56
|
+
label: "Auth0",
|
|
57
|
+
description: "Flexible identity platform for authentication and authorization",
|
|
58
|
+
promptHint: "Flexible identity platform for authentication",
|
|
59
|
+
icon: "https://cdn.simpleicons.org/auth0/EB5424",
|
|
60
|
+
color: "from-orange-400 to-orange-600"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "none",
|
|
64
|
+
label: "No Auth",
|
|
65
|
+
description: "Skip authentication",
|
|
66
|
+
promptHint: "No authentication",
|
|
67
|
+
icon: "",
|
|
68
|
+
color: "from-red-400 to-red-600"
|
|
69
|
+
}
|
|
70
|
+
] };
|
|
71
|
+
const NATIVE_FRONTENDS = new Set([
|
|
72
|
+
"native-bare",
|
|
73
|
+
"native-uniwind",
|
|
74
|
+
"native-unistyles"
|
|
75
|
+
]);
|
|
76
|
+
const CONVEX_BETTER_AUTH_WEB = new Set([
|
|
77
|
+
"tanstack-router",
|
|
78
|
+
"tanstack-start",
|
|
79
|
+
"next"
|
|
80
|
+
]);
|
|
81
|
+
const CONVEX_CLERK_WEB = new Set([
|
|
82
|
+
"react-router",
|
|
83
|
+
"tanstack-router",
|
|
84
|
+
"tanstack-start",
|
|
85
|
+
"next"
|
|
86
|
+
]);
|
|
87
|
+
function capitalizeFirst(value) {
|
|
88
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
89
|
+
}
|
|
90
|
+
function dedupe(values) {
|
|
91
|
+
return [...new Set(values)];
|
|
92
|
+
}
|
|
93
|
+
function getFrontendSets(context) {
|
|
94
|
+
if (context.frontend) {
|
|
95
|
+
const webFrontend = context.frontend.filter((frontend) => !NATIVE_FRONTENDS.has(frontend));
|
|
96
|
+
const nativeFrontend = context.frontend.filter((frontend) => NATIVE_FRONTENDS.has(frontend));
|
|
97
|
+
return {
|
|
98
|
+
webFrontend: dedupe(webFrontend),
|
|
99
|
+
nativeFrontend: dedupe(nativeFrontend)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
webFrontend: dedupe(context.webFrontend ?? []),
|
|
104
|
+
nativeFrontend: dedupe(context.nativeFrontend ?? [])
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function isSelfBackend(backend) {
|
|
108
|
+
return backend === "self" || backend?.startsWith("self-") === true;
|
|
109
|
+
}
|
|
110
|
+
function getNextOnlyAuthLabel(optionId) {
|
|
111
|
+
switch (optionId) {
|
|
112
|
+
case "nextauth": return "Auth.js (NextAuth)";
|
|
113
|
+
case "stack-auth": return "Stack Auth";
|
|
114
|
+
case "supabase-auth": return "Supabase Auth";
|
|
115
|
+
case "auth0": return "Auth0";
|
|
116
|
+
default: {
|
|
117
|
+
const _exhaustive = optionId;
|
|
118
|
+
return String(_exhaustive);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function getAuthDisabledReason(context, optionId) {
|
|
123
|
+
if (optionId === "none") return null;
|
|
124
|
+
const ecosystem = context.ecosystem ?? "typescript";
|
|
125
|
+
const backend = context.backend;
|
|
126
|
+
const { webFrontend, nativeFrontend } = getFrontendSets(context);
|
|
127
|
+
const hasNextJs = webFrontend.includes("next");
|
|
128
|
+
const hasTanStackStart = webFrontend.includes("tanstack-start");
|
|
129
|
+
const hasNativeFrontend = nativeFrontend.some((frontend) => frontend !== "none");
|
|
130
|
+
if (optionId === "go-better-auth") return ecosystem === "go" ? null : "GoBetterAuth is available only for Go stacks";
|
|
131
|
+
if (ecosystem === "go") return "Go stacks currently support GoBetterAuth only";
|
|
132
|
+
if (ecosystem !== "typescript") return `${capitalizeFirst(ecosystem)} stacks do not support auth integrations yet`;
|
|
133
|
+
if (backend === "none") return "No backend selected";
|
|
134
|
+
if (optionId === "better-auth") {
|
|
135
|
+
if (backend === "convex") {
|
|
136
|
+
if (!(webFrontend.some((frontend) => CONVEX_BETTER_AUTH_WEB.has(frontend)) || nativeFrontend.some((frontend) => NATIVE_FRONTENDS.has(frontend)))) return "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native";
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (optionId === "clerk") {
|
|
141
|
+
if (backend === "convex") {
|
|
142
|
+
if (!(webFrontend.some((frontend) => CONVEX_CLERK_WEB.has(frontend)) || nativeFrontend.some((frontend) => NATIVE_FRONTENDS.has(frontend)))) return "Clerk with Convex requires React Router, TanStack Router, TanStack Start, Next.js, or React Native";
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (isSelfBackend(backend)) {
|
|
146
|
+
if ((hasNextJs || hasTanStackStart) && hasNativeFrontend) return "In Better-Fullstack, Clerk with self backend is currently supported only for web-only Next.js or TanStack Start projects (no native companion app)";
|
|
147
|
+
if (hasNextJs || hasTanStackStart) return null;
|
|
148
|
+
if (backend === "self-astro" || webFrontend.includes("astro")) return "In Better-Fullstack, Clerk is not yet supported for Astro fullstack projects";
|
|
149
|
+
if (backend === "self-nuxt" || webFrontend.includes("nuxt")) return "In Better-Fullstack, Clerk is not yet supported for Nuxt fullstack projects";
|
|
150
|
+
if (backend === "self-svelte" || webFrontend.includes("svelte")) return "In Better-Fullstack, Clerk is not yet supported for SvelteKit fullstack projects";
|
|
151
|
+
if (backend === "self-solid-start" || webFrontend.includes("solid-start")) return "In Better-Fullstack, Clerk is not yet supported for SolidStart fullstack projects";
|
|
152
|
+
return "In Better-Fullstack, Clerk is currently supported with Convex, Next.js fullstack, or TanStack Start fullstack";
|
|
153
|
+
}
|
|
154
|
+
return "In Better-Fullstack, Clerk is currently supported with Convex, Next.js fullstack, or TanStack Start fullstack";
|
|
155
|
+
}
|
|
156
|
+
const nextOnlyLabel = getNextOnlyAuthLabel(optionId);
|
|
157
|
+
if (backend !== "self" && backend !== "self-next") return `In Better-Fullstack, ${nextOnlyLabel} is currently supported only with the 'self' backend (fullstack Next.js)`;
|
|
158
|
+
if (!hasNextJs) return `In Better-Fullstack, ${nextOnlyLabel} currently requires the Next.js frontend`;
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
function getCapabilityDefinitions(capability) {
|
|
162
|
+
return CAPABILITY_DEFINITIONS[capability];
|
|
163
|
+
}
|
|
164
|
+
function getCapabilityDisabledReason(capability, context, optionId) {
|
|
165
|
+
if (capability === "auth") return getAuthDisabledReason(context, optionId);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function getSupportedCapabilityOptions(capability, context) {
|
|
169
|
+
return getCapabilityDefinitions(capability).filter((definition) => getCapabilityDisabledReason(capability, context, definition.id) === null);
|
|
170
|
+
}
|
|
171
|
+
function normalizeCapabilitySelection(capability, context, optionId) {
|
|
172
|
+
const fallbackValue = "none";
|
|
173
|
+
if (!optionId || optionId === fallbackValue) return {
|
|
174
|
+
value: optionId ?? fallbackValue,
|
|
175
|
+
normalized: false,
|
|
176
|
+
reason: null,
|
|
177
|
+
message: null
|
|
178
|
+
};
|
|
179
|
+
const reason = getCapabilityDisabledReason(capability, context, optionId);
|
|
180
|
+
if (!reason) return {
|
|
181
|
+
value: optionId,
|
|
182
|
+
normalized: false,
|
|
183
|
+
reason: null,
|
|
184
|
+
message: null
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
value: fallbackValue,
|
|
188
|
+
normalized: true,
|
|
189
|
+
reason,
|
|
190
|
+
message: `${capitalizeFirst(capability)} set to 'None' (${reason})`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/compatibility.ts
|
|
196
|
+
const CATEGORY_ORDER = [
|
|
197
|
+
...[
|
|
198
|
+
"webFrontend",
|
|
199
|
+
"nativeFrontend",
|
|
200
|
+
"astroIntegration",
|
|
201
|
+
"cssFramework",
|
|
202
|
+
"uiLibrary",
|
|
203
|
+
"backend",
|
|
204
|
+
"backendLibraries",
|
|
205
|
+
"runtime",
|
|
206
|
+
"api",
|
|
207
|
+
"database",
|
|
208
|
+
"orm",
|
|
209
|
+
"dbSetup",
|
|
210
|
+
"webDeploy",
|
|
211
|
+
"serverDeploy",
|
|
212
|
+
"auth",
|
|
213
|
+
"payments",
|
|
214
|
+
"email",
|
|
215
|
+
"fileUpload",
|
|
216
|
+
"logging",
|
|
217
|
+
"observability",
|
|
218
|
+
"featureFlags",
|
|
219
|
+
"analytics",
|
|
220
|
+
"ai",
|
|
221
|
+
"stateManagement",
|
|
222
|
+
"forms",
|
|
223
|
+
"validation",
|
|
224
|
+
"testing",
|
|
225
|
+
"realtime",
|
|
226
|
+
"jobQueue",
|
|
227
|
+
"caching",
|
|
228
|
+
"search",
|
|
229
|
+
"fileStorage",
|
|
230
|
+
"animation",
|
|
231
|
+
"cms",
|
|
232
|
+
"codeQuality",
|
|
233
|
+
"documentation",
|
|
234
|
+
"appPlatforms",
|
|
235
|
+
"packageManager",
|
|
236
|
+
"examples",
|
|
237
|
+
"aiDocs",
|
|
238
|
+
"git",
|
|
239
|
+
"install"
|
|
240
|
+
],
|
|
241
|
+
"rustWebFramework",
|
|
242
|
+
"rustFrontend",
|
|
243
|
+
"rustOrm",
|
|
244
|
+
"rustApi",
|
|
245
|
+
"rustCli",
|
|
246
|
+
"rustLibraries",
|
|
247
|
+
"pythonWebFramework",
|
|
248
|
+
"pythonOrm",
|
|
249
|
+
"pythonValidation",
|
|
250
|
+
"pythonAi",
|
|
251
|
+
"pythonTaskQueue",
|
|
252
|
+
"pythonQuality",
|
|
253
|
+
"goWebFramework",
|
|
254
|
+
"goOrm",
|
|
255
|
+
"goApi",
|
|
256
|
+
"goCli",
|
|
257
|
+
"goLogging"
|
|
258
|
+
];
|
|
259
|
+
const DEFAULT_RUNTIME = "bun";
|
|
260
|
+
function validateProjectName(name) {
|
|
261
|
+
const INVALID_CHARS = [
|
|
262
|
+
"<",
|
|
263
|
+
">",
|
|
264
|
+
":",
|
|
265
|
+
"\"",
|
|
266
|
+
"|",
|
|
267
|
+
"?",
|
|
268
|
+
"*"
|
|
269
|
+
];
|
|
270
|
+
const MAX_LENGTH = 255;
|
|
271
|
+
if (name === ".") return void 0;
|
|
272
|
+
if (!name) return "Project name cannot be empty";
|
|
273
|
+
if (name.length > MAX_LENGTH) return `Project name must be less than ${MAX_LENGTH} characters`;
|
|
274
|
+
if (INVALID_CHARS.some((char) => name.includes(char))) return "Project name contains invalid characters";
|
|
275
|
+
if (name.startsWith(".") || name.startsWith("-")) return "Project name cannot start with a dot or dash";
|
|
276
|
+
if (name.toLowerCase() === "node_modules" || name.toLowerCase() === "favicon.ico") return "Project name is reserved";
|
|
277
|
+
}
|
|
278
|
+
const hasPWACompatibleFrontend = (webFrontend) => webFrontend.some((f) => [
|
|
279
|
+
"tanstack-router",
|
|
280
|
+
"react-router",
|
|
281
|
+
"solid",
|
|
282
|
+
"next",
|
|
283
|
+
"astro"
|
|
284
|
+
].includes(f));
|
|
285
|
+
const hasTauriCompatibleFrontend = (webFrontend) => webFrontend.some((f) => [
|
|
286
|
+
"tanstack-router",
|
|
287
|
+
"react-router",
|
|
288
|
+
"nuxt",
|
|
289
|
+
"svelte",
|
|
290
|
+
"solid",
|
|
291
|
+
"next",
|
|
292
|
+
"astro"
|
|
293
|
+
].includes(f));
|
|
294
|
+
const isChatSdkExampleSupported = (stack) => {
|
|
295
|
+
if (stack.ecosystem !== "typescript") return false;
|
|
296
|
+
if (stack.backend === "self-next" || stack.backend === "self-tanstack-start") return true;
|
|
297
|
+
if (stack.backend === "self-nuxt") return true;
|
|
298
|
+
if (stack.backend === "hono") return stack.runtime === "node";
|
|
299
|
+
return false;
|
|
300
|
+
};
|
|
301
|
+
const requiresChatSdkVercelAI = (stack) => {
|
|
302
|
+
return stack.examples.includes("chat-sdk") && (stack.backend === "self-nuxt" || stack.backend === "hono" && stack.runtime === "node");
|
|
303
|
+
};
|
|
304
|
+
const getCategoryDisplayName = (categoryKey) => {
|
|
305
|
+
const rustCategoryNames = {
|
|
306
|
+
rustWebFramework: "Rust Web Framework",
|
|
307
|
+
rustFrontend: "Rust Frontend (WASM)",
|
|
308
|
+
rustOrm: "Rust ORM / Database",
|
|
309
|
+
rustApi: "Rust API Layer",
|
|
310
|
+
rustCli: "Rust CLI Tools",
|
|
311
|
+
rustLibraries: "Rust Core Libraries"
|
|
312
|
+
};
|
|
313
|
+
const pythonCategoryNames = {
|
|
314
|
+
pythonWebFramework: "Python Web Framework",
|
|
315
|
+
pythonOrm: "Python ORM / Database",
|
|
316
|
+
pythonValidation: "Python Validation",
|
|
317
|
+
pythonAi: "Python AI / ML",
|
|
318
|
+
pythonTaskQueue: "Python Task Queue",
|
|
319
|
+
pythonQuality: "Python Code Quality"
|
|
320
|
+
};
|
|
321
|
+
const goCategoryNames = {
|
|
322
|
+
goWebFramework: "Go Web Framework",
|
|
323
|
+
goOrm: "Go ORM / Database",
|
|
324
|
+
goApi: "Go API Layer",
|
|
325
|
+
goCli: "Go CLI Tools",
|
|
326
|
+
goLogging: "Go Logging"
|
|
327
|
+
};
|
|
328
|
+
if (rustCategoryNames[categoryKey]) return rustCategoryNames[categoryKey];
|
|
329
|
+
if (pythonCategoryNames[categoryKey]) return pythonCategoryNames[categoryKey];
|
|
330
|
+
if (goCategoryNames[categoryKey]) return goCategoryNames[categoryKey];
|
|
331
|
+
const result = categoryKey.replace(/([A-Z])/g, " $1");
|
|
332
|
+
return result.charAt(0).toUpperCase() + result.slice(1);
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Analyzes the stack and auto-adjusts incompatible selections.
|
|
336
|
+
* This follows the CLI approach: when you make a selection, dependent items adjust automatically.
|
|
337
|
+
* The flow is: frontend -> backend -> runtime -> database -> orm -> api -> auth -> etc.
|
|
338
|
+
*/
|
|
339
|
+
const analyzeStackCompatibility = (stack) => {
|
|
340
|
+
if (stack.yolo === "true") return {
|
|
341
|
+
adjustedStack: null,
|
|
342
|
+
notes: {},
|
|
343
|
+
changes: []
|
|
344
|
+
};
|
|
345
|
+
const nextStack = { ...stack };
|
|
346
|
+
let changed = false;
|
|
347
|
+
const notes = {};
|
|
348
|
+
const changes = [];
|
|
349
|
+
for (const cat of CATEGORY_ORDER) notes[cat] = {
|
|
350
|
+
notes: [],
|
|
351
|
+
hasIssue: false
|
|
352
|
+
};
|
|
353
|
+
if (nextStack.backend === "convex") {
|
|
354
|
+
for (const [key, value] of Object.entries({
|
|
355
|
+
runtime: "none",
|
|
356
|
+
database: "none",
|
|
357
|
+
orm: "none",
|
|
358
|
+
api: "none",
|
|
359
|
+
dbSetup: "none",
|
|
360
|
+
serverDeploy: "none",
|
|
361
|
+
search: "none",
|
|
362
|
+
fileStorage: "none"
|
|
363
|
+
})) {
|
|
364
|
+
const catKey = key;
|
|
365
|
+
if (nextStack[catKey] !== value) {
|
|
366
|
+
nextStack[catKey] = value;
|
|
367
|
+
changed = true;
|
|
368
|
+
changes.push({
|
|
369
|
+
category: "backend",
|
|
370
|
+
message: `${getCategoryDisplayName(catKey)} set to '${value}' (Convex provides this)`
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (nextStack.webFrontend.includes("solid")) {
|
|
375
|
+
nextStack.webFrontend = nextStack.webFrontend.filter((f) => f !== "solid");
|
|
376
|
+
if (nextStack.webFrontend.length === 0) nextStack.webFrontend = ["none"];
|
|
377
|
+
changed = true;
|
|
378
|
+
changes.push({
|
|
379
|
+
category: "backend",
|
|
380
|
+
message: "Removed Solid (incompatible with Convex)"
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (nextStack.webFrontend.includes("solid-start")) {
|
|
384
|
+
nextStack.webFrontend = nextStack.webFrontend.filter((f) => f !== "solid-start");
|
|
385
|
+
if (nextStack.webFrontend.length === 0) nextStack.webFrontend = ["none"];
|
|
386
|
+
changed = true;
|
|
387
|
+
changes.push({
|
|
388
|
+
category: "backend",
|
|
389
|
+
message: "Removed SolidStart (incompatible with Convex)"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (nextStack.webFrontend.includes("astro")) {
|
|
393
|
+
nextStack.webFrontend = nextStack.webFrontend.filter((f) => f !== "astro");
|
|
394
|
+
if (nextStack.webFrontend.length === 0) nextStack.webFrontend = ["none"];
|
|
395
|
+
nextStack.astroIntegration = "none";
|
|
396
|
+
changed = true;
|
|
397
|
+
changes.push({
|
|
398
|
+
category: "backend",
|
|
399
|
+
message: "Removed Astro (incompatible with Convex)"
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
if (nextStack.examples.includes("ai")) {
|
|
403
|
+
if (nextStack.webFrontend.some((f) => [
|
|
404
|
+
"solid",
|
|
405
|
+
"svelte",
|
|
406
|
+
"nuxt"
|
|
407
|
+
].includes(f))) {
|
|
408
|
+
nextStack.examples = nextStack.examples.filter((e) => e !== "ai");
|
|
409
|
+
if (nextStack.examples.length === 0) nextStack.examples = ["none"];
|
|
410
|
+
changed = true;
|
|
411
|
+
changes.push({
|
|
412
|
+
category: "examples",
|
|
413
|
+
message: "AI example removed (Convex AI only supports React-based frontends)"
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (nextStack.backend === "none") {
|
|
419
|
+
const noneOverrides = {
|
|
420
|
+
runtime: "none",
|
|
421
|
+
database: "none",
|
|
422
|
+
orm: "none",
|
|
423
|
+
api: "none",
|
|
424
|
+
dbSetup: "none",
|
|
425
|
+
serverDeploy: "none",
|
|
426
|
+
payments: "none",
|
|
427
|
+
search: "none",
|
|
428
|
+
fileStorage: "none"
|
|
429
|
+
};
|
|
430
|
+
if (nextStack.ecosystem !== "go") noneOverrides.auth = "none";
|
|
431
|
+
for (const [key, value] of Object.entries(noneOverrides)) {
|
|
432
|
+
const catKey = key;
|
|
433
|
+
if (nextStack[catKey] !== value) {
|
|
434
|
+
nextStack[catKey] = value;
|
|
435
|
+
changed = true;
|
|
436
|
+
changes.push({
|
|
437
|
+
category: "backend",
|
|
438
|
+
message: `${getCategoryDisplayName(catKey)} set to '${value}' (no backend)`
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (nextStack.examples.length > 0 && !(nextStack.examples.length === 1 && nextStack.examples[0] === "none")) {
|
|
443
|
+
nextStack.examples = ["none"];
|
|
444
|
+
changed = true;
|
|
445
|
+
changes.push({
|
|
446
|
+
category: "backend",
|
|
447
|
+
message: "Examples cleared (no backend)"
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (nextStack.backend === "self-next" || nextStack.backend === "self-tanstack-start" || nextStack.backend === "self-astro" || nextStack.backend === "self-nuxt" || nextStack.backend === "self-svelte" || nextStack.backend === "self-solid-start") {
|
|
452
|
+
if (nextStack.runtime !== "none") {
|
|
453
|
+
nextStack.runtime = "none";
|
|
454
|
+
changed = true;
|
|
455
|
+
changes.push({
|
|
456
|
+
category: "backend",
|
|
457
|
+
message: "Runtime set to 'None' (fullstack uses frontend's API routes)"
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (nextStack.serverDeploy !== "none") {
|
|
461
|
+
nextStack.serverDeploy = "none";
|
|
462
|
+
changed = true;
|
|
463
|
+
changes.push({
|
|
464
|
+
category: "backend",
|
|
465
|
+
message: "Server deploy set to 'None' (fullstack uses frontend deployment)"
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (nextStack.backend === "self-next" && !nextStack.webFrontend.includes("next")) {
|
|
469
|
+
nextStack.webFrontend = ["next"];
|
|
470
|
+
changed = true;
|
|
471
|
+
changes.push({
|
|
472
|
+
category: "backend",
|
|
473
|
+
message: "Frontend set to 'Next.js' (required for Next.js fullstack)"
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (nextStack.backend === "self-tanstack-start" && !nextStack.webFrontend.includes("tanstack-start")) {
|
|
477
|
+
nextStack.webFrontend = ["tanstack-start"];
|
|
478
|
+
changed = true;
|
|
479
|
+
changes.push({
|
|
480
|
+
category: "backend",
|
|
481
|
+
message: "Frontend set to 'TanStack Start' (required for TanStack Start fullstack)"
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
if (nextStack.backend === "self-astro" && !nextStack.webFrontend.includes("astro")) {
|
|
485
|
+
nextStack.webFrontend = ["astro"];
|
|
486
|
+
if (nextStack.astroIntegration === "none") nextStack.astroIntegration = "react";
|
|
487
|
+
changed = true;
|
|
488
|
+
changes.push({
|
|
489
|
+
category: "backend",
|
|
490
|
+
message: "Frontend set to 'Astro' (required for Astro fullstack)"
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (nextStack.backend === "self-nuxt" && !nextStack.webFrontend.includes("nuxt")) {
|
|
494
|
+
nextStack.webFrontend = ["nuxt"];
|
|
495
|
+
changed = true;
|
|
496
|
+
changes.push({
|
|
497
|
+
category: "backend",
|
|
498
|
+
message: "Frontend set to 'Nuxt' (required for Nuxt fullstack)"
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
if (nextStack.backend === "self-svelte" && !nextStack.webFrontend.includes("svelte")) {
|
|
502
|
+
nextStack.webFrontend = ["svelte"];
|
|
503
|
+
changed = true;
|
|
504
|
+
changes.push({
|
|
505
|
+
category: "backend",
|
|
506
|
+
message: "Frontend set to 'SvelteKit' (required for SvelteKit fullstack)"
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
if (nextStack.backend === "self-solid-start" && !nextStack.webFrontend.includes("solid-start")) {
|
|
510
|
+
nextStack.webFrontend = ["solid-start"];
|
|
511
|
+
changed = true;
|
|
512
|
+
changes.push({
|
|
513
|
+
category: "backend",
|
|
514
|
+
message: "Frontend set to 'SolidStart' (required for SolidStart fullstack)"
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (nextStack.runtime === "workers" && nextStack.backend !== "hono") {
|
|
519
|
+
nextStack.backend = "hono";
|
|
520
|
+
changed = true;
|
|
521
|
+
changes.push({
|
|
522
|
+
category: "runtime",
|
|
523
|
+
message: "Backend set to 'Hono' (required for Workers)"
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (nextStack.runtime === "workers" && nextStack.serverDeploy === "none") {
|
|
527
|
+
nextStack.serverDeploy = "cloudflare";
|
|
528
|
+
changed = true;
|
|
529
|
+
changes.push({
|
|
530
|
+
category: "runtime",
|
|
531
|
+
message: "Server deploy set to 'Cloudflare' (required for Workers)"
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
if (nextStack.runtime === "workers" && nextStack.database === "mongodb") {
|
|
535
|
+
nextStack.database = "sqlite";
|
|
536
|
+
nextStack.orm = "drizzle";
|
|
537
|
+
nextStack.dbSetup = "d1";
|
|
538
|
+
changed = true;
|
|
539
|
+
changes.push({
|
|
540
|
+
category: "runtime",
|
|
541
|
+
message: "Database changed to SQLite with D1 (Better-Fullstack doesn't support MongoDB with Workers)"
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (nextStack.runtime === "none" && nextStack.backend !== "convex" && nextStack.backend !== "none" && nextStack.backend !== "self-next" && nextStack.backend !== "self-tanstack-start" && nextStack.backend !== "self-astro" && nextStack.backend !== "self-nuxt" && nextStack.backend !== "self-svelte" && nextStack.backend !== "self-solid-start") {
|
|
545
|
+
nextStack.runtime = DEFAULT_RUNTIME;
|
|
546
|
+
changed = true;
|
|
547
|
+
changes.push({
|
|
548
|
+
category: "runtime",
|
|
549
|
+
message: `Runtime set to '${DEFAULT_RUNTIME}' (required for this backend)`
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (nextStack.backend !== "convex" && nextStack.backend !== "none") {
|
|
553
|
+
if (nextStack.database === "none") {
|
|
554
|
+
if (nextStack.orm !== "none") {
|
|
555
|
+
nextStack.orm = "none";
|
|
556
|
+
changed = true;
|
|
557
|
+
changes.push({
|
|
558
|
+
category: "database",
|
|
559
|
+
message: "ORM set to 'None' (no database selected)"
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
if (nextStack.dbSetup !== "none") {
|
|
563
|
+
nextStack.dbSetup = "none";
|
|
564
|
+
changed = true;
|
|
565
|
+
changes.push({
|
|
566
|
+
category: "database",
|
|
567
|
+
message: "DB Setup set to 'None' (no database selected)"
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (nextStack.database === "mongodb") {
|
|
572
|
+
if (nextStack.orm !== "prisma" && nextStack.orm !== "mongoose") {
|
|
573
|
+
nextStack.orm = "prisma";
|
|
574
|
+
changed = true;
|
|
575
|
+
changes.push({
|
|
576
|
+
category: "database",
|
|
577
|
+
message: "ORM set to 'Prisma' (required for MongoDB)"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (nextStack.dbSetup !== "mongodb-atlas" && nextStack.dbSetup !== "none" && nextStack.dbSetup !== "docker") {
|
|
581
|
+
nextStack.dbSetup = "none";
|
|
582
|
+
changed = true;
|
|
583
|
+
changes.push({
|
|
584
|
+
category: "database",
|
|
585
|
+
message: "DB Setup set to 'None' (incompatible with MongoDB)"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if ([
|
|
590
|
+
"sqlite",
|
|
591
|
+
"postgres",
|
|
592
|
+
"mysql"
|
|
593
|
+
].includes(nextStack.database)) {
|
|
594
|
+
if (nextStack.orm === "none") {
|
|
595
|
+
nextStack.orm = "drizzle";
|
|
596
|
+
changed = true;
|
|
597
|
+
changes.push({
|
|
598
|
+
category: "database",
|
|
599
|
+
message: "ORM set to 'Drizzle' (required for database)"
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
if (nextStack.orm === "mongoose") {
|
|
603
|
+
nextStack.orm = "drizzle";
|
|
604
|
+
changed = true;
|
|
605
|
+
changes.push({
|
|
606
|
+
category: "database",
|
|
607
|
+
message: "ORM set to 'Drizzle' (Mongoose only works with MongoDB)"
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (nextStack.orm !== "none" && nextStack.database === "none") if (nextStack.orm === "mongoose") {
|
|
612
|
+
nextStack.database = "mongodb";
|
|
613
|
+
changed = true;
|
|
614
|
+
changes.push({
|
|
615
|
+
category: "orm",
|
|
616
|
+
message: "Database set to 'MongoDB' (required for Mongoose)"
|
|
617
|
+
});
|
|
618
|
+
} else {
|
|
619
|
+
nextStack.database = "sqlite";
|
|
620
|
+
changed = true;
|
|
621
|
+
changes.push({
|
|
622
|
+
category: "orm",
|
|
623
|
+
message: "Database set to 'SQLite' (required for ORM)"
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (nextStack.dbSetup === "turso" && nextStack.database !== "sqlite") {
|
|
627
|
+
nextStack.database = "sqlite";
|
|
628
|
+
changed = true;
|
|
629
|
+
changes.push({
|
|
630
|
+
category: "dbSetup",
|
|
631
|
+
message: "Database set to 'SQLite' (required for Turso)"
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
if (nextStack.dbSetup === "d1") {
|
|
635
|
+
if (nextStack.database !== "sqlite") {
|
|
636
|
+
nextStack.database = "sqlite";
|
|
637
|
+
changed = true;
|
|
638
|
+
changes.push({
|
|
639
|
+
category: "dbSetup",
|
|
640
|
+
message: "Database set to 'SQLite' (required for D1)"
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
if (nextStack.runtime !== "workers") {
|
|
644
|
+
nextStack.runtime = "workers";
|
|
645
|
+
nextStack.backend = "hono";
|
|
646
|
+
changed = true;
|
|
647
|
+
changes.push({
|
|
648
|
+
category: "dbSetup",
|
|
649
|
+
message: "Runtime set to 'Workers' with 'Hono' (required for D1)"
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (nextStack.dbSetup === "neon" && nextStack.database !== "postgres") {
|
|
654
|
+
nextStack.database = "postgres";
|
|
655
|
+
changed = true;
|
|
656
|
+
changes.push({
|
|
657
|
+
category: "dbSetup",
|
|
658
|
+
message: "Database set to 'PostgreSQL' (required for Neon)"
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
if (nextStack.dbSetup === "supabase" && nextStack.database !== "postgres") {
|
|
662
|
+
nextStack.database = "postgres";
|
|
663
|
+
changed = true;
|
|
664
|
+
changes.push({
|
|
665
|
+
category: "dbSetup",
|
|
666
|
+
message: "Database set to 'PostgreSQL' (required for Supabase)"
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (nextStack.dbSetup === "prisma-postgres" && nextStack.database !== "postgres") {
|
|
670
|
+
nextStack.database = "postgres";
|
|
671
|
+
changed = true;
|
|
672
|
+
changes.push({
|
|
673
|
+
category: "dbSetup",
|
|
674
|
+
message: "Database set to 'PostgreSQL' (required for Prisma Postgres)"
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
if (nextStack.dbSetup === "mongodb-atlas" && nextStack.database !== "mongodb") {
|
|
678
|
+
nextStack.database = "mongodb";
|
|
679
|
+
if (nextStack.orm !== "prisma" && nextStack.orm !== "mongoose") nextStack.orm = "prisma";
|
|
680
|
+
changed = true;
|
|
681
|
+
changes.push({
|
|
682
|
+
category: "dbSetup",
|
|
683
|
+
message: "Database set to 'MongoDB' (required for MongoDB Atlas)"
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
if (nextStack.dbSetup === "planetscale" && nextStack.database !== "postgres" && nextStack.database !== "mysql") {
|
|
687
|
+
nextStack.database = "postgres";
|
|
688
|
+
changed = true;
|
|
689
|
+
changes.push({
|
|
690
|
+
category: "dbSetup",
|
|
691
|
+
message: "Database set to 'PostgreSQL' (required for PlanetScale)"
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
if (nextStack.dbSetup === "docker") {
|
|
695
|
+
if (nextStack.database === "sqlite") {
|
|
696
|
+
nextStack.dbSetup = "none";
|
|
697
|
+
changed = true;
|
|
698
|
+
changes.push({
|
|
699
|
+
category: "dbSetup",
|
|
700
|
+
message: "DB Setup set to 'None' (SQLite doesn't need Docker)"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
if (nextStack.runtime === "workers") {
|
|
704
|
+
nextStack.dbSetup = "d1";
|
|
705
|
+
changed = true;
|
|
706
|
+
changes.push({
|
|
707
|
+
category: "dbSetup",
|
|
708
|
+
message: "DB Setup set to 'D1' (Better-Fullstack doesn't support Docker setup with Workers)"
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (nextStack.backend !== "convex" && nextStack.backend !== "none") {
|
|
714
|
+
if (nextStack.webFrontend.some((f) => [
|
|
715
|
+
"nuxt",
|
|
716
|
+
"svelte",
|
|
717
|
+
"solid",
|
|
718
|
+
"solid-start"
|
|
719
|
+
].includes(f)) && nextStack.api === "trpc") {
|
|
720
|
+
nextStack.api = "orpc";
|
|
721
|
+
changed = true;
|
|
722
|
+
changes.push({
|
|
723
|
+
category: "api",
|
|
724
|
+
message: "API set to 'oRPC' (required for this frontend)"
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
if (nextStack.webFrontend.includes("astro") && nextStack.astroIntegration !== "react" && nextStack.api === "trpc") {
|
|
728
|
+
nextStack.api = "orpc";
|
|
729
|
+
changed = true;
|
|
730
|
+
changes.push({
|
|
731
|
+
category: "api",
|
|
732
|
+
message: "API set to 'oRPC' (tRPC requires React integration with Astro)"
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (!nextStack.webFrontend.includes("astro") && nextStack.astroIntegration !== "none") {
|
|
737
|
+
nextStack.astroIntegration = "none";
|
|
738
|
+
changed = true;
|
|
739
|
+
changes.push({
|
|
740
|
+
category: "astroIntegration",
|
|
741
|
+
message: "Astro integration reset (Astro not selected)"
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
if (nextStack.webFrontend.includes("astro") && nextStack.astroIntegration === "none") {
|
|
745
|
+
if (nextStack.api === "trpc") {
|
|
746
|
+
nextStack.astroIntegration = "react";
|
|
747
|
+
changed = true;
|
|
748
|
+
changes.push({
|
|
749
|
+
category: "astroIntegration",
|
|
750
|
+
message: "Astro integration set to 'React' (required for tRPC)"
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const normalizedAuth = normalizeCapabilitySelection("auth", {
|
|
755
|
+
ecosystem: nextStack.ecosystem,
|
|
756
|
+
backend: nextStack.backend,
|
|
757
|
+
webFrontend: nextStack.webFrontend,
|
|
758
|
+
nativeFrontend: nextStack.nativeFrontend
|
|
759
|
+
}, nextStack.auth);
|
|
760
|
+
if (normalizedAuth.normalized && nextStack.auth !== normalizedAuth.value) {
|
|
761
|
+
nextStack.auth = normalizedAuth.value;
|
|
762
|
+
changed = true;
|
|
763
|
+
changes.push({
|
|
764
|
+
category: "auth",
|
|
765
|
+
message: normalizedAuth.message ?? "Auth set to 'None'"
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
if (nextStack.payments === "polar") {
|
|
769
|
+
if (nextStack.auth !== "better-auth") {
|
|
770
|
+
nextStack.payments = "none";
|
|
771
|
+
changed = true;
|
|
772
|
+
changes.push({
|
|
773
|
+
category: "payments",
|
|
774
|
+
message: "Payments set to 'None' (Polar requires Better Auth)"
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
if (nextStack.backend === "convex") {
|
|
778
|
+
nextStack.payments = "none";
|
|
779
|
+
changed = true;
|
|
780
|
+
changes.push({
|
|
781
|
+
category: "payments",
|
|
782
|
+
message: "Payments set to 'None' (Polar incompatible with Convex)"
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (!nextStack.webFrontend.some((f) => f !== "none")) {
|
|
786
|
+
nextStack.payments = "none";
|
|
787
|
+
changed = true;
|
|
788
|
+
changes.push({
|
|
789
|
+
category: "payments",
|
|
790
|
+
message: "Payments set to 'None' (Polar requires web frontend)"
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (nextStack.email !== "none") {
|
|
795
|
+
if (nextStack.backend === "convex") {
|
|
796
|
+
nextStack.email = "none";
|
|
797
|
+
changed = true;
|
|
798
|
+
changes.push({
|
|
799
|
+
category: "email",
|
|
800
|
+
message: "Email set to 'None' (incompatible with Convex)"
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
if (nextStack.backend === "none") {
|
|
804
|
+
nextStack.email = "none";
|
|
805
|
+
changed = true;
|
|
806
|
+
changes.push({
|
|
807
|
+
category: "email",
|
|
808
|
+
message: "Email set to 'None' (requires backend)"
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (!nextStack.webFrontend.some((f) => f !== "none")) {
|
|
813
|
+
if (nextStack.cssFramework !== "none") {
|
|
814
|
+
nextStack.cssFramework = "none";
|
|
815
|
+
changed = true;
|
|
816
|
+
changes.push({
|
|
817
|
+
category: "cssFramework",
|
|
818
|
+
message: "CSS framework set to 'None' (no web frontend)"
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (nextStack.uiLibrary !== "none") {
|
|
822
|
+
nextStack.uiLibrary = "none";
|
|
823
|
+
changed = true;
|
|
824
|
+
changes.push({
|
|
825
|
+
category: "uiLibrary",
|
|
826
|
+
message: "UI library set to 'None' (no web frontend)"
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if ([
|
|
831
|
+
"shadcn-ui",
|
|
832
|
+
"daisyui",
|
|
833
|
+
"nextui"
|
|
834
|
+
].includes(nextStack.uiLibrary) && nextStack.cssFramework !== "tailwind") {
|
|
835
|
+
nextStack.cssFramework = "tailwind";
|
|
836
|
+
changed = true;
|
|
837
|
+
changes.push({
|
|
838
|
+
category: "cssFramework",
|
|
839
|
+
message: `CSS framework set to 'Tailwind' (required by ${nextStack.uiLibrary})`
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
const reactOnlyLibraries = [
|
|
843
|
+
"shadcn-ui",
|
|
844
|
+
"radix-ui",
|
|
845
|
+
"chakra-ui",
|
|
846
|
+
"nextui"
|
|
847
|
+
];
|
|
848
|
+
const reactFrontends = [
|
|
849
|
+
"tanstack-router",
|
|
850
|
+
"react-router",
|
|
851
|
+
"tanstack-start",
|
|
852
|
+
"next"
|
|
853
|
+
];
|
|
854
|
+
if (reactOnlyLibraries.includes(nextStack.uiLibrary)) {
|
|
855
|
+
const hasReactFrontend = nextStack.webFrontend.some((f) => reactFrontends.includes(f));
|
|
856
|
+
const hasAstroReact = nextStack.webFrontend.includes("astro") && nextStack.astroIntegration === "react";
|
|
857
|
+
if (!hasReactFrontend && !hasAstroReact && nextStack.webFrontend.some((f) => f !== "none")) {
|
|
858
|
+
nextStack.uiLibrary = "daisyui";
|
|
859
|
+
changed = true;
|
|
860
|
+
changes.push({
|
|
861
|
+
category: "uiLibrary",
|
|
862
|
+
message: "UI library changed to 'daisyUI' (React-only library incompatible with this frontend)"
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (nextStack.uiLibrary === "headless-ui") {
|
|
867
|
+
const hasReactFrontend = nextStack.webFrontend.some((f) => reactFrontends.includes(f));
|
|
868
|
+
const hasVueFrontend = nextStack.webFrontend.includes("nuxt");
|
|
869
|
+
const hasAstroReactOrVue = nextStack.webFrontend.includes("astro") && ["react", "vue"].includes(nextStack.astroIntegration);
|
|
870
|
+
if (!hasReactFrontend && !hasVueFrontend && !hasAstroReactOrVue) {
|
|
871
|
+
nextStack.uiLibrary = "daisyui";
|
|
872
|
+
changed = true;
|
|
873
|
+
changes.push({
|
|
874
|
+
category: "uiLibrary",
|
|
875
|
+
message: "UI library changed to 'daisyUI' (Headless UI requires React or Vue)"
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (nextStack.uiLibrary === "park-ui") {
|
|
880
|
+
const hasReactFrontend = nextStack.webFrontend.some((f) => reactFrontends.includes(f));
|
|
881
|
+
const hasVueFrontend = nextStack.webFrontend.includes("nuxt");
|
|
882
|
+
const hasSolidFrontend = nextStack.webFrontend.includes("solid");
|
|
883
|
+
const hasAstroCompatible = nextStack.webFrontend.includes("astro") && [
|
|
884
|
+
"react",
|
|
885
|
+
"vue",
|
|
886
|
+
"solid"
|
|
887
|
+
].includes(nextStack.astroIntegration);
|
|
888
|
+
if (!hasReactFrontend && !hasVueFrontend && !hasSolidFrontend && !hasAstroCompatible && nextStack.webFrontend.some((f) => f !== "none")) {
|
|
889
|
+
nextStack.uiLibrary = "daisyui";
|
|
890
|
+
changed = true;
|
|
891
|
+
changes.push({
|
|
892
|
+
category: "uiLibrary",
|
|
893
|
+
message: "UI library changed to 'daisyUI' (Park UI requires React, Vue, or Solid)"
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const pwaCompat = hasPWACompatibleFrontend(nextStack.webFrontend);
|
|
898
|
+
const tauriCompat = hasTauriCompatibleFrontend(nextStack.webFrontend);
|
|
899
|
+
if (!pwaCompat && nextStack.appPlatforms.includes("pwa")) {
|
|
900
|
+
nextStack.appPlatforms = nextStack.appPlatforms.filter((a) => a !== "pwa");
|
|
901
|
+
changed = true;
|
|
902
|
+
changes.push({
|
|
903
|
+
category: "appPlatforms",
|
|
904
|
+
message: "PWA removed (requires compatible frontend)"
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
if (!tauriCompat && nextStack.appPlatforms.includes("tauri")) {
|
|
908
|
+
nextStack.appPlatforms = nextStack.appPlatforms.filter((a) => a !== "tauri");
|
|
909
|
+
changed = true;
|
|
910
|
+
changes.push({
|
|
911
|
+
category: "appPlatforms",
|
|
912
|
+
message: "Tauri removed (requires compatible frontend)"
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
if (nextStack.examples.includes("ai")) {
|
|
916
|
+
if (nextStack.webFrontend.includes("solid") || nextStack.webFrontend.includes("solid-start")) {
|
|
917
|
+
nextStack.examples = nextStack.examples.filter((e) => e !== "ai");
|
|
918
|
+
if (nextStack.examples.length === 0) nextStack.examples = ["none"];
|
|
919
|
+
changed = true;
|
|
920
|
+
changes.push({
|
|
921
|
+
category: "examples",
|
|
922
|
+
message: "AI removed (not compatible with Solid frontend)"
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
if (nextStack.backend === "convex") {
|
|
926
|
+
if (nextStack.webFrontend.some((f) => ["svelte", "nuxt"].includes(f))) {
|
|
927
|
+
nextStack.examples = nextStack.examples.filter((e) => e !== "ai");
|
|
928
|
+
if (nextStack.examples.length === 0) nextStack.examples = ["none"];
|
|
929
|
+
changed = true;
|
|
930
|
+
changes.push({
|
|
931
|
+
category: "examples",
|
|
932
|
+
message: "AI removed (Convex AI only supports React-based frontends)"
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (nextStack.examples.includes("chat-sdk")) {
|
|
938
|
+
if (!isChatSdkExampleSupported(nextStack)) {
|
|
939
|
+
nextStack.examples = nextStack.examples.filter((e) => e !== "chat-sdk");
|
|
940
|
+
if (nextStack.examples.length === 0) nextStack.examples = ["none"];
|
|
941
|
+
changed = true;
|
|
942
|
+
let reason = "unsupported stack";
|
|
943
|
+
if (nextStack.ecosystem !== "typescript") reason = "TypeScript ecosystem only";
|
|
944
|
+
else if (nextStack.backend === "convex") reason = "Convex backend not supported in v1";
|
|
945
|
+
else if (nextStack.backend === "none") reason = "requires a backend";
|
|
946
|
+
else if (nextStack.backend === "hono" && nextStack.runtime !== "node") reason = "Hono profile requires Node runtime";
|
|
947
|
+
else if (nextStack.backend.startsWith("self-")) reason = "self backend only supports Next.js, TanStack Start, or Nuxt in v1";
|
|
948
|
+
changes.push({
|
|
949
|
+
category: "examples",
|
|
950
|
+
message: `Chat SDK removed (${reason})`
|
|
951
|
+
});
|
|
952
|
+
} else if (requiresChatSdkVercelAI(nextStack) && nextStack.aiSdk !== "vercel-ai") {
|
|
953
|
+
nextStack.aiSdk = "vercel-ai";
|
|
954
|
+
changed = true;
|
|
955
|
+
changes.push({
|
|
956
|
+
category: "ai",
|
|
957
|
+
message: "AI SDK set to 'Vercel AI SDK' (required by Chat SDK Nuxt/Hono profile in v1)"
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (nextStack.webFrontend.includes("fresh")) {
|
|
962
|
+
if (nextStack.forms === "tanstack-form") {
|
|
963
|
+
nextStack.forms = "none";
|
|
964
|
+
changed = true;
|
|
965
|
+
changes.push({
|
|
966
|
+
category: "forms",
|
|
967
|
+
message: "Forms set to 'None' (TanStack Form has no Preact adapter)"
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
if ([
|
|
971
|
+
"nanostores",
|
|
972
|
+
"xstate",
|
|
973
|
+
"tanstack-store"
|
|
974
|
+
].includes(nextStack.stateManagement)) {
|
|
975
|
+
const oldValue = nextStack.stateManagement;
|
|
976
|
+
nextStack.stateManagement = "none";
|
|
977
|
+
changed = true;
|
|
978
|
+
changes.push({
|
|
979
|
+
category: "stateManagement",
|
|
980
|
+
message: `State management set to 'None' (${oldValue} requires React bindings)`
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
if (nextStack.animation === "lottie") {
|
|
984
|
+
nextStack.animation = "none";
|
|
985
|
+
changed = true;
|
|
986
|
+
changes.push({
|
|
987
|
+
category: "animation",
|
|
988
|
+
message: "Animation set to 'None' (Lottie requires lottie-react)"
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (nextStack.webDeploy !== "none" && !nextStack.webFrontend.some((f) => f !== "none")) {
|
|
993
|
+
nextStack.webDeploy = "none";
|
|
994
|
+
changed = true;
|
|
995
|
+
changes.push({
|
|
996
|
+
category: "webDeploy",
|
|
997
|
+
message: "Web deploy set to 'None' (no web frontend)"
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
if (nextStack.serverDeploy === "cloudflare") {
|
|
1001
|
+
if (nextStack.runtime !== "workers" || nextStack.backend !== "hono") {
|
|
1002
|
+
nextStack.serverDeploy = "none";
|
|
1003
|
+
changed = true;
|
|
1004
|
+
changes.push({
|
|
1005
|
+
category: "serverDeploy",
|
|
1006
|
+
message: "Server deploy set to 'None' (Cloudflare requires Workers + Hono)"
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (nextStack.serverDeploy !== "none" && [
|
|
1011
|
+
"none",
|
|
1012
|
+
"convex",
|
|
1013
|
+
"self-next",
|
|
1014
|
+
"self-tanstack-start",
|
|
1015
|
+
"self-astro",
|
|
1016
|
+
"self-nuxt",
|
|
1017
|
+
"self-svelte",
|
|
1018
|
+
"self-solid-start"
|
|
1019
|
+
].includes(nextStack.backend)) {
|
|
1020
|
+
nextStack.serverDeploy = "none";
|
|
1021
|
+
changed = true;
|
|
1022
|
+
changes.push({
|
|
1023
|
+
category: "serverDeploy",
|
|
1024
|
+
message: "Server deploy set to 'None' (not needed for this backend)"
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
return {
|
|
1028
|
+
adjustedStack: changed ? nextStack : null,
|
|
1029
|
+
notes,
|
|
1030
|
+
changes
|
|
1031
|
+
};
|
|
1032
|
+
};
|
|
1033
|
+
/**
|
|
1034
|
+
* Returns a reason why an option is disabled, or null if it's enabled.
|
|
1035
|
+
*
|
|
1036
|
+
* PHILOSOPHY: Only disable options that are TRULY incompatible.
|
|
1037
|
+
* - Don't create circular dependencies
|
|
1038
|
+
* - Allow users to select options that will trigger auto-adjustments
|
|
1039
|
+
* - Follow CLI behavior: filter options based on UPSTREAM selections only
|
|
1040
|
+
*/
|
|
1041
|
+
const getDisabledReason = (currentStack, category, optionId) => {
|
|
1042
|
+
if (currentStack.backend === "convex") {
|
|
1043
|
+
if (category === "runtime" && optionId !== "none") return "Convex provides its own runtime";
|
|
1044
|
+
if (category === "database" && optionId !== "none") return "Convex provides its own database";
|
|
1045
|
+
if (category === "orm" && optionId !== "none") return "Convex has built-in data access";
|
|
1046
|
+
if (category === "api" && optionId !== "none") return "Convex provides its own API layer";
|
|
1047
|
+
if (category === "dbSetup" && optionId !== "none") return "Convex handles database setup";
|
|
1048
|
+
if (category === "serverDeploy" && optionId !== "none") return "Convex has its own deployment";
|
|
1049
|
+
if (category === "search" && optionId !== "none") return "Search requires a standalone backend";
|
|
1050
|
+
if (category === "fileStorage" && optionId !== "none") return "File storage requires a standalone backend";
|
|
1051
|
+
if (category === "webFrontend" && optionId === "solid") return "In Better-Fullstack, the Convex backend is currently not available with Solid";
|
|
1052
|
+
if (category === "webFrontend" && optionId === "astro") return "In Better-Fullstack, the Convex backend is currently not available with Astro";
|
|
1053
|
+
if (category === "examples" && optionId === "ai") {
|
|
1054
|
+
if (currentStack.webFrontend.some((f) => [
|
|
1055
|
+
"solid",
|
|
1056
|
+
"svelte",
|
|
1057
|
+
"nuxt"
|
|
1058
|
+
].includes(f))) return `Convex AI example only supports React-based frontends (not ${currentStack.webFrontend.find((f) => [
|
|
1059
|
+
"solid",
|
|
1060
|
+
"svelte",
|
|
1061
|
+
"nuxt"
|
|
1062
|
+
].includes(f))})`;
|
|
1063
|
+
}
|
|
1064
|
+
if (category === "payments" && optionId === "polar") return "In Better-Fullstack, Polar is currently not available with the Convex backend";
|
|
1065
|
+
}
|
|
1066
|
+
if (currentStack.backend === "none") {
|
|
1067
|
+
if (category === "runtime" && optionId !== "none") return "No backend selected";
|
|
1068
|
+
if (category === "database" && optionId !== "none") return "No backend selected";
|
|
1069
|
+
if (category === "orm" && optionId !== "none") return "No backend selected";
|
|
1070
|
+
if (category === "api" && optionId !== "none") return "No backend selected";
|
|
1071
|
+
if (category === "dbSetup" && optionId !== "none") return "No backend selected";
|
|
1072
|
+
if (category === "serverDeploy" && optionId !== "none") return "No backend selected";
|
|
1073
|
+
if (category === "payments" && optionId !== "none") return "No backend selected";
|
|
1074
|
+
if (category === "search" && optionId !== "none") return "No backend selected";
|
|
1075
|
+
if (category === "fileStorage" && optionId !== "none") return "No backend selected";
|
|
1076
|
+
if (category === "examples" && optionId !== "none") return "No backend selected";
|
|
1077
|
+
}
|
|
1078
|
+
if (currentStack.backend === "self-next") {
|
|
1079
|
+
if (category === "runtime" && optionId !== "none") return "Next.js fullstack uses built-in API routes";
|
|
1080
|
+
if (category === "webFrontend" && optionId !== "next" && optionId !== "none") return "Next.js fullstack requires Next.js frontend";
|
|
1081
|
+
if (category === "serverDeploy" && optionId !== "none") return "Fullstack uses frontend deployment";
|
|
1082
|
+
}
|
|
1083
|
+
if (currentStack.backend === "self-tanstack-start") {
|
|
1084
|
+
if (category === "runtime" && optionId !== "none") return "TanStack Start fullstack uses built-in API routes";
|
|
1085
|
+
if (category === "webFrontend" && optionId !== "tanstack-start" && optionId !== "none") return "TanStack Start fullstack requires TanStack Start frontend";
|
|
1086
|
+
if (category === "serverDeploy" && optionId !== "none") return "Fullstack uses frontend deployment";
|
|
1087
|
+
}
|
|
1088
|
+
if (currentStack.backend === "self-astro") {
|
|
1089
|
+
if (category === "runtime" && optionId !== "none") return "Astro fullstack uses built-in API routes";
|
|
1090
|
+
if (category === "webFrontend" && optionId !== "astro" && optionId !== "none") return "Astro fullstack requires Astro frontend";
|
|
1091
|
+
if (category === "serverDeploy" && optionId !== "none") return "Fullstack uses frontend deployment";
|
|
1092
|
+
}
|
|
1093
|
+
if (currentStack.backend === "self-nuxt") {
|
|
1094
|
+
if (category === "runtime" && optionId !== "none") return "Nuxt fullstack uses built-in API routes";
|
|
1095
|
+
if (category === "webFrontend" && optionId !== "nuxt" && optionId !== "none") return "Nuxt fullstack requires Nuxt frontend";
|
|
1096
|
+
if (category === "serverDeploy" && optionId !== "none") return "Fullstack uses frontend deployment";
|
|
1097
|
+
}
|
|
1098
|
+
if (currentStack.backend === "self-svelte") {
|
|
1099
|
+
if (category === "runtime" && optionId !== "none") return "SvelteKit fullstack uses built-in API routes";
|
|
1100
|
+
if (category === "webFrontend" && optionId !== "svelte" && optionId !== "none") return "SvelteKit fullstack requires SvelteKit frontend";
|
|
1101
|
+
if (category === "serverDeploy" && optionId !== "none") return "Fullstack uses frontend deployment";
|
|
1102
|
+
}
|
|
1103
|
+
if (currentStack.backend === "self-solid-start") {
|
|
1104
|
+
if (category === "runtime" && optionId !== "none") return "SolidStart fullstack uses built-in API routes";
|
|
1105
|
+
if (category === "webFrontend" && optionId !== "solid-start" && optionId !== "none") return "SolidStart fullstack requires SolidStart frontend";
|
|
1106
|
+
if (category === "serverDeploy" && optionId !== "none") return "Fullstack uses frontend deployment";
|
|
1107
|
+
}
|
|
1108
|
+
if (category === "backend") {
|
|
1109
|
+
if (optionId === "self-next" && !currentStack.webFrontend.includes("next")) return "Requires Next.js frontend";
|
|
1110
|
+
if (optionId === "self-tanstack-start" && !currentStack.webFrontend.includes("tanstack-start")) return "Requires TanStack Start frontend";
|
|
1111
|
+
if (optionId === "self-astro" && !currentStack.webFrontend.includes("astro")) return "Requires Astro frontend";
|
|
1112
|
+
if (optionId === "self-nuxt" && !currentStack.webFrontend.includes("nuxt")) return "Requires Nuxt frontend";
|
|
1113
|
+
if (optionId === "self-svelte" && !currentStack.webFrontend.includes("svelte")) return "Requires SvelteKit frontend";
|
|
1114
|
+
if (optionId === "self-solid-start" && !currentStack.webFrontend.includes("solid-start")) return "Requires SolidStart frontend";
|
|
1115
|
+
if (optionId === "convex" && currentStack.webFrontend.includes("solid")) return "In Better-Fullstack, Convex is currently not available with Solid";
|
|
1116
|
+
if (optionId === "convex" && currentStack.webFrontend.includes("solid-start")) return "In Better-Fullstack, Convex is currently not available with SolidStart";
|
|
1117
|
+
if (optionId === "convex" && currentStack.webFrontend.includes("astro")) return "In Better-Fullstack, Convex is currently not available with Astro";
|
|
1118
|
+
if (currentStack.runtime === "workers" && optionId !== "hono" && optionId !== "none") return "In Better-Fullstack, Workers runtime is currently supported only with Hono";
|
|
1119
|
+
}
|
|
1120
|
+
if (category === "runtime") {
|
|
1121
|
+
if (optionId === "workers" && currentStack.backend !== "hono") return "In Better-Fullstack, Workers runtime currently requires the Hono backend";
|
|
1122
|
+
if (optionId === "none") {
|
|
1123
|
+
if (![
|
|
1124
|
+
"convex",
|
|
1125
|
+
"none",
|
|
1126
|
+
"self-next",
|
|
1127
|
+
"self-tanstack-start",
|
|
1128
|
+
"self-astro",
|
|
1129
|
+
"self-nuxt",
|
|
1130
|
+
"self-svelte",
|
|
1131
|
+
"self-solid-start"
|
|
1132
|
+
].includes(currentStack.backend)) return "Runtime 'None' only for Convex or fullstack backends";
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (category === "database") {
|
|
1136
|
+
if (optionId === "mongodb" && currentStack.runtime === "workers") return "In Better-Fullstack, MongoDB is currently not available with Workers runtime";
|
|
1137
|
+
}
|
|
1138
|
+
if (category === "orm") {
|
|
1139
|
+
if (optionId === "mongoose") {
|
|
1140
|
+
if (currentStack.runtime === "workers") return "Mongoose requires MongoDB, and Better-Fullstack currently doesn't support MongoDB with Workers runtime";
|
|
1141
|
+
if (currentStack.database !== "none" && currentStack.database !== "mongodb") return "Mongoose only works with MongoDB";
|
|
1142
|
+
}
|
|
1143
|
+
if (optionId === "drizzle" && currentStack.database === "mongodb") return "Drizzle does not support MongoDB";
|
|
1144
|
+
if (optionId === "none" && currentStack.database !== "none") return "Database requires an ORM";
|
|
1145
|
+
}
|
|
1146
|
+
if (category === "dbSetup" && optionId !== "none") {
|
|
1147
|
+
if (currentStack.database === "none") return "Select a database first";
|
|
1148
|
+
if (optionId === "turso" && currentStack.database !== "sqlite") return "Turso requires SQLite";
|
|
1149
|
+
if (optionId === "d1") {
|
|
1150
|
+
if (currentStack.database !== "sqlite") return "D1 requires SQLite";
|
|
1151
|
+
if (currentStack.runtime !== "workers") return "D1 requires Workers runtime";
|
|
1152
|
+
}
|
|
1153
|
+
if (optionId === "neon" && currentStack.database !== "postgres") return "Neon requires PostgreSQL";
|
|
1154
|
+
if (optionId === "supabase" && currentStack.database !== "postgres") return "Supabase requires PostgreSQL";
|
|
1155
|
+
if (optionId === "prisma-postgres" && currentStack.database !== "postgres") return "Prisma Postgres requires PostgreSQL";
|
|
1156
|
+
if (optionId === "mongodb-atlas" && currentStack.database !== "mongodb") return "MongoDB Atlas requires MongoDB";
|
|
1157
|
+
if (optionId === "planetscale" && currentStack.database !== "postgres" && currentStack.database !== "mysql") return "PlanetScale requires PostgreSQL or MySQL";
|
|
1158
|
+
if (optionId === "docker") {
|
|
1159
|
+
if (currentStack.database === "sqlite") return "SQLite doesn't need Docker";
|
|
1160
|
+
if (currentStack.runtime === "workers") return "Docker is incompatible with Workers";
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (category === "api" && optionId === "trpc") {
|
|
1164
|
+
if (currentStack.webFrontend.some((f) => [
|
|
1165
|
+
"nuxt",
|
|
1166
|
+
"svelte",
|
|
1167
|
+
"solid",
|
|
1168
|
+
"solid-start"
|
|
1169
|
+
].includes(f))) return `${currentStack.webFrontend.find((f) => [
|
|
1170
|
+
"nuxt",
|
|
1171
|
+
"svelte",
|
|
1172
|
+
"solid",
|
|
1173
|
+
"solid-start"
|
|
1174
|
+
].includes(f))} requires oRPC, not tRPC`;
|
|
1175
|
+
if (currentStack.webFrontend.includes("astro") && currentStack.astroIntegration !== "react" && currentStack.astroIntegration !== "none") return `Astro with ${currentStack.astroIntegration} integration requires oRPC, not tRPC`;
|
|
1176
|
+
}
|
|
1177
|
+
if (category === "astroIntegration") {
|
|
1178
|
+
if (!currentStack.webFrontend.includes("astro") && optionId !== "none") return "Astro integration requires Astro frontend";
|
|
1179
|
+
if (currentStack.api === "trpc" && optionId !== "react" && optionId !== "none") return "tRPC requires React integration with Astro";
|
|
1180
|
+
}
|
|
1181
|
+
if (category === "auth") return getCapabilityDisabledReason("auth", {
|
|
1182
|
+
ecosystem: currentStack.ecosystem,
|
|
1183
|
+
backend: currentStack.backend,
|
|
1184
|
+
webFrontend: currentStack.webFrontend,
|
|
1185
|
+
nativeFrontend: currentStack.nativeFrontend
|
|
1186
|
+
}, optionId);
|
|
1187
|
+
if (category === "payments" && optionId === "polar") {
|
|
1188
|
+
if (currentStack.auth !== "better-auth") return "Polar requires Better Auth";
|
|
1189
|
+
if (!currentStack.webFrontend.some((f) => f !== "none")) return "Polar requires a web frontend";
|
|
1190
|
+
}
|
|
1191
|
+
if (category === "email" && optionId !== "none") {
|
|
1192
|
+
if (currentStack.backend === "convex") return "Email integration is not available with Convex backend";
|
|
1193
|
+
if (currentStack.backend === "none") return "Email integration requires a backend";
|
|
1194
|
+
}
|
|
1195
|
+
if (category === "ai" && requiresChatSdkVercelAI(currentStack) && optionId !== "vercel-ai") return "Chat SDK example (Nuxt/Hono profile) requires Vercel AI SDK in v1";
|
|
1196
|
+
if (category === "appPlatforms") {
|
|
1197
|
+
if (optionId === "pwa" && !hasPWACompatibleFrontend(currentStack.webFrontend)) return "PWA requires TanStack Router, React Router, Solid, Next.js, or Astro";
|
|
1198
|
+
if (optionId === "tauri" && !hasTauriCompatibleFrontend(currentStack.webFrontend)) return "Tauri requires TanStack Router, React Router, Nuxt, Svelte, Solid, Next.js, or Astro";
|
|
1199
|
+
}
|
|
1200
|
+
if (category === "examples") {
|
|
1201
|
+
if (optionId === "ai") {
|
|
1202
|
+
if (currentStack.webFrontend.includes("solid") || currentStack.webFrontend.includes("solid-start")) return "AI example not compatible with Solid frontend";
|
|
1203
|
+
if (currentStack.backend === "convex") {
|
|
1204
|
+
if (currentStack.webFrontend.some((f) => ["svelte", "nuxt"].includes(f))) return `Convex AI example only supports React-based frontends (not ${currentStack.webFrontend.find((f) => ["svelte", "nuxt"].includes(f))})`;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (optionId === "chat-sdk") {
|
|
1208
|
+
if (currentStack.ecosystem !== "typescript") return "Chat SDK example is currently available only for TypeScript stacks";
|
|
1209
|
+
if (currentStack.backend === "convex") return "Chat SDK example is not supported with Convex backend in v1";
|
|
1210
|
+
if (currentStack.backend === "self-astro" || currentStack.backend === "self-svelte" || currentStack.backend === "self-solid-start") return "Chat SDK self backend profile supports Next.js, TanStack Start, or Nuxt in v1";
|
|
1211
|
+
if (currentStack.backend === "self-next" || currentStack.backend === "self-tanstack-start") return null;
|
|
1212
|
+
if (currentStack.backend === "self-nuxt") return null;
|
|
1213
|
+
if (currentStack.backend === "hono") {
|
|
1214
|
+
if (currentStack.runtime !== "node") return "Chat SDK Hono profile requires Node runtime in v1";
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
if (currentStack.backend.startsWith("self-")) return "Chat SDK self backend profile supports Next.js, TanStack Start, or Nuxt in v1";
|
|
1218
|
+
if (currentStack.backend !== "none") return "Chat SDK example is only supported for self (Next/TanStack Start/Nuxt) or Hono+Node in v1";
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
const isFresh = currentStack.webFrontend.includes("fresh");
|
|
1222
|
+
if (category === "forms" && optionId === "tanstack-form" && isFresh) return "TanStack Form has no Preact adapter (Fresh uses Preact)";
|
|
1223
|
+
if (category === "stateManagement" && isFresh) {
|
|
1224
|
+
if (optionId === "nanostores") return "Nanostores requires @nanostores/react (no Preact support)";
|
|
1225
|
+
if (optionId === "xstate") return "XState requires @xstate/react (no Preact support)";
|
|
1226
|
+
if (optionId === "tanstack-store") return "TanStack Store requires @tanstack/react-store (no Preact support)";
|
|
1227
|
+
}
|
|
1228
|
+
if (category === "animation" && optionId === "lottie" && isFresh) return "Lottie uses lottie-react which requires React (not Preact)";
|
|
1229
|
+
if (category === "cssFramework") {
|
|
1230
|
+
if (!currentStack.webFrontend.some((f) => f !== "none")) {
|
|
1231
|
+
if (optionId !== "none") return "CSS framework requires a web frontend";
|
|
1232
|
+
}
|
|
1233
|
+
if ([
|
|
1234
|
+
"shadcn-ui",
|
|
1235
|
+
"daisyui",
|
|
1236
|
+
"nextui"
|
|
1237
|
+
].includes(currentStack.uiLibrary) && optionId !== "tailwind") return `${currentStack.uiLibrary === "shadcn-ui" ? "shadcn/ui" : currentStack.uiLibrary === "daisyui" ? "daisyUI" : "NextUI"} requires Tailwind CSS`;
|
|
1238
|
+
}
|
|
1239
|
+
if (category === "uiLibrary") {
|
|
1240
|
+
if (!currentStack.webFrontend.some((f) => f !== "none")) {
|
|
1241
|
+
if (optionId !== "none") return "UI library requires a web frontend";
|
|
1242
|
+
}
|
|
1243
|
+
const reactOnlyLibraries = [
|
|
1244
|
+
"shadcn-ui",
|
|
1245
|
+
"radix-ui",
|
|
1246
|
+
"chakra-ui",
|
|
1247
|
+
"nextui"
|
|
1248
|
+
];
|
|
1249
|
+
const reactFrontends = [
|
|
1250
|
+
"tanstack-router",
|
|
1251
|
+
"react-router",
|
|
1252
|
+
"tanstack-start",
|
|
1253
|
+
"next"
|
|
1254
|
+
];
|
|
1255
|
+
if (reactOnlyLibraries.includes(optionId)) {
|
|
1256
|
+
const hasReactFrontend = currentStack.webFrontend.some((f) => reactFrontends.includes(f));
|
|
1257
|
+
const hasAstroReact = currentStack.webFrontend.includes("astro") && currentStack.astroIntegration === "react";
|
|
1258
|
+
if (!hasReactFrontend && !hasAstroReact) return `${optionId === "shadcn-ui" ? "shadcn/ui" : optionId === "radix-ui" ? "Radix UI" : optionId === "chakra-ui" ? "Chakra UI" : "NextUI"} requires a React-based frontend`;
|
|
1259
|
+
}
|
|
1260
|
+
if (optionId === "headless-ui") {
|
|
1261
|
+
const hasReactFrontend = currentStack.webFrontend.some((f) => reactFrontends.includes(f));
|
|
1262
|
+
const hasVueFrontend = currentStack.webFrontend.includes("nuxt");
|
|
1263
|
+
const hasAstroReactOrVue = currentStack.webFrontend.includes("astro") && ["react", "vue"].includes(currentStack.astroIntegration);
|
|
1264
|
+
if (!hasReactFrontend && !hasVueFrontend && !hasAstroReactOrVue) return "Headless UI requires React or Vue frontend";
|
|
1265
|
+
}
|
|
1266
|
+
if (optionId === "park-ui") {
|
|
1267
|
+
const hasReactFrontend = currentStack.webFrontend.some((f) => reactFrontends.includes(f));
|
|
1268
|
+
const hasVueFrontend = currentStack.webFrontend.includes("nuxt");
|
|
1269
|
+
const hasSolidFrontend = currentStack.webFrontend.includes("solid") || currentStack.webFrontend.includes("solid-start");
|
|
1270
|
+
const hasAstroCompatible = currentStack.webFrontend.includes("astro") && [
|
|
1271
|
+
"react",
|
|
1272
|
+
"vue",
|
|
1273
|
+
"solid"
|
|
1274
|
+
].includes(currentStack.astroIntegration);
|
|
1275
|
+
if (!hasReactFrontend && !hasVueFrontend && !hasSolidFrontend && !hasAstroCompatible) return "Park UI requires React, Vue, or Solid frontend";
|
|
1276
|
+
if (currentStack.cssFramework === "none") return "Park UI requires a CSS framework";
|
|
1277
|
+
}
|
|
1278
|
+
if ([
|
|
1279
|
+
"shadcn-ui",
|
|
1280
|
+
"daisyui",
|
|
1281
|
+
"nextui"
|
|
1282
|
+
].includes(optionId)) {
|
|
1283
|
+
if (currentStack.cssFramework !== "tailwind") return `${optionId === "shadcn-ui" ? "shadcn/ui" : optionId === "daisyui" ? "daisyUI" : "NextUI"} requires Tailwind CSS`;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (category === "webDeploy" && optionId !== "none") {
|
|
1287
|
+
if (!currentStack.webFrontend.some((f) => f !== "none")) return "Web deployment requires a web frontend";
|
|
1288
|
+
}
|
|
1289
|
+
if (category === "serverDeploy") {
|
|
1290
|
+
if (optionId === "cloudflare") {
|
|
1291
|
+
if (currentStack.runtime !== "workers") return "In Better-Fullstack, Cloudflare server deploy currently requires Workers runtime";
|
|
1292
|
+
if (currentStack.backend !== "hono") return "In Better-Fullstack, Cloudflare server deploy is currently supported only with Hono";
|
|
1293
|
+
}
|
|
1294
|
+
if (optionId !== "none") {
|
|
1295
|
+
if ([
|
|
1296
|
+
"none",
|
|
1297
|
+
"convex",
|
|
1298
|
+
"self-next",
|
|
1299
|
+
"self-tanstack-start",
|
|
1300
|
+
"self-astro",
|
|
1301
|
+
"self-nuxt",
|
|
1302
|
+
"self-svelte",
|
|
1303
|
+
"self-solid-start"
|
|
1304
|
+
].includes(currentStack.backend)) return "Server deployment not needed for this backend";
|
|
1305
|
+
}
|
|
1306
|
+
if (optionId === "none" && currentStack.runtime === "workers") return "Workers requires server deployment";
|
|
1307
|
+
}
|
|
1308
|
+
return null;
|
|
1309
|
+
};
|
|
1310
|
+
const isOptionCompatible = (currentStack, category, optionId) => {
|
|
1311
|
+
if (currentStack.yolo === "true") return true;
|
|
1312
|
+
return getDisabledReason(currentStack, category, optionId) === null;
|
|
1313
|
+
};
|
|
1314
|
+
const WEB_FRAMEWORKS = [
|
|
1315
|
+
"tanstack-router",
|
|
1316
|
+
"react-router",
|
|
1317
|
+
"tanstack-start",
|
|
1318
|
+
"next",
|
|
1319
|
+
"nuxt",
|
|
1320
|
+
"svelte",
|
|
1321
|
+
"solid",
|
|
1322
|
+
"solid-start",
|
|
1323
|
+
"astro",
|
|
1324
|
+
"qwik",
|
|
1325
|
+
"angular",
|
|
1326
|
+
"redwood",
|
|
1327
|
+
"fresh",
|
|
1328
|
+
"none"
|
|
1329
|
+
];
|
|
1330
|
+
const UI_LIBRARY_COMPATIBILITY = {
|
|
1331
|
+
"shadcn-ui": {
|
|
1332
|
+
frontends: [
|
|
1333
|
+
"tanstack-router",
|
|
1334
|
+
"react-router",
|
|
1335
|
+
"tanstack-start",
|
|
1336
|
+
"next",
|
|
1337
|
+
"astro"
|
|
1338
|
+
],
|
|
1339
|
+
cssFrameworks: ["tailwind"]
|
|
1340
|
+
},
|
|
1341
|
+
daisyui: {
|
|
1342
|
+
frontends: [
|
|
1343
|
+
"tanstack-router",
|
|
1344
|
+
"react-router",
|
|
1345
|
+
"tanstack-start",
|
|
1346
|
+
"next",
|
|
1347
|
+
"nuxt",
|
|
1348
|
+
"svelte",
|
|
1349
|
+
"solid",
|
|
1350
|
+
"solid-start",
|
|
1351
|
+
"astro",
|
|
1352
|
+
"qwik",
|
|
1353
|
+
"angular",
|
|
1354
|
+
"redwood",
|
|
1355
|
+
"fresh"
|
|
1356
|
+
],
|
|
1357
|
+
cssFrameworks: ["tailwind"]
|
|
1358
|
+
},
|
|
1359
|
+
"radix-ui": {
|
|
1360
|
+
frontends: [
|
|
1361
|
+
"tanstack-router",
|
|
1362
|
+
"react-router",
|
|
1363
|
+
"tanstack-start",
|
|
1364
|
+
"next",
|
|
1365
|
+
"astro"
|
|
1366
|
+
],
|
|
1367
|
+
cssFrameworks: [
|
|
1368
|
+
"tailwind",
|
|
1369
|
+
"scss",
|
|
1370
|
+
"less",
|
|
1371
|
+
"postcss-only",
|
|
1372
|
+
"none"
|
|
1373
|
+
]
|
|
1374
|
+
},
|
|
1375
|
+
"headless-ui": {
|
|
1376
|
+
frontends: [
|
|
1377
|
+
"tanstack-router",
|
|
1378
|
+
"react-router",
|
|
1379
|
+
"tanstack-start",
|
|
1380
|
+
"next",
|
|
1381
|
+
"nuxt",
|
|
1382
|
+
"astro"
|
|
1383
|
+
],
|
|
1384
|
+
cssFrameworks: [
|
|
1385
|
+
"tailwind",
|
|
1386
|
+
"scss",
|
|
1387
|
+
"less",
|
|
1388
|
+
"postcss-only",
|
|
1389
|
+
"none"
|
|
1390
|
+
]
|
|
1391
|
+
},
|
|
1392
|
+
"park-ui": {
|
|
1393
|
+
frontends: [
|
|
1394
|
+
"tanstack-router",
|
|
1395
|
+
"react-router",
|
|
1396
|
+
"tanstack-start",
|
|
1397
|
+
"next",
|
|
1398
|
+
"nuxt",
|
|
1399
|
+
"solid",
|
|
1400
|
+
"solid-start",
|
|
1401
|
+
"astro"
|
|
1402
|
+
],
|
|
1403
|
+
cssFrameworks: [
|
|
1404
|
+
"tailwind",
|
|
1405
|
+
"scss",
|
|
1406
|
+
"less",
|
|
1407
|
+
"postcss-only"
|
|
1408
|
+
]
|
|
1409
|
+
},
|
|
1410
|
+
"chakra-ui": {
|
|
1411
|
+
frontends: [
|
|
1412
|
+
"tanstack-router",
|
|
1413
|
+
"react-router",
|
|
1414
|
+
"tanstack-start",
|
|
1415
|
+
"next",
|
|
1416
|
+
"astro"
|
|
1417
|
+
],
|
|
1418
|
+
cssFrameworks: [
|
|
1419
|
+
"tailwind",
|
|
1420
|
+
"scss",
|
|
1421
|
+
"less",
|
|
1422
|
+
"postcss-only",
|
|
1423
|
+
"none"
|
|
1424
|
+
]
|
|
1425
|
+
},
|
|
1426
|
+
nextui: {
|
|
1427
|
+
frontends: [
|
|
1428
|
+
"tanstack-router",
|
|
1429
|
+
"react-router",
|
|
1430
|
+
"tanstack-start",
|
|
1431
|
+
"next",
|
|
1432
|
+
"astro"
|
|
1433
|
+
],
|
|
1434
|
+
cssFrameworks: ["tailwind"]
|
|
1435
|
+
},
|
|
1436
|
+
mantine: {
|
|
1437
|
+
frontends: [
|
|
1438
|
+
"tanstack-router",
|
|
1439
|
+
"react-router",
|
|
1440
|
+
"tanstack-start",
|
|
1441
|
+
"next",
|
|
1442
|
+
"astro"
|
|
1443
|
+
],
|
|
1444
|
+
cssFrameworks: [
|
|
1445
|
+
"tailwind",
|
|
1446
|
+
"scss",
|
|
1447
|
+
"less",
|
|
1448
|
+
"postcss-only",
|
|
1449
|
+
"none"
|
|
1450
|
+
]
|
|
1451
|
+
},
|
|
1452
|
+
"base-ui": {
|
|
1453
|
+
frontends: [
|
|
1454
|
+
"tanstack-router",
|
|
1455
|
+
"react-router",
|
|
1456
|
+
"tanstack-start",
|
|
1457
|
+
"next",
|
|
1458
|
+
"astro"
|
|
1459
|
+
],
|
|
1460
|
+
cssFrameworks: [
|
|
1461
|
+
"tailwind",
|
|
1462
|
+
"scss",
|
|
1463
|
+
"less",
|
|
1464
|
+
"postcss-only",
|
|
1465
|
+
"none"
|
|
1466
|
+
]
|
|
1467
|
+
},
|
|
1468
|
+
"ark-ui": {
|
|
1469
|
+
frontends: [
|
|
1470
|
+
"tanstack-router",
|
|
1471
|
+
"react-router",
|
|
1472
|
+
"tanstack-start",
|
|
1473
|
+
"next",
|
|
1474
|
+
"nuxt",
|
|
1475
|
+
"svelte",
|
|
1476
|
+
"solid",
|
|
1477
|
+
"solid-start",
|
|
1478
|
+
"astro"
|
|
1479
|
+
],
|
|
1480
|
+
cssFrameworks: [
|
|
1481
|
+
"tailwind",
|
|
1482
|
+
"scss",
|
|
1483
|
+
"less",
|
|
1484
|
+
"postcss-only",
|
|
1485
|
+
"none"
|
|
1486
|
+
]
|
|
1487
|
+
},
|
|
1488
|
+
"react-aria": {
|
|
1489
|
+
frontends: [
|
|
1490
|
+
"tanstack-router",
|
|
1491
|
+
"react-router",
|
|
1492
|
+
"tanstack-start",
|
|
1493
|
+
"next",
|
|
1494
|
+
"astro"
|
|
1495
|
+
],
|
|
1496
|
+
cssFrameworks: [
|
|
1497
|
+
"tailwind",
|
|
1498
|
+
"scss",
|
|
1499
|
+
"less",
|
|
1500
|
+
"postcss-only",
|
|
1501
|
+
"none"
|
|
1502
|
+
]
|
|
1503
|
+
},
|
|
1504
|
+
none: {
|
|
1505
|
+
frontends: WEB_FRAMEWORKS,
|
|
1506
|
+
cssFrameworks: [
|
|
1507
|
+
"tailwind",
|
|
1508
|
+
"scss",
|
|
1509
|
+
"less",
|
|
1510
|
+
"postcss-only",
|
|
1511
|
+
"none"
|
|
1512
|
+
]
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
const ADDON_COMPATIBILITY = {
|
|
1516
|
+
pwa: [
|
|
1517
|
+
"tanstack-router",
|
|
1518
|
+
"react-router",
|
|
1519
|
+
"solid",
|
|
1520
|
+
"next",
|
|
1521
|
+
"astro",
|
|
1522
|
+
"qwik",
|
|
1523
|
+
"angular",
|
|
1524
|
+
"redwood",
|
|
1525
|
+
"fresh"
|
|
1526
|
+
],
|
|
1527
|
+
tauri: [
|
|
1528
|
+
"tanstack-router",
|
|
1529
|
+
"react-router",
|
|
1530
|
+
"nuxt",
|
|
1531
|
+
"svelte",
|
|
1532
|
+
"solid",
|
|
1533
|
+
"next",
|
|
1534
|
+
"astro",
|
|
1535
|
+
"qwik",
|
|
1536
|
+
"angular",
|
|
1537
|
+
"redwood",
|
|
1538
|
+
"fresh"
|
|
1539
|
+
],
|
|
1540
|
+
biome: [],
|
|
1541
|
+
husky: [],
|
|
1542
|
+
lefthook: [],
|
|
1543
|
+
turborepo: [],
|
|
1544
|
+
starlight: [],
|
|
1545
|
+
ultracite: [],
|
|
1546
|
+
ruler: [],
|
|
1547
|
+
mcp: [],
|
|
1548
|
+
skills: [],
|
|
1549
|
+
oxlint: [],
|
|
1550
|
+
fumadocs: [],
|
|
1551
|
+
opentui: [],
|
|
1552
|
+
wxt: [],
|
|
1553
|
+
msw: [],
|
|
1554
|
+
storybook: [
|
|
1555
|
+
"tanstack-router",
|
|
1556
|
+
"react-router",
|
|
1557
|
+
"next",
|
|
1558
|
+
"nuxt",
|
|
1559
|
+
"svelte",
|
|
1560
|
+
"solid"
|
|
1561
|
+
],
|
|
1562
|
+
none: []
|
|
1563
|
+
};
|
|
1564
|
+
function isWebFrontend(value) {
|
|
1565
|
+
return WEB_FRAMEWORKS.includes(value);
|
|
1566
|
+
}
|
|
1567
|
+
function splitFrontends(values = []) {
|
|
1568
|
+
return {
|
|
1569
|
+
web: values.filter((f) => isWebFrontend(f)),
|
|
1570
|
+
native: values.filter((f) => f === "native-bare" || f === "native-uniwind" || f === "native-unistyles")
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
function allowedApisForFrontends(frontends = [], astroIntegration) {
|
|
1574
|
+
const includesNuxt = frontends.includes("nuxt");
|
|
1575
|
+
const includesSvelte = frontends.includes("svelte");
|
|
1576
|
+
const includesSolid = frontends.includes("solid");
|
|
1577
|
+
const includesAstro = frontends.includes("astro");
|
|
1578
|
+
const includesQwik = frontends.includes("qwik");
|
|
1579
|
+
const includesAngular = frontends.includes("angular");
|
|
1580
|
+
const includesRedwood = frontends.includes("redwood");
|
|
1581
|
+
const includesFresh = frontends.includes("fresh");
|
|
1582
|
+
const base = [
|
|
1583
|
+
"trpc",
|
|
1584
|
+
"orpc",
|
|
1585
|
+
"ts-rest",
|
|
1586
|
+
"garph",
|
|
1587
|
+
"none"
|
|
1588
|
+
];
|
|
1589
|
+
if (includesQwik || includesAngular || includesRedwood || includesFresh) return ["none"];
|
|
1590
|
+
const includesSolidStartApi = frontends.includes("solid-start");
|
|
1591
|
+
if (includesNuxt || includesSvelte || includesSolid || includesSolidStartApi) return ["orpc", "none"];
|
|
1592
|
+
if (includesAstro && astroIntegration && astroIntegration !== "react") return ["orpc", "none"];
|
|
1593
|
+
return base;
|
|
1594
|
+
}
|
|
1595
|
+
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
1596
|
+
if (backend === "convex" && frontend === "solid") return false;
|
|
1597
|
+
if (backend === "convex" && frontend === "solid-start") return false;
|
|
1598
|
+
if (backend === "convex" && frontend === "astro") return false;
|
|
1599
|
+
if (backend === "convex" && frontend === "qwik") return false;
|
|
1600
|
+
if (backend === "convex" && frontend === "angular") return false;
|
|
1601
|
+
if (backend === "convex" && frontend === "redwood") return false;
|
|
1602
|
+
if (backend === "convex" && frontend === "fresh") return false;
|
|
1603
|
+
if (frontend === "qwik" && backend && backend !== "none") return false;
|
|
1604
|
+
if (frontend === "angular" && backend && backend !== "none") return false;
|
|
1605
|
+
if (frontend === "redwood" && backend && backend !== "none") return false;
|
|
1606
|
+
if (frontend === "fresh" && backend && backend !== "none") return false;
|
|
1607
|
+
if (auth && auth !== "none") return getCapabilityDisabledReason("auth", {
|
|
1608
|
+
ecosystem: "typescript",
|
|
1609
|
+
backend,
|
|
1610
|
+
frontend: [frontend]
|
|
1611
|
+
}, auth) === null;
|
|
1612
|
+
return true;
|
|
1613
|
+
}
|
|
1614
|
+
function isExampleAIAllowed(backend, frontends = []) {
|
|
1615
|
+
const includesSolid = frontends.includes("solid");
|
|
1616
|
+
const includesSolidStart = frontends.includes("solid-start");
|
|
1617
|
+
if (includesSolid || includesSolidStart) return false;
|
|
1618
|
+
if (backend === "convex") {
|
|
1619
|
+
const includesNuxt = frontends.includes("nuxt");
|
|
1620
|
+
const includesSvelte = frontends.includes("svelte");
|
|
1621
|
+
if (includesNuxt || includesSvelte) return false;
|
|
1622
|
+
}
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
1625
|
+
function hasExampleChatSdkSelfFrontend(frontends = []) {
|
|
1626
|
+
return frontends.some((f) => [
|
|
1627
|
+
"next",
|
|
1628
|
+
"tanstack-start",
|
|
1629
|
+
"nuxt"
|
|
1630
|
+
].includes(f));
|
|
1631
|
+
}
|
|
1632
|
+
function isExampleChatSdkAllowed(backend, frontends = [], runtime) {
|
|
1633
|
+
if (!backend || backend === "none" || backend === "convex") return false;
|
|
1634
|
+
if (backend === "self") return hasExampleChatSdkSelfFrontend(frontends);
|
|
1635
|
+
if (backend === "self-next" || backend === "self-tanstack-start" || backend === "self-nuxt") return true;
|
|
1636
|
+
if (backend === "self-astro" || backend === "self-svelte" || backend === "self-solid-start") return false;
|
|
1637
|
+
if (backend === "hono") return runtime === "node";
|
|
1638
|
+
return false;
|
|
1639
|
+
}
|
|
1640
|
+
function requiresChatSdkVercelAIForSelection(backend, frontends = [], runtime) {
|
|
1641
|
+
if (backend === "self" && frontends.includes("nuxt")) return true;
|
|
1642
|
+
if (backend === "self-nuxt") return true;
|
|
1643
|
+
if (backend === "hono" && runtime === "node") return true;
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
1647
|
+
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
1648
|
+
if (compatibleFrontends.length > 0) {
|
|
1649
|
+
if (!frontend.some((f) => compatibleFrontends.includes(f))) return {
|
|
1650
|
+
isCompatible: false,
|
|
1651
|
+
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
return { isCompatible: true };
|
|
1655
|
+
}
|
|
1656
|
+
function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth) {
|
|
1657
|
+
return allAddons.filter((addon) => {
|
|
1658
|
+
if (existingAddons.includes(addon)) return false;
|
|
1659
|
+
if (addon === "none") return false;
|
|
1660
|
+
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth);
|
|
1661
|
+
return isCompatible;
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
function getCompatibleUILibraries(frontends = [], astroIntegration) {
|
|
1665
|
+
const { web } = splitFrontends(frontends);
|
|
1666
|
+
if (web.length === 0) return ["none"];
|
|
1667
|
+
const webFrontend = web[0];
|
|
1668
|
+
return Object.keys(UI_LIBRARY_COMPATIBILITY).filter((lib) => {
|
|
1669
|
+
if (lib === "none") return true;
|
|
1670
|
+
const compatibility = UI_LIBRARY_COMPATIBILITY[lib];
|
|
1671
|
+
if (webFrontend === "astro") {
|
|
1672
|
+
if (astroIntegration === "react") return compatibility.frontends.some((f) => [
|
|
1673
|
+
"tanstack-router",
|
|
1674
|
+
"react-router",
|
|
1675
|
+
"tanstack-start",
|
|
1676
|
+
"next",
|
|
1677
|
+
"astro"
|
|
1678
|
+
].includes(f));
|
|
1679
|
+
return compatibility.frontends.some((f) => [
|
|
1680
|
+
"nuxt",
|
|
1681
|
+
"svelte",
|
|
1682
|
+
"solid",
|
|
1683
|
+
"qwik",
|
|
1684
|
+
"angular"
|
|
1685
|
+
].includes(f));
|
|
1686
|
+
}
|
|
1687
|
+
return compatibility.frontends.includes(webFrontend);
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
function getCompatibleCSSFrameworks(uiLibrary) {
|
|
1691
|
+
if (!uiLibrary || uiLibrary === "none") return [
|
|
1692
|
+
"tailwind",
|
|
1693
|
+
"scss",
|
|
1694
|
+
"less",
|
|
1695
|
+
"postcss-only",
|
|
1696
|
+
"none"
|
|
1697
|
+
];
|
|
1698
|
+
return UI_LIBRARY_COMPATIBILITY[uiLibrary].cssFrameworks;
|
|
1699
|
+
}
|
|
1700
|
+
function hasWebStyling(frontends = []) {
|
|
1701
|
+
const { web } = splitFrontends(frontends);
|
|
1702
|
+
return web.length > 0;
|
|
1703
|
+
}
|
|
1704
|
+
function getCompatibleFormLibraries(frontends = []) {
|
|
1705
|
+
const hasSolid = frontends.includes("solid");
|
|
1706
|
+
const hasSolidStart = frontends.includes("solid-start");
|
|
1707
|
+
const hasQwik = frontends.includes("qwik");
|
|
1708
|
+
const hasFresh = frontends.includes("fresh");
|
|
1709
|
+
const all = [
|
|
1710
|
+
"tanstack-form",
|
|
1711
|
+
"react-hook-form",
|
|
1712
|
+
"formik",
|
|
1713
|
+
"final-form",
|
|
1714
|
+
"conform",
|
|
1715
|
+
"modular-forms",
|
|
1716
|
+
"none"
|
|
1717
|
+
];
|
|
1718
|
+
if (hasFresh) return all.filter((f) => f !== "tanstack-form" && f !== "react-hook-form" && f !== "formik");
|
|
1719
|
+
if (hasSolid || hasSolidStart || hasQwik) return all.filter((f) => f !== "react-hook-form" && f !== "formik" && f !== "final-form");
|
|
1720
|
+
return all;
|
|
1721
|
+
}
|
|
1722
|
+
function evaluateCompatibility(input) {
|
|
1723
|
+
const issues = [];
|
|
1724
|
+
const scalarChecks = [
|
|
1725
|
+
["runtime", input.runtime],
|
|
1726
|
+
["backend", input.backend],
|
|
1727
|
+
["database", input.database],
|
|
1728
|
+
["orm", input.orm],
|
|
1729
|
+
["dbSetup", input.dbSetup],
|
|
1730
|
+
["api", input.api],
|
|
1731
|
+
["auth", input.auth],
|
|
1732
|
+
["payments", input.payments],
|
|
1733
|
+
["email", input.email],
|
|
1734
|
+
["cssFramework", input.cssFramework],
|
|
1735
|
+
["uiLibrary", input.uiLibrary],
|
|
1736
|
+
["webDeploy", input.webDeploy],
|
|
1737
|
+
["serverDeploy", input.serverDeploy],
|
|
1738
|
+
["forms", input.forms],
|
|
1739
|
+
["stateManagement", input.stateManagement],
|
|
1740
|
+
["animation", input.animation],
|
|
1741
|
+
["ai", input.aiSdk]
|
|
1742
|
+
];
|
|
1743
|
+
for (const [category, optionId] of scalarChecks) {
|
|
1744
|
+
const reason = getDisabledReason(input, category, optionId);
|
|
1745
|
+
if (reason) issues.push({
|
|
1746
|
+
code: `INCOMPATIBLE_${category.toUpperCase()}`,
|
|
1747
|
+
message: reason,
|
|
1748
|
+
category,
|
|
1749
|
+
optionId
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
for (const frontend of input.webFrontend) {
|
|
1753
|
+
const reason = getDisabledReason(input, "webFrontend", frontend);
|
|
1754
|
+
if (reason) issues.push({
|
|
1755
|
+
code: "INCOMPATIBLE_FRONTEND",
|
|
1756
|
+
message: reason,
|
|
1757
|
+
category: "webFrontend",
|
|
1758
|
+
optionId: frontend
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
for (const addon of [
|
|
1762
|
+
...input.codeQuality,
|
|
1763
|
+
...input.documentation,
|
|
1764
|
+
...input.appPlatforms
|
|
1765
|
+
]) {
|
|
1766
|
+
const reason = getDisabledReason(input, "appPlatforms", addon);
|
|
1767
|
+
if (reason) issues.push({
|
|
1768
|
+
code: "INCOMPATIBLE_ADDON",
|
|
1769
|
+
message: reason,
|
|
1770
|
+
category: "appPlatforms",
|
|
1771
|
+
optionId: addon
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
return { issues };
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
//#endregion
|
|
1778
|
+
//#region src/option-metadata.ts
|
|
1779
|
+
const WEB_FRONTEND_VALUES = [
|
|
1780
|
+
"tanstack-router",
|
|
1781
|
+
"react-router",
|
|
1782
|
+
"tanstack-start",
|
|
1783
|
+
"next",
|
|
1784
|
+
"nuxt",
|
|
1785
|
+
"svelte",
|
|
1786
|
+
"solid",
|
|
1787
|
+
"solid-start",
|
|
1788
|
+
"astro",
|
|
1789
|
+
"qwik",
|
|
1790
|
+
"angular",
|
|
1791
|
+
"redwood",
|
|
1792
|
+
"fresh",
|
|
1793
|
+
"none"
|
|
1794
|
+
];
|
|
1795
|
+
const NATIVE_FRONTEND_VALUES = [
|
|
1796
|
+
"native-bare",
|
|
1797
|
+
"native-uniwind",
|
|
1798
|
+
"native-unistyles",
|
|
1799
|
+
"none"
|
|
1800
|
+
];
|
|
1801
|
+
const BACKEND_BUILDER_VALUES = [
|
|
1802
|
+
"hono",
|
|
1803
|
+
"express",
|
|
1804
|
+
"fastify",
|
|
1805
|
+
"elysia",
|
|
1806
|
+
"fets",
|
|
1807
|
+
"nestjs",
|
|
1808
|
+
"adonisjs",
|
|
1809
|
+
"nitro",
|
|
1810
|
+
"encore",
|
|
1811
|
+
"convex",
|
|
1812
|
+
"self-next",
|
|
1813
|
+
"self-tanstack-start",
|
|
1814
|
+
"self-astro",
|
|
1815
|
+
"self-nuxt",
|
|
1816
|
+
"self-svelte",
|
|
1817
|
+
"self-solid-start",
|
|
1818
|
+
"none"
|
|
1819
|
+
];
|
|
1820
|
+
const CODE_QUALITY_VALUES = [
|
|
1821
|
+
"biome",
|
|
1822
|
+
"oxlint",
|
|
1823
|
+
"ultracite",
|
|
1824
|
+
"lefthook",
|
|
1825
|
+
"husky",
|
|
1826
|
+
"ruler"
|
|
1827
|
+
];
|
|
1828
|
+
const DOCUMENTATION_VALUES = ["starlight", "fumadocs"];
|
|
1829
|
+
const APP_PLATFORM_VALUES = [
|
|
1830
|
+
"turborepo",
|
|
1831
|
+
"pwa",
|
|
1832
|
+
"tauri",
|
|
1833
|
+
"wxt",
|
|
1834
|
+
"opentui",
|
|
1835
|
+
"mcp",
|
|
1836
|
+
"skills",
|
|
1837
|
+
"msw",
|
|
1838
|
+
"storybook"
|
|
1839
|
+
];
|
|
1840
|
+
const EXAMPLE_VALUES = ["ai", "chat-sdk"];
|
|
1841
|
+
const BOOLEAN_OPTION_VALUES = ["true", "false"];
|
|
1842
|
+
const MULTI_SELECT_CATEGORIES = new Set([
|
|
1843
|
+
"webFrontend",
|
|
1844
|
+
"nativeFrontend",
|
|
1845
|
+
"codeQuality",
|
|
1846
|
+
"documentation",
|
|
1847
|
+
"appPlatforms",
|
|
1848
|
+
"examples",
|
|
1849
|
+
"aiDocs"
|
|
1850
|
+
]);
|
|
1851
|
+
const CATEGORY_VALUE_IDS = {
|
|
1852
|
+
api: API_VALUES,
|
|
1853
|
+
webFrontend: WEB_FRONTEND_VALUES,
|
|
1854
|
+
nativeFrontend: NATIVE_FRONTEND_VALUES,
|
|
1855
|
+
astroIntegration: ASTRO_INTEGRATION_VALUES,
|
|
1856
|
+
runtime: RUNTIME_VALUES,
|
|
1857
|
+
backend: BACKEND_BUILDER_VALUES,
|
|
1858
|
+
database: DATABASE_VALUES,
|
|
1859
|
+
orm: ORM_VALUES,
|
|
1860
|
+
dbSetup: DATABASE_SETUP_VALUES,
|
|
1861
|
+
webDeploy: WEB_DEPLOY_VALUES,
|
|
1862
|
+
serverDeploy: SERVER_DEPLOY_VALUES,
|
|
1863
|
+
auth: AUTH_VALUES,
|
|
1864
|
+
payments: PAYMENTS_VALUES,
|
|
1865
|
+
email: EMAIL_VALUES,
|
|
1866
|
+
fileUpload: FILE_UPLOAD_VALUES,
|
|
1867
|
+
logging: LOGGING_VALUES,
|
|
1868
|
+
observability: OBSERVABILITY_VALUES,
|
|
1869
|
+
backendLibraries: EFFECT_VALUES,
|
|
1870
|
+
stateManagement: STATE_MANAGEMENT_VALUES,
|
|
1871
|
+
forms: FORMS_VALUES,
|
|
1872
|
+
validation: VALIDATION_VALUES,
|
|
1873
|
+
testing: TESTING_VALUES,
|
|
1874
|
+
realtime: REALTIME_VALUES,
|
|
1875
|
+
jobQueue: JOB_QUEUE_VALUES,
|
|
1876
|
+
caching: CACHING_VALUES,
|
|
1877
|
+
search: SEARCH_VALUES,
|
|
1878
|
+
fileStorage: FILE_STORAGE_VALUES,
|
|
1879
|
+
animation: ANIMATION_VALUES,
|
|
1880
|
+
cssFramework: CSS_FRAMEWORK_VALUES,
|
|
1881
|
+
uiLibrary: UI_LIBRARY_VALUES,
|
|
1882
|
+
cms: CMS_VALUES,
|
|
1883
|
+
featureFlags: FEATURE_FLAGS_VALUES,
|
|
1884
|
+
analytics: ANALYTICS_VALUES,
|
|
1885
|
+
codeQuality: CODE_QUALITY_VALUES,
|
|
1886
|
+
documentation: DOCUMENTATION_VALUES,
|
|
1887
|
+
appPlatforms: APP_PLATFORM_VALUES,
|
|
1888
|
+
packageManager: PACKAGE_MANAGER_VALUES,
|
|
1889
|
+
examples: EXAMPLE_VALUES,
|
|
1890
|
+
ai: AI_VALUES,
|
|
1891
|
+
aiDocs: AI_DOCS_VALUES,
|
|
1892
|
+
git: BOOLEAN_OPTION_VALUES,
|
|
1893
|
+
install: BOOLEAN_OPTION_VALUES,
|
|
1894
|
+
effect: EFFECT_VALUES,
|
|
1895
|
+
shadcnBase: SHADCN_BASE_VALUES,
|
|
1896
|
+
shadcnStyle: SHADCN_STYLE_VALUES,
|
|
1897
|
+
shadcnIconLibrary: SHADCN_ICON_LIBRARY_VALUES,
|
|
1898
|
+
shadcnColorTheme: SHADCN_COLOR_THEME_VALUES,
|
|
1899
|
+
shadcnBaseColor: SHADCN_BASE_COLOR_VALUES,
|
|
1900
|
+
shadcnFont: SHADCN_FONT_VALUES,
|
|
1901
|
+
shadcnRadius: SHADCN_RADIUS_VALUES,
|
|
1902
|
+
rustWebFramework: RUST_WEB_FRAMEWORK_VALUES,
|
|
1903
|
+
rustFrontend: RUST_FRONTEND_VALUES,
|
|
1904
|
+
rustOrm: RUST_ORM_VALUES,
|
|
1905
|
+
rustApi: RUST_API_VALUES,
|
|
1906
|
+
rustCli: RUST_CLI_VALUES,
|
|
1907
|
+
rustLibraries: RUST_LIBRARIES_VALUES,
|
|
1908
|
+
pythonWebFramework: PYTHON_WEB_FRAMEWORK_VALUES,
|
|
1909
|
+
pythonOrm: PYTHON_ORM_VALUES,
|
|
1910
|
+
pythonValidation: PYTHON_VALIDATION_VALUES,
|
|
1911
|
+
pythonAi: PYTHON_AI_VALUES,
|
|
1912
|
+
pythonTaskQueue: PYTHON_TASK_QUEUE_VALUES,
|
|
1913
|
+
pythonQuality: PYTHON_QUALITY_VALUES,
|
|
1914
|
+
goWebFramework: GO_WEB_FRAMEWORK_VALUES,
|
|
1915
|
+
goOrm: GO_ORM_VALUES,
|
|
1916
|
+
goApi: GO_API_VALUES,
|
|
1917
|
+
goCli: GO_CLI_VALUES,
|
|
1918
|
+
goLogging: GO_LOGGING_VALUES
|
|
1919
|
+
};
|
|
1920
|
+
const EXACT_LABEL_OVERRIDES = {
|
|
1921
|
+
api: {
|
|
1922
|
+
trpc: "tRPC",
|
|
1923
|
+
orpc: "oRPC"
|
|
1924
|
+
},
|
|
1925
|
+
webFrontend: {
|
|
1926
|
+
next: "Next.js",
|
|
1927
|
+
svelte: "SvelteKit",
|
|
1928
|
+
redwood: "RedwoodJS"
|
|
1929
|
+
},
|
|
1930
|
+
nativeFrontend: {
|
|
1931
|
+
"native-bare": "Expo + Bare",
|
|
1932
|
+
"native-uniwind": "Expo + Uniwind",
|
|
1933
|
+
"native-unistyles": "Expo + Unistyles"
|
|
1934
|
+
},
|
|
1935
|
+
runtime: {
|
|
1936
|
+
node: "Node.js",
|
|
1937
|
+
workers: "Cloudflare Workers"
|
|
1938
|
+
},
|
|
1939
|
+
backend: {
|
|
1940
|
+
fets: "feTS",
|
|
1941
|
+
nestjs: "NestJS",
|
|
1942
|
+
encore: "Encore.ts",
|
|
1943
|
+
"self-next": "Fullstack Next.js",
|
|
1944
|
+
"self-tanstack-start": "Fullstack TanStack Start",
|
|
1945
|
+
"self-astro": "Fullstack Astro",
|
|
1946
|
+
"self-nuxt": "Fullstack Nuxt",
|
|
1947
|
+
"self-svelte": "Fullstack SvelteKit",
|
|
1948
|
+
"self-solid-start": "Fullstack SolidStart"
|
|
1949
|
+
},
|
|
1950
|
+
database: {
|
|
1951
|
+
sqlite: "SQLite",
|
|
1952
|
+
postgres: "PostgreSQL",
|
|
1953
|
+
mongodb: "MongoDB",
|
|
1954
|
+
edgedb: "EdgeDB"
|
|
1955
|
+
},
|
|
1956
|
+
orm: {
|
|
1957
|
+
typeorm: "TypeORM",
|
|
1958
|
+
mikroorm: "MikroORM"
|
|
1959
|
+
},
|
|
1960
|
+
dbSetup: {
|
|
1961
|
+
d1: "Cloudflare D1",
|
|
1962
|
+
neon: "Neon Postgres",
|
|
1963
|
+
"prisma-postgres": "Prisma PostgreSQL",
|
|
1964
|
+
"mongodb-atlas": "MongoDB Atlas",
|
|
1965
|
+
planetscale: "PlanetScale"
|
|
1966
|
+
},
|
|
1967
|
+
webDeploy: {
|
|
1968
|
+
cloudflare: "Cloudflare",
|
|
1969
|
+
fly: "Fly.io",
|
|
1970
|
+
sst: "SST"
|
|
1971
|
+
},
|
|
1972
|
+
serverDeploy: {
|
|
1973
|
+
cloudflare: "Cloudflare",
|
|
1974
|
+
fly: "Fly.io",
|
|
1975
|
+
sst: "SST"
|
|
1976
|
+
},
|
|
1977
|
+
auth: {},
|
|
1978
|
+
payments: {
|
|
1979
|
+
"lemon-squeezy": "Lemon Squeezy",
|
|
1980
|
+
dodo: "Dodo Payments"
|
|
1981
|
+
},
|
|
1982
|
+
email: {
|
|
1983
|
+
"react-email": "React Email",
|
|
1984
|
+
sendgrid: "SendGrid",
|
|
1985
|
+
"aws-ses": "AWS SES"
|
|
1986
|
+
},
|
|
1987
|
+
fileUpload: {
|
|
1988
|
+
uploadthing: "UploadThing",
|
|
1989
|
+
filepond: "FilePond",
|
|
1990
|
+
uppy: "Uppy"
|
|
1991
|
+
},
|
|
1992
|
+
observability: { opentelemetry: "OpenTelemetry" },
|
|
1993
|
+
backendLibraries: {
|
|
1994
|
+
effect: "Effect (Core)",
|
|
1995
|
+
"effect-full": "Effect Full"
|
|
1996
|
+
},
|
|
1997
|
+
stateManagement: {
|
|
1998
|
+
"redux-toolkit": "Redux Toolkit",
|
|
1999
|
+
xstate: "XState"
|
|
2000
|
+
},
|
|
2001
|
+
forms: {
|
|
2002
|
+
"react-hook-form": "React Hook Form",
|
|
2003
|
+
"tanstack-form": "TanStack Form",
|
|
2004
|
+
"final-form": "Final Form",
|
|
2005
|
+
"modular-forms": "Modular Forms"
|
|
2006
|
+
},
|
|
2007
|
+
validation: {
|
|
2008
|
+
zod: "Zod",
|
|
2009
|
+
arktype: "ArkType",
|
|
2010
|
+
typebox: "TypeBox",
|
|
2011
|
+
"effect-schema": "@effect/schema"
|
|
2012
|
+
},
|
|
2013
|
+
testing: { "vitest-playwright": "Vitest + Playwright" },
|
|
2014
|
+
realtime: {
|
|
2015
|
+
"socket-io": "Socket.IO",
|
|
2016
|
+
yjs: "Y.js"
|
|
2017
|
+
},
|
|
2018
|
+
jobQueue: {
|
|
2019
|
+
bullmq: "BullMQ",
|
|
2020
|
+
"trigger-dev": "Trigger.dev"
|
|
2021
|
+
},
|
|
2022
|
+
cssFramework: {
|
|
2023
|
+
tailwind: "Tailwind CSS",
|
|
2024
|
+
scss: "SCSS",
|
|
2025
|
+
"postcss-only": "PostCSS Only"
|
|
2026
|
+
},
|
|
2027
|
+
uiLibrary: {
|
|
2028
|
+
"shadcn-ui": "shadcn/ui",
|
|
2029
|
+
daisyui: "daisyUI",
|
|
2030
|
+
"radix-ui": "Radix UI",
|
|
2031
|
+
"headless-ui": "Headless UI",
|
|
2032
|
+
"park-ui": "Park UI",
|
|
2033
|
+
"chakra-ui": "Chakra UI",
|
|
2034
|
+
nextui: "NextUI",
|
|
2035
|
+
"base-ui": "Base UI",
|
|
2036
|
+
"ark-ui": "Ark UI",
|
|
2037
|
+
"react-aria": "React Aria"
|
|
2038
|
+
},
|
|
2039
|
+
featureFlags: {
|
|
2040
|
+
growthbook: "GrowthBook",
|
|
2041
|
+
posthog: "PostHog"
|
|
2042
|
+
},
|
|
2043
|
+
analytics: {
|
|
2044
|
+
plausible: "Plausible",
|
|
2045
|
+
umami: "Umami"
|
|
2046
|
+
},
|
|
2047
|
+
codeQuality: {
|
|
2048
|
+
biome: "Biome",
|
|
2049
|
+
oxlint: "Oxlint",
|
|
2050
|
+
ultracite: "Ultracite",
|
|
2051
|
+
lefthook: "Lefthook",
|
|
2052
|
+
husky: "Husky",
|
|
2053
|
+
ruler: "Ruler"
|
|
2054
|
+
},
|
|
2055
|
+
documentation: {
|
|
2056
|
+
starlight: "Starlight",
|
|
2057
|
+
fumadocs: "Fumadocs"
|
|
2058
|
+
},
|
|
2059
|
+
appPlatforms: {
|
|
2060
|
+
pwa: "PWA",
|
|
2061
|
+
wxt: "WXT",
|
|
2062
|
+
opentui: "OpenTUI",
|
|
2063
|
+
mcp: "MCP",
|
|
2064
|
+
msw: "MSW"
|
|
2065
|
+
},
|
|
2066
|
+
examples: {
|
|
2067
|
+
ai: "AI Example",
|
|
2068
|
+
"chat-sdk": "Chat SDK Bots"
|
|
2069
|
+
},
|
|
2070
|
+
ai: {
|
|
2071
|
+
"vercel-ai": "Vercel AI SDK",
|
|
2072
|
+
voltagent: "VoltAgent",
|
|
2073
|
+
langgraph: "LangGraph.js",
|
|
2074
|
+
"openai-agents": "OpenAI Agents SDK",
|
|
2075
|
+
"google-adk": "Google ADK",
|
|
2076
|
+
modelfusion: "ModelFusion",
|
|
2077
|
+
langchain: "LangChain",
|
|
2078
|
+
llamaindex: "LlamaIndex"
|
|
2079
|
+
},
|
|
2080
|
+
aiDocs: {
|
|
2081
|
+
"claude-md": "CLAUDE.md",
|
|
2082
|
+
"agents-md": "Agents.md",
|
|
2083
|
+
cursorrules: ".cursorrules"
|
|
2084
|
+
},
|
|
2085
|
+
git: {
|
|
2086
|
+
true: "Git",
|
|
2087
|
+
false: "No Git"
|
|
2088
|
+
},
|
|
2089
|
+
install: {
|
|
2090
|
+
true: "Install Dependencies",
|
|
2091
|
+
false: "Skip Install"
|
|
2092
|
+
},
|
|
2093
|
+
effect: {
|
|
2094
|
+
effect: "Effect (Core)",
|
|
2095
|
+
"effect-full": "Effect Full"
|
|
2096
|
+
},
|
|
2097
|
+
shadcnBase: {
|
|
2098
|
+
radix: "Radix UI",
|
|
2099
|
+
base: "Base UI"
|
|
2100
|
+
},
|
|
2101
|
+
shadcnStyle: {
|
|
2102
|
+
vega: "Vega",
|
|
2103
|
+
nova: "Nova",
|
|
2104
|
+
maia: "Maia",
|
|
2105
|
+
lyra: "Lyra",
|
|
2106
|
+
mira: "Mira"
|
|
2107
|
+
},
|
|
2108
|
+
shadcnIconLibrary: {
|
|
2109
|
+
lucide: "Lucide",
|
|
2110
|
+
tabler: "Tabler Icons",
|
|
2111
|
+
hugeicons: "HugeIcons",
|
|
2112
|
+
phosphor: "Phosphor Icons",
|
|
2113
|
+
remixicon: "Remix Icon"
|
|
2114
|
+
},
|
|
2115
|
+
shadcnColorTheme: { neutral: "Neutral" },
|
|
2116
|
+
shadcnBaseColor: { neutral: "Neutral" },
|
|
2117
|
+
shadcnFont: {
|
|
2118
|
+
inter: "Inter",
|
|
2119
|
+
geist: "Geist",
|
|
2120
|
+
figtree: "Figtree",
|
|
2121
|
+
"noto-sans": "Noto Sans",
|
|
2122
|
+
"nunito-sans": "Nunito Sans",
|
|
2123
|
+
roboto: "Roboto",
|
|
2124
|
+
raleway: "Raleway",
|
|
2125
|
+
"dm-sans": "DM Sans",
|
|
2126
|
+
"public-sans": "Public Sans",
|
|
2127
|
+
outfit: "Outfit",
|
|
2128
|
+
"jetbrains-mono": "JetBrains Mono",
|
|
2129
|
+
"geist-mono": "Geist Mono"
|
|
2130
|
+
},
|
|
2131
|
+
shadcnRadius: { default: "Default" },
|
|
2132
|
+
rustWebFramework: {
|
|
2133
|
+
axum: "Axum",
|
|
2134
|
+
"actix-web": "Actix-web"
|
|
2135
|
+
},
|
|
2136
|
+
rustFrontend: {
|
|
2137
|
+
leptos: "Leptos",
|
|
2138
|
+
dioxus: "Dioxus"
|
|
2139
|
+
},
|
|
2140
|
+
rustOrm: {
|
|
2141
|
+
"sea-orm": "SeaORM",
|
|
2142
|
+
sqlx: "SQLx"
|
|
2143
|
+
},
|
|
2144
|
+
rustApi: {
|
|
2145
|
+
"async-graphql": "async-graphql",
|
|
2146
|
+
tonic: "Tonic"
|
|
2147
|
+
},
|
|
2148
|
+
rustCli: {
|
|
2149
|
+
clap: "Clap",
|
|
2150
|
+
ratatui: "Ratatui"
|
|
2151
|
+
},
|
|
2152
|
+
rustLibraries: {
|
|
2153
|
+
serde: "Serde",
|
|
2154
|
+
validator: "Validator",
|
|
2155
|
+
jsonwebtoken: "jsonwebtoken",
|
|
2156
|
+
argon2: "Argon2",
|
|
2157
|
+
"tokio-test": "Tokio Test",
|
|
2158
|
+
mockall: "Mockall"
|
|
2159
|
+
},
|
|
2160
|
+
pythonWebFramework: {
|
|
2161
|
+
fastapi: "FastAPI",
|
|
2162
|
+
django: "Django"
|
|
2163
|
+
},
|
|
2164
|
+
pythonOrm: {
|
|
2165
|
+
sqlalchemy: "SQLAlchemy",
|
|
2166
|
+
sqlmodel: "SQLModel"
|
|
2167
|
+
},
|
|
2168
|
+
pythonValidation: { pydantic: "Pydantic" },
|
|
2169
|
+
pythonAi: {
|
|
2170
|
+
langchain: "LangChain",
|
|
2171
|
+
llamaindex: "LlamaIndex",
|
|
2172
|
+
"openai-sdk": "OpenAI SDK",
|
|
2173
|
+
"anthropic-sdk": "Anthropic SDK",
|
|
2174
|
+
langgraph: "LangGraph",
|
|
2175
|
+
crewai: "CrewAI"
|
|
2176
|
+
},
|
|
2177
|
+
pythonTaskQueue: { celery: "Celery" },
|
|
2178
|
+
pythonQuality: { ruff: "Ruff" },
|
|
2179
|
+
goWebFramework: {
|
|
2180
|
+
gin: "Gin",
|
|
2181
|
+
echo: "Echo"
|
|
2182
|
+
},
|
|
2183
|
+
goOrm: {
|
|
2184
|
+
gorm: "GORM",
|
|
2185
|
+
sqlc: "sqlc"
|
|
2186
|
+
},
|
|
2187
|
+
goApi: { "grpc-go": "gRPC-Go" },
|
|
2188
|
+
goCli: {
|
|
2189
|
+
cobra: "Cobra",
|
|
2190
|
+
bubbletea: "Bubble Tea"
|
|
2191
|
+
},
|
|
2192
|
+
goLogging: { zap: "Zap" }
|
|
2193
|
+
};
|
|
2194
|
+
const OPTION_ALIASES = {
|
|
2195
|
+
webFrontend: { svelte: ["sveltekit"] },
|
|
2196
|
+
backend: { "self-svelte": ["self-sveltekit"] }
|
|
2197
|
+
};
|
|
2198
|
+
const CLI_VALUE_OVERRIDES = { backend: {
|
|
2199
|
+
"self-next": "self",
|
|
2200
|
+
"self-tanstack-start": "self",
|
|
2201
|
+
"self-astro": "self",
|
|
2202
|
+
"self-nuxt": "self",
|
|
2203
|
+
"self-svelte": "self",
|
|
2204
|
+
"self-solid-start": "self"
|
|
2205
|
+
} };
|
|
2206
|
+
const TOKEN_LABELS = {
|
|
2207
|
+
ai: "AI",
|
|
2208
|
+
api: "API",
|
|
2209
|
+
auth: "Auth",
|
|
2210
|
+
css: "CSS",
|
|
2211
|
+
db: "DB",
|
|
2212
|
+
graphql: "GraphQL",
|
|
2213
|
+
grpc: "gRPC",
|
|
2214
|
+
js: "JS",
|
|
2215
|
+
md: "MD",
|
|
2216
|
+
orm: "ORM",
|
|
2217
|
+
sdk: "SDK",
|
|
2218
|
+
ses: "SES",
|
|
2219
|
+
sql: "SQL",
|
|
2220
|
+
ui: "UI"
|
|
2221
|
+
};
|
|
2222
|
+
function toStartCaseToken(token) {
|
|
2223
|
+
const lower = token.toLowerCase();
|
|
2224
|
+
if (TOKEN_LABELS[lower]) return TOKEN_LABELS[lower];
|
|
2225
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
2226
|
+
}
|
|
2227
|
+
function humanizeOptionId(id) {
|
|
2228
|
+
return id.split("-").filter(Boolean).map(toStartCaseToken).join(" ");
|
|
2229
|
+
}
|
|
2230
|
+
function getOptionLabel(category, id) {
|
|
2231
|
+
if (category === "auth") return getCapabilityDefinitions("auth").find((option) => option.id === id)?.label ?? humanizeOptionId(id);
|
|
2232
|
+
return EXACT_LABEL_OVERRIDES[category]?.[id] ?? humanizeOptionId(id);
|
|
2233
|
+
}
|
|
2234
|
+
function getOptionAliases(category, id) {
|
|
2235
|
+
return OPTION_ALIASES[category]?.[id] ?? [];
|
|
2236
|
+
}
|
|
2237
|
+
function getCliValue(category, id) {
|
|
2238
|
+
return CLI_VALUE_OVERRIDES[category]?.[id] ?? id;
|
|
2239
|
+
}
|
|
2240
|
+
function buildCategoryMetadata(category) {
|
|
2241
|
+
return {
|
|
2242
|
+
selectionMode: MULTI_SELECT_CATEGORIES.has(category) ? "multiple" : "single",
|
|
2243
|
+
options: CATEGORY_VALUE_IDS[category].map((id) => ({
|
|
2244
|
+
id,
|
|
2245
|
+
label: getOptionLabel(category, id),
|
|
2246
|
+
aliases: getOptionAliases(category, id),
|
|
2247
|
+
cliValue: getCliValue(category, id)
|
|
2248
|
+
}))
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
const OPTION_CATEGORY_METADATA = {
|
|
2252
|
+
api: buildCategoryMetadata("api"),
|
|
2253
|
+
webFrontend: buildCategoryMetadata("webFrontend"),
|
|
2254
|
+
nativeFrontend: buildCategoryMetadata("nativeFrontend"),
|
|
2255
|
+
astroIntegration: buildCategoryMetadata("astroIntegration"),
|
|
2256
|
+
runtime: buildCategoryMetadata("runtime"),
|
|
2257
|
+
backend: buildCategoryMetadata("backend"),
|
|
2258
|
+
database: buildCategoryMetadata("database"),
|
|
2259
|
+
orm: buildCategoryMetadata("orm"),
|
|
2260
|
+
dbSetup: buildCategoryMetadata("dbSetup"),
|
|
2261
|
+
webDeploy: buildCategoryMetadata("webDeploy"),
|
|
2262
|
+
serverDeploy: buildCategoryMetadata("serverDeploy"),
|
|
2263
|
+
auth: buildCategoryMetadata("auth"),
|
|
2264
|
+
payments: buildCategoryMetadata("payments"),
|
|
2265
|
+
email: buildCategoryMetadata("email"),
|
|
2266
|
+
fileUpload: buildCategoryMetadata("fileUpload"),
|
|
2267
|
+
logging: buildCategoryMetadata("logging"),
|
|
2268
|
+
observability: buildCategoryMetadata("observability"),
|
|
2269
|
+
backendLibraries: buildCategoryMetadata("backendLibraries"),
|
|
2270
|
+
stateManagement: buildCategoryMetadata("stateManagement"),
|
|
2271
|
+
forms: buildCategoryMetadata("forms"),
|
|
2272
|
+
validation: buildCategoryMetadata("validation"),
|
|
2273
|
+
testing: buildCategoryMetadata("testing"),
|
|
2274
|
+
realtime: buildCategoryMetadata("realtime"),
|
|
2275
|
+
jobQueue: buildCategoryMetadata("jobQueue"),
|
|
2276
|
+
caching: buildCategoryMetadata("caching"),
|
|
2277
|
+
search: buildCategoryMetadata("search"),
|
|
2278
|
+
fileStorage: buildCategoryMetadata("fileStorage"),
|
|
2279
|
+
animation: buildCategoryMetadata("animation"),
|
|
2280
|
+
cssFramework: buildCategoryMetadata("cssFramework"),
|
|
2281
|
+
uiLibrary: buildCategoryMetadata("uiLibrary"),
|
|
2282
|
+
cms: buildCategoryMetadata("cms"),
|
|
2283
|
+
featureFlags: buildCategoryMetadata("featureFlags"),
|
|
2284
|
+
analytics: buildCategoryMetadata("analytics"),
|
|
2285
|
+
codeQuality: buildCategoryMetadata("codeQuality"),
|
|
2286
|
+
documentation: buildCategoryMetadata("documentation"),
|
|
2287
|
+
appPlatforms: buildCategoryMetadata("appPlatforms"),
|
|
2288
|
+
packageManager: buildCategoryMetadata("packageManager"),
|
|
2289
|
+
examples: buildCategoryMetadata("examples"),
|
|
2290
|
+
ai: buildCategoryMetadata("ai"),
|
|
2291
|
+
aiDocs: buildCategoryMetadata("aiDocs"),
|
|
2292
|
+
git: buildCategoryMetadata("git"),
|
|
2293
|
+
install: buildCategoryMetadata("install"),
|
|
2294
|
+
effect: buildCategoryMetadata("effect"),
|
|
2295
|
+
shadcnBase: buildCategoryMetadata("shadcnBase"),
|
|
2296
|
+
shadcnStyle: buildCategoryMetadata("shadcnStyle"),
|
|
2297
|
+
shadcnIconLibrary: buildCategoryMetadata("shadcnIconLibrary"),
|
|
2298
|
+
shadcnColorTheme: buildCategoryMetadata("shadcnColorTheme"),
|
|
2299
|
+
shadcnBaseColor: buildCategoryMetadata("shadcnBaseColor"),
|
|
2300
|
+
shadcnFont: buildCategoryMetadata("shadcnFont"),
|
|
2301
|
+
shadcnRadius: buildCategoryMetadata("shadcnRadius"),
|
|
2302
|
+
rustWebFramework: buildCategoryMetadata("rustWebFramework"),
|
|
2303
|
+
rustFrontend: buildCategoryMetadata("rustFrontend"),
|
|
2304
|
+
rustOrm: buildCategoryMetadata("rustOrm"),
|
|
2305
|
+
rustApi: buildCategoryMetadata("rustApi"),
|
|
2306
|
+
rustCli: buildCategoryMetadata("rustCli"),
|
|
2307
|
+
rustLibraries: buildCategoryMetadata("rustLibraries"),
|
|
2308
|
+
pythonWebFramework: buildCategoryMetadata("pythonWebFramework"),
|
|
2309
|
+
pythonOrm: buildCategoryMetadata("pythonOrm"),
|
|
2310
|
+
pythonValidation: buildCategoryMetadata("pythonValidation"),
|
|
2311
|
+
pythonAi: buildCategoryMetadata("pythonAi"),
|
|
2312
|
+
pythonTaskQueue: buildCategoryMetadata("pythonTaskQueue"),
|
|
2313
|
+
pythonQuality: buildCategoryMetadata("pythonQuality"),
|
|
2314
|
+
goWebFramework: buildCategoryMetadata("goWebFramework"),
|
|
2315
|
+
goOrm: buildCategoryMetadata("goOrm"),
|
|
2316
|
+
goApi: buildCategoryMetadata("goApi"),
|
|
2317
|
+
goCli: buildCategoryMetadata("goCli"),
|
|
2318
|
+
goLogging: buildCategoryMetadata("goLogging")
|
|
2319
|
+
};
|
|
2320
|
+
const OPTION_LOOKUP = Object.fromEntries(Object.entries(OPTION_CATEGORY_METADATA).map(([category, metadata]) => [category, new Map(metadata.options.flatMap((option) => [[option.id.toLowerCase(), option.id], ...option.aliases.map((alias) => [alias.toLowerCase(), option.id])]))]));
|
|
2321
|
+
function isMultiSelectCategory(category) {
|
|
2322
|
+
return OPTION_CATEGORY_METADATA[category].selectionMode === "multiple";
|
|
2323
|
+
}
|
|
2324
|
+
function getOptionMetadata(category, optionId) {
|
|
2325
|
+
return OPTION_CATEGORY_METADATA[category].options.find((option) => option.id === optionId);
|
|
2326
|
+
}
|
|
2327
|
+
function getCategoryOptionIds(category) {
|
|
2328
|
+
return OPTION_CATEGORY_METADATA[category].options.map((option) => option.id);
|
|
2329
|
+
}
|
|
2330
|
+
function getCategoryCliValues(category) {
|
|
2331
|
+
return [...new Set(OPTION_CATEGORY_METADATA[category].options.map((option) => option.cliValue))];
|
|
2332
|
+
}
|
|
2333
|
+
function normalizeOptionId(category, value) {
|
|
2334
|
+
return OPTION_LOOKUP[category].get(value.toLowerCase()) ?? value;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
//#endregion
|
|
2338
|
+
//#region src/local-dev.ts
|
|
2339
|
+
const VITE_WEB_FRONTENDS = new Set([
|
|
2340
|
+
"react-router",
|
|
2341
|
+
"svelte",
|
|
2342
|
+
"fresh"
|
|
2343
|
+
]);
|
|
2344
|
+
function getLocalWebDevPort(frontend) {
|
|
2345
|
+
return frontend.some((entry) => VITE_WEB_FRONTENDS.has(entry)) ? 5173 : 3001;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
//#endregion
|
|
2349
|
+
export { ADDONS_VALUES, AISchema, AI_DOCS_VALUES, AI_VALUES, ANALYTICS_VALUES, ANIMATION_VALUES, APISchema, API_VALUES, ASTRO_INTEGRATION_VALUES, AUTH_VALUES, AddInputSchema, AddonsSchema, AiDocsSchema, AnalyticsSchema, AnimationSchema, AstroIntegrationSchema, AuthSchema, BACKEND_VALUES, BackendSchema, BetterTStackConfigFileSchema, BetterTStackConfigSchema, CACHING_VALUES, CLIInputSchema, CMSSchema, CMS_VALUES, CSSFrameworkSchema, CSS_FRAMEWORK_VALUES, CachingSchema, CreateInputSchema, DATABASE_SETUP_VALUES, DATABASE_VALUES, DIRECTORY_CONFLICT_VALUES, DatabaseSchema, DatabaseSetupSchema, DirectoryConflictSchema, ECOSYSTEM_VALUES, EFFECT_VALUES, EMAIL_VALUES, EXAMPLES_VALUES, EcosystemSchema, EffectSchema, EmailSchema, ExamplesSchema, FEATURE_FLAGS_VALUES, FILE_STORAGE_VALUES, FILE_UPLOAD_VALUES, FORMS_VALUES, FRONTEND_VALUES, FeatureFlagsSchema, FileStorageSchema, FileUploadSchema, FormsSchema, FrontendSchema, GO_API_VALUES, GO_CLI_VALUES, GO_LOGGING_VALUES, GO_ORM_VALUES, GO_WEB_FRAMEWORK_VALUES, GoApiSchema, GoCliSchema, GoLoggingSchema, GoOrmSchema, GoWebFrameworkSchema, InitResultSchema, JOB_QUEUE_VALUES, JobQueueSchema, LOGGING_VALUES, LoggingSchema, OBSERVABILITY_VALUES, OPTION_CATEGORY_METADATA, ORMSchema, ORM_VALUES, ObservabilitySchema, PACKAGE_MANAGER_VALUES, PAYMENTS_VALUES, PYTHON_AI_VALUES, PYTHON_ORM_VALUES, PYTHON_QUALITY_VALUES, PYTHON_TASK_QUEUE_VALUES, PYTHON_VALIDATION_VALUES, PYTHON_WEB_FRAMEWORK_VALUES, PackageManagerSchema, PaymentsSchema, ProjectConfigSchema, ProjectNameSchema, PythonAiSchema, PythonOrmSchema, PythonQualitySchema, PythonTaskQueueSchema, PythonValidationSchema, PythonWebFrameworkSchema, REALTIME_VALUES, RUNTIME_VALUES, RUST_API_VALUES, RUST_CLI_VALUES, RUST_FRONTEND_VALUES, RUST_LIBRARIES_VALUES, RUST_ORM_VALUES, RUST_WEB_FRAMEWORK_VALUES, RealtimeSchema, RuntimeSchema, RustApiSchema, RustCliSchema, RustFrontendSchema, RustLibrariesSchema, RustOrmSchema, RustWebFrameworkSchema, SEARCH_VALUES, SERVER_DEPLOY_VALUES, SHADCN_BASE_COLOR_VALUES, SHADCN_BASE_VALUES, SHADCN_COLOR_THEME_VALUES, SHADCN_FONT_VALUES, SHADCN_ICON_LIBRARY_VALUES, SHADCN_RADIUS_VALUES, SHADCN_STYLE_VALUES, STATE_MANAGEMENT_VALUES, SearchSchema, ServerDeploySchema, ShadcnBaseColorSchema, ShadcnBaseSchema, ShadcnColorThemeSchema, ShadcnFontSchema, ShadcnIconLibrarySchema, ShadcnRadiusSchema, ShadcnStyleSchema, StateManagementSchema, TEMPLATE_VALUES, TESTING_VALUES, TemplateSchema, TestingSchema, UILibrarySchema, UI_LIBRARY_VALUES, VALIDATION_VALUES, ValidationSchema, WEB_DEPLOY_VALUES, WebDeploySchema, allowedApisForFrontends, analyzeStackCompatibility, evaluateCompatibility, getCapabilityDefinitions, getCapabilityDisabledReason, getCategoryCliValues, getCategoryDisplayName, getCategoryOptionIds, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleFormLibraries, getCompatibleUILibraries, getDisabledReason, getLocalWebDevPort, getOptionMetadata, getSupportedCapabilityOptions, hasPWACompatibleFrontend, hasTauriCompatibleFrontend, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isMultiSelectCategory, isOptionCompatible, isWebFrontend, normalizeCapabilitySelection, normalizeOptionId, requiresChatSdkVercelAI, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility, validateProjectName };
|
|
2350
|
+
//# sourceMappingURL=index.mjs.map
|