Skip to content

Commit ba3d62b

Browse files
feat(cli): add polar as better-auth plugin (#578)
1 parent 3f22373 commit ba3d62b

File tree

77 files changed

+1224
-311
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1224
-311
lines changed

apps/cli/src/constants.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const DEFAULT_CONFIG_BASE = {
1313
database: "sqlite",
1414
orm: "drizzle",
1515
auth: "better-auth",
16+
payments: "none",
1617
addons: ["turborepo"],
1718
examples: [],
1819
git: true,
@@ -39,8 +40,8 @@ export function getDefaultConfig() {
3940
export const DEFAULT_CONFIG = getDefaultConfig();
4041

4142
export const dependencyVersionMap = {
42-
"better-auth": "^1.3.9",
43-
"@better-auth/expo": "^1.3.9",
43+
"better-auth": "^1.3.10",
44+
"@better-auth/expo": "^1.3.10",
4445

4546
"@clerk/nextjs": "^6.31.5",
4647
"@clerk/clerk-react": "^5.45.0",
@@ -139,9 +140,9 @@ export const dependencyVersionMap = {
139140
"@tanstack/react-query-devtools": "^5.85.5",
140141
"@tanstack/react-query": "^5.85.5",
141142

142-
"@tanstack/solid-query": "^5.75.0",
143-
"@tanstack/solid-query-devtools": "^5.75.0",
144-
"@tanstack/solid-router-devtools": "^1.131.25",
143+
"@tanstack/solid-query": "^5.87.4",
144+
"@tanstack/solid-query-devtools": "^5.87.4",
145+
"@tanstack/solid-router-devtools": "^1.131.44",
145146

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

157158
dotenv: "^17.2.1",
159+
160+
"@polar-sh/better-auth": "^1.1.3",
161+
"@polar-sh/sdk": "^0.34.16",
158162
} as const;
159163

160164
export type AvailableDependencies = keyof typeof dependencyVersionMap;

apps/cli/src/helpers/core/add-addons.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import path from "node:path";
22
import { log } from "@clack/prompts";
33
import pc from "picocolors";
44
import type { AddInput, Addons, ProjectConfig } from "../../types";
5-
import { validateAddonCompatibility } from "../../utils/addon-compatibility";
65
import { updateBtsConfig } from "../../utils/bts-config";
6+
import { validateAddonCompatibility } from "../../utils/compatibility-rules";
77
import { exitWithError } from "../../utils/errors";
88
import { setupAddons } from "../addons/addons-setup";
99
import {
@@ -45,6 +45,7 @@ export async function addAddonsToProject(
4545
addons: input.addons,
4646
examples: detectedConfig.examples || [],
4747
auth: detectedConfig.auth || "none",
48+
payments: detectedConfig.payments || "none",
4849
git: false,
4950
packageManager:
5051
input.packageManager || detectedConfig.packageManager || "npm",

apps/cli/src/helpers/core/add-deployment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export async function addDeploymentToProject(
6969
addons: detectedConfig.addons || [],
7070
examples: detectedConfig.examples || [],
7171
auth: detectedConfig.auth || "none",
72+
payments: detectedConfig.payments || "none",
7273
git: false,
7374
packageManager:
7475
input.packageManager || detectedConfig.packageManager || "npm",

apps/cli/src/helpers/core/command-handlers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export async function createProjectHandler(
103103
addons: [],
104104
examples: [],
105105
auth: "none",
106+
payments: "none",
106107
git: false,
107108
packageManager: "npm",
108109
install: false,
@@ -272,6 +273,7 @@ export async function addAddonsHandler(input: AddInput) {
272273
const addonsPrompt = await getAddonsToAdd(
273274
detectedConfig.frontend || [],
274275
detectedConfig.addons || [],
276+
detectedConfig.auth,
275277
);
276278

277279
if (addonsPrompt.length > 0) {

apps/cli/src/helpers/core/create-project.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { createReadme } from "./create-readme";
1717
import { setupEnvironmentVariables } from "./env-setup";
1818
import { initializeGit } from "./git";
1919
import { installDependencies } from "./install-dependencies";
20+
import { setupPayments } from "./payments-setup";
2021
import { displayPostInstallInstructions } from "./post-installation";
2122
import { updatePackageConfigurations } from "./project-config";
2223
import {
@@ -30,6 +31,7 @@ import {
3031
setupDockerComposeTemplates,
3132
setupExamplesTemplate,
3233
setupFrontendTemplates,
34+
setupPaymentsTemplate,
3335
} from "./template-manager";
3436

3537
export async function createProject(
@@ -50,6 +52,9 @@ export async function createProject(
5052
await setupDockerComposeTemplates(projectDir, options);
5153
}
5254
await setupAuthTemplate(projectDir, options);
55+
if (options.payments && options.payments !== "none") {
56+
await setupPaymentsTemplate(projectDir, options);
57+
}
5358
if (options.examples.length > 0 && options.examples[0] !== "none") {
5459
await setupExamplesTemplate(projectDir, options);
5560
}
@@ -76,6 +81,10 @@ export async function createProject(
7681
await setupAuth(options);
7782
}
7883

84+
if (options.payments && options.payments !== "none") {
85+
await setupPayments(options);
86+
}
87+
7988
await handleExtras(projectDir, options);
8089

8190
await setupEnvironmentVariables(options);

apps/cli/src/helpers/core/detect-project-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export async function detectProjectConfig(projectDir: string) {
1717
addons: btsConfig.addons,
1818
examples: btsConfig.examples,
1919
auth: btsConfig.auth,
20+
payments: btsConfig.payments,
2021
packageManager: btsConfig.packageManager,
2122
dbSetup: btsConfig.dbSetup,
2223
api: btsConfig.api,

apps/cli/src/helpers/core/env-setup.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,16 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
280280
value: "",
281281
condition: examples?.includes("ai") || false,
282282
},
283+
{
284+
key: "POLAR_ACCESS_TOKEN",
285+
value: "",
286+
condition: config.payments === "polar",
287+
},
288+
{
289+
key: "POLAR_SUCCESS_URL",
290+
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
291+
condition: config.payments === "polar",
292+
},
283293
];
284294

285295
await addEnvVariablesToFile(envPath, serverVars);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import path from "node:path";
2+
import fs from "fs-extra";
3+
import type { ProjectConfig } from "../../types";
4+
import { addPackageDependency } from "../../utils/add-package-deps";
5+
6+
export async function setupPayments(config: ProjectConfig) {
7+
const { payments, projectDir, frontend } = config;
8+
9+
if (!payments || payments === "none") {
10+
return;
11+
}
12+
13+
const serverDir = path.join(projectDir, "apps/server");
14+
const clientDir = path.join(projectDir, "apps/web");
15+
16+
const serverDirExists = await fs.pathExists(serverDir);
17+
const clientDirExists = await fs.pathExists(clientDir);
18+
19+
if (!serverDirExists) {
20+
return;
21+
}
22+
23+
if (payments === "polar") {
24+
await addPackageDependency({
25+
dependencies: ["@polar-sh/better-auth", "@polar-sh/sdk"],
26+
projectDir: serverDir,
27+
});
28+
29+
if (clientDirExists) {
30+
const hasWebFrontend = frontend.some((f) =>
31+
[
32+
"react-router",
33+
"tanstack-router",
34+
"tanstack-start",
35+
"next",
36+
"nuxt",
37+
"svelte",
38+
"solid",
39+
].includes(f),
40+
);
41+
42+
if (hasWebFrontend) {
43+
await addPackageDependency({
44+
dependencies: ["@polar-sh/better-auth"],
45+
projectDir: clientDir,
46+
});
47+
}
48+
}
49+
}
50+
}

apps/cli/src/helpers/core/post-installation.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export async function displayPostInstallInstructions(
7272
: "";
7373
const clerkInstructions =
7474
isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
75+
const polarInstructions =
76+
config.payments === "polar" && config.auth === "better-auth"
77+
? getPolarInstructions()
78+
: "";
7579
const wranglerDeployInstructions = getWranglerDeployInstructions(
7680
runCmd,
7781
webDeploy,
@@ -188,6 +192,7 @@ export async function displayPostInstallInstructions(
188192
output += `\n${alchemyDeployInstructions.trim()}\n`;
189193
if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
190194
if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
195+
if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
191196

192197
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
193198
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
@@ -447,6 +452,10 @@ function getClerkInstructions() {
447452
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`;
448453
}
449454

455+
function getPolarInstructions() {
456+
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`;
457+
}
458+
450459
function getAlchemyDeployInstructions(
451460
runCmd?: string,
452461
webDeploy?: string,

apps/cli/src/helpers/core/template-manager.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,102 @@ export async function setupAuthTemplate(
607607
}
608608
}
609609

610+
export async function setupPaymentsTemplate(
611+
projectDir: string,
612+
context: ProjectConfig,
613+
) {
614+
if (!context.payments || context.payments === "none") return;
615+
616+
const serverAppDir = path.join(projectDir, "apps/server");
617+
const webAppDir = path.join(projectDir, "apps/web");
618+
619+
const serverAppDirExists = await fs.pathExists(serverAppDir);
620+
const webAppDirExists = await fs.pathExists(webAppDir);
621+
622+
if (serverAppDirExists && context.backend !== "convex") {
623+
const paymentsServerSrc = path.join(
624+
PKG_ROOT,
625+
`templates/payments/${context.payments}/server/base`,
626+
);
627+
if (await fs.pathExists(paymentsServerSrc)) {
628+
await processAndCopyFiles(
629+
"**/*",
630+
paymentsServerSrc,
631+
serverAppDir,
632+
context,
633+
);
634+
}
635+
}
636+
637+
const hasReactWeb = context.frontend.some((f) =>
638+
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
639+
);
640+
const hasNuxtWeb = context.frontend.includes("nuxt");
641+
const hasSvelteWeb = context.frontend.includes("svelte");
642+
const hasSolidWeb = context.frontend.includes("solid");
643+
644+
if (
645+
webAppDirExists &&
646+
(hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb)
647+
) {
648+
if (hasReactWeb) {
649+
const reactFramework = context.frontend.find((f) =>
650+
["tanstack-router", "react-router", "tanstack-start", "next"].includes(
651+
f,
652+
),
653+
);
654+
if (reactFramework) {
655+
const paymentsWebSrc = path.join(
656+
PKG_ROOT,
657+
`templates/payments/${context.payments}/web/react/${reactFramework}`,
658+
);
659+
if (await fs.pathExists(paymentsWebSrc)) {
660+
await processAndCopyFiles("**/*", paymentsWebSrc, webAppDir, context);
661+
}
662+
}
663+
} else if (hasNuxtWeb) {
664+
const paymentsWebNuxtSrc = path.join(
665+
PKG_ROOT,
666+
`templates/payments/${context.payments}/web/nuxt`,
667+
);
668+
if (await fs.pathExists(paymentsWebNuxtSrc)) {
669+
await processAndCopyFiles(
670+
"**/*",
671+
paymentsWebNuxtSrc,
672+
webAppDir,
673+
context,
674+
);
675+
}
676+
} else if (hasSvelteWeb) {
677+
const paymentsWebSvelteSrc = path.join(
678+
PKG_ROOT,
679+
`templates/payments/${context.payments}/web/svelte`,
680+
);
681+
if (await fs.pathExists(paymentsWebSvelteSrc)) {
682+
await processAndCopyFiles(
683+
"**/*",
684+
paymentsWebSvelteSrc,
685+
webAppDir,
686+
context,
687+
);
688+
}
689+
} else if (hasSolidWeb) {
690+
const paymentsWebSolidSrc = path.join(
691+
PKG_ROOT,
692+
`templates/payments/${context.payments}/web/solid`,
693+
);
694+
if (await fs.pathExists(paymentsWebSolidSrc)) {
695+
await processAndCopyFiles(
696+
"**/*",
697+
paymentsWebSolidSrc,
698+
webAppDir,
699+
context,
700+
);
701+
}
702+
}
703+
}
704+
}
705+
610706
export async function setupAddonsTemplate(
611707
projectDir: string,
612708
context: ProjectConfig,

0 commit comments

Comments
 (0)