feat(cli): add elysia + aisdk support and fix fastify ai example

This commit is contained in:
Aman Varshney
2025-09-09 23:39:35 +05:30
parent d0a9a5d223
commit e5ba83fe3b
7 changed files with 112 additions and 130 deletions

View File

@@ -1,88 +1,88 @@
{ {
"name": "create-better-t-stack", "name": "create-better-t-stack",
"version": "2.42.0", "version": "2.42.0",
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations", "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"author": "Aman Varshney", "author": "Aman Varshney",
"bin": { "bin": {
"create-better-t-stack": "dist/cli.js" "create-better-t-stack": "dist/cli.js"
}, },
"files": [ "files": [
"templates", "templates",
"dist" "dist"
], ],
"keywords": [ "keywords": [
"better-t-stack", "better-t-stack",
"typescript", "typescript",
"boilerplate", "boilerplate",
"starter", "starter",
"cli", "cli",
"turborepo", "turborepo",
"trpc", "trpc",
"better-auth", "better-auth",
"monorepo", "monorepo",
"fullstack", "fullstack",
"type-safety", "type-safety",
"react", "react",
"react-native", "react-native",
"expo", "expo",
"hono", "hono",
"elysia", "elysia",
"drizzle", "drizzle",
"prisma", "prisma",
"tanstack", "tanstack",
"tailwind", "tailwind",
"shadcn", "shadcn",
"pwa", "pwa",
"tauri", "tauri",
"biome" "biome"
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git", "url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
"directory": "apps/cli" "directory": "apps/cli"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"homepage": "https://better-t-stack.dev/", "homepage": "https://better-t-stack.dev/",
"scripts": { "scripts": {
"build": "tsdown", "build": "tsdown",
"dev": "tsdown --watch", "dev": "tsdown --watch",
"check-types": "tsc --noEmit", "check-types": "tsc --noEmit",
"check": "biome check --write .", "check": "biome check --write .",
"test": "bun run build && vitest run", "test": "bun run build && vitest run",
"test:ui": "bun run build && vitest --ui", "test:ui": "bun run build && vitest --ui",
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui", "test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js"
} }
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "^1.0.0-alpha.4", "@clack/prompts": "^1.0.0-alpha.4",
"consola": "^3.4.2", "consola": "^3.4.2",
"execa": "^9.6.0", "execa": "^9.6.0",
"fs-extra": "^11.3.1", "fs-extra": "^11.3.1",
"gradient-string": "^3.0.0", "gradient-string": "^3.0.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"jsonc-parser": "^3.3.1", "jsonc-parser": "^3.3.1",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"trpc-cli": "^0.10.2", "trpc-cli": "^0.10.2",
"ts-morph": "^27.0.0", "ts-morph": "^27.0.0",
"zod": "^4.1.5" "zod": "^4.1.5"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.3.1", "@types/node": "^24.3.1",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"tsdown": "^0.14.2", "tsdown": "^0.14.2",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vitest": "^3.2.4" "vitest": "^3.2.4"
} }
} }

View File

@@ -92,7 +92,7 @@ export const dependencyVersionMap = {
"@elysiajs/cors": "^1.3.3", "@elysiajs/cors": "^1.3.3",
"@elysiajs/trpc": "^1.1.0", "@elysiajs/trpc": "^1.1.0",
elysia: "^1.3.20", "elysia": "^1.3.21",
"@hono/node-server": "^1.14.4", "@hono/node-server": "^1.14.4",
"@hono/trpc-server": "^0.4.0", "@hono/trpc-server": "^0.4.0",
@@ -108,12 +108,12 @@ export const dependencyVersionMap = {
turbo: "^2.5.4", turbo: "^2.5.4",
ai: "^5.0.9", "ai": "^5.0.39",
"@ai-sdk/google": "^2.0.3", "@ai-sdk/google": "^2.0.13",
"@ai-sdk/vue": "^2.0.9", "@ai-sdk/vue": "^2.0.39",
"@ai-sdk/svelte": "^3.0.9", "@ai-sdk/svelte": "^3.0.39",
"@ai-sdk/react": "^2.0.9", "@ai-sdk/react": "^2.0.39",
streamdown: "^1.1.6", streamdown: "^1.2.0",
"@orpc/server": "^1.8.6", "@orpc/server": "^1.8.6",
"@orpc/client": "^1.8.6", "@orpc/client": "^1.8.6",

View File

@@ -33,10 +33,6 @@ export async function getExamplesChoice(
if (database === "none") return []; if (database === "none") return [];
const noFrontendSelected = !frontends || frontends.length === 0;
if (noFrontendSelected) return [];
let response: Examples[] | symbol = []; let response: Examples[] | symbol = [];
const options: { value: Examples; label: string; hint: string }[] = []; const options: { value: Examples; label: string; hint: string }[] = [];

View File

@@ -157,11 +157,10 @@ export function isExampleTodoAllowed(
} }
export function isExampleAIAllowed( export function isExampleAIAllowed(
backend?: ProjectConfig["backend"], _backend?: ProjectConfig["backend"],
frontends: Frontend[] = [], frontends: Frontend[] = [],
) { ) {
const includesSolid = frontends.includes("solid"); const includesSolid = frontends.includes("solid");
if (backend === "elysia") return false;
if (includesSolid) return false; if (includesSolid) return false;
return true; return true;
} }
@@ -226,11 +225,6 @@ export function validateExamplesCompatibility(
"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.", "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.",
); );
} }
if (examplesArr.includes("ai") && backend === "elysia") {
exitWithError(
"The 'ai' example is not compatible with the Elysia backend.",
);
}
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) { if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) {
exitWithError( exitWithError(
"The 'ai' example is not compatible with the Solid frontend.", "The 'ai' example is not compatible with the Solid frontend.",

View File

@@ -4,6 +4,10 @@ import { node } from "@elysiajs/node";
{{/if}} {{/if}}
import { Elysia } from "elysia"; import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors"; import { cors } from "@elysiajs/cors";
{{#if (includes examples "ai")}}
import { google } from "@ai-sdk/google";
import { convertToModelMessages, streamText } from "ai";
{{/if}}
{{#if (eq api "trpc")}} {{#if (eq api "trpc")}}
import { createContext } from "./lib/context"; import { createContext } from "./lib/context";
import { appRouter } from "./routers/index"; import { appRouter } from "./routers/index";
@@ -94,6 +98,18 @@ const app = new Elysia()
}); });
return res; return res;
}) })
{{/if}}
{{#if (includes examples "ai")}}
.post("/ai", async (context) => {
const body = await context.request.json();
const uiMessages = body.messages || [];
const result = streamText({
model: google("gemini-2.0-flash"),
messages: convertToModelMessages(uiMessages)
});
return result.toUIMessageStreamResponse();
})
{{/if}} {{/if}}
.get("/", () => "OK") .get("/", () => "OK")
.listen(3000, () => { .listen(3000, () => {

View File

@@ -158,15 +158,14 @@ interface AiRequestBody {
messages: UIMessage[]; messages: UIMessage[];
} }
fastify.post('/ai', async function (request, reply) { fastify.post('/ai', async function (request) {
// there are some issues with the ai sdk and fastify, docs: https://ai-sdk.dev/cookbook/api-servers/fastify
const { messages } = request.body as AiRequestBody; const { messages } = request.body as AiRequestBody;
const result = streamText({ const result = streamText({
model: google('gemini-1.5-flash'), model: google('gemini-1.5-flash'),
messages: convertToModelMessages(messages), messages: convertToModelMessages(messages),
}); });
return result.pipeUIMessageStreamToResponse(reply.raw); return result.toUIMessageStreamResponse();
}); });
{{/if}} {{/if}}

View File

@@ -912,13 +912,6 @@ export const analyzeStackCompatibility = (
"Todo example removed (requires a database but 'None' was selected)", "Todo example removed (requires a database but 'None' was selected)",
}); });
} }
if (nextStack.backend === "elysia" && nextStack.examples.includes("ai")) {
incompatibleExamples.push("ai");
changes.push({
category: "examples",
message: "AI example removed (not compatible with Elysia backend)",
});
}
if (isSolid && nextStack.examples.includes("ai")) { if (isSolid && nextStack.examples.includes("ai")) {
incompatibleExamples.push("ai"); incompatibleExamples.push("ai");
changes.push({ changes.push({
@@ -942,19 +935,6 @@ export const analyzeStackCompatibility = (
notes.database.hasIssue = true; notes.database.hasIssue = true;
notes.examples.hasIssue = true; notes.examples.hasIssue = true;
} }
if (
nextStack.backend === "elysia" &&
uniqueIncompatibleExamples.includes("ai")
) {
notes.backend.notes.push(
"AI example is not compatible with Elysia. It will be removed.",
);
notes.examples.notes.push(
"AI example is not compatible with Elysia. It will be removed.",
);
notes.backend.hasIssue = true;
notes.examples.hasIssue = true;
}
if (isSolid && uniqueIncompatibleExamples.includes("ai")) { if (isSolid && uniqueIncompatibleExamples.includes("ai")) {
notes.webFrontend.notes.push( notes.webFrontend.notes.push(
"AI example is not compatible with Solid. It will be removed.", "AI example is not compatible with Solid. It will be removed.",
@@ -1551,9 +1531,6 @@ export const getDisabledReason = (
} }
if (category === "examples" && optionId === "ai") { if (category === "examples" && optionId === "ai") {
if (finalStack.backend === "elysia") {
return "AI example is not compatible with Elysia backend. Try Hono, Express, or Fastify.";
}
if (finalStack.webFrontend.includes("solid")) { if (finalStack.webFrontend.includes("solid")) {
return "AI example is not compatible with Solid frontend. Try React-based frontends."; return "AI example is not compatible with Solid frontend. Try React-based frontends.";
} }