auth
Version:
The CLI for Better Auth
1,224 lines (1,217 loc) • 242 kB
JavaScript
#!/usr/bin/env node
import { Command } from "commander";
import { exec, execSync, spawn } from "node:child_process";
import * as fs$2 from "node:fs";
import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
import * as os$1 from "node:os";
import os from "node:os";
import * as path$1 from "node:path";
import path, { join } from "node:path";
import chalk from "chalk";
import prompts from "prompts";
import yoctoSpinner from "yocto-spinner";
import fs$1 from "node:fs/promises";
import { createTelemetry, getTelemetryAuthConfig } from "@better-auth/telemetry";
import { getAdapter } from "better-auth/db/adapter";
import * as z from "zod";
import { capitalizeFirstLetter, toSnakeCase } from "@better-auth/core/utils/string";
import { initGetFieldName, initGetModelName } from "better-auth/adapters";
import { getAuthTables } from "better-auth/db";
import prettier, { format } from "prettier";
import { getMigrations } from "better-auth/db/migration";
import { produceSchema } from "@mrleebo/prisma-ast";
import Crypto from "node:crypto";
import babelPresetReact from "@babel/preset-react";
import babelPresetTypeScript from "@babel/preset-typescript";
import { BetterAuthError } from "@better-auth/core/error";
import { loadConfig } from "c12";
import { createPathsMatcher, getTsconfig, parseTsconfig } from "get-tsconfig";
import open from "open";
import { env } from "@better-auth/core/env";
import { log } from "@clack/prompts";
import { base64 } from "@better-auth/utils/base64";
import * as semver from "semver";
import "dotenv/config";
//#region src/commands/ai.ts
const PROTOCOL_URL = "https://agent-auth-protocol.com";
const AGENT_CLI_PKG = "@auth/agent-cli";
const AGENT_PLUGIN_PKG = "@better-auth/agent-auth";
const DEFAULT_REGISTRY = "https://agent-auth.directory";
const SKILLS_REPO = "better-auth/agent-auth";
function cancelled() {
console.log(chalk.yellow("\n✋ Setup cancelled."));
process.exit(0);
}
function check(value) {
if (value === void 0 || value === null) cancelled();
return value;
}
async function aiAction() {
console.log("\n" + [
` ██ ████`,
` ████ ██ ${chalk.bold("Agent Auth")} ${chalk.dim("Setup")}`,
` ██ ████ ${chalk.gray("AI agent authentication & capability-based authorization.")}`
].join("\n"));
console.log();
const { setup } = await prompts({
type: "select",
name: "setup",
message: "What would you like to do?",
choices: [{
title: "Integrate Agent Auth client",
value: "client",
description: "MCP server, CLI, or SDK for your agents"
}, {
title: "Create an Agent Auth server",
value: "server",
description: "expose capabilities from your service to AI agents"
}]
});
check(setup);
if (setup === "client") await setupClient();
else await setupServerSelection();
}
async function setupClient() {
const { method } = await prompts({
type: "select",
name: "method",
message: "How do you want to integrate?",
choices: [{
title: "MCP Server",
value: "mcp",
description: "for AI tools — Claude, Cursor, Windsurf, etc."
}, {
title: "CLI",
value: "cli",
description: "command-line tool for agent workflows"
}]
});
check(method);
if (method === "mcp") await setupMcp();
else await setupCli();
}
async function setupServerSelection() {
const { implementation } = await prompts({
type: "select",
name: "implementation",
message: "Choose an implementation",
choices: [{
title: "Better Auth + Agent Auth",
value: "better-auth",
description: "TypeScript"
}]
});
check(implementation);
await setupServer();
}
async function setupMcp() {
const { tool } = await prompts({
type: "select",
name: "tool",
message: "Which AI tool?",
choices: [
{
title: "Cursor",
value: "cursor"
},
{
title: "Claude Code",
value: "claude-code"
},
{
title: "Claude Desktop",
value: "claude-desktop"
},
{
title: "Windsurf",
value: "windsurf"
},
{
title: "VS Code / Copilot",
value: "vscode"
},
{
title: "Open Code",
value: "opencode"
},
{
title: "Other",
value: "other"
}
]
});
check(tool);
let scope = "global";
if (tool === "cursor" || tool === "vscode") {
const { s } = await prompts({
type: "select",
name: "s",
message: "Where should it be configured?",
choices: [{
title: "This project",
value: "project",
description: tool === "cursor" ? ".cursor/mcp.json" : ".vscode/mcp.json"
}, {
title: "Global (all projects)",
value: "global",
description: tool === "cursor" ? "~/.cursor/mcp.json" : "user settings"
}]
});
check(s);
scope = s;
}
const { registryUrl } = await prompts({
type: "text",
name: "registryUrl",
message: "Registry URL",
initial: DEFAULT_REGISTRY
});
const mcpArgs = buildMcpArgs(registryUrl?.trim() || DEFAULT_REGISTRY);
if (tool === "claude-code") await setupClaudeCode(mcpArgs);
else if (tool === "opencode") await setupOpenCode(mcpArgs);
else if (tool === "other") showJsonConfig({
command: "npx",
args: mcpArgs
});
else await writeMcpConfigInteractive(tool, scope, mcpArgs);
await offerSkillInstall("agent-auth-mcp");
showNextSteps([`${chalk.cyan("Docs")} ${PROTOCOL_URL}/docs/integrate-client`, `${chalk.cyan("GitHub")} https://github.com/better-auth/agent-auth`]);
console.log(chalk.green("\n✔ ") + chalk.bold("Done! ") + "Restart your AI tool to connect.\n");
}
async function setupClaudeCode(args) {
const { scope } = await prompts({
type: "select",
name: "scope",
message: "Where should it be configured?",
choices: [{
title: "This project",
value: "project",
description: "--scope project"
}, {
title: "Global (all projects)",
value: "user",
description: "--scope user"
}]
});
check(scope);
const cmd = [
"claude",
"mcp",
"add",
"agent-auth",
"--scope",
scope,
"--",
"npx",
...args
].join(" ");
console.log(chalk.bold.white("\nRun this command:"));
console.log(chalk.cyan(` ${cmd}\n`));
const { run } = await prompts({
type: "confirm",
name: "run",
message: "Run it now?",
initial: true
});
if (run) {
const s = yoctoSpinner({
text: "Adding MCP server to Claude Code…",
color: "white"
});
s.start();
try {
execSync(cmd, { stdio: "pipe" });
s.success("Added to Claude Code.");
} catch {
s.stop();
console.log(chalk.yellow("⚠ Could not run the command automatically."));
console.log(chalk.gray(" Run the command above manually."));
}
}
}
async function setupOpenCode(args) {
const configPath = path$1.join(process.cwd(), "opencode.json");
const display = "opencode.json";
const openCodeEntry = {
type: "stdio",
command: "npx",
args,
enabled: true
};
const { write } = await prompts({
type: "confirm",
name: "write",
message: `Write config to ${chalk.cyan(display)}?`,
initial: true
});
if (write) {
writeOpenCodeConfig(configPath, openCodeEntry);
console.log(chalk.green(`\n✓ Written to ${display}`));
} else {
const json = JSON.stringify({
$schema: "https://opencode.ai/config.json",
mcp: { "agent-auth": openCodeEntry }
}, null, 2);
console.log(chalk.bold.white("\nAdd to your opencode.json:\n"));
console.log(json.split("\n").map((line) => chalk.cyan(` ${line}`)).join("\n"));
console.log();
}
}
function writeOpenCodeConfig(configPath, entry) {
let config = {};
if (fs$2.existsSync(configPath)) try {
config = JSON.parse(fs$2.readFileSync(configPath, "utf-8"));
} catch {}
const mcp = config.mcp ?? {};
mcp["agent-auth"] = entry;
config.$schema = "https://opencode.ai/config.json";
config.mcp = mcp;
fs$2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
}
async function writeMcpConfigInteractive(tool, scope, args) {
const entry = {
command: "npx",
args
};
const configPath = getMcpConfigPath(tool, scope);
if (!configPath) {
showJsonConfig(entry);
return;
}
const display = displayPath(configPath, scope);
const { write } = await prompts({
type: "confirm",
name: "write",
message: `Write config to ${chalk.cyan(display)}?`,
initial: true
});
if (write) {
writeMcpConfig(configPath, entry);
console.log(chalk.green(`\n✓ Written to ${display}`));
} else showJsonConfig(entry);
}
async function setupCli() {
const { installCli } = await prompts({
type: "confirm",
name: "installCli",
message: `Install ${chalk.cyan(AGENT_CLI_PKG)} globally?`,
initial: true
});
if (installCli) {
const s = yoctoSpinner({
text: `Installing ${AGENT_CLI_PKG}…`,
color: "white"
});
s.start();
try {
execSync(`npm install -g ${AGENT_CLI_PKG}`, { stdio: "pipe" });
s.success(`${AGENT_CLI_PKG} installed globally.`);
} catch {
s.stop();
console.log(chalk.yellow("⚠ Could not install automatically. Run manually:"));
console.log(chalk.cyan(` npm install -g ${AGENT_CLI_PKG}\n`));
}
} else console.log(chalk.dim(`\n To install later: npm install -g ${AGENT_CLI_PKG}\n`));
await offerSkillInstall("agent-auth-cli");
console.log(chalk.bold.white("\nUsage:"));
console.log(chalk.gray(" # Discover a provider"));
console.log(chalk.cyan(" auth-agent discover https://api.example.com"));
console.log(chalk.gray("\n # Search the registry for providers"));
console.log(chalk.cyan(` auth-agent search "send email"`));
console.log(chalk.gray("\n # Connect an agent with capabilities"));
console.log(chalk.cyan(" auth-agent connect --provider <url> --capabilities <cap1> <cap2>"));
console.log(chalk.gray("\n # Execute a capability"));
console.log(chalk.cyan(` auth-agent execute <agent-id> <capability> --args '{"key":"value"}'`));
console.log(chalk.gray("\n # Run as MCP server"));
console.log(chalk.cyan(` auth-agent mcp`));
showNextSteps([`${chalk.cyan("Docs")} ${PROTOCOL_URL}/docs/integrate-client`, `${chalk.cyan("GitHub")} https://github.com/better-auth/agent-auth`]);
console.log(chalk.green("\n✔ ") + chalk.bold("Ready. ") + "Run auth-agent --help to see all commands.\n");
}
async function setupServer() {
const { source } = await prompts({
type: "select",
name: "source",
message: "How do you want to define capabilities?",
choices: [
{
title: "Default",
value: "manual",
description: "define capabilities in code"
},
{
title: "From an OpenAPI spec",
value: "openapi",
description: "derive capabilities from an OpenAPI document"
},
{
title: "From an MCP server",
value: "mcp",
description: "proxy an existing MCP server's tools"
}
]
});
check(source);
const { name } = await prompts({
type: "text",
name: "name",
message: "What's your service called?",
validate: (v) => v?.trim() ? true : "Name is required."
});
check(name);
const { description } = await prompts({
type: "text",
name: "description",
message: `Short description ${chalk.dim("(press Enter to skip)")}`
});
const desc = description?.trim() || void 0;
let sourceUrl;
if (source === "openapi") {
const { url } = await prompts({
type: "text",
name: "url",
message: `OpenAPI spec URL ${chalk.dim("(e.g. https://api.example.com/openapi.json)")}`,
validate: (v) => v?.trim() ? true : "URL is required."
});
check(url);
sourceUrl = url.trim();
} else if (source === "mcp") {
const { url } = await prompts({
type: "text",
name: "url",
message: `MCP server URL ${chalk.dim("(e.g. https://api.example.com/mcp)")}`,
validate: (v) => v?.trim() ? true : "URL is required."
});
check(url);
sourceUrl = url.trim();
}
const code = generateServerCode(name.trim(), desc, source, sourceUrl);
const { write } = await prompts({
type: "confirm",
name: "write",
message: "Generate an auth config file?",
initial: true
});
if (write) {
const { filePath } = await prompts({
type: "text",
name: "filePath",
message: "File path",
initial: "lib/auth.ts"
});
const target = filePath?.trim() || "lib/auth.ts";
if (fs$2.existsSync(target)) {
const { overwrite } = await prompts({
type: "confirm",
name: "overwrite",
message: `${chalk.yellow(target)} already exists. Overwrite?`,
initial: false
});
if (!overwrite) {
showCodeBlock(code, "auth config");
showServerOutro();
return;
}
}
const dir = path$1.dirname(target);
if (dir && dir !== "." && !fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
fs$2.writeFileSync(target, code);
console.log(chalk.green(`\n✓ Created ${target}`));
} else showCodeBlock(code, "auth config");
showServerOutro();
}
function generateServerCode(name, description, source, sourceUrl) {
const descLine = description ? `\n\t\t\tproviderDescription: ${JSON.stringify(description)},` : "";
if (source === "openapi" && sourceUrl) return `import { betterAuth } from "better-auth";
import { agentAuth } from "${AGENT_PLUGIN_PKG}";
import { createFromOpenAPI } from "${AGENT_PLUGIN_PKG}/openapi";
const spec = await fetch(${JSON.stringify(sourceUrl)}).then(r => r.json());
const openapi = createFromOpenAPI(spec, {
\tbaseUrl: ${JSON.stringify(sourceUrl.replace(/\/openapi\.json$|\/openapi\.yaml$|\/swagger\.json$|\/docs\/openapi$/, ""))},
});
export const auth = betterAuth({
\tplugins: [
\t\tagentAuth({
\t\t\tproviderName: ${JSON.stringify(name)},${descLine}
\t\t\t...openapi,
\t\t}),
\t],
});
`;
if (source === "mcp" && sourceUrl) return `import { betterAuth } from "better-auth";
import { agentAuth } from "${AGENT_PLUGIN_PKG}";
export const auth = betterAuth({
\tplugins: [
\t\tagentAuth({
\t\t\tproviderName: ${JSON.stringify(name)},${descLine}
\t\t\tmcpServer: ${JSON.stringify(sourceUrl)},
\t\t}),
\t],
});
`;
return `import { betterAuth } from "better-auth";
import { agentAuth } from "${AGENT_PLUGIN_PKG}";
export const auth = betterAuth({
\tplugins: [
\t\tagentAuth({
\t\t\tproviderName: ${JSON.stringify(name)},${descLine}
\t\t\tcapabilities: [
\t\t\t\t{
\t\t\t\t\tname: "example",
\t\t\t\t\tdescription: "An example capability — replace with your own",
\t\t\t\t\tinput: {
\t\t\t\t\t\ttype: "object",
\t\t\t\t\t\tproperties: {
\t\t\t\t\t\t\tmessage: { type: "string", description: "Input message" },
\t\t\t\t\t\t},
\t\t\t\t\t},
\t\t\t\t},
\t\t\t],
\t\t\tasync onExecute({ capability, arguments: args }) {
\t\t\t\tswitch (capability) {
\t\t\t\t\tcase "example":
\t\t\t\t\t\treturn { message: \`Hello from \${(args as Record<string, string>).message}\` };
\t\t\t\t\tdefault:
\t\t\t\t\t\tthrow new Error(\`Unknown capability: \${capability}\`);
\t\t\t\t}
\t\t\t},
\t\t}),
\t],
});
`;
}
function showServerOutro() {
console.log(chalk.bold.white("\nNext steps:\n"));
console.log(chalk.white(" 1. Install dependencies:"));
console.log(chalk.cyan(` npm install better-auth ${AGENT_PLUGIN_PKG}\n`));
console.log(chalk.white(" 2. Configure your database:"));
console.log(chalk.gray(" Better Auth needs a database to store agents, hosts, and grants."));
console.log(chalk.cyan(" https://www.better-auth.com/docs/concepts/database\n"));
console.log(chalk.white(" 3. Run database migrations:"));
console.log(chalk.cyan(" npx auth migrate\n"));
console.log(chalk.white(" 4. Expose the discovery endpoint at your app root:"));
console.log(chalk.gray(" GET /.well-known/agent-configuration"));
console.log(chalk.gray(" → return auth.api.getAgentConfiguration({ headers })\n"));
console.log(` ${chalk.cyan("Docs")} ${PROTOCOL_URL}/docs/build-server`);
console.log(` ${chalk.cyan("GitHub")} https://github.com/better-auth/agent-auth`);
console.log(chalk.green("\n✔ ") + chalk.bold("Server scaffolded. ") + "Follow the steps above to finish setup.\n");
}
async function offerSkillInstall(skillName) {
const { installSkill } = await prompts({
type: "confirm",
name: "installSkill",
message: `Install the ${chalk.cyan(skillName)} skill for your coding agents?`,
initial: true
});
if (!installSkill) return;
const cmd = `npx -y skills add ${SKILLS_REPO} --skill ${skillName}`;
const s = yoctoSpinner({
text: `Installing ${skillName} skill…`,
color: "white"
});
s.start();
try {
execSync(cmd, { stdio: "pipe" });
s.success(`${skillName} skill installed.`);
} catch {
s.stop();
console.log(chalk.yellow("⚠ Could not install automatically. Run manually:"));
console.log(chalk.cyan(` ${cmd}\n`));
}
}
function buildMcpArgs(registry) {
const args = [
"-y",
AGENT_CLI_PKG,
"mcp"
];
if (registry && registry !== DEFAULT_REGISTRY) args.push("--registry-url", registry);
return args;
}
function getMcpConfigPath(tool, scope) {
const home = os$1.homedir();
switch (tool) {
case "cursor": return scope === "global" ? path$1.join(home, ".cursor", "mcp.json") : path$1.join(process.cwd(), ".cursor", "mcp.json");
case "claude-desktop":
if (process.platform === "win32") return path$1.join(process.env.APPDATA || home, "Claude", "claude_desktop_config.json");
if (process.platform === "darwin") return path$1.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
return path$1.join(home, ".config", "Claude", "claude_desktop_config.json");
case "windsurf": return path$1.join(home, ".codeium", "windsurf", "mcp_config.json");
case "vscode": return scope === "global" ? null : path$1.join(process.cwd(), ".vscode", "mcp.json");
default: return null;
}
}
function writeMcpConfig(configPath, entry) {
let config = {};
if (fs$2.existsSync(configPath)) try {
config = JSON.parse(fs$2.readFileSync(configPath, "utf-8"));
} catch {}
const servers = config.mcpServers ?? {};
servers["agent-auth"] = entry;
config.mcpServers = servers;
const dir = path$1.dirname(configPath);
if (!fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
fs$2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
}
function displayPath(filePath, scope) {
if (scope === "project") return path$1.relative(process.cwd(), filePath) || filePath;
return filePath.replace(os$1.homedir(), "~");
}
function showJsonConfig(entry) {
const json = JSON.stringify({ mcpServers: { "agent-auth": entry } }, null, 2);
console.log(chalk.bold.white("\nAdd to your MCP configuration:\n"));
console.log(json.split("\n").map((line) => chalk.cyan(` ${line}`)).join("\n"));
console.log();
}
function showCodeBlock(code, title) {
console.log(chalk.bold.white(`\n${title}:\n`));
console.log(code.split("\n").map((line) => chalk.dim(` ${line}`)).join("\n"));
}
function showNextSteps(lines) {
console.log(chalk.bold.white("\nLearn more:\n"));
for (const line of lines) console.log(` ${line}`);
}
const ai = new Command("ai").description("Interactive setup for Agent Auth — AI agent authentication").action(aiAction);
//#endregion
//#region src/generators/drizzle.ts
function convertToSnakeCase(str, camelCase) {
return camelCase ? str : toSnakeCase(str);
}
const generateDrizzleSchema = async ({ options, file, adapter }) => {
const tables = getAuthTables(options);
const filePath = file || "./auth-schema.ts";
const databaseType = adapter.options?.provider;
if (!databaseType) throw new Error(`Database provider type is undefined during Drizzle schema generation. Please define a \`provider\` in the Drizzle adapter config. Read more at https://better-auth.com/docs/adapters/drizzle`);
const fileExist = existsSync(filePath);
let code = generateImport({
databaseType,
tables,
options
});
const getModelName = initGetModelName({
schema: tables,
usePlural: adapter.options?.adapterConfig?.usePlural
});
const getFieldName = initGetFieldName({
schema: tables,
usePlural: adapter.options?.adapterConfig?.usePlural
});
for (const tableKey in tables) {
const table = tables[tableKey];
if (table.disableMigrations) continue;
const modelName = getModelName(tableKey);
const fields = table.fields;
function getType(name, field) {
if (!databaseType) throw new Error(`Database provider type is undefined during Drizzle schema generation. Please define a \`provider\` in the Drizzle adapter config. Read more at https://better-auth.com/docs/adapters/drizzle`);
name = convertToSnakeCase(name, adapter.options?.camelCase);
if (field.references?.field === "id") {
const useNumberId = options.advanced?.database?.generateId === "serial";
const useUUIDs = options.advanced?.database?.generateId === "uuid";
if (useNumberId) if (databaseType === "pg") return `integer('${name}')`;
else if (databaseType === "mysql") return `int('${name}')`;
else return `integer('${name}')`;
if (useUUIDs && databaseType === "pg") return `uuid('${name}')`;
if (field.references.field) {
if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
}
return `text('${name}')`;
}
const type = field.type;
if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
}[databaseType];
else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
const dbTypeMap = {
string: {
sqlite: `text('${name}')`,
pg: `text('${name}')`,
mysql: field.unique ? `varchar('${name}', { length: 255 })` : field.references ? `varchar('${name}', { length: 36 })` : field.sortable ? `varchar('${name}', { length: 255 })` : field.index ? `varchar('${name}', { length: 255 })` : `text('${name}')`
},
boolean: {
sqlite: `integer('${name}', { mode: 'boolean' })`,
pg: `boolean('${name}')`,
mysql: `boolean('${name}')`
},
number: {
sqlite: `integer('${name}')`,
pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
},
date: {
sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
pg: `timestamp('${name}')`,
mysql: `timestamp('${name}', { fsp: 3 })`
},
"number[]": {
sqlite: `text('${name}', { mode: "json" })`,
pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
mysql: `text('${name}', { mode: 'json' })`
},
"string[]": {
sqlite: `text('${name}', { mode: "json" })`,
pg: `text('${name}').array()`,
mysql: `text('${name}', { mode: "json" })`
},
json: {
sqlite: `text('${name}', { mode: "json" })`,
pg: `jsonb('${name}')`,
mysql: `json('${name}', { mode: "json" })`
}
}[type];
if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
return dbTypeMap[databaseType];
}
let id = "";
const useNumberId = options.advanced?.database?.generateId === "serial";
if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
else id = `int("id").autoincrement().primaryKey()`;
else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
else if (databaseType === "pg") id = `text('id').primaryKey()`;
else id = `text('id').primaryKey()`;
const indexes = [];
const assignIndexes = (indexes) => {
if (!indexes.length) return "";
const code = [`, (table) => [`];
for (const index of indexes) code.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
code.push(`]`);
return code.join("\n");
};
const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
id: ${id},
${Object.keys(fields).map((field) => {
const attr = fields[field];
const fieldName = attr.fieldName || field;
let type = getType(fieldName, attr);
if (attr.index && !attr.unique) indexes.push({
type: "index",
name: `${modelName}_${fieldName}_idx`,
on: fieldName
});
if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
else type += `.defaultNow()`;
} else if (typeof attr.defaultValue === "string") type += `.default(${JSON.stringify(attr.defaultValue)})`;
else if (Array.isArray(attr.defaultValue)) {
const elements = attr.defaultValue.map((value) => JSON.stringify(value)).join(", ");
type += `.default([${elements}])`;
} else if (typeof attr.defaultValue === "object" && attr.defaultValue !== null) type += `.default(${JSON.stringify(attr.defaultValue)})`;
else type += `.default(${attr.defaultValue})`;
if (attr.onUpdate && attr.type === "date") {
if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
}
return `${fieldName}: ${type}${attr.required !== false ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
model: attr.references.model,
field: attr.references.field
})}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
}).join(",\n ")}
}${assignIndexes(indexes)});`;
code += `\n${schema}\n`;
}
let relationsString = "";
for (const tableKey in tables) {
const table = tables[tableKey];
if (table.disableMigrations) continue;
const modelName = getModelName(tableKey);
const oneRelations = [];
const manyRelations = [];
const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
const foreignFieldCounts = /* @__PURE__ */ new Map();
for (const [_, field] of foreignFields) {
const referencedModel = getModelName(field.references.model);
foreignFieldCounts.set(referencedModel, (foreignFieldCounts.get(referencedModel) ?? 0) + 1);
}
const usedOneRelationKeys = /* @__PURE__ */ new Set();
for (const [fieldName, field] of foreignFields) {
const referencedModel = field.references.model;
const hasMultipleRelations = (foreignFieldCounts.get(getModelName(referencedModel)) ?? 0) > 1;
let relationKey = hasMultipleRelations ? fieldName.replace(/Id$/, "") : getModelName(referencedModel);
if (usedOneRelationKeys.has(relationKey)) relationKey = fieldName;
if (usedOneRelationKeys.has(relationKey)) {
let suffix = 2;
while (usedOneRelationKeys.has(`${relationKey}_${suffix}`)) suffix++;
relationKey = `${relationKey}_${suffix}`;
}
usedOneRelationKeys.add(relationKey);
const fieldRef = `${getModelName(tableKey)}.${getFieldName({
model: tableKey,
field: fieldName
})}`;
const referenceRef = `${getModelName(referencedModel)}.${getFieldName({
model: referencedModel,
field: field.references.field || "id"
})}`;
oneRelations.push({
key: relationKey,
model: getModelName(referencedModel),
type: "one",
relationName: hasMultipleRelations ? `${getModelName(tableKey)}_${fieldName}` : void 0,
reference: {
field: fieldRef,
references: referenceRef
}
});
}
const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
for (const [modelName, otherTable] of otherModels) {
const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
if (foreignKeysPointingHere.length === 0) continue;
for (const [fieldName, field] of foreignKeysPointingHere) {
const relationType = field.unique ? "one" : "many";
let relationKey = getModelName(modelName);
if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
const hasMultipleRelations = foreignKeysPointingHere.length > 1;
if (hasMultipleRelations) relationKey = `${relationKey}By${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
manyRelations.push({
key: relationKey,
model: getModelName(modelName),
type: relationType,
relationName: hasMultipleRelations ? `${getModelName(modelName)}_${fieldName}` : void 0
});
}
}
const hasForwardOne = oneRelations.length > 0;
const hasReverseOne = manyRelations.some((relation) => relation.type === "one");
const hasReverseMany = manyRelations.some((relation) => relation.type === "many");
const hasOne = hasForwardOne || hasReverseOne;
const hasMany = hasReverseMany;
const renderOneRelation = (relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
fields: [${relation.reference.field}],
references: [${relation.reference.references}],
${relation.relationName ? `relationName: "${relation.relationName}",` : ""}
})` : "";
const renderReverseRelation = ({ key, model, type, relationName }) => {
return ` ${key}: ${type === "one" ? "one" : "many"}(${model}${relationName ? `, { relationName: "${relationName}" }` : ""})`;
};
if (hasOne || hasMany) {
const helpers = [hasOne ? "one" : null, hasMany ? "many" : null].filter(Boolean).join(", ");
const relationEntries = [...oneRelations.map(renderOneRelation).filter((x) => x !== ""), ...manyRelations.map(renderReverseRelation)].join(",\n ");
const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ ${helpers} }) => ({
${relationEntries}
}))`;
relationsString += `\n${tableRelation}\n`;
}
}
code += `\n${relationsString}`;
return {
code: await prettier.format(code, { parser: "typescript" }),
fileName: filePath,
overwrite: fileExist
};
};
function generateImport({ databaseType, tables, options }) {
const rootImports = ["relations"];
const coreImports = [];
let hasBigint = false;
let hasJson = false;
for (const table of Object.values(tables)) {
for (const field of Object.values(table.fields)) {
if (field.bigint) hasBigint = true;
if (field.type === "json") hasJson = true;
}
if (hasJson && hasBigint) break;
}
const useNumberId = options.advanced?.database?.generateId === "serial";
const useUUIDs = options.advanced?.database?.generateId === "uuid";
coreImports.push(`${databaseType}Table`);
coreImports.push(databaseType === "mysql" ? "varchar, text" : databaseType === "pg" ? "text" : "text");
coreImports.push(hasBigint ? databaseType !== "sqlite" ? "bigint" : "" : "");
coreImports.push(databaseType !== "sqlite" ? "timestamp, boolean" : "");
if (databaseType === "mysql") {
const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
if (useNumberId || hasNonBigintNumber) coreImports.push("int");
if (Object.values(tables).some((table) => Object.values(table.fields).some((field) => typeof field.type !== "string" && Array.isArray(field.type) && field.type.every((x) => typeof x === "string")))) coreImports.push("mysqlEnum");
} else if (databaseType === "pg") {
if (useUUIDs) rootImports.push("sql");
const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
if (hasNonBigintNumber || options.advanced?.database?.generateId === "serial" && hasFkToId) coreImports.push("integer");
} else coreImports.push("integer");
if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
if (hasJson) {
if (databaseType === "pg") coreImports.push("jsonb");
if (databaseType === "mysql") coreImports.push("json");
}
if (databaseType === "sqlite" && Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.type === "date" && field.defaultValue && typeof field.defaultValue === "function" && field.defaultValue.toString().includes("new Date()")))) rootImports.push("sql");
if (Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique))) coreImports.push("index");
return `${rootImports.length > 0 ? `import { ${rootImports.join(", ")} } from "drizzle-orm";\n` : ""}import { ${coreImports.map((x) => x.trim()).filter((x) => x !== "").join(", ")} } from "drizzle-orm/${databaseType}-core";\n`;
}
//#endregion
//#region src/generators/kysely.ts
const generateKyselySchema = async ({ options, file }) => {
const { compileMigrations } = await getMigrations(options);
const migrations = await compileMigrations();
return {
code: migrations.trim() === ";" ? "" : migrations,
fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
};
};
//#endregion
//#region src/utils/helper.ts
async function tryCatch(promise) {
try {
return {
data: await promise,
error: null
};
} catch (error) {
return {
data: null,
error
};
}
}
const generateSecretHash = () => {
return Crypto.randomBytes(32).toString("hex");
};
const spawnCommand = (cmd, cwd = process.cwd()) => new Promise((resolve, reject) => {
const child = spawn(cmd, {
cwd,
stdio: "inherit",
shell: true
});
child.on("close", (code, signal) => {
if (code !== 0 && code !== null) reject(/* @__PURE__ */ new Error(`Exited with code ${code}`));
else if (signal) reject(/* @__PURE__ */ new Error(`Killed with signal ${signal}`));
else resolve();
});
child.on("error", reject);
});
//#endregion
//#region src/utils/get-package-info.ts
function getPackageInfo(cwd) {
const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
}
function getPrismaVersion(cwd) {
try {
const packageInfo = getPackageInfo(cwd);
const prismaVersion = packageInfo.dependencies?.prisma || packageInfo.devDependencies?.prisma || packageInfo.dependencies?.["@prisma/client"] || packageInfo.devDependencies?.["@prisma/client"];
if (!prismaVersion) return null;
const match = prismaVersion.match(/(\d+)/);
return match ? parseInt(match[1], 10) : null;
} catch {
return null;
}
}
/**
* Checks if a package has a specific dependency.
*
* @param packageJson The package.json object
* @param dependency The dependency to check for
* @returns true if the package has the dependency
*/
function hasDependency(packageJson, dependency) {
let hasDependency = false;
if (packageJson.dependencies?.[dependency] || packageJson.devDependencies?.[dependency] || packageJson.peerDependencies?.[dependency] || packageJson.optionalDependencies?.[dependency]) hasDependency = true;
return hasDependency;
}
/**
* Checks if a directory is a monorepo root by looking for common monorepo indicators.
*
* @param dir Directory to check
* @returns true if the directory appears to be a monorepo root
*/
async function isMonorepoRoot(dir) {
const { data: files } = await tryCatch(fs$1.readdir(dir, "utf-8"));
if (!files) return false;
if (files.includes("pnpm-workspace.yaml")) return true;
if (files.includes("package.json")) {
const packageJsonPath = path.join(dir, "package.json");
const { data } = await tryCatch(fs$1.readFile(packageJsonPath, "utf-8"));
if (data) try {
const packageJson = JSON.parse(data);
if (packageJson.workspaces && (Array.isArray(packageJson.workspaces) || typeof packageJson.workspaces === "object")) return true;
} catch {}
}
return [
"lerna.json",
"turbo.json",
"nx.json",
"rush.json"
].some((indicator) => files.includes(indicator));
}
/**
* Finds the monorepo root by walking up the directory tree.
*
* @param startDir Starting directory
* @returns Path to monorepo root, or null if not found
*/
async function findMonorepoRoot(startDir) {
let currentDir = path.resolve(startDir);
const root = path.parse(currentDir).root;
while (currentDir !== root) {
if (await isMonorepoRoot(currentDir)) return currentDir;
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) break;
currentDir = parentDir;
}
return null;
}
//#endregion
//#region src/generators/prisma.ts
const generatePrismaSchema = async ({ adapter, options, file }) => {
const provider = adapter.options?.provider || "postgresql";
const tables = getAuthTables(options);
const filePath = file || "./prisma/schema.prisma";
const resolvedFilePath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
const schemaPrismaExist = existsSync(resolvedFilePath);
const getModelName = initGetModelName({
schema: getAuthTables(options),
usePlural: adapter.options?.adapterConfig?.usePlural
});
const getFieldName = initGetFieldName({
schema: getAuthTables(options),
usePlural: false
});
let schemaPrisma = "";
if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(resolvedFilePath, "utf-8");
else schemaPrisma = getNewPrisma(provider, process.cwd());
const prismaVersion = getPrismaVersion(process.cwd());
if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
const generator = builder.findByType("generator", { name: "client" });
if (generator && generator.properties) {
const providerProp = generator.properties.find((prop) => prop.type === "assignment" && prop.key === "provider");
if (providerProp && providerProp.value === "\"prisma-client-js\"") providerProp.value = "\"prisma-client\"";
}
const datasource = builder.findByType("datasource", { name: "db" });
if (datasource && datasource.properties) {
const urlIndex = datasource.properties.findIndex((prop) => prop.type === "assignment" && prop.key === "url");
if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
}
});
const manyToManyRelations = /* @__PURE__ */ new Map();
for (const table in tables) {
const fields = tables[table]?.fields;
for (const field in fields) {
const attr = fields[field];
if (attr.references) {
const referencedOriginalModel = attr.references.model;
const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
manyToManyRelations.get(referencedModelNameCap).add(currentModelNameCap);
}
}
}
const indexedFields = /* @__PURE__ */ new Map();
for (const table in tables) {
const fields = tables[table]?.fields;
const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
indexedFields.set(modelName, []);
for (const field in fields) {
const attr = fields[field];
if (attr.index && !attr.unique) {
const fieldName = attr.fieldName || field;
indexedFields.get(modelName).push(fieldName);
}
}
}
const schema = produceSchema(schemaPrisma, (builder) => {
for (const table in tables) {
if (tables[table]?.disableMigrations) continue;
const originalTableName = table;
const customModelName = tables[table]?.modelName || table;
const modelName = capitalizeFirstLetter(getModelName(customModelName));
const fields = tables[table]?.fields;
function getType({ isBigint, isOptional, type }) {
if (type === "string") return isOptional ? "String?" : "String";
if (type === "number" && isBigint) return isOptional ? "BigInt?" : "BigInt";
if (type === "number") return isOptional ? "Int?" : "Int";
if (type === "boolean") return isOptional ? "Boolean?" : "Boolean";
if (type === "date") return isOptional ? "DateTime?" : "DateTime";
if (type === "json") {
if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
return isOptional ? "Json?" : "Json";
}
if (type === "string[]") {
if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
return "String[]";
}
if (type === "number[]") {
if (provider === "sqlite" || provider === "mysql") return "String";
return "Int[]";
}
}
function getFieldTypeParts(type) {
const isArray = type.endsWith("[]");
const typeWithoutArray = isArray ? type.slice(0, -2) : type;
const isOptional = typeWithoutArray.endsWith("?");
return {
fieldType: isOptional ? typeWithoutArray.slice(0, -1) : typeWithoutArray,
isArray,
isOptional
};
}
const prismaModel = builder.findByType("model", { name: modelName });
if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
else {
const useNumberId = options.advanced?.database?.generateId === "serial";
const useUUIDs = options.advanced?.database?.generateId === "uuid";
if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
else builder.model(modelName).field("id", "String").attribute("id");
}
for (const field in fields) {
const attr = fields[field];
const fieldName = attr.fieldName || field;
const useUUIDs = options.advanced?.database?.generateId === "uuid";
const useNumberId = options.advanced?.database?.generateId === "serial";
const fieldType = field === "id" && useNumberId ? getType({
isBigint: false,
isOptional: false,
type: "number"
}) : getType({
isBigint: attr?.bigint || false,
isOptional: attr?.required === false,
type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
});
if (prismaModel) {
const isAlreadyExist = builder.findByType("field", {
name: fieldName,
within: prismaModel.properties
});
if (isAlreadyExist) {
if (fieldType && typeof isAlreadyExist.fieldType === "string") {
const fieldTypeParts = getFieldTypeParts(fieldType);
const existingFieldTypeParts = getFieldTypeParts(isAlreadyExist.fieldType);
if ((existingFieldTypeParts.fieldType === "Int" || existingFieldTypeParts.fieldType === "BigInt") && (fieldTypeParts.fieldType === "Int" || fieldTypeParts.fieldType === "BigInt")) {
isAlreadyExist.fieldType = fieldTypeParts.fieldType;
isAlreadyExist.optional = fieldTypeParts.isOptional || void 0;
isAlreadyExist.array = fieldTypeParts.isArray || void 0;
}
}
continue;
}
}
if (!fieldType) throw new Error(`Unsupported Prisma field type for model "${modelName}", field "${fieldName}"${attr.type ? ` (source type: "${attr.type}")` : ""}.`);
const fieldBuilder = builder.model(modelName).field(fieldName, fieldType);
if (field === "id") {
fieldBuilder.attribute("id");
if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
}
if (attr.unique) builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
if (attr.defaultValue !== void 0) {
if (Array.isArray(attr.defaultValue)) {
if (attr.type === "json") {
if (Object.prototype.toString.call(attr.defaultValue[0]) === "[object Object]") {
fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
continue;
}
const jsonArray = [];
for (const value of attr.defaultValue) jsonArray.push(value);
fieldBuilder.attribute(`default("${JSON.stringify(jsonArray).replace(/"/g, "\\\"")}")`);
continue;
}
if (attr.defaultValue.length === 0) {
fieldBuilder.attribute(`default([])`);
continue;
} else if (typeof attr.defaultValue[0] === "string" && attr.type === "string[]") {
const valueArray = [];
for (const value of attr.defaultValue) valueArray.push(JSON.stringify(value));
fieldBuilder.attribute(`default([${valueArray}])`);
} else if (typeof attr.defaultValue[0] === "number") {
const valueArray = [];
for (const value of attr.defaultValue) valueArray.push(`${value}`);
fieldBuilder.attribute(`default([${valueArray}])`);
}
} else if (typeof attr.defaultValue === "object" && !Array.isArray(attr.defaultValue) && attr.defaultValue !== null) {
if (Object.entries(attr.defaultValue).length === 0) {
fieldBuilder.attribute(`default("{}")`);
continue;
}
fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
}
if (field === "createdAt") fieldBuilder.attribute("default(now())");
else if (typeof attr.defaultValue === "string" && provider !== "mysql") fieldBuilder.attribute(`default("${attr.defaultValue}")`);
else if (typeof attr.defaultValue === "boolean" || typeof attr.defaultValue === "number") fieldBuilder.attribute(`default(${attr.defaultValue})`);
else if (typeof attr.defaultValue === "function") {}
}
if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
else if (attr.onUpdate) {}
if (attr.references) {
if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
const referencedOriginalModelName = getModelName(attr.references.model);
const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
let action = "Cascade";
if (attr.references.onDelete === "no action") action = "NoAction";
else if (attr.references.onDelete === "set null") action = "SetNull";
else if (attr.references.onDelete === "set default") action = "SetDefault";
else if (attr.references.onDelete === "restrict") action = "Restrict";
const relationField = `relation(fields: [${getFieldName({
model: originalTableName,
field: fieldName
})}], references: [${getFieldName({
model: attr.references.model,
field: attr.references.field
})}], onDelete: ${action})`;
builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${attr.required === false ? "?" : ""}`).attribute(relationField);
}
if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
}
if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
const isUnique = fkFieldAttr?.unique === true;
const fieldName = isUnique || adapter.options?.usePlural === true ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
if (!builder.findByType("field", {
name: fieldName,
within: prismaModel?.properties
})) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
}
const indexedFieldsForModel = indexedFields.get(modelName);
if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
if (prismaModel) {
if (prismaModel.properties.some((v) => v.type === "attribute" && v.name === "index" && JSON.stringify(v.args[0]?.value).includes(fieldName))) continue;
}
const field = Object.entries(fields).find(([key, attr]) => (attr.fieldName || key) === fieldName)?.[1];
let indexField = fieldName;
if (provider === "mysql" && field && field.type === "string") {
const useNumberId = options.advanced?.database?.generateId === "serial";
const useUUIDs = options.advanced?.database?.generateId === "uuid";
if (field.references?.field === "id" && (useNumberId || useUUIDs)) indexField = `${fieldName}`;
else indexField = `${fieldName}(length: 191)`;
}
builder.model(modelName).blockAttribute(`index([${indexField}])`);
}
const hasAttribute = builder.findByType("attribute", {
name: "map",
within: prismaModel?.properties
});
const hasChanged = customModelName !== originalTableName;
if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
}
});
const schemaChanged = schema.trim() !== schemaPrisma.trim();
return {
code: schemaChanged ? schema : "",
fileName: filePath,
overwrite: schemaPrismaExist && schemaChanged
};
};
const getNewPrisma = (provider, cwd) => {
const prismaVersion = getPrismaVersion(cwd);
const isV7 = prismaVersion && prismaVersion >= 7;
const clientProvider = isV7 ? "prisma-client" : "prisma-client-js";
if (isV7) return `generator client {
provider = "${clientProvider}"
}
datasource db {
provider = "${provider}"
}`;
return `generator client {
provider = "${clientProvider}"
}
datasource db {
provider = "${provider}"
url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
}`;
};
//#endregion
//#region src/generators/index.ts
const adapters = {
prisma: generatePrismaSchema,
drizzle: gen