@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
JavaScript
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
};
};