auth 1.7.0-beta.2 → 1.7.0-beta.4

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/dist/index.mjs CHANGED
@@ -10,22 +10,23 @@ import path, { join } from "node:path";
10
10
  import chalk from "chalk";
11
11
  import prompts from "prompts";
12
12
  import yoctoSpinner from "yocto-spinner";
13
+ import { APIError, BetterAuthError } from "@better-auth/core/error";
14
+ import { betterAuth } from "better-auth";
15
+ import * as z from "zod";
16
+ import babelPresetReact from "@babel/preset-react";
17
+ import babelPresetTypeScript from "@babel/preset-typescript";
18
+ import { loadConfig } from "c12";
19
+ import { createPathsMatcher, getTsconfig, parseTsconfig } from "get-tsconfig";
13
20
  import fs$1 from "node:fs/promises";
14
21
  import { createTelemetry, getTelemetryAuthConfig } from "@better-auth/telemetry";
15
22
  import { getAdapter } from "better-auth/db/adapter";
16
- import * as z from "zod";
23
+ import { capitalizeFirstLetter, toSnakeCase } from "@better-auth/core/utils/string";
17
24
  import { initGetFieldName, initGetModelName } from "better-auth/adapters";
18
25
  import { getAuthTables } from "better-auth/db";
19
26
  import prettier, { format } from "prettier";
20
27
  import { getMigrations } from "better-auth/db/migration";
21
- import { capitalizeFirstLetter } from "@better-auth/core/utils/string";
22
28
  import { produceSchema } from "@mrleebo/prisma-ast";
23
29
  import Crypto from "node:crypto";
24
- import babelPresetReact from "@babel/preset-react";
25
- import babelPresetTypeScript from "@babel/preset-typescript";
26
- import { BetterAuthError } from "@better-auth/core/error";
27
- import { loadConfig } from "c12";
28
- import { createPathsMatcher, getTsconfig, parseTsconfig } from "get-tsconfig";
29
30
  import open from "open";
30
31
  import { env } from "@better-auth/core/env";
31
32
  import { log } from "@clack/prompts";
@@ -577,1139 +578,1237 @@ function showNextSteps(lines) {
577
578
  }
578
579
  const ai = new Command("ai").description("Interactive setup for Agent Auth — AI agent authentication").action(aiAction);
579
580
  //#endregion
580
- //#region src/generators/drizzle.ts
581
- function convertToSnakeCase(str, camelCase) {
582
- if (camelCase) return str;
583
- return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
581
+ //#region src/utils/add-cloudflare-modules.ts
582
+ const createModule = () => {
583
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
584
+ const createStub = (label) => {
585
+ const handler = {
586
+ get(_, prop) {
587
+ if (prop === "toString") return () => label;
588
+ if (prop === "valueOf") return () => label;
589
+ if (prop === Symbol.toPrimitive) return () => label;
590
+ if (prop === Symbol.toStringTag) return "Object";
591
+ if (prop === "then") return undefined;
592
+ return createStub(label + "." + String(prop));
593
+ },
594
+ apply(_, __, args) {
595
+ return createStub(label + "()")
596
+ },
597
+ construct() {
598
+ return createStub(label + "#instance");
599
+ },
600
+ };
601
+ const fn = () => createStub(label + "()");
602
+ return new Proxy(fn, handler);
603
+ };
604
+
605
+ class WorkerEntrypoint {
606
+ constructor(ctx, env) {
607
+ this.ctx = ctx;
608
+ this.env = env;
609
+ }
584
610
  }
585
- const generateDrizzleSchema = async ({ options, file, adapter }) => {
586
- const tables = getAuthTables(options);
587
- const filePath = file || "./auth-schema.ts";
588
- const databaseType = adapter.options?.provider;
589
- 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`);
590
- const fileExist = existsSync(filePath);
591
- let code = generateImport({
592
- databaseType,
593
- tables,
594
- options
595
- });
596
- const getModelName = initGetModelName({
597
- schema: tables,
598
- usePlural: adapter.options?.adapterConfig?.usePlural
599
- });
600
- const getFieldName = initGetFieldName({
601
- schema: tables,
602
- usePlural: adapter.options?.adapterConfig?.usePlural
603
- });
604
- for (const tableKey in tables) {
605
- const table = tables[tableKey];
606
- const modelName = getModelName(tableKey);
607
- const fields = table.fields;
608
- function getType(name, field) {
609
- 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`);
610
- name = convertToSnakeCase(name, adapter.options?.camelCase);
611
- if (field.references?.field === "id") {
612
- const useNumberId = options.advanced?.database?.generateId === "serial";
613
- const useUUIDs = options.advanced?.database?.generateId === "uuid";
614
- if (useNumberId) if (databaseType === "pg") return `integer('${name}')`;
615
- else if (databaseType === "mysql") return `int('${name}')`;
616
- else return `integer('${name}')`;
617
- if (useUUIDs && databaseType === "pg") return `uuid('${name}')`;
618
- if (field.references.field) {
619
- if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
611
+
612
+ class DurableObject {
613
+ constructor(state, env) {
614
+ this.state = state;
615
+ this.env = env;
616
+ }
617
+ }
618
+
619
+ class RpcTarget {
620
+ constructor(value) {
621
+ this.value = value;
622
+ }
623
+ }
624
+
625
+ const RpcStub = RpcTarget;
626
+
627
+ const env = createStub("env");
628
+ const caches = createStub("caches");
629
+ const scheduler = createStub("scheduler");
630
+ const executionCtx = createStub("executionCtx");
631
+
632
+ export { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint, caches, env, executionCtx, scheduler };
633
+
634
+ const defaultExport = {
635
+ DurableObject,
636
+ RpcStub,
637
+ RpcTarget,
638
+ WorkerEntrypoint,
639
+ caches,
640
+ env,
641
+ executionCtx,
642
+ scheduler,
643
+ };
644
+
645
+ export default defaultExport;
646
+ // jiti dirty hack: .unknown
647
+ `)}`;
648
+ };
649
+ const CLOUDFLARE_STUB_MODULE = createModule();
650
+ function addCloudflareModules(aliases, _cwd) {
651
+ if (!aliases["cloudflare:workers"]) aliases["cloudflare:workers"] = CLOUDFLARE_STUB_MODULE;
652
+ if (!aliases["cloudflare:test"]) aliases["cloudflare:test"] = CLOUDFLARE_STUB_MODULE;
653
+ }
654
+ //#endregion
655
+ //#region src/utils/add-svelte-kit-env-modules.ts
656
+ /**
657
+ * Adds SvelteKit environment modules and path aliases
658
+ * @param aliases - The aliases object to populate
659
+ * @param cwd - Current working directory (optional, defaults to process.cwd())
660
+ */
661
+ function addSvelteKitEnvModules(aliases, cwd) {
662
+ const workingDir = cwd || process.cwd();
663
+ aliases["$env/dynamic/private"] = createDataUriModule(createDynamicEnvModule());
664
+ aliases["$env/dynamic/public"] = createDataUriModule(createDynamicEnvModule());
665
+ aliases["$env/static/private"] = createDataUriModule(createStaticEnvModule(filterPrivateEnv("PUBLIC_", "")));
666
+ aliases["$env/static/public"] = createDataUriModule(createStaticEnvModule(filterPublicEnv("PUBLIC_", "")));
667
+ const svelteKitAliases = getSvelteKitPathAliases(workingDir);
668
+ Object.assign(aliases, svelteKitAliases);
669
+ }
670
+ function getSvelteKitPathAliases(cwd) {
671
+ const aliases = {};
672
+ const packageJsonPath = path.join(cwd, "package.json");
673
+ const svelteConfigPath = path.join(cwd, "svelte.config.js");
674
+ const svelteConfigTsPath = path.join(cwd, "svelte.config.ts");
675
+ let isSvelteKitProject = false;
676
+ if (fs.existsSync(packageJsonPath)) try {
677
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
678
+ isSvelteKitProject = !!{
679
+ ...packageJson.dependencies,
680
+ ...packageJson.devDependencies
681
+ }["@sveltejs/kit"];
682
+ } catch {}
683
+ if (!isSvelteKitProject) isSvelteKitProject = fs.existsSync(svelteConfigPath) || fs.existsSync(svelteConfigTsPath);
684
+ if (!isSvelteKitProject) return aliases;
685
+ const libPaths = [path.join(cwd, "src", "lib"), path.join(cwd, "lib")];
686
+ for (const libPath of libPaths) if (fs.existsSync(libPath)) {
687
+ aliases["$lib"] = libPath;
688
+ for (const subPath of [
689
+ "server",
690
+ "utils",
691
+ "components",
692
+ "stores"
693
+ ]) {
694
+ const subDir = path.join(libPath, subPath);
695
+ if (fs.existsSync(subDir)) aliases[`$lib/${subPath}`] = subDir;
696
+ }
697
+ break;
698
+ }
699
+ aliases["$app/server"] = createDataUriModule(createAppServerModule());
700
+ const customAliases = getSvelteConfigAliases(cwd);
701
+ Object.assign(aliases, customAliases);
702
+ return aliases;
703
+ }
704
+ function getSvelteConfigAliases(cwd) {
705
+ const aliases = {};
706
+ const configPaths = [path.join(cwd, "svelte.config.js"), path.join(cwd, "svelte.config.ts")];
707
+ for (const configPath of configPaths) if (fs.existsSync(configPath)) {
708
+ try {
709
+ const aliasMatch = fs.readFileSync(configPath, "utf-8").match(/alias\s*:\s*\{([^}]+)\}/);
710
+ if (aliasMatch && aliasMatch[1]) {
711
+ const aliasMatches = aliasMatch[1].matchAll(/['"`](\$[^'"`]+)['"`]\s*:\s*['"`]([^'"`]+)['"`]/g);
712
+ for (const match of aliasMatches) {
713
+ const [, alias, target] = match;
714
+ if (alias && target) {
715
+ aliases[alias + "/*"] = path.resolve(cwd, target) + "/*";
716
+ aliases[alias] = path.resolve(cwd, target);
717
+ }
620
718
  }
621
- return `text('${name}')`;
622
719
  }
623
- const type = field.type;
624
- if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
625
- sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
626
- pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
627
- mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
628
- }[databaseType];
629
- else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
630
- const dbTypeMap = {
631
- string: {
632
- sqlite: `text('${name}')`,
633
- pg: `text('${name}')`,
634
- 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}')`
635
- },
636
- boolean: {
637
- sqlite: `integer('${name}', { mode: 'boolean' })`,
638
- pg: `boolean('${name}')`,
639
- mysql: `boolean('${name}')`
640
- },
641
- number: {
642
- sqlite: `integer('${name}')`,
643
- pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
644
- mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
645
- },
646
- date: {
647
- sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
648
- pg: `timestamp('${name}')`,
649
- mysql: `timestamp('${name}', { fsp: 3 })`
650
- },
651
- "number[]": {
652
- sqlite: `text('${name}', { mode: "json" })`,
653
- pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
654
- mysql: `text('${name}', { mode: 'json' })`
655
- },
656
- "string[]": {
657
- sqlite: `text('${name}', { mode: "json" })`,
658
- pg: `text('${name}').array()`,
659
- mysql: `text('${name}', { mode: "json" })`
660
- },
661
- json: {
662
- sqlite: `text('${name}', { mode: "json" })`,
663
- pg: `jsonb('${name}')`,
664
- mysql: `json('${name}', { mode: "json" })`
665
- }
666
- }[type];
667
- if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
668
- return dbTypeMap[databaseType];
669
- }
670
- let id = "";
671
- const useNumberId = options.advanced?.database?.generateId === "serial";
672
- if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
673
- else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
674
- else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
675
- else id = `int("id").autoincrement().primaryKey()`;
676
- else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
677
- else if (databaseType === "pg") id = `text('id').primaryKey()`;
678
- else id = `text('id').primaryKey()`;
679
- const indexes = [];
680
- const assignIndexes = (indexes) => {
681
- if (!indexes.length) return "";
682
- const code = [`, (table) => [`];
683
- for (const index of indexes) code.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
684
- code.push(`]`);
685
- return code.join("\n");
686
- };
687
- const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
688
- id: ${id},
689
- ${Object.keys(fields).map((field) => {
690
- const attr = fields[field];
691
- const fieldName = attr.fieldName || field;
692
- let type = getType(fieldName, attr);
693
- if (attr.index && !attr.unique) indexes.push({
694
- type: "index",
695
- name: `${modelName}_${fieldName}_idx`,
696
- on: fieldName
697
- });
698
- else if (attr.index && attr.unique) indexes.push({
699
- type: "uniqueIndex",
700
- name: `${modelName}_${fieldName}_uidx`,
701
- on: fieldName
702
- });
703
- if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
704
- if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
705
- else type += `.defaultNow()`;
706
- } else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
707
- else type += `.default(${attr.defaultValue})`;
708
- if (attr.onUpdate && attr.type === "date") {
709
- if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
710
- }
711
- return `${fieldName}: ${type}${attr.required !== false ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
712
- model: attr.references.model,
713
- field: attr.references.field
714
- })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
715
- }).join(",\n ")}
716
- }${assignIndexes(indexes)});`;
717
- code += `\n${schema}\n`;
718
- }
719
- let relationsString = "";
720
- for (const tableKey in tables) {
721
- const table = tables[tableKey];
722
- const modelName = getModelName(tableKey);
723
- const oneRelations = [];
724
- const manyRelations = [];
725
- const manyRelationsSet = /* @__PURE__ */ new Set();
726
- const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
727
- for (const [fieldName, field] of foreignFields) {
728
- const referencedModel = field.references.model;
729
- const relationKey = getModelName(referencedModel);
730
- const fieldRef = `${getModelName(tableKey)}.${getFieldName({
731
- model: tableKey,
732
- field: fieldName
733
- })}`;
734
- const referenceRef = `${getModelName(referencedModel)}.${getFieldName({
735
- model: referencedModel,
736
- field: field.references.field || "id"
737
- })}`;
738
- oneRelations.push({
739
- key: relationKey,
740
- model: getModelName(referencedModel),
741
- type: "one",
742
- reference: {
743
- field: fieldRef,
744
- references: referenceRef,
745
- fieldName
746
- }
747
- });
748
- }
749
- const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
750
- const modelRelationsMap = /* @__PURE__ */ new Map();
751
- for (const [modelName, otherTable] of otherModels) {
752
- const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
753
- if (foreignKeysPointingHere.length === 0) continue;
754
- const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
755
- const hasMany = foreignKeysPointingHere.some(([_, field]) => !field.unique);
756
- modelRelationsMap.set(modelName, {
757
- modelName,
758
- hasUnique,
759
- hasMany
760
- });
761
- }
762
- for (const { modelName, hasMany } of modelRelationsMap.values()) {
763
- const relationType = hasMany ? "many" : "one";
764
- let relationKey = getModelName(modelName);
765
- if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
766
- if (!manyRelationsSet.has(relationKey)) {
767
- manyRelationsSet.add(relationKey);
768
- manyRelations.push({
769
- key: relationKey,
770
- model: getModelName(modelName),
771
- type: relationType
772
- });
773
- }
774
- }
775
- const relationsByModel = /* @__PURE__ */ new Map();
776
- for (const relation of oneRelations) if (relation.reference) {
777
- const modelKey = relation.key;
778
- if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
779
- relationsByModel.get(modelKey).push(relation);
780
- }
781
- const duplicateRelations = [];
782
- const singleRelations = [];
783
- for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
784
- else singleRelations.push(relations[0]);
785
- for (const relation of duplicateRelations) if (relation.reference) {
786
- const fieldName = relation.reference.fieldName;
787
- const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
788
- ${relation.key}: one(${relation.model}, {
789
- fields: [${relation.reference.field}],
790
- references: [${relation.reference.references}],
791
- })
792
- }))`;
793
- relationsString += `\n${tableRelation}\n`;
794
- }
795
- const hasOne = singleRelations.length > 0;
796
- const hasMany = manyRelations.length > 0;
797
- if (hasOne && hasMany) {
798
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
799
- ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
800
- fields: [${relation.reference.field}],
801
- references: [${relation.reference.references}],
802
- })` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
803
- ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
804
- }))`;
805
- relationsString += `\n${tableRelation}\n`;
806
- } else if (hasOne) {
807
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
808
- ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
809
- fields: [${relation.reference.field}],
810
- references: [${relation.reference.references}],
811
- })` : "").filter((x) => x !== "").join(",\n ")}
812
- }))`;
813
- relationsString += `\n${tableRelation}\n`;
814
- } else if (hasMany) {
815
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
816
- ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
817
- }))`;
818
- relationsString += `\n${tableRelation}\n`;
819
- }
820
- }
821
- code += `\n${relationsString}`;
822
- return {
823
- code: await prettier.format(code, { parser: "typescript" }),
824
- fileName: filePath,
825
- overwrite: fileExist
826
- };
827
- };
828
- function generateImport({ databaseType, tables, options }) {
829
- const rootImports = ["relations"];
830
- const coreImports = [];
831
- let hasBigint = false;
832
- let hasJson = false;
833
- for (const table of Object.values(tables)) {
834
- for (const field of Object.values(table.fields)) {
835
- if (field.bigint) hasBigint = true;
836
- if (field.type === "json") hasJson = true;
837
- }
838
- if (hasJson && hasBigint) break;
839
- }
840
- const useNumberId = options.advanced?.database?.generateId === "serial";
841
- const useUUIDs = options.advanced?.database?.generateId === "uuid";
842
- coreImports.push(`${databaseType}Table`);
843
- coreImports.push(databaseType === "mysql" ? "varchar, text" : databaseType === "pg" ? "text" : "text");
844
- coreImports.push(hasBigint ? databaseType !== "sqlite" ? "bigint" : "" : "");
845
- coreImports.push(databaseType !== "sqlite" ? "timestamp, boolean" : "");
846
- if (databaseType === "mysql") {
847
- const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
848
- if (useNumberId || hasNonBigintNumber) coreImports.push("int");
849
- 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");
850
- } else if (databaseType === "pg") {
851
- if (useUUIDs) rootImports.push("sql");
852
- const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
853
- const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
854
- if (hasNonBigintNumber || options.advanced?.database?.generateId === "serial" && hasFkToId) coreImports.push("integer");
855
- } else coreImports.push("integer");
856
- if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
857
- if (hasJson) {
858
- if (databaseType === "pg") coreImports.push("jsonb");
859
- if (databaseType === "mysql") coreImports.push("json");
860
- }
861
- 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");
862
- const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
863
- const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
864
- if (hasIndexes) coreImports.push("index");
865
- if (hasUniqueIndexes) coreImports.push("uniqueIndex");
866
- 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`;
867
- }
868
- //#endregion
869
- //#region src/generators/kysely.ts
870
- const generateKyselySchema = async ({ options, file }) => {
871
- const { compileMigrations } = await getMigrations(options);
872
- const migrations = await compileMigrations();
873
- return {
874
- code: migrations.trim() === ";" ? "" : migrations,
875
- fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
876
- };
877
- };
878
- //#endregion
879
- //#region src/utils/helper.ts
880
- async function tryCatch(promise) {
881
- try {
882
- return {
883
- data: await promise,
884
- error: null
885
- };
886
- } catch (error) {
887
- return {
888
- data: null,
889
- error
890
- };
720
+ } catch {}
721
+ break;
891
722
  }
723
+ return aliases;
892
724
  }
893
- const generateSecretHash = () => {
894
- return Crypto.randomBytes(16).toString("hex");
895
- };
896
- const spawnCommand = (cmd, cwd = process.cwd()) => new Promise((resolve, reject) => {
897
- const child = spawn(cmd, {
898
- cwd,
899
- stdio: "inherit",
900
- shell: true
901
- });
902
- child.on("close", (code, signal) => {
903
- if (code !== 0 && code !== null) reject(/* @__PURE__ */ new Error(`Exited with code ${code}`));
904
- else if (signal) reject(/* @__PURE__ */ new Error(`Killed with signal ${signal}`));
905
- else resolve();
906
- });
907
- child.on("error", reject);
908
- });
909
- //#endregion
910
- //#region src/utils/get-package-info.ts
911
- function getPackageInfo(cwd) {
912
- const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
913
- return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
725
+ function createAppServerModule() {
726
+ return `
727
+ // $app/server stub for CLI compatibility
728
+ export default {};
729
+ // jiti dirty hack: .unknown
730
+ `;
914
731
  }
915
- function getPrismaVersion(cwd) {
916
- try {
917
- const packageInfo = getPackageInfo(cwd);
918
- const prismaVersion = packageInfo.dependencies?.prisma || packageInfo.devDependencies?.prisma || packageInfo.dependencies?.["@prisma/client"] || packageInfo.devDependencies?.["@prisma/client"];
919
- if (!prismaVersion) return null;
920
- const match = prismaVersion.match(/(\d+)/);
921
- return match ? parseInt(match[1], 10) : null;
922
- } catch {
923
- return null;
924
- }
732
+ function createDataUriModule(module) {
733
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(module)}`;
925
734
  }
926
- /**
927
- * Checks if a package has a specific dependency.
928
- *
929
- * @param packageJson The package.json object
930
- * @param dependency The dependency to check for
931
- * @returns true if the package has the dependency
932
- */
933
- function hasDependency(packageJson, dependency) {
934
- let hasDependency = false;
935
- if (packageJson.dependencies?.[dependency] || packageJson.devDependencies?.[dependency] || packageJson.peerDependencies?.[dependency] || packageJson.optionalDependencies?.[dependency]) hasDependency = true;
936
- return hasDependency;
735
+ function createStaticEnvModule(env) {
736
+ return `
737
+ ${Object.keys(env).filter((k) => validIdentifier.test(k) && !reserved.has(k)).map((k) => `export const ${k} = ${JSON.stringify(env[k])};`).join("\n")}
738
+ // jiti dirty hack: .unknown
739
+ `;
937
740
  }
938
- /**
939
- * Checks if a directory is a monorepo root by looking for common monorepo indicators.
940
- *
941
- * @param dir Directory to check
942
- * @returns true if the directory appears to be a monorepo root
741
+ function createDynamicEnvModule() {
742
+ return `
743
+ export const env = process.env;
744
+ // jiti dirty hack: .unknown
745
+ `;
746
+ }
747
+ function filterPrivateEnv(publicPrefix, privatePrefix) {
748
+ return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(privatePrefix) && (publicPrefix === "" || !k.startsWith(publicPrefix))));
749
+ }
750
+ function filterPublicEnv(publicPrefix, privatePrefix) {
751
+ return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(publicPrefix) && (privatePrefix === "" || !k.startsWith(privatePrefix))));
752
+ }
753
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
754
+ const reserved = new Set([
755
+ "do",
756
+ "if",
757
+ "in",
758
+ "for",
759
+ "let",
760
+ "new",
761
+ "try",
762
+ "var",
763
+ "case",
764
+ "else",
765
+ "enum",
766
+ "eval",
767
+ "null",
768
+ "this",
769
+ "true",
770
+ "void",
771
+ "with",
772
+ "await",
773
+ "break",
774
+ "catch",
775
+ "class",
776
+ "const",
777
+ "false",
778
+ "super",
779
+ "throw",
780
+ "while",
781
+ "yield",
782
+ "delete",
783
+ "export",
784
+ "import",
785
+ "public",
786
+ "return",
787
+ "static",
788
+ "switch",
789
+ "typeof",
790
+ "default",
791
+ "extends",
792
+ "finally",
793
+ "package",
794
+ "private",
795
+ "continue",
796
+ "debugger",
797
+ "function",
798
+ "arguments",
799
+ "interface",
800
+ "protected",
801
+ "implements",
802
+ "instanceof"
803
+ ]);
804
+ //#endregion
805
+ //#region src/utils/get-config.ts
806
+ let possiblePaths$1 = [
807
+ "auth.ts",
808
+ "auth.tsx",
809
+ "auth.js",
810
+ "auth.jsx",
811
+ "auth.server.js",
812
+ "auth.server.ts",
813
+ "auth/index.ts",
814
+ "auth/index.tsx",
815
+ "auth/index.js",
816
+ "auth/index.jsx",
817
+ "auth/index.server.js",
818
+ "auth/index.server.ts"
819
+ ];
820
+ possiblePaths$1 = [
821
+ ...possiblePaths$1,
822
+ ...possiblePaths$1.map((it) => `lib/server/${it}`),
823
+ ...possiblePaths$1.map((it) => `server/auth/${it}`),
824
+ ...possiblePaths$1.map((it) => `server/${it}`),
825
+ ...possiblePaths$1.map((it) => `auth/${it}`),
826
+ ...possiblePaths$1.map((it) => `lib/${it}`),
827
+ ...possiblePaths$1.map((it) => `utils/${it}`)
828
+ ];
829
+ possiblePaths$1 = [
830
+ ...possiblePaths$1,
831
+ ...possiblePaths$1.map((it) => `src/${it}`),
832
+ ...possiblePaths$1.map((it) => `app/${it}`)
833
+ ];
834
+ /** Reads `references` from raw tsconfig JSON (stripped out by `parseTsconfig`). */
835
+ function readRawTsconfigReferences(tsconfigPath) {
836
+ try {
837
+ const stripped = fs.readFileSync(tsconfigPath, "utf-8").replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(?=\s*[}\]])/g, "");
838
+ return JSON.parse(stripped).references;
839
+ } catch {
840
+ return;
841
+ }
842
+ }
843
+ /** Recursively collects tsconfigs reachable via `references`. */
844
+ function collectReferencedTsconfigs(tsconfigPath, visited = /* @__PURE__ */ new Set()) {
845
+ const result = [];
846
+ const refs = readRawTsconfigReferences(tsconfigPath);
847
+ if (!refs) return result;
848
+ const configDir = path.dirname(tsconfigPath);
849
+ for (const ref of refs) {
850
+ const resolvedRef = path.resolve(configDir, ref.path);
851
+ const refTsconfigPath = resolvedRef.endsWith(".json") ? resolvedRef : path.join(resolvedRef, "tsconfig.json");
852
+ if (visited.has(refTsconfigPath)) continue;
853
+ visited.add(refTsconfigPath);
854
+ try {
855
+ const refConfig = parseTsconfig(refTsconfigPath);
856
+ result.push({
857
+ path: refTsconfigPath,
858
+ config: refConfig
859
+ });
860
+ } catch {
861
+ continue;
862
+ }
863
+ result.push(...collectReferencedTsconfigs(refTsconfigPath, visited));
864
+ }
865
+ return result;
866
+ }
867
+ /**
868
+ * Ordered `paths` matchers from the project tsconfig and any referenced
869
+ * tsconfigs, following TypeScript canonical resolution semantics.
870
+ * @see https://github.com/microsoft/TypeScript/blob/main/src/compiler/moduleNameResolver.ts
943
871
  */
944
- async function isMonorepoRoot(dir) {
945
- const { data: files } = await tryCatch(fs$1.readdir(dir, "utf-8"));
946
- if (!files) return false;
947
- if (files.includes("pnpm-workspace.yaml")) return true;
948
- if (files.includes("package.json")) {
949
- const packageJsonPath = path.join(dir, "package.json");
950
- const { data } = await tryCatch(fs$1.readFile(packageJsonPath, "utf-8"));
951
- if (data) try {
952
- const packageJson = JSON.parse(data);
953
- if (packageJson.workspaces && (Array.isArray(packageJson.workspaces) || typeof packageJson.workspaces === "object")) return true;
954
- } catch {}
872
+ function collectPathsMatchers(cwd) {
873
+ const tsconfig = getTsconfig(cwd, fs.existsSync(path.join(cwd, "tsconfig.json")) ? "tsconfig.json" : "jsconfig.json");
874
+ if (!tsconfig) return [];
875
+ const matchers = [];
876
+ try {
877
+ const mainMatcher = createPathsMatcher(tsconfig);
878
+ if (mainMatcher) matchers.push(mainMatcher);
879
+ for (const refTsconfig of collectReferencedTsconfigs(tsconfig.path)) {
880
+ const refMatcher = createPathsMatcher(refTsconfig);
881
+ if (refMatcher) matchers.push(refMatcher);
882
+ }
883
+ } catch (error) {
884
+ console.error(error);
885
+ throw new BetterAuthError("Error parsing tsconfig.json");
955
886
  }
956
- return [
957
- "lerna.json",
958
- "turbo.json",
959
- "nx.json",
960
- "rush.json"
961
- ].some((indicator) => files.includes(indicator));
887
+ return matchers;
962
888
  }
963
889
  /**
964
- * Finds the monorepo root by walking up the directory tree.
965
- *
966
- * @param startDir Starting directory
967
- * @returns Path to monorepo root, or null if not found
890
+ * Source file extensions jiti can load. Shared between the jiti `extensions`
891
+ * option and `resolveCandidateFile` so both stay in sync.
968
892
  */
969
- async function findMonorepoRoot(startDir) {
970
- let currentDir = path.resolve(startDir);
971
- const root = path.parse(currentDir).root;
972
- while (currentDir !== root) {
973
- if (await isMonorepoRoot(currentDir)) return currentDir;
974
- const parentDir = path.dirname(currentDir);
975
- if (parentDir === currentDir) break;
976
- currentDir = parentDir;
893
+ const SOURCE_EXTENSIONS = [
894
+ ".ts",
895
+ ".tsx",
896
+ ".mts",
897
+ ".cts",
898
+ ".js",
899
+ ".jsx",
900
+ ".mjs",
901
+ ".cjs"
902
+ ];
903
+ const SOURCE_EXTENSIONS_SET = new Set(SOURCE_EXTENSIONS);
904
+ /** Probes a candidate as-is, with known extensions, and as a directory index. */
905
+ function resolveCandidateFile(candidate) {
906
+ try {
907
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
908
+ } catch {}
909
+ if (SOURCE_EXTENSIONS_SET.has(path.extname(candidate))) return;
910
+ for (const ext of SOURCE_EXTENSIONS) {
911
+ const withExt = candidate + ext;
912
+ if (fs.existsSync(withExt)) return withExt;
913
+ }
914
+ for (const ext of SOURCE_EXTENSIONS) {
915
+ const asIndex = path.join(candidate, `index${ext}`);
916
+ if (fs.existsSync(asIndex)) return asIndex;
977
917
  }
978
- return null;
979
918
  }
980
- //#endregion
981
- //#region src/generators/prisma.ts
982
- const generatePrismaSchema = async ({ adapter, options, file }) => {
983
- const provider = adapter.options?.provider || "postgresql";
984
- const tables = getAuthTables(options);
985
- const filePath = file || "./prisma/schema.prisma";
986
- const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
987
- const getModelName = initGetModelName({
988
- schema: getAuthTables(options),
989
- usePlural: adapter.options?.adapterConfig?.usePlural
990
- });
991
- const getFieldName = initGetFieldName({
992
- schema: getAuthTables(options),
993
- usePlural: false
994
- });
995
- let schemaPrisma = "";
996
- if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(path.join(process.cwd(), filePath), "utf-8");
997
- else schemaPrisma = getNewPrisma(provider, process.cwd());
998
- const prismaVersion = getPrismaVersion(process.cwd());
999
- if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
1000
- const generator = builder.findByType("generator", { name: "client" });
1001
- if (generator && generator.properties) {
1002
- const providerProp = generator.properties.find((prop) => prop.type === "assignment" && prop.key === "provider");
1003
- if (providerProp && providerProp.value === "\"prisma-client-js\"") providerProp.value = "\"prisma-client\"";
1004
- }
1005
- const datasource = builder.findByType("datasource", { name: "db" });
1006
- if (datasource && datasource.properties) {
1007
- const urlIndex = datasource.properties.findIndex((prop) => prop.type === "assignment" && prop.key === "url");
1008
- if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
1009
- }
1010
- });
1011
- const manyToManyRelations = /* @__PURE__ */ new Map();
1012
- for (const table in tables) {
1013
- const fields = tables[table]?.fields;
1014
- for (const field in fields) {
1015
- const attr = fields[field];
1016
- if (attr.references) {
1017
- const referencedOriginalModel = attr.references.model;
1018
- const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
1019
- if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
1020
- const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
1021
- manyToManyRelations.get(referencedModelNameCap).add(currentModelNameCap);
1022
- }
1023
- }
919
+ function resolveWithMatchers(specifier, matchers) {
920
+ for (const matcher of matchers) for (const candidate of matcher(specifier)) {
921
+ const resolved = resolveCandidateFile(candidate);
922
+ if (resolved) return resolved;
1024
923
  }
1025
- const indexedFields = /* @__PURE__ */ new Map();
1026
- for (const table in tables) {
1027
- const fields = tables[table]?.fields;
1028
- const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
1029
- indexedFields.set(modelName, []);
1030
- for (const field in fields) {
1031
- const attr = fields[field];
1032
- if (attr.index && !attr.unique) {
1033
- const fieldName = attr.fieldName || field;
1034
- indexedFields.get(modelName).push(fieldName);
924
+ }
925
+ /**
926
+ * Callees whose first string argument is a module specifier. `jitiImport` is
927
+ * a jiti-side preprocessor artifact observed in the AST; revisit on jiti
928
+ * major version bumps (the regression suite catches a rename but not the why).
929
+ */
930
+ const LOADER_IDENTIFIERS = new Set([
931
+ "require",
932
+ "import",
933
+ "jitiImport"
934
+ ]);
935
+ /**
936
+ * Rewrites aliased specifiers at AST level. Required because jiti's `alias`
937
+ * option only supports prefix matching and cannot express mid-path wildcards.
938
+ *
939
+ * Matchers always take precedence over native resolution, mirroring
940
+ * TypeScript's own `paths` → `node_modules` order.
941
+ */
942
+ function createRewriteImportPathsPlugin(matchers) {
943
+ return ({ types: t }) => {
944
+ const rewrite = (source) => {
945
+ if (!source) return;
946
+ const resolved = resolveWithMatchers(source.value, matchers);
947
+ if (resolved) source.value = resolved;
948
+ };
949
+ return { visitor: {
950
+ ImportDeclaration(p) {
951
+ rewrite(p.node.source);
952
+ },
953
+ ExportNamedDeclaration(p) {
954
+ rewrite(p.node.source);
955
+ },
956
+ ExportAllDeclaration(p) {
957
+ rewrite(p.node.source);
958
+ },
959
+ ImportExpression(p) {
960
+ if (t.isStringLiteral(p.node.source)) rewrite(p.node.source);
961
+ },
962
+ CallExpression(p) {
963
+ const { callee, arguments: args } = p.node;
964
+ const first = args[0];
965
+ if (!t.isStringLiteral(first)) return;
966
+ if (!(t.isIdentifier(callee) && LOADER_IDENTIFIERS.has(callee.name) || t.isImport(callee))) return;
967
+ rewrite(first);
968
+ }
969
+ } };
970
+ };
971
+ }
972
+ /** Virtual module aliases; real tsconfig paths go through the babel plugin. */
973
+ function getVirtualModuleAliases() {
974
+ const result = {};
975
+ addSvelteKitEnvModules(result);
976
+ addCloudflareModules(result);
977
+ return result;
978
+ }
979
+ /**
980
+ * .tsx files are not supported by Jiti.
981
+ */
982
+ const jitiOptions = (cwd) => {
983
+ const matchers = collectPathsMatchers(cwd);
984
+ const plugins = matchers.length > 0 ? [createRewriteImportPathsPlugin(matchers)] : [];
985
+ return {
986
+ transformOptions: { babel: {
987
+ presets: [[babelPresetTypeScript, {
988
+ isTSX: true,
989
+ allExtensions: true
990
+ }], [babelPresetReact, { runtime: "automatic" }]],
991
+ plugins
992
+ } },
993
+ extensions: [...SOURCE_EXTENSIONS],
994
+ alias: getVirtualModuleAliases()
995
+ };
996
+ };
997
+ /**
998
+ * Picks the auth instance from the loaded module, supporting `export const auth`,
999
+ * `export default auth`, and `export default { auth }`. Falls back to the raw
1000
+ * module as a defensive default.
1001
+ */
1002
+ const resolveAuthModule = (mod) => {
1003
+ const m = mod;
1004
+ return m?.auth ?? m?.default?.auth ?? m?.default ?? mod;
1005
+ };
1006
+ const isServerOnlyError = (e) => typeof e === "object" && e !== null && "message" in e && typeof e.message === "string" && e.message.includes("This module cannot be imported from a Client Component module");
1007
+ const SERVER_ONLY_HINT = `Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`;
1008
+ async function getConfig({ cwd, configPath, shouldThrowOnError = false }) {
1009
+ const fail = (message, error) => {
1010
+ if (shouldThrowOnError) throw error instanceof Error ? error : new Error(message);
1011
+ const log = `[#better-auth]: ${message}`;
1012
+ if (error) console.error(log, error);
1013
+ else console.error(log);
1014
+ process.exit(1);
1015
+ };
1016
+ const load = (configFile) => loadConfig({
1017
+ configFile,
1018
+ dotenv: { fileName: [".env", ".env.local"] },
1019
+ jitiOptions: jitiOptions(cwd),
1020
+ resolveModule: resolveAuthModule,
1021
+ cwd
1022
+ });
1023
+ try {
1024
+ if (configPath) {
1025
+ const resolvedPath = existsSync(configPath) ? configPath : path.join(cwd, configPath);
1026
+ const { config } = await load(resolvedPath);
1027
+ const options = config?.options;
1028
+ if (!options) return fail(`Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
1029
+ return options;
1030
+ }
1031
+ for (const possiblePath of possiblePaths$1) {
1032
+ let config;
1033
+ try {
1034
+ ({config} = await load(possiblePath));
1035
+ } catch (e) {
1036
+ if (isServerOnlyError(e)) return fail(SERVER_ONLY_HINT);
1037
+ return fail("Couldn't read your auth config.", e);
1035
1038
  }
1039
+ if (Object.keys(config).length === 0) continue;
1040
+ if (!config.options) return fail("Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth.");
1041
+ return config.options;
1036
1042
  }
1043
+ return null;
1044
+ } catch (e) {
1045
+ if (isServerOnlyError(e)) return fail(SERVER_ONLY_HINT);
1046
+ return fail("Couldn't read your auth config.", e);
1037
1047
  }
1038
- const schema = produceSchema(schemaPrisma, (builder) => {
1039
- for (const table in tables) {
1040
- const originalTableName = table;
1041
- const customModelName = tables[table]?.modelName || table;
1042
- const modelName = capitalizeFirstLetter(getModelName(customModelName));
1043
- const fields = tables[table]?.fields;
1044
- function getType({ isBigint, isOptional, type }) {
1045
- if (type === "string") return isOptional ? "String?" : "String";
1046
- if (type === "number" && isBigint) return isOptional ? "BigInt?" : "BigInt";
1047
- if (type === "number") return isOptional ? "Int?" : "Int";
1048
- if (type === "boolean") return isOptional ? "Boolean?" : "Boolean";
1049
- if (type === "date") return isOptional ? "DateTime?" : "DateTime";
1050
- if (type === "json") {
1051
- if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
1052
- return isOptional ? "Json?" : "Json";
1053
- }
1054
- if (type === "string[]") {
1055
- if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
1056
- return "String[]";
1057
- }
1058
- if (type === "number[]") {
1059
- if (provider === "sqlite" || provider === "mysql") return "String";
1060
- return "Int[]";
1061
- }
1048
+ }
1049
+ //#endregion
1050
+ //#region src/commands/create-admin.ts
1051
+ function exitWithError(message) {
1052
+ console.error(chalk.red(`Error: ${message}`));
1053
+ process.exit(1);
1054
+ }
1055
+ function parseData(data) {
1056
+ if (!data) return void 0;
1057
+ try {
1058
+ const parsed = JSON.parse(data);
1059
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) exitWithError("--data must be a JSON object.");
1060
+ return parsed;
1061
+ } catch (error) {
1062
+ if (error instanceof SyntaxError) exitWithError("--data must be valid JSON.");
1063
+ throw error;
1064
+ }
1065
+ }
1066
+ async function resolveRequiredInput(options) {
1067
+ let { email, password } = options;
1068
+ if (!email) email = (await prompts({
1069
+ type: "text",
1070
+ name: "email",
1071
+ message: "Admin email"
1072
+ })).email;
1073
+ if (!password) password = (await prompts({
1074
+ type: "password",
1075
+ name: "password",
1076
+ message: "Admin password"
1077
+ })).password;
1078
+ if (!email) exitWithError("Admin email is required.");
1079
+ if (!password) exitWithError("Admin password is required.");
1080
+ return {
1081
+ email,
1082
+ password
1083
+ };
1084
+ }
1085
+ /** @internal */
1086
+ async function createAdminAction(opts) {
1087
+ const options = z.object({
1088
+ cwd: z.string(),
1089
+ config: z.string().optional(),
1090
+ email: z.string().optional(),
1091
+ password: z.string().optional(),
1092
+ name: z.string().default("Admin"),
1093
+ role: z.string().default("admin"),
1094
+ data: z.string().optional(),
1095
+ emailVerified: z.boolean().default(true),
1096
+ force: z.boolean().optional(),
1097
+ y: z.boolean().optional(),
1098
+ yes: z.boolean().optional()
1099
+ }).parse(opts);
1100
+ const cwd = path.resolve(options.cwd);
1101
+ if (!existsSync(cwd)) exitWithError(`The directory "${cwd}" does not exist.`);
1102
+ if (options.y) {
1103
+ console.warn("WARNING: --y is deprecated. Consider -y or --yes");
1104
+ options.yes = true;
1105
+ }
1106
+ const config = await getConfig({
1107
+ cwd,
1108
+ configPath: options.config
1109
+ });
1110
+ if (!config) exitWithError("No configuration file found. Add an `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");
1111
+ if (!config.database) exitWithError("No database is configured. Add a persistent database before creating an admin user.");
1112
+ const auth = betterAuth(config);
1113
+ const createUser = auth.api.createUser;
1114
+ if (typeof createUser !== "function") exitWithError("The admin plugin is required. Add `admin()` to your Better Auth plugins before running this command.");
1115
+ const { email, password } = await resolveRequiredInput(options);
1116
+ if (!z.email().safeParse(email).success) exitWithError("Invalid email address.");
1117
+ const data = {
1118
+ ...parseData(options.data),
1119
+ emailVerified: options.emailVerified
1120
+ };
1121
+ try {
1122
+ const totalUsers = await (await auth.$context).internalAdapter.countTotalUsers();
1123
+ if (totalUsers > 0 && !options.force && !options.yes) {
1124
+ if (!(await prompts({
1125
+ type: "confirm",
1126
+ name: "confirmed",
1127
+ message: `Found ${totalUsers} existing user${totalUsers === 1 ? "" : "s"}. Create an admin user anyway?`,
1128
+ initial: false
1129
+ })).confirmed) {
1130
+ console.log("Create admin cancelled.");
1131
+ process.exit(0);
1132
+ return;
1062
1133
  }
1063
- const prismaModel = builder.findByType("model", { name: modelName });
1064
- if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
1065
- else {
1134
+ }
1135
+ } catch (error) {
1136
+ exitWithError(`Failed to inspect existing users. Make sure your database is reachable and your Better Auth schema is migrated before running this command.${error instanceof Error ? `\n${error.message}` : ""}`);
1137
+ }
1138
+ try {
1139
+ const result = await createUser({ body: {
1140
+ email,
1141
+ password,
1142
+ name: options.name,
1143
+ role: options.role,
1144
+ data
1145
+ } });
1146
+ console.log(chalk.green("Admin user created successfully."));
1147
+ if (result.user?.id) console.log(`User ID: ${result.user.id}`);
1148
+ console.log(`Email: ${result.user?.email ?? email.toLowerCase()}`);
1149
+ console.log(`Role: ${result.user?.role ?? options.role}`);
1150
+ process.exit(0);
1151
+ } catch (error) {
1152
+ if (error instanceof APIError) exitWithError(error.message);
1153
+ if (error instanceof Error) exitWithError(error.message);
1154
+ exitWithError("Failed to create admin user.");
1155
+ }
1156
+ }
1157
+ const createAdmin = new Command("create-admin").description("Create an initial admin user").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("--config <config>", "the path to the configuration file. defaults to the first configuration file found.").option("--email <email>", "the email address for the admin user").option("--password <password>", "the password for the admin user").option("--name <name>", "the name for the admin user", "Admin").option("--role <role>", "the role to assign to the user", "admin").option("--data <json>", "additional user fields as a JSON object").option("--no-email-verified", "create the admin user with an unverified email").option("--force", "create an admin user even when users already exist", false).option("-y, --yes", "automatically confirm creating an admin when users already exist", false).option("--y", "(deprecated) same as --yes", false).action(createAdminAction);
1158
+ //#endregion
1159
+ //#region src/generators/drizzle.ts
1160
+ function convertToSnakeCase(str, camelCase) {
1161
+ return camelCase ? str : toSnakeCase(str);
1162
+ }
1163
+ const generateDrizzleSchema = async ({ options, file, adapter }) => {
1164
+ const tables = getAuthTables(options);
1165
+ const filePath = file || "./auth-schema.ts";
1166
+ const databaseType = adapter.options?.provider;
1167
+ 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`);
1168
+ const fileExist = existsSync(filePath);
1169
+ let code = generateImport({
1170
+ databaseType,
1171
+ tables,
1172
+ options
1173
+ });
1174
+ const getModelName = initGetModelName({
1175
+ schema: tables,
1176
+ usePlural: adapter.options?.adapterConfig?.usePlural
1177
+ });
1178
+ const getFieldName = initGetFieldName({
1179
+ schema: tables,
1180
+ usePlural: adapter.options?.adapterConfig?.usePlural
1181
+ });
1182
+ for (const tableKey in tables) {
1183
+ const table = tables[tableKey];
1184
+ const modelName = getModelName(tableKey);
1185
+ const fields = table.fields;
1186
+ function getType(name, field) {
1187
+ 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`);
1188
+ name = convertToSnakeCase(name, adapter.options?.camelCase);
1189
+ if (field.references?.field === "id") {
1066
1190
  const useNumberId = options.advanced?.database?.generateId === "serial";
1067
1191
  const useUUIDs = options.advanced?.database?.generateId === "uuid";
1068
- if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
1069
- else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
1070
- else builder.model(modelName).field("id", "String").attribute("id");
1192
+ if (useNumberId) if (databaseType === "pg") return `integer('${name}')`;
1193
+ else if (databaseType === "mysql") return `int('${name}')`;
1194
+ else return `integer('${name}')`;
1195
+ if (useUUIDs && databaseType === "pg") return `uuid('${name}')`;
1196
+ if (field.references.field) {
1197
+ if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
1198
+ }
1199
+ return `text('${name}')`;
1071
1200
  }
1072
- for (const field in fields) {
1073
- const attr = fields[field];
1074
- const fieldName = attr.fieldName || field;
1075
- if (prismaModel) {
1076
- if (builder.findByType("field", {
1077
- name: fieldName,
1078
- within: prismaModel.properties
1079
- })) continue;
1201
+ const type = field.type;
1202
+ if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
1203
+ sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
1204
+ pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
1205
+ mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
1206
+ }[databaseType];
1207
+ else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
1208
+ const dbTypeMap = {
1209
+ string: {
1210
+ sqlite: `text('${name}')`,
1211
+ pg: `text('${name}')`,
1212
+ 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}')`
1213
+ },
1214
+ boolean: {
1215
+ sqlite: `integer('${name}', { mode: 'boolean' })`,
1216
+ pg: `boolean('${name}')`,
1217
+ mysql: `boolean('${name}')`
1218
+ },
1219
+ number: {
1220
+ sqlite: `integer('${name}')`,
1221
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
1222
+ mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
1223
+ },
1224
+ date: {
1225
+ sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
1226
+ pg: `timestamp('${name}')`,
1227
+ mysql: `timestamp('${name}', { fsp: 3 })`
1228
+ },
1229
+ "number[]": {
1230
+ sqlite: `text('${name}', { mode: "json" })`,
1231
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
1232
+ mysql: `text('${name}', { mode: 'json' })`
1233
+ },
1234
+ "string[]": {
1235
+ sqlite: `text('${name}', { mode: "json" })`,
1236
+ pg: `text('${name}').array()`,
1237
+ mysql: `text('${name}', { mode: "json" })`
1238
+ },
1239
+ json: {
1240
+ sqlite: `text('${name}', { mode: "json" })`,
1241
+ pg: `jsonb('${name}')`,
1242
+ mysql: `json('${name}', { mode: "json" })`
1080
1243
  }
1081
- const useUUIDs = options.advanced?.database?.generateId === "uuid";
1082
- const useNumberId = options.advanced?.database?.generateId === "serial";
1083
- const fieldBuilder = builder.model(modelName).field(fieldName, field === "id" && useNumberId ? getType({
1084
- isBigint: false,
1085
- isOptional: false,
1086
- type: "number"
1087
- }) : getType({
1088
- isBigint: attr?.bigint || false,
1089
- isOptional: attr?.required === false,
1090
- type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
1091
- }));
1092
- if (field === "id") {
1093
- fieldBuilder.attribute("id");
1094
- if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
1095
- }
1096
- if (attr.unique) builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
1097
- if (attr.defaultValue !== void 0) {
1098
- if (Array.isArray(attr.defaultValue)) {
1099
- if (attr.type === "json") {
1100
- if (Object.prototype.toString.call(attr.defaultValue[0]) === "[object Object]") {
1101
- fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
1102
- continue;
1103
- }
1104
- const jsonArray = [];
1105
- for (const value of attr.defaultValue) jsonArray.push(value);
1106
- fieldBuilder.attribute(`default("${JSON.stringify(jsonArray).replace(/"/g, "\\\"")}")`);
1107
- continue;
1108
- }
1109
- if (attr.defaultValue.length === 0) {
1110
- fieldBuilder.attribute(`default([])`);
1111
- continue;
1112
- } else if (typeof attr.defaultValue[0] === "string" && attr.type === "string[]") {
1113
- const valueArray = [];
1114
- for (const value of attr.defaultValue) valueArray.push(JSON.stringify(value));
1115
- fieldBuilder.attribute(`default([${valueArray}])`);
1116
- } else if (typeof attr.defaultValue[0] === "number") {
1117
- const valueArray = [];
1118
- for (const value of attr.defaultValue) valueArray.push(`${value}`);
1119
- fieldBuilder.attribute(`default([${valueArray}])`);
1120
- }
1121
- } else if (typeof attr.defaultValue === "object" && !Array.isArray(attr.defaultValue) && attr.defaultValue !== null) {
1122
- if (Object.entries(attr.defaultValue).length === 0) {
1123
- fieldBuilder.attribute(`default("{}")`);
1124
- continue;
1125
- }
1126
- fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
1127
- }
1128
- if (field === "createdAt") fieldBuilder.attribute("default(now())");
1129
- else if (typeof attr.defaultValue === "string" && provider !== "mysql") fieldBuilder.attribute(`default("${attr.defaultValue}")`);
1130
- else if (typeof attr.defaultValue === "boolean" || typeof attr.defaultValue === "number") fieldBuilder.attribute(`default(${attr.defaultValue})`);
1131
- else if (typeof attr.defaultValue === "function") {}
1132
- }
1133
- if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
1134
- else if (attr.onUpdate) {}
1135
- if (attr.references) {
1136
- if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
1137
- const referencedOriginalModelName = getModelName(attr.references.model);
1138
- const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
1139
- let action = "Cascade";
1140
- if (attr.references.onDelete === "no action") action = "NoAction";
1141
- else if (attr.references.onDelete === "set null") action = "SetNull";
1142
- else if (attr.references.onDelete === "set default") action = "SetDefault";
1143
- else if (attr.references.onDelete === "restrict") action = "Restrict";
1144
- const relationField = `relation(fields: [${getFieldName({
1145
- model: originalTableName,
1146
- field: fieldName
1147
- })}], references: [${getFieldName({
1148
- model: attr.references.model,
1149
- field: attr.references.field
1150
- })}], onDelete: ${action})`;
1151
- builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${attr.required === false ? "?" : ""}`).attribute(relationField);
1152
- }
1153
- if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
1154
- }
1155
- if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
1156
- const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
1157
- const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
1158
- const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
1159
- const isUnique = fkFieldAttr?.unique === true;
1160
- const fieldName = isUnique || adapter.options?.usePlural === true ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
1161
- if (!builder.findByType("field", {
1162
- name: fieldName,
1163
- within: prismaModel?.properties
1164
- })) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
1244
+ }[type];
1245
+ if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
1246
+ return dbTypeMap[databaseType];
1247
+ }
1248
+ let id = "";
1249
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1250
+ if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
1251
+ else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
1252
+ else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
1253
+ else id = `int("id").autoincrement().primaryKey()`;
1254
+ else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
1255
+ else if (databaseType === "pg") id = `text('id').primaryKey()`;
1256
+ else id = `text('id').primaryKey()`;
1257
+ const indexes = [];
1258
+ const assignIndexes = (indexes) => {
1259
+ if (!indexes.length) return "";
1260
+ const code = [`, (table) => [`];
1261
+ for (const index of indexes) code.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
1262
+ code.push(`]`);
1263
+ return code.join("\n");
1264
+ };
1265
+ const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
1266
+ id: ${id},
1267
+ ${Object.keys(fields).map((field) => {
1268
+ const attr = fields[field];
1269
+ const fieldName = attr.fieldName || field;
1270
+ let type = getType(fieldName, attr);
1271
+ if (attr.index && !attr.unique) indexes.push({
1272
+ type: "index",
1273
+ name: `${modelName}_${fieldName}_idx`,
1274
+ on: fieldName
1275
+ });
1276
+ else if (attr.index && attr.unique) indexes.push({
1277
+ type: "uniqueIndex",
1278
+ name: `${modelName}_${fieldName}_uidx`,
1279
+ on: fieldName
1280
+ });
1281
+ if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
1282
+ if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
1283
+ else type += `.defaultNow()`;
1284
+ } else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
1285
+ else type += `.default(${attr.defaultValue})`;
1286
+ if (attr.onUpdate && attr.type === "date") {
1287
+ if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
1165
1288
  }
1166
- const indexedFieldsForModel = indexedFields.get(modelName);
1167
- if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
1168
- if (prismaModel) {
1169
- if (prismaModel.properties.some((v) => v.type === "attribute" && v.name === "index" && JSON.stringify(v.args[0]?.value).includes(fieldName))) continue;
1170
- }
1171
- const field = Object.entries(fields).find(([key, attr]) => (attr.fieldName || key) === fieldName)?.[1];
1172
- let indexField = fieldName;
1173
- if (provider === "mysql" && field && field.type === "string") {
1174
- const useNumberId = options.advanced?.database?.generateId === "serial";
1175
- const useUUIDs = options.advanced?.database?.generateId === "uuid";
1176
- if (field.references?.field === "id" && (useNumberId || useUUIDs)) indexField = `${fieldName}`;
1177
- else indexField = `${fieldName}(length: 191)`;
1289
+ return `${fieldName}: ${type}${attr.required !== false ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
1290
+ model: attr.references.model,
1291
+ field: attr.references.field
1292
+ })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
1293
+ }).join(",\n ")}
1294
+ }${assignIndexes(indexes)});`;
1295
+ code += `\n${schema}\n`;
1296
+ }
1297
+ let relationsString = "";
1298
+ for (const tableKey in tables) {
1299
+ const table = tables[tableKey];
1300
+ const modelName = getModelName(tableKey);
1301
+ const oneRelations = [];
1302
+ const manyRelations = [];
1303
+ const manyRelationsSet = /* @__PURE__ */ new Set();
1304
+ const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
1305
+ for (const [fieldName, field] of foreignFields) {
1306
+ const referencedModel = field.references.model;
1307
+ const relationKey = getModelName(referencedModel);
1308
+ const fieldRef = `${getModelName(tableKey)}.${getFieldName({
1309
+ model: tableKey,
1310
+ field: fieldName
1311
+ })}`;
1312
+ const referenceRef = `${getModelName(referencedModel)}.${getFieldName({
1313
+ model: referencedModel,
1314
+ field: field.references.field || "id"
1315
+ })}`;
1316
+ oneRelations.push({
1317
+ key: relationKey,
1318
+ model: getModelName(referencedModel),
1319
+ type: "one",
1320
+ reference: {
1321
+ field: fieldRef,
1322
+ references: referenceRef,
1323
+ fieldName
1178
1324
  }
1179
- builder.model(modelName).blockAttribute(`index([${indexField}])`);
1180
- }
1181
- const hasAttribute = builder.findByType("attribute", {
1182
- name: "map",
1183
- within: prismaModel?.properties
1184
1325
  });
1185
- const hasChanged = customModelName !== originalTableName;
1186
- if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
1187
1326
  }
1188
- });
1189
- const schemaChanged = schema.trim() !== schemaPrisma.trim();
1327
+ const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
1328
+ const modelRelationsMap = /* @__PURE__ */ new Map();
1329
+ for (const [modelName, otherTable] of otherModels) {
1330
+ const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
1331
+ if (foreignKeysPointingHere.length === 0) continue;
1332
+ const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
1333
+ const hasMany = foreignKeysPointingHere.some(([_, field]) => !field.unique);
1334
+ modelRelationsMap.set(modelName, {
1335
+ modelName,
1336
+ hasUnique,
1337
+ hasMany
1338
+ });
1339
+ }
1340
+ for (const { modelName, hasMany } of modelRelationsMap.values()) {
1341
+ const relationType = hasMany ? "many" : "one";
1342
+ let relationKey = getModelName(modelName);
1343
+ if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
1344
+ if (!manyRelationsSet.has(relationKey)) {
1345
+ manyRelationsSet.add(relationKey);
1346
+ manyRelations.push({
1347
+ key: relationKey,
1348
+ model: getModelName(modelName),
1349
+ type: relationType
1350
+ });
1351
+ }
1352
+ }
1353
+ const relationsByModel = /* @__PURE__ */ new Map();
1354
+ for (const relation of oneRelations) if (relation.reference) {
1355
+ const modelKey = relation.key;
1356
+ if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
1357
+ relationsByModel.get(modelKey).push(relation);
1358
+ }
1359
+ const duplicateRelations = [];
1360
+ const singleRelations = [];
1361
+ for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
1362
+ else singleRelations.push(relations[0]);
1363
+ for (const relation of duplicateRelations) if (relation.reference) {
1364
+ const fieldName = relation.reference.fieldName;
1365
+ const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
1366
+ ${relation.key}: one(${relation.model}, {
1367
+ fields: [${relation.reference.field}],
1368
+ references: [${relation.reference.references}],
1369
+ })
1370
+ }))`;
1371
+ relationsString += `\n${tableRelation}\n`;
1372
+ }
1373
+ const hasOne = singleRelations.length > 0;
1374
+ const hasMany = manyRelations.length > 0;
1375
+ if (hasOne && hasMany) {
1376
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
1377
+ ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
1378
+ fields: [${relation.reference.field}],
1379
+ references: [${relation.reference.references}],
1380
+ })` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
1381
+ ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
1382
+ }))`;
1383
+ relationsString += `\n${tableRelation}\n`;
1384
+ } else if (hasOne) {
1385
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
1386
+ ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
1387
+ fields: [${relation.reference.field}],
1388
+ references: [${relation.reference.references}],
1389
+ })` : "").filter((x) => x !== "").join(",\n ")}
1390
+ }))`;
1391
+ relationsString += `\n${tableRelation}\n`;
1392
+ } else if (hasMany) {
1393
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
1394
+ ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
1395
+ }))`;
1396
+ relationsString += `\n${tableRelation}\n`;
1397
+ }
1398
+ }
1399
+ code += `\n${relationsString}`;
1190
1400
  return {
1191
- code: schemaChanged ? schema : "",
1401
+ code: await prettier.format(code, { parser: "typescript" }),
1192
1402
  fileName: filePath,
1193
- overwrite: schemaPrismaExist && schemaChanged
1403
+ overwrite: fileExist
1194
1404
  };
1195
1405
  };
1196
- const getNewPrisma = (provider, cwd) => {
1197
- const prismaVersion = getPrismaVersion(cwd);
1198
- const isV7 = prismaVersion && prismaVersion >= 7;
1199
- const clientProvider = isV7 ? "prisma-client" : "prisma-client-js";
1200
- if (isV7) return `generator client {
1201
- provider = "${clientProvider}"
1202
- }
1203
-
1204
- datasource db {
1205
- provider = "${provider}"
1206
- }`;
1207
- return `generator client {
1208
- provider = "${clientProvider}"
1209
- }
1210
-
1211
- datasource db {
1212
- provider = "${provider}"
1213
- url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
1214
- }`;
1215
- };
1216
- //#endregion
1217
- //#region src/generators/index.ts
1218
- const adapters = {
1219
- prisma: generatePrismaSchema,
1220
- drizzle: generateDrizzleSchema,
1221
- kysely: generateKyselySchema
1222
- };
1223
- const generateSchema = (opts) => {
1224
- const adapter = opts.adapter;
1225
- const generator = adapter.id in adapters ? adapters[adapter.id] : null;
1226
- if (generator) return generator(opts);
1227
- if (adapter.createSchema) return adapter.createSchema(opts.options, opts.file).then(({ code, path: fileName, overwrite }) => ({
1228
- code,
1229
- fileName,
1230
- overwrite
1231
- }));
1232
- throw new Error(`${adapter.id} is not supported. If it is a custom adapter, please request the maintainer to implement createSchema`);
1233
- };
1234
- //#endregion
1235
- //#region src/utils/add-cloudflare-modules.ts
1236
- const createModule = () => {
1237
- return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
1238
- const createStub = (label) => {
1239
- const handler = {
1240
- get(_, prop) {
1241
- if (prop === "toString") return () => label;
1242
- if (prop === "valueOf") return () => label;
1243
- if (prop === Symbol.toPrimitive) return () => label;
1244
- if (prop === Symbol.toStringTag) return "Object";
1245
- if (prop === "then") return undefined;
1246
- return createStub(label + "." + String(prop));
1247
- },
1248
- apply(_, __, args) {
1249
- return createStub(label + "()")
1250
- },
1251
- construct() {
1252
- return createStub(label + "#instance");
1253
- },
1254
- };
1255
- const fn = () => createStub(label + "()");
1256
- return new Proxy(fn, handler);
1257
- };
1258
-
1259
- class WorkerEntrypoint {
1260
- constructor(ctx, env) {
1261
- this.ctx = ctx;
1262
- this.env = env;
1263
- }
1264
- }
1265
-
1266
- class DurableObject {
1267
- constructor(state, env) {
1268
- this.state = state;
1269
- this.env = env;
1270
- }
1271
- }
1272
-
1273
- class RpcTarget {
1274
- constructor(value) {
1275
- this.value = value;
1276
- }
1277
- }
1278
-
1279
- const RpcStub = RpcTarget;
1280
-
1281
- const env = createStub("env");
1282
- const caches = createStub("caches");
1283
- const scheduler = createStub("scheduler");
1284
- const executionCtx = createStub("executionCtx");
1285
-
1286
- export { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint, caches, env, executionCtx, scheduler };
1287
-
1288
- const defaultExport = {
1289
- DurableObject,
1290
- RpcStub,
1291
- RpcTarget,
1292
- WorkerEntrypoint,
1293
- caches,
1294
- env,
1295
- executionCtx,
1296
- scheduler,
1297
- };
1298
-
1299
- export default defaultExport;
1300
- // jiti dirty hack: .unknown
1301
- `)}`;
1302
- };
1303
- const CLOUDFLARE_STUB_MODULE = createModule();
1304
- function addCloudflareModules(aliases, _cwd) {
1305
- if (!aliases["cloudflare:workers"]) aliases["cloudflare:workers"] = CLOUDFLARE_STUB_MODULE;
1306
- if (!aliases["cloudflare:test"]) aliases["cloudflare:test"] = CLOUDFLARE_STUB_MODULE;
1307
- }
1308
- //#endregion
1309
- //#region src/utils/add-svelte-kit-env-modules.ts
1310
- /**
1311
- * Adds SvelteKit environment modules and path aliases
1312
- * @param aliases - The aliases object to populate
1313
- * @param cwd - Current working directory (optional, defaults to process.cwd())
1314
- */
1315
- function addSvelteKitEnvModules(aliases, cwd) {
1316
- const workingDir = cwd || process.cwd();
1317
- aliases["$env/dynamic/private"] = createDataUriModule(createDynamicEnvModule());
1318
- aliases["$env/dynamic/public"] = createDataUriModule(createDynamicEnvModule());
1319
- aliases["$env/static/private"] = createDataUriModule(createStaticEnvModule(filterPrivateEnv("PUBLIC_", "")));
1320
- aliases["$env/static/public"] = createDataUriModule(createStaticEnvModule(filterPublicEnv("PUBLIC_", "")));
1321
- const svelteKitAliases = getSvelteKitPathAliases(workingDir);
1322
- Object.assign(aliases, svelteKitAliases);
1323
- }
1324
- function getSvelteKitPathAliases(cwd) {
1325
- const aliases = {};
1326
- const packageJsonPath = path.join(cwd, "package.json");
1327
- const svelteConfigPath = path.join(cwd, "svelte.config.js");
1328
- const svelteConfigTsPath = path.join(cwd, "svelte.config.ts");
1329
- let isSvelteKitProject = false;
1330
- if (fs.existsSync(packageJsonPath)) try {
1331
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1332
- isSvelteKitProject = !!{
1333
- ...packageJson.dependencies,
1334
- ...packageJson.devDependencies
1335
- }["@sveltejs/kit"];
1336
- } catch {}
1337
- if (!isSvelteKitProject) isSvelteKitProject = fs.existsSync(svelteConfigPath) || fs.existsSync(svelteConfigTsPath);
1338
- if (!isSvelteKitProject) return aliases;
1339
- const libPaths = [path.join(cwd, "src", "lib"), path.join(cwd, "lib")];
1340
- for (const libPath of libPaths) if (fs.existsSync(libPath)) {
1341
- aliases["$lib"] = libPath;
1342
- for (const subPath of [
1343
- "server",
1344
- "utils",
1345
- "components",
1346
- "stores"
1347
- ]) {
1348
- const subDir = path.join(libPath, subPath);
1349
- if (fs.existsSync(subDir)) aliases[`$lib/${subPath}`] = subDir;
1406
+ function generateImport({ databaseType, tables, options }) {
1407
+ const rootImports = ["relations"];
1408
+ const coreImports = [];
1409
+ let hasBigint = false;
1410
+ let hasJson = false;
1411
+ for (const table of Object.values(tables)) {
1412
+ for (const field of Object.values(table.fields)) {
1413
+ if (field.bigint) hasBigint = true;
1414
+ if (field.type === "json") hasJson = true;
1350
1415
  }
1351
- break;
1352
- }
1353
- aliases["$app/server"] = createDataUriModule(createAppServerModule());
1354
- const customAliases = getSvelteConfigAliases(cwd);
1355
- Object.assign(aliases, customAliases);
1356
- return aliases;
1357
- }
1358
- function getSvelteConfigAliases(cwd) {
1359
- const aliases = {};
1360
- const configPaths = [path.join(cwd, "svelte.config.js"), path.join(cwd, "svelte.config.ts")];
1361
- for (const configPath of configPaths) if (fs.existsSync(configPath)) {
1362
- try {
1363
- const aliasMatch = fs.readFileSync(configPath, "utf-8").match(/alias\s*:\s*\{([^}]+)\}/);
1364
- if (aliasMatch && aliasMatch[1]) {
1365
- const aliasMatches = aliasMatch[1].matchAll(/['"`](\$[^'"`]+)['"`]\s*:\s*['"`]([^'"`]+)['"`]/g);
1366
- for (const match of aliasMatches) {
1367
- const [, alias, target] = match;
1368
- if (alias && target) {
1369
- aliases[alias + "/*"] = path.resolve(cwd, target) + "/*";
1370
- aliases[alias] = path.resolve(cwd, target);
1371
- }
1372
- }
1373
- }
1374
- } catch {}
1375
- break;
1376
- }
1377
- return aliases;
1378
- }
1379
- function createAppServerModule() {
1380
- return `
1381
- // $app/server stub for CLI compatibility
1382
- export default {};
1383
- // jiti dirty hack: .unknown
1384
- `;
1385
- }
1386
- function createDataUriModule(module) {
1387
- return `data:text/javascript;charset=utf-8,${encodeURIComponent(module)}`;
1388
- }
1389
- function createStaticEnvModule(env) {
1390
- return `
1391
- ${Object.keys(env).filter((k) => validIdentifier.test(k) && !reserved.has(k)).map((k) => `export const ${k} = ${JSON.stringify(env[k])};`).join("\n")}
1392
- // jiti dirty hack: .unknown
1393
- `;
1394
- }
1395
- function createDynamicEnvModule() {
1396
- return `
1397
- export const env = process.env;
1398
- // jiti dirty hack: .unknown
1399
- `;
1400
- }
1401
- function filterPrivateEnv(publicPrefix, privatePrefix) {
1402
- return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(privatePrefix) && (publicPrefix === "" || !k.startsWith(publicPrefix))));
1403
- }
1404
- function filterPublicEnv(publicPrefix, privatePrefix) {
1405
- return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(publicPrefix) && (privatePrefix === "" || !k.startsWith(privatePrefix))));
1406
- }
1407
- const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1408
- const reserved = new Set([
1409
- "do",
1410
- "if",
1411
- "in",
1412
- "for",
1413
- "let",
1414
- "new",
1415
- "try",
1416
- "var",
1417
- "case",
1418
- "else",
1419
- "enum",
1420
- "eval",
1421
- "null",
1422
- "this",
1423
- "true",
1424
- "void",
1425
- "with",
1426
- "await",
1427
- "break",
1428
- "catch",
1429
- "class",
1430
- "const",
1431
- "false",
1432
- "super",
1433
- "throw",
1434
- "while",
1435
- "yield",
1436
- "delete",
1437
- "export",
1438
- "import",
1439
- "public",
1440
- "return",
1441
- "static",
1442
- "switch",
1443
- "typeof",
1444
- "default",
1445
- "extends",
1446
- "finally",
1447
- "package",
1448
- "private",
1449
- "continue",
1450
- "debugger",
1451
- "function",
1452
- "arguments",
1453
- "interface",
1454
- "protected",
1455
- "implements",
1456
- "instanceof"
1457
- ]);
1458
- //#endregion
1459
- //#region src/utils/get-config.ts
1460
- let possiblePaths$1 = [
1461
- "auth.ts",
1462
- "auth.tsx",
1463
- "auth.js",
1464
- "auth.jsx",
1465
- "auth.server.js",
1466
- "auth.server.ts",
1467
- "auth/index.ts",
1468
- "auth/index.tsx",
1469
- "auth/index.js",
1470
- "auth/index.jsx",
1471
- "auth/index.server.js",
1472
- "auth/index.server.ts"
1473
- ];
1474
- possiblePaths$1 = [
1475
- ...possiblePaths$1,
1476
- ...possiblePaths$1.map((it) => `lib/server/${it}`),
1477
- ...possiblePaths$1.map((it) => `server/auth/${it}`),
1478
- ...possiblePaths$1.map((it) => `server/${it}`),
1479
- ...possiblePaths$1.map((it) => `auth/${it}`),
1480
- ...possiblePaths$1.map((it) => `lib/${it}`),
1481
- ...possiblePaths$1.map((it) => `utils/${it}`)
1482
- ];
1483
- possiblePaths$1 = [
1484
- ...possiblePaths$1,
1485
- ...possiblePaths$1.map((it) => `src/${it}`),
1486
- ...possiblePaths$1.map((it) => `app/${it}`)
1487
- ];
1488
- /** Reads `references` from raw tsconfig JSON (stripped out by `parseTsconfig`). */
1489
- function readRawTsconfigReferences(tsconfigPath) {
1490
- try {
1491
- const stripped = fs.readFileSync(tsconfigPath, "utf-8").replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(?=\s*[}\]])/g, "");
1492
- return JSON.parse(stripped).references;
1493
- } catch {
1494
- return;
1416
+ if (hasJson && hasBigint) break;
1495
1417
  }
1496
- }
1497
- /** Recursively collects tsconfigs reachable via `references`. */
1498
- function collectReferencedTsconfigs(tsconfigPath, visited = /* @__PURE__ */ new Set()) {
1499
- const result = [];
1500
- const refs = readRawTsconfigReferences(tsconfigPath);
1501
- if (!refs) return result;
1502
- const configDir = path.dirname(tsconfigPath);
1503
- for (const ref of refs) {
1504
- const resolvedRef = path.resolve(configDir, ref.path);
1505
- const refTsconfigPath = resolvedRef.endsWith(".json") ? resolvedRef : path.join(resolvedRef, "tsconfig.json");
1506
- if (visited.has(refTsconfigPath)) continue;
1507
- visited.add(refTsconfigPath);
1508
- try {
1509
- const refConfig = parseTsconfig(refTsconfigPath);
1510
- result.push({
1511
- path: refTsconfigPath,
1512
- config: refConfig
1513
- });
1514
- } catch {
1515
- continue;
1516
- }
1517
- result.push(...collectReferencedTsconfigs(refTsconfigPath, visited));
1418
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1419
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1420
+ coreImports.push(`${databaseType}Table`);
1421
+ coreImports.push(databaseType === "mysql" ? "varchar, text" : databaseType === "pg" ? "text" : "text");
1422
+ coreImports.push(hasBigint ? databaseType !== "sqlite" ? "bigint" : "" : "");
1423
+ coreImports.push(databaseType !== "sqlite" ? "timestamp, boolean" : "");
1424
+ if (databaseType === "mysql") {
1425
+ const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
1426
+ if (useNumberId || hasNonBigintNumber) coreImports.push("int");
1427
+ 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");
1428
+ } else if (databaseType === "pg") {
1429
+ if (useUUIDs) rootImports.push("sql");
1430
+ const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
1431
+ const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
1432
+ if (hasNonBigintNumber || options.advanced?.database?.generateId === "serial" && hasFkToId) coreImports.push("integer");
1433
+ } else coreImports.push("integer");
1434
+ if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
1435
+ if (hasJson) {
1436
+ if (databaseType === "pg") coreImports.push("jsonb");
1437
+ if (databaseType === "mysql") coreImports.push("json");
1518
1438
  }
1519
- return result;
1439
+ 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");
1440
+ const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
1441
+ const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
1442
+ if (hasIndexes) coreImports.push("index");
1443
+ if (hasUniqueIndexes) coreImports.push("uniqueIndex");
1444
+ 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`;
1520
1445
  }
1521
- /**
1522
- * Ordered `paths` matchers from the project tsconfig and any referenced
1523
- * tsconfigs, following TypeScript canonical resolution semantics.
1524
- * @see https://github.com/microsoft/TypeScript/blob/main/src/compiler/moduleNameResolver.ts
1525
- */
1526
- function collectPathsMatchers(cwd) {
1527
- const tsconfig = getTsconfig(cwd, fs.existsSync(path.join(cwd, "tsconfig.json")) ? "tsconfig.json" : "jsconfig.json");
1528
- if (!tsconfig) return [];
1529
- const matchers = [];
1446
+ //#endregion
1447
+ //#region src/generators/kysely.ts
1448
+ const generateKyselySchema = async ({ options, file }) => {
1449
+ const { compileMigrations } = await getMigrations(options);
1450
+ const migrations = await compileMigrations();
1451
+ return {
1452
+ code: migrations.trim() === ";" ? "" : migrations,
1453
+ fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
1454
+ };
1455
+ };
1456
+ //#endregion
1457
+ //#region src/utils/helper.ts
1458
+ async function tryCatch(promise) {
1530
1459
  try {
1531
- const mainMatcher = createPathsMatcher(tsconfig);
1532
- if (mainMatcher) matchers.push(mainMatcher);
1533
- for (const refTsconfig of collectReferencedTsconfigs(tsconfig.path)) {
1534
- const refMatcher = createPathsMatcher(refTsconfig);
1535
- if (refMatcher) matchers.push(refMatcher);
1536
- }
1460
+ return {
1461
+ data: await promise,
1462
+ error: null
1463
+ };
1537
1464
  } catch (error) {
1538
- console.error(error);
1539
- throw new BetterAuthError("Error parsing tsconfig.json");
1465
+ return {
1466
+ data: null,
1467
+ error
1468
+ };
1540
1469
  }
1541
- return matchers;
1542
1470
  }
1543
- /**
1544
- * Source file extensions jiti can load. Shared between the jiti `extensions`
1545
- * option and `resolveCandidateFile` so both stay in sync.
1546
- */
1547
- const SOURCE_EXTENSIONS = [
1548
- ".ts",
1549
- ".tsx",
1550
- ".mts",
1551
- ".cts",
1552
- ".js",
1553
- ".jsx",
1554
- ".mjs",
1555
- ".cjs"
1556
- ];
1557
- const SOURCE_EXTENSIONS_SET = new Set(SOURCE_EXTENSIONS);
1558
- /** Probes a candidate as-is, with known extensions, and as a directory index. */
1559
- function resolveCandidateFile(candidate) {
1560
- try {
1561
- if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
1562
- } catch {}
1563
- if (SOURCE_EXTENSIONS_SET.has(path.extname(candidate))) return;
1564
- for (const ext of SOURCE_EXTENSIONS) {
1565
- const withExt = candidate + ext;
1566
- if (fs.existsSync(withExt)) return withExt;
1567
- }
1568
- for (const ext of SOURCE_EXTENSIONS) {
1569
- const asIndex = path.join(candidate, `index${ext}`);
1570
- if (fs.existsSync(asIndex)) return asIndex;
1571
- }
1471
+ const generateSecretHash = () => {
1472
+ return Crypto.randomBytes(16).toString("hex");
1473
+ };
1474
+ const spawnCommand = (cmd, cwd = process.cwd()) => new Promise((resolve, reject) => {
1475
+ const child = spawn(cmd, {
1476
+ cwd,
1477
+ stdio: "inherit",
1478
+ shell: true
1479
+ });
1480
+ child.on("close", (code, signal) => {
1481
+ if (code !== 0 && code !== null) reject(/* @__PURE__ */ new Error(`Exited with code ${code}`));
1482
+ else if (signal) reject(/* @__PURE__ */ new Error(`Killed with signal ${signal}`));
1483
+ else resolve();
1484
+ });
1485
+ child.on("error", reject);
1486
+ });
1487
+ //#endregion
1488
+ //#region src/utils/get-package-info.ts
1489
+ function getPackageInfo(cwd) {
1490
+ const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
1491
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1572
1492
  }
1573
- function resolveWithMatchers(specifier, matchers) {
1574
- for (const matcher of matchers) for (const candidate of matcher(specifier)) {
1575
- const resolved = resolveCandidateFile(candidate);
1576
- if (resolved) return resolved;
1493
+ function getPrismaVersion(cwd) {
1494
+ try {
1495
+ const packageInfo = getPackageInfo(cwd);
1496
+ const prismaVersion = packageInfo.dependencies?.prisma || packageInfo.devDependencies?.prisma || packageInfo.dependencies?.["@prisma/client"] || packageInfo.devDependencies?.["@prisma/client"];
1497
+ if (!prismaVersion) return null;
1498
+ const match = prismaVersion.match(/(\d+)/);
1499
+ return match ? parseInt(match[1], 10) : null;
1500
+ } catch {
1501
+ return null;
1577
1502
  }
1578
1503
  }
1579
1504
  /**
1580
- * Callees whose first string argument is a module specifier. `jitiImport` is
1581
- * a jiti-side preprocessor artifact observed in the AST; revisit on jiti
1582
- * major version bumps (the regression suite catches a rename but not the why).
1505
+ * Checks if a package has a specific dependency.
1506
+ *
1507
+ * @param packageJson The package.json object
1508
+ * @param dependency The dependency to check for
1509
+ * @returns true if the package has the dependency
1583
1510
  */
1584
- const LOADER_IDENTIFIERS = new Set([
1585
- "require",
1586
- "import",
1587
- "jitiImport"
1588
- ]);
1511
+ function hasDependency(packageJson, dependency) {
1512
+ let hasDependency = false;
1513
+ if (packageJson.dependencies?.[dependency] || packageJson.devDependencies?.[dependency] || packageJson.peerDependencies?.[dependency] || packageJson.optionalDependencies?.[dependency]) hasDependency = true;
1514
+ return hasDependency;
1515
+ }
1589
1516
  /**
1590
- * Rewrites aliased specifiers at AST level. Required because jiti's `alias`
1591
- * option only supports prefix matching and cannot express mid-path wildcards.
1517
+ * Checks if a directory is a monorepo root by looking for common monorepo indicators.
1592
1518
  *
1593
- * Matchers always take precedence over native resolution, mirroring
1594
- * TypeScript's own `paths` `node_modules` order.
1519
+ * @param dir Directory to check
1520
+ * @returns true if the directory appears to be a monorepo root
1595
1521
  */
1596
- function createRewriteImportPathsPlugin(matchers) {
1597
- return ({ types: t }) => {
1598
- const rewrite = (source) => {
1599
- if (!source) return;
1600
- const resolved = resolveWithMatchers(source.value, matchers);
1601
- if (resolved) source.value = resolved;
1602
- };
1603
- return { visitor: {
1604
- ImportDeclaration(p) {
1605
- rewrite(p.node.source);
1606
- },
1607
- ExportNamedDeclaration(p) {
1608
- rewrite(p.node.source);
1609
- },
1610
- ExportAllDeclaration(p) {
1611
- rewrite(p.node.source);
1612
- },
1613
- ImportExpression(p) {
1614
- if (t.isStringLiteral(p.node.source)) rewrite(p.node.source);
1615
- },
1616
- CallExpression(p) {
1617
- const { callee, arguments: args } = p.node;
1618
- const first = args[0];
1619
- if (!t.isStringLiteral(first)) return;
1620
- if (!(t.isIdentifier(callee) && LOADER_IDENTIFIERS.has(callee.name) || t.isImport(callee))) return;
1621
- rewrite(first);
1622
- }
1623
- } };
1624
- };
1625
- }
1626
- /** Virtual module aliases; real tsconfig paths go through the babel plugin. */
1627
- function getVirtualModuleAliases() {
1628
- const result = {};
1629
- addSvelteKitEnvModules(result);
1630
- addCloudflareModules(result);
1631
- return result;
1522
+ async function isMonorepoRoot(dir) {
1523
+ const { data: files } = await tryCatch(fs$1.readdir(dir, "utf-8"));
1524
+ if (!files) return false;
1525
+ if (files.includes("pnpm-workspace.yaml")) return true;
1526
+ if (files.includes("package.json")) {
1527
+ const packageJsonPath = path.join(dir, "package.json");
1528
+ const { data } = await tryCatch(fs$1.readFile(packageJsonPath, "utf-8"));
1529
+ if (data) try {
1530
+ const packageJson = JSON.parse(data);
1531
+ if (packageJson.workspaces && (Array.isArray(packageJson.workspaces) || typeof packageJson.workspaces === "object")) return true;
1532
+ } catch {}
1533
+ }
1534
+ return [
1535
+ "lerna.json",
1536
+ "turbo.json",
1537
+ "nx.json",
1538
+ "rush.json"
1539
+ ].some((indicator) => files.includes(indicator));
1632
1540
  }
1633
1541
  /**
1634
- * .tsx files are not supported by Jiti.
1542
+ * Finds the monorepo root by walking up the directory tree.
1543
+ *
1544
+ * @param startDir Starting directory
1545
+ * @returns Path to monorepo root, or null if not found
1635
1546
  */
1636
- const jitiOptions = (cwd) => {
1637
- const matchers = collectPathsMatchers(cwd);
1638
- const plugins = matchers.length > 0 ? [createRewriteImportPathsPlugin(matchers)] : [];
1639
- return {
1640
- transformOptions: { babel: {
1641
- presets: [[babelPresetTypeScript, {
1642
- isTSX: true,
1643
- allExtensions: true
1644
- }], [babelPresetReact, { runtime: "automatic" }]],
1645
- plugins
1646
- } },
1647
- extensions: [...SOURCE_EXTENSIONS],
1648
- alias: getVirtualModuleAliases()
1649
- };
1650
- };
1651
- const isDefaultExport = (object) => {
1652
- return typeof object === "object" && object !== null && !Array.isArray(object) && Object.keys(object).length > 0 && "options" in object;
1653
- };
1654
- async function getConfig({ cwd, configPath, shouldThrowOnError = false }) {
1655
- try {
1656
- let configFile = null;
1657
- if (configPath) {
1658
- let resolvedPath = path.join(cwd, configPath);
1659
- if (existsSync(configPath)) resolvedPath = configPath;
1660
- const { config } = await loadConfig({
1661
- configFile: resolvedPath,
1662
- dotenv: { fileName: [".env", ".env.local"] },
1663
- jitiOptions: jitiOptions(cwd),
1664
- cwd
1665
- });
1666
- if (!("auth" in config) && !isDefaultExport(config)) {
1667
- if (shouldThrowOnError) throw new Error(`Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
1668
- console.error(`[#better-auth]: Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
1669
- process.exit(1);
1547
+ async function findMonorepoRoot(startDir) {
1548
+ let currentDir = path.resolve(startDir);
1549
+ const root = path.parse(currentDir).root;
1550
+ while (currentDir !== root) {
1551
+ if (await isMonorepoRoot(currentDir)) return currentDir;
1552
+ const parentDir = path.dirname(currentDir);
1553
+ if (parentDir === currentDir) break;
1554
+ currentDir = parentDir;
1555
+ }
1556
+ return null;
1557
+ }
1558
+ //#endregion
1559
+ //#region src/generators/prisma.ts
1560
+ const generatePrismaSchema = async ({ adapter, options, file }) => {
1561
+ const provider = adapter.options?.provider || "postgresql";
1562
+ const tables = getAuthTables(options);
1563
+ const filePath = file || "./prisma/schema.prisma";
1564
+ const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
1565
+ const getModelName = initGetModelName({
1566
+ schema: getAuthTables(options),
1567
+ usePlural: adapter.options?.adapterConfig?.usePlural
1568
+ });
1569
+ const getFieldName = initGetFieldName({
1570
+ schema: getAuthTables(options),
1571
+ usePlural: false
1572
+ });
1573
+ let schemaPrisma = "";
1574
+ if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(path.join(process.cwd(), filePath), "utf-8");
1575
+ else schemaPrisma = getNewPrisma(provider, process.cwd());
1576
+ const prismaVersion = getPrismaVersion(process.cwd());
1577
+ if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
1578
+ const generator = builder.findByType("generator", { name: "client" });
1579
+ if (generator && generator.properties) {
1580
+ const providerProp = generator.properties.find((prop) => prop.type === "assignment" && prop.key === "provider");
1581
+ if (providerProp && providerProp.value === "\"prisma-client-js\"") providerProp.value = "\"prisma-client\"";
1582
+ }
1583
+ const datasource = builder.findByType("datasource", { name: "db" });
1584
+ if (datasource && datasource.properties) {
1585
+ const urlIndex = datasource.properties.findIndex((prop) => prop.type === "assignment" && prop.key === "url");
1586
+ if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
1587
+ }
1588
+ });
1589
+ const manyToManyRelations = /* @__PURE__ */ new Map();
1590
+ for (const table in tables) {
1591
+ const fields = tables[table]?.fields;
1592
+ for (const field in fields) {
1593
+ const attr = fields[field];
1594
+ if (attr.references) {
1595
+ const referencedOriginalModel = attr.references.model;
1596
+ const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
1597
+ if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
1598
+ const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
1599
+ manyToManyRelations.get(referencedModelNameCap).add(currentModelNameCap);
1670
1600
  }
1671
- configFile = "auth" in config ? config.auth?.options : config.options;
1672
1601
  }
1673
- if (!configFile) for (const possiblePath of possiblePaths$1) try {
1674
- const { config } = await loadConfig({
1675
- configFile: possiblePath,
1676
- dotenv: { fileName: [".env", ".env.local"] },
1677
- jitiOptions: jitiOptions(cwd),
1678
- cwd
1679
- });
1680
- if (Object.keys(config).length > 0) {
1681
- configFile = config.auth?.options || config.default?.options || null;
1682
- if (!configFile) {
1683
- if (shouldThrowOnError) throw new Error("Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth.");
1684
- console.error("[#better-auth]: Couldn't read your auth config.");
1685
- console.log("");
1686
- console.log("[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth.");
1687
- process.exit(1);
1602
+ }
1603
+ const indexedFields = /* @__PURE__ */ new Map();
1604
+ for (const table in tables) {
1605
+ const fields = tables[table]?.fields;
1606
+ const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
1607
+ indexedFields.set(modelName, []);
1608
+ for (const field in fields) {
1609
+ const attr = fields[field];
1610
+ if (attr.index && !attr.unique) {
1611
+ const fieldName = attr.fieldName || field;
1612
+ indexedFields.get(modelName).push(fieldName);
1613
+ }
1614
+ }
1615
+ }
1616
+ const schema = produceSchema(schemaPrisma, (builder) => {
1617
+ for (const table in tables) {
1618
+ const originalTableName = table;
1619
+ const customModelName = tables[table]?.modelName || table;
1620
+ const modelName = capitalizeFirstLetter(getModelName(customModelName));
1621
+ const fields = tables[table]?.fields;
1622
+ function getType({ isBigint, isOptional, type }) {
1623
+ if (type === "string") return isOptional ? "String?" : "String";
1624
+ if (type === "number" && isBigint) return isOptional ? "BigInt?" : "BigInt";
1625
+ if (type === "number") return isOptional ? "Int?" : "Int";
1626
+ if (type === "boolean") return isOptional ? "Boolean?" : "Boolean";
1627
+ if (type === "date") return isOptional ? "DateTime?" : "DateTime";
1628
+ if (type === "json") {
1629
+ if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
1630
+ return isOptional ? "Json?" : "Json";
1631
+ }
1632
+ if (type === "string[]") {
1633
+ if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
1634
+ return "String[]";
1635
+ }
1636
+ if (type === "number[]") {
1637
+ if (provider === "sqlite" || provider === "mysql") return "String";
1638
+ return "Int[]";
1688
1639
  }
1689
- break;
1690
1640
  }
1691
- } catch (e) {
1692
- if (typeof e === "object" && e && "message" in e && typeof e.message === "string" && e.message.includes("This module cannot be imported from a Client Component module")) {
1693
- if (shouldThrowOnError) throw new Error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
1694
- console.error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
1695
- process.exit(1);
1641
+ const prismaModel = builder.findByType("model", { name: modelName });
1642
+ if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
1643
+ else {
1644
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1645
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1646
+ if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
1647
+ else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
1648
+ else builder.model(modelName).field("id", "String").attribute("id");
1696
1649
  }
1697
- if (shouldThrowOnError) throw e;
1698
- console.error("[#better-auth]: Couldn't read your auth config.", e);
1699
- process.exit(1);
1700
- }
1701
- return configFile;
1702
- } catch (e) {
1703
- if (typeof e === "object" && e && "message" in e && typeof e.message === "string" && e.message.includes("This module cannot be imported from a Client Component module")) {
1704
- if (shouldThrowOnError) throw new Error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
1705
- console.error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
1706
- process.exit(1);
1650
+ for (const field in fields) {
1651
+ const attr = fields[field];
1652
+ const fieldName = attr.fieldName || field;
1653
+ if (prismaModel) {
1654
+ if (builder.findByType("field", {
1655
+ name: fieldName,
1656
+ within: prismaModel.properties
1657
+ })) continue;
1658
+ }
1659
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1660
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1661
+ const fieldBuilder = builder.model(modelName).field(fieldName, field === "id" && useNumberId ? getType({
1662
+ isBigint: false,
1663
+ isOptional: false,
1664
+ type: "number"
1665
+ }) : getType({
1666
+ isBigint: attr?.bigint || false,
1667
+ isOptional: attr?.required === false,
1668
+ type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
1669
+ }));
1670
+ if (field === "id") {
1671
+ fieldBuilder.attribute("id");
1672
+ if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
1673
+ }
1674
+ if (attr.unique) builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
1675
+ if (attr.defaultValue !== void 0) {
1676
+ if (Array.isArray(attr.defaultValue)) {
1677
+ if (attr.type === "json") {
1678
+ if (Object.prototype.toString.call(attr.defaultValue[0]) === "[object Object]") {
1679
+ fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
1680
+ continue;
1681
+ }
1682
+ const jsonArray = [];
1683
+ for (const value of attr.defaultValue) jsonArray.push(value);
1684
+ fieldBuilder.attribute(`default("${JSON.stringify(jsonArray).replace(/"/g, "\\\"")}")`);
1685
+ continue;
1686
+ }
1687
+ if (attr.defaultValue.length === 0) {
1688
+ fieldBuilder.attribute(`default([])`);
1689
+ continue;
1690
+ } else if (typeof attr.defaultValue[0] === "string" && attr.type === "string[]") {
1691
+ const valueArray = [];
1692
+ for (const value of attr.defaultValue) valueArray.push(JSON.stringify(value));
1693
+ fieldBuilder.attribute(`default([${valueArray}])`);
1694
+ } else if (typeof attr.defaultValue[0] === "number") {
1695
+ const valueArray = [];
1696
+ for (const value of attr.defaultValue) valueArray.push(`${value}`);
1697
+ fieldBuilder.attribute(`default([${valueArray}])`);
1698
+ }
1699
+ } else if (typeof attr.defaultValue === "object" && !Array.isArray(attr.defaultValue) && attr.defaultValue !== null) {
1700
+ if (Object.entries(attr.defaultValue).length === 0) {
1701
+ fieldBuilder.attribute(`default("{}")`);
1702
+ continue;
1703
+ }
1704
+ fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
1705
+ }
1706
+ if (field === "createdAt") fieldBuilder.attribute("default(now())");
1707
+ else if (typeof attr.defaultValue === "string" && provider !== "mysql") fieldBuilder.attribute(`default("${attr.defaultValue}")`);
1708
+ else if (typeof attr.defaultValue === "boolean" || typeof attr.defaultValue === "number") fieldBuilder.attribute(`default(${attr.defaultValue})`);
1709
+ else if (typeof attr.defaultValue === "function") {}
1710
+ }
1711
+ if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
1712
+ else if (attr.onUpdate) {}
1713
+ if (attr.references) {
1714
+ if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
1715
+ const referencedOriginalModelName = getModelName(attr.references.model);
1716
+ const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
1717
+ let action = "Cascade";
1718
+ if (attr.references.onDelete === "no action") action = "NoAction";
1719
+ else if (attr.references.onDelete === "set null") action = "SetNull";
1720
+ else if (attr.references.onDelete === "set default") action = "SetDefault";
1721
+ else if (attr.references.onDelete === "restrict") action = "Restrict";
1722
+ const relationField = `relation(fields: [${getFieldName({
1723
+ model: originalTableName,
1724
+ field: fieldName
1725
+ })}], references: [${getFieldName({
1726
+ model: attr.references.model,
1727
+ field: attr.references.field
1728
+ })}], onDelete: ${action})`;
1729
+ builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${attr.required === false ? "?" : ""}`).attribute(relationField);
1730
+ }
1731
+ if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
1732
+ }
1733
+ if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
1734
+ const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
1735
+ const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
1736
+ const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
1737
+ const isUnique = fkFieldAttr?.unique === true;
1738
+ const fieldName = isUnique || adapter.options?.usePlural === true ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
1739
+ if (!builder.findByType("field", {
1740
+ name: fieldName,
1741
+ within: prismaModel?.properties
1742
+ })) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
1743
+ }
1744
+ const indexedFieldsForModel = indexedFields.get(modelName);
1745
+ if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
1746
+ if (prismaModel) {
1747
+ if (prismaModel.properties.some((v) => v.type === "attribute" && v.name === "index" && JSON.stringify(v.args[0]?.value).includes(fieldName))) continue;
1748
+ }
1749
+ const field = Object.entries(fields).find(([key, attr]) => (attr.fieldName || key) === fieldName)?.[1];
1750
+ let indexField = fieldName;
1751
+ if (provider === "mysql" && field && field.type === "string") {
1752
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1753
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1754
+ if (field.references?.field === "id" && (useNumberId || useUUIDs)) indexField = `${fieldName}`;
1755
+ else indexField = `${fieldName}(length: 191)`;
1756
+ }
1757
+ builder.model(modelName).blockAttribute(`index([${indexField}])`);
1758
+ }
1759
+ const hasAttribute = builder.findByType("attribute", {
1760
+ name: "map",
1761
+ within: prismaModel?.properties
1762
+ });
1763
+ const hasChanged = customModelName !== originalTableName;
1764
+ if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
1707
1765
  }
1708
- if (shouldThrowOnError) throw e;
1709
- console.error("Couldn't read your auth config.", e);
1710
- process.exit(1);
1711
- }
1712
- }
1766
+ });
1767
+ const schemaChanged = schema.trim() !== schemaPrisma.trim();
1768
+ return {
1769
+ code: schemaChanged ? schema : "",
1770
+ fileName: filePath,
1771
+ overwrite: schemaPrismaExist && schemaChanged
1772
+ };
1773
+ };
1774
+ const getNewPrisma = (provider, cwd) => {
1775
+ const prismaVersion = getPrismaVersion(cwd);
1776
+ const isV7 = prismaVersion && prismaVersion >= 7;
1777
+ const clientProvider = isV7 ? "prisma-client" : "prisma-client-js";
1778
+ if (isV7) return `generator client {
1779
+ provider = "${clientProvider}"
1780
+ }
1781
+
1782
+ datasource db {
1783
+ provider = "${provider}"
1784
+ }`;
1785
+ return `generator client {
1786
+ provider = "${clientProvider}"
1787
+ }
1788
+
1789
+ datasource db {
1790
+ provider = "${provider}"
1791
+ url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
1792
+ }`;
1793
+ };
1794
+ //#endregion
1795
+ //#region src/generators/index.ts
1796
+ const adapters = {
1797
+ prisma: generatePrismaSchema,
1798
+ drizzle: generateDrizzleSchema,
1799
+ kysely: generateKyselySchema
1800
+ };
1801
+ const generateSchema = (opts) => {
1802
+ const adapter = opts.adapter;
1803
+ const generator = adapter.id in adapters ? adapters[adapter.id] : null;
1804
+ if (generator) return generator(opts);
1805
+ if (adapter.createSchema) return adapter.createSchema(opts.options, opts.file).then(({ code, path: fileName, overwrite }) => ({
1806
+ code,
1807
+ fileName,
1808
+ overwrite
1809
+ }));
1810
+ throw new Error(`${adapter.id} is not supported. If it is a custom adapter, please request the maintainer to implement createSchema`);
1811
+ };
1713
1812
  //#endregion
1714
1813
  //#region src/commands/generate.ts
1715
1814
  function createMockAdapter$1(adapterId, dialect) {
@@ -1746,6 +1845,9 @@ function createMockAdapter$1(adapterId, dialect) {
1746
1845
  deleteMany: async () => {
1747
1846
  throw new Error("Mock adapter methods should not be called");
1748
1847
  },
1848
+ consumeOne: async () => {
1849
+ throw new Error("Mock adapter methods should not be called");
1850
+ },
1749
1851
  transaction: async (callback) => {
1750
1852
  throw new Error("Mock adapter methods should not be called");
1751
1853
  },
@@ -5277,12 +5379,9 @@ const databasesConfig = [
5277
5379
  imports: [createImport({ name: "createPool" })],
5278
5380
  isNamedImport: false
5279
5381
  }],
5280
- preCode: `const dialect = createPool({ host: "localhost", user: "root", password: "password", database: "database", timezone: "Z" })`,
5382
+ preCode: `const database = createPool({ host: "localhost", user: "root", password: "password", database: "database", timezone: "Z" })`,
5281
5383
  code({ additionalOptions }) {
5282
- return kyselyCode({
5283
- provider: "mysql",
5284
- additionalOptions
5285
- });
5384
+ return `database`;
5286
5385
  },
5287
5386
  dependencies: ["mysql2"]
5288
5387
  },
@@ -5293,12 +5392,9 @@ const databasesConfig = [
5293
5392
  imports: [createImport({ name: "Pool" })],
5294
5393
  isNamedImport: false
5295
5394
  }],
5296
- preCode: `const dialect = new Pool({ connectionString: "postgresql://postgres:password@localhost:5432/database" })`,
5395
+ preCode: `const database = new Pool({ connectionString: "postgresql://postgres:password@localhost:5432/database" })`,
5297
5396
  code({ additionalOptions }) {
5298
- return kyselyCode({
5299
- provider: "postgresql",
5300
- additionalOptions
5301
- });
5397
+ return `database`;
5302
5398
  },
5303
5399
  dependencies: ["pg"],
5304
5400
  devDependencies: ["@types/pg"]
@@ -6664,7 +6760,7 @@ const initActionOptionsSchema = z.object({
6664
6760
  //#region src/commands/login.ts
6665
6761
  async function loginAction() {
6666
6762
  try {
6667
- await spawnCommand("npx @better-auth/cli@latest login");
6763
+ await spawnCommand("npx auth@latest login");
6668
6764
  } catch (error) {
6669
6765
  log.error(error.message || "An unknown error occurred");
6670
6766
  process.exit(1);
@@ -6674,7 +6770,7 @@ async function loginAction() {
6674
6770
  const login = new Command("login").description("Login to Better Auth Infrastructure").action(loginAction);
6675
6771
  async function logoutAction() {
6676
6772
  try {
6677
- await spawnCommand("npx @better-auth/cli@latest logout");
6773
+ await spawnCommand("npx auth@latest logout");
6678
6774
  } catch (error) {
6679
6775
  log.error(error.message || "An unknown error occurred");
6680
6776
  process.exit(1);
@@ -7076,7 +7172,7 @@ async function main() {
7076
7172
  packageInfo = await getPackageInfo();
7077
7173
  cliVersion = packageInfo.version || "1.1.2";
7078
7174
  } catch {}
7079
- program.addCommand(ai).addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(info).addCommand(login).addCommand(logout).addCommand(mcp).addCommand(upgrade).version(cliVersion).description("Better Auth CLI").action(() => program.help());
7175
+ program.addCommand(ai).addCommand(createAdmin).addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(info).addCommand(login).addCommand(logout).addCommand(mcp).addCommand(upgrade).version(cliVersion).description("Better Auth CLI").action(() => program.help());
7080
7176
  program.parse();
7081
7177
  }
7082
7178
  main().catch((error) => {