Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,12 @@ export const dependencyVersionMap = {
"@trpc/server": "^11.5.0",
"@trpc/client": "^11.5.0",

convex: "^1.25.4",
convex: "^1.27.0",
"@convex-dev/react-query": "^0.0.0-alpha.8",
"convex-svelte": "^0.0.11",
"convex-nuxt": "0.1.5",
"convex-vue": "^0.1.5",
"@convex-dev/better-auth": "^0.8.4",

"@tanstack/svelte-query": "^5.85.3",
"@tanstack/svelte-query-devtools": "^5.85.3",
Expand Down
41 changes: 41 additions & 0 deletions apps/cli/src/helpers/core/auth-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,47 @@ export async function setupAuth(config: ProjectConfig) {
}
}

if (auth === "better-auth") {
const convexBackendDir = path.join(projectDir, "packages/backend");
const convexBackendDirExists = await fs.pathExists(convexBackendDir);

if (convexBackendDirExists) {
await addPackageDependency({
dependencies: ["better-auth", "@convex-dev/better-auth"],
customDependencies: { "better-auth": "1.3.8" },
projectDir: convexBackendDir,
});
}

if (clientDirExists) {
const hasNextJs = frontend.includes("next");
const hasTanStackStart = frontend.includes("tanstack-start");
const hasViteReactOther = frontend.some((f) =>
["tanstack-router", "react-router"].includes(f),
);

if (hasNextJs) {
await addPackageDependency({
dependencies: ["better-auth", "@convex-dev/better-auth"],
customDependencies: { "better-auth": "1.3.8" },
projectDir: clientDir,
});
} else if (hasTanStackStart) {
await addPackageDependency({
dependencies: ["better-auth", "@convex-dev/better-auth"],
customDependencies: { "better-auth": "1.3.8" },
projectDir: clientDir,
});
} else if (hasViteReactOther) {
await addPackageDependency({
dependencies: ["better-auth", "@convex-dev/better-auth"],
customDependencies: { "better-auth": "1.3.8" },
projectDir: clientDir,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Dependency Version Conflict

The better-auth package is hardcoded to version 1.3.8 in customDependencies when setting up better-auth projects. This conflicts with the ^1.3.10 version defined in constants.ts, which can lead to inconsistent dependency resolution and an older version being installed.

Fix in Cursor Fix in Web

});
}
}
}

const hasNativeWind = frontend.includes("native-nativewind");
const hasUnistyles = frontend.includes("native-unistyles");
if (
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/helpers/core/convex-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { execa } from "execa";
import type { PackageManager } from "../../types";
import { getPackageExecutionCommand } from "../../utils/package-runner";

// having problems running this in convex + better-auth
export async function runConvexCodegen(
projectDir: string,
packageManager: PackageManager | null | undefined,
Expand Down
5 changes: 0 additions & 5 deletions apps/cli/src/helpers/core/create-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { setupRuntime } from "../core/runtime-setup";
import { setupServerDeploy } from "../deployment/server-deploy-setup";
import { setupWebDeploy } from "../deployment/web-deploy-setup";
import { setupAuth } from "./auth-setup";
import { runConvexCodegen } from "./convex-codegen";
import { createReadme } from "./create-readme";
import { setupEnvironmentVariables } from "./env-setup";
import { initializeGit } from "./git";
Expand Down Expand Up @@ -97,10 +96,6 @@ export async function createProject(

await writeBtsConfig(options);

if (isConvex) {
await runConvexCodegen(projectDir, options.packageManager);
}

log.success("Project template successfully scaffolded!");

if (options.install) {
Expand Down
59 changes: 58 additions & 1 deletion apps/cli/src/helpers/core/env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface EnvVariable {
key: string;
value: string | null | undefined;
condition: boolean;
comment?: string;
}

export async function addEnvVariablesToFile(
Expand All @@ -24,7 +25,7 @@ export async function addEnvVariablesToFile(
let contentToAdd = "";
const exampleVariables: string[] = [];

for (const { key, value, condition } of variables) {
for (const { key, value, condition, comment } of variables) {
if (condition) {
const regex = new RegExp(`^${key}=.*$`, "m");
const valueToWrite = value ?? "";
Expand All @@ -37,6 +38,9 @@ export async function addEnvVariablesToFile(
modified = true;
}
} else {
if (comment) {
contentToAdd += `# ${comment}\n`;
}
contentToAdd += `${key}=${valueToWrite}\n`;
modified = true;
}
Expand Down Expand Up @@ -179,6 +183,22 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
}
}

if (backend === "convex" && auth === "better-auth") {
if (hasNextJs) {
clientVars.push({
key: "NEXT_PUBLIC_CONVEX_SITE_URL",
value: "https://<YOUR_CONVEX_URL>",
condition: true,
});
} else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
clientVars.push({
key: "VITE_CONVEX_SITE_URL",
value: "https://<YOUR_CONVEX_URL>",
condition: true,
});
}
}

await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
}
}
Expand Down Expand Up @@ -217,6 +237,43 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
}

if (backend === "convex") {
if (auth === "better-auth") {
const convexBackendDir = path.join(projectDir, "packages/backend");
if (await fs.pathExists(convexBackendDir)) {
const envLocalPath = path.join(convexBackendDir, ".env.local");

if (
!(await fs.pathExists(envLocalPath)) ||
!(await fs.readFile(envLocalPath, "utf8")).includes(
"npx convex env set",
)
) {
const convexCommands = `# Set Convex environment variables
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
npx convex env set SITE_URL http://localhost:3001

`;
await fs.appendFile(envLocalPath, convexCommands);
}

const convexBackendVars: EnvVariable[] = [
{
key: hasNextJs
? "NEXT_PUBLIC_CONVEX_SITE_URL"
: "VITE_CONVEX_SITE_URL",
value: "",
condition: true,
comment: "Same as CONVEX_URL but ends in .site",
},
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Backend Environment Misconfigured with Frontend Variables

The Convex backend environment setup incorrectly adds frontend-specific NEXT_PUBLIC_CONVEX_SITE_URL or VITE_CONVEX_SITE_URL variables to packages/backend/.env.local. The backend does not use these variables. Additionally, the logic for choosing between NEXT_PUBLIC_ and VITE_ prefixes is flawed, as NEXT_PUBLIC_ is specific to Next.js and should be determined by hasNextJs, not hasTanStackStart.

Fix in Cursor Fix in Web

key: "SITE_URL",
value: "http://localhost:3001",
condition: true,
},
];
await addEnvVariablesToFile(envLocalPath, convexBackendVars);
}
}
return;
}

Expand Down
53 changes: 53 additions & 0 deletions apps/cli/src/helpers/core/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,59 @@ export async function setupAuthTemplate(
return;
}

if (context.backend === "convex" && authProvider === "better-auth") {
const convexBackendDestDir = path.join(projectDir, "packages/backend");
const convexBetterAuthBackendSrc = path.join(
PKG_ROOT,
"templates/auth/better-auth/convex/backend",
);
if (await fs.pathExists(convexBetterAuthBackendSrc)) {
await fs.ensureDir(convexBackendDestDir);
await processAndCopyFiles(
"**/*",
convexBetterAuthBackendSrc,
convexBackendDestDir,
context,
);
}

if (webAppDirExists && hasReactWeb) {
const convexBetterAuthWebBaseSrc = path.join(
PKG_ROOT,
"templates/auth/better-auth/convex/web/react/base",
);
if (await fs.pathExists(convexBetterAuthWebBaseSrc)) {
await processAndCopyFiles(
"**/*",
convexBetterAuthWebBaseSrc,
webAppDir,
context,
);
}

const reactFramework = context.frontend.find((f) =>
["tanstack-router", "react-router", "tanstack-start", "next"].includes(
f,
),
);
if (reactFramework) {
const convexBetterAuthWebSrc = path.join(
PKG_ROOT,
`templates/auth/better-auth/convex/web/react/${reactFramework}`,
);
if (await fs.pathExists(convexBetterAuthWebSrc)) {
await processAndCopyFiles(
"**/*",
convexBetterAuthWebSrc,
webAppDir,
context,
);
}
}
}
return;
}

if (serverAppDirExists && context.backend !== "convex") {
const authServerBaseSrc = path.join(
PKG_ROOT,
Expand Down
46 changes: 33 additions & 13 deletions apps/cli/src/prompts/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,45 @@ export async function getAuthChoice(
) {
if (auth !== undefined) return auth;
if (backend === "convex") {
const unsupportedFrontends = frontend?.filter((f) =>
["nuxt", "svelte", "solid"].includes(f),
const supportedBetterAuthFrontends = frontend?.some((f) =>
["tanstack-router", "tanstack-start", "next"].includes(f),
);

if (unsupportedFrontends && unsupportedFrontends.length > 0) {
return "none";
const hasClerkCompatibleFrontends = frontend?.some((f) =>
[
"react-router",
"tanstack-router",
"tanstack-start",
"next",
"native-nativewind",
"native-unistyles",
].includes(f),
);

const options = [];

if (supportedBetterAuthFrontends) {
options.push({
value: "better-auth",
label: "Better-Auth",
hint: "comprehensive auth framework for TypeScript",
});
}

if (hasClerkCompatibleFrontends) {
options.push({
value: "clerk",
label: "Clerk",
hint: "More than auth, Complete User Management",
});
}

options.push({ value: "none", label: "None", hint: "No auth" });

const response = await select({
message: "Select authentication provider",
options: [
{
value: "clerk",
label: "Clerk",
hint: "More than auth, Complete User Management",
},
{ value: "none", label: "None" },
],
initialValue: "clerk",
options,
initialValue: "none",
});
if (isCancel(response)) return exitCancelled("Operation cancelled");
return response as Auth;
Expand Down
16 changes: 13 additions & 3 deletions apps/cli/src/utils/add-package-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ import { type AvailableDependencies, dependencyVersionMap } from "../constants";
export const addPackageDependency = async (opts: {
dependencies?: AvailableDependencies[];
devDependencies?: AvailableDependencies[];
customDependencies?: Record<string, string>;
customDevDependencies?: Record<string, string>;
projectDir: string;
}) => {
const { dependencies = [], devDependencies = [], projectDir } = opts;
const {
dependencies = [],
devDependencies = [],
customDependencies = {},
customDevDependencies = {},
projectDir,
} = opts;

const pkgJsonPath = path.join(projectDir, "package.json");

Expand All @@ -18,7 +26,8 @@ export const addPackageDependency = async (opts: {
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};

for (const pkgName of dependencies) {
const version = dependencyVersionMap[pkgName];
const version =
customDependencies[pkgName] || dependencyVersionMap[pkgName];
if (version) {
pkgJson.dependencies[pkgName] = version;
} else {
Expand All @@ -27,7 +36,8 @@ export const addPackageDependency = async (opts: {
}

for (const pkgName of devDependencies) {
const version = dependencyVersionMap[pkgName];
const version =
customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
if (version) {
pkgJson.devDependencies[pkgName] = version;
} else {
Expand Down
8 changes: 1 addition & 7 deletions apps/cli/src/utils/compatibility-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export function validateAddonsAgainstFrontends(
export function validatePaymentsCompatibility(
payments: Payments | undefined,
auth: Auth | undefined,
backend: Backend | undefined,
_backend: Backend | undefined,
frontends: Frontend[] = [],
) {
if (!payments || payments === "none") return;
Expand All @@ -266,12 +266,6 @@ export function validatePaymentsCompatibility(
);
}

if (backend === "convex") {
exitWithError(
"Polar payments is not compatible with Convex backend. Please use a different backend or choose a different payments provider.",
);
}

const { web } = splitFrontends(frontends);
if (web.length === 0 && frontends.length > 0) {
exitWithError(
Expand Down
11 changes: 9 additions & 2 deletions apps/cli/src/utils/config-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,16 @@ export function validateConvexConstraints(
}

if (has("auth") && config.auth === "better-auth") {
exitWithError(
"Better-Auth is not compatible with Convex backend. Please use '--auth clerk' or '--auth none'.",
const supportedFrontends = ["tanstack-router", "tanstack-start", "next"];
const hasSupportedFrontend = config.frontend?.some((f) =>
supportedFrontends.includes(f),
);

if (!hasSupportedFrontend) {
exitWithError(
"Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.",
);
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion apps/cli/templates/addons/biome/biome.json.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"!**/.expo",
"!**/.wrangler",
"!**/.alchemy",
"!**/.svelte-kit",
"!**/wrangler.jsonc",
"!**/.source"
]
Expand Down Expand Up @@ -66,7 +67,7 @@
"quoteStyle": "double"
}
}
{{#if (or (eq frontend "svelte") (eq frontend "nuxt"))}}
{{#if (or (includes frontend "svelte") (includes frontend "nuxt"))}}
,
"overrides": [
{
Expand Down
Loading