UNPKG

@reliverse/rse

Version:

@reliverse/rse is your all-in-one companion for bootstrapping and improving any kind of projects (especially web apps built with frameworks like Next.js) — whether you're kicking off something new or upgrading an existing app. It is also a little AI-power

568 lines (567 loc) 16.2 kB
import { logger } from "better-auth"; export async function generateAuthConfig({ format, current_user_config, spinner, plugins, database }) { const _start_of_plugins_common_index = { START_OF_PLUGINS: { type: "regex", regex: /betterAuth\([\w\W]*plugins:[\W]*\[()/m, getIndex: ({ matchIndex, match }) => { return matchIndex + match[0].length; } } }; const common_indexes = { START_OF_PLUGINS: _start_of_plugins_common_index.START_OF_PLUGINS, END_OF_PLUGINS: { type: "manual", getIndex: ({ content, additionalFields }) => { const closingBracketIndex = findClosingBracket( content, additionalFields.start_of_plugins, "[", "]" ); return closingBracketIndex; } }, START_OF_BETTERAUTH: { type: "regex", regex: /betterAuth\({()/m, getIndex: ({ matchIndex }) => { return matchIndex + "betterAuth({".length; } } }; const config_generation = { add_plugin: async (opts) => { const start_of_plugins = getGroupInfo( opts.config, common_indexes.START_OF_PLUGINS, {} ); if (!start_of_plugins) { throw new Error( "Couldn't find start of your plugins array in your auth config file." ); } const end_of_plugins = getGroupInfo( opts.config, common_indexes.END_OF_PLUGINS, { start_of_plugins: start_of_plugins.index } ); if (!end_of_plugins) { throw new Error( "Couldn't find end of your plugins array in your auth config file." ); } let new_content; if (opts.direction_in_plugins_array === "prepend") { new_content = insertContent({ line: start_of_plugins.line, character: start_of_plugins.character, content: opts.config, insert_content: `${opts.pluginFunctionName}(${opts.pluginContents}),` }); } else { let has_found_comma = false; const str = opts.config.slice(start_of_plugins.index, end_of_plugins.index).split("").reverse(); for (let index = 0; index < str.length; index++) { const char = str[index]; if (char === ",") { has_found_comma = true; } if (char === ")") { break; } } new_content = insertContent({ line: end_of_plugins.line, character: end_of_plugins.character, content: opts.config, insert_content: `${!has_found_comma ? "," : ""}${opts.pluginFunctionName}(${opts.pluginContents})` }); } try { new_content = await format(new_content); } catch (error) { console.error(error); throw new Error( `Failed to generate new auth config during plugin addition phase.` ); } return { code: await new_content, dependencies: [], envs: [] }; }, add_import: async (opts) => { let importString = ""; for (const import_ of opts.imports) { if (Array.isArray(import_.variables)) { importString += `import { ${import_.variables.map( (x) => `${x.asType ? "type " : ""}${x.name}${x.as ? ` as ${x.as}` : ""}` ).join(", ")} } from "${import_.path}"; `; } else { importString += `import ${import_.variables.asType ? "type " : ""}${import_.variables.name}${import_.variables.as ? ` as ${import_.variables.as}` : ""} from "${import_.path}"; `; } } try { const new_content = format(importString + opts.config); return { code: await new_content, dependencies: [], envs: [] }; } catch (error) { console.error(error); throw new Error( `Failed to generate new auth config during import addition phase.` ); } }, add_database: async (opts) => { const required_envs = []; const required_deps = []; let database_code_str = ""; async function add_db({ db_code, dependencies, envs, imports, code_before_betterAuth }) { if (code_before_betterAuth) { const start_of_betterauth2 = getGroupInfo( opts.config, common_indexes.START_OF_BETTERAUTH, {} ); if (!start_of_betterauth2) { throw new Error("Couldn't find start of betterAuth() function."); } opts.config = insertContent({ line: start_of_betterauth2.line - 1, character: 0, content: opts.config, insert_content: ` ${code_before_betterAuth} ` }); } const code_gen = await config_generation.add_import({ config: opts.config, imports }); opts.config = code_gen.code; database_code_str = db_code; required_envs.push(...envs, ...code_gen.envs); required_deps.push(...dependencies, ...code_gen.dependencies); } if (opts.database === "sqlite") { await add_db({ db_code: `new Database(process.env.DATABASE_URL || "database.sqlite")`, dependencies: ["better-sqlite3"], envs: ["DATABASE_URL"], imports: [ { path: "better-sqlite3", variables: { asType: false, name: "Database" } } ] }); } else if (opts.database === "postgres") { await add_db({ db_code: `new Pool({ connectionString: process.env.DATABASE_URL || "postgresql://postgres:password@localhost:5432/database" })`, dependencies: ["pg"], envs: ["DATABASE_URL"], imports: [ { path: "pg", variables: [ { asType: false, name: "Pool" } ] } ] }); } else if (opts.database === "mysql") { await add_db({ db_code: `createPool(process.env.DATABASE_URL!)`, dependencies: ["mysql2"], envs: ["DATABASE_URL"], imports: [ { path: "mysql2/promise", variables: [ { asType: false, name: "createPool" } ] } ] }); } else if (opts.database === "mssql") { const dialectCode = `new MssqlDialect({ tarn: { ...Tarn, options: { min: 0, max: 10, }, }, tedious: { ...Tedious, connectionFactory: () => new Tedious.Connection({ authentication: { options: { password: 'password', userName: 'username', }, type: 'default', }, options: { database: 'some_db', port: 1433, trustServerCertificate: true, }, server: 'localhost', }), }, })`; await add_db({ code_before_betterAuth: dialectCode, db_code: `dialect`, dependencies: ["tedious", "tarn", "kysely"], envs: ["DATABASE_URL"], imports: [ { path: "tedious", variables: { name: "*", as: "Tedious" } }, { path: "tarn", variables: { name: "*", as: "Tarn" } }, { path: "kysely", variables: [ { name: "MssqlDialect" } ] } ] }); } else if (opts.database === "drizzle:mysql" || opts.database === "drizzle:sqlite" || opts.database === "drizzle:pg") { await add_db({ db_code: `drizzleAdapter(db, { provider: "${opts.database.replace( "drizzle:", "" )}", })`, dependencies: [""], envs: [], imports: [ { path: "better-auth/adapters/drizzle", variables: [ { name: "drizzleAdapter" } ] }, { path: "./database.ts", variables: [ { name: "db" } ] } ] }); } else if (opts.database === "prisma:mysql" || opts.database === "prisma:sqlite" || opts.database === "prisma:postgresql") { await add_db({ db_code: `prismaAdapter(client, { provider: "${opts.database.replace( "prisma:", "" )}", })`, dependencies: [`@prisma/client`], envs: [], code_before_betterAuth: "const client = new PrismaClient();", imports: [ { path: "better-auth/adapters/prisma", variables: [ { name: "prismaAdapter" } ] }, { path: "@prisma/client", variables: [ { name: "PrismaClient" } ] } ] }); } else if (opts.database === "mongodb") { await add_db({ db_code: `mongodbAdapter(db)`, dependencies: ["mongodb"], envs: [`DATABASE_URL`], code_before_betterAuth: [ `const client = new MongoClient(process.env.DATABASE_URL || "mongodb://localhost:27017/database");`, `const db = client.db();` ].join("\n"), imports: [ { path: "better-auth/adapters/mongo", variables: [ { name: "mongodbAdapter" } ] }, { path: "mongodb", variables: [ { name: "MongoClient" } ] } ] }); } const start_of_betterauth = getGroupInfo( opts.config, common_indexes.START_OF_BETTERAUTH, {} ); if (!start_of_betterauth) { throw new Error("Couldn't find start of betterAuth() function."); } let new_content; new_content = insertContent({ line: start_of_betterauth.line, character: start_of_betterauth.character, content: opts.config, insert_content: `database: ${database_code_str},` }); try { new_content = await format(new_content); return { code: new_content, dependencies: required_deps, envs: required_envs }; } catch (error) { console.error(error); throw new Error( `Failed to generate new auth config during database addition phase.` ); } } }; let new_user_config = await format(current_user_config); const total_dependencies = []; const total_envs = []; if (plugins.length !== 0) { const imports = []; for await (const plugin of plugins) { const existingIndex = imports.findIndex((x) => x.path === plugin.path); if (existingIndex !== -1) { imports[existingIndex].variables.push({ name: plugin.name, asType: false }); } else { imports.push({ path: plugin.path, variables: [ { name: plugin.name, asType: false } ] }); } } if (imports.length !== 0) { const { code, envs, dependencies } = await config_generation.add_import({ config: new_user_config, imports }); total_dependencies.push(...dependencies); total_envs.push(...envs); new_user_config = code; } } for await (const plugin of plugins) { try { let pluginContents = ""; if (plugin.id === "magic-link") { pluginContents = `{ sendMagicLink({ email, token, url }, request) { // Send email with magic link }, }`; } else if (plugin.id === "email-otp") { pluginContents = `{ async sendVerificationOTP({ email, otp, type }, request) { // Send email with OTP }, }`; } else if (plugin.id === "generic-oauth") { pluginContents = `{ config: [], }`; } else if (plugin.id === "oidc") { pluginContents = `{ loginPage: "/sign-in", }`; } const { code, dependencies, envs } = await config_generation.add_plugin({ config: new_user_config, direction_in_plugins_array: plugin.id === "next-cookies" ? "append" : "prepend", pluginFunctionName: plugin.name, pluginContents }); new_user_config = code; total_envs.push(...envs); total_dependencies.push(...dependencies); } catch (error) { spinner.stop( `Something went wrong while generating/updating your new auth config file.`, 1 ); logger.error(error.message); process.exit(1); } } if (database) { try { const { code, dependencies, envs } = await config_generation.add_database( { config: new_user_config, database } ); new_user_config = code; total_dependencies.push(...dependencies); total_envs.push(...envs); } catch (error) { spinner.stop( `Something went wrong while generating/updating your new auth config file.`, 1 ); logger.error(error.message); process.exit(1); } } return { generatedCode: new_user_config, dependencies: total_dependencies, envs: total_envs }; } function findClosingBracket(content, startIndex, openingBracket, closingBracket) { let stack = 0; let inString = false; let quoteChar = null; for (let i = startIndex; i < content.length; i++) { const char = content[i]; if (char === '"' || char === "'" || char === "`") { if (!inString) { inString = true; quoteChar = char; } else if (char === quoteChar) { inString = false; quoteChar = null; } continue; } if (!inString) { if (char === openingBracket) { stack++; } else if (char === closingBracket) { if (stack === 0) { return i; } stack--; } } } return null; } function insertContent(params) { const { line, character, content, insert_content } = params; const lines = content.split("\n"); if (line < 1 || line > lines.length) { throw new Error("Invalid line number"); } const targetLineIndex = line - 1; if (character < 0 || character > lines[targetLineIndex].length) { throw new Error("Invalid character index"); } const targetLine = lines[targetLineIndex]; const updatedLine = targetLine.slice(0, character) + insert_content + targetLine.slice(character); lines[targetLineIndex] = updatedLine; return lines.join("\n"); } function getGroupInfo(content, commonIndexConfig, additionalFields) { if (commonIndexConfig.type === "regex") { const { regex, getIndex } = commonIndexConfig; const match = regex.exec(content); if (match) { const matchIndex = match.index; const groupIndex = getIndex({ matchIndex, match, additionalFields }); if (groupIndex === null) return null; const position = getPosition(content, groupIndex); return { line: position.line, character: position.character, index: groupIndex }; } return null; } else { const { getIndex } = commonIndexConfig; const index = getIndex({ content, additionalFields }); if (index === null) return null; const { line, character } = getPosition(content, index); return { line, character, index }; } } const getPosition = (str, index) => { const lines = str.slice(0, index).split("\n"); return { line: lines.length, character: lines[lines.length - 1].length }; };