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.
Files changed (116) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2409 -0
  3. package/package.json +37 -0
  4. package/templates/base-nextjs/.prettierignore +26 -0
  5. package/templates/base-nextjs/.prettierrc +7 -0
  6. package/templates/base-nextjs/README.md +256 -0
  7. package/templates/base-nextjs/_gitignore +2 -0
  8. package/templates/base-nextjs/components.json +24 -0
  9. package/templates/base-nextjs/convex/_generated/api.d.ts +125 -0
  10. package/templates/base-nextjs/convex/_generated/api.js +23 -0
  11. package/templates/base-nextjs/convex/_generated/dataModel.d.ts +60 -0
  12. package/templates/base-nextjs/convex/_generated/server.d.ts +143 -0
  13. package/templates/base-nextjs/convex/_generated/server.js +93 -0
  14. package/templates/base-nextjs/convex/auth/adapter/index.ts +230 -0
  15. package/templates/base-nextjs/convex/auth/adapter/utils.ts +547 -0
  16. package/templates/base-nextjs/convex/auth/api.ts +8 -0
  17. package/templates/base-nextjs/convex/auth/config.ts +10 -0
  18. package/templates/base-nextjs/convex/auth/db.ts +303 -0
  19. package/templates/base-nextjs/convex/auth/index.ts +14 -0
  20. package/templates/base-nextjs/convex/auth/options.ts +63 -0
  21. package/templates/base-nextjs/convex/auth/plugins/index.ts +20 -0
  22. package/templates/base-nextjs/convex/auth/sessions.ts +60 -0
  23. package/templates/base-nextjs/convex/auth.config.ts +7 -0
  24. package/templates/base-nextjs/convex/convex.config.ts +5 -0
  25. package/templates/base-nextjs/convex/http.ts +28 -0
  26. package/templates/base-nextjs/convex/schema.ts +3 -0
  27. package/templates/base-nextjs/convex/vex/auth.ts +38 -0
  28. package/templates/base-nextjs/convex/vex/collections.ts +321 -0
  29. package/templates/base-nextjs/convex/vex/firstUser.ts +134 -0
  30. package/templates/base-nextjs/convex/vex/helpers.ts +33 -0
  31. package/templates/base-nextjs/convex/vex/impersonation.ts +51 -0
  32. package/templates/base-nextjs/convex/vex/media.ts +246 -0
  33. package/templates/base-nextjs/convex/vex/migrate.ts +74 -0
  34. package/templates/base-nextjs/convex/vex/model/collections.ts +196 -0
  35. package/templates/base-nextjs/convex/vex/model/media.ts +87 -0
  36. package/templates/base-nextjs/convex/vex/model/versions.ts +254 -0
  37. package/templates/base-nextjs/convex/vex/previewSnapshot.ts +53 -0
  38. package/templates/base-nextjs/convex/vex/versions.ts +588 -0
  39. package/templates/base-nextjs/eslint.config.mjs +164 -0
  40. package/templates/base-nextjs/next.config.ts +10 -0
  41. package/templates/base-nextjs/package.json +67 -0
  42. package/templates/base-nextjs/postcss.config.mjs +7 -0
  43. package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
  44. package/templates/base-nextjs/public/file.svg +1 -0
  45. package/templates/base-nextjs/public/globe.svg +1 -0
  46. package/templates/base-nextjs/public/next.svg +1 -0
  47. package/templates/base-nextjs/public/vercel.svg +1 -0
  48. package/templates/base-nextjs/public/window.svg +1 -0
  49. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/page.tsx +9 -0
  50. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +23 -0
  51. package/templates/base-nextjs/src/app/(frontend)/@auth/default.tsx +3 -0
  52. package/templates/base-nextjs/src/app/(frontend)/@auth/page.tsx +3 -0
  53. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/page.tsx +9 -0
  54. package/templates/base-nextjs/src/app/(frontend)/auth/[pathname]/view.tsx +11 -0
  55. package/templates/base-nextjs/src/app/(frontend)/layout.tsx +14 -0
  56. package/templates/base-nextjs/src/app/(frontend)/page.tsx +116 -0
  57. package/templates/base-nextjs/src/app/admin/AdminLayoutWrapper.tsx +33 -0
  58. package/templates/base-nextjs/src/app/admin/AdminPageWrapper.tsx +38 -0
  59. package/templates/base-nextjs/src/app/admin/RichTextFieldWithMedia.tsx +101 -0
  60. package/templates/base-nextjs/src/app/admin/[[...path]]/page.tsx +15 -0
  61. package/templates/base-nextjs/src/app/admin/layout.tsx +33 -0
  62. package/templates/base-nextjs/src/app/api/auth/[...all]/route.ts +6 -0
  63. package/templates/base-nextjs/src/app/favicon.ico +0 -0
  64. package/templates/base-nextjs/src/app/globals.css +130 -0
  65. package/templates/base-nextjs/src/app/layout.tsx +30 -0
  66. package/templates/base-nextjs/src/auth/client.tsx +41 -0
  67. package/templates/base-nextjs/src/auth/permissions.ts +102 -0
  68. package/templates/base-nextjs/src/auth/server.ts +16 -0
  69. package/templates/base-nextjs/src/auth/serverUtils.ts +64 -0
  70. package/templates/base-nextjs/src/auth/types.ts +3 -0
  71. package/templates/base-nextjs/src/components/component-example.tsx +474 -0
  72. package/templates/base-nextjs/src/components/example.tsx +52 -0
  73. package/templates/base-nextjs/src/components/providers/client.tsx +9 -0
  74. package/templates/base-nextjs/src/components/providers/convex.tsx +32 -0
  75. package/templates/base-nextjs/src/components/providers/server.tsx +15 -0
  76. package/templates/base-nextjs/src/components/providers/theme.tsx +11 -0
  77. package/templates/base-nextjs/src/components/ui/alert-dialog.tsx +162 -0
  78. package/templates/base-nextjs/src/components/ui/badge.tsx +52 -0
  79. package/templates/base-nextjs/src/components/ui/button.tsx +60 -0
  80. package/templates/base-nextjs/src/components/ui/card.tsx +92 -0
  81. package/templates/base-nextjs/src/components/ui/combobox.tsx +271 -0
  82. package/templates/base-nextjs/src/components/ui/dialog.tsx +135 -0
  83. package/templates/base-nextjs/src/components/ui/dropdown-menu.tsx +246 -0
  84. package/templates/base-nextjs/src/components/ui/field.tsx +224 -0
  85. package/templates/base-nextjs/src/components/ui/input-group.tsx +146 -0
  86. package/templates/base-nextjs/src/components/ui/input.tsx +20 -0
  87. package/templates/base-nextjs/src/components/ui/label.tsx +20 -0
  88. package/templates/base-nextjs/src/components/ui/select.tsx +189 -0
  89. package/templates/base-nextjs/src/components/ui/separator.tsx +21 -0
  90. package/templates/base-nextjs/src/components/ui/textarea.tsx +18 -0
  91. package/templates/base-nextjs/src/components/ui/theme-toggle.tsx +67 -0
  92. package/templates/base-nextjs/src/db/constants/auth.ts +8 -0
  93. package/templates/base-nextjs/src/db/constants/index.ts +44 -0
  94. package/templates/base-nextjs/src/db/types.ts +6 -0
  95. package/templates/base-nextjs/src/env.mjs +48 -0
  96. package/templates/base-nextjs/src/lib/utils.ts +6 -0
  97. package/templates/base-nextjs/src/proxy.ts +23 -0
  98. package/templates/base-nextjs/src/vexcms/access.ts +28 -0
  99. package/templates/base-nextjs/src/vexcms/auth.ts +4 -0
  100. package/templates/base-nextjs/src/vexcms/collections/index.ts +1 -0
  101. package/templates/base-nextjs/src/vexcms/collections/media.ts +15 -0
  102. package/templates/base-nextjs/src/vexcms/collections/users.ts +46 -0
  103. package/templates/base-nextjs/tsconfig.json +60 -0
  104. package/templates/base-nextjs/vex.config.ts +23 -0
  105. package/templates/marketing-site/convex/pages.ts +46 -0
  106. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +58 -0
  107. package/templates/marketing-site/src/app/(frontend)/preview/[slug]/page.tsx +69 -0
  108. package/templates/marketing-site/src/app/admin/AdminLayoutWrapper.tsx +71 -0
  109. package/templates/marketing-site/src/db/constants/index.ts +52 -0
  110. package/templates/marketing-site/src/vexcms/collections/footers.ts +28 -0
  111. package/templates/marketing-site/src/vexcms/collections/headers.ts +35 -0
  112. package/templates/marketing-site/src/vexcms/collections/index.ts +7 -0
  113. package/templates/marketing-site/src/vexcms/collections/pages.ts +40 -0
  114. package/templates/marketing-site/src/vexcms/collections/site_settings.ts +31 -0
  115. package/templates/marketing-site/src/vexcms/collections/themes.ts +37 -0
  116. 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
+ });