Skip to content

Commit e5ba83f

Browse files
feat(cli): add elysia + aisdk support and fix fastify ai example
1 parent d0a9a5d commit e5ba83f

File tree

7 files changed

+112
-130
lines changed

7 files changed

+112
-130
lines changed

apps/cli/package.json

Lines changed: 86 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,88 @@
11
{
2-
"name": "create-better-t-stack",
3-
"version": "2.42.0",
4-
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5-
"type": "module",
6-
"license": "MIT",
7-
"author": "Aman Varshney",
8-
"bin": {
9-
"create-better-t-stack": "dist/cli.js"
10-
},
11-
"files": [
12-
"templates",
13-
"dist"
14-
],
15-
"keywords": [
16-
"better-t-stack",
17-
"typescript",
18-
"boilerplate",
19-
"starter",
20-
"cli",
21-
"turborepo",
22-
"trpc",
23-
"better-auth",
24-
"monorepo",
25-
"fullstack",
26-
"type-safety",
27-
"react",
28-
"react-native",
29-
"expo",
30-
"hono",
31-
"elysia",
32-
"drizzle",
33-
"prisma",
34-
"tanstack",
35-
"tailwind",
36-
"shadcn",
37-
"pwa",
38-
"tauri",
39-
"biome"
40-
],
41-
"repository": {
42-
"type": "git",
43-
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
44-
"directory": "apps/cli"
45-
},
46-
"publishConfig": {
47-
"access": "public"
48-
},
49-
"homepage": "https://better-t-stack.dev/",
50-
"scripts": {
51-
"build": "tsdown",
52-
"dev": "tsdown --watch",
53-
"check-types": "tsc --noEmit",
54-
"check": "biome check --write .",
55-
"test": "bun run build && vitest run",
56-
"test:ui": "bun run build && vitest --ui",
57-
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
58-
"prepublishOnly": "npm run build"
59-
},
60-
"exports": {
61-
".": {
62-
"types": "./dist/index.d.ts",
63-
"import": "./dist/index.js"
64-
}
65-
},
66-
"dependencies": {
67-
"@clack/prompts": "^1.0.0-alpha.4",
68-
"consola": "^3.4.2",
69-
"execa": "^9.6.0",
70-
"fs-extra": "^11.3.1",
71-
"gradient-string": "^3.0.0",
72-
"handlebars": "^4.7.8",
73-
"jsonc-parser": "^3.3.1",
74-
"picocolors": "^1.1.1",
75-
"tinyglobby": "^0.2.15",
76-
"trpc-cli": "^0.10.2",
77-
"ts-morph": "^27.0.0",
78-
"zod": "^4.1.5"
79-
},
80-
"devDependencies": {
81-
"@types/fs-extra": "^11.0.4",
82-
"@types/node": "^24.3.1",
83-
"@vitest/ui": "^3.2.4",
84-
"tsdown": "^0.14.2",
85-
"typescript": "^5.9.2",
86-
"vitest": "^3.2.4"
87-
}
2+
"name": "create-better-t-stack",
3+
"version": "2.42.0",
4+
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5+
"type": "module",
6+
"license": "MIT",
7+
"author": "Aman Varshney",
8+
"bin": {
9+
"create-better-t-stack": "dist/cli.js"
10+
},
11+
"files": [
12+
"templates",
13+
"dist"
14+
],
15+
"keywords": [
16+
"better-t-stack",
17+
"typescript",
18+
"boilerplate",
19+
"starter",
20+
"cli",
21+
"turborepo",
22+
"trpc",
23+
"better-auth",
24+
"monorepo",
25+
"fullstack",
26+
"type-safety",
27+
"react",
28+
"react-native",
29+
"expo",
30+
"hono",
31+
"elysia",
32+
"drizzle",
33+
"prisma",
34+
"tanstack",
35+
"tailwind",
36+
"shadcn",
37+
"pwa",
38+
"tauri",
39+
"biome"
40+
],
41+
"repository": {
42+
"type": "git",
43+
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
44+
"directory": "apps/cli"
45+
},
46+
"publishConfig": {
47+
"access": "public"
48+
},
49+
"homepage": "https://better-t-stack.dev/",
50+
"scripts": {
51+
"build": "tsdown",
52+
"dev": "tsdown --watch",
53+
"check-types": "tsc --noEmit",
54+
"check": "biome check --write .",
55+
"test": "bun run build && vitest run",
56+
"test:ui": "bun run build && vitest --ui",
57+
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
58+
"prepublishOnly": "npm run build"
59+
},
60+
"exports": {
61+
".": {
62+
"types": "./dist/index.d.ts",
63+
"import": "./dist/index.js"
64+
}
65+
},
66+
"dependencies": {
67+
"@clack/prompts": "^1.0.0-alpha.4",
68+
"consola": "^3.4.2",
69+
"execa": "^9.6.0",
70+
"fs-extra": "^11.3.1",
71+
"gradient-string": "^3.0.0",
72+
"handlebars": "^4.7.8",
73+
"jsonc-parser": "^3.3.1",
74+
"picocolors": "^1.1.1",
75+
"tinyglobby": "^0.2.15",
76+
"trpc-cli": "^0.10.2",
77+
"ts-morph": "^27.0.0",
78+
"zod": "^4.1.5"
79+
},
80+
"devDependencies": {
81+
"@types/fs-extra": "^11.0.4",
82+
"@types/node": "^24.3.1",
83+
"@vitest/ui": "^3.2.4",
84+
"tsdown": "^0.14.2",
85+
"typescript": "^5.9.2",
86+
"vitest": "^3.2.4"
87+
}
8888
}

apps/cli/src/constants.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const dependencyVersionMap = {
9292

9393
"@elysiajs/cors": "^1.3.3",
9494
"@elysiajs/trpc": "^1.1.0",
95-
elysia: "^1.3.20",
95+
"elysia": "^1.3.21",
9696

9797
"@hono/node-server": "^1.14.4",
9898
"@hono/trpc-server": "^0.4.0",
@@ -108,12 +108,12 @@ export const dependencyVersionMap = {
108108

109109
turbo: "^2.5.4",
110110

111-
ai: "^5.0.9",
112-
"@ai-sdk/google": "^2.0.3",
113-
"@ai-sdk/vue": "^2.0.9",
114-
"@ai-sdk/svelte": "^3.0.9",
115-
"@ai-sdk/react": "^2.0.9",
116-
streamdown: "^1.1.6",
111+
"ai": "^5.0.39",
112+
"@ai-sdk/google": "^2.0.13",
113+
"@ai-sdk/vue": "^2.0.39",
114+
"@ai-sdk/svelte": "^3.0.39",
115+
"@ai-sdk/react": "^2.0.39",
116+
streamdown: "^1.2.0",
117117

118118
"@orpc/server": "^1.8.6",
119119
"@orpc/client": "^1.8.6",

apps/cli/src/prompts/examples.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ export async function getExamplesChoice(
3333

3434
if (database === "none") return [];
3535

36-
const noFrontendSelected = !frontends || frontends.length === 0;
37-
38-
if (noFrontendSelected) return [];
39-
4036
let response: Examples[] | symbol = [];
4137
const options: { value: Examples; label: string; hint: string }[] = [];
4238

apps/cli/src/utils/compatibility-rules.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,10 @@ export function isExampleTodoAllowed(
157157
}
158158

159159
export function isExampleAIAllowed(
160-
backend?: ProjectConfig["backend"],
160+
_backend?: ProjectConfig["backend"],
161161
frontends: Frontend[] = [],
162162
) {
163163
const includesSolid = frontends.includes("solid");
164-
if (backend === "elysia") return false;
165164
if (includesSolid) return false;
166165
return true;
167166
}
@@ -226,11 +225,6 @@ export function validateExamplesCompatibility(
226225
"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.",
227226
);
228227
}
229-
if (examplesArr.includes("ai") && backend === "elysia") {
230-
exitWithError(
231-
"The 'ai' example is not compatible with the Elysia backend.",
232-
);
233-
}
234228
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) {
235229
exitWithError(
236230
"The 'ai' example is not compatible with the Solid frontend.",

apps/cli/templates/backend/server/elysia/src/index.ts.hbs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { node } from "@elysiajs/node";
44
{{/if}}
55
import { Elysia } from "elysia";
66
import { cors } from "@elysiajs/cors";
7+
{{#if (includes examples "ai")}}
8+
import { google } from "@ai-sdk/google";
9+
import { convertToModelMessages, streamText } from "ai";
10+
{{/if}}
711
{{#if (eq api "trpc")}}
812
import { createContext } from "./lib/context";
913
import { appRouter } from "./routers/index";
@@ -94,6 +98,18 @@ const app = new Elysia()
9498
});
9599
return res;
96100
})
101+
{{/if}}
102+
{{#if (includes examples "ai")}}
103+
.post("/ai", async (context) => {
104+
const body = await context.request.json();
105+
const uiMessages = body.messages || [];
106+
const result = streamText({
107+
model: google("gemini-2.0-flash"),
108+
messages: convertToModelMessages(uiMessages)
109+
});
110+
111+
return result.toUIMessageStreamResponse();
112+
})
97113
{{/if}}
98114
.get("/", () => "OK")
99115
.listen(3000, () => {

apps/cli/templates/backend/server/fastify/src/index.ts.hbs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,14 @@ interface AiRequestBody {
158158
messages: UIMessage[];
159159
}
160160

161-
fastify.post('/ai', async function (request, reply) {
162-
// there are some issues with the ai sdk and fastify, docs: https://ai-sdk.dev/cookbook/api-servers/fastify
161+
fastify.post('/ai', async function (request) {
163162
const { messages } = request.body as AiRequestBody;
164163
const result = streamText({
165164
model: google('gemini-1.5-flash'),
166165
messages: convertToModelMessages(messages),
167166
});
168167

169-
return result.pipeUIMessageStreamToResponse(reply.raw);
168+
return result.toUIMessageStreamResponse();
170169
});
171170
{{/if}}
172171

apps/web/src/app/(home)/new/_components/utils.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -912,13 +912,6 @@ export const analyzeStackCompatibility = (
912912
"Todo example removed (requires a database but 'None' was selected)",
913913
});
914914
}
915-
if (nextStack.backend === "elysia" && nextStack.examples.includes("ai")) {
916-
incompatibleExamples.push("ai");
917-
changes.push({
918-
category: "examples",
919-
message: "AI example removed (not compatible with Elysia backend)",
920-
});
921-
}
922915
if (isSolid && nextStack.examples.includes("ai")) {
923916
incompatibleExamples.push("ai");
924917
changes.push({
@@ -942,19 +935,6 @@ export const analyzeStackCompatibility = (
942935
notes.database.hasIssue = true;
943936
notes.examples.hasIssue = true;
944937
}
945-
if (
946-
nextStack.backend === "elysia" &&
947-
uniqueIncompatibleExamples.includes("ai")
948-
) {
949-
notes.backend.notes.push(
950-
"AI example is not compatible with Elysia. It will be removed.",
951-
);
952-
notes.examples.notes.push(
953-
"AI example is not compatible with Elysia. It will be removed.",
954-
);
955-
notes.backend.hasIssue = true;
956-
notes.examples.hasIssue = true;
957-
}
958938
if (isSolid && uniqueIncompatibleExamples.includes("ai")) {
959939
notes.webFrontend.notes.push(
960940
"AI example is not compatible with Solid. It will be removed.",
@@ -1551,9 +1531,6 @@ export const getDisabledReason = (
15511531
}
15521532

15531533
if (category === "examples" && optionId === "ai") {
1554-
if (finalStack.backend === "elysia") {
1555-
return "AI example is not compatible with Elysia backend. Try Hono, Express, or Fastify.";
1556-
}
15571534
if (finalStack.webFrontend.includes("solid")) {
15581535
return "AI example is not compatible with Solid frontend. Try React-based frontends.";
15591536
}

0 commit comments

Comments
 (0)