create-better-t-stack 2.45.5 → 2.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -1
- package/dist/{src-Cun9EO6e.js → src-NOw0j6Z9.js} +272 -132
- package/package.json +1 -1
- package/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs +153 -6
- package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +36 -2
- package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs +22 -0
- package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +6 -0
- package/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs +58 -0
- package/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +31 -41
- package/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs +37 -3
- package/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +50 -32
- package/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +51 -36
- package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs +11 -0
- package/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs +52 -29
- package/templates/auth/better-auth/web/svelte/src/lib/{auth-client.ts → auth-client.ts.hbs} +6 -0
- package/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs +24 -3
- package/templates/backend/server/server-base/package.json.hbs +1 -1
- package/templates/extras/bunfig.toml.hbs +4 -0
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +4 -0
- package/templates/frontend/solid/package.json.hbs +10 -10
- package/templates/payments/polar/server/base/src/lib/payments.ts.hbs +6 -0
- package/templates/payments/polar/web/nuxt/app/pages/success.vue.hbs +11 -0
- package/templates/payments/polar/web/react/next/src/app/success/page.tsx.hbs +15 -0
- package/templates/payments/polar/web/react/react-router/src/routes/success.tsx.hbs +13 -0
- package/templates/payments/polar/web/react/tanstack-router/src/routes/success.tsx.hbs +19 -0
- package/templates/payments/polar/web/react/tanstack-start/src/routes/success.tsx.hbs +19 -0
- package/templates/payments/polar/web/solid/src/routes/success.tsx.hbs +23 -0
- package/templates/payments/polar/web/svelte/src/routes/success/+page.svelte.hbs +12 -0
- package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts +0 -16
- package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts +0 -5
- /package/templates/auth/better-auth/web/nuxt/app/components/{SignInForm.vue → SignInForm.vue.hbs} +0 -0
- /package/templates/auth/better-auth/web/nuxt/app/components/{SignUpForm.vue → SignUpForm.vue.hbs} +0 -0
- /package/templates/auth/better-auth/web/nuxt/app/components/{UserMenu.vue → UserMenu.vue.hbs} +0 -0
- /package/templates/auth/better-auth/web/nuxt/app/pages/{login.vue → login.vue.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/next/src/app/login/{page.tsx → page.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/next/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/next/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/next/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/next/src/components/{user-menu.tsx → user-menu.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/react-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/react-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/react-router/src/components/{user-menu.tsx → user-menu.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/react-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/svelte/src/components/{SignInForm.svelte → SignInForm.svelte.hbs} +0 -0
- /package/templates/auth/better-auth/web/svelte/src/components/{SignUpForm.svelte → SignUpForm.svelte.hbs} +0 -0
- /package/templates/auth/better-auth/web/svelte/src/components/{UserMenu.svelte → UserMenu.svelte.hbs} +0 -0
- /package/templates/auth/better-auth/web/svelte/src/routes/login/{+page.svelte → +page.svelte.hbs} +0 -0
|
@@ -36,6 +36,7 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
36
36
|
database: "sqlite",
|
|
37
37
|
orm: "drizzle",
|
|
38
38
|
auth: "better-auth",
|
|
39
|
+
payments: "none",
|
|
39
40
|
addons: ["turborepo"],
|
|
40
41
|
examples: [],
|
|
41
42
|
git: true,
|
|
@@ -59,8 +60,8 @@ function getDefaultConfig() {
|
|
|
59
60
|
}
|
|
60
61
|
const DEFAULT_CONFIG = getDefaultConfig();
|
|
61
62
|
const dependencyVersionMap = {
|
|
62
|
-
"better-auth": "^1.3.
|
|
63
|
-
"@better-auth/expo": "^1.3.
|
|
63
|
+
"better-auth": "^1.3.10",
|
|
64
|
+
"@better-auth/expo": "^1.3.10",
|
|
64
65
|
"@clerk/nextjs": "^6.31.5",
|
|
65
66
|
"@clerk/clerk-react": "^5.45.0",
|
|
66
67
|
"@clerk/tanstack-react-start": "^0.23.1",
|
|
@@ -131,9 +132,9 @@ const dependencyVersionMap = {
|
|
|
131
132
|
"@tanstack/vue-query": "^5.83.0",
|
|
132
133
|
"@tanstack/react-query-devtools": "^5.85.5",
|
|
133
134
|
"@tanstack/react-query": "^5.85.5",
|
|
134
|
-
"@tanstack/solid-query": "^5.
|
|
135
|
-
"@tanstack/solid-query-devtools": "^5.
|
|
136
|
-
"@tanstack/solid-router-devtools": "^1.131.
|
|
135
|
+
"@tanstack/solid-query": "^5.87.4",
|
|
136
|
+
"@tanstack/solid-query-devtools": "^5.87.4",
|
|
137
|
+
"@tanstack/solid-router-devtools": "^1.131.44",
|
|
137
138
|
wrangler: "^4.23.0",
|
|
138
139
|
"@cloudflare/vite-plugin": "^1.9.0",
|
|
139
140
|
"@opennextjs/cloudflare": "^1.6.5",
|
|
@@ -142,7 +143,9 @@ const dependencyVersionMap = {
|
|
|
142
143
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
143
144
|
alchemy: "^0.65.1",
|
|
144
145
|
nitropack: "^2.12.4",
|
|
145
|
-
dotenv: "^17.2.1"
|
|
146
|
+
dotenv: "^17.2.1",
|
|
147
|
+
"@polar-sh/better-auth": "^1.1.3",
|
|
148
|
+
"@polar-sh/sdk": "^0.34.16"
|
|
146
149
|
};
|
|
147
150
|
const ADDON_COMPATIBILITY = {
|
|
148
151
|
pwa: [
|
|
@@ -256,6 +259,7 @@ const AuthSchema = z.enum([
|
|
|
256
259
|
"clerk",
|
|
257
260
|
"none"
|
|
258
261
|
]).describe("Authentication provider");
|
|
262
|
+
const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
|
|
259
263
|
const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
|
|
260
264
|
const invalidChars = [
|
|
261
265
|
"<",
|
|
@@ -286,28 +290,16 @@ const DirectoryConflictSchema = z.enum([
|
|
|
286
290
|
]).describe("How to handle existing directory conflicts");
|
|
287
291
|
|
|
288
292
|
//#endregion
|
|
289
|
-
//#region src/utils/
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
return { isCompatible: true };
|
|
302
|
-
}
|
|
303
|
-
function getCompatibleAddons(allAddons, frontend, existingAddons = []) {
|
|
304
|
-
return allAddons.filter((addon) => {
|
|
305
|
-
if (existingAddons.includes(addon)) return false;
|
|
306
|
-
if (addon === "none") return false;
|
|
307
|
-
const { isCompatible } = validateAddonCompatibility(addon, frontend);
|
|
308
|
-
return isCompatible;
|
|
309
|
-
});
|
|
310
|
-
}
|
|
293
|
+
//#region src/utils/compatibility.ts
|
|
294
|
+
const WEB_FRAMEWORKS = [
|
|
295
|
+
"tanstack-router",
|
|
296
|
+
"react-router",
|
|
297
|
+
"tanstack-start",
|
|
298
|
+
"next",
|
|
299
|
+
"nuxt",
|
|
300
|
+
"svelte",
|
|
301
|
+
"solid"
|
|
302
|
+
];
|
|
311
303
|
|
|
312
304
|
//#endregion
|
|
313
305
|
//#region src/utils/errors.ts
|
|
@@ -331,6 +323,121 @@ function handleError(error, fallbackMessage) {
|
|
|
331
323
|
process.exit(1);
|
|
332
324
|
}
|
|
333
325
|
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/utils/compatibility-rules.ts
|
|
328
|
+
function isWebFrontend(value) {
|
|
329
|
+
return WEB_FRAMEWORKS.includes(value);
|
|
330
|
+
}
|
|
331
|
+
function splitFrontends(values = []) {
|
|
332
|
+
const web = values.filter((f) => isWebFrontend(f));
|
|
333
|
+
const native = values.filter((f) => f === "native-nativewind" || f === "native-unistyles");
|
|
334
|
+
return {
|
|
335
|
+
web,
|
|
336
|
+
native
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function ensureSingleWebAndNative(frontends) {
|
|
340
|
+
const { web, native } = splitFrontends(frontends);
|
|
341
|
+
if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
|
|
342
|
+
if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
|
|
343
|
+
}
|
|
344
|
+
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
345
|
+
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
|
|
346
|
+
if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
|
|
347
|
+
if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
|
|
348
|
+
if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. 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.");
|
|
349
|
+
if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
|
|
350
|
+
if (providedFlags.has("dbSetup") && options.dbSetup === "docker" && config.runtime === "workers") exitWithError("Docker setup (--db-setup docker) is not compatible 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.");
|
|
351
|
+
}
|
|
352
|
+
function validateApiFrontendCompatibility(api, frontends = []) {
|
|
353
|
+
const includesNuxt = frontends.includes("nuxt");
|
|
354
|
+
const includesSvelte = frontends.includes("svelte");
|
|
355
|
+
const includesSolid = frontends.includes("solid");
|
|
356
|
+
if ((includesNuxt || includesSvelte || includesSolid) && api === "trpc") exitWithError(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`);
|
|
357
|
+
}
|
|
358
|
+
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
359
|
+
if (backend === "convex" && frontend === "solid") return false;
|
|
360
|
+
if (auth === "clerk" && backend === "convex") {
|
|
361
|
+
const incompatibleFrontends = [
|
|
362
|
+
"nuxt",
|
|
363
|
+
"svelte",
|
|
364
|
+
"solid"
|
|
365
|
+
];
|
|
366
|
+
if (incompatibleFrontends.includes(frontend)) return false;
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
function allowedApisForFrontends(frontends = []) {
|
|
371
|
+
const includesNuxt = frontends.includes("nuxt");
|
|
372
|
+
const includesSvelte = frontends.includes("svelte");
|
|
373
|
+
const includesSolid = frontends.includes("solid");
|
|
374
|
+
const base = [
|
|
375
|
+
"trpc",
|
|
376
|
+
"orpc",
|
|
377
|
+
"none"
|
|
378
|
+
];
|
|
379
|
+
if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
|
|
380
|
+
return base;
|
|
381
|
+
}
|
|
382
|
+
function isExampleTodoAllowed(backend, database) {
|
|
383
|
+
return !(backend !== "convex" && backend !== "none" && database === "none");
|
|
384
|
+
}
|
|
385
|
+
function isExampleAIAllowed(_backend, frontends = []) {
|
|
386
|
+
const includesSolid = frontends.includes("solid");
|
|
387
|
+
if (includesSolid) return false;
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
391
|
+
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
392
|
+
}
|
|
393
|
+
function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
394
|
+
if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
|
|
395
|
+
}
|
|
396
|
+
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
397
|
+
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
398
|
+
if (compatibleFrontends.length > 0) {
|
|
399
|
+
const hasCompatibleFrontend = frontend.some((f) => compatibleFrontends.includes(f));
|
|
400
|
+
if (!hasCompatibleFrontend) {
|
|
401
|
+
const frontendList = compatibleFrontends.join(", ");
|
|
402
|
+
return {
|
|
403
|
+
isCompatible: false,
|
|
404
|
+
reason: `${addon} addon requires one of these frontends: ${frontendList}`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return { isCompatible: true };
|
|
409
|
+
}
|
|
410
|
+
function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth) {
|
|
411
|
+
return allAddons.filter((addon) => {
|
|
412
|
+
if (existingAddons.includes(addon)) return false;
|
|
413
|
+
if (addon === "none") return false;
|
|
414
|
+
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth);
|
|
415
|
+
return isCompatible;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
|
|
419
|
+
for (const addon of addons) {
|
|
420
|
+
if (addon === "none") continue;
|
|
421
|
+
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth);
|
|
422
|
+
if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function validatePaymentsCompatibility(payments, auth, backend, frontends = []) {
|
|
426
|
+
if (!payments || payments === "none") return;
|
|
427
|
+
if (payments === "polar") {
|
|
428
|
+
if (!auth || auth === "none" || auth !== "better-auth") exitWithError("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
|
|
429
|
+
if (backend === "convex") exitWithError("Polar payments is not compatible with Convex backend. Please use a different backend or choose a different payments provider.");
|
|
430
|
+
const { web } = splitFrontends(frontends);
|
|
431
|
+
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.");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function validateExamplesCompatibility(examples, backend, database, frontend) {
|
|
435
|
+
const examplesArr = examples ?? [];
|
|
436
|
+
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
437
|
+
if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
|
|
438
|
+
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
439
|
+
}
|
|
440
|
+
|
|
334
441
|
//#endregion
|
|
335
442
|
//#region src/prompts/addons.ts
|
|
336
443
|
function getAddonDisplay(addon) {
|
|
@@ -401,7 +508,7 @@ const ADDON_GROUPS = {
|
|
|
401
508
|
"husky"
|
|
402
509
|
]
|
|
403
510
|
};
|
|
404
|
-
async function getAddonsChoice(addons, frontends) {
|
|
511
|
+
async function getAddonsChoice(addons, frontends, auth) {
|
|
405
512
|
if (addons !== void 0) return addons;
|
|
406
513
|
const allAddons = AddonsSchema.options.filter((addon) => addon !== "none");
|
|
407
514
|
const groupedOptions = {
|
|
@@ -411,7 +518,7 @@ async function getAddonsChoice(addons, frontends) {
|
|
|
411
518
|
};
|
|
412
519
|
const frontendsArray = frontends || [];
|
|
413
520
|
for (const addon of allAddons) {
|
|
414
|
-
const { isCompatible } = validateAddonCompatibility(addon, frontendsArray);
|
|
521
|
+
const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth);
|
|
415
522
|
if (!isCompatible) continue;
|
|
416
523
|
const { label, hint } = getAddonDisplay(addon);
|
|
417
524
|
const option = {
|
|
@@ -437,14 +544,14 @@ async function getAddonsChoice(addons, frontends) {
|
|
|
437
544
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
438
545
|
return response;
|
|
439
546
|
}
|
|
440
|
-
async function getAddonsToAdd(frontend, existingAddons = []) {
|
|
547
|
+
async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
441
548
|
const groupedOptions = {
|
|
442
549
|
Documentation: [],
|
|
443
550
|
Linting: [],
|
|
444
551
|
Other: []
|
|
445
552
|
};
|
|
446
553
|
const frontendArray = frontend || [];
|
|
447
|
-
const compatibleAddons = getCompatibleAddons(AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons);
|
|
554
|
+
const compatibleAddons = getCompatibleAddons(AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
|
|
448
555
|
for (const addon of compatibleAddons) {
|
|
449
556
|
const { label, hint } = getAddonDisplay(addon);
|
|
450
557
|
const option = {
|
|
@@ -470,102 +577,6 @@ async function getAddonsToAdd(frontend, existingAddons = []) {
|
|
|
470
577
|
return response;
|
|
471
578
|
}
|
|
472
579
|
|
|
473
|
-
//#endregion
|
|
474
|
-
//#region src/utils/compatibility.ts
|
|
475
|
-
const WEB_FRAMEWORKS = [
|
|
476
|
-
"tanstack-router",
|
|
477
|
-
"react-router",
|
|
478
|
-
"tanstack-start",
|
|
479
|
-
"next",
|
|
480
|
-
"nuxt",
|
|
481
|
-
"svelte",
|
|
482
|
-
"solid"
|
|
483
|
-
];
|
|
484
|
-
|
|
485
|
-
//#endregion
|
|
486
|
-
//#region src/utils/compatibility-rules.ts
|
|
487
|
-
function isWebFrontend(value) {
|
|
488
|
-
return WEB_FRAMEWORKS.includes(value);
|
|
489
|
-
}
|
|
490
|
-
function splitFrontends(values = []) {
|
|
491
|
-
const web = values.filter((f) => isWebFrontend(f));
|
|
492
|
-
const native = values.filter((f) => f === "native-nativewind" || f === "native-unistyles");
|
|
493
|
-
return {
|
|
494
|
-
web,
|
|
495
|
-
native
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
function ensureSingleWebAndNative(frontends) {
|
|
499
|
-
const { web, native } = splitFrontends(frontends);
|
|
500
|
-
if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
|
|
501
|
-
if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
|
|
502
|
-
}
|
|
503
|
-
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
504
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
|
|
505
|
-
if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
|
|
506
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.database === "mongodb") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
|
|
507
|
-
if (providedFlags.has("runtime") && options.runtime === "workers" && config.dbSetup === "docker") exitWithError("Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. 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.");
|
|
508
|
-
if (providedFlags.has("database") && config.database === "mongodb" && config.runtime === "workers") exitWithError("MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.");
|
|
509
|
-
if (providedFlags.has("dbSetup") && options.dbSetup === "docker" && config.runtime === "workers") exitWithError("Docker setup (--db-setup docker) is not compatible 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.");
|
|
510
|
-
}
|
|
511
|
-
function validateApiFrontendCompatibility(api, frontends = []) {
|
|
512
|
-
const includesNuxt = frontends.includes("nuxt");
|
|
513
|
-
const includesSvelte = frontends.includes("svelte");
|
|
514
|
-
const includesSolid = frontends.includes("solid");
|
|
515
|
-
if ((includesNuxt || includesSvelte || includesSolid) && api === "trpc") exitWithError(`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`);
|
|
516
|
-
}
|
|
517
|
-
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
518
|
-
if (backend === "convex" && frontend === "solid") return false;
|
|
519
|
-
if (auth === "clerk" && backend === "convex") {
|
|
520
|
-
const incompatibleFrontends = [
|
|
521
|
-
"nuxt",
|
|
522
|
-
"svelte",
|
|
523
|
-
"solid"
|
|
524
|
-
];
|
|
525
|
-
if (incompatibleFrontends.includes(frontend)) return false;
|
|
526
|
-
}
|
|
527
|
-
return true;
|
|
528
|
-
}
|
|
529
|
-
function allowedApisForFrontends(frontends = []) {
|
|
530
|
-
const includesNuxt = frontends.includes("nuxt");
|
|
531
|
-
const includesSvelte = frontends.includes("svelte");
|
|
532
|
-
const includesSolid = frontends.includes("solid");
|
|
533
|
-
const base = [
|
|
534
|
-
"trpc",
|
|
535
|
-
"orpc",
|
|
536
|
-
"none"
|
|
537
|
-
];
|
|
538
|
-
if (includesNuxt || includesSvelte || includesSolid) return ["orpc", "none"];
|
|
539
|
-
return base;
|
|
540
|
-
}
|
|
541
|
-
function isExampleTodoAllowed(backend, database) {
|
|
542
|
-
return !(backend !== "convex" && backend !== "none" && database === "none");
|
|
543
|
-
}
|
|
544
|
-
function isExampleAIAllowed(_backend, frontends = []) {
|
|
545
|
-
const includesSolid = frontends.includes("solid");
|
|
546
|
-
if (includesSolid) return false;
|
|
547
|
-
return true;
|
|
548
|
-
}
|
|
549
|
-
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
550
|
-
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) exitWithError("'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.");
|
|
551
|
-
}
|
|
552
|
-
function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
553
|
-
if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
|
|
554
|
-
}
|
|
555
|
-
function validateAddonsAgainstFrontends(addons = [], frontends = []) {
|
|
556
|
-
for (const addon of addons) {
|
|
557
|
-
if (addon === "none") continue;
|
|
558
|
-
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends);
|
|
559
|
-
if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
function validateExamplesCompatibility(examples, backend, database, frontend) {
|
|
563
|
-
const examplesArr = examples ?? [];
|
|
564
|
-
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
565
|
-
if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
|
|
566
|
-
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
567
|
-
}
|
|
568
|
-
|
|
569
580
|
//#endregion
|
|
570
581
|
//#region src/prompts/api.ts
|
|
571
582
|
async function getApiChoice(Api, frontend, backend) {
|
|
@@ -1037,6 +1048,30 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
1037
1048
|
return response;
|
|
1038
1049
|
}
|
|
1039
1050
|
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region src/prompts/payments.ts
|
|
1053
|
+
async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
1054
|
+
if (payments !== void 0) return payments;
|
|
1055
|
+
const isPolarCompatible = auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0);
|
|
1056
|
+
if (!isPolarCompatible) return "none";
|
|
1057
|
+
const options = [{
|
|
1058
|
+
value: "polar",
|
|
1059
|
+
label: "Polar",
|
|
1060
|
+
hint: "Turn your software into a business. 6 lines of code."
|
|
1061
|
+
}, {
|
|
1062
|
+
value: "none",
|
|
1063
|
+
label: "None",
|
|
1064
|
+
hint: "No payments integration"
|
|
1065
|
+
}];
|
|
1066
|
+
const response = await select({
|
|
1067
|
+
message: "Select payments provider",
|
|
1068
|
+
options,
|
|
1069
|
+
initialValue: DEFAULT_CONFIG.payments
|
|
1070
|
+
});
|
|
1071
|
+
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1072
|
+
return response;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1040
1075
|
//#endregion
|
|
1041
1076
|
//#region src/prompts/runtime.ts
|
|
1042
1077
|
async function getRuntimeChoice(runtime, backend) {
|
|
@@ -1226,7 +1261,8 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
1226
1261
|
orm: ({ results }) => getORMChoice(flags.orm, results.database !== "none", results.database, results.backend, results.runtime),
|
|
1227
1262
|
api: ({ results }) => getApiChoice(flags.api, results.frontend, results.backend),
|
|
1228
1263
|
auth: ({ results }) => getAuthChoice(flags.auth, results.database !== "none", results.backend, results.frontend),
|
|
1229
|
-
|
|
1264
|
+
payments: ({ results }) => getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend),
|
|
1265
|
+
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend, results.auth),
|
|
1230
1266
|
examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
|
|
1231
1267
|
dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend, results.runtime),
|
|
1232
1268
|
webDeploy: ({ results }) => getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
|
|
@@ -1245,6 +1281,7 @@ async function gatherConfig(flags, projectName, projectDir, relativePath) {
|
|
|
1245
1281
|
database: result.database,
|
|
1246
1282
|
orm: result.orm,
|
|
1247
1283
|
auth: result.auth,
|
|
1284
|
+
payments: result.payments,
|
|
1248
1285
|
addons: result.addons,
|
|
1249
1286
|
examples: result.examples,
|
|
1250
1287
|
git: result.git,
|
|
@@ -1388,6 +1425,7 @@ function displayConfig(config) {
|
|
|
1388
1425
|
if (config.database !== void 0) configDisplay.push(`${pc.blue("Database:")} ${String(config.database)}`);
|
|
1389
1426
|
if (config.orm !== void 0) configDisplay.push(`${pc.blue("ORM:")} ${String(config.orm)}`);
|
|
1390
1427
|
if (config.auth !== void 0) configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`);
|
|
1428
|
+
if (config.payments !== void 0) configDisplay.push(`${pc.blue("Payments:")} ${String(config.payments)}`);
|
|
1391
1429
|
if (config.addons !== void 0) {
|
|
1392
1430
|
const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
|
|
1393
1431
|
const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
|
|
@@ -1426,6 +1464,7 @@ function generateReproducibleCommand(config) {
|
|
|
1426
1464
|
flags.push(`--orm ${config.orm}`);
|
|
1427
1465
|
flags.push(`--api ${config.api}`);
|
|
1428
1466
|
flags.push(`--auth ${config.auth}`);
|
|
1467
|
+
flags.push(`--payments ${config.payments}`);
|
|
1429
1468
|
if (config.addons && config.addons.length > 0) flags.push(`--addons ${config.addons.join(" ")}`);
|
|
1430
1469
|
else flags.push("--addons none");
|
|
1431
1470
|
if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
|
|
@@ -1595,6 +1634,7 @@ function processFlags(options, projectName) {
|
|
|
1595
1634
|
if (options.database) config.database = options.database;
|
|
1596
1635
|
if (options.orm) config.orm = options.orm;
|
|
1597
1636
|
if (options.auth !== void 0) config.auth = options.auth;
|
|
1637
|
+
if (options.payments !== void 0) config.payments = options.payments;
|
|
1598
1638
|
if (options.git !== void 0) config.git = options.git;
|
|
1599
1639
|
if (options.install !== void 0) config.install = options.install;
|
|
1600
1640
|
if (options.runtime) config.runtime = options.runtime;
|
|
@@ -1751,7 +1791,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
1751
1791
|
validateWorkersCompatibility(providedFlags, options, config);
|
|
1752
1792
|
if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'wrangler' or 'alchemy' for --server-deploy.");
|
|
1753
1793
|
if (config.addons && config.addons.length > 0) {
|
|
1754
|
-
validateAddonsAgainstFrontends(config.addons, config.frontend);
|
|
1794
|
+
validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
1755
1795
|
config.addons = [...new Set(config.addons)];
|
|
1756
1796
|
}
|
|
1757
1797
|
validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
|
|
@@ -1761,7 +1801,8 @@ function validateConfigForProgrammaticUse(config) {
|
|
|
1761
1801
|
validateDatabaseOrmAuth(config);
|
|
1762
1802
|
if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
|
|
1763
1803
|
validateApiFrontendCompatibility(config.api, config.frontend);
|
|
1764
|
-
|
|
1804
|
+
validatePaymentsCompatibility(config.payments, config.auth, config.backend, config.frontend);
|
|
1805
|
+
if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
|
|
1765
1806
|
validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
|
|
1766
1807
|
} catch (error) {
|
|
1767
1808
|
if (error instanceof Error) throw error;
|
|
@@ -1859,6 +1900,7 @@ async function writeBtsConfig(projectConfig) {
|
|
|
1859
1900
|
addons: projectConfig.addons,
|
|
1860
1901
|
examples: projectConfig.examples,
|
|
1861
1902
|
auth: projectConfig.auth,
|
|
1903
|
+
payments: projectConfig.payments,
|
|
1862
1904
|
packageManager: projectConfig.packageManager,
|
|
1863
1905
|
dbSetup: projectConfig.dbSetup,
|
|
1864
1906
|
api: projectConfig.api,
|
|
@@ -2483,6 +2525,7 @@ async function detectProjectConfig(projectDir) {
|
|
|
2483
2525
|
addons: btsConfig.addons,
|
|
2484
2526
|
examples: btsConfig.examples,
|
|
2485
2527
|
auth: btsConfig.auth,
|
|
2528
|
+
payments: btsConfig.payments,
|
|
2486
2529
|
packageManager: btsConfig.packageManager,
|
|
2487
2530
|
dbSetup: btsConfig.dbSetup,
|
|
2488
2531
|
api: btsConfig.api,
|
|
@@ -2863,6 +2906,49 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2863
2906
|
}
|
|
2864
2907
|
}
|
|
2865
2908
|
}
|
|
2909
|
+
async function setupPaymentsTemplate(projectDir, context) {
|
|
2910
|
+
if (!context.payments || context.payments === "none") return;
|
|
2911
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2912
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
2913
|
+
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2914
|
+
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2915
|
+
if (serverAppDirExists && context.backend !== "convex") {
|
|
2916
|
+
const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
|
|
2917
|
+
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, serverAppDir, context);
|
|
2918
|
+
}
|
|
2919
|
+
const hasReactWeb = context.frontend.some((f) => [
|
|
2920
|
+
"tanstack-router",
|
|
2921
|
+
"react-router",
|
|
2922
|
+
"tanstack-start",
|
|
2923
|
+
"next"
|
|
2924
|
+
].includes(f));
|
|
2925
|
+
const hasNuxtWeb = context.frontend.includes("nuxt");
|
|
2926
|
+
const hasSvelteWeb = context.frontend.includes("svelte");
|
|
2927
|
+
const hasSolidWeb = context.frontend.includes("solid");
|
|
2928
|
+
if (webAppDirExists && (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb)) {
|
|
2929
|
+
if (hasReactWeb) {
|
|
2930
|
+
const reactFramework = context.frontend.find((f) => [
|
|
2931
|
+
"tanstack-router",
|
|
2932
|
+
"react-router",
|
|
2933
|
+
"tanstack-start",
|
|
2934
|
+
"next"
|
|
2935
|
+
].includes(f));
|
|
2936
|
+
if (reactFramework) {
|
|
2937
|
+
const paymentsWebSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/react/${reactFramework}`);
|
|
2938
|
+
if (await fs.pathExists(paymentsWebSrc)) await processAndCopyFiles("**/*", paymentsWebSrc, webAppDir, context);
|
|
2939
|
+
}
|
|
2940
|
+
} else if (hasNuxtWeb) {
|
|
2941
|
+
const paymentsWebNuxtSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/nuxt`);
|
|
2942
|
+
if (await fs.pathExists(paymentsWebNuxtSrc)) await processAndCopyFiles("**/*", paymentsWebNuxtSrc, webAppDir, context);
|
|
2943
|
+
} else if (hasSvelteWeb) {
|
|
2944
|
+
const paymentsWebSvelteSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/svelte`);
|
|
2945
|
+
if (await fs.pathExists(paymentsWebSvelteSrc)) await processAndCopyFiles("**/*", paymentsWebSvelteSrc, webAppDir, context);
|
|
2946
|
+
} else if (hasSolidWeb) {
|
|
2947
|
+
const paymentsWebSolidSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/solid`);
|
|
2948
|
+
if (await fs.pathExists(paymentsWebSolidSrc)) await processAndCopyFiles("**/*", paymentsWebSolidSrc, webAppDir, context);
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2866
2952
|
async function setupAddonsTemplate(projectDir, context) {
|
|
2867
2953
|
if (!context.addons || context.addons.length === 0) return;
|
|
2868
2954
|
for (const addon of context.addons) {
|
|
@@ -3057,6 +3143,7 @@ async function addAddonsToProject(input) {
|
|
|
3057
3143
|
addons: input.addons,
|
|
3058
3144
|
examples: detectedConfig.examples || [],
|
|
3059
3145
|
auth: detectedConfig.auth || "none",
|
|
3146
|
+
payments: detectedConfig.payments || "none",
|
|
3060
3147
|
git: false,
|
|
3061
3148
|
packageManager: input.packageManager || detectedConfig.packageManager || "npm",
|
|
3062
3149
|
install: input.install || false,
|
|
@@ -3843,6 +3930,7 @@ async function addDeploymentToProject(input) {
|
|
|
3843
3930
|
addons: detectedConfig.addons || [],
|
|
3844
3931
|
examples: detectedConfig.examples || [],
|
|
3845
3932
|
auth: detectedConfig.auth || "none",
|
|
3933
|
+
payments: detectedConfig.payments || "none",
|
|
3846
3934
|
git: false,
|
|
3847
3935
|
packageManager: input.packageManager || detectedConfig.packageManager || "npm",
|
|
3848
3936
|
install: input.install || false,
|
|
@@ -4384,6 +4472,16 @@ async function setupEnvironmentVariables(config) {
|
|
|
4384
4472
|
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4385
4473
|
value: "",
|
|
4386
4474
|
condition: examples?.includes("ai") || false
|
|
4475
|
+
},
|
|
4476
|
+
{
|
|
4477
|
+
key: "POLAR_ACCESS_TOKEN",
|
|
4478
|
+
value: "",
|
|
4479
|
+
condition: config.payments === "polar"
|
|
4480
|
+
},
|
|
4481
|
+
{
|
|
4482
|
+
key: "POLAR_SUCCESS_URL",
|
|
4483
|
+
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
4484
|
+
condition: config.payments === "polar"
|
|
4387
4485
|
}
|
|
4388
4486
|
];
|
|
4389
4487
|
await addEnvVariablesToFile(envPath, serverVars);
|
|
@@ -5944,6 +6042,39 @@ async function initializeGit(projectDir, useGit) {
|
|
|
5944
6042
|
await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
|
|
5945
6043
|
}
|
|
5946
6044
|
|
|
6045
|
+
//#endregion
|
|
6046
|
+
//#region src/helpers/core/payments-setup.ts
|
|
6047
|
+
async function setupPayments(config) {
|
|
6048
|
+
const { payments, projectDir, frontend } = config;
|
|
6049
|
+
if (!payments || payments === "none") return;
|
|
6050
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
6051
|
+
const clientDir = path.join(projectDir, "apps/web");
|
|
6052
|
+
const serverDirExists = await fs.pathExists(serverDir);
|
|
6053
|
+
const clientDirExists = await fs.pathExists(clientDir);
|
|
6054
|
+
if (!serverDirExists) return;
|
|
6055
|
+
if (payments === "polar") {
|
|
6056
|
+
await addPackageDependency({
|
|
6057
|
+
dependencies: ["@polar-sh/better-auth", "@polar-sh/sdk"],
|
|
6058
|
+
projectDir: serverDir
|
|
6059
|
+
});
|
|
6060
|
+
if (clientDirExists) {
|
|
6061
|
+
const hasWebFrontend$1 = frontend.some((f) => [
|
|
6062
|
+
"react-router",
|
|
6063
|
+
"tanstack-router",
|
|
6064
|
+
"tanstack-start",
|
|
6065
|
+
"next",
|
|
6066
|
+
"nuxt",
|
|
6067
|
+
"svelte",
|
|
6068
|
+
"solid"
|
|
6069
|
+
].includes(f));
|
|
6070
|
+
if (hasWebFrontend$1) await addPackageDependency({
|
|
6071
|
+
dependencies: ["@polar-sh/better-auth"],
|
|
6072
|
+
projectDir: clientDir
|
|
6073
|
+
});
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
}
|
|
6077
|
+
|
|
5947
6078
|
//#endregion
|
|
5948
6079
|
//#region src/utils/docker-utils.ts
|
|
5949
6080
|
async function isDockerInstalled() {
|
|
@@ -6011,6 +6142,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
6011
6142
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
6012
6143
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
6013
6144
|
const clerkInstructions = isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
|
|
6145
|
+
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions() : "";
|
|
6014
6146
|
const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy);
|
|
6015
6147
|
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy);
|
|
6016
6148
|
const hasWeb = frontend?.some((f) => [
|
|
@@ -6063,6 +6195,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
6063
6195
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
6064
6196
|
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
|
|
6065
6197
|
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
|
|
6198
|
+
if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
|
|
6066
6199
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
6067
6200
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
6068
6201
|
output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}\n\n`;
|
|
@@ -6151,6 +6284,9 @@ function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy) {
|
|
|
6151
6284
|
function getClerkInstructions() {
|
|
6152
6285
|
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`;
|
|
6153
6286
|
}
|
|
6287
|
+
function getPolarInstructions() {
|
|
6288
|
+
return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in apps/server/.env`;
|
|
6289
|
+
}
|
|
6154
6290
|
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
|
|
6155
6291
|
const instructions = [];
|
|
6156
6292
|
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
|
|
@@ -6355,6 +6491,7 @@ async function createProject(options, cliInput) {
|
|
|
6355
6491
|
await setupDockerComposeTemplates(projectDir, options);
|
|
6356
6492
|
}
|
|
6357
6493
|
await setupAuthTemplate(projectDir, options);
|
|
6494
|
+
if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
|
|
6358
6495
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
6359
6496
|
await setupAddonsTemplate(projectDir, options);
|
|
6360
6497
|
await setupDeploymentTemplates(projectDir, options);
|
|
@@ -6367,6 +6504,7 @@ async function createProject(options, cliInput) {
|
|
|
6367
6504
|
}
|
|
6368
6505
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
6369
6506
|
if (options.auth && options.auth !== "none") await setupAuth(options);
|
|
6507
|
+
if (options.payments && options.payments !== "none") await setupPayments(options);
|
|
6370
6508
|
await handleExtras(projectDir, options);
|
|
6371
6509
|
await setupEnvironmentVariables(options);
|
|
6372
6510
|
await updatePackageConfigurations(projectDir, options);
|
|
@@ -6445,6 +6583,7 @@ async function createProjectHandler(input) {
|
|
|
6445
6583
|
addons: [],
|
|
6446
6584
|
examples: [],
|
|
6447
6585
|
auth: "none",
|
|
6586
|
+
payments: "none",
|
|
6448
6587
|
git: false,
|
|
6449
6588
|
packageManager: "npm",
|
|
6450
6589
|
install: false,
|
|
@@ -6552,7 +6691,7 @@ async function addAddonsHandler(input) {
|
|
|
6552
6691
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
6553
6692
|
if (!detectedConfig) exitWithError("Could not detect project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
6554
6693
|
if (!input.addons || input.addons.length === 0) {
|
|
6555
|
-
const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || []);
|
|
6694
|
+
const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || [], detectedConfig.auth);
|
|
6556
6695
|
if (addonsPrompt.length > 0) input.addons = addonsPrompt;
|
|
6557
6696
|
}
|
|
6558
6697
|
if (!input.webDeploy) {
|
|
@@ -6691,6 +6830,7 @@ const router = t.router({
|
|
|
6691
6830
|
database: DatabaseSchema.optional(),
|
|
6692
6831
|
orm: ORMSchema.optional(),
|
|
6693
6832
|
auth: AuthSchema.optional(),
|
|
6833
|
+
payments: PaymentsSchema.optional(),
|
|
6694
6834
|
frontend: z.array(FrontendSchema).optional(),
|
|
6695
6835
|
addons: z.array(AddonsSchema).optional(),
|
|
6696
6836
|
examples: z.array(ExamplesSchema).optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.46.0",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|