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