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
7 changes: 6 additions & 1 deletion apps/cli/src/helpers/core/api-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ function getApiDependencies(

if (frontendType.hasReactWeb) {
if (api === "orpc") {
deps.web = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
deps.web = {
dependencies: ["@orpc/tanstack-query", "@orpc/client", "@orpc/server"],
};
} else if (api === "trpc") {
deps.web = {
dependencies: [
Expand All @@ -84,6 +86,7 @@ function getApiDependencies(
"@tanstack/vue-query",
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
],
devDependencies: ["@tanstack/vue-query-devtools"],
};
Expand All @@ -92,6 +95,7 @@ function getApiDependencies(
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
"@tanstack/svelte-query",
],
devDependencies: ["@tanstack/svelte-query-devtools"],
Expand All @@ -101,6 +105,7 @@ function getApiDependencies(
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
"@tanstack/solid-query",
],
devDependencies: [
Expand Down
3 changes: 3 additions & 0 deletions apps/cli/src/helpers/core/create-project.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { log } from "@clack/prompts";
import fs from "fs-extra";
import type { ProjectConfig } from "../../types";
import { setupBetterAuthPlugins } from "../../utils/better-auth-plugin-setup";
import { writeBtsConfig } from "../../utils/bts-config";
import { exitWithError } from "../../utils/errors";
import { setupCatalogs } from "../../utils/setup-catalogs";
Expand Down Expand Up @@ -86,6 +87,8 @@ export async function createProject(
await setupAuth(options);
}

await setupBetterAuthPlugins(projectDir, options);

if (options.payments && options.payments !== "none") {
await setupPayments(options);
}
Expand Down
12 changes: 10 additions & 2 deletions apps/cli/src/helpers/core/env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function getClientServerVar(
const hasNextJs = frontend.includes("next");
const hasNuxt = frontend.includes("nuxt");
const hasSvelte = frontend.includes("svelte");
const hasTanstackStart = frontend.includes("tanstack-start");

// For fullstack self, no base URL is needed (same-origin)
if (backend === "self") {
Expand All @@ -20,6 +21,7 @@ function getClientServerVar(
if (hasNextJs) key = "NEXT_PUBLIC_SERVER_URL";
else if (hasNuxt) key = "NUXT_PUBLIC_SERVER_URL";
else if (hasSvelte) key = "PUBLIC_SERVER_URL";
else if (hasTanstackStart) key = "VITE_SERVER_URL";

return { key, value: "http://localhost:3000", write: true } as const;
}
Expand All @@ -28,9 +30,11 @@ function getConvexVar(frontend: string[]) {
const hasNextJs = frontend.includes("next");
const hasNuxt = frontend.includes("nuxt");
const hasSvelte = frontend.includes("svelte");
const hasTanstackStart = frontend.includes("tanstack-start");
if (hasNextJs) return "NEXT_PUBLIC_CONVEX_URL";
if (hasNuxt) return "NUXT_PUBLIC_CONVEX_URL";
if (hasSvelte) return "PUBLIC_CONVEX_URL";
if (hasTanstackStart) return "VITE_CONVEX_URL";
return "VITE_CONVEX_URL";
}

Expand Down Expand Up @@ -227,8 +231,12 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
const nativeDir = path.join(projectDir, "apps/native");
if (await fs.pathExists(nativeDir)) {
let envVarName = "EXPO_PUBLIC_SERVER_URL";
let serverUrl =
backend === "self" ? "http://localhost:3001" : "http://localhost:3000";
let serverUrl = "http://localhost:3000";

if (backend === "self") {
// Both TanStack Start and Next.js use port 3001 for fullstack
serverUrl = "http://localhost:3001";
}

if (backend === "convex") {
envVarName = "EXPO_PUBLIC_CONVEX_URL";
Expand Down
12 changes: 8 additions & 4 deletions apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export async function displayPostInstallInstructions(
const nativeInstructions =
frontend?.includes("native-nativewind") ||
frontend?.includes("native-unistyles")
? getNativeInstructions(isConvex, isBackendSelf)
? getNativeInstructions(isConvex, isBackendSelf, frontend || [])
: "";
const pwaInstructions =
addons?.includes("pwa") && frontend?.includes("react-router")
Expand Down Expand Up @@ -171,12 +171,12 @@ export async function displayPostInstallInstructions(
output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;

if (api === "orpc") {
output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\n`;
}
}

if (isBackendSelf && api === "orpc") {
output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/rpc/api\n`;
output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/api/rpc/api-reference\n`;
}

if (addons?.includes("starlight")) {
Expand Down Expand Up @@ -214,7 +214,11 @@ export async function displayPostInstallInstructions(
consola.box(output);
}

function getNativeInstructions(isConvex: boolean, isBackendSelf: boolean) {
function getNativeInstructions(
isConvex: boolean,
isBackendSelf: boolean,
_frontend: string[],
) {
const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
const exampleUrl = isConvex
? "https://<YOUR_CONVEX_URL>"
Expand Down
18 changes: 12 additions & 6 deletions apps/cli/src/helpers/core/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ export async function setupFrontendTemplates(

if (
context.backend === "self" &&
reactFramework === "next" &&
(reactFramework === "next" || reactFramework === "tanstack-start") &&
context.api !== "none"
) {
const apiFullstackDir = path.join(
PKG_ROOT,
`templates/api/${context.api}/fullstack/next`,
`templates/api/${context.api}/fullstack/${reactFramework}`,
);
if (await fs.pathExists(apiFullstackDir)) {
await processAndCopyFiles(
Expand Down Expand Up @@ -597,10 +597,13 @@ export async function setupAuthTemplate(
);
}

if (context.backend === "self" && reactFramework === "next") {
if (
context.backend === "self" &&
(reactFramework === "next" || reactFramework === "tanstack-start")
) {
const authFullstackSrc = path.join(
PKG_ROOT,
`templates/auth/${authProvider}/fullstack/next`,
`templates/auth/${authProvider}/fullstack/${reactFramework}`,
);
if (await fs.pathExists(authFullstackSrc)) {
await processAndCopyFiles(
Expand Down Expand Up @@ -938,10 +941,13 @@ export async function setupExamplesTemplate(
} else {
}

if (context.backend === "self" && reactFramework === "next") {
if (
context.backend === "self" &&
(reactFramework === "next" || reactFramework === "tanstack-start")
) {
const exampleFullstackSrc = path.join(
exampleBaseDir,
"fullstack/next",
`fullstack/${reactFramework}`,
);
if (await fs.pathExists(exampleFullstackSrc)) {
await processAndCopyFiles(
Expand Down
34 changes: 21 additions & 13 deletions apps/cli/src/helpers/core/workspace-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,47 @@ export async function setupWorkspaceDependencies(

const authPackageDir = path.join(projectDir, "packages/auth");
if (await fs.pathExists(authPackageDir)) {
const authDeps: Record<string, string> = {};
if (options.database !== "none" && (await fs.pathExists(dbPackageDir))) {
authDeps[`@${projectName}/db`] = workspaceVersion;
}

await addPackageDependency({
dependencies: commonDeps,
devDependencies: commonDevDeps,
customDependencies: {
[`@${projectName}/db`]: workspaceVersion,
},
customDependencies: authDeps,
projectDir: authPackageDir,
});
}

const apiPackageDir = path.join(projectDir, "packages/api");
if (await fs.pathExists(apiPackageDir)) {
const apiDeps: Record<string, string> = {};
if (options.auth !== "none" && (await fs.pathExists(authPackageDir))) {
apiDeps[`@${projectName}/auth`] = workspaceVersion;
}
if (options.database !== "none" && (await fs.pathExists(dbPackageDir))) {
apiDeps[`@${projectName}/db`] = workspaceVersion;
}

await addPackageDependency({
dependencies: commonDeps,
devDependencies: commonDevDeps,
customDependencies: {
[`@${projectName}/auth`]: workspaceVersion,
[`@${projectName}/db`]: workspaceVersion,
},
customDependencies: apiDeps,
projectDir: apiPackageDir,
});
}

const serverPackageDir = path.join(projectDir, "apps/server");
if (await fs.pathExists(serverPackageDir)) {
const serverDeps: Record<string, string> = {};
if (await fs.pathExists(apiPackageDir)) {
if (options.api !== "none" && (await fs.pathExists(apiPackageDir))) {
serverDeps[`@${projectName}/api`] = workspaceVersion;
}
if (await fs.pathExists(authPackageDir)) {
if (options.auth !== "none" && (await fs.pathExists(authPackageDir))) {
serverDeps[`@${projectName}/auth`] = workspaceVersion;
}
if (await fs.pathExists(dbPackageDir)) {
if (options.database !== "none" && (await fs.pathExists(dbPackageDir))) {
serverDeps[`@${projectName}/db`] = workspaceVersion;
}

Expand All @@ -74,10 +82,10 @@ export async function setupWorkspaceDependencies(

if (await fs.pathExists(webPackageDir)) {
const webDeps: Record<string, string> = {};
if (await fs.pathExists(apiPackageDir)) {
if (options.api !== "none" && (await fs.pathExists(apiPackageDir))) {
webDeps[`@${projectName}/api`] = workspaceVersion;
}
if (await fs.pathExists(authPackageDir)) {
if (options.auth !== "none" && (await fs.pathExists(authPackageDir))) {
webDeps[`@${projectName}/auth`] = workspaceVersion;
}

Expand All @@ -93,7 +101,7 @@ export async function setupWorkspaceDependencies(

if (await fs.pathExists(nativePackageDir)) {
const nativeDeps: Record<string, string> = {};
if (await fs.pathExists(apiPackageDir)) {
if (options.api !== "none" && (await fs.pathExists(apiPackageDir))) {
nativeDeps[`@${projectName}/api`] = workspaceVersion;
}

Expand Down
4 changes: 2 additions & 2 deletions apps/cli/src/prompts/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { DEFAULT_CONFIG } from "../constants";
import type { Backend, Frontend } from "../types";
import { exitCancelled } from "../utils/errors";

// Temporarily restrict to Next.js only for backend="self"
// Temporarily restrict to Next.js and TanStack Start only for backend="self"
const FULLSTACK_FRONTENDS: readonly Frontend[] = [
"next",
"tanstack-start",
// "nuxt", // TODO: Add support in future update
// "svelte", // TODO: Add support in future update
// "tanstack-start", // TODO: Add support in future update
] as const;

export async function getBackendFrameworkChoice(
Expand Down
81 changes: 81 additions & 0 deletions apps/cli/src/utils/better-auth-plugin-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { SyntaxKind } from "ts-morph";
import type { ProjectConfig } from "../types";
import { ensureArrayProperty, tsProject } from "./ts-morph";

export async function setupBetterAuthPlugins(
projectDir: string,
config: ProjectConfig,
) {
const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);

if (!authIndexFile) {
console.warn("Better Auth index file not found, skipping plugin setup");
return;
}

const pluginsToAdd: string[] = [];
const importsToAdd: string[] = [];

if (
config.backend === "self" &&
config.frontend?.includes("tanstack-start")
) {
pluginsToAdd.push("reactStartCookies()");
importsToAdd.push(
'import { reactStartCookies } from "better-auth/react-start";',
);
}

if (
config.frontend?.includes("native-nativewind") ||
config.frontend?.includes("native-unistyles")
) {
pluginsToAdd.push("expo()");
importsToAdd.push('import { expo } from "@better-auth/expo";');
}

if (pluginsToAdd.length === 0) {
return;
}

importsToAdd.forEach((importStatement) => {
const existingImport = authIndexFile.getImportDeclaration((declaration) =>
declaration
.getModuleSpecifierValue()
.includes(importStatement.split('"')[1]),
);

if (!existingImport) {
authIndexFile.insertImportDeclaration(0, {
moduleSpecifier: importStatement.split('"')[1],
namedImports: [importStatement.split("{")[1].split("}")[0].trim()],
});
}
});

const betterAuthCall = authIndexFile
.getDescendantsOfKind(SyntaxKind.CallExpression)
.find((call) => call.getExpression().getText() === "betterAuth");

if (betterAuthCall) {
const configObject = betterAuthCall.getArguments()[0];

if (
configObject &&
configObject.getKind() === SyntaxKind.ObjectLiteralExpression
) {
const objLiteral = configObject.asKindOrThrow(
SyntaxKind.ObjectLiteralExpression,
);

const pluginsArray = ensureArrayProperty(objLiteral, "plugins");

pluginsToAdd.forEach((plugin) => {
pluginsArray.addElement(plugin);
});
}
}

authIndexFile.save();
}
8 changes: 4 additions & 4 deletions apps/cli/src/utils/compatibility-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ export function ensureSingleWebAndNative(frontends: Frontend[]) {
}
}

// Temporarily restrict to Next.js only for backend="self"
// Temporarily restrict to Next.js and TanStack Start only for backend="self"
const FULLSTACK_FRONTENDS: readonly Frontend[] = [
"next",
"tanstack-start",
// "nuxt", // TODO: Add support in future update
// "svelte", // TODO: Add support in future update
// "tanstack-start", // TODO: Add support in future update
] as const;

export function validateSelfBackendCompatibility(
Expand All @@ -66,7 +66,7 @@ export function validateSelfBackendCompatibility(

if (!hasSupportedWeb) {
exitWithError(
"Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.",
"Backend 'self' (fullstack) currently only supports Next.js and TanStack Start frontends. Please use --frontend next or --frontend tanstack-start. Support for Nuxt and SvelteKit will be added in a future update.",
);
}

Expand All @@ -86,7 +86,7 @@ export function validateSelfBackendCompatibility(
backend === "self"
) {
exitWithError(
"Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next or choose a different backend. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.",
"Backend 'self' (fullstack) currently only supports Next.js and TanStack Start frontends. Please use --frontend next or --frontend tanstack-start or choose a different backend. Support for Nuxt and SvelteKit will be added in a future update.",
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function handleRequest(req: NextRequest) {
if (rpcResult.response) return rpcResult.response;

const apiResult = await apiHandler.handle(req, {
prefix: "/api/rpc/api",
prefix: "/api/rpc/api-reference",
context: await createContext(req),
});
if (apiResult.response) return apiResult.response;
Expand Down
Loading