in packages/cli/src/generators/auth-config.ts [38:590]
export async function generateAuthConfig({
format,
current_user_config,
spinner,
plugins,
database,
}: {
format: Format;
current_user_config: string;
spinner: ReturnType<typeof clackSpinner>;
plugins: SupportedPlugin[];
database: SupportedDatabases | null;
}): Promise<{
generatedCode: string;
dependencies: string[];
envs: string[];
}> {
let _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;
},
} satisfies CommonIndexConfig<{}>,
};
const common_indexes = {
START_OF_PLUGINS:
_start_of_plugins_common_index.START_OF_PLUGINS satisfies CommonIndexConfig<{}>,
END_OF_PLUGINS: {
type: "manual",
getIndex: ({ content, additionalFields }) => {
const closingBracketIndex = findClosingBracket(
content,
additionalFields.start_of_plugins,
"[",
"]",
);
return closingBracketIndex;
},
} satisfies CommonIndexConfig<{ start_of_plugins: number }>,
START_OF_BETTERAUTH: {
type: "regex",
regex: /betterAuth\({()/m,
getIndex: ({ matchIndex }) => {
return matchIndex + "betterAuth({".length;
},
} satisfies CommonIndexConfig<{}>,
};
const config_generation = {
add_plugin: async (opts: {
direction_in_plugins_array: "append" | "prepend";
pluginFunctionName: string;
pluginContents: string;
config: string;
}): Promise<{ code: string; dependencies: string[]; envs: string[] }> => {
let start_of_plugins = getGroupInfo(
opts.config,
common_indexes.START_OF_PLUGINS,
{},
);
// console.log(`start of plugins:`, start_of_plugins);
if (!start_of_plugins) {
throw new Error(
"Couldn't find start of your plugins array in your auth config file.",
);
}
let end_of_plugins = getGroupInfo(
opts.config,
common_indexes.END_OF_PLUGINS,
{ start_of_plugins: start_of_plugins.index },
);
// console.log(`end of plugins:`, end_of_plugins);
if (!end_of_plugins) {
throw new Error(
"Couldn't find end of your plugins array in your auth config file.",
);
}
// console.log(
// "slice:\n",
// opts.config.slice(start_of_plugins.index, end_of_plugins.index),
// );
let new_content: string;
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})`,
});
}
// console.log(`new_content`, new_content);
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: {
imports: Import[];
config: string;
}): Promise<{ code: string; dependencies: string[]; envs: string[] }> => {
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}";\n`;
} else {
importString += `import ${import_.variables.asType ? "type " : ""}${
import_.variables.name
}${import_.variables.as ? ` as ${import_.variables.as}` : ""} from "${
import_.path
}";\n`;
}
}
try {
let 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: {
database: SupportedDatabases;
config: string;
}): Promise<{ code: string; dependencies: string[]; envs: string[] }> => {
const required_envs: string[] = [];
const required_deps: string[] = [];
let database_code_str: string = "";
async function add_db({
db_code,
dependencies,
envs,
imports,
code_before_betterAuth,
}: {
imports: Import[];
db_code: string;
envs: string[];
dependencies: string[];
/**
* Any code you want to put before the betterAuth export
*/
code_before_betterAuth?: string;
}) {
if (code_before_betterAuth) {
let 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.");
}
opts.config = insertContent({
line: start_of_betterauth.line - 1,
character: 0,
content: opts.config,
insert_content: `\n${code_before_betterAuth}\n`,
});
}
const code_gen = await config_generation.add_import({
config: opts.config,
imports: 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({\nconnectionString: process.env.DATABASE_URL || "postgresql://postgres:password@localhost:5432/database"\n})`,
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, {\nprovider: "${opts.database.replace(
"drizzle:",
"",
)}",\n})`,
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, {\nprovider: "${opts.database.replace(
"prisma:",
"",
)}",\n})`,
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",
},
],
},
],
});
}
let 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: string;
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: string = await format(current_user_config);
let total_dependencies: string[] = [];
let total_envs: string[] = [];
if (plugins.length !== 0) {
const imports: {
path: string;
variables: {
asType: boolean;
name: string;
}[];
}[] = [];
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: imports,
});
total_dependencies.push(...dependencies);
total_envs.push(...envs);
new_user_config = code;
}
}
for await (const plugin of plugins) {
try {
// console.log(`--------- UPDATE: ${plugin} ---------`);
let pluginContents = "";
if (plugin.id === "magic-link") {
pluginContents = `{\nsendMagicLink({ email, token, url }, request) {\n// Send email with magic link\n},\n}`;
} else if (plugin.id === "email-otp") {
pluginContents = `{\nasync sendVerificationOTP({ email, otp, type }, request) {\n// Send email with OTP\n},\n}`;
} else if (plugin.id === "generic-oauth") {
pluginContents = `{\nconfig: [],\n}`;
} else if (plugin.id === "oidc") {
pluginContents = `{\nloginPage: "/sign-in",\n}`;
}
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: pluginContents,
});
new_user_config = code;
total_envs.push(...envs);
total_dependencies.push(...dependencies);
// console.log(new_user_config);
// console.log(`--------- UPDATE END ---------`);
} catch (error: any) {
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: database,
},
);
new_user_config = code;
total_dependencies.push(...dependencies);
total_envs.push(...envs);
} catch (error: any) {
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,
};
}