create-better-fullstack 1.3.16 → 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/README.md +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +96 -6
- package/dist/index.mjs +1 -1
- package/dist/{src-zCIIp5Gn.mjs → src-CISBB09-.mjs} +646 -627
- package/package.json +3 -3
|
@@ -13,13 +13,14 @@ import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TE
|
|
|
13
13
|
import gradient from "gradient-string";
|
|
14
14
|
import path$1 from "path";
|
|
15
15
|
import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-writer";
|
|
16
|
+
import { allowedApisForFrontends, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getLocalWebDevPort, hasWebStyling, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
|
|
16
17
|
import consola, { consola as consola$1 } from "consola";
|
|
17
18
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
19
|
import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
|
|
19
20
|
import * as JSONC from "jsonc-parser";
|
|
20
21
|
import { $, execa } from "execa";
|
|
21
|
-
import { format } from "oxfmt";
|
|
22
22
|
import os$1 from "node:os";
|
|
23
|
+
import { format } from "oxfmt";
|
|
23
24
|
|
|
24
25
|
//#region src/utils/get-package-manager.ts
|
|
25
26
|
const getUserPkgManager = () => {
|
|
@@ -74,6 +75,13 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
74
75
|
serverDeploy: "none",
|
|
75
76
|
cssFramework: "tailwind",
|
|
76
77
|
uiLibrary: "shadcn-ui",
|
|
78
|
+
shadcnBase: "radix",
|
|
79
|
+
shadcnStyle: "nova",
|
|
80
|
+
shadcnIconLibrary: "lucide",
|
|
81
|
+
shadcnColorTheme: "neutral",
|
|
82
|
+
shadcnBaseColor: "neutral",
|
|
83
|
+
shadcnFont: "inter",
|
|
84
|
+
shadcnRadius: "default",
|
|
77
85
|
rustWebFramework: "none",
|
|
78
86
|
rustFrontend: "none",
|
|
79
87
|
rustOrm: "none",
|
|
@@ -107,254 +115,6 @@ function getDefaultConfig() {
|
|
|
107
115
|
};
|
|
108
116
|
}
|
|
109
117
|
const DEFAULT_CONFIG = getDefaultConfig();
|
|
110
|
-
const ADDON_COMPATIBILITY = {
|
|
111
|
-
pwa: [
|
|
112
|
-
"tanstack-router",
|
|
113
|
-
"react-router",
|
|
114
|
-
"solid",
|
|
115
|
-
"next",
|
|
116
|
-
"astro",
|
|
117
|
-
"qwik",
|
|
118
|
-
"angular",
|
|
119
|
-
"redwood",
|
|
120
|
-
"fresh"
|
|
121
|
-
],
|
|
122
|
-
tauri: [
|
|
123
|
-
"tanstack-router",
|
|
124
|
-
"react-router",
|
|
125
|
-
"nuxt",
|
|
126
|
-
"svelte",
|
|
127
|
-
"solid",
|
|
128
|
-
"next",
|
|
129
|
-
"astro",
|
|
130
|
-
"qwik",
|
|
131
|
-
"angular",
|
|
132
|
-
"redwood",
|
|
133
|
-
"fresh"
|
|
134
|
-
],
|
|
135
|
-
biome: [],
|
|
136
|
-
husky: [],
|
|
137
|
-
lefthook: [],
|
|
138
|
-
turborepo: [],
|
|
139
|
-
starlight: [],
|
|
140
|
-
ultracite: [],
|
|
141
|
-
ruler: [],
|
|
142
|
-
mcp: [],
|
|
143
|
-
skills: [],
|
|
144
|
-
oxlint: [],
|
|
145
|
-
fumadocs: [],
|
|
146
|
-
opentui: [],
|
|
147
|
-
wxt: [],
|
|
148
|
-
msw: [],
|
|
149
|
-
storybook: [
|
|
150
|
-
"tanstack-router",
|
|
151
|
-
"react-router",
|
|
152
|
-
"next",
|
|
153
|
-
"nuxt",
|
|
154
|
-
"svelte",
|
|
155
|
-
"solid"
|
|
156
|
-
],
|
|
157
|
-
none: []
|
|
158
|
-
};
|
|
159
|
-
/**
|
|
160
|
-
* UI Library compatibility rules
|
|
161
|
-
* Defines which frontends and CSS frameworks each UI library supports
|
|
162
|
-
*/
|
|
163
|
-
const UI_LIBRARY_COMPATIBILITY = {
|
|
164
|
-
"shadcn-ui": {
|
|
165
|
-
frontends: [
|
|
166
|
-
"tanstack-router",
|
|
167
|
-
"react-router",
|
|
168
|
-
"tanstack-start",
|
|
169
|
-
"next",
|
|
170
|
-
"astro"
|
|
171
|
-
],
|
|
172
|
-
cssFrameworks: ["tailwind"]
|
|
173
|
-
},
|
|
174
|
-
daisyui: {
|
|
175
|
-
frontends: [
|
|
176
|
-
"tanstack-router",
|
|
177
|
-
"react-router",
|
|
178
|
-
"tanstack-start",
|
|
179
|
-
"next",
|
|
180
|
-
"nuxt",
|
|
181
|
-
"svelte",
|
|
182
|
-
"solid",
|
|
183
|
-
"astro",
|
|
184
|
-
"qwik",
|
|
185
|
-
"angular",
|
|
186
|
-
"redwood",
|
|
187
|
-
"fresh"
|
|
188
|
-
],
|
|
189
|
-
cssFrameworks: ["tailwind"]
|
|
190
|
-
},
|
|
191
|
-
"radix-ui": {
|
|
192
|
-
frontends: [
|
|
193
|
-
"tanstack-router",
|
|
194
|
-
"react-router",
|
|
195
|
-
"tanstack-start",
|
|
196
|
-
"next",
|
|
197
|
-
"astro"
|
|
198
|
-
],
|
|
199
|
-
cssFrameworks: [
|
|
200
|
-
"tailwind",
|
|
201
|
-
"scss",
|
|
202
|
-
"less",
|
|
203
|
-
"postcss-only",
|
|
204
|
-
"none"
|
|
205
|
-
]
|
|
206
|
-
},
|
|
207
|
-
"headless-ui": {
|
|
208
|
-
frontends: [
|
|
209
|
-
"tanstack-router",
|
|
210
|
-
"react-router",
|
|
211
|
-
"tanstack-start",
|
|
212
|
-
"next",
|
|
213
|
-
"nuxt",
|
|
214
|
-
"astro"
|
|
215
|
-
],
|
|
216
|
-
cssFrameworks: [
|
|
217
|
-
"tailwind",
|
|
218
|
-
"scss",
|
|
219
|
-
"less",
|
|
220
|
-
"postcss-only",
|
|
221
|
-
"none"
|
|
222
|
-
]
|
|
223
|
-
},
|
|
224
|
-
"park-ui": {
|
|
225
|
-
frontends: [
|
|
226
|
-
"tanstack-router",
|
|
227
|
-
"react-router",
|
|
228
|
-
"tanstack-start",
|
|
229
|
-
"next",
|
|
230
|
-
"nuxt",
|
|
231
|
-
"solid",
|
|
232
|
-
"astro"
|
|
233
|
-
],
|
|
234
|
-
cssFrameworks: [
|
|
235
|
-
"tailwind",
|
|
236
|
-
"scss",
|
|
237
|
-
"less",
|
|
238
|
-
"postcss-only"
|
|
239
|
-
]
|
|
240
|
-
},
|
|
241
|
-
"chakra-ui": {
|
|
242
|
-
frontends: [
|
|
243
|
-
"tanstack-router",
|
|
244
|
-
"react-router",
|
|
245
|
-
"tanstack-start",
|
|
246
|
-
"next",
|
|
247
|
-
"astro"
|
|
248
|
-
],
|
|
249
|
-
cssFrameworks: [
|
|
250
|
-
"tailwind",
|
|
251
|
-
"scss",
|
|
252
|
-
"less",
|
|
253
|
-
"postcss-only",
|
|
254
|
-
"none"
|
|
255
|
-
]
|
|
256
|
-
},
|
|
257
|
-
nextui: {
|
|
258
|
-
frontends: [
|
|
259
|
-
"tanstack-router",
|
|
260
|
-
"react-router",
|
|
261
|
-
"tanstack-start",
|
|
262
|
-
"next",
|
|
263
|
-
"astro"
|
|
264
|
-
],
|
|
265
|
-
cssFrameworks: ["tailwind"]
|
|
266
|
-
},
|
|
267
|
-
mantine: {
|
|
268
|
-
frontends: [
|
|
269
|
-
"tanstack-router",
|
|
270
|
-
"react-router",
|
|
271
|
-
"tanstack-start",
|
|
272
|
-
"next",
|
|
273
|
-
"astro"
|
|
274
|
-
],
|
|
275
|
-
cssFrameworks: [
|
|
276
|
-
"tailwind",
|
|
277
|
-
"scss",
|
|
278
|
-
"less",
|
|
279
|
-
"postcss-only",
|
|
280
|
-
"none"
|
|
281
|
-
]
|
|
282
|
-
},
|
|
283
|
-
"base-ui": {
|
|
284
|
-
frontends: [
|
|
285
|
-
"tanstack-router",
|
|
286
|
-
"react-router",
|
|
287
|
-
"tanstack-start",
|
|
288
|
-
"next",
|
|
289
|
-
"astro"
|
|
290
|
-
],
|
|
291
|
-
cssFrameworks: [
|
|
292
|
-
"tailwind",
|
|
293
|
-
"scss",
|
|
294
|
-
"less",
|
|
295
|
-
"postcss-only",
|
|
296
|
-
"none"
|
|
297
|
-
]
|
|
298
|
-
},
|
|
299
|
-
"ark-ui": {
|
|
300
|
-
frontends: [
|
|
301
|
-
"tanstack-router",
|
|
302
|
-
"react-router",
|
|
303
|
-
"tanstack-start",
|
|
304
|
-
"next",
|
|
305
|
-
"nuxt",
|
|
306
|
-
"svelte",
|
|
307
|
-
"solid",
|
|
308
|
-
"astro"
|
|
309
|
-
],
|
|
310
|
-
cssFrameworks: [
|
|
311
|
-
"tailwind",
|
|
312
|
-
"scss",
|
|
313
|
-
"less",
|
|
314
|
-
"postcss-only",
|
|
315
|
-
"none"
|
|
316
|
-
]
|
|
317
|
-
},
|
|
318
|
-
"react-aria": {
|
|
319
|
-
frontends: [
|
|
320
|
-
"tanstack-router",
|
|
321
|
-
"react-router",
|
|
322
|
-
"tanstack-start",
|
|
323
|
-
"next",
|
|
324
|
-
"astro"
|
|
325
|
-
],
|
|
326
|
-
cssFrameworks: [
|
|
327
|
-
"tailwind",
|
|
328
|
-
"scss",
|
|
329
|
-
"less",
|
|
330
|
-
"postcss-only",
|
|
331
|
-
"none"
|
|
332
|
-
]
|
|
333
|
-
},
|
|
334
|
-
none: {
|
|
335
|
-
frontends: [
|
|
336
|
-
"tanstack-router",
|
|
337
|
-
"react-router",
|
|
338
|
-
"tanstack-start",
|
|
339
|
-
"next",
|
|
340
|
-
"nuxt",
|
|
341
|
-
"svelte",
|
|
342
|
-
"solid",
|
|
343
|
-
"astro",
|
|
344
|
-
"qwik",
|
|
345
|
-
"angular",
|
|
346
|
-
"redwood",
|
|
347
|
-
"fresh"
|
|
348
|
-
],
|
|
349
|
-
cssFrameworks: [
|
|
350
|
-
"tailwind",
|
|
351
|
-
"scss",
|
|
352
|
-
"less",
|
|
353
|
-
"postcss-only",
|
|
354
|
-
"none"
|
|
355
|
-
]
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
118
|
/**
|
|
359
119
|
* Default UI library for each frontend framework
|
|
360
120
|
* Falls back based on what's compatible
|
|
@@ -367,6 +127,7 @@ const DEFAULT_UI_LIBRARY_BY_FRONTEND = {
|
|
|
367
127
|
nuxt: "daisyui",
|
|
368
128
|
svelte: "daisyui",
|
|
369
129
|
solid: "daisyui",
|
|
130
|
+
"solid-start": "daisyui",
|
|
370
131
|
astro: "daisyui",
|
|
371
132
|
qwik: "daisyui",
|
|
372
133
|
angular: "daisyui",
|
|
@@ -542,6 +303,7 @@ async function historyHandler(input) {
|
|
|
542
303
|
*/
|
|
543
304
|
function formatUpdate(info) {
|
|
544
305
|
const colorFn = {
|
|
306
|
+
downgrade: pc.red,
|
|
545
307
|
major: pc.red,
|
|
546
308
|
minor: pc.yellow,
|
|
547
309
|
patch: pc.green,
|
|
@@ -597,6 +359,7 @@ async function interactiveUpdate(updates) {
|
|
|
597
359
|
console.log(formatUpdate(update));
|
|
598
360
|
if (update.ecosystem) console.log(pc.dim(` Ecosystem: ${update.ecosystem}`));
|
|
599
361
|
if (update.updateType === "major") console.log(pc.yellow(" Warning: Breaking changes possible. Check changelog."));
|
|
362
|
+
else if (update.updateType === "downgrade") console.log(pc.red(" Warning: npm latest is lower than current pinned version. Review carefully."));
|
|
600
363
|
const action = await select({
|
|
601
364
|
message: "What would you like to do?",
|
|
602
365
|
options: [
|
|
@@ -652,6 +415,7 @@ async function updateDepsHandler(options) {
|
|
|
652
415
|
console.log(generateCliReport(result));
|
|
653
416
|
if (check || result.outdated.length === 0) return;
|
|
654
417
|
let toApply = [];
|
|
418
|
+
const downgradeCount = result.outdated.filter((u) => u.updateType === "downgrade").length;
|
|
655
419
|
if (patch) {
|
|
656
420
|
toApply = result.outdated.filter((u) => u.updateType === "patch" || u.updateType === "minor");
|
|
657
421
|
if (toApply.length === 0) {
|
|
@@ -659,6 +423,7 @@ async function updateDepsHandler(options) {
|
|
|
659
423
|
return;
|
|
660
424
|
}
|
|
661
425
|
log.info(`Found ${toApply.length} patch/minor updates to apply automatically.`);
|
|
426
|
+
if (downgradeCount > 0) log.warn(`${downgradeCount} downgrade${downgradeCount === 1 ? "" : "s"} detected and excluded from --patch mode.`);
|
|
662
427
|
const shouldProceed = await confirm({ message: `Apply ${toApply.length} safe updates?` });
|
|
663
428
|
if (isCancel(shouldProceed) || !shouldProceed) {
|
|
664
429
|
log.info("Cancelled.");
|
|
@@ -672,7 +437,7 @@ async function updateDepsHandler(options) {
|
|
|
672
437
|
return;
|
|
673
438
|
}
|
|
674
439
|
} else {
|
|
675
|
-
const shouldProceed = await confirm({ message: `Apply all ${result.outdated.length} updates?` });
|
|
440
|
+
const shouldProceed = await confirm({ message: downgradeCount > 0 ? `Apply all ${result.outdated.length} updates (including ${downgradeCount} downgrade${downgradeCount === 1 ? "" : "s"})?` : `Apply all ${result.outdated.length} updates?` });
|
|
676
441
|
if (isCancel(shouldProceed) || !shouldProceed) {
|
|
677
442
|
log.info("Cancelled. Use --check to only view updates without prompting.");
|
|
678
443
|
return;
|
|
@@ -715,23 +480,6 @@ var types_exports = {};
|
|
|
715
480
|
import * as import__better_fullstack_types from "@better-fullstack/types";
|
|
716
481
|
__reExport(types_exports, import__better_fullstack_types);
|
|
717
482
|
|
|
718
|
-
//#endregion
|
|
719
|
-
//#region src/utils/compatibility.ts
|
|
720
|
-
const WEB_FRAMEWORKS = [
|
|
721
|
-
"tanstack-router",
|
|
722
|
-
"react-router",
|
|
723
|
-
"tanstack-start",
|
|
724
|
-
"next",
|
|
725
|
-
"nuxt",
|
|
726
|
-
"svelte",
|
|
727
|
-
"solid",
|
|
728
|
-
"astro",
|
|
729
|
-
"qwik",
|
|
730
|
-
"angular",
|
|
731
|
-
"redwood",
|
|
732
|
-
"fresh"
|
|
733
|
-
];
|
|
734
|
-
|
|
735
483
|
//#endregion
|
|
736
484
|
//#region src/utils/context.ts
|
|
737
485
|
const cliStorage = new AsyncLocalStorage();
|
|
@@ -879,17 +627,14 @@ function constraintError(opts) {
|
|
|
879
627
|
|
|
880
628
|
//#endregion
|
|
881
629
|
//#region src/utils/compatibility-rules.ts
|
|
882
|
-
function isWebFrontend(value) {
|
|
883
|
-
return
|
|
630
|
+
function isWebFrontend$1(value) {
|
|
631
|
+
return isWebFrontend(value);
|
|
884
632
|
}
|
|
885
|
-
function splitFrontends(values = []) {
|
|
886
|
-
return
|
|
887
|
-
web: values.filter((f) => isWebFrontend(f)),
|
|
888
|
-
native: values.filter((f) => f === "native-bare" || f === "native-uniwind" || f === "native-unistyles")
|
|
889
|
-
};
|
|
633
|
+
function splitFrontends$1(values = []) {
|
|
634
|
+
return splitFrontends(values);
|
|
890
635
|
}
|
|
891
636
|
function ensureSingleWebAndNative(frontends) {
|
|
892
|
-
const { web, native } = splitFrontends(frontends);
|
|
637
|
+
const { web, native } = splitFrontends$1(frontends);
|
|
893
638
|
if (web.length > 1) invalidSelectionError({
|
|
894
639
|
message: "Only one web framework can be selected per project.",
|
|
895
640
|
provided: { frontend: web },
|
|
@@ -905,18 +650,20 @@ const FULLSTACK_FRONTENDS$1 = [
|
|
|
905
650
|
"next",
|
|
906
651
|
"tanstack-start",
|
|
907
652
|
"astro",
|
|
908
|
-
"nuxt"
|
|
653
|
+
"nuxt",
|
|
654
|
+
"svelte",
|
|
655
|
+
"solid-start"
|
|
909
656
|
];
|
|
910
657
|
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
911
658
|
const backend = config.backend || options.backend;
|
|
912
659
|
const frontends = config.frontend || options.frontend || [];
|
|
913
660
|
if (backend === "self") {
|
|
914
|
-
const { web, native } = splitFrontends(frontends);
|
|
915
|
-
if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) exitWithError("Backend 'self' (fullstack) only supports Next.js, TanStack Start, Astro, or
|
|
661
|
+
const { web, native } = splitFrontends$1(frontends);
|
|
662
|
+
if (!(web.length === 1 && FULLSTACK_FRONTENDS$1.includes(web[0]))) exitWithError("Backend 'self' (fullstack) only supports Next.js, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, or --frontend solid-start.");
|
|
916
663
|
if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles");
|
|
917
664
|
}
|
|
918
665
|
const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
|
|
919
|
-
if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) only supports Next.js, TanStack Start, Astro, or
|
|
666
|
+
if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) only supports Next.js, TanStack Start, Astro, Nuxt, SvelteKit, or SolidStart frontends. Please use --frontend next, --frontend tanstack-start, --frontend astro, --frontend nuxt, --frontend svelte, --frontend solid-start, or choose a different backend.");
|
|
920
667
|
}
|
|
921
668
|
const WORKERS_COMPATIBLE_BACKENDS = [
|
|
922
669
|
"hono",
|
|
@@ -925,7 +672,7 @@ const WORKERS_COMPATIBLE_BACKENDS = [
|
|
|
925
672
|
];
|
|
926
673
|
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
927
674
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend)) incompatibilityError({
|
|
928
|
-
message: "Cloudflare Workers runtime
|
|
675
|
+
message: "In Better-Fullstack, Cloudflare Workers runtime is currently supported only with compatible backends (Hono, Nitro, or Fets).",
|
|
929
676
|
provided: {
|
|
930
677
|
runtime: "workers",
|
|
931
678
|
backend: config.backend
|
|
@@ -938,7 +685,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
938
685
|
]
|
|
939
686
|
});
|
|
940
687
|
if (providedFlags.has("backend") && config.backend && !WORKERS_COMPATIBLE_BACKENDS.includes(config.backend) && config.runtime === "workers") incompatibilityError({
|
|
941
|
-
message: `
|
|
688
|
+
message: `In Better-Fullstack, backend '${config.backend}' is currently not available with Cloudflare Workers runtime.`,
|
|
942
689
|
provided: {
|
|
943
690
|
backend: config.backend,
|
|
944
691
|
runtime: "workers"
|
|
@@ -946,7 +693,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
946
693
|
suggestions: ["Use --backend hono, --backend nitro, or --backend fets", "Choose a different runtime (node, bun)"]
|
|
947
694
|
});
|
|
948
695
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") incompatibilityError({
|
|
949
|
-
message: "Cloudflare Workers runtime is not
|
|
696
|
+
message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with MongoDB.",
|
|
950
697
|
provided: {
|
|
951
698
|
runtime: "workers",
|
|
952
699
|
database: "mongodb"
|
|
@@ -954,7 +701,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
954
701
|
suggestions: ["Use a different database (postgres, sqlite, mysql)", "Choose a different runtime (node, bun)"]
|
|
955
702
|
});
|
|
956
703
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") incompatibilityError({
|
|
957
|
-
message: "Cloudflare Workers runtime is not
|
|
704
|
+
message: "In Better-Fullstack, Cloudflare Workers runtime is currently not available with Docker database setup.",
|
|
958
705
|
provided: {
|
|
959
706
|
runtime: "workers",
|
|
960
707
|
"db-setup": "docker"
|
|
@@ -962,7 +709,7 @@ function validateWorkersCompatibility(providedFlags, options, config) {
|
|
|
962
709
|
suggestions: ["Use --db-setup d1 for SQLite", "Choose a different runtime (node, bun)"]
|
|
963
710
|
});
|
|
964
711
|
if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") incompatibilityError({
|
|
965
|
-
message: "MongoDB is not
|
|
712
|
+
message: "In Better-Fullstack, MongoDB is currently not available with Cloudflare Workers runtime.",
|
|
966
713
|
provided: {
|
|
967
714
|
database: "mongodb",
|
|
968
715
|
runtime: "workers"
|
|
@@ -979,9 +726,10 @@ function validateApiFrontendCompatibility(api, frontends = [], astroIntegration)
|
|
|
979
726
|
const includesAngular = frontends.includes("angular");
|
|
980
727
|
const includesRedwood = frontends.includes("redwood");
|
|
981
728
|
const includesFresh = frontends.includes("fresh");
|
|
982
|
-
|
|
729
|
+
const includesSolidStart = frontends.includes("solid-start");
|
|
730
|
+
if ((includesNuxt || includesSvelte || includesSolid || includesSolidStart) && (api === "trpc" || api === "ts-rest" || api === "garph")) {
|
|
983
731
|
const apiName = api === "trpc" ? "tRPC" : api === "ts-rest" ? "ts-rest" : "garph";
|
|
984
|
-
const incompatibleFrontend = includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid";
|
|
732
|
+
const incompatibleFrontend = includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "solid-start";
|
|
985
733
|
incompatibilityError({
|
|
986
734
|
message: `${apiName} API requires React-based frontends.`,
|
|
987
735
|
provided: {
|
|
@@ -1040,90 +788,20 @@ function validateApiFrontendCompatibility(api, frontends = [], astroIntegration)
|
|
|
1040
788
|
]
|
|
1041
789
|
});
|
|
1042
790
|
}
|
|
1043
|
-
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
1044
|
-
|
|
1045
|
-
if (backend === "convex" && frontend === "astro") return false;
|
|
1046
|
-
if (backend === "convex" && frontend === "qwik") return false;
|
|
1047
|
-
if (backend === "convex" && frontend === "angular") return false;
|
|
1048
|
-
if (backend === "convex" && frontend === "redwood") return false;
|
|
1049
|
-
if (backend === "convex" && frontend === "fresh") return false;
|
|
1050
|
-
if (frontend === "qwik" && backend && backend !== "none") return false;
|
|
1051
|
-
if (frontend === "angular" && backend && backend !== "none") return false;
|
|
1052
|
-
if (frontend === "redwood" && backend && backend !== "none") return false;
|
|
1053
|
-
if (frontend === "fresh" && backend && backend !== "none") return false;
|
|
1054
|
-
if (auth === "clerk" && backend === "convex") {
|
|
1055
|
-
if ([
|
|
1056
|
-
"nuxt",
|
|
1057
|
-
"svelte",
|
|
1058
|
-
"solid"
|
|
1059
|
-
].includes(frontend)) return false;
|
|
1060
|
-
}
|
|
1061
|
-
if (auth === "nextauth") {
|
|
1062
|
-
if (frontend !== "next") return false;
|
|
1063
|
-
if (backend !== "self") return false;
|
|
1064
|
-
}
|
|
1065
|
-
if (auth === "supabase-auth") {
|
|
1066
|
-
if (frontend !== "next") return false;
|
|
1067
|
-
if (backend !== "self") return false;
|
|
1068
|
-
}
|
|
1069
|
-
return true;
|
|
791
|
+
function isFrontendAllowedWithBackend$1(frontend, backend, auth) {
|
|
792
|
+
return isFrontendAllowedWithBackend(frontend, backend, auth);
|
|
1070
793
|
}
|
|
1071
|
-
function
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
}
|
|
1083
|
-
function validateSupabaseAuthCompatibility(auth, backend, frontends = []) {
|
|
1084
|
-
if (auth !== "supabase-auth") return;
|
|
1085
|
-
const hasNextJs = frontends.includes("next");
|
|
1086
|
-
if (backend !== "self") exitWithError("Supabase Auth is only supported with the 'self' backend (fullstack Next.js). Please use '--backend self' or choose a different auth provider.");
|
|
1087
|
-
if (!hasNextJs) exitWithError("Supabase Auth requires Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1088
|
-
}
|
|
1089
|
-
function validateAuth0Compatibility(auth, backend, frontends = []) {
|
|
1090
|
-
if (auth !== "auth0") return;
|
|
1091
|
-
const hasNextJs = frontends.includes("next");
|
|
1092
|
-
if (backend !== "self") exitWithError("Auth0 is only supported with the 'self' backend (fullstack Next.js). Please use '--backend self' or choose a different auth provider.");
|
|
1093
|
-
if (!hasNextJs) exitWithError("Auth0 requires Next.js frontend. Please use '--frontend next' or choose a different auth provider.");
|
|
1094
|
-
}
|
|
1095
|
-
function allowedApisForFrontends(frontends = [], astroIntegration) {
|
|
1096
|
-
const includesNuxt = frontends.includes("nuxt");
|
|
1097
|
-
const includesSvelte = frontends.includes("svelte");
|
|
1098
|
-
const includesSolid = frontends.includes("solid");
|
|
1099
|
-
const includesAstro = frontends.includes("astro");
|
|
1100
|
-
const includesQwik = frontends.includes("qwik");
|
|
1101
|
-
const includesAngular = frontends.includes("angular");
|
|
1102
|
-
const includesRedwood = frontends.includes("redwood");
|
|
1103
|
-
const includesFresh = frontends.includes("fresh");
|
|
1104
|
-
const base = [
|
|
1105
|
-
"trpc",
|
|
1106
|
-
"orpc",
|
|
1107
|
-
"ts-rest",
|
|
1108
|
-
"garph",
|
|
1109
|
-
"none"
|
|
1110
|
-
];
|
|
1111
|
-
if (includesQwik) return ["none"];
|
|
1112
|
-
if (includesAngular) return ["none"];
|
|
1113
|
-
if (includesRedwood) return ["none"];
|
|
1114
|
-
if (includesFresh) return ["none"];
|
|
1115
|
-
if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
|
|
1116
|
-
if (includesAstro && astroIntegration && astroIntegration !== "react") return ["orpc", "none"];
|
|
1117
|
-
return base;
|
|
1118
|
-
}
|
|
1119
|
-
function isExampleAIAllowed(backend, frontends = []) {
|
|
1120
|
-
if (frontends.includes("solid")) return false;
|
|
1121
|
-
if (backend === "convex") {
|
|
1122
|
-
const includesNuxt = frontends.includes("nuxt");
|
|
1123
|
-
const includesSvelte = frontends.includes("svelte");
|
|
1124
|
-
if (includesNuxt || includesSvelte) return false;
|
|
1125
|
-
}
|
|
1126
|
-
return true;
|
|
794
|
+
function allowedApisForFrontends$1(frontends = [], astroIntegration) {
|
|
795
|
+
return allowedApisForFrontends(frontends, astroIntegration);
|
|
796
|
+
}
|
|
797
|
+
function isExampleAIAllowed$1(backend, frontends = []) {
|
|
798
|
+
return isExampleAIAllowed(backend, frontends);
|
|
799
|
+
}
|
|
800
|
+
function isExampleChatSdkAllowed$1(backend, frontends = [], runtime) {
|
|
801
|
+
return isExampleChatSdkAllowed(backend, frontends, runtime);
|
|
802
|
+
}
|
|
803
|
+
function requiresChatSdkVercelAI(backend, frontends = [], runtime) {
|
|
804
|
+
return requiresChatSdkVercelAIForSelection(backend, frontends, runtime);
|
|
1127
805
|
}
|
|
1128
806
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
1129
807
|
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
@@ -1131,28 +809,16 @@ function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
|
1131
809
|
function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
1132
810
|
if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
|
|
1133
811
|
}
|
|
1134
|
-
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
1135
|
-
|
|
1136
|
-
if (compatibleFrontends.length > 0) {
|
|
1137
|
-
if (!frontend.some((f) => compatibleFrontends.includes(f))) return {
|
|
1138
|
-
isCompatible: false,
|
|
1139
|
-
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
return { isCompatible: true };
|
|
812
|
+
function validateAddonCompatibility$1(addon, frontend, _auth) {
|
|
813
|
+
return validateAddonCompatibility(addon, frontend, _auth);
|
|
1143
814
|
}
|
|
1144
|
-
function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth) {
|
|
1145
|
-
return allAddons
|
|
1146
|
-
if (existingAddons.includes(addon)) return false;
|
|
1147
|
-
if (addon === "none") return false;
|
|
1148
|
-
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth);
|
|
1149
|
-
return isCompatible;
|
|
1150
|
-
});
|
|
815
|
+
function getCompatibleAddons$1(allAddons, frontend, existingAddons = [], auth) {
|
|
816
|
+
return getCompatibleAddons(allAddons, frontend, existingAddons, auth);
|
|
1151
817
|
}
|
|
1152
818
|
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
|
|
1153
819
|
for (const addon of addons) {
|
|
1154
820
|
if (addon === "none") continue;
|
|
1155
|
-
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth);
|
|
821
|
+
const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth);
|
|
1156
822
|
if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
|
|
1157
823
|
}
|
|
1158
824
|
}
|
|
@@ -1160,68 +826,63 @@ function validatePaymentsCompatibility(payments, auth, _backend, frontends = [])
|
|
|
1160
826
|
if (!payments || payments === "none") return;
|
|
1161
827
|
if (payments === "polar") {
|
|
1162
828
|
if (!auth || auth === "none" || auth !== "better-auth") exitWithError("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
|
|
1163
|
-
const { web } = splitFrontends(frontends);
|
|
829
|
+
const { web } = splitFrontends$1(frontends);
|
|
1164
830
|
if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
|
|
1165
831
|
}
|
|
1166
832
|
}
|
|
1167
|
-
function validateExamplesCompatibility(examples, backend, frontend) {
|
|
833
|
+
function validateExamplesCompatibility(examples, backend, frontend, runtime, ai) {
|
|
1168
834
|
const examplesArr = examples ?? [];
|
|
1169
835
|
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
1170
836
|
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
837
|
+
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid-start")) exitWithError("The 'ai' example is not compatible with the SolidStart frontend.");
|
|
1171
838
|
if (examplesArr.includes("ai") && backend === "convex") {
|
|
1172
839
|
const frontendArr = frontend ?? [];
|
|
1173
840
|
const includesNuxt = frontendArr.includes("nuxt");
|
|
1174
841
|
const includesSvelte = frontendArr.includes("svelte");
|
|
1175
842
|
if (includesNuxt || includesSvelte) exitWithError("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
|
|
1176
843
|
}
|
|
844
|
+
if (examplesArr.includes("chat-sdk")) {
|
|
845
|
+
const frontendArr = frontend ?? [];
|
|
846
|
+
if (!isExampleChatSdkAllowed$1(backend, frontendArr, runtime)) {
|
|
847
|
+
if (backend === "none") exitWithError("The 'chat-sdk' example requires a backend.");
|
|
848
|
+
if (backend === "convex") exitWithError("The 'chat-sdk' example is not supported with the Convex backend in v1. Use self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime.");
|
|
849
|
+
if (backend === "self") exitWithError("The 'chat-sdk' example with self backend only supports Next.js, TanStack Start, or Nuxt frontends in v1.");
|
|
850
|
+
if (backend === "hono" && runtime !== "node") exitWithError("The 'chat-sdk' example with Hono requires '--runtime node' in v1 (Bun/Workers not supported yet).");
|
|
851
|
+
exitWithError("The 'chat-sdk' example is only supported with self backend (Next.js, TanStack Start, Nuxt) or Hono with Node runtime in v1.");
|
|
852
|
+
}
|
|
853
|
+
if (requiresChatSdkVercelAI(backend, frontendArr, runtime) && ai && ai !== "vercel-ai") exitWithError("The 'chat-sdk' example requires '--ai vercel-ai' for the selected stack in v1 (Nuxt Discord and Hono GitHub profiles).");
|
|
854
|
+
}
|
|
1177
855
|
}
|
|
1178
856
|
/**
|
|
1179
857
|
* Validates that a UI library is compatible with the selected frontend(s)
|
|
1180
858
|
*/
|
|
1181
859
|
function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astroIntegration) {
|
|
1182
860
|
if (!uiLibrary || uiLibrary === "none") return;
|
|
1183
|
-
const
|
|
1184
|
-
if (
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
"tanstack-router",
|
|
1190
|
-
"react-router",
|
|
1191
|
-
"tanstack-start",
|
|
1192
|
-
"next"
|
|
1193
|
-
].includes(f));
|
|
1194
|
-
const supportsNonReact = compatibility.frontends.some((f) => [
|
|
1195
|
-
"nuxt",
|
|
1196
|
-
"svelte",
|
|
1197
|
-
"solid",
|
|
1198
|
-
"qwik",
|
|
1199
|
-
"angular"
|
|
1200
|
-
].includes(f));
|
|
1201
|
-
if (astroIntegration === "react") {
|
|
1202
|
-
if (supportsReact) return;
|
|
1203
|
-
}
|
|
1204
|
-
if (!supportsNonReact) {
|
|
1205
|
-
const integrationName = astroIntegration || "none";
|
|
861
|
+
const compatible = getCompatibleUILibraries(frontends, astroIntegration);
|
|
862
|
+
if (!compatible.includes(uiLibrary)) {
|
|
863
|
+
const { web } = splitFrontends$1(frontends);
|
|
864
|
+
const isAstroNonReact = web.includes("astro") && astroIntegration !== "react";
|
|
865
|
+
const supportsAstroReact = getCompatibleUILibraries(["astro"], "react").includes(uiLibrary);
|
|
866
|
+
if (isAstroNonReact && supportsAstroReact) {
|
|
1206
867
|
incompatibilityError({
|
|
1207
868
|
message: `UI library '${uiLibrary}' requires React.`,
|
|
1208
869
|
provided: {
|
|
1209
870
|
"ui-library": uiLibrary,
|
|
1210
|
-
"astro-integration":
|
|
871
|
+
"astro-integration": astroIntegration || "none"
|
|
1211
872
|
},
|
|
1212
873
|
suggestions: ["Use --astro-integration react", "Choose a different UI library (daisyui, ark-ui)"]
|
|
1213
874
|
});
|
|
875
|
+
return;
|
|
1214
876
|
}
|
|
1215
|
-
|
|
877
|
+
incompatibilityError({
|
|
878
|
+
message: `UI library '${uiLibrary}' is not compatible with the selected frontend.`,
|
|
879
|
+
provided: {
|
|
880
|
+
"ui-library": uiLibrary,
|
|
881
|
+
frontend: frontends
|
|
882
|
+
},
|
|
883
|
+
suggestions: [`Supported choices for this stack: ${compatible.join(", ")}`, "Choose a different UI library"]
|
|
884
|
+
});
|
|
1216
885
|
}
|
|
1217
|
-
if (!compatibility.frontends.includes(webFrontend)) incompatibilityError({
|
|
1218
|
-
message: `UI library '${uiLibrary}' is not compatible with '${webFrontend}'.`,
|
|
1219
|
-
provided: {
|
|
1220
|
-
"ui-library": uiLibrary,
|
|
1221
|
-
frontend: webFrontend
|
|
1222
|
-
},
|
|
1223
|
-
suggestions: [`Supported frontends: ${compatibility.frontends.join(", ")}`, "Choose a different UI library"]
|
|
1224
|
-
});
|
|
1225
886
|
}
|
|
1226
887
|
/**
|
|
1227
888
|
* Validates that a UI library is compatible with the selected CSS framework
|
|
@@ -1229,57 +890,26 @@ function validateUILibraryFrontendCompatibility(uiLibrary, frontends = [], astro
|
|
|
1229
890
|
function validateUILibraryCSSFrameworkCompatibility(uiLibrary, cssFramework) {
|
|
1230
891
|
if (!uiLibrary || uiLibrary === "none") return;
|
|
1231
892
|
if (!cssFramework) return;
|
|
1232
|
-
const
|
|
1233
|
-
if (!
|
|
893
|
+
const supported = getCompatibleCSSFrameworks(uiLibrary);
|
|
894
|
+
if (!supported.includes(cssFramework)) exitWithError(`UI library '${uiLibrary}' is not compatible with '${cssFramework}' CSS framework. Supported CSS frameworks: ${supported.join(", ")}`);
|
|
1234
895
|
}
|
|
1235
896
|
/**
|
|
1236
897
|
* Gets list of UI libraries compatible with the selected frontend(s)
|
|
1237
898
|
*/
|
|
1238
|
-
function getCompatibleUILibraries(frontends = [], astroIntegration) {
|
|
1239
|
-
|
|
1240
|
-
if (web.length === 0) return ["none"];
|
|
1241
|
-
const webFrontend = web[0];
|
|
1242
|
-
return Object.keys(UI_LIBRARY_COMPATIBILITY).filter((lib) => {
|
|
1243
|
-
if (lib === "none") return true;
|
|
1244
|
-
const compatibility = UI_LIBRARY_COMPATIBILITY[lib];
|
|
1245
|
-
if (webFrontend === "astro") {
|
|
1246
|
-
if (astroIntegration === "react") return compatibility.frontends.some((f) => [
|
|
1247
|
-
"tanstack-router",
|
|
1248
|
-
"react-router",
|
|
1249
|
-
"tanstack-start",
|
|
1250
|
-
"next",
|
|
1251
|
-
"astro"
|
|
1252
|
-
].includes(f));
|
|
1253
|
-
return compatibility.frontends.some((f) => [
|
|
1254
|
-
"nuxt",
|
|
1255
|
-
"svelte",
|
|
1256
|
-
"solid",
|
|
1257
|
-
"qwik",
|
|
1258
|
-
"angular"
|
|
1259
|
-
].includes(f));
|
|
1260
|
-
}
|
|
1261
|
-
return compatibility.frontends.includes(webFrontend);
|
|
1262
|
-
});
|
|
899
|
+
function getCompatibleUILibraries$1(frontends = [], astroIntegration) {
|
|
900
|
+
return getCompatibleUILibraries(frontends, astroIntegration);
|
|
1263
901
|
}
|
|
1264
902
|
/**
|
|
1265
903
|
* Gets list of CSS frameworks compatible with the selected UI library
|
|
1266
904
|
*/
|
|
1267
|
-
function getCompatibleCSSFrameworks(uiLibrary) {
|
|
1268
|
-
|
|
1269
|
-
"tailwind",
|
|
1270
|
-
"scss",
|
|
1271
|
-
"less",
|
|
1272
|
-
"postcss-only",
|
|
1273
|
-
"none"
|
|
1274
|
-
];
|
|
1275
|
-
return UI_LIBRARY_COMPATIBILITY[uiLibrary].cssFrameworks;
|
|
905
|
+
function getCompatibleCSSFrameworks$1(uiLibrary) {
|
|
906
|
+
return getCompatibleCSSFrameworks(uiLibrary);
|
|
1276
907
|
}
|
|
1277
908
|
/**
|
|
1278
909
|
* Checks if a frontend has web styling (excludes native-only frontends)
|
|
1279
910
|
*/
|
|
1280
|
-
function hasWebStyling(frontends = []) {
|
|
1281
|
-
|
|
1282
|
-
return web.length > 0;
|
|
911
|
+
function hasWebStyling$1(frontends = []) {
|
|
912
|
+
return hasWebStyling(frontends);
|
|
1283
913
|
}
|
|
1284
914
|
|
|
1285
915
|
//#endregion
|
|
@@ -1620,7 +1250,7 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
1620
1250
|
};
|
|
1621
1251
|
const frontendsArray = frontends || [];
|
|
1622
1252
|
for (const addon of allAddons) {
|
|
1623
|
-
const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth);
|
|
1253
|
+
const { isCompatible } = validateAddonCompatibility$1(addon, frontendsArray, auth);
|
|
1624
1254
|
if (!isCompatible) continue;
|
|
1625
1255
|
const { label, hint } = getAddonDisplay(addon);
|
|
1626
1256
|
const option = {
|
|
@@ -1659,7 +1289,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
|
1659
1289
|
"AI Agents": []
|
|
1660
1290
|
};
|
|
1661
1291
|
const frontendArray = frontend || [];
|
|
1662
|
-
const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
|
|
1292
|
+
const compatibleAddons = getCompatibleAddons$1(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
|
|
1663
1293
|
for (const addon of compatibleAddons) {
|
|
1664
1294
|
const { label, hint } = getAddonDisplay(addon);
|
|
1665
1295
|
const option = {
|
|
@@ -2404,7 +2034,8 @@ const SKILL_SOURCES = {
|
|
|
2404
2034
|
"prisma/skills": { label: "Prisma" },
|
|
2405
2035
|
"elysiajs/skills": { label: "ElysiaJS" },
|
|
2406
2036
|
"waynesutton/convexskills": { label: "Convex" },
|
|
2407
|
-
"msmps/opentui-skill": { label: "OpenTUI Platform" }
|
|
2037
|
+
"msmps/opentui-skill": { label: "OpenTUI Platform" },
|
|
2038
|
+
"haydenbleasel/ultracite": { label: "Ultracite" }
|
|
2408
2039
|
};
|
|
2409
2040
|
const AVAILABLE_AGENTS = [
|
|
2410
2041
|
{
|
|
@@ -2536,6 +2167,7 @@ function getRecommendedSourceKeys(config) {
|
|
|
2536
2167
|
if (backend === "elysia") sources.push("elysiajs/skills");
|
|
2537
2168
|
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
2538
2169
|
if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
|
|
2170
|
+
if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
|
|
2539
2171
|
return sources;
|
|
2540
2172
|
}
|
|
2541
2173
|
const CURATED_SKILLS_BY_SOURCE = {
|
|
@@ -2593,7 +2225,8 @@ const CURATED_SKILLS_BY_SOURCE = {
|
|
|
2593
2225
|
"convex-migrations",
|
|
2594
2226
|
"convex-security-check"
|
|
2595
2227
|
],
|
|
2596
|
-
"msmps/opentui-skill": () => ["opentui"]
|
|
2228
|
+
"msmps/opentui-skill": () => ["opentui"],
|
|
2229
|
+
"haydenbleasel/ultracite": () => ["ultracite"]
|
|
2597
2230
|
};
|
|
2598
2231
|
function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
2599
2232
|
return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
|
|
@@ -2723,12 +2356,11 @@ async function setupTauri(config) {
|
|
|
2723
2356
|
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2724
2357
|
try {
|
|
2725
2358
|
s.start("Setting up Tauri desktop app support...");
|
|
2726
|
-
const hasReactRouter = frontend.includes("react-router");
|
|
2727
2359
|
const hasNuxt = frontend.includes("nuxt");
|
|
2728
2360
|
const hasSvelte = frontend.includes("svelte");
|
|
2729
2361
|
const hasNext = frontend.includes("next");
|
|
2730
|
-
const devUrl =
|
|
2731
|
-
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" :
|
|
2362
|
+
const devUrl = `http://localhost:${getLocalWebDevPort(frontend)}`;
|
|
2363
|
+
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : frontend.includes("react-router") ? "../build/client" : "../dist";
|
|
2732
2364
|
const tauriArgs = [
|
|
2733
2365
|
"@tauri-apps/cli@latest",
|
|
2734
2366
|
"init",
|
|
@@ -3385,7 +3017,7 @@ async function getAiDocsChoice(aiDocs) {
|
|
|
3385
3017
|
//#region src/prompts/animation.ts
|
|
3386
3018
|
async function getAnimationChoice(animation, frontends) {
|
|
3387
3019
|
if (animation !== void 0) return animation;
|
|
3388
|
-
const { web } = splitFrontends(frontends);
|
|
3020
|
+
const { web } = splitFrontends$1(frontends);
|
|
3389
3021
|
if (web.length === 0) return "none";
|
|
3390
3022
|
const isReact = web.some((f) => [
|
|
3391
3023
|
"tanstack-router",
|
|
@@ -3437,7 +3069,7 @@ async function getAnimationChoice(animation, frontends) {
|
|
|
3437
3069
|
//#region src/prompts/api.ts
|
|
3438
3070
|
async function getApiChoice(Api, frontend, backend, astroIntegration) {
|
|
3439
3071
|
if (backend === "convex" || backend === "none") return "none";
|
|
3440
|
-
const allowed = allowedApisForFrontends(frontend ?? [], astroIntegration);
|
|
3072
|
+
const allowed = allowedApisForFrontends$1(frontend ?? [], astroIntegration);
|
|
3441
3073
|
if (Api) return allowed.includes(Api) ? Api : allowed[0];
|
|
3442
3074
|
const apiOptionMap = {
|
|
3443
3075
|
trpc: {
|
|
@@ -3517,93 +3149,36 @@ async function getAstroIntegrationChoice(astroIntegration) {
|
|
|
3517
3149
|
|
|
3518
3150
|
//#endregion
|
|
3519
3151
|
//#region src/prompts/auth.ts
|
|
3520
|
-
async function getAuthChoice(auth, backend, frontend) {
|
|
3152
|
+
async function getAuthChoice(auth, backend, frontend, ecosystem = "typescript") {
|
|
3521
3153
|
if (auth !== void 0) return auth;
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
].includes(f));
|
|
3541
|
-
const options$1 = [];
|
|
3542
|
-
if (supportedBetterAuthFrontends) options$1.push({
|
|
3543
|
-
value: "better-auth",
|
|
3544
|
-
label: "Better-Auth",
|
|
3545
|
-
hint: "comprehensive auth framework for TypeScript"
|
|
3546
|
-
});
|
|
3547
|
-
if (hasClerkCompatibleFrontends) options$1.push({
|
|
3548
|
-
value: "clerk",
|
|
3549
|
-
label: "Clerk",
|
|
3550
|
-
hint: "More than auth, Complete User Management"
|
|
3551
|
-
});
|
|
3552
|
-
if (options$1.length === 0) return "none";
|
|
3553
|
-
options$1.push({
|
|
3554
|
-
value: "none",
|
|
3555
|
-
label: "None",
|
|
3556
|
-
hint: "No auth"
|
|
3557
|
-
});
|
|
3558
|
-
const response$1 = await navigableSelect({
|
|
3559
|
-
message: "Select authentication provider",
|
|
3560
|
-
options: options$1,
|
|
3561
|
-
initialValue: "none"
|
|
3562
|
-
});
|
|
3563
|
-
if (isCancel$1(response$1)) return exitCancelled("Operation cancelled");
|
|
3564
|
-
return response$1;
|
|
3565
|
-
}
|
|
3566
|
-
const supportsNextJsAuth = frontend?.includes("next") && backend === "self";
|
|
3567
|
-
const options = [{
|
|
3568
|
-
value: "better-auth",
|
|
3569
|
-
label: "Better-Auth",
|
|
3570
|
-
hint: "comprehensive auth framework for TypeScript"
|
|
3571
|
-
}, {
|
|
3572
|
-
value: "clerk",
|
|
3573
|
-
label: "Clerk",
|
|
3574
|
-
hint: "More than auth, Complete User Management"
|
|
3575
|
-
}];
|
|
3576
|
-
if (supportsNextJsAuth) {
|
|
3577
|
-
options.push({
|
|
3578
|
-
value: "nextauth",
|
|
3579
|
-
label: "Auth.js (NextAuth)",
|
|
3580
|
-
hint: "Authentication for Next.js (formerly NextAuth.js)"
|
|
3581
|
-
});
|
|
3582
|
-
options.push({
|
|
3583
|
-
value: "stack-auth",
|
|
3584
|
-
label: "Stack Auth",
|
|
3585
|
-
hint: "Open-source Auth0/Clerk alternative"
|
|
3586
|
-
});
|
|
3587
|
-
options.push({
|
|
3588
|
-
value: "supabase-auth",
|
|
3589
|
-
label: "Supabase Auth",
|
|
3590
|
-
hint: "Auth with Supabase platform integration"
|
|
3591
|
-
});
|
|
3592
|
-
options.push({
|
|
3593
|
-
value: "auth0",
|
|
3594
|
-
label: "Auth0",
|
|
3595
|
-
hint: "Flexible identity platform for authentication"
|
|
3596
|
-
});
|
|
3597
|
-
}
|
|
3598
|
-
options.push({
|
|
3599
|
-
value: "none",
|
|
3600
|
-
label: "None",
|
|
3601
|
-
hint: "No authentication"
|
|
3154
|
+
const authOptionOrder = [
|
|
3155
|
+
{ value: "better-auth" },
|
|
3156
|
+
{ value: "go-better-auth" },
|
|
3157
|
+
{ value: "clerk" },
|
|
3158
|
+
{ value: "nextauth" },
|
|
3159
|
+
{ value: "stack-auth" },
|
|
3160
|
+
{ value: "supabase-auth" },
|
|
3161
|
+
{ value: "auth0" },
|
|
3162
|
+
{ value: "none" }
|
|
3163
|
+
];
|
|
3164
|
+
const supportedOptions = (0, types_exports.getSupportedCapabilityOptions)("auth", {
|
|
3165
|
+
ecosystem,
|
|
3166
|
+
backend,
|
|
3167
|
+
frontend
|
|
3168
|
+
});
|
|
3169
|
+
const options = authOptionOrder.flatMap(({ value }) => {
|
|
3170
|
+
const option = supportedOptions.find((candidate) => candidate.id === value);
|
|
3171
|
+
return option ? [option] : [];
|
|
3602
3172
|
});
|
|
3173
|
+
if (options.length === 1 && options[0]?.id === "none") return "none";
|
|
3603
3174
|
const response = await navigableSelect({
|
|
3604
3175
|
message: "Select authentication provider",
|
|
3605
|
-
options
|
|
3606
|
-
|
|
3176
|
+
options: options.map((option) => ({
|
|
3177
|
+
value: option.id,
|
|
3178
|
+
label: option.label,
|
|
3179
|
+
hint: option.promptHint
|
|
3180
|
+
})),
|
|
3181
|
+
initialValue: options.some((option) => option.id === DEFAULT_CONFIG.auth) ? DEFAULT_CONFIG.auth : options.find((option) => option.id !== "none")?.id ?? "none"
|
|
3607
3182
|
});
|
|
3608
3183
|
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
3609
3184
|
return response;
|
|
@@ -3615,11 +3190,13 @@ const FULLSTACK_FRONTENDS = [
|
|
|
3615
3190
|
"next",
|
|
3616
3191
|
"tanstack-start",
|
|
3617
3192
|
"astro",
|
|
3618
|
-
"nuxt"
|
|
3193
|
+
"nuxt",
|
|
3194
|
+
"svelte",
|
|
3195
|
+
"solid-start"
|
|
3619
3196
|
];
|
|
3620
3197
|
async function getBackendFrameworkChoice(backendFramework, frontends) {
|
|
3621
3198
|
if (backendFramework !== void 0) return backendFramework;
|
|
3622
|
-
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
|
|
3199
|
+
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid" || f === "solid-start");
|
|
3623
3200
|
const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
|
|
3624
3201
|
const backendOptions = [];
|
|
3625
3202
|
if (hasFullstackFrontend) backendOptions.push({
|
|
@@ -3765,7 +3342,7 @@ const CSS_FRAMEWORK_OPTIONS = {
|
|
|
3765
3342
|
}
|
|
3766
3343
|
};
|
|
3767
3344
|
async function getCSSFrameworkChoice(cssFramework, uiLibrary) {
|
|
3768
|
-
const compatibleFrameworks = getCompatibleCSSFrameworks(uiLibrary);
|
|
3345
|
+
const compatibleFrameworks = getCompatibleCSSFrameworks$1(uiLibrary);
|
|
3769
3346
|
if (cssFramework !== void 0) return compatibleFrameworks.includes(cssFramework) ? cssFramework : compatibleFrameworks[0];
|
|
3770
3347
|
const selected = await navigableSelect({
|
|
3771
3348
|
message: "Select CSS framework",
|
|
@@ -4076,16 +3653,21 @@ async function getEmailChoice(email, backend) {
|
|
|
4076
3653
|
|
|
4077
3654
|
//#endregion
|
|
4078
3655
|
//#region src/prompts/examples.ts
|
|
4079
|
-
async function getExamplesChoice(examples, frontends, backend) {
|
|
3656
|
+
async function getExamplesChoice(examples, frontends, backend, runtime) {
|
|
4080
3657
|
if (examples !== void 0) return examples;
|
|
4081
3658
|
if (backend === "none") return [];
|
|
4082
3659
|
let response = [];
|
|
4083
3660
|
const options = [];
|
|
4084
|
-
if (isExampleAIAllowed(backend, frontends ?? [])) options.push({
|
|
3661
|
+
if (isExampleAIAllowed$1(backend, frontends ?? [])) options.push({
|
|
4085
3662
|
value: "ai",
|
|
4086
3663
|
label: "AI Chat",
|
|
4087
3664
|
hint: "A simple AI chat interface using AI SDK"
|
|
4088
3665
|
});
|
|
3666
|
+
if (isExampleChatSdkAllowed$1(backend, frontends ?? [], runtime)) options.push({
|
|
3667
|
+
value: "chat-sdk",
|
|
3668
|
+
label: "Chat SDK Bots",
|
|
3669
|
+
hint: "Framework-specific Chat SDK bot example (Slack/Discord/GitHub depending on stack)"
|
|
3670
|
+
});
|
|
4089
3671
|
if (options.length === 0) return [];
|
|
4090
3672
|
response = await navigableMultiselect({
|
|
4091
3673
|
message: "Include examples",
|
|
@@ -4166,7 +3748,7 @@ async function getFileUploadChoice(fileUpload, backend) {
|
|
|
4166
3748
|
//#region src/prompts/forms.ts
|
|
4167
3749
|
async function getFormsChoice(forms, frontends) {
|
|
4168
3750
|
if (forms !== void 0) return forms;
|
|
4169
|
-
const { web } = splitFrontends(frontends);
|
|
3751
|
+
const { web } = splitFrontends$1(frontends);
|
|
4170
3752
|
if (web.length === 0) return "none";
|
|
4171
3753
|
const isReact = web.some((f) => [
|
|
4172
3754
|
"tanstack-router",
|
|
@@ -4271,14 +3853,19 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
4271
3853
|
},
|
|
4272
3854
|
{
|
|
4273
3855
|
value: "svelte",
|
|
4274
|
-
label: "
|
|
4275
|
-
hint: "
|
|
3856
|
+
label: "SvelteKit",
|
|
3857
|
+
hint: "Full-stack Svelte framework with SSR and server routes"
|
|
4276
3858
|
},
|
|
4277
3859
|
{
|
|
4278
3860
|
value: "solid",
|
|
4279
3861
|
label: "Solid",
|
|
4280
3862
|
hint: "Simple and performant reactivity for building user interfaces"
|
|
4281
3863
|
},
|
|
3864
|
+
{
|
|
3865
|
+
value: "solid-start",
|
|
3866
|
+
label: "SolidStart",
|
|
3867
|
+
hint: "Full-stack Solid framework with SSR and API routes"
|
|
3868
|
+
},
|
|
4282
3869
|
{
|
|
4283
3870
|
value: "astro",
|
|
4284
3871
|
label: "Astro",
|
|
@@ -4309,7 +3896,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
4309
3896
|
label: "Fresh",
|
|
4310
3897
|
hint: "Deno-native framework with islands architecture"
|
|
4311
3898
|
}
|
|
4312
|
-
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth)),
|
|
3899
|
+
].filter((option) => isFrontendAllowedWithBackend$1(option.value, backend, auth)),
|
|
4313
3900
|
initialValue: DEFAULT_CONFIG.frontend[0]
|
|
4314
3901
|
});
|
|
4315
3902
|
if (isGoBack(webFramework)) shouldRestart = true;
|
|
@@ -4803,7 +4390,7 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
4803
4390
|
async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
4804
4391
|
if (payments !== void 0) return payments;
|
|
4805
4392
|
if (backend === "none") return "none";
|
|
4806
|
-
const isPolarCompatible = auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0);
|
|
4393
|
+
const isPolarCompatible = auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends$1(frontends).web.length > 0);
|
|
4807
4394
|
const options = [];
|
|
4808
4395
|
if (isPolarCompatible) options.push({
|
|
4809
4396
|
value: "polar",
|
|
@@ -5284,11 +4871,379 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
|
|
|
5284
4871
|
return "none";
|
|
5285
4872
|
}
|
|
5286
4873
|
|
|
4874
|
+
//#endregion
|
|
4875
|
+
//#region src/prompts/shadcn-options.ts
|
|
4876
|
+
const BASE_OPTIONS = [{
|
|
4877
|
+
value: "radix",
|
|
4878
|
+
label: "Radix UI",
|
|
4879
|
+
hint: "Battle-tested headless primitives (130M+ monthly downloads)"
|
|
4880
|
+
}, {
|
|
4881
|
+
value: "base",
|
|
4882
|
+
label: "Base UI",
|
|
4883
|
+
hint: "MUI's headless library with cleaner APIs and native multi-select"
|
|
4884
|
+
}];
|
|
4885
|
+
const STYLE_OPTIONS = [
|
|
4886
|
+
{
|
|
4887
|
+
value: "vega",
|
|
4888
|
+
label: "Vega",
|
|
4889
|
+
hint: "Classic shadcn/ui look"
|
|
4890
|
+
},
|
|
4891
|
+
{
|
|
4892
|
+
value: "nova",
|
|
4893
|
+
label: "Nova",
|
|
4894
|
+
hint: "Compact layout with reduced padding"
|
|
4895
|
+
},
|
|
4896
|
+
{
|
|
4897
|
+
value: "maia",
|
|
4898
|
+
label: "Maia",
|
|
4899
|
+
hint: "Soft, rounded with generous spacing"
|
|
4900
|
+
},
|
|
4901
|
+
{
|
|
4902
|
+
value: "lyra",
|
|
4903
|
+
label: "Lyra",
|
|
4904
|
+
hint: "Boxy and sharp, pairs well with mono fonts"
|
|
4905
|
+
},
|
|
4906
|
+
{
|
|
4907
|
+
value: "mira",
|
|
4908
|
+
label: "Mira",
|
|
4909
|
+
hint: "Dense, made for data-heavy interfaces"
|
|
4910
|
+
}
|
|
4911
|
+
];
|
|
4912
|
+
const ICON_LIBRARY_OPTIONS = [
|
|
4913
|
+
{
|
|
4914
|
+
value: "lucide",
|
|
4915
|
+
label: "Lucide",
|
|
4916
|
+
hint: "Default icon library — clean, consistent icons"
|
|
4917
|
+
},
|
|
4918
|
+
{
|
|
4919
|
+
value: "tabler",
|
|
4920
|
+
label: "Tabler Icons",
|
|
4921
|
+
hint: "2000+ open-source SVG icons"
|
|
4922
|
+
},
|
|
4923
|
+
{
|
|
4924
|
+
value: "hugeicons",
|
|
4925
|
+
label: "HugeIcons",
|
|
4926
|
+
hint: "Modern icon set with wrapper component"
|
|
4927
|
+
},
|
|
4928
|
+
{
|
|
4929
|
+
value: "phosphor",
|
|
4930
|
+
label: "Phosphor Icons",
|
|
4931
|
+
hint: "Flexible, consistent icon family"
|
|
4932
|
+
},
|
|
4933
|
+
{
|
|
4934
|
+
value: "remixicon",
|
|
4935
|
+
label: "Remix Icon",
|
|
4936
|
+
hint: "Open-source neutral style icons"
|
|
4937
|
+
}
|
|
4938
|
+
];
|
|
4939
|
+
const COLOR_THEME_OPTIONS = [
|
|
4940
|
+
{
|
|
4941
|
+
value: "neutral",
|
|
4942
|
+
label: "Neutral",
|
|
4943
|
+
hint: "Clean and minimal"
|
|
4944
|
+
},
|
|
4945
|
+
{
|
|
4946
|
+
value: "stone",
|
|
4947
|
+
label: "Stone",
|
|
4948
|
+
hint: "Warm neutral tones"
|
|
4949
|
+
},
|
|
4950
|
+
{
|
|
4951
|
+
value: "zinc",
|
|
4952
|
+
label: "Zinc",
|
|
4953
|
+
hint: "Cool neutral tones"
|
|
4954
|
+
},
|
|
4955
|
+
{
|
|
4956
|
+
value: "gray",
|
|
4957
|
+
label: "Gray",
|
|
4958
|
+
hint: "Blue-tinted neutral"
|
|
4959
|
+
},
|
|
4960
|
+
{
|
|
4961
|
+
value: "blue",
|
|
4962
|
+
label: "Blue",
|
|
4963
|
+
hint: "Trust and reliability"
|
|
4964
|
+
},
|
|
4965
|
+
{
|
|
4966
|
+
value: "violet",
|
|
4967
|
+
label: "Violet",
|
|
4968
|
+
hint: "Creative and modern"
|
|
4969
|
+
},
|
|
4970
|
+
{
|
|
4971
|
+
value: "green",
|
|
4972
|
+
label: "Green",
|
|
4973
|
+
hint: "Growth and success"
|
|
4974
|
+
},
|
|
4975
|
+
{
|
|
4976
|
+
value: "red",
|
|
4977
|
+
label: "Red",
|
|
4978
|
+
hint: "Bold and energetic"
|
|
4979
|
+
},
|
|
4980
|
+
{
|
|
4981
|
+
value: "rose",
|
|
4982
|
+
label: "Rose",
|
|
4983
|
+
hint: "Warm and inviting"
|
|
4984
|
+
},
|
|
4985
|
+
{
|
|
4986
|
+
value: "orange",
|
|
4987
|
+
label: "Orange",
|
|
4988
|
+
hint: "Friendly and vibrant"
|
|
4989
|
+
},
|
|
4990
|
+
{
|
|
4991
|
+
value: "amber",
|
|
4992
|
+
label: "Amber",
|
|
4993
|
+
hint: "Warm and golden"
|
|
4994
|
+
},
|
|
4995
|
+
{
|
|
4996
|
+
value: "yellow",
|
|
4997
|
+
label: "Yellow",
|
|
4998
|
+
hint: "Bright and optimistic"
|
|
4999
|
+
},
|
|
5000
|
+
{
|
|
5001
|
+
value: "lime",
|
|
5002
|
+
label: "Lime",
|
|
5003
|
+
hint: "Fresh and lively"
|
|
5004
|
+
},
|
|
5005
|
+
{
|
|
5006
|
+
value: "emerald",
|
|
5007
|
+
label: "Emerald",
|
|
5008
|
+
hint: "Rich and luxurious"
|
|
5009
|
+
},
|
|
5010
|
+
{
|
|
5011
|
+
value: "teal",
|
|
5012
|
+
label: "Teal",
|
|
5013
|
+
hint: "Calm and sophisticated"
|
|
5014
|
+
},
|
|
5015
|
+
{
|
|
5016
|
+
value: "cyan",
|
|
5017
|
+
label: "Cyan",
|
|
5018
|
+
hint: "Cool and refreshing"
|
|
5019
|
+
},
|
|
5020
|
+
{
|
|
5021
|
+
value: "sky",
|
|
5022
|
+
label: "Sky",
|
|
5023
|
+
hint: "Light and airy"
|
|
5024
|
+
},
|
|
5025
|
+
{
|
|
5026
|
+
value: "indigo",
|
|
5027
|
+
label: "Indigo",
|
|
5028
|
+
hint: "Deep and focused"
|
|
5029
|
+
},
|
|
5030
|
+
{
|
|
5031
|
+
value: "purple",
|
|
5032
|
+
label: "Purple",
|
|
5033
|
+
hint: "Royal and elegant"
|
|
5034
|
+
},
|
|
5035
|
+
{
|
|
5036
|
+
value: "fuchsia",
|
|
5037
|
+
label: "Fuchsia",
|
|
5038
|
+
hint: "Playful and bold"
|
|
5039
|
+
},
|
|
5040
|
+
{
|
|
5041
|
+
value: "pink",
|
|
5042
|
+
label: "Pink",
|
|
5043
|
+
hint: "Soft and expressive"
|
|
5044
|
+
}
|
|
5045
|
+
];
|
|
5046
|
+
const BASE_COLOR_OPTIONS = [
|
|
5047
|
+
{
|
|
5048
|
+
value: "neutral",
|
|
5049
|
+
label: "Neutral",
|
|
5050
|
+
hint: "Pure neutral grays"
|
|
5051
|
+
},
|
|
5052
|
+
{
|
|
5053
|
+
value: "stone",
|
|
5054
|
+
label: "Stone",
|
|
5055
|
+
hint: "Warm-tinted grays"
|
|
5056
|
+
},
|
|
5057
|
+
{
|
|
5058
|
+
value: "zinc",
|
|
5059
|
+
label: "Zinc",
|
|
5060
|
+
hint: "Cool-tinted grays"
|
|
5061
|
+
},
|
|
5062
|
+
{
|
|
5063
|
+
value: "gray",
|
|
5064
|
+
label: "Gray",
|
|
5065
|
+
hint: "Blue-tinted grays"
|
|
5066
|
+
}
|
|
5067
|
+
];
|
|
5068
|
+
const FONT_OPTIONS = [
|
|
5069
|
+
{
|
|
5070
|
+
value: "inter",
|
|
5071
|
+
label: "Inter",
|
|
5072
|
+
hint: "Clean variable sans-serif (default)"
|
|
5073
|
+
},
|
|
5074
|
+
{
|
|
5075
|
+
value: "geist",
|
|
5076
|
+
label: "Geist",
|
|
5077
|
+
hint: "Vercel's modern typeface"
|
|
5078
|
+
},
|
|
5079
|
+
{
|
|
5080
|
+
value: "figtree",
|
|
5081
|
+
label: "Figtree",
|
|
5082
|
+
hint: "Friendly geometric sans-serif"
|
|
5083
|
+
},
|
|
5084
|
+
{
|
|
5085
|
+
value: "noto-sans",
|
|
5086
|
+
label: "Noto Sans",
|
|
5087
|
+
hint: "Google's universal typeface"
|
|
5088
|
+
},
|
|
5089
|
+
{
|
|
5090
|
+
value: "nunito-sans",
|
|
5091
|
+
label: "Nunito Sans",
|
|
5092
|
+
hint: "Rounded, balanced sans-serif"
|
|
5093
|
+
},
|
|
5094
|
+
{
|
|
5095
|
+
value: "roboto",
|
|
5096
|
+
label: "Roboto",
|
|
5097
|
+
hint: "Google's Material Design typeface"
|
|
5098
|
+
},
|
|
5099
|
+
{
|
|
5100
|
+
value: "raleway",
|
|
5101
|
+
label: "Raleway",
|
|
5102
|
+
hint: "Elegant thin-weight display font"
|
|
5103
|
+
},
|
|
5104
|
+
{
|
|
5105
|
+
value: "dm-sans",
|
|
5106
|
+
label: "DM Sans",
|
|
5107
|
+
hint: "Low-contrast geometric sans"
|
|
5108
|
+
},
|
|
5109
|
+
{
|
|
5110
|
+
value: "public-sans",
|
|
5111
|
+
label: "Public Sans",
|
|
5112
|
+
hint: "Neutral, US government typeface"
|
|
5113
|
+
},
|
|
5114
|
+
{
|
|
5115
|
+
value: "outfit",
|
|
5116
|
+
label: "Outfit",
|
|
5117
|
+
hint: "Modern geometric variable font"
|
|
5118
|
+
},
|
|
5119
|
+
{
|
|
5120
|
+
value: "jetbrains-mono",
|
|
5121
|
+
label: "JetBrains Mono",
|
|
5122
|
+
hint: "Developer-focused monospace"
|
|
5123
|
+
},
|
|
5124
|
+
{
|
|
5125
|
+
value: "geist-mono",
|
|
5126
|
+
label: "Geist Mono",
|
|
5127
|
+
hint: "Vercel's monospace typeface"
|
|
5128
|
+
}
|
|
5129
|
+
];
|
|
5130
|
+
const RADIUS_OPTIONS = [
|
|
5131
|
+
{
|
|
5132
|
+
value: "default",
|
|
5133
|
+
label: "Default",
|
|
5134
|
+
hint: "Use the style's default radius"
|
|
5135
|
+
},
|
|
5136
|
+
{
|
|
5137
|
+
value: "none",
|
|
5138
|
+
label: "None",
|
|
5139
|
+
hint: "Sharp corners (0)"
|
|
5140
|
+
},
|
|
5141
|
+
{
|
|
5142
|
+
value: "small",
|
|
5143
|
+
label: "Small",
|
|
5144
|
+
hint: "Subtle rounding (0.45rem)"
|
|
5145
|
+
},
|
|
5146
|
+
{
|
|
5147
|
+
value: "medium",
|
|
5148
|
+
label: "Medium",
|
|
5149
|
+
hint: "Moderate rounding (0.625rem)"
|
|
5150
|
+
},
|
|
5151
|
+
{
|
|
5152
|
+
value: "large",
|
|
5153
|
+
label: "Large",
|
|
5154
|
+
hint: "Generous rounding (0.875rem)"
|
|
5155
|
+
}
|
|
5156
|
+
];
|
|
5157
|
+
const SHADCN_DEFAULTS = {
|
|
5158
|
+
shadcnBase: "radix",
|
|
5159
|
+
shadcnStyle: "nova",
|
|
5160
|
+
shadcnIconLibrary: "lucide",
|
|
5161
|
+
shadcnColorTheme: "neutral",
|
|
5162
|
+
shadcnBaseColor: "neutral",
|
|
5163
|
+
shadcnFont: "inter",
|
|
5164
|
+
shadcnRadius: "default"
|
|
5165
|
+
};
|
|
5166
|
+
async function getShadcnOptions(flags) {
|
|
5167
|
+
const fallback = (key) => isSilent() ? SHADCN_DEFAULTS[key] : void 0;
|
|
5168
|
+
return {
|
|
5169
|
+
shadcnBase: flags.shadcnBase ?? fallback("shadcnBase") ?? await promptShadcnBase(),
|
|
5170
|
+
shadcnStyle: flags.shadcnStyle ?? fallback("shadcnStyle") ?? await promptShadcnStyle(),
|
|
5171
|
+
shadcnIconLibrary: flags.shadcnIconLibrary ?? fallback("shadcnIconLibrary") ?? await promptShadcnIconLibrary(),
|
|
5172
|
+
shadcnColorTheme: flags.shadcnColorTheme ?? fallback("shadcnColorTheme") ?? await promptShadcnColorTheme(),
|
|
5173
|
+
shadcnBaseColor: flags.shadcnBaseColor ?? fallback("shadcnBaseColor") ?? await promptShadcnBaseColor(),
|
|
5174
|
+
shadcnFont: flags.shadcnFont ?? fallback("shadcnFont") ?? await promptShadcnFont(),
|
|
5175
|
+
shadcnRadius: flags.shadcnRadius ?? fallback("shadcnRadius") ?? await promptShadcnRadius()
|
|
5176
|
+
};
|
|
5177
|
+
}
|
|
5178
|
+
async function promptShadcnBase() {
|
|
5179
|
+
const selected = await navigableSelect({
|
|
5180
|
+
message: "Select shadcn/ui base library",
|
|
5181
|
+
options: BASE_OPTIONS,
|
|
5182
|
+
initialValue: "radix"
|
|
5183
|
+
});
|
|
5184
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5185
|
+
return selected;
|
|
5186
|
+
}
|
|
5187
|
+
async function promptShadcnStyle() {
|
|
5188
|
+
const selected = await navigableSelect({
|
|
5189
|
+
message: "Select shadcn/ui visual style",
|
|
5190
|
+
options: STYLE_OPTIONS,
|
|
5191
|
+
initialValue: "nova"
|
|
5192
|
+
});
|
|
5193
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5194
|
+
return selected;
|
|
5195
|
+
}
|
|
5196
|
+
async function promptShadcnIconLibrary() {
|
|
5197
|
+
const selected = await navigableSelect({
|
|
5198
|
+
message: "Select shadcn/ui icon library",
|
|
5199
|
+
options: ICON_LIBRARY_OPTIONS,
|
|
5200
|
+
initialValue: "lucide"
|
|
5201
|
+
});
|
|
5202
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5203
|
+
return selected;
|
|
5204
|
+
}
|
|
5205
|
+
async function promptShadcnColorTheme() {
|
|
5206
|
+
const selected = await navigableSelect({
|
|
5207
|
+
message: "Select shadcn/ui color theme",
|
|
5208
|
+
options: COLOR_THEME_OPTIONS,
|
|
5209
|
+
initialValue: "neutral"
|
|
5210
|
+
});
|
|
5211
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5212
|
+
return selected;
|
|
5213
|
+
}
|
|
5214
|
+
async function promptShadcnBaseColor() {
|
|
5215
|
+
const selected = await navigableSelect({
|
|
5216
|
+
message: "Select shadcn/ui base neutral color",
|
|
5217
|
+
options: BASE_COLOR_OPTIONS,
|
|
5218
|
+
initialValue: "neutral"
|
|
5219
|
+
});
|
|
5220
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5221
|
+
return selected;
|
|
5222
|
+
}
|
|
5223
|
+
async function promptShadcnFont() {
|
|
5224
|
+
const selected = await navigableSelect({
|
|
5225
|
+
message: "Select shadcn/ui font",
|
|
5226
|
+
options: FONT_OPTIONS,
|
|
5227
|
+
initialValue: "inter"
|
|
5228
|
+
});
|
|
5229
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5230
|
+
return selected;
|
|
5231
|
+
}
|
|
5232
|
+
async function promptShadcnRadius() {
|
|
5233
|
+
const selected = await navigableSelect({
|
|
5234
|
+
message: "Select shadcn/ui border radius",
|
|
5235
|
+
options: RADIUS_OPTIONS,
|
|
5236
|
+
initialValue: "default"
|
|
5237
|
+
});
|
|
5238
|
+
if (isCancel$1(selected)) return exitCancelled("Operation cancelled");
|
|
5239
|
+
return selected;
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5287
5242
|
//#endregion
|
|
5288
5243
|
//#region src/prompts/state-management.ts
|
|
5289
5244
|
async function getStateManagementChoice(stateManagement, frontends) {
|
|
5290
5245
|
if (stateManagement !== void 0) return stateManagement;
|
|
5291
|
-
const { web } = splitFrontends(frontends);
|
|
5246
|
+
const { web } = splitFrontends$1(frontends);
|
|
5292
5247
|
if (web.length === 0) return "none";
|
|
5293
5248
|
const isReact = web.some((f) => [
|
|
5294
5249
|
"tanstack-router",
|
|
@@ -5448,9 +5403,9 @@ const UI_LIBRARY_OPTIONS = {
|
|
|
5448
5403
|
}
|
|
5449
5404
|
};
|
|
5450
5405
|
async function getUILibraryChoice(uiLibrary, frontends, astroIntegration) {
|
|
5451
|
-
const { web } = splitFrontends(frontends);
|
|
5406
|
+
const { web } = splitFrontends$1(frontends);
|
|
5452
5407
|
if (web.length === 0) return "none";
|
|
5453
|
-
const compatibleLibraries = getCompatibleUILibraries(frontends, astroIntegration);
|
|
5408
|
+
const compatibleLibraries = getCompatibleUILibraries$1(frontends, astroIntegration);
|
|
5454
5409
|
if (uiLibrary !== void 0) return compatibleLibraries.includes(uiLibrary) ? uiLibrary : compatibleLibraries[0];
|
|
5455
5410
|
const options = compatibleLibraries.map((lib) => ({
|
|
5456
5411
|
value: lib,
|
|
@@ -5521,6 +5476,24 @@ async function getValidationChoice(validation) {
|
|
|
5521
5476
|
return response;
|
|
5522
5477
|
}
|
|
5523
5478
|
|
|
5479
|
+
//#endregion
|
|
5480
|
+
//#region src/utils/compatibility.ts
|
|
5481
|
+
const WEB_FRAMEWORKS = [
|
|
5482
|
+
"tanstack-router",
|
|
5483
|
+
"react-router",
|
|
5484
|
+
"tanstack-start",
|
|
5485
|
+
"next",
|
|
5486
|
+
"nuxt",
|
|
5487
|
+
"svelte",
|
|
5488
|
+
"solid",
|
|
5489
|
+
"solid-start",
|
|
5490
|
+
"astro",
|
|
5491
|
+
"qwik",
|
|
5492
|
+
"angular",
|
|
5493
|
+
"redwood",
|
|
5494
|
+
"fresh"
|
|
5495
|
+
];
|
|
5496
|
+
|
|
5524
5497
|
//#endregion
|
|
5525
5498
|
//#region src/prompts/web-deploy.ts
|
|
5526
5499
|
function hasWebFrontend(frontends) {
|
|
@@ -5571,12 +5544,24 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5571
5544
|
},
|
|
5572
5545
|
uiLibrary: ({ results }) => {
|
|
5573
5546
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
5574
|
-
if (hasWebStyling(results.frontend)) return getUILibraryChoice(flags.uiLibrary, results.frontend, results.astroIntegration);
|
|
5547
|
+
if (hasWebStyling$1(results.frontend)) return getUILibraryChoice(flags.uiLibrary, results.frontend, results.astroIntegration);
|
|
5575
5548
|
return Promise.resolve("none");
|
|
5576
5549
|
},
|
|
5550
|
+
shadcnOptions: ({ results }) => {
|
|
5551
|
+
if (results.uiLibrary !== "shadcn-ui") return Promise.resolve(void 0);
|
|
5552
|
+
return getShadcnOptions({
|
|
5553
|
+
shadcnBase: flags.shadcnBase,
|
|
5554
|
+
shadcnStyle: flags.shadcnStyle,
|
|
5555
|
+
shadcnIconLibrary: flags.shadcnIconLibrary,
|
|
5556
|
+
shadcnColorTheme: flags.shadcnColorTheme,
|
|
5557
|
+
shadcnBaseColor: flags.shadcnBaseColor,
|
|
5558
|
+
shadcnFont: flags.shadcnFont,
|
|
5559
|
+
shadcnRadius: flags.shadcnRadius
|
|
5560
|
+
});
|
|
5561
|
+
},
|
|
5577
5562
|
cssFramework: ({ results }) => {
|
|
5578
5563
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
5579
|
-
if (hasWebStyling(results.frontend)) return getCSSFrameworkChoice(flags.cssFramework, results.uiLibrary);
|
|
5564
|
+
if (hasWebStyling$1(results.frontend)) return getCSSFrameworkChoice(flags.cssFramework, results.uiLibrary);
|
|
5580
5565
|
return Promise.resolve("none");
|
|
5581
5566
|
},
|
|
5582
5567
|
backend: ({ results }) => {
|
|
@@ -5600,8 +5585,9 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5600
5585
|
return getApiChoice(flags.api, results.frontend, results.backend, results.astroIntegration);
|
|
5601
5586
|
},
|
|
5602
5587
|
auth: ({ results }) => {
|
|
5603
|
-
if (results.ecosystem
|
|
5604
|
-
return getAuthChoice(flags.auth,
|
|
5588
|
+
if (results.ecosystem === "typescript") return getAuthChoice(flags.auth, results.backend, results.frontend, "typescript");
|
|
5589
|
+
if (results.ecosystem === "go") return getAuthChoice(flags.auth, void 0, void 0, "go");
|
|
5590
|
+
return Promise.resolve("none");
|
|
5605
5591
|
},
|
|
5606
5592
|
payments: ({ results }) => {
|
|
5607
5593
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
@@ -5621,7 +5607,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5621
5607
|
},
|
|
5622
5608
|
examples: ({ results }) => {
|
|
5623
5609
|
if (results.ecosystem !== "typescript") return Promise.resolve([]);
|
|
5624
|
-
return getExamplesChoice(flags.examples, results.frontend, results.backend);
|
|
5610
|
+
return getExamplesChoice(flags.examples, results.frontend, results.backend, results.runtime);
|
|
5625
5611
|
},
|
|
5626
5612
|
dbSetup: ({ results }) => {
|
|
5627
5613
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
@@ -5637,6 +5623,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5637
5623
|
},
|
|
5638
5624
|
ai: ({ results }) => {
|
|
5639
5625
|
if (results.ecosystem !== "typescript") return Promise.resolve("none");
|
|
5626
|
+
if (flags.ai === void 0 && results.examples?.includes("chat-sdk") && requiresChatSdkVercelAI(results.backend, results.frontend, results.runtime)) return Promise.resolve("vercel-ai");
|
|
5640
5627
|
return getAIChoice(flags.ai);
|
|
5641
5628
|
},
|
|
5642
5629
|
validation: ({ results }) => {
|
|
@@ -5786,6 +5773,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
5786
5773
|
frontend: result.frontend,
|
|
5787
5774
|
astroIntegration: result.astroIntegration,
|
|
5788
5775
|
uiLibrary: result.uiLibrary,
|
|
5776
|
+
...result.shadcnOptions ?? {},
|
|
5789
5777
|
cssFramework: result.cssFramework,
|
|
5790
5778
|
backend: result.backend,
|
|
5791
5779
|
runtime: result.runtime,
|
|
@@ -6014,6 +6002,15 @@ function generateReproducibleCommand(config) {
|
|
|
6014
6002
|
flags.push(`--effect ${config.effect}`);
|
|
6015
6003
|
flags.push(`--css-framework ${config.cssFramework}`);
|
|
6016
6004
|
flags.push(`--ui-library ${config.uiLibrary}`);
|
|
6005
|
+
if (config.uiLibrary === "shadcn-ui") {
|
|
6006
|
+
flags.push(`--shadcn-base ${config.shadcnBase}`);
|
|
6007
|
+
flags.push(`--shadcn-style ${config.shadcnStyle}`);
|
|
6008
|
+
flags.push(`--shadcn-icon-library ${config.shadcnIconLibrary}`);
|
|
6009
|
+
flags.push(`--shadcn-color-theme ${config.shadcnColorTheme}`);
|
|
6010
|
+
flags.push(`--shadcn-base-color ${config.shadcnBaseColor}`);
|
|
6011
|
+
flags.push(`--shadcn-font ${config.shadcnFont}`);
|
|
6012
|
+
flags.push(`--shadcn-radius ${config.shadcnRadius}`);
|
|
6013
|
+
}
|
|
6017
6014
|
flags.push(`--ai ${config.ai}`);
|
|
6018
6015
|
flags.push(`--state-management ${config.stateManagement}`);
|
|
6019
6016
|
flags.push(`--forms ${config.forms}`);
|
|
@@ -6269,6 +6266,13 @@ function processFlags(options, projectName) {
|
|
|
6269
6266
|
if (options.astroIntegration) config.astroIntegration = options.astroIntegration;
|
|
6270
6267
|
if (options.cssFramework) config.cssFramework = options.cssFramework;
|
|
6271
6268
|
if (options.uiLibrary) config.uiLibrary = options.uiLibrary;
|
|
6269
|
+
if (options.shadcnBase) config.shadcnBase = options.shadcnBase;
|
|
6270
|
+
if (options.shadcnStyle) config.shadcnStyle = options.shadcnStyle;
|
|
6271
|
+
if (options.shadcnIconLibrary) config.shadcnIconLibrary = options.shadcnIconLibrary;
|
|
6272
|
+
if (options.shadcnColorTheme) config.shadcnColorTheme = options.shadcnColorTheme;
|
|
6273
|
+
if (options.shadcnBaseColor) config.shadcnBaseColor = options.shadcnBaseColor;
|
|
6274
|
+
if (options.shadcnFont) config.shadcnFont = options.shadcnFont;
|
|
6275
|
+
if (options.shadcnRadius) config.shadcnRadius = options.shadcnRadius;
|
|
6272
6276
|
if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
|
|
6273
6277
|
if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
|
|
6274
6278
|
if (options.aiDocs !== void 0) config.aiDocs = processArrayOption(options.aiDocs);
|
|
@@ -6510,7 +6514,7 @@ function validateDatabaseOrmAuth(cfg, flags) {
|
|
|
6510
6514
|
suggestions: ["Use --orm mongoose or --orm prisma for MongoDB", "Choose a different database (postgres, sqlite, mysql)"]
|
|
6511
6515
|
});
|
|
6512
6516
|
if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") incompatibilityError({
|
|
6513
|
-
message: "MongoDB only
|
|
6517
|
+
message: "In Better-Fullstack, MongoDB is currently supported only with Mongoose or Prisma ORM.",
|
|
6514
6518
|
provided: {
|
|
6515
6519
|
database: "mongodb",
|
|
6516
6520
|
orm
|
|
@@ -6593,7 +6597,7 @@ function validateDatabaseSetup(config, providedFlags) {
|
|
|
6593
6597
|
runtime: "workers",
|
|
6594
6598
|
errorMessage: "Cloudflare D1 setup requires SQLite database and Cloudflare Workers runtime."
|
|
6595
6599
|
},
|
|
6596
|
-
docker: { errorMessage: "Docker setup is not
|
|
6600
|
+
docker: { errorMessage: "In Better-Fullstack, Docker setup is currently not available with SQLite database or Cloudflare Workers runtime." },
|
|
6597
6601
|
none: { errorMessage: "" }
|
|
6598
6602
|
};
|
|
6599
6603
|
if (dbSetup && dbSetup !== "none") {
|
|
@@ -6603,11 +6607,23 @@ function validateDatabaseSetup(config, providedFlags) {
|
|
|
6603
6607
|
} else if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
|
|
6604
6608
|
if (validation.runtime && runtime !== validation.runtime) exitWithError(validation.errorMessage);
|
|
6605
6609
|
if (dbSetup === "docker") {
|
|
6606
|
-
if (database === "sqlite") exitWithError("Docker setup is not
|
|
6607
|
-
if (runtime === "workers") exitWithError("Docker setup is not
|
|
6610
|
+
if (database === "sqlite") exitWithError("In Better-Fullstack, Docker setup is currently not available with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
|
|
6611
|
+
if (runtime === "workers") exitWithError("In Better-Fullstack, Docker setup is currently not available with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
|
|
6608
6612
|
}
|
|
6609
6613
|
}
|
|
6610
6614
|
}
|
|
6615
|
+
function validateEcosystemAuthCompatibility(config, providedFlags) {
|
|
6616
|
+
const auth = config.auth;
|
|
6617
|
+
if (!auth || auth === "none") return;
|
|
6618
|
+
const normalized = (0, types_exports.normalizeCapabilitySelection)("auth", {
|
|
6619
|
+
ecosystem: config.ecosystem,
|
|
6620
|
+
backend: config.backend,
|
|
6621
|
+
frontend: config.frontend
|
|
6622
|
+
}, auth);
|
|
6623
|
+
if (!normalized.normalized || normalized.value === auth) return;
|
|
6624
|
+
config.auth = normalized.value;
|
|
6625
|
+
if (providedFlags?.has("auth") && normalized.reason && !isSilent()) consola.warn(pc.yellow(`Unsupported auth selection '${auth}' for the current stack: ${normalized.reason}. Falling back to '--auth ${normalized.value}'.`));
|
|
6626
|
+
}
|
|
6611
6627
|
function validateConvexConstraints(config, providedFlags) {
|
|
6612
6628
|
const { backend } = config;
|
|
6613
6629
|
if (backend !== "convex") return;
|
|
@@ -6660,30 +6676,6 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
6660
6676
|
},
|
|
6661
6677
|
suggestions: ["Remove --server-deploy flag", "Set --server-deploy none"]
|
|
6662
6678
|
});
|
|
6663
|
-
if (has("auth") && config.auth === "better-auth") {
|
|
6664
|
-
const supportedFrontends = [
|
|
6665
|
-
"tanstack-router",
|
|
6666
|
-
"tanstack-start",
|
|
6667
|
-
"next",
|
|
6668
|
-
"native-bare",
|
|
6669
|
-
"native-uniwind",
|
|
6670
|
-
"native-unistyles"
|
|
6671
|
-
];
|
|
6672
|
-
if (!config.frontend?.some((f) => supportedFrontends.includes(f))) incompatibilityError({
|
|
6673
|
-
message: "Better-Auth with Convex requires specific frontends.",
|
|
6674
|
-
provided: {
|
|
6675
|
-
backend: "convex",
|
|
6676
|
-
auth: "better-auth",
|
|
6677
|
-
frontend: config.frontend || []
|
|
6678
|
-
},
|
|
6679
|
-
suggestions: [
|
|
6680
|
-
"Use --frontend tanstack-router",
|
|
6681
|
-
"Use --frontend tanstack-start",
|
|
6682
|
-
"Use --frontend next",
|
|
6683
|
-
"Use a native frontend"
|
|
6684
|
-
]
|
|
6685
|
-
});
|
|
6686
|
-
}
|
|
6687
6679
|
}
|
|
6688
6680
|
function validateBackendNoneConstraints(config, providedFlags) {
|
|
6689
6681
|
const { backend } = config;
|
|
@@ -6693,7 +6685,6 @@ function validateBackendNoneConstraints(config, providedFlags) {
|
|
|
6693
6685
|
if (has("database") && config.database !== "none") exitWithError("Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.");
|
|
6694
6686
|
if (has("orm") && config.orm !== "none") exitWithError("Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.");
|
|
6695
6687
|
if (has("api") && config.api !== "none") exitWithError("Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
6696
|
-
if (has("auth") && config.auth !== "none") exitWithError("Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.");
|
|
6697
6688
|
if (has("payments") && config.payments !== "none") exitWithError("Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.");
|
|
6698
6689
|
if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
|
|
6699
6690
|
if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
|
|
@@ -6723,15 +6714,6 @@ function validateAdonisJSConstraints(config, providedFlags) {
|
|
|
6723
6714
|
}
|
|
6724
6715
|
function validateBackendConstraints(config, providedFlags, options) {
|
|
6725
6716
|
const { backend } = config;
|
|
6726
|
-
if (config.auth === "clerk" && backend !== "convex") exitWithError("Clerk authentication is only supported with the Convex backend. Please use '--backend convex' or choose a different auth provider.");
|
|
6727
|
-
if (backend === "convex" && config.auth === "clerk" && config.frontend) {
|
|
6728
|
-
const incompatibleFrontends = config.frontend.filter((f) => [
|
|
6729
|
-
"nuxt",
|
|
6730
|
-
"svelte",
|
|
6731
|
-
"solid"
|
|
6732
|
-
].includes(f));
|
|
6733
|
-
if (incompatibleFrontends.length > 0) exitWithError(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
|
|
6734
|
-
}
|
|
6735
6717
|
if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self" && backend !== "encore") {
|
|
6736
6718
|
if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex', '--backend none', '--backend self', or '--backend encore'. Please choose 'bun', 'node', or remove the --runtime flag.");
|
|
6737
6719
|
}
|
|
@@ -6746,11 +6728,32 @@ function validateFrontendConstraints(config, providedFlags) {
|
|
|
6746
6728
|
ensureSingleWebAndNative(frontend);
|
|
6747
6729
|
if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) validateApiFrontendCompatibility(config.api, frontend, config.astroIntegration);
|
|
6748
6730
|
}
|
|
6749
|
-
const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend(f));
|
|
6731
|
+
const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend$1(f));
|
|
6750
6732
|
validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
|
|
6751
6733
|
}
|
|
6752
6734
|
function validateApiConstraints(_config, _options) {}
|
|
6735
|
+
function validateShadcnConstraints(config, providedFlags) {
|
|
6736
|
+
const shadcnFlagMap = {
|
|
6737
|
+
shadcnBase: "--shadcn-base",
|
|
6738
|
+
shadcnStyle: "--shadcn-style",
|
|
6739
|
+
shadcnIconLibrary: "--shadcn-icon-library",
|
|
6740
|
+
shadcnColorTheme: "--shadcn-color-theme",
|
|
6741
|
+
shadcnBaseColor: "--shadcn-base-color",
|
|
6742
|
+
shadcnFont: "--shadcn-font",
|
|
6743
|
+
shadcnRadius: "--shadcn-radius"
|
|
6744
|
+
};
|
|
6745
|
+
const providedShadcnFlags = Object.keys(shadcnFlagMap).filter((f) => providedFlags.has(f));
|
|
6746
|
+
if (providedShadcnFlags.length > 0 && config.uiLibrary !== "shadcn-ui") incompatibilityError({
|
|
6747
|
+
message: "shadcn/ui customization flags require --ui-library shadcn-ui.",
|
|
6748
|
+
provided: {
|
|
6749
|
+
"ui-library": config.uiLibrary || "none",
|
|
6750
|
+
...Object.fromEntries(providedShadcnFlags.map((f) => [shadcnFlagMap[f], String(config[f] ?? "")]))
|
|
6751
|
+
},
|
|
6752
|
+
suggestions: ["Add --ui-library shadcn-ui to use shadcn customization flags", "Remove the --shadcn-* flags if not using shadcn/ui"]
|
|
6753
|
+
});
|
|
6754
|
+
}
|
|
6753
6755
|
function validateFullConfig(config, providedFlags, options) {
|
|
6756
|
+
validateEcosystemAuthCompatibility(config, providedFlags);
|
|
6754
6757
|
validateDatabaseOrmAuth(config, providedFlags);
|
|
6755
6758
|
validateDatabaseSetup(config, providedFlags);
|
|
6756
6759
|
validateConvexConstraints(config, providedFlags);
|
|
@@ -6770,28 +6773,22 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
6770
6773
|
validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
6771
6774
|
config.addons = [...new Set(config.addons)];
|
|
6772
6775
|
}
|
|
6773
|
-
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? []);
|
|
6776
|
+
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
|
|
6774
6777
|
validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend ?? []);
|
|
6775
|
-
validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6776
|
-
validateStackAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6777
|
-
validateSupabaseAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6778
|
-
validateAuth0Compatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6779
6778
|
validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
|
|
6780
6779
|
validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
|
|
6780
|
+
validateShadcnConstraints(config, providedFlags);
|
|
6781
6781
|
validatePeerDependencies(config);
|
|
6782
6782
|
}
|
|
6783
6783
|
function validateConfigForProgrammaticUse(config) {
|
|
6784
6784
|
try {
|
|
6785
|
+
validateEcosystemAuthCompatibility(config);
|
|
6785
6786
|
validateDatabaseOrmAuth(config);
|
|
6786
6787
|
if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
|
|
6787
6788
|
validateApiFrontendCompatibility(config.api, config.frontend, config.astroIntegration);
|
|
6788
6789
|
validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
|
|
6789
|
-
validateNextAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6790
|
-
validateStackAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6791
|
-
validateSupabaseAuthCompatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6792
|
-
validateAuth0Compatibility(config.auth, config.backend, config.frontend ?? []);
|
|
6793
6790
|
if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
6794
|
-
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? []);
|
|
6791
|
+
validateExamplesCompatibility(config.examples ?? [], config.backend, config.frontend ?? [], config.runtime, config.ai);
|
|
6795
6792
|
validateUILibraryFrontendCompatibility(config.uiLibrary, config.frontend ?? [], config.astroIntegration);
|
|
6796
6793
|
validateUILibraryCSSFrameworkCompatibility(config.uiLibrary, config.cssFramework);
|
|
6797
6794
|
validatePeerDependencies(config);
|
|
@@ -6897,6 +6894,8 @@ async function formatCode(filePath, content) {
|
|
|
6897
6894
|
}
|
|
6898
6895
|
async function formatProject(projectDir) {
|
|
6899
6896
|
async function formatDirectory(dir) {
|
|
6897
|
+
const denoConfigPath = path.join(dir, "deno.json");
|
|
6898
|
+
if (await fs.pathExists(denoConfigPath)) return;
|
|
6900
6899
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
6901
6900
|
await Promise.all(entries.map(async (entry) => {
|
|
6902
6901
|
const fullPath = path.join(dir, entry.name);
|
|
@@ -8113,20 +8112,20 @@ async function displayPostInstallInstructions(config) {
|
|
|
8113
8112
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
8114
8113
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
8115
8114
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
8116
|
-
const clerkInstructions =
|
|
8115
|
+
const clerkInstructions = config.auth === "clerk" ? getClerkInstructions(config.backend, config.frontend ?? []) : "";
|
|
8117
8116
|
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions(backend) : "";
|
|
8118
8117
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
8119
8118
|
const hasWeb = frontend?.some((f) => WEB_FRAMEWORKS.includes(f));
|
|
8120
8119
|
const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
|
|
8121
8120
|
const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
|
|
8122
8121
|
const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
|
|
8123
|
-
const
|
|
8124
|
-
const
|
|
8125
|
-
const webPort = hasReactRouter || hasSvelte ? "5173" : "3001";
|
|
8122
|
+
const hasFresh = frontend?.includes("fresh");
|
|
8123
|
+
const webPort = String(getLocalWebDevPort(frontend ?? []));
|
|
8126
8124
|
const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager) : "";
|
|
8127
8125
|
let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
|
|
8128
8126
|
let stepCounter = 2;
|
|
8129
8127
|
if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
|
|
8128
|
+
if (hasFresh) output += `${pc.yellow("NOTE:")} Fresh projects require ${pc.white("deno")} on your PATH\n`;
|
|
8130
8129
|
if (database === "sqlite" && dbSetup !== "d1") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\n${pc.dim(" (optional - starts local SQLite database)")}\n`;
|
|
8131
8130
|
if (isConvex) {
|
|
8132
8131
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
@@ -8244,8 +8243,13 @@ function getNoOrmWarning() {
|
|
|
8244
8243
|
function getBunWebNativeWarning() {
|
|
8245
8244
|
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
8246
8245
|
}
|
|
8247
|
-
function getClerkInstructions() {
|
|
8248
|
-
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
|
|
8246
|
+
function getClerkInstructions(backend, frontend) {
|
|
8247
|
+
if (backend === "convex") return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
|
|
8248
|
+
if (backend === "self" && (frontend.includes("next") || frontend.includes("tanstack-start"))) {
|
|
8249
|
+
const publishableKeyVar = frontend.includes("next") ? "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY" : "VITE_CLERK_PUBLISHABLE_KEY";
|
|
8250
|
+
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Create an application in ${pc.underline("https://dashboard.clerk.com/")}\n${pc.cyan("•")} Set ${publishableKeyVar} in ${pc.white("apps/web/.env")}\n${pc.cyan("•")} Set CLERK_SECRET_KEY in ${pc.white("apps/web/.env")}\n${pc.cyan("•")} Clerk middleware and a protected dashboard route are already generated`;
|
|
8251
|
+
}
|
|
8252
|
+
return "";
|
|
8249
8253
|
}
|
|
8250
8254
|
function getBetterAuthConvexInstructions(hasWeb, webPort, packageManager) {
|
|
8251
8255
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
@@ -8433,7 +8437,7 @@ async function setPackageManagerVersion(projectDir, packageManager) {
|
|
|
8433
8437
|
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
8434
8438
|
if (!await fs.pathExists(pkgJsonPath)) return;
|
|
8435
8439
|
try {
|
|
8436
|
-
const { stdout } = await
|
|
8440
|
+
const { stdout } = await $({ cwd: os$1.tmpdir() })`${packageManager} -v`;
|
|
8437
8441
|
const version = stdout.trim();
|
|
8438
8442
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
8439
8443
|
pkgJson.packageManager = `${packageManager}@${version}`;
|
|
@@ -8763,7 +8767,7 @@ const router = os.router({
|
|
|
8763
8767
|
yes: z.boolean().optional().default(false).describe("Use default configuration"),
|
|
8764
8768
|
yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
|
|
8765
8769
|
verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
|
|
8766
|
-
ecosystem: types_exports.EcosystemSchema.optional().describe("Language ecosystem (typescript, rust, or
|
|
8770
|
+
ecosystem: types_exports.EcosystemSchema.optional().describe("Language ecosystem (typescript, rust, python, or go)"),
|
|
8767
8771
|
database: types_exports.DatabaseSchema.optional(),
|
|
8768
8772
|
orm: types_exports.ORMSchema.optional(),
|
|
8769
8773
|
auth: types_exports.AuthSchema.optional(),
|
|
@@ -8781,6 +8785,7 @@ const router = os.router({
|
|
|
8781
8785
|
animation: types_exports.AnimationSchema.optional(),
|
|
8782
8786
|
logging: types_exports.LoggingSchema.optional(),
|
|
8783
8787
|
observability: types_exports.ObservabilitySchema.optional(),
|
|
8788
|
+
featureFlags: types_exports.FeatureFlagsSchema.optional().describe("Feature flags provider"),
|
|
8784
8789
|
analytics: types_exports.AnalyticsSchema.optional().describe("Privacy-focused analytics"),
|
|
8785
8790
|
cms: types_exports.CMSSchema.optional().describe("Headless CMS solution"),
|
|
8786
8791
|
caching: types_exports.CachingSchema.optional().describe("Caching solution"),
|
|
@@ -8799,6 +8804,13 @@ const router = os.router({
|
|
|
8799
8804
|
api: types_exports.APISchema.optional(),
|
|
8800
8805
|
cssFramework: types_exports.CSSFrameworkSchema.optional(),
|
|
8801
8806
|
uiLibrary: types_exports.UILibrarySchema.optional(),
|
|
8807
|
+
shadcnBase: types_exports.ShadcnBaseSchema.optional().describe("shadcn/ui headless library (radix, base)"),
|
|
8808
|
+
shadcnStyle: types_exports.ShadcnStyleSchema.optional().describe("shadcn/ui visual style (vega, nova, maia, lyra, mira)"),
|
|
8809
|
+
shadcnIconLibrary: types_exports.ShadcnIconLibrarySchema.optional().describe("shadcn/ui icon library (lucide, tabler, hugeicons, phosphor, remixicon)"),
|
|
8810
|
+
shadcnColorTheme: types_exports.ShadcnColorThemeSchema.optional().describe("shadcn/ui color theme (neutral, blue, violet, etc.)"),
|
|
8811
|
+
shadcnBaseColor: types_exports.ShadcnBaseColorSchema.optional().describe("shadcn/ui base neutral color (neutral, stone, zinc, gray)"),
|
|
8812
|
+
shadcnFont: types_exports.ShadcnFontSchema.optional().describe("shadcn/ui font (inter, geist, figtree, etc.)"),
|
|
8813
|
+
shadcnRadius: types_exports.ShadcnRadiusSchema.optional().describe("shadcn/ui border radius (default, none, small, medium, large)"),
|
|
8802
8814
|
webDeploy: types_exports.WebDeploySchema.optional(),
|
|
8803
8815
|
serverDeploy: types_exports.ServerDeploySchema.optional(),
|
|
8804
8816
|
directoryConflict: types_exports.DirectoryConflictSchema.optional(),
|
|
@@ -9017,6 +9029,13 @@ async function createVirtual(options) {
|
|
|
9017
9029
|
serverDeploy: options.serverDeploy || "none",
|
|
9018
9030
|
cssFramework: options.cssFramework || "tailwind",
|
|
9019
9031
|
uiLibrary: options.uiLibrary || "shadcn-ui",
|
|
9032
|
+
shadcnBase: options.shadcnBase ?? "radix",
|
|
9033
|
+
shadcnStyle: options.shadcnStyle ?? "nova",
|
|
9034
|
+
shadcnIconLibrary: options.shadcnIconLibrary ?? "lucide",
|
|
9035
|
+
shadcnColorTheme: options.shadcnColorTheme ?? "neutral",
|
|
9036
|
+
shadcnBaseColor: options.shadcnBaseColor ?? "neutral",
|
|
9037
|
+
shadcnFont: options.shadcnFont ?? "inter",
|
|
9038
|
+
shadcnRadius: options.shadcnRadius ?? "default",
|
|
9020
9039
|
ai: options.ai || "none",
|
|
9021
9040
|
stateManagement: options.stateManagement || "none",
|
|
9022
9041
|
forms: options.forms || "react-hook-form",
|