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
14 changes: 9 additions & 5 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DEFAULT_CONFIG_BASE = {
database: "sqlite",
orm: "drizzle",
auth: "better-auth",
payments: "none",
addons: ["turborepo"],
examples: [],
git: true,
Expand All @@ -39,8 +40,8 @@ export function getDefaultConfig() {
export const DEFAULT_CONFIG = getDefaultConfig();

export const dependencyVersionMap = {
"better-auth": "^1.3.9",
"@better-auth/expo": "^1.3.9",
"better-auth": "^1.3.10",
"@better-auth/expo": "^1.3.10",

"@clerk/nextjs": "^6.31.5",
"@clerk/clerk-react": "^5.45.0",
Expand Down Expand Up @@ -139,9 +140,9 @@ export const dependencyVersionMap = {
"@tanstack/react-query-devtools": "^5.85.5",
"@tanstack/react-query": "^5.85.5",

"@tanstack/solid-query": "^5.75.0",
"@tanstack/solid-query-devtools": "^5.75.0",
"@tanstack/solid-router-devtools": "^1.131.25",
"@tanstack/solid-query": "^5.87.4",
"@tanstack/solid-query-devtools": "^5.87.4",
"@tanstack/solid-router-devtools": "^1.131.44",

wrangler: "^4.23.0",
"@cloudflare/vite-plugin": "^1.9.0",
Expand All @@ -155,6 +156,9 @@ export const dependencyVersionMap = {
nitropack: "^2.12.4",

dotenv: "^17.2.1",

"@polar-sh/better-auth": "^1.1.3",
"@polar-sh/sdk": "^0.34.16",
} as const;

export type AvailableDependencies = keyof typeof dependencyVersionMap;
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/src/helpers/core/add-addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import path from "node:path";
import { log } from "@clack/prompts";
import pc from "picocolors";
import type { AddInput, Addons, ProjectConfig } from "../../types";
import { validateAddonCompatibility } from "../../utils/addon-compatibility";
import { updateBtsConfig } from "../../utils/bts-config";
import { validateAddonCompatibility } from "../../utils/compatibility-rules";
import { exitWithError } from "../../utils/errors";
import { setupAddons } from "../addons/addons-setup";
import {
Expand Down Expand Up @@ -45,6 +45,7 @@ export async function addAddonsToProject(
addons: input.addons,
examples: detectedConfig.examples || [],
auth: detectedConfig.auth || "none",
payments: detectedConfig.payments || "none",
git: false,
packageManager:
input.packageManager || detectedConfig.packageManager || "npm",
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/helpers/core/add-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export async function addDeploymentToProject(
addons: detectedConfig.addons || [],
examples: detectedConfig.examples || [],
auth: detectedConfig.auth || "none",
payments: detectedConfig.payments || "none",
git: false,
packageManager:
input.packageManager || detectedConfig.packageManager || "npm",
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/src/helpers/core/command-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export async function createProjectHandler(
addons: [],
examples: [],
auth: "none",
payments: "none",
git: false,
packageManager: "npm",
install: false,
Expand Down Expand Up @@ -272,6 +273,7 @@ export async function addAddonsHandler(input: AddInput) {
const addonsPrompt = await getAddonsToAdd(
detectedConfig.frontend || [],
detectedConfig.addons || [],
detectedConfig.auth,
);

if (addonsPrompt.length > 0) {
Expand Down
9 changes: 9 additions & 0 deletions apps/cli/src/helpers/core/create-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { createReadme } from "./create-readme";
import { setupEnvironmentVariables } from "./env-setup";
import { initializeGit } from "./git";
import { installDependencies } from "./install-dependencies";
import { setupPayments } from "./payments-setup";
import { displayPostInstallInstructions } from "./post-installation";
import { updatePackageConfigurations } from "./project-config";
import {
Expand All @@ -30,6 +31,7 @@ import {
setupDockerComposeTemplates,
setupExamplesTemplate,
setupFrontendTemplates,
setupPaymentsTemplate,
} from "./template-manager";

export async function createProject(
Expand All @@ -50,6 +52,9 @@ export async function createProject(
await setupDockerComposeTemplates(projectDir, options);
}
await setupAuthTemplate(projectDir, options);
if (options.payments && options.payments !== "none") {
await setupPaymentsTemplate(projectDir, options);
}
if (options.examples.length > 0 && options.examples[0] !== "none") {
await setupExamplesTemplate(projectDir, options);
}
Expand All @@ -76,6 +81,10 @@ export async function createProject(
await setupAuth(options);
}

if (options.payments && options.payments !== "none") {
await setupPayments(options);
}

await handleExtras(projectDir, options);

await setupEnvironmentVariables(options);
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/helpers/core/detect-project-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export async function detectProjectConfig(projectDir: string) {
addons: btsConfig.addons,
examples: btsConfig.examples,
auth: btsConfig.auth,
payments: btsConfig.payments,
packageManager: btsConfig.packageManager,
dbSetup: btsConfig.dbSetup,
api: btsConfig.api,
Expand Down
10 changes: 10 additions & 0 deletions apps/cli/src/helpers/core/env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,16 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
value: "",
condition: examples?.includes("ai") || false,
},
{
key: "POLAR_ACCESS_TOKEN",
value: "",
condition: config.payments === "polar",
},
{
key: "POLAR_SUCCESS_URL",
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
condition: config.payments === "polar",
},
];

await addEnvVariablesToFile(envPath, serverVars);
Expand Down
50 changes: 50 additions & 0 deletions apps/cli/src/helpers/core/payments-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import path from "node:path";
import fs from "fs-extra";
import type { ProjectConfig } from "../../types";
import { addPackageDependency } from "../../utils/add-package-deps";

export async function setupPayments(config: ProjectConfig) {
const { payments, projectDir, frontend } = config;

if (!payments || payments === "none") {
return;
}

const serverDir = path.join(projectDir, "apps/server");
const clientDir = path.join(projectDir, "apps/web");

const serverDirExists = await fs.pathExists(serverDir);
const clientDirExists = await fs.pathExists(clientDir);

if (!serverDirExists) {
return;
}

if (payments === "polar") {
await addPackageDependency({
dependencies: ["@polar-sh/better-auth", "@polar-sh/sdk"],
projectDir: serverDir,
});

if (clientDirExists) {
const hasWebFrontend = frontend.some((f) =>
[
"react-router",
"tanstack-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
].includes(f),
);

if (hasWebFrontend) {
await addPackageDependency({
dependencies: ["@polar-sh/better-auth"],
projectDir: clientDir,
});
}
}
}
}
9 changes: 9 additions & 0 deletions apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export async function displayPostInstallInstructions(
: "";
const clerkInstructions =
isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
const polarInstructions =
config.payments === "polar" && config.auth === "better-auth"
? getPolarInstructions()
: "";
const wranglerDeployInstructions = getWranglerDeployInstructions(
runCmd,
webDeploy,
Expand Down Expand Up @@ -188,6 +192,7 @@ export async function displayPostInstallInstructions(
output += `\n${alchemyDeployInstructions.trim()}\n`;
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;

if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
Expand Down Expand Up @@ -447,6 +452,10 @@ function getClerkInstructions() {
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
}

function getPolarInstructions() {
return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in apps/server/.env`;
}

function getAlchemyDeployInstructions(
runCmd?: string,
webDeploy?: string,
Expand Down
96 changes: 96 additions & 0 deletions apps/cli/src/helpers/core/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,102 @@ export async function setupAuthTemplate(
}
}

export async function setupPaymentsTemplate(
projectDir: string,
context: ProjectConfig,
) {
if (!context.payments || context.payments === "none") return;

const serverAppDir = path.join(projectDir, "apps/server");
const webAppDir = path.join(projectDir, "apps/web");

const serverAppDirExists = await fs.pathExists(serverAppDir);
const webAppDirExists = await fs.pathExists(webAppDir);

if (serverAppDirExists && context.backend !== "convex") {
const paymentsServerSrc = path.join(
PKG_ROOT,
`templates/payments/${context.payments}/server/base`,
);
if (await fs.pathExists(paymentsServerSrc)) {
await processAndCopyFiles(
"**/*",
paymentsServerSrc,
serverAppDir,
context,
);
}
}

const hasReactWeb = context.frontend.some((f) =>
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
);
const hasNuxtWeb = context.frontend.includes("nuxt");
const hasSvelteWeb = context.frontend.includes("svelte");
const hasSolidWeb = context.frontend.includes("solid");

if (
webAppDirExists &&
(hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb)
) {
if (hasReactWeb) {
const reactFramework = context.frontend.find((f) =>
["tanstack-router", "react-router", "tanstack-start", "next"].includes(
f,
),
);
if (reactFramework) {
const paymentsWebSrc = path.join(
PKG_ROOT,
`templates/payments/${context.payments}/web/react/${reactFramework}`,
);
if (await fs.pathExists(paymentsWebSrc)) {
await processAndCopyFiles("**/*", paymentsWebSrc, webAppDir, context);
}
}
} else if (hasNuxtWeb) {
const paymentsWebNuxtSrc = path.join(
PKG_ROOT,
`templates/payments/${context.payments}/web/nuxt`,
);
if (await fs.pathExists(paymentsWebNuxtSrc)) {
await processAndCopyFiles(
"**/*",
paymentsWebNuxtSrc,
webAppDir,
context,
);
}
} else if (hasSvelteWeb) {
const paymentsWebSvelteSrc = path.join(
PKG_ROOT,
`templates/payments/${context.payments}/web/svelte`,
);
if (await fs.pathExists(paymentsWebSvelteSrc)) {
await processAndCopyFiles(
"**/*",
paymentsWebSvelteSrc,
webAppDir,
context,
);
}
} else if (hasSolidWeb) {
const paymentsWebSolidSrc = path.join(
PKG_ROOT,
`templates/payments/${context.payments}/web/solid`,
);
if (await fs.pathExists(paymentsWebSolidSrc)) {
await processAndCopyFiles(
"**/*",
paymentsWebSolidSrc,
webAppDir,
context,
);
}
}
}
}

export async function setupAddonsTemplate(
projectDir: string,
context: ProjectConfig,
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
ORMSchema,
type PackageManager,
PackageManagerSchema,
PaymentsSchema,
type ProjectConfig,
ProjectNameSchema,
type Runtime,
Expand Down Expand Up @@ -80,6 +81,7 @@ export const router = t.router({
database: DatabaseSchema.optional(),
orm: ORMSchema.optional(),
auth: AuthSchema.optional(),
payments: PaymentsSchema.optional(),
frontend: z.array(FrontendSchema).optional(),
addons: z.array(AddonsSchema).optional(),
examples: z.array(ExamplesSchema).optional(),
Expand Down
13 changes: 10 additions & 3 deletions apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { groupMultiselect, isCancel } from "@clack/prompts";
import { DEFAULT_CONFIG } from "../constants";
import { type Addons, AddonsSchema, type Frontend } from "../types";
import { type Addons, AddonsSchema, type Auth, type Frontend } from "../types";
import {
getCompatibleAddons,
validateAddonCompatibility,
} from "../utils/addon-compatibility";
} from "../utils/compatibility-rules";
import { exitCancelled } from "../utils/errors";

type AddonOption = {
Expand Down Expand Up @@ -75,6 +75,7 @@ const ADDON_GROUPS = {
export async function getAddonsChoice(
addons?: Addons[],
frontends?: Frontend[],
auth?: Auth,
) {
if (addons !== undefined) return addons;

Expand All @@ -88,7 +89,11 @@ export async function getAddonsChoice(
const frontendsArray = frontends || [];

for (const addon of allAddons) {
const { isCompatible } = validateAddonCompatibility(addon, frontendsArray);
const { isCompatible } = validateAddonCompatibility(
addon,
frontendsArray,
auth,
);
if (!isCompatible) continue;

const { label, hint } = getAddonDisplay(addon);
Expand Down Expand Up @@ -131,6 +136,7 @@ export async function getAddonsChoice(
export async function getAddonsToAdd(
frontend: Frontend[],
existingAddons: Addons[] = [],
auth?: Auth,
) {
const groupedOptions: Record<string, AddonOption[]> = {
Documentation: [],
Expand All @@ -144,6 +150,7 @@ export async function getAddonsToAdd(
AddonsSchema.options.filter((addon) => addon !== "none"),
frontendArray,
existingAddons,
auth,
);

for (const addon of compatibleAddons) {
Expand Down
Loading