create-vexcms 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2409 -0
- package/package.json +37 -0
- package/templates/base-nextjs/.prettierignore +26 -0
- package/templates/base-nextjs/.prettierrc +7 -0
- package/templates/base-nextjs/README.md +256 -0
- package/templates/base-nextjs/_gitignore +2 -0
- package/templates/base-nextjs/components.json +24 -0
- package/templates/base-nextjs/convex/_generated/api.d.ts +125 -0
- package/templates/base-nextjs/convex/_generated/api.js +23 -0
- package/templates/base-nextjs/convex/_generated/dataModel.d.ts +60 -0
- package/templates/base-nextjs/convex/_generated/server.d.ts +143 -0
- package/templates/base-nextjs/convex/_generated/server.js +93 -0
- package/templates/base-nextjs/convex/auth/adapter/index.ts +230 -0
- package/templates/base-nextjs/convex/auth/adapter/utils.ts +547 -0
- package/templates/base-nextjs/convex/auth/api.ts +8 -0
- package/templates/base-nextjs/convex/auth/config.ts +10 -0
- package/templates/base-nextjs/convex/auth/db.ts +303 -0
- package/templates/base-nextjs/convex/auth/index.ts +14 -0
- package/templates/base-nextjs/convex/auth/options.ts +63 -0
- package/templates/base-nextjs/convex/auth/plugins/index.ts +20 -0
- package/templates/base-nextjs/convex/auth/sessions.ts +60 -0
- package/templates/base-nextjs/convex/auth.config.ts +7 -0
- package/templates/base-nextjs/convex/convex.config.ts +5 -0
- package/templates/base-nextjs/convex/http.ts +28 -0
- package/templates/base-nextjs/convex/schema.ts +3 -0
- package/templates/base-nextjs/convex/vex/auth.ts +38 -0
- package/templates/base-nextjs/convex/vex/collections.ts +321 -0
- package/templates/base-nextjs/convex/vex/firstUser.ts +134 -0
- package/templates/base-nextjs/convex/vex/helpers.ts +33 -0
- package/templates/base-nextjs/convex/vex/impersonation.ts +51 -0
- package/templates/base-nextjs/convex/vex/media.ts +246 -0
- package/templates/base-nextjs/convex/vex/migrate.ts +74 -0
- package/templates/base-nextjs/convex/vex/model/collections.ts +196 -0
- package/templates/base-nextjs/convex/vex/model/media.ts +87 -0
- package/templates/base-nextjs/convex/vex/model/versions.ts +254 -0
- package/templates/base-nextjs/convex/vex/previewSnapshot.ts +53 -0
- package/templates/base-nextjs/convex/vex/versions.ts +588 -0
- package/templates/base-nextjs/eslint.config.mjs +164 -0
- package/templates/base-nextjs/next.config.ts +10 -0
- package/templates/base-nextjs/package.json +67 -0
- package/templates/base-nextjs/postcss.config.mjs +7 -0
- package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
- package/templates/base-nextjs/public/file.svg +1 -0
- package/templates/base-nextjs/public/globe.svg +1 -0
- package/templates/base-nextjs/public/next.svg +1 -0
- package/templates/base-nextjs/public/vercel.svg +1 -0
- package/templates/base-nextjs/public/window.svg +1 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/page.tsx +9 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +23 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/default.tsx +3 -0
- package/templates/base-nextjs/src/app/(frontend)/@auth/page.tsx +3 -0
- package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/page.tsx +9 -0
- package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +11 -0
- package/templates/base-nextjs/src/app/(frontend)/layout.tsx +14 -0
- package/templates/base-nextjs/src/app/(frontend)/page.tsx +116 -0
- package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +33 -0
- package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +38 -0
- package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +101 -0
- package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +15 -0
- package/templates/base-nextjs/src/app/admin/layout.tsx +33 -0
- package/templates/base-nextjs/src/app/api/auth/[...all]/route.ts +6 -0
- package/templates/base-nextjs/src/app/favicon.ico +0 -0
- package/templates/base-nextjs/src/app/globals.css +130 -0
- package/templates/base-nextjs/src/app/layout.tsx +30 -0
- package/templates/base-nextjs/src/auth/client.tsx +41 -0
- package/templates/base-nextjs/src/auth/permissions.ts +102 -0
- package/templates/base-nextjs/src/auth/server.ts +16 -0
- package/templates/base-nextjs/src/auth/serverUtils.ts +64 -0
- package/templates/base-nextjs/src/auth/types.ts +3 -0
- package/templates/base-nextjs/src/components/component-example.tsx +474 -0
- package/templates/base-nextjs/src/components/example.tsx +52 -0
- package/templates/base-nextjs/src/components/providers/client.tsx +9 -0
- package/templates/base-nextjs/src/components/providers/convex.tsx +32 -0
- package/templates/base-nextjs/src/components/providers/server.tsx +15 -0
- package/templates/base-nextjs/src/components/providers/theme.tsx +11 -0
- package/templates/base-nextjs/src/components/ui/alert-dialog.tsx +162 -0
- package/templates/base-nextjs/src/components/ui/badge.tsx +52 -0
- package/templates/base-nextjs/src/components/ui/button.tsx +60 -0
- package/templates/base-nextjs/src/components/ui/card.tsx +92 -0
- package/templates/base-nextjs/src/components/ui/combobox.tsx +271 -0
- package/templates/base-nextjs/src/components/ui/dialog.tsx +135 -0
- package/templates/base-nextjs/src/components/ui/dropdown-menu.tsx +246 -0
- package/templates/base-nextjs/src/components/ui/field.tsx +224 -0
- package/templates/base-nextjs/src/components/ui/input-group.tsx +146 -0
- package/templates/base-nextjs/src/components/ui/input.tsx +20 -0
- package/templates/base-nextjs/src/components/ui/label.tsx +20 -0
- package/templates/base-nextjs/src/components/ui/select.tsx +189 -0
- package/templates/base-nextjs/src/components/ui/separator.tsx +21 -0
- package/templates/base-nextjs/src/components/ui/textarea.tsx +18 -0
- package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +67 -0
- package/templates/base-nextjs/src/db/constants/auth.ts +8 -0
- package/templates/base-nextjs/src/db/constants/index.ts +44 -0
- package/templates/base-nextjs/src/db/types.ts +6 -0
- package/templates/base-nextjs/src/env.mjs +48 -0
- package/templates/base-nextjs/src/lib/utils.ts +6 -0
- package/templates/base-nextjs/src/proxy.ts +23 -0
- package/templates/base-nextjs/src/vexcms/access.ts +28 -0
- package/templates/base-nextjs/src/vexcms/auth.ts +4 -0
- package/templates/base-nextjs/src/vexcms/collections/index.ts +1 -0
- package/templates/base-nextjs/src/vexcms/collections/media.ts +15 -0
- package/templates/base-nextjs/src/vexcms/collections/users.ts +46 -0
- package/templates/base-nextjs/tsconfig.json +60 -0
- package/templates/base-nextjs/vex.config.ts +23 -0
- package/templates/marketing-site/convex/pages.ts +46 -0
- package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +58 -0
- package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +69 -0
- package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +71 -0
- package/templates/marketing-site/src/db/constants/index.ts +52 -0
- package/templates/marketing-site/src/vexcms/collections/footers.ts +28 -0
- package/templates/marketing-site/src/vexcms/collections/headers.ts +35 -0
- package/templates/marketing-site/src/vexcms/collections/index.ts +7 -0
- package/templates/marketing-site/src/vexcms/collections/pages.ts +40 -0
- package/templates/marketing-site/src/vexcms/collections/site_settings.ts +31 -0
- package/templates/marketing-site/src/vexcms/collections/themes.ts +37 -0
- package/templates/marketing-site/vex.config.ts +32 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2409 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk2 from "chalk";
|
|
6
|
+
import { input, select, confirm, checkbox } from "@inquirer/prompts";
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
import fs5 from "fs-extra";
|
|
9
|
+
|
|
10
|
+
// src/utils/validation.ts
|
|
11
|
+
import validateNpmPackageName from "validate-npm-package-name";
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
function validateProjectName(name) {
|
|
14
|
+
const result = validateNpmPackageName(name);
|
|
15
|
+
if (result.validForNewPackages) {
|
|
16
|
+
return { valid: true, errors: [] };
|
|
17
|
+
}
|
|
18
|
+
const errors = [];
|
|
19
|
+
if (result.errors) {
|
|
20
|
+
errors.push(...result.errors);
|
|
21
|
+
}
|
|
22
|
+
if (result.warnings) {
|
|
23
|
+
errors.push(...result.warnings);
|
|
24
|
+
}
|
|
25
|
+
return { valid: false, errors };
|
|
26
|
+
}
|
|
27
|
+
async function checkDirectoryExists(targetPath) {
|
|
28
|
+
return await fs.pathExists(targetPath);
|
|
29
|
+
}
|
|
30
|
+
async function isDirectoryEmpty(dirPath) {
|
|
31
|
+
try {
|
|
32
|
+
const files = await fs.readdir(dirPath);
|
|
33
|
+
const visibleFiles = files.filter((file) => !file.startsWith("."));
|
|
34
|
+
return visibleFiles.length === 0;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function resolveProjectName(input2, cwd) {
|
|
40
|
+
return input2;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/utils/messages.ts
|
|
44
|
+
import chalk from "chalk";
|
|
45
|
+
function displayInvalidNameError(name, errors) {
|
|
46
|
+
console.error();
|
|
47
|
+
console.error(chalk.red(`Error: Invalid project name '${name}'.`));
|
|
48
|
+
console.error();
|
|
49
|
+
if (errors.length > 0) {
|
|
50
|
+
console.error(chalk.yellow("Validation errors:"));
|
|
51
|
+
errors.forEach((error) => {
|
|
52
|
+
console.error(chalk.yellow(` - ${error}`));
|
|
53
|
+
});
|
|
54
|
+
console.error();
|
|
55
|
+
}
|
|
56
|
+
console.error(chalk.yellow("Project names must be valid npm package names."));
|
|
57
|
+
console.error(chalk.yellow("They should be lowercase, contain no spaces, and use hyphens for word separation."));
|
|
58
|
+
console.error();
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
function displayDirectoryNotEmptyError() {
|
|
62
|
+
console.error();
|
|
63
|
+
console.error(chalk.red("Error: Current directory is not empty."));
|
|
64
|
+
console.error(chalk.yellow("Please use a different directory or provide a project name."));
|
|
65
|
+
console.error();
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
function displaySuccessMessage(projectName, targetPath, isCurrentDir) {
|
|
69
|
+
console.log();
|
|
70
|
+
if (isCurrentDir) {
|
|
71
|
+
console.log(chalk.green(`\u2713 Successfully created project '${projectName}' in current directory`));
|
|
72
|
+
} else {
|
|
73
|
+
console.log(chalk.green(`\u2713 Successfully created project '${projectName}' at ${targetPath}`));
|
|
74
|
+
}
|
|
75
|
+
console.log();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/installers/base.ts
|
|
79
|
+
import fs3 from "fs-extra";
|
|
80
|
+
import { execa } from "execa";
|
|
81
|
+
import ora from "ora";
|
|
82
|
+
import crypto from "crypto";
|
|
83
|
+
import path from "path";
|
|
84
|
+
|
|
85
|
+
// src/helpers/fileOperations.ts
|
|
86
|
+
import fs2 from "fs-extra";
|
|
87
|
+
import { join, dirname } from "path";
|
|
88
|
+
import { fileURLToPath } from "url";
|
|
89
|
+
async function copyTemplate(framework, targetPath) {
|
|
90
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
91
|
+
const __dirname = dirname(__filename);
|
|
92
|
+
const templateDir = framework === "tanstack" ? "base-tanstack" : "base-nextjs";
|
|
93
|
+
let templatePath = join(__dirname, "../../templates", templateDir);
|
|
94
|
+
if (!fs2.existsSync(templatePath)) {
|
|
95
|
+
templatePath = join(__dirname, "../templates", templateDir);
|
|
96
|
+
}
|
|
97
|
+
await fs2.copy(templatePath, targetPath, {
|
|
98
|
+
overwrite: false,
|
|
99
|
+
errorOnExist: false,
|
|
100
|
+
filter: (src) => {
|
|
101
|
+
return !src.endsWith("_gitignore");
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const sourceGitignore = join(templatePath, "_gitignore");
|
|
105
|
+
const targetGitignore = join(targetPath, ".gitignore");
|
|
106
|
+
if (await fs2.pathExists(sourceGitignore)) {
|
|
107
|
+
await fs2.copy(sourceGitignore, targetGitignore, {
|
|
108
|
+
overwrite: false,
|
|
109
|
+
errorOnExist: false
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function overlayTemplate({ overlayDir, targetDir }) {
|
|
114
|
+
await fs2.copy(overlayDir, targetDir, { overwrite: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/installers/base.ts
|
|
118
|
+
var VexFrameworkInstaller = class {
|
|
119
|
+
constructor(targetPath, projectName) {
|
|
120
|
+
this.targetPath = targetPath;
|
|
121
|
+
this.projectName = projectName;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Copy base template files to target directory
|
|
125
|
+
*/
|
|
126
|
+
async copyBaseFiles() {
|
|
127
|
+
await copyTemplate(this.frameworkName, this.targetPath);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Apply a template overlay on top of the base template.
|
|
131
|
+
* Merges overlay files onto the already-copied base — existing files
|
|
132
|
+
* not in the overlay are left untouched.
|
|
133
|
+
*/
|
|
134
|
+
async applyTemplateOverlay(overlay) {
|
|
135
|
+
const { dirname: dirname2 } = await import("path");
|
|
136
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
137
|
+
const __filename = fileURLToPath2(import.meta.url);
|
|
138
|
+
const __dirname = dirname2(__filename);
|
|
139
|
+
let overlayDir = path.join(__dirname, "../../templates", overlay);
|
|
140
|
+
if (!await fs3.pathExists(overlayDir)) {
|
|
141
|
+
overlayDir = path.join(__dirname, "../templates", overlay);
|
|
142
|
+
}
|
|
143
|
+
await overlayTemplate({ overlayDir, targetDir: this.targetPath });
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Update the package.json name field and vex:update script
|
|
147
|
+
* to match the detected package manager.
|
|
148
|
+
*/
|
|
149
|
+
async updatePackageName(name) {
|
|
150
|
+
const pkgPath = path.join(this.targetPath, "package.json");
|
|
151
|
+
const pkg = await fs3.readJson(pkgPath);
|
|
152
|
+
pkg.name = name;
|
|
153
|
+
const pm = this.detectPackageManager();
|
|
154
|
+
if (pkg.scripts?.["vex:update"] && pm !== "pnpm") {
|
|
155
|
+
const addCmd = pm === "yarn" ? "yarn add" : pm === "bun" ? "bun add" : "npm install";
|
|
156
|
+
pkg.scripts["vex:update"] = pkg.scripts["vex:update"].replace(/^pnpm add/, addCmd);
|
|
157
|
+
}
|
|
158
|
+
await fs3.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Detect the package manager used to invoke the CLI
|
|
162
|
+
*/
|
|
163
|
+
detectPackageManager() {
|
|
164
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
165
|
+
if (userAgent) {
|
|
166
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
167
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
168
|
+
if (userAgent.includes("bun")) return "bun";
|
|
169
|
+
}
|
|
170
|
+
return "npm";
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Install project dependencies using detected package manager
|
|
174
|
+
*/
|
|
175
|
+
async installDependencies() {
|
|
176
|
+
const packageManager = this.detectPackageManager();
|
|
177
|
+
const spinner = ora(`Installing dependencies with ${packageManager}...`).start();
|
|
178
|
+
try {
|
|
179
|
+
await execa(packageManager, ["install"], {
|
|
180
|
+
cwd: this.targetPath,
|
|
181
|
+
stdio: "pipe"
|
|
182
|
+
});
|
|
183
|
+
spinner.succeed("Dependencies installed successfully");
|
|
184
|
+
} catch (error) {
|
|
185
|
+
spinner.fail("Failed to install dependencies");
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Dependency installation failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Format code using project's formatter
|
|
193
|
+
*/
|
|
194
|
+
async formatCode() {
|
|
195
|
+
const packageManager = this.detectPackageManager();
|
|
196
|
+
const spinner = ora("Formatting code...").start();
|
|
197
|
+
try {
|
|
198
|
+
const result = await execa(packageManager, ["run", "format"], {
|
|
199
|
+
cwd: this.targetPath,
|
|
200
|
+
stdio: "pipe",
|
|
201
|
+
reject: false
|
|
202
|
+
});
|
|
203
|
+
if (result.exitCode === 0) {
|
|
204
|
+
spinner.succeed("Code formatted successfully");
|
|
205
|
+
} else {
|
|
206
|
+
spinner.warn("Formatting completed with warnings");
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
210
|
+
spinner.warn(`Failed to format code: ${errorMessage}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Lint and fix code using project's ESLint configuration
|
|
215
|
+
*/
|
|
216
|
+
async lintCode() {
|
|
217
|
+
const packageManager = this.detectPackageManager();
|
|
218
|
+
const spinner = ora("Linting and fixing code...").start();
|
|
219
|
+
try {
|
|
220
|
+
const args2 = packageManager === "pnpm" ? ["lint", "--fix"] : ["run", "lint", "--", "--fix"];
|
|
221
|
+
const result = await execa(packageManager, args2, {
|
|
222
|
+
cwd: this.targetPath,
|
|
223
|
+
stdio: "pipe",
|
|
224
|
+
reject: false
|
|
225
|
+
});
|
|
226
|
+
if (result.exitCode === 0) {
|
|
227
|
+
spinner.succeed("Code linted and fixed successfully");
|
|
228
|
+
} else {
|
|
229
|
+
spinner.warn("Linting completed with warnings");
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
233
|
+
spinner.warn(`Failed to lint code: ${errorMessage}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Initialize Git repository in target directory
|
|
238
|
+
*/
|
|
239
|
+
async initGitRepo() {
|
|
240
|
+
const spinner = ora("Initializing Git repository...").start();
|
|
241
|
+
try {
|
|
242
|
+
try {
|
|
243
|
+
await execa("git", ["--version"], { stdio: "pipe" });
|
|
244
|
+
} catch {
|
|
245
|
+
spinner.fail("Git is not installed");
|
|
246
|
+
throw new Error("Git is not installed. Please install Git to initialize a repository.");
|
|
247
|
+
}
|
|
248
|
+
await execa("git", ["init"], { cwd: this.targetPath, stdio: "pipe" });
|
|
249
|
+
await execa("git", ["add", "."], { cwd: this.targetPath, stdio: "pipe" });
|
|
250
|
+
await execa(
|
|
251
|
+
"git",
|
|
252
|
+
["commit", "-m", "Initial commit from create-vexcms"],
|
|
253
|
+
{ cwd: this.targetPath, stdio: "pipe" }
|
|
254
|
+
);
|
|
255
|
+
spinner.succeed("Git repository initialized");
|
|
256
|
+
} catch (error) {
|
|
257
|
+
spinner.fail("Failed to initialize Git repository");
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Git initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Generate a secure random secret for Better Auth
|
|
265
|
+
*/
|
|
266
|
+
generateAuthSecret() {
|
|
267
|
+
return crypto.randomBytes(32).toString("hex");
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Write the generated auth secret to .env.example
|
|
271
|
+
*/
|
|
272
|
+
async writeAuthSecret() {
|
|
273
|
+
const envPath = path.join(this.targetPath, ".env.example");
|
|
274
|
+
const secret = this.generateAuthSecret();
|
|
275
|
+
if (await fs3.pathExists(envPath)) {
|
|
276
|
+
let content = await fs3.readFile(envPath, "utf-8");
|
|
277
|
+
content = content.replace(
|
|
278
|
+
/BETTER_AUTH_SECRET=.*/,
|
|
279
|
+
`BETTER_AUTH_SECRET=${secret}`
|
|
280
|
+
);
|
|
281
|
+
await fs3.writeFile(envPath, content);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Configure the Better Auth organizations plugin.
|
|
286
|
+
* When enabled, replaces placeholders with organization import and plugin.
|
|
287
|
+
* When disabled, removes the placeholder lines.
|
|
288
|
+
*/
|
|
289
|
+
async configureOrganizations(enabled) {
|
|
290
|
+
const pluginsPath = path.join(this.targetPath, "convex/auth/plugins/index.ts");
|
|
291
|
+
if (!await fs3.pathExists(pluginsPath)) return;
|
|
292
|
+
let content = await fs3.readFile(pluginsPath, "utf-8");
|
|
293
|
+
if (enabled) {
|
|
294
|
+
content = content.replace(
|
|
295
|
+
"// {{ORGANIZATIONS_IMPORT}}",
|
|
296
|
+
'import { organization } from "better-auth/plugins"'
|
|
297
|
+
);
|
|
298
|
+
content = content.replace(
|
|
299
|
+
" // {{ORGANIZATIONS_PLUGIN}}",
|
|
300
|
+
" organization(),"
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
content = content.replace(/.*\/\/ \{\{ORGANIZATIONS_IMPORT\}\}\n?/, "");
|
|
304
|
+
content = content.replace(/.*\/\/ \{\{ORGANIZATIONS_PLUGIN\}\}\n?/, "");
|
|
305
|
+
}
|
|
306
|
+
await fs3.writeFile(pluginsPath, content);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Main orchestration method for project initialization
|
|
310
|
+
*/
|
|
311
|
+
async initProject(options) {
|
|
312
|
+
const copySpinner = ora("Copying template files...").start();
|
|
313
|
+
try {
|
|
314
|
+
await this.copyBaseFiles();
|
|
315
|
+
copySpinner.succeed("Template files copied");
|
|
316
|
+
} catch (error) {
|
|
317
|
+
copySpinner.fail("Failed to copy template files");
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
if (!options.bare) {
|
|
321
|
+
const overlaySpinner = ora("Applying marketing site template...").start();
|
|
322
|
+
try {
|
|
323
|
+
await this.applyTemplateOverlay("marketing-site");
|
|
324
|
+
overlaySpinner.succeed("Marketing site template applied");
|
|
325
|
+
} catch (error) {
|
|
326
|
+
overlaySpinner.fail("Failed to apply template overlay");
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const nameSpinner = ora("Configuring project...").start();
|
|
331
|
+
try {
|
|
332
|
+
await this.updatePackageName(options.projectName);
|
|
333
|
+
nameSpinner.succeed("Project configured");
|
|
334
|
+
} catch (error) {
|
|
335
|
+
nameSpinner.fail("Failed to configure project");
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
const authSpinner = ora("Configuring authentication...").start();
|
|
339
|
+
try {
|
|
340
|
+
await this.updateOAuthConfig(options.oauthProviders, options.emailPasswordAuth);
|
|
341
|
+
authSpinner.succeed(
|
|
342
|
+
options.emailPasswordAuth || options.oauthProviders.length > 0 ? "Authentication configuration updated" : "Authentication placeholders cleaned up"
|
|
343
|
+
);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
authSpinner.fail("Failed to configure authentication");
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
const oauthUISpinner = ora("Configuring OAuth UI...").start();
|
|
349
|
+
try {
|
|
350
|
+
await this.updateOAuthUIConfig(options.oauthProviders, options.emailPasswordAuth);
|
|
351
|
+
oauthUISpinner.succeed(
|
|
352
|
+
options.oauthProviders.length > 0 ? "OAuth UI configuration updated" : "OAuth UI placeholders cleaned up"
|
|
353
|
+
);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
oauthUISpinner.fail("Failed to configure OAuth UI");
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
const envSpinner = ora("Updating .env.example...").start();
|
|
359
|
+
try {
|
|
360
|
+
await this.updateEnvExample(options.oauthProviders);
|
|
361
|
+
envSpinner.succeed(
|
|
362
|
+
options.oauthProviders.length > 0 ? ".env.example updated" : ".env.example placeholders cleaned up"
|
|
363
|
+
);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
envSpinner.fail("Failed to update .env.example");
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
const envTsSpinner = ora("Updating typed env configuration...").start();
|
|
369
|
+
try {
|
|
370
|
+
await this.updateEnvTs(options.oauthProviders);
|
|
371
|
+
envTsSpinner.succeed(
|
|
372
|
+
options.oauthProviders.length > 0 ? "Typed env configuration updated" : "Typed env placeholders cleaned up"
|
|
373
|
+
);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
envTsSpinner.fail("Failed to update typed env configuration");
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
const readmeSpinner = ora("Updating README...").start();
|
|
379
|
+
try {
|
|
380
|
+
await this.updateReadme(options.oauthProviders);
|
|
381
|
+
readmeSpinner.succeed(
|
|
382
|
+
options.oauthProviders.length > 0 ? "README updated" : "README placeholders cleaned up"
|
|
383
|
+
);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
readmeSpinner.fail("Failed to update README");
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
const orgsSpinner = ora("Configuring organizations...").start();
|
|
389
|
+
try {
|
|
390
|
+
await this.configureOrganizations(options.orgs);
|
|
391
|
+
orgsSpinner.succeed(
|
|
392
|
+
options.orgs ? "Organizations plugin enabled" : "Organizations placeholders cleaned up"
|
|
393
|
+
);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
orgsSpinner.fail("Failed to configure organizations");
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
const secretSpinner = ora("Generating auth secret...").start();
|
|
399
|
+
try {
|
|
400
|
+
await this.writeAuthSecret();
|
|
401
|
+
secretSpinner.succeed("Auth secret generated");
|
|
402
|
+
} catch (error) {
|
|
403
|
+
secretSpinner.fail("Failed to generate auth secret");
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
if (options.initGit) {
|
|
407
|
+
await this.initGitRepo();
|
|
408
|
+
}
|
|
409
|
+
if (options.installDependencies) {
|
|
410
|
+
await this.installDependencies();
|
|
411
|
+
await this.lintCode();
|
|
412
|
+
await this.formatCode();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/installers/nextjs.ts
|
|
418
|
+
import { join as join2 } from "path";
|
|
419
|
+
|
|
420
|
+
// src/installers/string-utils.ts
|
|
421
|
+
import fs4 from "fs-extra";
|
|
422
|
+
|
|
423
|
+
// src/installers/providers.ts
|
|
424
|
+
var OAUTH_PROVIDERS = {
|
|
425
|
+
// ========================================
|
|
426
|
+
// POPULAR PROVIDERS (10)
|
|
427
|
+
// ========================================
|
|
428
|
+
google: {
|
|
429
|
+
id: "google",
|
|
430
|
+
name: "Google",
|
|
431
|
+
envPrefix: "GOOGLE",
|
|
432
|
+
clientIdVar: "GOOGLE_CLIENT_ID",
|
|
433
|
+
clientSecretVar: "GOOGLE_CLIENT_SECRET",
|
|
434
|
+
popular: true,
|
|
435
|
+
betterAuthConfig: {
|
|
436
|
+
import: "",
|
|
437
|
+
clientSideProvider: '"google"',
|
|
438
|
+
socialProvider: `google({
|
|
439
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
440
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
441
|
+
})`,
|
|
442
|
+
scopes: []
|
|
443
|
+
},
|
|
444
|
+
env: [
|
|
445
|
+
{
|
|
446
|
+
name: "GOOGLE_CLIENT_ID",
|
|
447
|
+
type: "server",
|
|
448
|
+
description: "Google OAuth Client ID"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "GOOGLE_CLIENT_SECRET",
|
|
452
|
+
type: "server",
|
|
453
|
+
description: "Google OAuth Client Secret"
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
docs: {
|
|
457
|
+
provider: "https://console.cloud.google.com/apis/credentials",
|
|
458
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
459
|
+
},
|
|
460
|
+
requiresExtraConfig: false,
|
|
461
|
+
extraConfigNotes: "",
|
|
462
|
+
readme: {
|
|
463
|
+
title: "Google OAuth Setup",
|
|
464
|
+
content: `## Google OAuth Setup
|
|
465
|
+
|
|
466
|
+
1. Create OAuth credentials at https://console.cloud.google.com/apis/credentials
|
|
467
|
+
2. Set the Authorized redirect URI to: \`http://localhost:3000/api/auth/callback/google\` (update for production)
|
|
468
|
+
3. Copy the Client ID and Client Secret to your \`.env\` file
|
|
469
|
+
|
|
470
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
github: {
|
|
474
|
+
id: "github",
|
|
475
|
+
name: "GitHub",
|
|
476
|
+
envPrefix: "GITHUB",
|
|
477
|
+
clientIdVar: "GITHUB_CLIENT_ID",
|
|
478
|
+
clientSecretVar: "GITHUB_CLIENT_SECRET",
|
|
479
|
+
popular: true,
|
|
480
|
+
betterAuthConfig: {
|
|
481
|
+
import: "",
|
|
482
|
+
clientSideProvider: '"github"',
|
|
483
|
+
socialProvider: `github({
|
|
484
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
485
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
486
|
+
})`,
|
|
487
|
+
scopes: ["user:email"]
|
|
488
|
+
},
|
|
489
|
+
env: [
|
|
490
|
+
{
|
|
491
|
+
name: "GITHUB_CLIENT_ID",
|
|
492
|
+
type: "server",
|
|
493
|
+
description: "GitHub OAuth App Client ID"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: "GITHUB_CLIENT_SECRET",
|
|
497
|
+
type: "server",
|
|
498
|
+
description: "GitHub OAuth App Client Secret"
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
docs: {
|
|
502
|
+
provider: "https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app",
|
|
503
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
504
|
+
},
|
|
505
|
+
requiresExtraConfig: true,
|
|
506
|
+
extraConfigNotes: "You MUST include the user:email scope in your GitHub app. For GitHub Apps, enable Read-Only access to Email Addresses in Permissions.",
|
|
507
|
+
readme: {
|
|
508
|
+
title: "GitHub OAuth Setup",
|
|
509
|
+
content: `## GitHub OAuth Setup
|
|
510
|
+
|
|
511
|
+
1. Create a GitHub OAuth App at https://github.com/settings/developers
|
|
512
|
+
2. Set the Authorization callback URL to: \`http://localhost:3000/api/auth/callback/github\` (update for production)
|
|
513
|
+
3. Copy the Client ID and Client Secret to your \`.env\` file
|
|
514
|
+
4. **Important**: Include the \`user:email\` scope in your GitHub app permissions
|
|
515
|
+
|
|
516
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
discord: {
|
|
520
|
+
id: "discord",
|
|
521
|
+
name: "Discord",
|
|
522
|
+
envPrefix: "DISCORD",
|
|
523
|
+
clientIdVar: "DISCORD_CLIENT_ID",
|
|
524
|
+
clientSecretVar: "DISCORD_CLIENT_SECRET",
|
|
525
|
+
popular: true,
|
|
526
|
+
betterAuthConfig: {
|
|
527
|
+
import: "",
|
|
528
|
+
clientSideProvider: '"discord"',
|
|
529
|
+
socialProvider: `discord({
|
|
530
|
+
clientId: process.env.DISCORD_CLIENT_ID!,
|
|
531
|
+
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
532
|
+
})`,
|
|
533
|
+
scopes: []
|
|
534
|
+
},
|
|
535
|
+
env: [
|
|
536
|
+
{
|
|
537
|
+
name: "DISCORD_CLIENT_ID",
|
|
538
|
+
type: "server",
|
|
539
|
+
description: "Discord OAuth Application Client ID"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
name: "DISCORD_CLIENT_SECRET",
|
|
543
|
+
type: "server",
|
|
544
|
+
description: "Discord OAuth Application Client Secret"
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
docs: {
|
|
548
|
+
provider: "https://discord.com/developers/applications",
|
|
549
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
550
|
+
},
|
|
551
|
+
requiresExtraConfig: false,
|
|
552
|
+
extraConfigNotes: "You can optionally add a permissions field to request additional Discord permissions.",
|
|
553
|
+
readme: {
|
|
554
|
+
title: "Discord OAuth Setup",
|
|
555
|
+
content: `## Discord OAuth Setup
|
|
556
|
+
|
|
557
|
+
1. Create an application at https://discord.com/developers/applications
|
|
558
|
+
2. Add a redirect URL: \`http://localhost:3000/api/auth/callback/discord\` (update for production)
|
|
559
|
+
3. Copy the Client ID and Client Secret to your \`.env\` file
|
|
560
|
+
|
|
561
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
apple: {
|
|
565
|
+
id: "apple",
|
|
566
|
+
name: "Apple",
|
|
567
|
+
envPrefix: "APPLE",
|
|
568
|
+
clientIdVar: "APPLE_CLIENT_ID",
|
|
569
|
+
clientSecretVar: "APPLE_CLIENT_SECRET",
|
|
570
|
+
popular: true,
|
|
571
|
+
betterAuthConfig: {
|
|
572
|
+
import: "",
|
|
573
|
+
clientSideProvider: '"apple"',
|
|
574
|
+
socialProvider: `apple({
|
|
575
|
+
clientId: process.env.APPLE_CLIENT_ID!,
|
|
576
|
+
clientSecret: process.env.APPLE_CLIENT_SECRET!,
|
|
577
|
+
})`,
|
|
578
|
+
scopes: []
|
|
579
|
+
},
|
|
580
|
+
env: [
|
|
581
|
+
{
|
|
582
|
+
name: "APPLE_CLIENT_ID",
|
|
583
|
+
type: "server",
|
|
584
|
+
description: "Apple Sign In Service ID"
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: "APPLE_CLIENT_SECRET",
|
|
588
|
+
type: "server",
|
|
589
|
+
description: "Apple Sign In Client Secret (JWT token)"
|
|
590
|
+
}
|
|
591
|
+
],
|
|
592
|
+
docs: {
|
|
593
|
+
provider: "https://developer.apple.com/sign-in-with-apple/get-started/",
|
|
594
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
595
|
+
},
|
|
596
|
+
requiresExtraConfig: true,
|
|
597
|
+
extraConfigNotes: "Apple requires additional configuration: clientId is your Service ID, clientSecret must be a JWT token generated from your Team ID, Key ID, and Private Key. You may also need to provide appBundleIdentifier for iOS apps.",
|
|
598
|
+
readme: {
|
|
599
|
+
title: "Apple Sign In Setup",
|
|
600
|
+
content: `## Apple Sign In Setup
|
|
601
|
+
|
|
602
|
+
1. Create a Sign In with Apple service at https://developer.apple.com
|
|
603
|
+
2. Configure your Service ID and return URLs
|
|
604
|
+
3. Generate a client secret JWT using your private key, Team ID, and Key ID
|
|
605
|
+
4. Add credentials to your \`.env\` file
|
|
606
|
+
|
|
607
|
+
**Note**: The clientSecret must be a JWT token generated from your Apple credentials, not a standard client secret.
|
|
608
|
+
|
|
609
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
microsoft: {
|
|
613
|
+
id: "microsoft",
|
|
614
|
+
name: "Microsoft",
|
|
615
|
+
envPrefix: "MICROSOFT",
|
|
616
|
+
clientIdVar: "MICROSOFT_CLIENT_ID",
|
|
617
|
+
clientSecretVar: "MICROSOFT_CLIENT_SECRET",
|
|
618
|
+
popular: true,
|
|
619
|
+
betterAuthConfig: {
|
|
620
|
+
import: "",
|
|
621
|
+
clientSideProvider: '"microsoft"',
|
|
622
|
+
socialProvider: `microsoft({
|
|
623
|
+
clientId: process.env.MICROSOFT_CLIENT_ID!,
|
|
624
|
+
clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
|
|
625
|
+
tenantId: process.env.MICROSOFT_TENANT_ID,
|
|
626
|
+
})`,
|
|
627
|
+
scopes: []
|
|
628
|
+
},
|
|
629
|
+
env: [
|
|
630
|
+
{
|
|
631
|
+
name: "MICROSOFT_CLIENT_ID",
|
|
632
|
+
type: "server",
|
|
633
|
+
description: "Microsoft Entra ID Application (client) ID"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: "MICROSOFT_CLIENT_SECRET",
|
|
637
|
+
type: "server",
|
|
638
|
+
description: "Microsoft Entra ID Client Secret"
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: "MICROSOFT_TENANT_ID",
|
|
642
|
+
type: "server",
|
|
643
|
+
description: 'Microsoft Entra ID Tenant ID (optional, defaults to "common")'
|
|
644
|
+
}
|
|
645
|
+
],
|
|
646
|
+
docs: {
|
|
647
|
+
provider: "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps",
|
|
648
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
649
|
+
},
|
|
650
|
+
requiresExtraConfig: true,
|
|
651
|
+
extraConfigNotes: 'Optional fields: tenantId (defaults to "common"), authority (custom authority URL), prompt (consent behavior).',
|
|
652
|
+
readme: {
|
|
653
|
+
title: "Microsoft Entra ID OAuth Setup",
|
|
654
|
+
content: `## Microsoft Entra ID OAuth Setup
|
|
655
|
+
|
|
656
|
+
1. Register an application at https://portal.azure.com
|
|
657
|
+
2. Add a redirect URI: \`http://localhost:3000/api/auth/callback/microsoft\` (update for production)
|
|
658
|
+
3. Create a client secret in "Certificates & secrets"
|
|
659
|
+
4. Copy the Application (client) ID and Client Secret to your \`.env\` file
|
|
660
|
+
5. (Optional) Copy the Directory (tenant) ID if you want to restrict to a specific tenant
|
|
661
|
+
|
|
662
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
facebook: {
|
|
666
|
+
id: "facebook",
|
|
667
|
+
name: "Facebook",
|
|
668
|
+
envPrefix: "FACEBOOK",
|
|
669
|
+
clientIdVar: "FACEBOOK_CLIENT_ID",
|
|
670
|
+
clientSecretVar: "FACEBOOK_CLIENT_SECRET",
|
|
671
|
+
popular: true,
|
|
672
|
+
betterAuthConfig: {
|
|
673
|
+
import: "",
|
|
674
|
+
clientSideProvider: '"facebook"',
|
|
675
|
+
socialProvider: `facebook({
|
|
676
|
+
clientId: process.env.FACEBOOK_CLIENT_ID!,
|
|
677
|
+
clientSecret: process.env.FACEBOOK_CLIENT_SECRET!,
|
|
678
|
+
})`,
|
|
679
|
+
scopes: []
|
|
680
|
+
},
|
|
681
|
+
env: [
|
|
682
|
+
{
|
|
683
|
+
name: "FACEBOOK_CLIENT_ID",
|
|
684
|
+
type: "server",
|
|
685
|
+
description: "Facebook App ID"
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: "FACEBOOK_CLIENT_SECRET",
|
|
689
|
+
type: "server",
|
|
690
|
+
description: "Facebook App Secret"
|
|
691
|
+
}
|
|
692
|
+
],
|
|
693
|
+
docs: {
|
|
694
|
+
provider: "https://developers.facebook.com/apps/",
|
|
695
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
696
|
+
},
|
|
697
|
+
requiresExtraConfig: false,
|
|
698
|
+
extraConfigNotes: "Facebook supports custom scopes and fields arrays to request additional user data.",
|
|
699
|
+
readme: {
|
|
700
|
+
title: "Facebook OAuth Setup",
|
|
701
|
+
content: `## Facebook OAuth Setup
|
|
702
|
+
|
|
703
|
+
1. Create an app at https://developers.facebook.com/apps/
|
|
704
|
+
2. Add Facebook Login product to your app
|
|
705
|
+
3. Add OAuth redirect URI: \`http://localhost:3000/api/auth/callback/facebook\` (update for production)
|
|
706
|
+
4. Copy the App ID and App Secret to your \`.env\` file
|
|
707
|
+
|
|
708
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
twitter: {
|
|
712
|
+
id: "twitter",
|
|
713
|
+
name: "Twitter/X",
|
|
714
|
+
envPrefix: "TWITTER",
|
|
715
|
+
clientIdVar: "TWITTER_CLIENT_ID",
|
|
716
|
+
clientSecretVar: "TWITTER_CLIENT_SECRET",
|
|
717
|
+
popular: true,
|
|
718
|
+
betterAuthConfig: {
|
|
719
|
+
import: "",
|
|
720
|
+
clientSideProvider: '"twitter"',
|
|
721
|
+
socialProvider: `twitter({
|
|
722
|
+
clientId: process.env.TWITTER_CLIENT_ID!,
|
|
723
|
+
clientSecret: process.env.TWITTER_CLIENT_SECRET!,
|
|
724
|
+
})`,
|
|
725
|
+
scopes: []
|
|
726
|
+
},
|
|
727
|
+
env: [
|
|
728
|
+
{
|
|
729
|
+
name: "TWITTER_CLIENT_ID",
|
|
730
|
+
type: "server",
|
|
731
|
+
description: "Twitter/X OAuth 2.0 Client ID"
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
name: "TWITTER_CLIENT_SECRET",
|
|
735
|
+
type: "server",
|
|
736
|
+
description: "Twitter/X OAuth 2.0 Client Secret"
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
docs: {
|
|
740
|
+
provider: "https://developer.twitter.com/en/portal/projects-and-apps",
|
|
741
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
742
|
+
},
|
|
743
|
+
requiresExtraConfig: false,
|
|
744
|
+
extraConfigNotes: "",
|
|
745
|
+
readme: {
|
|
746
|
+
title: "Twitter/X OAuth Setup",
|
|
747
|
+
content: `## Twitter/X OAuth Setup
|
|
748
|
+
|
|
749
|
+
1. Create an app at https://developer.twitter.com/en/portal/projects-and-apps
|
|
750
|
+
2. Enable OAuth 2.0 authentication
|
|
751
|
+
3. Add callback URL: \`http://localhost:3000/api/auth/callback/twitter\` (update for production)
|
|
752
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
753
|
+
|
|
754
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
linkedin: {
|
|
758
|
+
id: "linkedin",
|
|
759
|
+
name: "LinkedIn",
|
|
760
|
+
envPrefix: "LINKEDIN",
|
|
761
|
+
clientIdVar: "LINKEDIN_CLIENT_ID",
|
|
762
|
+
clientSecretVar: "LINKEDIN_CLIENT_SECRET",
|
|
763
|
+
popular: true,
|
|
764
|
+
betterAuthConfig: {
|
|
765
|
+
import: "",
|
|
766
|
+
clientSideProvider: '"linkedin"',
|
|
767
|
+
socialProvider: `linkedin({
|
|
768
|
+
clientId: process.env.LINKEDIN_CLIENT_ID!,
|
|
769
|
+
clientSecret: process.env.LINKEDIN_CLIENT_SECRET!,
|
|
770
|
+
})`,
|
|
771
|
+
scopes: []
|
|
772
|
+
},
|
|
773
|
+
env: [
|
|
774
|
+
{
|
|
775
|
+
name: "LINKEDIN_CLIENT_ID",
|
|
776
|
+
type: "server",
|
|
777
|
+
description: "LinkedIn OAuth Client ID"
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: "LINKEDIN_CLIENT_SECRET",
|
|
781
|
+
type: "server",
|
|
782
|
+
description: "LinkedIn OAuth Client Secret"
|
|
783
|
+
}
|
|
784
|
+
],
|
|
785
|
+
docs: {
|
|
786
|
+
provider: "https://www.linkedin.com/developers/apps",
|
|
787
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
788
|
+
},
|
|
789
|
+
requiresExtraConfig: false,
|
|
790
|
+
extraConfigNotes: "",
|
|
791
|
+
readme: {
|
|
792
|
+
title: "LinkedIn OAuth Setup",
|
|
793
|
+
content: `## LinkedIn OAuth Setup
|
|
794
|
+
|
|
795
|
+
1. Create an app at https://www.linkedin.com/developers/apps
|
|
796
|
+
2. Add redirect URL: \`http://localhost:3000/api/auth/callback/linkedin\` (update for production)
|
|
797
|
+
3. Request access to Sign In with LinkedIn
|
|
798
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
799
|
+
|
|
800
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
twitch: {
|
|
804
|
+
id: "twitch",
|
|
805
|
+
name: "Twitch",
|
|
806
|
+
envPrefix: "TWITCH",
|
|
807
|
+
clientIdVar: "TWITCH_CLIENT_ID",
|
|
808
|
+
clientSecretVar: "TWITCH_CLIENT_SECRET",
|
|
809
|
+
popular: true,
|
|
810
|
+
betterAuthConfig: {
|
|
811
|
+
import: "",
|
|
812
|
+
clientSideProvider: '"twitch"',
|
|
813
|
+
socialProvider: `twitch({
|
|
814
|
+
clientId: process.env.TWITCH_CLIENT_ID!,
|
|
815
|
+
clientSecret: process.env.TWITCH_CLIENT_SECRET!,
|
|
816
|
+
})`,
|
|
817
|
+
scopes: []
|
|
818
|
+
},
|
|
819
|
+
env: [
|
|
820
|
+
{
|
|
821
|
+
name: "TWITCH_CLIENT_ID",
|
|
822
|
+
type: "server",
|
|
823
|
+
description: "Twitch Application Client ID"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
name: "TWITCH_CLIENT_SECRET",
|
|
827
|
+
type: "server",
|
|
828
|
+
description: "Twitch Application Client Secret"
|
|
829
|
+
}
|
|
830
|
+
],
|
|
831
|
+
docs: {
|
|
832
|
+
provider: "https://dev.twitch.tv/console/apps",
|
|
833
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
834
|
+
},
|
|
835
|
+
requiresExtraConfig: false,
|
|
836
|
+
extraConfigNotes: "",
|
|
837
|
+
readme: {
|
|
838
|
+
title: "Twitch OAuth Setup",
|
|
839
|
+
content: `## Twitch OAuth Setup
|
|
840
|
+
|
|
841
|
+
1. Register an application at https://dev.twitch.tv/console/apps
|
|
842
|
+
2. Set OAuth Redirect URL to: \`http://localhost:3000/api/auth/callback/twitch\` (update for production)
|
|
843
|
+
3. Copy the Client ID and generate a Client Secret
|
|
844
|
+
4. Add credentials to your \`.env\` file
|
|
845
|
+
|
|
846
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
spotify: {
|
|
850
|
+
id: "spotify",
|
|
851
|
+
name: "Spotify",
|
|
852
|
+
envPrefix: "SPOTIFY",
|
|
853
|
+
clientIdVar: "SPOTIFY_CLIENT_ID",
|
|
854
|
+
clientSecretVar: "SPOTIFY_CLIENT_SECRET",
|
|
855
|
+
popular: true,
|
|
856
|
+
betterAuthConfig: {
|
|
857
|
+
import: "",
|
|
858
|
+
clientSideProvider: '"spotify"',
|
|
859
|
+
socialProvider: `spotify({
|
|
860
|
+
clientId: process.env.SPOTIFY_CLIENT_ID!,
|
|
861
|
+
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
|
|
862
|
+
})`,
|
|
863
|
+
scopes: []
|
|
864
|
+
},
|
|
865
|
+
env: [
|
|
866
|
+
{
|
|
867
|
+
name: "SPOTIFY_CLIENT_ID",
|
|
868
|
+
type: "server",
|
|
869
|
+
description: "Spotify App Client ID"
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
name: "SPOTIFY_CLIENT_SECRET",
|
|
873
|
+
type: "server",
|
|
874
|
+
description: "Spotify App Client Secret"
|
|
875
|
+
}
|
|
876
|
+
],
|
|
877
|
+
docs: {
|
|
878
|
+
provider: "https://developer.spotify.com/dashboard/applications",
|
|
879
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
880
|
+
},
|
|
881
|
+
requiresExtraConfig: false,
|
|
882
|
+
extraConfigNotes: "",
|
|
883
|
+
readme: {
|
|
884
|
+
title: "Spotify OAuth Setup",
|
|
885
|
+
content: `## Spotify OAuth Setup
|
|
886
|
+
|
|
887
|
+
1. Create an app at https://developer.spotify.com/dashboard/applications
|
|
888
|
+
2. Add redirect URI: \`http://localhost:3000/api/auth/callback/spotify\` (update for production)
|
|
889
|
+
3. Copy the Client ID and Client Secret to your \`.env\` file
|
|
890
|
+
|
|
891
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
// ========================================
|
|
895
|
+
// ADDITIONAL PROVIDERS (23)
|
|
896
|
+
// ========================================
|
|
897
|
+
atlassian: {
|
|
898
|
+
id: "atlassian",
|
|
899
|
+
name: "Atlassian",
|
|
900
|
+
envPrefix: "ATLASSIAN",
|
|
901
|
+
clientIdVar: "ATLASSIAN_CLIENT_ID",
|
|
902
|
+
clientSecretVar: "ATLASSIAN_CLIENT_SECRET",
|
|
903
|
+
popular: false,
|
|
904
|
+
betterAuthConfig: {
|
|
905
|
+
import: "",
|
|
906
|
+
clientSideProvider: '"atlassian"',
|
|
907
|
+
socialProvider: `atlassian({
|
|
908
|
+
clientId: process.env.ATLASSIAN_CLIENT_ID!,
|
|
909
|
+
clientSecret: process.env.ATLASSIAN_CLIENT_SECRET!,
|
|
910
|
+
})`,
|
|
911
|
+
scopes: []
|
|
912
|
+
},
|
|
913
|
+
env: [
|
|
914
|
+
{
|
|
915
|
+
name: "ATLASSIAN_CLIENT_ID",
|
|
916
|
+
type: "server",
|
|
917
|
+
description: "Atlassian OAuth 2.0 Client ID"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "ATLASSIAN_CLIENT_SECRET",
|
|
921
|
+
type: "server",
|
|
922
|
+
description: "Atlassian OAuth 2.0 Client Secret"
|
|
923
|
+
}
|
|
924
|
+
],
|
|
925
|
+
docs: {
|
|
926
|
+
provider: "https://developer.atlassian.com/console/myapps/",
|
|
927
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
928
|
+
},
|
|
929
|
+
requiresExtraConfig: false,
|
|
930
|
+
extraConfigNotes: "Default scopes include read:jira-user, read:jira-work, and offline_access.",
|
|
931
|
+
readme: {
|
|
932
|
+
title: "Atlassian OAuth Setup",
|
|
933
|
+
content: `## Atlassian OAuth Setup
|
|
934
|
+
|
|
935
|
+
1. Create an app at https://developer.atlassian.com/console/myapps/
|
|
936
|
+
2. Configure OAuth 2.0 integration
|
|
937
|
+
3. Add callback URL: \`http://localhost:3000/api/auth/callback/atlassian\` (update for production)
|
|
938
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
939
|
+
|
|
940
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
cognito: {
|
|
944
|
+
id: "cognito",
|
|
945
|
+
name: "AWS Cognito",
|
|
946
|
+
envPrefix: "COGNITO",
|
|
947
|
+
clientIdVar: "COGNITO_CLIENT_ID",
|
|
948
|
+
clientSecretVar: "COGNITO_CLIENT_SECRET",
|
|
949
|
+
popular: false,
|
|
950
|
+
betterAuthConfig: {
|
|
951
|
+
import: "",
|
|
952
|
+
clientSideProvider: '"cognito"',
|
|
953
|
+
socialProvider: `cognito({
|
|
954
|
+
clientId: process.env.COGNITO_CLIENT_ID!,
|
|
955
|
+
clientSecret: process.env.COGNITO_CLIENT_SECRET!,
|
|
956
|
+
domain: process.env.COGNITO_DOMAIN!,
|
|
957
|
+
region: process.env.COGNITO_REGION!,
|
|
958
|
+
userPoolId: process.env.COGNITO_USER_POOL_ID!,
|
|
959
|
+
})`,
|
|
960
|
+
scopes: []
|
|
961
|
+
},
|
|
962
|
+
env: [
|
|
963
|
+
{
|
|
964
|
+
name: "COGNITO_CLIENT_ID",
|
|
965
|
+
type: "server",
|
|
966
|
+
description: "AWS Cognito App Client ID"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
name: "COGNITO_CLIENT_SECRET",
|
|
970
|
+
type: "server",
|
|
971
|
+
description: "AWS Cognito App Client Secret"
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "COGNITO_DOMAIN",
|
|
975
|
+
type: "server",
|
|
976
|
+
description: "AWS Cognito domain (e.g., your-domain.auth.us-east-1.amazoncognito.com)"
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
name: "COGNITO_REGION",
|
|
980
|
+
type: "server",
|
|
981
|
+
description: "AWS region (e.g., us-east-1)"
|
|
982
|
+
},
|
|
983
|
+
{
|
|
984
|
+
name: "COGNITO_USER_POOL_ID",
|
|
985
|
+
type: "server",
|
|
986
|
+
description: "AWS Cognito User Pool ID"
|
|
987
|
+
}
|
|
988
|
+
],
|
|
989
|
+
docs: {
|
|
990
|
+
provider: "https://console.aws.amazon.com/cognito/",
|
|
991
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
992
|
+
},
|
|
993
|
+
requiresExtraConfig: true,
|
|
994
|
+
extraConfigNotes: "AWS Cognito requires domain, region, and userPoolId in addition to clientId and clientSecret.",
|
|
995
|
+
readme: {
|
|
996
|
+
title: "AWS Cognito OAuth Setup",
|
|
997
|
+
content: `## AWS Cognito OAuth Setup
|
|
998
|
+
|
|
999
|
+
1. Create a User Pool at https://console.aws.amazon.com/cognito/
|
|
1000
|
+
2. Configure an App Client with OAuth 2.0 flows
|
|
1001
|
+
3. Set up a Cognito domain for your user pool
|
|
1002
|
+
4. Add callback URL: \`http://localhost:3000/api/auth/callback/cognito\` (update for production)
|
|
1003
|
+
5. Copy the following to your \`.env\` file:
|
|
1004
|
+
- App Client ID
|
|
1005
|
+
- App Client Secret
|
|
1006
|
+
- Cognito Domain
|
|
1007
|
+
- AWS Region
|
|
1008
|
+
- User Pool ID
|
|
1009
|
+
|
|
1010
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1011
|
+
}
|
|
1012
|
+
},
|
|
1013
|
+
dropbox: {
|
|
1014
|
+
id: "dropbox",
|
|
1015
|
+
name: "Dropbox",
|
|
1016
|
+
envPrefix: "DROPBOX",
|
|
1017
|
+
clientIdVar: "DROPBOX_CLIENT_ID",
|
|
1018
|
+
clientSecretVar: "DROPBOX_CLIENT_SECRET",
|
|
1019
|
+
popular: false,
|
|
1020
|
+
betterAuthConfig: {
|
|
1021
|
+
import: "",
|
|
1022
|
+
clientSideProvider: '"dropbox"',
|
|
1023
|
+
socialProvider: `dropbox({
|
|
1024
|
+
clientId: process.env.DROPBOX_CLIENT_ID!,
|
|
1025
|
+
clientSecret: process.env.DROPBOX_CLIENT_SECRET!,
|
|
1026
|
+
})`,
|
|
1027
|
+
scopes: []
|
|
1028
|
+
},
|
|
1029
|
+
env: [
|
|
1030
|
+
{
|
|
1031
|
+
name: "DROPBOX_CLIENT_ID",
|
|
1032
|
+
type: "server",
|
|
1033
|
+
description: "Dropbox App Key"
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
name: "DROPBOX_CLIENT_SECRET",
|
|
1037
|
+
type: "server",
|
|
1038
|
+
description: "Dropbox App Secret"
|
|
1039
|
+
}
|
|
1040
|
+
],
|
|
1041
|
+
docs: {
|
|
1042
|
+
provider: "https://www.dropbox.com/developers/apps",
|
|
1043
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1044
|
+
},
|
|
1045
|
+
requiresExtraConfig: false,
|
|
1046
|
+
extraConfigNotes: "",
|
|
1047
|
+
readme: {
|
|
1048
|
+
title: "Dropbox OAuth Setup",
|
|
1049
|
+
content: `## Dropbox OAuth Setup
|
|
1050
|
+
|
|
1051
|
+
1. Create an app at https://www.dropbox.com/developers/apps
|
|
1052
|
+
2. Choose OAuth 2 settings
|
|
1053
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/dropbox\` (update for production)
|
|
1054
|
+
4. Copy the App Key and App Secret to your \`.env\` file
|
|
1055
|
+
|
|
1056
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
figma: {
|
|
1060
|
+
id: "figma",
|
|
1061
|
+
name: "Figma",
|
|
1062
|
+
envPrefix: "FIGMA",
|
|
1063
|
+
clientIdVar: "FIGMA_CLIENT_ID",
|
|
1064
|
+
clientSecretVar: "FIGMA_CLIENT_SECRET",
|
|
1065
|
+
popular: false,
|
|
1066
|
+
betterAuthConfig: {
|
|
1067
|
+
import: "",
|
|
1068
|
+
clientSideProvider: '"figma"',
|
|
1069
|
+
socialProvider: `figma({
|
|
1070
|
+
clientId: process.env.FIGMA_CLIENT_ID!,
|
|
1071
|
+
clientSecret: process.env.FIGMA_CLIENT_SECRET!,
|
|
1072
|
+
clientKey: process.env.FIGMA_CLIENT_KEY!,
|
|
1073
|
+
})`,
|
|
1074
|
+
scopes: []
|
|
1075
|
+
},
|
|
1076
|
+
env: [
|
|
1077
|
+
{
|
|
1078
|
+
name: "FIGMA_CLIENT_ID",
|
|
1079
|
+
type: "server",
|
|
1080
|
+
description: "Figma OAuth Client ID"
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
name: "FIGMA_CLIENT_SECRET",
|
|
1084
|
+
type: "server",
|
|
1085
|
+
description: "Figma OAuth Client Secret"
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
name: "FIGMA_CLIENT_KEY",
|
|
1089
|
+
type: "server",
|
|
1090
|
+
description: "Figma OAuth Client Key"
|
|
1091
|
+
}
|
|
1092
|
+
],
|
|
1093
|
+
docs: {
|
|
1094
|
+
provider: "https://www.figma.com/developers/apps",
|
|
1095
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1096
|
+
},
|
|
1097
|
+
requiresExtraConfig: true,
|
|
1098
|
+
extraConfigNotes: "Figma requires a clientKey in addition to clientId and clientSecret.",
|
|
1099
|
+
readme: {
|
|
1100
|
+
title: "Figma OAuth Setup",
|
|
1101
|
+
content: `## Figma OAuth Setup
|
|
1102
|
+
|
|
1103
|
+
1. Create an app at https://www.figma.com/developers/apps
|
|
1104
|
+
2. Configure OAuth settings
|
|
1105
|
+
3. Add callback URL: \`http://localhost:3000/api/auth/callback/figma\` (update for production)
|
|
1106
|
+
4. Copy the Client ID, Client Secret, and Client Key to your \`.env\` file
|
|
1107
|
+
|
|
1108
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1109
|
+
}
|
|
1110
|
+
},
|
|
1111
|
+
gitlab: {
|
|
1112
|
+
id: "gitlab",
|
|
1113
|
+
name: "GitLab",
|
|
1114
|
+
envPrefix: "GITLAB",
|
|
1115
|
+
clientIdVar: "GITLAB_CLIENT_ID",
|
|
1116
|
+
clientSecretVar: "GITLAB_CLIENT_SECRET",
|
|
1117
|
+
popular: false,
|
|
1118
|
+
betterAuthConfig: {
|
|
1119
|
+
import: "",
|
|
1120
|
+
clientSideProvider: '"gitlab"',
|
|
1121
|
+
socialProvider: `gitlab({
|
|
1122
|
+
clientId: process.env.GITLAB_CLIENT_ID!,
|
|
1123
|
+
clientSecret: process.env.GITLAB_CLIENT_SECRET!,
|
|
1124
|
+
})`,
|
|
1125
|
+
scopes: []
|
|
1126
|
+
},
|
|
1127
|
+
env: [
|
|
1128
|
+
{
|
|
1129
|
+
name: "GITLAB_CLIENT_ID",
|
|
1130
|
+
type: "server",
|
|
1131
|
+
description: "GitLab Application ID"
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
name: "GITLAB_CLIENT_SECRET",
|
|
1135
|
+
type: "server",
|
|
1136
|
+
description: "GitLab Application Secret"
|
|
1137
|
+
}
|
|
1138
|
+
],
|
|
1139
|
+
docs: {
|
|
1140
|
+
provider: "https://gitlab.com/-/profile/applications",
|
|
1141
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1142
|
+
},
|
|
1143
|
+
requiresExtraConfig: false,
|
|
1144
|
+
extraConfigNotes: "Optionally supports an issuer field for self-hosted GitLab instances.",
|
|
1145
|
+
readme: {
|
|
1146
|
+
title: "GitLab OAuth Setup",
|
|
1147
|
+
content: `## GitLab OAuth Setup
|
|
1148
|
+
|
|
1149
|
+
1. Create an application at https://gitlab.com/-/profile/applications
|
|
1150
|
+
2. Add redirect URI: \`http://localhost:3000/api/auth/callback/gitlab\` (update for production)
|
|
1151
|
+
3. Select the required scopes (read_user is recommended)
|
|
1152
|
+
4. Copy the Application ID and Secret to your \`.env\` file
|
|
1153
|
+
|
|
1154
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1155
|
+
}
|
|
1156
|
+
},
|
|
1157
|
+
huggingface: {
|
|
1158
|
+
id: "huggingface",
|
|
1159
|
+
name: "Hugging Face",
|
|
1160
|
+
envPrefix: "HUGGINGFACE",
|
|
1161
|
+
clientIdVar: "HUGGINGFACE_CLIENT_ID",
|
|
1162
|
+
clientSecretVar: "HUGGINGFACE_CLIENT_SECRET",
|
|
1163
|
+
popular: false,
|
|
1164
|
+
betterAuthConfig: {
|
|
1165
|
+
import: "",
|
|
1166
|
+
clientSideProvider: '"huggingface"',
|
|
1167
|
+
socialProvider: `huggingface({
|
|
1168
|
+
clientId: process.env.HUGGINGFACE_CLIENT_ID!,
|
|
1169
|
+
clientSecret: process.env.HUGGINGFACE_CLIENT_SECRET!,
|
|
1170
|
+
})`,
|
|
1171
|
+
scopes: ["email"]
|
|
1172
|
+
},
|
|
1173
|
+
env: [
|
|
1174
|
+
{
|
|
1175
|
+
name: "HUGGINGFACE_CLIENT_ID",
|
|
1176
|
+
type: "server",
|
|
1177
|
+
description: "Hugging Face OAuth Client ID"
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: "HUGGINGFACE_CLIENT_SECRET",
|
|
1181
|
+
type: "server",
|
|
1182
|
+
description: "Hugging Face OAuth Client Secret"
|
|
1183
|
+
}
|
|
1184
|
+
],
|
|
1185
|
+
docs: {
|
|
1186
|
+
provider: "https://huggingface.co/settings/connected-applications",
|
|
1187
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1188
|
+
},
|
|
1189
|
+
requiresExtraConfig: true,
|
|
1190
|
+
extraConfigNotes: "You MUST include the email scope for Hugging Face.",
|
|
1191
|
+
readme: {
|
|
1192
|
+
title: "Hugging Face OAuth Setup",
|
|
1193
|
+
content: `## Hugging Face OAuth Setup
|
|
1194
|
+
|
|
1195
|
+
1. Create an OAuth app at https://huggingface.co/settings/connected-applications
|
|
1196
|
+
2. Set redirect URI to: \`http://localhost:3000/api/auth/callback/huggingface\` (update for production)
|
|
1197
|
+
3. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1198
|
+
4. **Important**: Make sure to include the \`email\` scope in your configuration
|
|
1199
|
+
|
|
1200
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
kakao: {
|
|
1204
|
+
id: "kakao",
|
|
1205
|
+
name: "Kakao",
|
|
1206
|
+
envPrefix: "KAKAO",
|
|
1207
|
+
clientIdVar: "KAKAO_CLIENT_ID",
|
|
1208
|
+
clientSecretVar: "KAKAO_CLIENT_SECRET",
|
|
1209
|
+
popular: false,
|
|
1210
|
+
betterAuthConfig: {
|
|
1211
|
+
import: "",
|
|
1212
|
+
clientSideProvider: '"kakao"',
|
|
1213
|
+
socialProvider: `kakao({
|
|
1214
|
+
clientId: process.env.KAKAO_CLIENT_ID!,
|
|
1215
|
+
clientSecret: process.env.KAKAO_CLIENT_SECRET!,
|
|
1216
|
+
})`,
|
|
1217
|
+
scopes: []
|
|
1218
|
+
},
|
|
1219
|
+
env: [
|
|
1220
|
+
{
|
|
1221
|
+
name: "KAKAO_CLIENT_ID",
|
|
1222
|
+
type: "server",
|
|
1223
|
+
description: "Kakao REST API Key"
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
name: "KAKAO_CLIENT_SECRET",
|
|
1227
|
+
type: "server",
|
|
1228
|
+
description: "Kakao Client Secret"
|
|
1229
|
+
}
|
|
1230
|
+
],
|
|
1231
|
+
docs: {
|
|
1232
|
+
provider: "https://developers.kakao.com/console/app",
|
|
1233
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1234
|
+
},
|
|
1235
|
+
requiresExtraConfig: false,
|
|
1236
|
+
extraConfigNotes: "",
|
|
1237
|
+
readme: {
|
|
1238
|
+
title: "Kakao OAuth Setup",
|
|
1239
|
+
content: `## Kakao OAuth Setup
|
|
1240
|
+
|
|
1241
|
+
1. Create an application at https://developers.kakao.com/console/app
|
|
1242
|
+
2. Enable Kakao Login in the app settings
|
|
1243
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/kakao\` (update for production)
|
|
1244
|
+
4. Copy the REST API Key and Client Secret to your \`.env\` file
|
|
1245
|
+
|
|
1246
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1249
|
+
kick: {
|
|
1250
|
+
id: "kick",
|
|
1251
|
+
name: "Kick",
|
|
1252
|
+
envPrefix: "KICK",
|
|
1253
|
+
clientIdVar: "KICK_CLIENT_ID",
|
|
1254
|
+
clientSecretVar: "KICK_CLIENT_SECRET",
|
|
1255
|
+
popular: false,
|
|
1256
|
+
betterAuthConfig: {
|
|
1257
|
+
import: "",
|
|
1258
|
+
clientSideProvider: '"kick"',
|
|
1259
|
+
socialProvider: `kick({
|
|
1260
|
+
clientId: process.env.KICK_CLIENT_ID!,
|
|
1261
|
+
clientSecret: process.env.KICK_CLIENT_SECRET!,
|
|
1262
|
+
})`,
|
|
1263
|
+
scopes: []
|
|
1264
|
+
},
|
|
1265
|
+
env: [
|
|
1266
|
+
{
|
|
1267
|
+
name: "KICK_CLIENT_ID",
|
|
1268
|
+
type: "server",
|
|
1269
|
+
description: "Kick OAuth Client ID"
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
name: "KICK_CLIENT_SECRET",
|
|
1273
|
+
type: "server",
|
|
1274
|
+
description: "Kick OAuth Client Secret"
|
|
1275
|
+
}
|
|
1276
|
+
],
|
|
1277
|
+
docs: {
|
|
1278
|
+
provider: "https://dev.kick.com/",
|
|
1279
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1280
|
+
},
|
|
1281
|
+
requiresExtraConfig: false,
|
|
1282
|
+
extraConfigNotes: "",
|
|
1283
|
+
readme: {
|
|
1284
|
+
title: "Kick OAuth Setup",
|
|
1285
|
+
content: `## Kick OAuth Setup
|
|
1286
|
+
|
|
1287
|
+
1. Create an application at https://dev.kick.com/
|
|
1288
|
+
2. Configure OAuth settings
|
|
1289
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/kick\` (update for production)
|
|
1290
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1291
|
+
|
|
1292
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1295
|
+
line: {
|
|
1296
|
+
id: "line",
|
|
1297
|
+
name: "LINE",
|
|
1298
|
+
envPrefix: "LINE",
|
|
1299
|
+
clientIdVar: "LINE_CLIENT_ID",
|
|
1300
|
+
clientSecretVar: "LINE_CLIENT_SECRET",
|
|
1301
|
+
popular: false,
|
|
1302
|
+
betterAuthConfig: {
|
|
1303
|
+
import: "",
|
|
1304
|
+
clientSideProvider: '"line"',
|
|
1305
|
+
socialProvider: `line({
|
|
1306
|
+
clientId: process.env.LINE_CLIENT_ID!,
|
|
1307
|
+
clientSecret: process.env.LINE_CLIENT_SECRET!,
|
|
1308
|
+
})`,
|
|
1309
|
+
scopes: []
|
|
1310
|
+
},
|
|
1311
|
+
env: [
|
|
1312
|
+
{
|
|
1313
|
+
name: "LINE_CLIENT_ID",
|
|
1314
|
+
type: "server",
|
|
1315
|
+
description: "LINE Channel ID"
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: "LINE_CLIENT_SECRET",
|
|
1319
|
+
type: "server",
|
|
1320
|
+
description: "LINE Channel Secret"
|
|
1321
|
+
}
|
|
1322
|
+
],
|
|
1323
|
+
docs: {
|
|
1324
|
+
provider: "https://developers.line.biz/console/",
|
|
1325
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1326
|
+
},
|
|
1327
|
+
requiresExtraConfig: false,
|
|
1328
|
+
extraConfigNotes: "Supports multi-channel configuration.",
|
|
1329
|
+
readme: {
|
|
1330
|
+
title: "LINE OAuth Setup",
|
|
1331
|
+
content: `## LINE OAuth Setup
|
|
1332
|
+
|
|
1333
|
+
1. Create a channel at https://developers.line.biz/console/
|
|
1334
|
+
2. Enable LINE Login
|
|
1335
|
+
3. Add callback URL: \`http://localhost:3000/api/auth/callback/line\` (update for production)
|
|
1336
|
+
4. Copy the Channel ID and Channel Secret to your \`.env\` file
|
|
1337
|
+
|
|
1338
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
linear: {
|
|
1342
|
+
id: "linear",
|
|
1343
|
+
name: "Linear",
|
|
1344
|
+
envPrefix: "LINEAR",
|
|
1345
|
+
clientIdVar: "LINEAR_CLIENT_ID",
|
|
1346
|
+
clientSecretVar: "LINEAR_CLIENT_SECRET",
|
|
1347
|
+
popular: false,
|
|
1348
|
+
betterAuthConfig: {
|
|
1349
|
+
import: "",
|
|
1350
|
+
clientSideProvider: '"linear"',
|
|
1351
|
+
socialProvider: `linear({
|
|
1352
|
+
clientId: process.env.LINEAR_CLIENT_ID!,
|
|
1353
|
+
clientSecret: process.env.LINEAR_CLIENT_SECRET!,
|
|
1354
|
+
})`,
|
|
1355
|
+
scopes: []
|
|
1356
|
+
},
|
|
1357
|
+
env: [
|
|
1358
|
+
{
|
|
1359
|
+
name: "LINEAR_CLIENT_ID",
|
|
1360
|
+
type: "server",
|
|
1361
|
+
description: "Linear OAuth Client ID"
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
name: "LINEAR_CLIENT_SECRET",
|
|
1365
|
+
type: "server",
|
|
1366
|
+
description: "Linear OAuth Client Secret"
|
|
1367
|
+
}
|
|
1368
|
+
],
|
|
1369
|
+
docs: {
|
|
1370
|
+
provider: "https://linear.app/settings/api",
|
|
1371
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1372
|
+
},
|
|
1373
|
+
requiresExtraConfig: false,
|
|
1374
|
+
extraConfigNotes: "Supports custom scope options.",
|
|
1375
|
+
readme: {
|
|
1376
|
+
title: "Linear OAuth Setup",
|
|
1377
|
+
content: `## Linear OAuth Setup
|
|
1378
|
+
|
|
1379
|
+
1. Create an OAuth application at https://linear.app/settings/api
|
|
1380
|
+
2. Add redirect URL: \`http://localhost:3000/api/auth/callback/linear\` (update for production)
|
|
1381
|
+
3. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1382
|
+
|
|
1383
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
1386
|
+
naver: {
|
|
1387
|
+
id: "naver",
|
|
1388
|
+
name: "Naver",
|
|
1389
|
+
envPrefix: "NAVER",
|
|
1390
|
+
clientIdVar: "NAVER_CLIENT_ID",
|
|
1391
|
+
clientSecretVar: "NAVER_CLIENT_SECRET",
|
|
1392
|
+
popular: false,
|
|
1393
|
+
betterAuthConfig: {
|
|
1394
|
+
import: "",
|
|
1395
|
+
clientSideProvider: '"naver"',
|
|
1396
|
+
socialProvider: `naver({
|
|
1397
|
+
clientId: process.env.NAVER_CLIENT_ID!,
|
|
1398
|
+
clientSecret: process.env.NAVER_CLIENT_SECRET!,
|
|
1399
|
+
})`,
|
|
1400
|
+
scopes: []
|
|
1401
|
+
},
|
|
1402
|
+
env: [
|
|
1403
|
+
{
|
|
1404
|
+
name: "NAVER_CLIENT_ID",
|
|
1405
|
+
type: "server",
|
|
1406
|
+
description: "Naver Client ID"
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
name: "NAVER_CLIENT_SECRET",
|
|
1410
|
+
type: "server",
|
|
1411
|
+
description: "Naver Client Secret"
|
|
1412
|
+
}
|
|
1413
|
+
],
|
|
1414
|
+
docs: {
|
|
1415
|
+
provider: "https://developers.naver.com/apps/",
|
|
1416
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1417
|
+
},
|
|
1418
|
+
requiresExtraConfig: false,
|
|
1419
|
+
extraConfigNotes: "",
|
|
1420
|
+
readme: {
|
|
1421
|
+
title: "Naver OAuth Setup",
|
|
1422
|
+
content: `## Naver OAuth Setup
|
|
1423
|
+
|
|
1424
|
+
1. Register an application at https://developers.naver.com/apps/
|
|
1425
|
+
2. Configure Login API settings
|
|
1426
|
+
3. Add callback URL: \`http://localhost:3000/api/auth/callback/naver\` (update for production)
|
|
1427
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1428
|
+
|
|
1429
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
notion: {
|
|
1433
|
+
id: "notion",
|
|
1434
|
+
name: "Notion",
|
|
1435
|
+
envPrefix: "NOTION",
|
|
1436
|
+
clientIdVar: "NOTION_CLIENT_ID",
|
|
1437
|
+
clientSecretVar: "NOTION_CLIENT_SECRET",
|
|
1438
|
+
popular: false,
|
|
1439
|
+
betterAuthConfig: {
|
|
1440
|
+
import: "",
|
|
1441
|
+
clientSideProvider: '"notion"',
|
|
1442
|
+
socialProvider: `notion({
|
|
1443
|
+
clientId: process.env.NOTION_CLIENT_ID!,
|
|
1444
|
+
clientSecret: process.env.NOTION_CLIENT_SECRET!,
|
|
1445
|
+
})`,
|
|
1446
|
+
scopes: []
|
|
1447
|
+
},
|
|
1448
|
+
env: [
|
|
1449
|
+
{
|
|
1450
|
+
name: "NOTION_CLIENT_ID",
|
|
1451
|
+
type: "server",
|
|
1452
|
+
description: "Notion OAuth Client ID"
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
name: "NOTION_CLIENT_SECRET",
|
|
1456
|
+
type: "server",
|
|
1457
|
+
description: "Notion OAuth Client Secret"
|
|
1458
|
+
}
|
|
1459
|
+
],
|
|
1460
|
+
docs: {
|
|
1461
|
+
provider: "https://www.notion.so/my-integrations",
|
|
1462
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1463
|
+
},
|
|
1464
|
+
requiresExtraConfig: false,
|
|
1465
|
+
extraConfigNotes: "",
|
|
1466
|
+
readme: {
|
|
1467
|
+
title: "Notion OAuth Setup",
|
|
1468
|
+
content: `## Notion OAuth Setup
|
|
1469
|
+
|
|
1470
|
+
1. Create an integration at https://www.notion.so/my-integrations
|
|
1471
|
+
2. Configure OAuth settings and capabilities
|
|
1472
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/notion\` (update for production)
|
|
1473
|
+
4. Copy the OAuth Client ID and Secret to your \`.env\` file
|
|
1474
|
+
|
|
1475
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
paybin: {
|
|
1479
|
+
id: "paybin",
|
|
1480
|
+
name: "Paybin",
|
|
1481
|
+
envPrefix: "PAYBIN",
|
|
1482
|
+
clientIdVar: "PAYBIN_CLIENT_ID",
|
|
1483
|
+
clientSecretVar: "PAYBIN_CLIENT_SECRET",
|
|
1484
|
+
popular: false,
|
|
1485
|
+
betterAuthConfig: {
|
|
1486
|
+
import: "",
|
|
1487
|
+
clientSideProvider: '"paybin"',
|
|
1488
|
+
socialProvider: `paybin({
|
|
1489
|
+
clientId: process.env.PAYBIN_CLIENT_ID!,
|
|
1490
|
+
clientSecret: process.env.PAYBIN_CLIENT_SECRET!,
|
|
1491
|
+
})`,
|
|
1492
|
+
scopes: []
|
|
1493
|
+
},
|
|
1494
|
+
env: [
|
|
1495
|
+
{
|
|
1496
|
+
name: "PAYBIN_CLIENT_ID",
|
|
1497
|
+
type: "server",
|
|
1498
|
+
description: "Paybin OAuth Client ID"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
name: "PAYBIN_CLIENT_SECRET",
|
|
1502
|
+
type: "server",
|
|
1503
|
+
description: "Paybin OAuth Client Secret"
|
|
1504
|
+
}
|
|
1505
|
+
],
|
|
1506
|
+
docs: {
|
|
1507
|
+
provider: "https://paybin.io/",
|
|
1508
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1509
|
+
},
|
|
1510
|
+
requiresExtraConfig: false,
|
|
1511
|
+
extraConfigNotes: "Uses OpenID Connect scopes.",
|
|
1512
|
+
readme: {
|
|
1513
|
+
title: "Paybin OAuth Setup",
|
|
1514
|
+
content: `## Paybin OAuth Setup
|
|
1515
|
+
|
|
1516
|
+
1. Create an application at https://paybin.io/
|
|
1517
|
+
2. Configure OAuth settings
|
|
1518
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/paybin\` (update for production)
|
|
1519
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1520
|
+
|
|
1521
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
paypal: {
|
|
1525
|
+
id: "paypal",
|
|
1526
|
+
name: "PayPal",
|
|
1527
|
+
envPrefix: "PAYPAL",
|
|
1528
|
+
clientIdVar: "PAYPAL_CLIENT_ID",
|
|
1529
|
+
clientSecretVar: "PAYPAL_CLIENT_SECRET",
|
|
1530
|
+
popular: false,
|
|
1531
|
+
betterAuthConfig: {
|
|
1532
|
+
import: "",
|
|
1533
|
+
clientSideProvider: '"paypal"',
|
|
1534
|
+
socialProvider: `paypal({
|
|
1535
|
+
clientId: process.env.PAYPAL_CLIENT_ID!,
|
|
1536
|
+
clientSecret: process.env.PAYPAL_CLIENT_SECRET!,
|
|
1537
|
+
environment: process.env.PAYPAL_ENVIRONMENT || "sandbox",
|
|
1538
|
+
})`,
|
|
1539
|
+
scopes: []
|
|
1540
|
+
},
|
|
1541
|
+
env: [
|
|
1542
|
+
{
|
|
1543
|
+
name: "PAYPAL_CLIENT_ID",
|
|
1544
|
+
type: "server",
|
|
1545
|
+
description: "PayPal REST API Client ID"
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
name: "PAYPAL_CLIENT_SECRET",
|
|
1549
|
+
type: "server",
|
|
1550
|
+
description: "PayPal REST API Secret"
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
name: "PAYPAL_ENVIRONMENT",
|
|
1554
|
+
type: "server",
|
|
1555
|
+
description: 'PayPal environment: "sandbox" or "live" (default: sandbox)'
|
|
1556
|
+
}
|
|
1557
|
+
],
|
|
1558
|
+
docs: {
|
|
1559
|
+
provider: "https://developer.paypal.com/dashboard/applications",
|
|
1560
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1561
|
+
},
|
|
1562
|
+
requiresExtraConfig: true,
|
|
1563
|
+
extraConfigNotes: "PayPal supports environment (sandbox/live) and requestShippingAddress options.",
|
|
1564
|
+
readme: {
|
|
1565
|
+
title: "PayPal OAuth Setup",
|
|
1566
|
+
content: `## PayPal OAuth Setup
|
|
1567
|
+
|
|
1568
|
+
1. Create an app at https://developer.paypal.com/dashboard/applications
|
|
1569
|
+
2. Configure OAuth settings in the app
|
|
1570
|
+
3. Add return URL: \`http://localhost:3000/api/auth/callback/paypal\` (update for production)
|
|
1571
|
+
4. Copy the Client ID and Secret to your \`.env\` file
|
|
1572
|
+
5. Set PAYPAL_ENVIRONMENT to "sandbox" for testing or "live" for production
|
|
1573
|
+
|
|
1574
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
polar: {
|
|
1578
|
+
id: "polar",
|
|
1579
|
+
name: "Polar",
|
|
1580
|
+
envPrefix: "POLAR",
|
|
1581
|
+
clientIdVar: "POLAR_CLIENT_ID",
|
|
1582
|
+
clientSecretVar: "POLAR_CLIENT_SECRET",
|
|
1583
|
+
popular: false,
|
|
1584
|
+
betterAuthConfig: {
|
|
1585
|
+
import: "",
|
|
1586
|
+
clientSideProvider: '"polar"',
|
|
1587
|
+
socialProvider: `polar({
|
|
1588
|
+
clientId: process.env.POLAR_CLIENT_ID!,
|
|
1589
|
+
clientSecret: process.env.POLAR_CLIENT_SECRET!,
|
|
1590
|
+
})`,
|
|
1591
|
+
scopes: []
|
|
1592
|
+
},
|
|
1593
|
+
env: [
|
|
1594
|
+
{
|
|
1595
|
+
name: "POLAR_CLIENT_ID",
|
|
1596
|
+
type: "server",
|
|
1597
|
+
description: "Polar OAuth Client ID"
|
|
1598
|
+
},
|
|
1599
|
+
{
|
|
1600
|
+
name: "POLAR_CLIENT_SECRET",
|
|
1601
|
+
type: "server",
|
|
1602
|
+
description: "Polar OAuth Client Secret"
|
|
1603
|
+
}
|
|
1604
|
+
],
|
|
1605
|
+
docs: {
|
|
1606
|
+
provider: "https://polar.sh/",
|
|
1607
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1608
|
+
},
|
|
1609
|
+
requiresExtraConfig: false,
|
|
1610
|
+
extraConfigNotes: "Uses OpenID Connect scopes.",
|
|
1611
|
+
readme: {
|
|
1612
|
+
title: "Polar OAuth Setup",
|
|
1613
|
+
content: `## Polar OAuth Setup
|
|
1614
|
+
|
|
1615
|
+
1. Create an OAuth application at https://polar.sh/
|
|
1616
|
+
2. Configure OAuth settings
|
|
1617
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/polar\` (update for production)
|
|
1618
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1619
|
+
|
|
1620
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1621
|
+
}
|
|
1622
|
+
},
|
|
1623
|
+
reddit: {
|
|
1624
|
+
id: "reddit",
|
|
1625
|
+
name: "Reddit",
|
|
1626
|
+
envPrefix: "REDDIT",
|
|
1627
|
+
clientIdVar: "REDDIT_CLIENT_ID",
|
|
1628
|
+
clientSecretVar: "REDDIT_CLIENT_SECRET",
|
|
1629
|
+
popular: false,
|
|
1630
|
+
betterAuthConfig: {
|
|
1631
|
+
import: "",
|
|
1632
|
+
clientSideProvider: '"reddit"',
|
|
1633
|
+
socialProvider: `reddit({
|
|
1634
|
+
clientId: process.env.REDDIT_CLIENT_ID!,
|
|
1635
|
+
clientSecret: process.env.REDDIT_CLIENT_SECRET!,
|
|
1636
|
+
})`,
|
|
1637
|
+
scopes: []
|
|
1638
|
+
},
|
|
1639
|
+
env: [
|
|
1640
|
+
{
|
|
1641
|
+
name: "REDDIT_CLIENT_ID",
|
|
1642
|
+
type: "server",
|
|
1643
|
+
description: "Reddit App Client ID"
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
name: "REDDIT_CLIENT_SECRET",
|
|
1647
|
+
type: "server",
|
|
1648
|
+
description: "Reddit App Client Secret"
|
|
1649
|
+
}
|
|
1650
|
+
],
|
|
1651
|
+
docs: {
|
|
1652
|
+
provider: "https://www.reddit.com/prefs/apps",
|
|
1653
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1654
|
+
},
|
|
1655
|
+
requiresExtraConfig: false,
|
|
1656
|
+
extraConfigNotes: "Supports duration and scope fields for custom access.",
|
|
1657
|
+
readme: {
|
|
1658
|
+
title: "Reddit OAuth Setup",
|
|
1659
|
+
content: `## Reddit OAuth Setup
|
|
1660
|
+
|
|
1661
|
+
1. Create an app at https://www.reddit.com/prefs/apps
|
|
1662
|
+
2. Choose "web app" as the app type
|
|
1663
|
+
3. Set redirect URI to: \`http://localhost:3000/api/auth/callback/reddit\` (update for production)
|
|
1664
|
+
4. Copy the Client ID (under app name) and Client Secret to your \`.env\` file
|
|
1665
|
+
|
|
1666
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1667
|
+
}
|
|
1668
|
+
},
|
|
1669
|
+
roblox: {
|
|
1670
|
+
id: "roblox",
|
|
1671
|
+
name: "Roblox",
|
|
1672
|
+
envPrefix: "ROBLOX",
|
|
1673
|
+
clientIdVar: "ROBLOX_CLIENT_ID",
|
|
1674
|
+
clientSecretVar: "ROBLOX_CLIENT_SECRET",
|
|
1675
|
+
popular: false,
|
|
1676
|
+
betterAuthConfig: {
|
|
1677
|
+
import: "",
|
|
1678
|
+
clientSideProvider: '"roblox"',
|
|
1679
|
+
socialProvider: `roblox({
|
|
1680
|
+
clientId: process.env.ROBLOX_CLIENT_ID!,
|
|
1681
|
+
clientSecret: process.env.ROBLOX_CLIENT_SECRET!,
|
|
1682
|
+
})`,
|
|
1683
|
+
scopes: []
|
|
1684
|
+
},
|
|
1685
|
+
env: [
|
|
1686
|
+
{
|
|
1687
|
+
name: "ROBLOX_CLIENT_ID",
|
|
1688
|
+
type: "server",
|
|
1689
|
+
description: "Roblox OAuth Client ID"
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
name: "ROBLOX_CLIENT_SECRET",
|
|
1693
|
+
type: "server",
|
|
1694
|
+
description: "Roblox OAuth Client Secret"
|
|
1695
|
+
}
|
|
1696
|
+
],
|
|
1697
|
+
docs: {
|
|
1698
|
+
provider: "https://create.roblox.com/credentials",
|
|
1699
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1700
|
+
},
|
|
1701
|
+
requiresExtraConfig: true,
|
|
1702
|
+
extraConfigNotes: "Note: Roblox OAuth does not provide user email addresses.",
|
|
1703
|
+
readme: {
|
|
1704
|
+
title: "Roblox OAuth Setup",
|
|
1705
|
+
content: `## Roblox OAuth Setup
|
|
1706
|
+
|
|
1707
|
+
1. Create OAuth credentials at https://create.roblox.com/credentials
|
|
1708
|
+
2. Configure OAuth 2.0 settings
|
|
1709
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/roblox\` (update for production)
|
|
1710
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1711
|
+
|
|
1712
|
+
**Note**: Roblox does not provide email addresses through OAuth.
|
|
1713
|
+
|
|
1714
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1715
|
+
}
|
|
1716
|
+
},
|
|
1717
|
+
salesforce: {
|
|
1718
|
+
id: "salesforce",
|
|
1719
|
+
name: "Salesforce",
|
|
1720
|
+
envPrefix: "SALESFORCE",
|
|
1721
|
+
clientIdVar: "SALESFORCE_CLIENT_ID",
|
|
1722
|
+
clientSecretVar: "SALESFORCE_CLIENT_SECRET",
|
|
1723
|
+
popular: false,
|
|
1724
|
+
betterAuthConfig: {
|
|
1725
|
+
import: "",
|
|
1726
|
+
clientSideProvider: '"salesforce"',
|
|
1727
|
+
socialProvider: `salesforce({
|
|
1728
|
+
clientId: process.env.SALESFORCE_CLIENT_ID!,
|
|
1729
|
+
clientSecret: process.env.SALESFORCE_CLIENT_SECRET!,
|
|
1730
|
+
environment: process.env.SALESFORCE_ENVIRONMENT || "login",
|
|
1731
|
+
})`,
|
|
1732
|
+
scopes: []
|
|
1733
|
+
},
|
|
1734
|
+
env: [
|
|
1735
|
+
{
|
|
1736
|
+
name: "SALESFORCE_CLIENT_ID",
|
|
1737
|
+
type: "server",
|
|
1738
|
+
description: "Salesforce Connected App Consumer Key"
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
name: "SALESFORCE_CLIENT_SECRET",
|
|
1742
|
+
type: "server",
|
|
1743
|
+
description: "Salesforce Connected App Consumer Secret"
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
name: "SALESFORCE_ENVIRONMENT",
|
|
1747
|
+
type: "server",
|
|
1748
|
+
description: 'Salesforce environment: "login" (production) or "test" (sandbox) (default: login)'
|
|
1749
|
+
}
|
|
1750
|
+
],
|
|
1751
|
+
docs: {
|
|
1752
|
+
provider: "https://developer.salesforce.com/",
|
|
1753
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1754
|
+
},
|
|
1755
|
+
requiresExtraConfig: true,
|
|
1756
|
+
extraConfigNotes: 'Salesforce supports environment field: "login" for production, "test" for sandbox.',
|
|
1757
|
+
readme: {
|
|
1758
|
+
title: "Salesforce OAuth Setup",
|
|
1759
|
+
content: `## Salesforce OAuth Setup
|
|
1760
|
+
|
|
1761
|
+
1. Create a Connected App in Salesforce Setup
|
|
1762
|
+
2. Enable OAuth Settings
|
|
1763
|
+
3. Add callback URL: \`http://localhost:3000/api/auth/callback/salesforce\` (update for production)
|
|
1764
|
+
4. Copy the Consumer Key and Consumer Secret to your \`.env\` file
|
|
1765
|
+
5. Set SALESFORCE_ENVIRONMENT to "login" for production or "test" for sandbox
|
|
1766
|
+
|
|
1767
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
slack: {
|
|
1771
|
+
id: "slack",
|
|
1772
|
+
name: "Slack",
|
|
1773
|
+
envPrefix: "SLACK",
|
|
1774
|
+
clientIdVar: "SLACK_CLIENT_ID",
|
|
1775
|
+
clientSecretVar: "SLACK_CLIENT_SECRET",
|
|
1776
|
+
popular: false,
|
|
1777
|
+
betterAuthConfig: {
|
|
1778
|
+
import: "",
|
|
1779
|
+
clientSideProvider: '"slack"',
|
|
1780
|
+
socialProvider: `slack({
|
|
1781
|
+
clientId: process.env.SLACK_CLIENT_ID!,
|
|
1782
|
+
clientSecret: process.env.SLACK_CLIENT_SECRET!,
|
|
1783
|
+
})`,
|
|
1784
|
+
scopes: []
|
|
1785
|
+
},
|
|
1786
|
+
env: [
|
|
1787
|
+
{
|
|
1788
|
+
name: "SLACK_CLIENT_ID",
|
|
1789
|
+
type: "server",
|
|
1790
|
+
description: "Slack App Client ID"
|
|
1791
|
+
},
|
|
1792
|
+
{
|
|
1793
|
+
name: "SLACK_CLIENT_SECRET",
|
|
1794
|
+
type: "server",
|
|
1795
|
+
description: "Slack App Client Secret"
|
|
1796
|
+
}
|
|
1797
|
+
],
|
|
1798
|
+
docs: {
|
|
1799
|
+
provider: "https://api.slack.com/apps",
|
|
1800
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1801
|
+
},
|
|
1802
|
+
requiresExtraConfig: false,
|
|
1803
|
+
extraConfigNotes: "Supports optional team field for workspace restrictions.",
|
|
1804
|
+
readme: {
|
|
1805
|
+
title: "Slack OAuth Setup",
|
|
1806
|
+
content: `## Slack OAuth Setup
|
|
1807
|
+
|
|
1808
|
+
1. Create a Slack app at https://api.slack.com/apps
|
|
1809
|
+
2. Add OAuth & Permissions and configure redirect URLs
|
|
1810
|
+
3. Add redirect URL: \`http://localhost:3000/api/auth/callback/slack\` (update for production)
|
|
1811
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1812
|
+
|
|
1813
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1814
|
+
}
|
|
1815
|
+
},
|
|
1816
|
+
tiktok: {
|
|
1817
|
+
id: "tiktok",
|
|
1818
|
+
name: "TikTok",
|
|
1819
|
+
envPrefix: "TIKTOK",
|
|
1820
|
+
clientIdVar: "TIKTOK_CLIENT_KEY",
|
|
1821
|
+
clientSecretVar: "TIKTOK_CLIENT_SECRET",
|
|
1822
|
+
popular: false,
|
|
1823
|
+
betterAuthConfig: {
|
|
1824
|
+
import: "",
|
|
1825
|
+
clientSideProvider: '"tiktok"',
|
|
1826
|
+
socialProvider: `tiktok({
|
|
1827
|
+
clientKey: process.env.TIKTOK_CLIENT_KEY!,
|
|
1828
|
+
clientSecret: process.env.TIKTOK_CLIENT_SECRET!,
|
|
1829
|
+
})`,
|
|
1830
|
+
scopes: []
|
|
1831
|
+
},
|
|
1832
|
+
env: [
|
|
1833
|
+
{
|
|
1834
|
+
name: "TIKTOK_CLIENT_KEY",
|
|
1835
|
+
type: "server",
|
|
1836
|
+
description: "TikTok Client Key (not Client ID)"
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
name: "TIKTOK_CLIENT_SECRET",
|
|
1840
|
+
type: "server",
|
|
1841
|
+
description: "TikTok Client Secret"
|
|
1842
|
+
}
|
|
1843
|
+
],
|
|
1844
|
+
docs: {
|
|
1845
|
+
provider: "https://developers.tiktok.com/",
|
|
1846
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1847
|
+
},
|
|
1848
|
+
requiresExtraConfig: true,
|
|
1849
|
+
extraConfigNotes: "TikTok uses clientKey instead of clientId. Make sure to use the correct field name.",
|
|
1850
|
+
readme: {
|
|
1851
|
+
title: "TikTok OAuth Setup",
|
|
1852
|
+
content: `## TikTok OAuth Setup
|
|
1853
|
+
|
|
1854
|
+
1. Register an app at https://developers.tiktok.com/
|
|
1855
|
+
2. Configure Login Kit
|
|
1856
|
+
3. Add redirect URI: \`http://localhost:3000/api/auth/callback/tiktok\` (update for production)
|
|
1857
|
+
4. Copy the Client Key (not Client ID) and Client Secret to your \`.env\` file
|
|
1858
|
+
|
|
1859
|
+
**Important**: TikTok uses clientKey instead of clientId.
|
|
1860
|
+
|
|
1861
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1862
|
+
}
|
|
1863
|
+
},
|
|
1864
|
+
vercel: {
|
|
1865
|
+
id: "vercel",
|
|
1866
|
+
name: "Vercel",
|
|
1867
|
+
envPrefix: "VERCEL",
|
|
1868
|
+
clientIdVar: "VERCEL_CLIENT_ID",
|
|
1869
|
+
clientSecretVar: "VERCEL_CLIENT_SECRET",
|
|
1870
|
+
popular: false,
|
|
1871
|
+
betterAuthConfig: {
|
|
1872
|
+
import: "",
|
|
1873
|
+
clientSideProvider: '"vercel"',
|
|
1874
|
+
socialProvider: `vercel({
|
|
1875
|
+
clientId: process.env.VERCEL_CLIENT_ID!,
|
|
1876
|
+
clientSecret: process.env.VERCEL_CLIENT_SECRET!,
|
|
1877
|
+
})`,
|
|
1878
|
+
scopes: []
|
|
1879
|
+
},
|
|
1880
|
+
env: [
|
|
1881
|
+
{
|
|
1882
|
+
name: "VERCEL_CLIENT_ID",
|
|
1883
|
+
type: "server",
|
|
1884
|
+
description: "Vercel OAuth Client ID"
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
name: "VERCEL_CLIENT_SECRET",
|
|
1888
|
+
type: "server",
|
|
1889
|
+
description: "Vercel OAuth Client Secret"
|
|
1890
|
+
}
|
|
1891
|
+
],
|
|
1892
|
+
docs: {
|
|
1893
|
+
provider: "https://vercel.com/account/integrations",
|
|
1894
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1895
|
+
},
|
|
1896
|
+
requiresExtraConfig: false,
|
|
1897
|
+
extraConfigNotes: "Uses PKCE for enhanced security.",
|
|
1898
|
+
readme: {
|
|
1899
|
+
title: "Vercel OAuth Setup",
|
|
1900
|
+
content: `## Vercel OAuth Setup
|
|
1901
|
+
|
|
1902
|
+
1. Create an integration at https://vercel.com/account/integrations
|
|
1903
|
+
2. Configure OAuth settings
|
|
1904
|
+
3. Add redirect URL: \`http://localhost:3000/api/auth/callback/vercel\` (update for production)
|
|
1905
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1906
|
+
|
|
1907
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1908
|
+
}
|
|
1909
|
+
},
|
|
1910
|
+
vk: {
|
|
1911
|
+
id: "vk",
|
|
1912
|
+
name: "VK",
|
|
1913
|
+
envPrefix: "VK",
|
|
1914
|
+
clientIdVar: "VK_CLIENT_ID",
|
|
1915
|
+
clientSecretVar: "VK_CLIENT_SECRET",
|
|
1916
|
+
popular: false,
|
|
1917
|
+
betterAuthConfig: {
|
|
1918
|
+
import: "",
|
|
1919
|
+
clientSideProvider: '"vk"',
|
|
1920
|
+
socialProvider: `vk({
|
|
1921
|
+
clientId: process.env.VK_CLIENT_ID!,
|
|
1922
|
+
clientSecret: process.env.VK_CLIENT_SECRET!,
|
|
1923
|
+
})`,
|
|
1924
|
+
scopes: []
|
|
1925
|
+
},
|
|
1926
|
+
env: [
|
|
1927
|
+
{
|
|
1928
|
+
name: "VK_CLIENT_ID",
|
|
1929
|
+
type: "server",
|
|
1930
|
+
description: "VK Application ID"
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
name: "VK_CLIENT_SECRET",
|
|
1934
|
+
type: "server",
|
|
1935
|
+
description: "VK Secure Key"
|
|
1936
|
+
}
|
|
1937
|
+
],
|
|
1938
|
+
docs: {
|
|
1939
|
+
provider: "https://vk.com/apps?act=manage",
|
|
1940
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1941
|
+
},
|
|
1942
|
+
requiresExtraConfig: false,
|
|
1943
|
+
extraConfigNotes: "",
|
|
1944
|
+
readme: {
|
|
1945
|
+
title: "VK OAuth Setup",
|
|
1946
|
+
content: `## VK OAuth Setup
|
|
1947
|
+
|
|
1948
|
+
1. Create an app at https://vk.com/apps?act=manage
|
|
1949
|
+
2. Configure OAuth settings in the app
|
|
1950
|
+
3. Add authorized redirect URI: \`http://localhost:3000/api/auth/callback/vk\` (update for production)
|
|
1951
|
+
4. Copy the Application ID and Secure Key to your \`.env\` file
|
|
1952
|
+
|
|
1953
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
1954
|
+
}
|
|
1955
|
+
},
|
|
1956
|
+
zoom: {
|
|
1957
|
+
id: "zoom",
|
|
1958
|
+
name: "Zoom",
|
|
1959
|
+
envPrefix: "ZOOM",
|
|
1960
|
+
clientIdVar: "ZOOM_CLIENT_ID",
|
|
1961
|
+
clientSecretVar: "ZOOM_CLIENT_SECRET",
|
|
1962
|
+
popular: false,
|
|
1963
|
+
betterAuthConfig: {
|
|
1964
|
+
import: "",
|
|
1965
|
+
clientSideProvider: '"zoom"',
|
|
1966
|
+
socialProvider: `zoom({
|
|
1967
|
+
clientId: process.env.ZOOM_CLIENT_ID!,
|
|
1968
|
+
clientSecret: process.env.ZOOM_CLIENT_SECRET!,
|
|
1969
|
+
})`,
|
|
1970
|
+
scopes: ["user:read:user"]
|
|
1971
|
+
},
|
|
1972
|
+
env: [
|
|
1973
|
+
{
|
|
1974
|
+
name: "ZOOM_CLIENT_ID",
|
|
1975
|
+
type: "server",
|
|
1976
|
+
description: "Zoom OAuth Client ID"
|
|
1977
|
+
},
|
|
1978
|
+
{
|
|
1979
|
+
name: "ZOOM_CLIENT_SECRET",
|
|
1980
|
+
type: "server",
|
|
1981
|
+
description: "Zoom OAuth Client Secret"
|
|
1982
|
+
}
|
|
1983
|
+
],
|
|
1984
|
+
docs: {
|
|
1985
|
+
provider: "https://marketplace.zoom.us/",
|
|
1986
|
+
betterAuth: "https://www.better-auth.com/docs/authentication/social"
|
|
1987
|
+
},
|
|
1988
|
+
requiresExtraConfig: true,
|
|
1989
|
+
extraConfigNotes: "You MUST include the user:read:user scope for Zoom.",
|
|
1990
|
+
readme: {
|
|
1991
|
+
title: "Zoom OAuth Setup",
|
|
1992
|
+
content: `## Zoom OAuth Setup
|
|
1993
|
+
|
|
1994
|
+
1. Create an app at https://marketplace.zoom.us/
|
|
1995
|
+
2. Choose OAuth as the app type
|
|
1996
|
+
3. Add redirect URL: \`http://localhost:3000/api/auth/callback/zoom\` (update for production)
|
|
1997
|
+
4. Copy the Client ID and Client Secret to your \`.env\` file
|
|
1998
|
+
5. **Important**: Make sure to include the \`user:read:user\` scope
|
|
1999
|
+
|
|
2000
|
+
For more details, see the [Better Auth documentation](https://www.better-auth.com/docs/authentication/social).`
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
function getProvider(id) {
|
|
2005
|
+
return OAUTH_PROVIDERS[id];
|
|
2006
|
+
}
|
|
2007
|
+
function getPopularProviders() {
|
|
2008
|
+
return Object.values(OAUTH_PROVIDERS).filter(
|
|
2009
|
+
(provider) => provider.popular === true
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
function getAdditionalProviders() {
|
|
2013
|
+
return Object.values(OAUTH_PROVIDERS).filter(
|
|
2014
|
+
(provider) => provider.popular !== true
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// src/installers/string-utils.ts
|
|
2019
|
+
function detectIndentation(line) {
|
|
2020
|
+
const match = line.match(/^(\s*)/);
|
|
2021
|
+
return match ? match[1] : "";
|
|
2022
|
+
}
|
|
2023
|
+
async function replacePlaceholder(filePath, placeholder, content, options) {
|
|
2024
|
+
const fileContent = await fs4.readFile(filePath, "utf-8");
|
|
2025
|
+
if (!fileContent.includes(placeholder)) {
|
|
2026
|
+
if (options?.graceful) {
|
|
2027
|
+
console.warn(
|
|
2028
|
+
`Warning: Placeholder "${placeholder}" not found in file: ${filePath}. Skipping replacement.`
|
|
2029
|
+
);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
throw new Error(
|
|
2033
|
+
`Placeholder "${placeholder}" not found in file: ${filePath}`
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
if (options?.inline) {
|
|
2037
|
+
const updatedContent = fileContent.replace(placeholder, content);
|
|
2038
|
+
await fs4.writeFile(filePath, updatedContent, "utf-8");
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
const lines = fileContent.split("\n");
|
|
2042
|
+
const updatedLines = [];
|
|
2043
|
+
for (const line of lines) {
|
|
2044
|
+
if (line.includes(placeholder)) {
|
|
2045
|
+
if (content === "" || content.startsWith("__REMOVE_")) {
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
const indentation = detectIndentation(line);
|
|
2049
|
+
const indentedContent = content.split("\n").map((contentLine, index) => {
|
|
2050
|
+
if (index === 0) {
|
|
2051
|
+
return indentation + contentLine;
|
|
2052
|
+
}
|
|
2053
|
+
return contentLine ? indentation + contentLine : "";
|
|
2054
|
+
}).join("\n");
|
|
2055
|
+
updatedLines.push(indentedContent);
|
|
2056
|
+
} else {
|
|
2057
|
+
updatedLines.push(line);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
await fs4.writeFile(filePath, updatedLines.join("\n"), "utf-8");
|
|
2061
|
+
}
|
|
2062
|
+
function generateCredentialsValue(enabled) {
|
|
2063
|
+
return `credentials={${enabled}}`;
|
|
2064
|
+
}
|
|
2065
|
+
function generateAuthProvidersBlock(oauthProviders, emailPasswordEnabled) {
|
|
2066
|
+
const parts = [];
|
|
2067
|
+
parts.push(`emailAndPassword: {
|
|
2068
|
+
enabled: ${emailPasswordEnabled}
|
|
2069
|
+
},`);
|
|
2070
|
+
if (oauthProviders.length > 0) {
|
|
2071
|
+
const providersObject = oauthProviders.map((providerId) => {
|
|
2072
|
+
const provider = getProvider(providerId);
|
|
2073
|
+
if (!provider) {
|
|
2074
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
2075
|
+
}
|
|
2076
|
+
const configLines = [
|
|
2077
|
+
`clientId: process.env.${provider.envPrefix}_CLIENT_ID!,`,
|
|
2078
|
+
`clientSecret: process.env.${provider.envPrefix}_CLIENT_SECRET!,`
|
|
2079
|
+
];
|
|
2080
|
+
if (providerId === "figma") {
|
|
2081
|
+
configLines.push(`clientKey: process.env.FIGMA_CLIENT_KEY!,`);
|
|
2082
|
+
}
|
|
2083
|
+
return `${providerId}: {
|
|
2084
|
+
${configLines.join("\n ")}
|
|
2085
|
+
}`;
|
|
2086
|
+
}).join(",\n ");
|
|
2087
|
+
parts.push(`socialProviders: {
|
|
2088
|
+
${providersObject}
|
|
2089
|
+
},`);
|
|
2090
|
+
}
|
|
2091
|
+
return parts.join("\n ");
|
|
2092
|
+
}
|
|
2093
|
+
function generateEnvTsServerSchema(providers) {
|
|
2094
|
+
if (providers.length === 0) {
|
|
2095
|
+
return "";
|
|
2096
|
+
}
|
|
2097
|
+
const schemas = providers.map((providerId) => {
|
|
2098
|
+
const provider = getProvider(providerId);
|
|
2099
|
+
if (!provider) {
|
|
2100
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
2101
|
+
}
|
|
2102
|
+
const lines = [];
|
|
2103
|
+
lines.push(`${provider.envPrefix}_CLIENT_ID: z.string(),`);
|
|
2104
|
+
lines.push(`${provider.envPrefix}_CLIENT_SECRET: z.string(),`);
|
|
2105
|
+
if (providerId === "figma") {
|
|
2106
|
+
lines.push(`FIGMA_CLIENT_KEY: z.string(),`);
|
|
2107
|
+
}
|
|
2108
|
+
return lines.join("\n ");
|
|
2109
|
+
}).join("\n ");
|
|
2110
|
+
return schemas;
|
|
2111
|
+
}
|
|
2112
|
+
function generateEnvTsRuntimeMapping(providers) {
|
|
2113
|
+
if (providers.length === 0) {
|
|
2114
|
+
return "";
|
|
2115
|
+
}
|
|
2116
|
+
const mappings = providers.map((providerId) => {
|
|
2117
|
+
const provider = getProvider(providerId);
|
|
2118
|
+
if (!provider) {
|
|
2119
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
2120
|
+
}
|
|
2121
|
+
const lines = [];
|
|
2122
|
+
lines.push(`${provider.envPrefix}_CLIENT_ID: process.env.${provider.envPrefix}_CLIENT_ID,`);
|
|
2123
|
+
lines.push(`${provider.envPrefix}_CLIENT_SECRET: process.env.${provider.envPrefix}_CLIENT_SECRET,`);
|
|
2124
|
+
if (providerId === "figma") {
|
|
2125
|
+
lines.push(`FIGMA_CLIENT_KEY: process.env.FIGMA_CLIENT_KEY,`);
|
|
2126
|
+
}
|
|
2127
|
+
return lines.join("\n ");
|
|
2128
|
+
}).join("\n ");
|
|
2129
|
+
return mappings;
|
|
2130
|
+
}
|
|
2131
|
+
function generateOAuthUIProvidersBlock(providers) {
|
|
2132
|
+
if (providers.length === 0) {
|
|
2133
|
+
return "";
|
|
2134
|
+
}
|
|
2135
|
+
const providerList = providers.map((id) => `"${id}"`).join(", ");
|
|
2136
|
+
return `social={{
|
|
2137
|
+
providers: [${providerList}]
|
|
2138
|
+
}}`;
|
|
2139
|
+
}
|
|
2140
|
+
function generateEnvVarsBlock(providers, framework) {
|
|
2141
|
+
if (providers.length === 0) {
|
|
2142
|
+
return "";
|
|
2143
|
+
}
|
|
2144
|
+
const envVars = providers.flatMap((providerId) => {
|
|
2145
|
+
const provider = getProvider(providerId);
|
|
2146
|
+
if (!provider) {
|
|
2147
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
2148
|
+
}
|
|
2149
|
+
if (provider.env && provider.env.length > 0) {
|
|
2150
|
+
return provider.env.map((envVar) => {
|
|
2151
|
+
let prefix = "";
|
|
2152
|
+
if (envVar.type === "client") {
|
|
2153
|
+
prefix = framework === "nextjs" ? "NEXT_PUBLIC_" : "VITE_";
|
|
2154
|
+
}
|
|
2155
|
+
return `# ${envVar.description}
|
|
2156
|
+
${prefix}${envVar.name}=`;
|
|
2157
|
+
});
|
|
2158
|
+
} else {
|
|
2159
|
+
return [
|
|
2160
|
+
`${provider.clientIdVar}=`,
|
|
2161
|
+
`${provider.clientSecretVar}=`
|
|
2162
|
+
];
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
return envVars.join("\n");
|
|
2166
|
+
}
|
|
2167
|
+
function generateReadmeSection(providers) {
|
|
2168
|
+
if (providers.length === 0) {
|
|
2169
|
+
return "";
|
|
2170
|
+
}
|
|
2171
|
+
const sections = providers.map((providerId) => {
|
|
2172
|
+
const provider = getProvider(providerId);
|
|
2173
|
+
if (!provider) {
|
|
2174
|
+
throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
2175
|
+
}
|
|
2176
|
+
if (!provider.readme) {
|
|
2177
|
+
throw new Error(
|
|
2178
|
+
`Provider ${providerId} missing readme metadata`
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
return provider.readme.content;
|
|
2182
|
+
}).filter(Boolean);
|
|
2183
|
+
if (sections.length === 0) {
|
|
2184
|
+
return "";
|
|
2185
|
+
}
|
|
2186
|
+
return `# OAuth Provider Setup
|
|
2187
|
+
|
|
2188
|
+
${sections.join("\n\n---\n\n")}`;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// src/installers/nextjs.ts
|
|
2192
|
+
var VexNextJSInstaller = class extends VexFrameworkInstaller {
|
|
2193
|
+
get frameworkName() {
|
|
2194
|
+
return "nextjs";
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Update OAuth configuration in Convex auth options file
|
|
2198
|
+
* Target file: convex/auth/options.ts
|
|
2199
|
+
*/
|
|
2200
|
+
async updateOAuthConfig(selectedProviders, emailPasswordEnabled) {
|
|
2201
|
+
const authFilePath = join2(this.targetPath, "convex/auth/options.ts");
|
|
2202
|
+
const authProvidersBlock = generateAuthProvidersBlock(
|
|
2203
|
+
selectedProviders,
|
|
2204
|
+
emailPasswordEnabled
|
|
2205
|
+
);
|
|
2206
|
+
await replacePlaceholder(
|
|
2207
|
+
authFilePath,
|
|
2208
|
+
"// {{OAUTH_PROVIDERS}}",
|
|
2209
|
+
authProvidersBlock
|
|
2210
|
+
);
|
|
2211
|
+
await replacePlaceholder(
|
|
2212
|
+
authFilePath,
|
|
2213
|
+
"// {{EMAIL_PASSWORD_AUTH}}",
|
|
2214
|
+
"",
|
|
2215
|
+
{ graceful: true }
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Update OAuth UI configuration in auth client file
|
|
2220
|
+
* Target file: src/auth/client.tsx
|
|
2221
|
+
*/
|
|
2222
|
+
async updateOAuthUIConfig(selectedProviders, emailPasswordEnabled) {
|
|
2223
|
+
const providersFilePath = join2(this.targetPath, "src/auth/client.tsx");
|
|
2224
|
+
const uiConfigBlock = generateOAuthUIProvidersBlock(selectedProviders);
|
|
2225
|
+
await replacePlaceholder(
|
|
2226
|
+
providersFilePath,
|
|
2227
|
+
"// {{OAUTH_UI_PROVIDERS}}",
|
|
2228
|
+
uiConfigBlock
|
|
2229
|
+
);
|
|
2230
|
+
const credentialsValue = generateCredentialsValue(emailPasswordEnabled);
|
|
2231
|
+
await replacePlaceholder(
|
|
2232
|
+
providersFilePath,
|
|
2233
|
+
"/* {{EMAIL_PASSWORD_CREDENTIALS}} */",
|
|
2234
|
+
credentialsValue
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Update .env.example with OAuth environment variables
|
|
2239
|
+
* Target file: .env.example
|
|
2240
|
+
*/
|
|
2241
|
+
async updateEnvExample(selectedProviders) {
|
|
2242
|
+
const envFilePath = join2(this.targetPath, ".env.example");
|
|
2243
|
+
const envVarsBlock = generateEnvVarsBlock(selectedProviders, "nextjs");
|
|
2244
|
+
await replacePlaceholder(
|
|
2245
|
+
envFilePath,
|
|
2246
|
+
"# {{ENV_OAUTH_VARS}}",
|
|
2247
|
+
envVarsBlock
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Update README with OAuth provider setup guides
|
|
2252
|
+
* Target file: README.md
|
|
2253
|
+
*/
|
|
2254
|
+
async updateReadme(selectedProviders) {
|
|
2255
|
+
const readmeFilePath = join2(this.targetPath, "README.md");
|
|
2256
|
+
const readmeSection = generateReadmeSection(selectedProviders);
|
|
2257
|
+
await replacePlaceholder(
|
|
2258
|
+
readmeFilePath,
|
|
2259
|
+
"<!-- {{OAUTH_SETUP_GUIDE}} -->",
|
|
2260
|
+
readmeSection,
|
|
2261
|
+
{ graceful: true }
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Update env.mjs with OAuth provider environment variables
|
|
2266
|
+
* Target file: src/env.mjs
|
|
2267
|
+
*/
|
|
2268
|
+
async updateEnvTs(selectedProviders) {
|
|
2269
|
+
const envFilePath = join2(this.targetPath, "src/env.mjs");
|
|
2270
|
+
const serverSchema = generateEnvTsServerSchema(selectedProviders);
|
|
2271
|
+
await replacePlaceholder(
|
|
2272
|
+
envFilePath,
|
|
2273
|
+
"// {{OAUTH_ENV_SERVER_SCHEMA}}",
|
|
2274
|
+
serverSchema
|
|
2275
|
+
);
|
|
2276
|
+
const runtimeMapping = generateEnvTsRuntimeMapping(selectedProviders);
|
|
2277
|
+
await replacePlaceholder(
|
|
2278
|
+
envFilePath,
|
|
2279
|
+
"// {{OAUTH_ENV_RUNTIME_MAPPING}}",
|
|
2280
|
+
runtimeMapping
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
// src/installers/createInstaller.ts
|
|
2286
|
+
function createInstaller(props) {
|
|
2287
|
+
switch (props.framework) {
|
|
2288
|
+
case "nextjs":
|
|
2289
|
+
return new VexNextJSInstaller(props.projectDir, props.projectName);
|
|
2290
|
+
case "tanstack":
|
|
2291
|
+
throw new Error("TanStack Start is not yet supported");
|
|
2292
|
+
default:
|
|
2293
|
+
throw new Error(`Unknown framework: ${props.framework}`);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// src/index.ts
|
|
2298
|
+
var program = new Command().name("create-vexcms").description("Scaffold a new VEX CMS project").argument("[project-name]", "Project directory name").option("--bare", "Skip marketing site collections, scaffold empty project").option("--orgs", "Enable multi-tenant organizations").version("0.0.2").parse();
|
|
2299
|
+
var args = program.args;
|
|
2300
|
+
var opts = program.opts();
|
|
2301
|
+
async function main() {
|
|
2302
|
+
console.log();
|
|
2303
|
+
console.log(chalk2.bold(" create-vexcms"));
|
|
2304
|
+
console.log();
|
|
2305
|
+
const bare = opts.bare ?? false;
|
|
2306
|
+
let inputArg;
|
|
2307
|
+
if (args[0]) {
|
|
2308
|
+
inputArg = args[0];
|
|
2309
|
+
} else {
|
|
2310
|
+
inputArg = await input({
|
|
2311
|
+
message: "What is your project named?",
|
|
2312
|
+
default: "my-vexcms-app",
|
|
2313
|
+
validate: (value) => {
|
|
2314
|
+
const name = value.includes("/") ? path2.basename(value) : value;
|
|
2315
|
+
if (name === ".") return true;
|
|
2316
|
+
const result = validateProjectName(name);
|
|
2317
|
+
if (result.valid) return true;
|
|
2318
|
+
return result.errors[0] ?? "Invalid project name";
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
inputArg = resolveProjectName(inputArg, process.cwd());
|
|
2323
|
+
const projectName = inputArg === "." ? path2.basename(process.cwd()) : path2.basename(inputArg);
|
|
2324
|
+
const validation = validateProjectName(projectName);
|
|
2325
|
+
if (!validation.valid) {
|
|
2326
|
+
displayInvalidNameError(projectName, validation.errors);
|
|
2327
|
+
process.exit(1);
|
|
2328
|
+
}
|
|
2329
|
+
const targetDir = inputArg === "." ? process.cwd() : path2.resolve(process.cwd(), inputArg);
|
|
2330
|
+
if (await checkDirectoryExists(targetDir)) {
|
|
2331
|
+
if (!await isDirectoryEmpty(targetDir)) {
|
|
2332
|
+
displayDirectoryNotEmptyError();
|
|
2333
|
+
process.exit(1);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
let framework;
|
|
2337
|
+
while (true) {
|
|
2338
|
+
framework = await select({
|
|
2339
|
+
message: "Select a framework:",
|
|
2340
|
+
choices: [
|
|
2341
|
+
{ name: "Next.js (Recommended)", value: "nextjs" },
|
|
2342
|
+
{ name: "TanStack Start (Coming Soon)", value: "tanstack" }
|
|
2343
|
+
]
|
|
2344
|
+
});
|
|
2345
|
+
if (framework === "tanstack") {
|
|
2346
|
+
console.log(chalk2.yellow("\n TanStack Start support is coming soon! Please select Next.js for now.\n"));
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
const emailPasswordAuth = await confirm({
|
|
2352
|
+
message: "Enable email/password authentication?",
|
|
2353
|
+
default: true
|
|
2354
|
+
});
|
|
2355
|
+
const popularProviders = getPopularProviders();
|
|
2356
|
+
const additionalProviders = getAdditionalProviders();
|
|
2357
|
+
const allProviderChoices = [
|
|
2358
|
+
...popularProviders.map((p) => ({
|
|
2359
|
+
name: p.name,
|
|
2360
|
+
value: p.id
|
|
2361
|
+
})),
|
|
2362
|
+
{ name: "\u2500\u2500 Additional providers \u2500\u2500", value: "__separator__", disabled: true },
|
|
2363
|
+
...additionalProviders.map((p) => ({
|
|
2364
|
+
name: p.name,
|
|
2365
|
+
value: p.id
|
|
2366
|
+
}))
|
|
2367
|
+
];
|
|
2368
|
+
const oauthProviders = await checkbox({
|
|
2369
|
+
message: "Select OAuth providers (space to toggle, enter to confirm):",
|
|
2370
|
+
choices: allProviderChoices
|
|
2371
|
+
});
|
|
2372
|
+
const orgs = opts.orgs ?? await confirm({
|
|
2373
|
+
message: "Enable multi-tenant (organizations)?",
|
|
2374
|
+
default: false
|
|
2375
|
+
});
|
|
2376
|
+
const initGit = await confirm({
|
|
2377
|
+
message: "Initialize a Git repository?",
|
|
2378
|
+
default: true
|
|
2379
|
+
});
|
|
2380
|
+
const installDependencies = await confirm({
|
|
2381
|
+
message: "Install dependencies?",
|
|
2382
|
+
default: false
|
|
2383
|
+
});
|
|
2384
|
+
const options = {
|
|
2385
|
+
projectName,
|
|
2386
|
+
projectDir: targetDir,
|
|
2387
|
+
framework,
|
|
2388
|
+
bare,
|
|
2389
|
+
orgs,
|
|
2390
|
+
emailPasswordAuth,
|
|
2391
|
+
oauthProviders,
|
|
2392
|
+
initGit,
|
|
2393
|
+
installDependencies
|
|
2394
|
+
};
|
|
2395
|
+
await fs5.ensureDir(targetDir);
|
|
2396
|
+
const installer = createInstaller({
|
|
2397
|
+
framework,
|
|
2398
|
+
projectDir: targetDir,
|
|
2399
|
+
projectName: options.projectName
|
|
2400
|
+
});
|
|
2401
|
+
console.log();
|
|
2402
|
+
await installer.initProject(options);
|
|
2403
|
+
console.log();
|
|
2404
|
+
displaySuccessMessage(options.projectName, targetDir, inputArg === ".");
|
|
2405
|
+
}
|
|
2406
|
+
main().catch((error) => {
|
|
2407
|
+
console.error(chalk2.red("\nAn error occurred:"), error.message);
|
|
2408
|
+
process.exit(1);
|
|
2409
|
+
});
|