From b6be85e0443e851040f1946aa475068df6b3dfce Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Mon, 18 May 2026 16:28:46 +0530 Subject: [PATCH 1/4] fix(cli): align better auth mongo and convex templates --- apps/cli/test/auth.test.ts | 83 +++++++++++++- .../src/processors/auth-deps.ts | 13 ++- .../src/processors/db-deps.ts | 7 +- .../src/processors/env-vars.ts | 15 ++- .../src/templates.generated.ts | 103 +++++++++++------- .../template-generator/src/utils/add-deps.ts | 29 ++--- .../convex/backend/convex/auth.ts.hbs | 3 + .../convex/backend/convex/http.ts.hbs | 7 -- .../react-router/src/lib/auth-client.ts.hbs | 2 +- .../src/lib/auth-client.ts.hbs | 2 +- .../mongodb/src/models/auth.model.ts.hbs | 34 +++--- .../packages/backend/convex/tsconfig.json.hbs | 1 + .../db/mongoose/mongodb/src/index.ts.hbs | 6 +- .../mongoose/base/src/routers/todo.ts.hbs | 12 +- .../mongodb/src/models/todo.model.ts.hbs | 5 +- .../web/react/next/src/app/todos/page.tsx.hbs | 7 +- .../react-router/src/routes/todos.tsx.hbs | 8 +- .../tanstack-router/src/routes/todos.tsx.hbs | 8 +- .../tanstack-start/src/routes/todos.tsx.hbs | 8 +- 19 files changed, 246 insertions(+), 107 deletions(-) diff --git a/apps/cli/test/auth.test.ts b/apps/cli/test/auth.test.ts index 674826eab..eca811749 100644 --- a/apps/cli/test/auth.test.ts +++ b/apps/cli/test/auth.test.ts @@ -78,6 +78,43 @@ describe("Authentication Configurations", () => { }); expectSuccess(result); + expect(result.projectDir).toBeDefined(); + const projectDir = result.projectDir as string; + const authPackageJson = await fs.readJson( + path.join(projectDir, "packages/auth/package.json"), + ); + expect(authPackageJson.dependencies.mongodb).toBe("^7.2.0"); + + const dbIndex = await fs.readFile(path.join(projectDir, "packages/db/src/index.ts"), "utf8"); + expect(dbIndex).toContain("await mongoose.connect(env.DATABASE_URL);"); + expect(dbIndex).toContain("mongoose.connection.getClient().db()"); + expect(dbIndex).not.toContain(".catch("); + expect(dbIndex).not.toContain("myDB"); + + const todosRoute = await fs.readFile( + path.join(projectDir, "apps/web/src/routes/todos.tsx"), + "utf8", + ); + expect(todosRoute).toContain("type TodoId = string"); + expect(todosRoute).toContain("const handleToggleTodo = (id: TodoId"); + expect(todosRoute).toContain("const handleDeleteTodo = (id: TodoId"); + + const todoRouter = await fs.readFile( + path.join(projectDir, "packages/api/src/routers/todo.ts"), + "utf8", + ); + expect(todoRouter).toContain('import "@better-auth-mongodb/db";'); + expect(todoRouter).toContain("id: todo.id.toString()"); + + const authModels = await fs.readFile( + path.join(projectDir, "packages/db/src/models/auth.model.ts"), + "utf8", + ); + expect(authModels).toContain("const { ObjectId } = Schema.Types"); + expect(authModels).toContain("_id: { type: ObjectId, auto: true }"); + expect(authModels).toContain('userId: { type: ObjectId, ref: "User", required: true }'); + expect(authModels).toContain("sessionSchema.index({ userId: 1 })"); + expect(authModels).toContain("verificationSchema.index({ identifier: 1 })"); }); it("should add nextCookies plugin for Next.js self backend", async () => { @@ -187,6 +224,50 @@ describe("Authentication Configurations", () => { }); expectSuccess(result); + if (!result.projectDir) { + throw new Error("Expected projectDir to be defined"); + } + + const packageJson = await fs.readJson(path.join(result.projectDir, "package.json")); + const backendPackageJson = await fs.readJson( + path.join(result.projectDir, "packages/backend/package.json"), + ); + const webPackageJson = await fs.readJson( + path.join(result.projectDir, "apps/web/package.json"), + ); + const authFile = await fs.readFile( + path.join(result.projectDir, "packages/backend/convex/auth.ts"), + "utf8", + ); + const httpFile = await fs.readFile( + path.join(result.projectDir, "packages/backend/convex/http.ts"), + "utf8", + ); + const authClientFile = await fs.readFile( + path.join(result.projectDir, "apps/web/src/lib/auth-client.ts"), + "utf8", + ); + const convexTsconfig = await fs.readFile( + path.join(result.projectDir, "packages/backend/convex/tsconfig.json"), + "utf8", + ); + const convexEnvFile = await fs.readFile( + path.join(result.projectDir, "packages/backend/.env.local"), + "utf8", + ); + + expect(packageJson.workspaces.catalog["better-auth"]).toBe("~1.6.9"); + expect(packageJson.workspaces.catalog["@convex-dev/better-auth"]).toBe("^0.12.2"); + expect(backendPackageJson.dependencies["better-auth"]).toBe("catalog:"); + expect(webPackageJson.dependencies["better-auth"]).toBe("catalog:"); + expect(authFile).toContain("baseURL: process.env.CONVEX_SITE_URL"); + expect(httpFile).toContain("authComponent.registerRoutes(http, createAuth, { cors: true })"); + expect(authClientFile).toContain("plugins: [convexClient(), crossDomainClient()]"); + expect(convexTsconfig).toContain('"types": ["node"]'); + expect(convexEnvFile).toContain( + "# npx convex env set CONVEX_SITE_URL https://", + ); + expect(convexEnvFile).toContain("# CONVEX_SITE_URL="); }); it("should scaffold react-router with Convex Better Auth wiring", async () => { @@ -227,7 +308,7 @@ describe("Authentication Configurations", () => { expect(rootFile).toContain("ConvexBetterAuthProvider"); expect(rootFile).toContain('import { authClient } from "@/lib/auth-client";'); - expect(authClientFile).toContain("crossDomainClient(), convexClient()"); + expect(authClientFile).toContain("convexClient(), crossDomainClient()"); expect(dashboardFile).toContain("Authenticated"); expect(dashboardFile).toContain("Unauthenticated"); }); diff --git a/packages/template-generator/src/processors/auth-deps.ts b/packages/template-generator/src/processors/auth-deps.ts index 38449c5a6..8c4454034 100644 --- a/packages/template-generator/src/processors/auth-deps.ts +++ b/packages/template-generator/src/processors/auth-deps.ts @@ -1,9 +1,10 @@ import type { ProjectConfig } from "@better-t-stack/types"; import type { VirtualFileSystem } from "../core/virtual-fs"; -import { addPackageDependency } from "../utils/add-deps"; +import { addPackageDependency, type AvailableDependencies } from "../utils/add-deps"; -const CONVEX_BETTER_AUTH_VERSION = "1.6.9"; +// Intentional: @convex-dev/better-auth currently documents a pinned Better Auth range. +const CONVEX_BETTER_AUTH_VERSION = "~1.6.9"; export function processAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): void { const { auth, backend } = config; @@ -124,7 +125,7 @@ function processConvexAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): v } function processStandardAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): void { - const { auth, backend, frontend } = config; + const { auth, backend, frontend, orm } = config; const authPath = "packages/auth/package.json"; const apiPath = "packages/api/package.json"; const webPath = "apps/web/package.json"; @@ -202,7 +203,11 @@ function processStandardAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): } } else if (auth === "better-auth") { if (authExists) { - addPackageDependency({ vfs, packagePath: authPath, dependencies: ["better-auth"] }); + const authDependencies: AvailableDependencies[] = ["better-auth"]; + if (orm === "mongoose") { + authDependencies.push("mongodb"); + } + addPackageDependency({ vfs, packagePath: authPath, dependencies: authDependencies }); if (hasNative) { addPackageDependency({ vfs, packagePath: authPath, dependencies: ["@better-auth/expo"] }); } diff --git a/packages/template-generator/src/processors/db-deps.ts b/packages/template-generator/src/processors/db-deps.ts index 26877d827..bf930b826 100644 --- a/packages/template-generator/src/processors/db-deps.ts +++ b/packages/template-generator/src/processors/db-deps.ts @@ -33,17 +33,18 @@ function processPrismaDeps( const { database, dbSetup } = config; if (database === "mongodb") { + // Intentional: Prisma ORM v7 does not support MongoDB yet, so MongoDB stays on v6. addPackageDependency({ vfs, packagePath: dbPkgPath, - customDependencies: { "@prisma/client": "6.19.0" }, - customDevDependencies: { prisma: "6.19.0" }, + customDependencies: { "@prisma/client": "6.19.3" }, + customDevDependencies: { prisma: "6.19.3" }, }); if (webExists) { addPackageDependency({ vfs, packagePath: webPkgPath, - customDependencies: { "@prisma/client": "6.19.0" }, + customDependencies: { "@prisma/client": "6.19.3" }, }); } return; diff --git a/packages/template-generator/src/processors/env-vars.ts b/packages/template-generator/src/processors/env-vars.ts index c158d3681..02676fb1d 100644 --- a/packages/template-generator/src/processors/env-vars.ts +++ b/packages/template-generator/src/processors/env-vars.ts @@ -259,6 +259,8 @@ function buildConvexBackendVars( auth: ProjectConfig["auth"], examples: ProjectConfig["examples"], ): EnvVariable[] { + const hasReactRouter = frontend.includes("react-router"); + const hasTanStackRouter = frontend.includes("tanstack-router"); const hasNextJs = frontend.includes("next"); const hasNative = frontend.includes("native-bare") || @@ -294,6 +296,15 @@ function buildConvexBackendVars( } if (auth === "better-auth") { + if (hasReactRouter || hasTanStackRouter) { + vars.push({ + key: "CONVEX_SITE_URL", + value: "", + condition: true, + comment: "Same as CONVEX_URL but ends in .site", + }); + } + if (hasNative) { vars.push({ key: "EXPO_PUBLIC_CONVEX_SITE_URL", @@ -336,6 +347,8 @@ function buildConvexCommentBlocks( auth: ProjectConfig["auth"], examples: ProjectConfig["examples"], ): string { + const needsConvexSiteUrl = + frontend.includes("react-router") || frontend.includes("tanstack-router"); const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || @@ -370,7 +383,7 @@ function buildConvexCommentBlocks( if (auth === "better-auth") { commentBlocks += `# Set Convex environment variables # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32) -${hasWeb || hasNative ? `# npx convex env set SITE_URL ${defaultSiteUrl}\n` : ""}`; +${needsConvexSiteUrl ? "# npx convex env set CONVEX_SITE_URL https://\n" : ""}${hasWeb || hasNative ? `# npx convex env set SITE_URL ${defaultSiteUrl}\n` : ""}`; } return commentBlocks; diff --git a/packages/template-generator/src/templates.generated.ts b/packages/template-generator/src/templates.generated.ts index a746ea01f..e7665d6ec 100644 --- a/packages/template-generator/src/templates.generated.ts +++ b/packages/template-generator/src/templates.generated.ts @@ -2222,6 +2222,9 @@ export const authComponent = createClient(components.betterAuth); function createAuth(ctx: GenericCtx) { return betterAuth({ + {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router"))}} + baseURL: process.env.CONVEX_SITE_URL, + {{/if}} {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}} baseURL: siteUrl, {{/if}} @@ -2267,14 +2270,7 @@ import { authComponent, createAuth } from "./auth"; const http = httpRouter(); {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} -{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} -authComponent.registerRoutesLazy(http, createAuth, { - cors: true, - trustedOrigins: [process.env.SITE_URL!], -}); -{{else}} authComponent.registerRoutes(http, createAuth, { cors: true }); -{{/if}} {{else}} authComponent.registerRoutes(http, createAuth); {{/if}} @@ -4338,7 +4334,7 @@ import { env } from "@{{projectName}}/env/web"; export const authClient = createAuthClient({ baseURL: env.VITE_CONVEX_SITE_URL, - plugins: [crossDomainClient(), convexClient()], + plugins: [convexClient(), crossDomainClient()], }); `], ["auth/better-auth/convex/web/react/react-router/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form"; @@ -4742,7 +4738,7 @@ import { env } from "@{{projectName}}/env/web"; export const authClient = createAuthClient({ baseURL: env.VITE_CONVEX_SITE_URL, - plugins: [crossDomainClient(), convexClient()], + plugins: [convexClient(), crossDomainClient()], }); `], ["auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form"; @@ -8001,40 +7997,42 @@ export const accountRelations = relations(account, ({ one }) => ({ ["auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs", `import mongoose from 'mongoose'; const { Schema, model } = mongoose; +const { ObjectId } = Schema.Types; const userSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, name: { type: String, required: true }, email: { type: String, required: true, unique: true }, - emailVerified: { type: Boolean, required: true }, + emailVerified: { type: Boolean, required: true, default: false }, image: { type: String }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, }, { collection: 'user' } ); const sessionSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, expiresAt: { type: Date, required: true }, token: { type: String, required: true, unique: true }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, ipAddress: { type: String }, userAgent: { type: String }, - userId: { type: String, ref: 'User', required: true }, + userId: { type: ObjectId, ref: 'User', required: true }, }, { collection: 'session' } ); +sessionSchema.index({ userId: 1 }); const accountSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, accountId: { type: String, required: true }, providerId: { type: String, required: true }, - userId: { type: String, ref: 'User', required: true }, + userId: { type: ObjectId, ref: 'User', required: true }, accessToken: { type: String }, refreshToken: { type: String }, idToken: { type: String }, @@ -8042,23 +8040,25 @@ const accountSchema = new Schema( refreshTokenExpiresAt: { type: Date }, scope: { type: String }, password: { type: String }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, }, { collection: 'account' } ); +accountSchema.index({ userId: 1 }); const verificationSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, identifier: { type: String, required: true }, value: { type: String, required: true }, expiresAt: { type: Date, required: true }, - createdAt: { type: Date }, - updatedAt: { type: Date }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, }, { collection: 'verification' } ); +verificationSchema.index({ identifier: 1 }); const User = model('User', userSchema); const Session = model('Session', sessionSchema); @@ -13550,6 +13550,7 @@ export default defineSchema({ "jsx": "react-jsx", "skipLibCheck": true, "allowSyntheticDefaultImports": true, + "types": ["node"], /* These compiler options are required by Convex */ "target": "ESNext", @@ -14785,11 +14786,9 @@ export function createDb() { ["db/mongoose/mongodb/src/index.ts.hbs", `import mongoose from "mongoose"; import { env } from "@{{projectName}}/env/server"; -await mongoose.connect(env.DATABASE_URL).catch((error) => { - console.log("Error connecting to database:", error); -}); +await mongoose.connect(env.DATABASE_URL); -const client = mongoose.connection.getClient().db("myDB"); +const client = mongoose.connection.getClient().db(); export { client }; `], @@ -19516,19 +19515,21 @@ export const todo = sqliteTable("todo", { `], ["examples/todo/server/mongoose/base/src/routers/todo.ts.hbs", `{{#if (eq api "orpc")}} import z from "zod"; +import "@{{projectName}}/db"; import { publicProcedure } from "../index"; import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = { getAll: publicProcedure.handler(async () => { - return await Todo.find().lean(); + const todos = await Todo.find().lean(); + return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .handler(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return newTodo.toObject(); + return { ...newTodo.toObject(), id: newTodo.id.toString() }; }), toggle: publicProcedure @@ -19550,19 +19551,21 @@ export const todoRouter = { {{#if (eq api "trpc")}} import z from "zod"; +import "@{{projectName}}/db"; import { router, publicProcedure } from "../index"; import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = router({ getAll: publicProcedure.query(async () => { - return await Todo.find().lean(); + const todos = await Todo.find().lean(); + return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .mutation(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return newTodo.toObject(); + return { ...newTodo.toObject(), id: newTodo.id.toString() }; }), toggle: publicProcedure @@ -19587,8 +19590,9 @@ const { Schema, model } = mongoose; const todoSchema = new Schema({ id: { - type: mongoose.Schema.Types.ObjectId, - auto: true, + type: String, + required: true, + default: () => new mongoose.Types.ObjectId().toString(), }, text: { type: String, @@ -20207,6 +20211,9 @@ import { trpc } from "@/utils/trpc"; {{/if}} {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} export default function TodosPage() { const [newTodoText, setNewTodoText] = useState(""); @@ -20283,11 +20290,11 @@ export default function TodosPage() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} @@ -20451,6 +20458,10 @@ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; import { useMutation, useQuery } from "@tanstack/react-query"; {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} + export default function Todos() { const [newTodoText, setNewTodoText] = useState(""); @@ -20526,11 +20537,11 @@ export default function Todos() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} @@ -20695,6 +20706,10 @@ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; import { useMutation, useQuery } from "@tanstack/react-query"; {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} + export const Route = createFileRoute("/todos")({ component: TodosRoute, }); @@ -20774,11 +20789,11 @@ function TodosRoute() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} @@ -20949,6 +20964,10 @@ import { orpc } from "@/utils/orpc"; import { useMutation, useQuery } from "@tanstack/react-query"; {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} + export const Route = createFileRoute("/todos")({ component: TodosRoute, }); @@ -21050,11 +21069,11 @@ function TodosRoute() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} diff --git a/packages/template-generator/src/utils/add-deps.ts b/packages/template-generator/src/utils/add-deps.ts index 3b2644831..b2ece1aa2 100644 --- a/packages/template-generator/src/utils/add-deps.ts +++ b/packages/template-generator/src/utils/add-deps.ts @@ -14,8 +14,8 @@ type PackageJson = { export const dependencyVersionMap = { typescript: "^6", - "better-auth": "1.6.9", - "@better-auth/expo": "1.6.9", + "better-auth": "1.6.11", + "@better-auth/expo": "1.6.11", "@clerk/backend": "^3.2.1", "@clerk/express": "^2.0.5", @@ -41,17 +41,18 @@ export const dependencyVersionMap = { mysql2: "^3.14.0", - "@prisma/client": "^7.7.0", - prisma: "^7.7.0", - "@prisma/adapter-d1": "^7.7.0", - "@prisma/adapter-neon": "^7.7.0", - "@prisma/adapter-mariadb": "^7.7.0", - "@prisma/adapter-libsql": "^7.7.0", - "@prisma/adapter-better-sqlite3": "^7.7.0", - "@prisma/adapter-pg": "^7.7.0", - "@prisma/adapter-planetscale": "^7.7.0", + "@prisma/client": "^7.8.0", + prisma: "^7.8.0", + "@prisma/adapter-d1": "^7.8.0", + "@prisma/adapter-neon": "^7.8.0", + "@prisma/adapter-mariadb": "^7.8.0", + "@prisma/adapter-libsql": "^7.8.0", + "@prisma/adapter-better-sqlite3": "^7.8.0", + "@prisma/adapter-pg": "^7.8.0", + "@prisma/adapter-planetscale": "^7.8.0", - mongoose: "^8.14.0", + mongoose: "^9.6.2", + mongodb: "^7.2.0", "vite-plugin-pwa": "^1.2.0", "@vite-pwa/assets-generator": "^1.0.2", @@ -123,7 +124,7 @@ export const dependencyVersionMap = { "convex-svelte": "^0.0.12", "convex-nuxt": "0.1.5", "convex-vue": "^0.1.5", - "@convex-dev/better-auth": "^0.12.1", + "@convex-dev/better-auth": "^0.12.2", "@tanstack/svelte-query": "^5.85.3", "@tanstack/svelte-query-devtools": "^5.85.3", @@ -160,7 +161,7 @@ export const dependencyVersionMap = { "@t3-oss/env-nextjs": "^0.13.1", "@t3-oss/env-nuxt": "^0.13.1", - "@polar-sh/better-auth": "^1.8.3", + "@polar-sh/better-auth": "^1.8.4", "@polar-sh/sdk": "^0.42.2", evlog: "^2.14.1", diff --git a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs index 2fc3ae18f..5616bce46 100644 --- a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs @@ -24,6 +24,9 @@ export const authComponent = createClient(components.betterAuth); function createAuth(ctx: GenericCtx) { return betterAuth({ + {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router"))}} + baseURL: process.env.CONVEX_SITE_URL, + {{/if}} {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}} baseURL: siteUrl, {{/if}} diff --git a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs index be0961ef3..cf38370e9 100644 --- a/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs @@ -4,14 +4,7 @@ import { authComponent, createAuth } from "./auth"; const http = httpRouter(); {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} -{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} -authComponent.registerRoutesLazy(http, createAuth, { - cors: true, - trustedOrigins: [process.env.SITE_URL!], -}); -{{else}} authComponent.registerRoutes(http, createAuth, { cors: true }); -{{/if}} {{else}} authComponent.registerRoutes(http, createAuth); {{/if}} diff --git a/packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/lib/auth-client.ts.hbs b/packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/lib/auth-client.ts.hbs index d96ae8c57..8427a89c5 100644 --- a/packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/lib/auth-client.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/lib/auth-client.ts.hbs @@ -7,5 +7,5 @@ import { env } from "@{{projectName}}/env/web"; export const authClient = createAuthClient({ baseURL: env.VITE_CONVEX_SITE_URL, - plugins: [crossDomainClient(), convexClient()], + plugins: [convexClient(), crossDomainClient()], }); diff --git a/packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs b/packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs index e4afda463..dc3e4b684 100644 --- a/packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs @@ -7,5 +7,5 @@ import { env } from "@{{projectName}}/env/web"; export const authClient = createAuthClient({ baseURL: env.VITE_CONVEX_SITE_URL, - plugins: [crossDomainClient(), convexClient()], + plugins: [convexClient(), crossDomainClient()], }); diff --git a/packages/template-generator/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs b/packages/template-generator/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs index bb04aeda0..e2ba5ac3e 100644 --- a/packages/template-generator/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs +++ b/packages/template-generator/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs @@ -1,40 +1,42 @@ import mongoose from 'mongoose'; const { Schema, model } = mongoose; +const { ObjectId } = Schema.Types; const userSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, name: { type: String, required: true }, email: { type: String, required: true, unique: true }, - emailVerified: { type: Boolean, required: true }, + emailVerified: { type: Boolean, required: true, default: false }, image: { type: String }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, }, { collection: 'user' } ); const sessionSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, expiresAt: { type: Date, required: true }, token: { type: String, required: true, unique: true }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, ipAddress: { type: String }, userAgent: { type: String }, - userId: { type: String, ref: 'User', required: true }, + userId: { type: ObjectId, ref: 'User', required: true }, }, { collection: 'session' } ); +sessionSchema.index({ userId: 1 }); const accountSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, accountId: { type: String, required: true }, providerId: { type: String, required: true }, - userId: { type: String, ref: 'User', required: true }, + userId: { type: ObjectId, ref: 'User', required: true }, accessToken: { type: String }, refreshToken: { type: String }, idToken: { type: String }, @@ -42,23 +44,25 @@ const accountSchema = new Schema( refreshTokenExpiresAt: { type: Date }, scope: { type: String }, password: { type: String }, - createdAt: { type: Date, required: true }, - updatedAt: { type: Date, required: true }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, }, { collection: 'account' } ); +accountSchema.index({ userId: 1 }); const verificationSchema = new Schema( { - _id: { type: String }, + _id: { type: ObjectId, auto: true }, identifier: { type: String, required: true }, value: { type: String, required: true }, expiresAt: { type: Date, required: true }, - createdAt: { type: Date }, - updatedAt: { type: Date }, + createdAt: { type: Date, required: true, default: Date.now }, + updatedAt: { type: Date, required: true, default: Date.now }, }, { collection: 'verification' } ); +verificationSchema.index({ identifier: 1 }); const User = model('User', userSchema); const Session = model('Session', sessionSchema); diff --git a/packages/template-generator/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs b/packages/template-generator/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs index 73741270b..62eda335d 100644 --- a/packages/template-generator/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs +++ b/packages/template-generator/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs @@ -11,6 +11,7 @@ "jsx": "react-jsx", "skipLibCheck": true, "allowSyntheticDefaultImports": true, + "types": ["node"], /* These compiler options are required by Convex */ "target": "ESNext", diff --git a/packages/template-generator/templates/db/mongoose/mongodb/src/index.ts.hbs b/packages/template-generator/templates/db/mongoose/mongodb/src/index.ts.hbs index 80ec40248..f1d6ed200 100644 --- a/packages/template-generator/templates/db/mongoose/mongodb/src/index.ts.hbs +++ b/packages/template-generator/templates/db/mongoose/mongodb/src/index.ts.hbs @@ -1,10 +1,8 @@ import mongoose from "mongoose"; import { env } from "@{{projectName}}/env/server"; -await mongoose.connect(env.DATABASE_URL).catch((error) => { - console.log("Error connecting to database:", error); -}); +await mongoose.connect(env.DATABASE_URL); -const client = mongoose.connection.getClient().db("myDB"); +const client = mongoose.connection.getClient().db(); export { client }; diff --git a/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs b/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs index 187fee7fe..7f6cf671e 100644 --- a/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +++ b/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs @@ -1,18 +1,20 @@ {{#if (eq api "orpc")}} import z from "zod"; +import "@{{projectName}}/db"; import { publicProcedure } from "../index"; import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = { getAll: publicProcedure.handler(async () => { - return await Todo.find().lean(); + const todos = await Todo.find().lean(); + return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .handler(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return newTodo.toObject(); + return { ...newTodo.toObject(), id: newTodo.id.toString() }; }), toggle: publicProcedure @@ -34,19 +36,21 @@ export const todoRouter = { {{#if (eq api "trpc")}} import z from "zod"; +import "@{{projectName}}/db"; import { router, publicProcedure } from "../index"; import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = router({ getAll: publicProcedure.query(async () => { - return await Todo.find().lean(); + const todos = await Todo.find().lean(); + return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .mutation(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return newTodo.toObject(); + return { ...newTodo.toObject(), id: newTodo.id.toString() }; }), toggle: publicProcedure diff --git a/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs b/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs index 2e9656383..9bfc51d66 100644 --- a/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs +++ b/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs @@ -4,8 +4,9 @@ const { Schema, model } = mongoose; const todoSchema = new Schema({ id: { - type: mongoose.Schema.Types.ObjectId, - auto: true, + type: String, + required: true, + default: () => new mongoose.Types.ObjectId().toString(), }, text: { type: String, diff --git a/packages/template-generator/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs b/packages/template-generator/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs index 0feb7b883..630ab4583 100644 --- a/packages/template-generator/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs +++ b/packages/template-generator/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs @@ -27,6 +27,9 @@ import { trpc } from "@/utils/trpc"; {{/if}} {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} export default function TodosPage() { const [newTodoText, setNewTodoText] = useState(""); @@ -103,11 +106,11 @@ export default function TodosPage() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} diff --git a/packages/template-generator/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs b/packages/template-generator/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs index 20a5ecb4d..9cae46cb7 100644 --- a/packages/template-generator/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs +++ b/packages/template-generator/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs @@ -25,6 +25,10 @@ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; import { useMutation, useQuery } from "@tanstack/react-query"; {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} + export default function Todos() { const [newTodoText, setNewTodoText] = useState(""); @@ -100,11 +104,11 @@ export default function Todos() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} diff --git a/packages/template-generator/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs b/packages/template-generator/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs index 8ab48e16f..9b29dd3cb 100644 --- a/packages/template-generator/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs +++ b/packages/template-generator/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs @@ -26,6 +26,10 @@ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; import { useMutation, useQuery } from "@tanstack/react-query"; {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} + export const Route = createFileRoute("/todos")({ component: TodosRoute, }); @@ -105,11 +109,11 @@ function TodosRoute() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} diff --git a/packages/template-generator/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs b/packages/template-generator/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs index 943074e10..dafb77640 100644 --- a/packages/template-generator/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs +++ b/packages/template-generator/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs @@ -32,6 +32,10 @@ import { orpc } from "@/utils/orpc"; import { useMutation, useQuery } from "@tanstack/react-query"; {{/if}} +{{#unless (eq backend "convex")}} +type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}}; +{{/unless}} + export const Route = createFileRoute("/todos")({ component: TodosRoute, }); @@ -133,11 +137,11 @@ function TodosRoute() { } }; - const handleToggleTodo = (id: number, completed: boolean) => { + const handleToggleTodo = (id: TodoId, completed: boolean) => { toggleMutation.mutate({ id, completed: !completed }); }; - const handleDeleteTodo = (id: number) => { + const handleDeleteTodo = (id: TodoId) => { deleteMutation.mutate({ id }); }; {{/if}} From 8867df7896bc9d8ed42951b372c90025ae28efae Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 20 May 2026 14:29:23 +0530 Subject: [PATCH 2/4] fix(cli): approve pnpm build scripts for generated stacks --- apps/cli/test/pnpm-workspace.test.ts | 141 ++++++++++++++++++ .../src/template-handlers/extras.ts | 21 ++- .../src/templates.generated.ts | 30 +++- .../templates/extras/pnpm-workspace.yaml | 3 - .../templates/extras/pnpm-workspace.yaml.hbs | 31 ++++ 5 files changed, 211 insertions(+), 15 deletions(-) create mode 100644 apps/cli/test/pnpm-workspace.test.ts delete mode 100644 packages/template-generator/templates/extras/pnpm-workspace.yaml create mode 100644 packages/template-generator/templates/extras/pnpm-workspace.yaml.hbs diff --git a/apps/cli/test/pnpm-workspace.test.ts b/apps/cli/test/pnpm-workspace.test.ts new file mode 100644 index 000000000..d67a51306 --- /dev/null +++ b/apps/cli/test/pnpm-workspace.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from "bun:test"; +import { readFile } from "node:fs/promises"; +import path from "node:path"; + +import yaml from "yaml"; + +import { expectSuccess, runTRPCTest, type TestConfig } from "./test-utils"; + +async function readPnpmWorkspace(config: TestConfig) { + const result = await runTRPCTest({ + ...config, + packageManager: "pnpm", + install: false, + git: false, + }); + + expectSuccess(result); + + const workspacePath = path.join(result.projectDir!, "pnpm-workspace.yaml"); + const content = await readFile(workspacePath, "utf8"); + return yaml.parse(content) as { allowBuilds?: Record }; +} + +describe("pnpm workspace", () => { + it("adds build approvals for the Convex Better Auth Cloudflare stack", async () => { + const workspace = await readPnpmWorkspace({ + projectName: "pnpm-convex-cloudflare", + frontend: ["tanstack-start"], + backend: "convex", + runtime: "none", + api: "none", + database: "none", + orm: "none", + auth: "better-auth", + payments: "none", + addons: ["turborepo"], + examples: ["todo"], + dbSetup: "none", + webDeploy: "cloudflare", + serverDeploy: "none", + }); + + expect(workspace.allowBuilds).toMatchObject({ + esbuild: true, + msw: true, + sharp: true, + workerd: true, + }); + }); + + it("adds build approvals for Prisma engines", async () => { + const workspace = await readPnpmWorkspace({ + projectName: "pnpm-prisma-engines", + frontend: ["none"], + backend: "hono", + runtime: "bun", + api: "none", + database: "sqlite", + orm: "prisma", + auth: "none", + payments: "none", + addons: ["none"], + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + }); + + expect(workspace.allowBuilds).toMatchObject({ + "@prisma/engines": true, + prisma: true, + }); + }); + + it("adds build approvals for node runtime and workspace addons", async () => { + const workspace = await readPnpmWorkspace({ + projectName: "pnpm-node-addons", + frontend: ["none"], + backend: "hono", + runtime: "node", + api: "none", + database: "none", + orm: "none", + auth: "none", + payments: "none", + addons: ["nx", "lefthook"], + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + }); + + expect(workspace.allowBuilds).toMatchObject({ + esbuild: true, + lefthook: true, + nx: true, + }); + }); + + it("adds only the React UI approval when no other build-script deps are expected", async () => { + const workspace = await readPnpmWorkspace({ + projectName: "pnpm-react-ui", + frontend: ["tanstack-router"], + backend: "none", + runtime: "none", + api: "none", + database: "none", + orm: "none", + auth: "none", + payments: "none", + addons: ["none"], + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + }); + + expect(workspace.allowBuilds).toEqual({ msw: true }); + }); + + it("does not add build approvals for stacks without lifecycle-script dependencies", async () => { + const workspace = await readPnpmWorkspace({ + projectName: "pnpm-svelte", + frontend: ["svelte"], + backend: "none", + runtime: "none", + api: "none", + database: "none", + orm: "none", + auth: "none", + payments: "none", + addons: ["none"], + examples: ["none"], + dbSetup: "none", + webDeploy: "none", + serverDeploy: "none", + }); + + expect(workspace.allowBuilds).toBeUndefined(); + }); +}); diff --git a/packages/template-generator/src/template-handlers/extras.ts b/packages/template-generator/src/template-handlers/extras.ts index 01bf75f16..e38e1879c 100644 --- a/packages/template-generator/src/template-handlers/extras.ts +++ b/packages/template-generator/src/template-handlers/extras.ts @@ -1,12 +1,7 @@ import type { ProjectConfig } from "@better-t-stack/types"; import type { VirtualFileSystem } from "../core/virtual-fs"; -import { - type TemplateData, - hasTemplatesWithPrefix, - processTemplatesFromPrefix, - processSingleTemplate, -} from "./utils"; +import { type TemplateData, processSingleTemplate } from "./utils"; export async function processExtrasTemplates( vfs: VirtualFileSystem, @@ -19,17 +14,21 @@ export async function processExtrasTemplates( const hasNuxt = config.frontend.includes("nuxt"); if (config.packageManager === "pnpm") { - if (hasTemplatesWithPrefix(templates, "extras")) { - processTemplatesFromPrefix(vfs, templates, "extras/pnpm-workspace.yaml", "", config); - } + processSingleTemplate( + vfs, + templates, + "extras/pnpm-workspace.yaml", + "pnpm-workspace.yaml", + config, + ); } if (config.packageManager === "bun") { - processTemplatesFromPrefix(vfs, templates, "extras/bunfig.toml", "", config); + processSingleTemplate(vfs, templates, "extras/bunfig.toml", "bunfig.toml", config); } if (config.packageManager === "pnpm" && (hasNative || hasNuxt)) { - processTemplatesFromPrefix(vfs, templates, "extras/_npmrc", "", config); + processSingleTemplate(vfs, templates, "extras/_npmrc", ".npmrc", config); } if ( diff --git a/packages/template-generator/src/templates.generated.ts b/packages/template-generator/src/templates.generated.ts index e7665d6ec..b5c6cc705 100644 --- a/packages/template-generator/src/templates.generated.ts +++ b/packages/template-generator/src/templates.generated.ts @@ -21690,9 +21690,37 @@ declare module "cloudflare:workers" { } } `], - ["extras/pnpm-workspace.yaml", `packages: + ["extras/pnpm-workspace.yaml.hbs", `packages: - "apps/*" - "packages/*" +{{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next"))}} + +# pnpm 11 blocks dependency lifecycle scripts unless they are approved here. +# Entries are scoped to packages this generated stack can pull in. +allowBuilds: +{{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes frontend "tanstack-start"))}} + esbuild: true +{{/if}} +{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next"))}} + msw: true +{{/if}} +{{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes addons "pwa"))}} + sharp: true +{{/if}} +{{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare"))}} + workerd: true +{{/if}} +{{#if (eq orm "prisma")}} + "@prisma/engines": true + prisma: true +{{/if}} +{{#if (includes addons "lefthook")}} + lefthook: true +{{/if}} +{{#if (includes addons "nx")}} + nx: true +{{/if}} +{{/if}} `], ["frontend/astro/_gitignore", `# build output dist/ diff --git a/packages/template-generator/templates/extras/pnpm-workspace.yaml b/packages/template-generator/templates/extras/pnpm-workspace.yaml deleted file mode 100644 index 3ff5faaaf..000000000 --- a/packages/template-generator/templates/extras/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - - "apps/*" - - "packages/*" diff --git a/packages/template-generator/templates/extras/pnpm-workspace.yaml.hbs b/packages/template-generator/templates/extras/pnpm-workspace.yaml.hbs new file mode 100644 index 000000000..77213a51e --- /dev/null +++ b/packages/template-generator/templates/extras/pnpm-workspace.yaml.hbs @@ -0,0 +1,31 @@ +packages: + - "apps/*" + - "packages/*" +{{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next"))}} + +# pnpm 11 blocks dependency lifecycle scripts unless they are approved here. +# Entries are scoped to packages this generated stack can pull in. +allowBuilds: +{{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes frontend "tanstack-start"))}} + esbuild: true +{{/if}} +{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next"))}} + msw: true +{{/if}} +{{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes addons "pwa"))}} + sharp: true +{{/if}} +{{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare"))}} + workerd: true +{{/if}} +{{#if (eq orm "prisma")}} + "@prisma/engines": true + prisma: true +{{/if}} +{{#if (includes addons "lefthook")}} + lefthook: true +{{/if}} +{{#if (includes addons "nx")}} + nx: true +{{/if}} +{{/if}} From ad8bcbbcefc19b0e758aea0f8df1c93932335b5d Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Thu, 21 May 2026 02:42:07 +0530 Subject: [PATCH 3/4] fix(cli): avoid mongoose todo id virtual --- .../template-generator/src/templates.generated.ts | 13 ++++++++----- .../server/mongoose/base/src/routers/todo.ts.hbs | 10 ++++++---- .../mongoose/mongodb/src/models/todo.model.ts.hbs | 3 ++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/template-generator/src/templates.generated.ts b/packages/template-generator/src/templates.generated.ts index 11f302fe1..af78d5f65 100644 --- a/packages/template-generator/src/templates.generated.ts +++ b/packages/template-generator/src/templates.generated.ts @@ -19525,14 +19525,15 @@ import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = { getAll: publicProcedure.handler(async () => { const todos = await Todo.find().lean(); - return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); + return todos.map((todo) => ({ ...todo, id: todo.id })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .handler(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return { ...newTodo.toObject(), id: newTodo.id.toString() }; + const todo = newTodo.toObject(); + return { ...todo, id: todo.id }; }), toggle: publicProcedure @@ -19561,14 +19562,15 @@ import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = router({ getAll: publicProcedure.query(async () => { const todos = await Todo.find().lean(); - return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); + return todos.map((todo) => ({ ...todo, id: todo.id })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .mutation(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return { ...newTodo.toObject(), id: newTodo.id.toString() }; + const todo = newTodo.toObject(); + return { ...todo, id: todo.id }; }), toggle: publicProcedure @@ -19606,7 +19608,8 @@ const todoSchema = new Schema({ default: false, }, }, { - collection: 'todo' + collection: 'todo', + id: false, }); const Todo = model('Todo', todoSchema); diff --git a/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs b/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs index 7f6cf671e..ad4a2e0ef 100644 --- a/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +++ b/packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs @@ -7,14 +7,15 @@ import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = { getAll: publicProcedure.handler(async () => { const todos = await Todo.find().lean(); - return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); + return todos.map((todo) => ({ ...todo, id: todo.id })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .handler(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return { ...newTodo.toObject(), id: newTodo.id.toString() }; + const todo = newTodo.toObject(); + return { ...todo, id: todo.id }; }), toggle: publicProcedure @@ -43,14 +44,15 @@ import { Todo } from "@{{projectName}}/db/models/todo.model"; export const todoRouter = router({ getAll: publicProcedure.query(async () => { const todos = await Todo.find().lean(); - return todos.map((todo) => ({ ...todo, id: todo.id.toString() })); + return todos.map((todo) => ({ ...todo, id: todo.id })); }), create: publicProcedure .input(z.object({ text: z.string().min(1) })) .mutation(async ({ input }) => { const newTodo = await Todo.create({ text: input.text }); - return { ...newTodo.toObject(), id: newTodo.id.toString() }; + const todo = newTodo.toObject(); + return { ...todo, id: todo.id }; }), toggle: publicProcedure diff --git a/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs b/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs index 9bfc51d66..18f1fd1db 100644 --- a/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs +++ b/packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs @@ -17,7 +17,8 @@ const todoSchema = new Schema({ default: false, }, }, { - collection: 'todo' + collection: 'todo', + id: false, }); const Todo = model('Todo', todoSchema); From 934aa1dcf6f4e39b65abaa5d4b0cd48283058e29 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Thu, 21 May 2026 12:08:00 +0530 Subject: [PATCH 4/4] test(cli): update mongoose todo id assertion --- apps/cli/test/auth.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli/test/auth.test.ts b/apps/cli/test/auth.test.ts index eca811749..2058793f7 100644 --- a/apps/cli/test/auth.test.ts +++ b/apps/cli/test/auth.test.ts @@ -104,7 +104,7 @@ describe("Authentication Configurations", () => { "utf8", ); expect(todoRouter).toContain('import "@better-auth-mongodb/db";'); - expect(todoRouter).toContain("id: todo.id.toString()"); + expect(todoRouter).toContain("id: todo.id"); const authModels = await fs.readFile( path.join(projectDir, "packages/db/src/models/auth.model.ts"),