auth 1.7.0-beta.0 → 1.7.0-beta.10

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,1415 @@ 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/cloudflare-virtual-modules.ts
582
+ /**
583
+ * `cloudflare:workers` is a Workers-runtime built-in module. The CLI loads
584
+ * `auth.ts` with jiti, outside that runtime, so a config importing it would
585
+ * crash. It is aliased to an inert stub whose named exports mirror the real
586
+ * module so every import links.
587
+ *
588
+ * Like the SvelteKit stubs, these are *named* exports that must exist at link
589
+ * time, so the surface is enumerated by hand. The list mirrors workerd's
590
+ * `cloudflare:workers` re-export module: the entrypoint/RPC classes are real
591
+ * classes (so `extends` works), and the value/helper exports are recursive
592
+ * proxies that absorb any access (so `env.MY_BINDING.get()` does not throw).
593
+ *
594
+ * `cloudflare:test` is intentionally NOT stubbed: it is a different module with
595
+ * a different surface, provided only by `@cloudflare/vitest-pool-workers` for
596
+ * test runs, and an auth config never imports it.
597
+ *
598
+ * @see https://github.com/cloudflare/workerd/blob/main/src/cloudflare/workers.ts
599
+ */
600
+ const createModule = () => {
601
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
602
+ const createStub = (label) => {
603
+ const handler = {
604
+ get(_, prop) {
605
+ if (prop === "toString") return () => label;
606
+ if (prop === "valueOf") return () => label;
607
+ if (prop === Symbol.toPrimitive) return () => label;
608
+ if (prop === Symbol.toStringTag) return "Object";
609
+ if (prop === "then") return undefined;
610
+ return createStub(label + "." + String(prop));
611
+ },
612
+ apply() {
613
+ return createStub(label + "()");
614
+ },
615
+ construct() {
616
+ return createStub(label + "#instance");
617
+ },
618
+ };
619
+ return new Proxy(function () {}, handler);
620
+ };
621
+
622
+ class WorkerEntrypoint {
623
+ constructor(ctx, env) {
624
+ this.ctx = ctx;
625
+ this.env = env;
626
+ }
584
627
  }
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 })`;
620
- }
621
- return `text('${name}')`;
622
- }
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`;
628
+ class DurableObject {
629
+ constructor(ctx, env) {
630
+ this.ctx = ctx;
631
+ this.env = env;
632
+ }
867
633
  }
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
- };
891
- }
634
+ class WorkflowEntrypoint {
635
+ constructor(ctx, env) {
636
+ this.ctx = ctx;
637
+ this.env = env;
638
+ }
892
639
  }
893
- const generateSecretHash = () => {
894
- return Crypto.randomBytes(16).toString("hex");
640
+ class RpcTarget {}
641
+ class RpcStub {}
642
+ class RpcPromise {}
643
+ class RpcProperty {}
644
+ class ServiceStub {}
645
+
646
+ const env = createStub("env");
647
+ const exportsStub = createStub("exports");
648
+ const cache = createStub("cache");
649
+ const tracing = createStub("tracing");
650
+ const withEnv = createStub("withEnv");
651
+ const withExports = createStub("withExports");
652
+ const withEnvAndExports = createStub("withEnvAndExports");
653
+ const waitUntil = createStub("waitUntil");
654
+ const abortIsolate = createStub("abortIsolate");
655
+
656
+ export {
657
+ WorkerEntrypoint,
658
+ DurableObject,
659
+ WorkflowEntrypoint,
660
+ RpcTarget,
661
+ RpcStub,
662
+ RpcPromise,
663
+ RpcProperty,
664
+ ServiceStub,
665
+ env,
666
+ exportsStub as exports,
667
+ cache,
668
+ tracing,
669
+ withEnv,
670
+ withExports,
671
+ withEnvAndExports,
672
+ waitUntil,
673
+ abortIsolate,
895
674
  };
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"));
914
- }
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
- }
675
+ // jiti dirty hack: .unknown
676
+ `)}`;
677
+ };
678
+ const CLOUDFLARE_STUB_MODULE = createModule();
679
+ function addCloudflareVirtualModules(aliases) {
680
+ if (!aliases["cloudflare:workers"]) aliases["cloudflare:workers"] = CLOUDFLARE_STUB_MODULE;
925
681
  }
682
+ //#endregion
683
+ //#region src/utils/sveltekit-virtual-modules.ts
926
684
  /**
927
- * Checks if a package has a specific dependency.
685
+ * SvelteKit exposes virtual runtime modules (`$env/*`, `$app/*`,
686
+ * `$service-worker`) that exist only while its Vite plugin runs. The CLI loads
687
+ * `auth.ts` with jiti, outside Vite, so a config importing them would crash the
688
+ * loader. Each is aliased to an inert stub.
928
689
  *
929
- * @param packageJson The package.json object
930
- * @param dependency The dependency to check for
931
- * @returns true if the package has the dependency
690
+ * The stubs are injected unconditionally — a non-SvelteKit config never imports
691
+ * them, so the unused aliases are harmless, and it keeps this free of project
692
+ * detection. Real path aliases (`$lib` and any `kit.alias`) are deliberately
693
+ * NOT handled here: `svelte-kit sync` writes them into `.svelte-kit/tsconfig.json`,
694
+ * which the tsconfig `paths` matcher in `get-config.ts` resolves.
695
+ *
696
+ * Why the export surface is enumerated by hand: these are *named* exports, and
697
+ * ESM requires every imported name to exist at link time, so an opaque or
698
+ * wildcard stub is impossible. (Vite asset imports are default-export and so
699
+ * can be matched by rule in `vite-virtual-modules.ts`; a static analyzer can
700
+ * ignore these opaquely because it never runs the module — we do, so we have to
701
+ * provide runnable exports.) The shapes mirror SvelteKit's public, documented
702
+ * surface, which is the stable contract; the internal `__sveltekit/*` virtual
703
+ * modules the real files depend on are not, which is why we stub `$app/*`
704
+ * directly rather than resolving SvelteKit's on-disk files.
705
+ *
706
+ * The authoritative export surfaces this mirrors:
707
+ *
708
+ * @see https://github.com/sveltejs/kit/tree/main/packages/kit/src/runtime/app
709
+ * @see https://github.com/sveltejs/kit/blob/main/packages/kit/src/types/ambient.d.ts
932
710
  */
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;
711
+ function addSvelteKitVirtualModules(aliases) {
712
+ aliases["$env/dynamic/private"] = createStubModule(createDynamicEnvModule("private"));
713
+ aliases["$env/dynamic/public"] = createStubModule(createDynamicEnvModule("public"));
714
+ aliases["$env/static/private"] = createStubModule(createStaticEnvModule(filterPrivateEnv("PUBLIC_", "")));
715
+ aliases["$env/static/public"] = createStubModule(createStaticEnvModule(filterPublicEnv("PUBLIC_", "")));
716
+ for (const [id, body] of Object.entries(appModuleStubs)) aliases[id] = createStubModule(body);
937
717
  }
938
718
  /**
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
719
+ * `$app/env` is SvelteKit's alias for `$app/environment` with an identical
720
+ * export surface, so both specifiers share this body.
721
+ * @see https://github.com/sveltejs/kit/pull/15934
943
722
  */
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 {}
955
- }
956
- return [
957
- "lerna.json",
958
- "turbo.json",
959
- "nx.json",
960
- "rush.json"
961
- ].some((indicator) => files.includes(indicator));
723
+ const environmentStub = `
724
+ export const browser = false;
725
+ export const building = false;
726
+ export const dev = false;
727
+ export const version = "";
728
+ `;
729
+ /**
730
+ * Specifier → module source for the inert `$app/*` and `$service-worker`
731
+ * stubs. The bodies do nothing (the CLI never serves a request); they exist
732
+ * only so every name a config might import resolves. Keep each entry aligned
733
+ * with SvelteKit's documented exports.
734
+ * @see https://svelte.dev/docs/kit/$app-environment
735
+ * @see https://svelte.dev/docs/kit/$app-server
736
+ * @see https://svelte.dev/docs/kit/$service-worker
737
+ */
738
+ const appModuleStubs = {
739
+ "$app/environment": environmentStub,
740
+ "$app/env": environmentStub,
741
+ "$app/server": `
742
+ export function getRequestEvent() {}
743
+ export function read() {}
744
+ export function query() {}
745
+ export function prerender() {}
746
+ export function command() {}
747
+ export function form() {}
748
+ export const requested = false;
749
+ `,
750
+ "$app/paths": `
751
+ export const base = "";
752
+ export const assets = "";
753
+ export function resolve(path) { return path; }
754
+ export function resolveRoute(path) { return path; }
755
+ export function asset(value) { return value; }
756
+ export async function match() { return null; }
757
+ `,
758
+ "$app/navigation": `
759
+ export function goto() {}
760
+ export function invalidate() {}
761
+ export function invalidateAll() {}
762
+ export function preloadData() {}
763
+ export function preloadCode() {}
764
+ export function beforeNavigate() {}
765
+ export function afterNavigate() {}
766
+ export function onNavigate() {}
767
+ export function disableScrollHandling() {}
768
+ export function pushState() {}
769
+ export function replaceState() {}
770
+ export function refreshAll() {}
771
+ `,
772
+ "$app/state": `
773
+ export const page = {};
774
+ export const navigating = {};
775
+ export const updated = { check() { return Promise.resolve(false); } };
776
+ `,
777
+ "$app/stores": `
778
+ export const page = { subscribe() { return () => {}; } };
779
+ export const navigating = { subscribe() { return () => {}; } };
780
+ export const updated = { subscribe() { return () => {}; }, check() { return Promise.resolve(false); } };
781
+ export function getStores() { return { page, navigating, updated }; }
782
+ `,
783
+ "$app/forms": `
784
+ export function applyAction() {}
785
+ export function deserialize() {}
786
+ export function enhance() {}
787
+ `,
788
+ "$service-worker": `
789
+ export const base = "";
790
+ export const build = [];
791
+ export const files = [];
792
+ export const prerendered = [];
793
+ export const version = "";
794
+ `
795
+ };
796
+ /**
797
+ * Wraps a module body as a `data:` URI for use as a jiti alias target. The
798
+ * trailing marker is load-bearing: without an "extension", jiti resolves the
799
+ * alias value as a file path and fails with ENOENT, so the comment gives it an
800
+ * unknown extension and forces a native import instead. (Stubs injected by the
801
+ * Babel plugin in `vite-virtual-modules.ts` set the specifier directly and do
802
+ * not go through alias resolution, so they need no marker.)
803
+ */
804
+ function createStubModule(body) {
805
+ const source = `${body}\n// jiti dirty hack: .unknown\n`;
806
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(source)}`;
807
+ }
808
+ function createStaticEnvModule(env) {
809
+ return Object.keys(env).filter((k) => validIdentifier.test(k) && !reserved.has(k)).map((k) => `export const ${k} = ${JSON.stringify(env[k])};`).join("\n");
810
+ }
811
+ function createDynamicEnvModule(visibility) {
812
+ return `
813
+ const keep = (key) => typeof key === "string" && ${visibility === "public" ? `key.startsWith("PUBLIC_")` : `!key.startsWith("PUBLIC_")`};
814
+ export const env = new Proxy(
815
+ {},
816
+ {
817
+ get: (_, key) => (keep(key) ? process.env[key] : undefined),
818
+ has: (_, key) => keep(key) && key in process.env,
819
+ ownKeys: () => Object.keys(process.env).filter(keep),
820
+ getOwnPropertyDescriptor: (_, key) =>
821
+ keep(key) && key in process.env
822
+ ? { value: process.env[key], enumerable: true, configurable: true }
823
+ : undefined,
824
+ },
825
+ );`;
826
+ }
827
+ function filterPrivateEnv(publicPrefix, privatePrefix) {
828
+ return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(privatePrefix) && (publicPrefix === "" || !k.startsWith(publicPrefix))));
829
+ }
830
+ function filterPublicEnv(publicPrefix, privatePrefix) {
831
+ return Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith(publicPrefix) && (privatePrefix === "" || !k.startsWith(privatePrefix))));
962
832
  }
833
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
834
+ const reserved = new Set([
835
+ "do",
836
+ "if",
837
+ "in",
838
+ "for",
839
+ "let",
840
+ "new",
841
+ "try",
842
+ "var",
843
+ "case",
844
+ "else",
845
+ "enum",
846
+ "eval",
847
+ "null",
848
+ "this",
849
+ "true",
850
+ "void",
851
+ "with",
852
+ "await",
853
+ "break",
854
+ "catch",
855
+ "class",
856
+ "const",
857
+ "false",
858
+ "super",
859
+ "throw",
860
+ "while",
861
+ "yield",
862
+ "delete",
863
+ "export",
864
+ "import",
865
+ "public",
866
+ "return",
867
+ "static",
868
+ "switch",
869
+ "typeof",
870
+ "default",
871
+ "extends",
872
+ "finally",
873
+ "package",
874
+ "private",
875
+ "continue",
876
+ "debugger",
877
+ "function",
878
+ "arguments",
879
+ "interface",
880
+ "protected",
881
+ "implements",
882
+ "instanceof"
883
+ ]);
884
+ //#endregion
885
+ //#region src/utils/vite-virtual-modules.ts
963
886
  /**
964
- * Finds the monorepo root by walking up the directory tree.
887
+ * Stub modules for Vite's "special" imports (asset and query-suffixed
888
+ * modules). Vite resolves these through its plugin pipeline at build time; the
889
+ * CLI loads auth configs with jiti, where no such pipeline exists and there is
890
+ * no file on disk to read. Without a substitute, a config that transitively
891
+ * imports e.g. `./logo.svg`, `./app.css?inline`, or `./worker.ts?worker`
892
+ * crashes the loader.
965
893
  *
966
- * @param startDir Starting directory
967
- * @returns Path to monorepo root, or null if not found
894
+ * Detection mirrors Vite's own classification. The query-suffix patterns are
895
+ * tested against the full specifier, the extension membership against the
896
+ * specifier with its query stripped, and query patterns take precedence over
897
+ * extension membership (so `./a.css?raw` is raw text, not a stylesheet). The
898
+ * regexes are not part of Vite's public API, so they are copied here.
899
+ *
900
+ * @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts
901
+ * @see https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/asset.ts
968
902
  */
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;
977
- }
978
- return null;
903
+ const WORKER_RE = /[?&](?:worker|sharedworker)(?:&|$)/;
904
+ const URL_RE = /[?&]url(?:&|$)/;
905
+ const RAW_RE = /[?&]raw(?:&|$)/;
906
+ const INLINE_RE = /[?&]inline(?:&|$)/;
907
+ const WASM_INIT_RE = /\.wasm\?init\b/;
908
+ const CSS_MODULE_RE = /\.module\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
909
+ const CSS_LANGS_RE = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
910
+ const KNOWN_ASSET_RE = /\.(?:apng|bmp|png|jpe?g|jfif|pjpeg|pjp|gif|svg|ico|webp|avif|cur|jxl|mp4|webm|ogg|mp3|wav|flac|aac|opus|mov|m4a|vtt|woff2?|eot|ttf|otf|webmanifest|pdf|txt)(?:\?.*)?$/i;
911
+ function createDataUriModule(body) {
912
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(body)}`;
979
913
  }
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\"";
914
+ /**
915
+ * Returns a data-URI stub module for a Vite special import, or `undefined`
916
+ * when the specifier is an ordinary module that should resolve normally. The
917
+ * stub's runtime shape matches what Vite would emit (a string for raw/url, a
918
+ * worker constructor, a class-name proxy for CSS Modules, and so on).
919
+ */
920
+ function getViteAssetStub(specifier) {
921
+ if (WORKER_RE.test(specifier)) return createDataUriModule(URL_RE.test(specifier) ? `export default "";` : `export default function () {};`);
922
+ if (WASM_INIT_RE.test(specifier)) return createDataUriModule(`export default async () => ({ exports: {} });`);
923
+ if (RAW_RE.test(specifier) || INLINE_RE.test(specifier) || URL_RE.test(specifier)) return createDataUriModule(`export default "";`);
924
+ if (CSS_MODULE_RE.test(specifier)) return createDataUriModule(`export default new Proxy({}, { get: (_, key) => String(key) });`);
925
+ if (CSS_LANGS_RE.test(specifier)) return createDataUriModule(`export default undefined;`);
926
+ if (KNOWN_ASSET_RE.test(specifier)) return createDataUriModule(`export default "";`);
927
+ }
928
+ //#endregion
929
+ //#region src/utils/get-config.ts
930
+ let possiblePaths$1 = [
931
+ "auth.ts",
932
+ "auth.tsx",
933
+ "auth.js",
934
+ "auth.jsx",
935
+ "auth.server.js",
936
+ "auth.server.ts",
937
+ "auth/index.ts",
938
+ "auth/index.tsx",
939
+ "auth/index.js",
940
+ "auth/index.jsx",
941
+ "auth/index.server.js",
942
+ "auth/index.server.ts"
943
+ ];
944
+ possiblePaths$1 = [
945
+ ...possiblePaths$1,
946
+ ...possiblePaths$1.map((it) => `lib/server/${it}`),
947
+ ...possiblePaths$1.map((it) => `server/auth/${it}`),
948
+ ...possiblePaths$1.map((it) => `server/${it}`),
949
+ ...possiblePaths$1.map((it) => `auth/${it}`),
950
+ ...possiblePaths$1.map((it) => `lib/${it}`),
951
+ ...possiblePaths$1.map((it) => `utils/${it}`)
952
+ ];
953
+ possiblePaths$1 = [
954
+ ...possiblePaths$1,
955
+ ...possiblePaths$1.map((it) => `src/${it}`),
956
+ ...possiblePaths$1.map((it) => `app/${it}`)
957
+ ];
958
+ /** Reads `references` from raw tsconfig JSON (stripped out by `parseTsconfig`). */
959
+ function readRawTsconfigReferences(tsconfigPath) {
960
+ try {
961
+ const stripped = fs.readFileSync(tsconfigPath, "utf-8").replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(?=\s*[}\]])/g, "");
962
+ return JSON.parse(stripped).references;
963
+ } catch {
964
+ return;
965
+ }
966
+ }
967
+ /** Recursively collects tsconfigs reachable via `references`. */
968
+ function collectReferencedTsconfigs(tsconfigPath, visited = /* @__PURE__ */ new Set()) {
969
+ const result = [];
970
+ const refs = readRawTsconfigReferences(tsconfigPath);
971
+ if (!refs) return result;
972
+ const configDir = path.dirname(tsconfigPath);
973
+ for (const ref of refs) {
974
+ const resolvedRef = path.resolve(configDir, ref.path);
975
+ const refTsconfigPath = resolvedRef.endsWith(".json") ? resolvedRef : path.join(resolvedRef, "tsconfig.json");
976
+ if (visited.has(refTsconfigPath)) continue;
977
+ visited.add(refTsconfigPath);
978
+ try {
979
+ const refConfig = parseTsconfig(refTsconfigPath);
980
+ result.push({
981
+ path: refTsconfigPath,
982
+ config: refConfig
983
+ });
984
+ } catch {
985
+ continue;
1004
986
  }
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);
987
+ result.push(...collectReferencedTsconfigs(refTsconfigPath, visited));
988
+ }
989
+ return result;
990
+ }
991
+ /**
992
+ * Ordered `paths` matchers from the project tsconfig and any referenced
993
+ * tsconfigs, following TypeScript canonical resolution semantics.
994
+ * @see https://github.com/microsoft/TypeScript/blob/main/src/compiler/moduleNameResolver.ts
995
+ */
996
+ function collectPathsMatchers(cwd) {
997
+ const tsconfig = getTsconfig(cwd, fs.existsSync(path.join(cwd, "tsconfig.json")) ? "tsconfig.json" : "jsconfig.json");
998
+ if (!tsconfig) return [];
999
+ const matchers = [];
1000
+ try {
1001
+ const mainMatcher = createPathsMatcher(tsconfig);
1002
+ if (mainMatcher) matchers.push(mainMatcher);
1003
+ for (const refTsconfig of collectReferencedTsconfigs(tsconfig.path)) {
1004
+ const refMatcher = createPathsMatcher(refTsconfig);
1005
+ if (refMatcher) matchers.push(refMatcher);
1009
1006
  }
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);
1007
+ } catch (error) {
1008
+ console.error(error);
1009
+ throw new BetterAuthError("Error parsing tsconfig.json");
1010
+ }
1011
+ return matchers;
1012
+ }
1013
+ /**
1014
+ * Source file extensions jiti can load. Shared between the jiti `extensions`
1015
+ * option and `resolveCandidateFile` so both stay in sync.
1016
+ */
1017
+ const SOURCE_EXTENSIONS = [
1018
+ ".ts",
1019
+ ".tsx",
1020
+ ".mts",
1021
+ ".cts",
1022
+ ".js",
1023
+ ".jsx",
1024
+ ".mjs",
1025
+ ".cjs"
1026
+ ];
1027
+ const SOURCE_EXTENSIONS_SET = new Set(SOURCE_EXTENSIONS);
1028
+ /** Probes a candidate as-is, with known extensions, and as a directory index. */
1029
+ function resolveCandidateFile(candidate) {
1030
+ try {
1031
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
1032
+ } catch {}
1033
+ if (SOURCE_EXTENSIONS_SET.has(path.extname(candidate))) return;
1034
+ for (const ext of SOURCE_EXTENSIONS) {
1035
+ const withExt = candidate + ext;
1036
+ if (fs.existsSync(withExt)) return withExt;
1037
+ }
1038
+ for (const ext of SOURCE_EXTENSIONS) {
1039
+ const asIndex = path.join(candidate, `index${ext}`);
1040
+ if (fs.existsSync(asIndex)) return asIndex;
1041
+ }
1042
+ }
1043
+ function resolveWithMatchers(specifier, matchers) {
1044
+ for (const matcher of matchers) for (const candidate of matcher(specifier)) {
1045
+ const resolved = resolveCandidateFile(candidate);
1046
+ if (resolved) return resolved;
1047
+ }
1048
+ }
1049
+ /**
1050
+ * Callees whose first string argument is a module specifier. `jitiImport` is
1051
+ * a jiti-side preprocessor artifact observed in the AST; revisit on jiti
1052
+ * major version bumps (the regression suite catches a rename but not the why).
1053
+ */
1054
+ const LOADER_IDENTIFIERS = new Set([
1055
+ "require",
1056
+ "import",
1057
+ "jitiImport"
1058
+ ]);
1059
+ /**
1060
+ * Rewrites aliased specifiers at AST level. Required because jiti's `alias`
1061
+ * option only supports prefix matching and cannot express mid-path wildcards.
1062
+ *
1063
+ * Matchers always take precedence over native resolution, mirroring
1064
+ * TypeScript's own `paths` → `node_modules` order.
1065
+ */
1066
+ function createRewriteImportPathsPlugin(matchers) {
1067
+ return ({ types: t }) => {
1068
+ const rewrite = (source) => {
1069
+ if (!source) return;
1070
+ const stub = getViteAssetStub(source.value);
1071
+ if (stub) {
1072
+ source.value = stub;
1073
+ return;
1074
+ }
1075
+ const resolved = resolveWithMatchers(source.value, matchers);
1076
+ if (resolved) source.value = resolved;
1077
+ };
1078
+ return { visitor: {
1079
+ ImportDeclaration(p) {
1080
+ rewrite(p.node.source);
1081
+ },
1082
+ ExportNamedDeclaration(p) {
1083
+ rewrite(p.node.source);
1084
+ },
1085
+ ExportAllDeclaration(p) {
1086
+ rewrite(p.node.source);
1087
+ },
1088
+ ImportExpression(p) {
1089
+ if (t.isStringLiteral(p.node.source)) rewrite(p.node.source);
1090
+ },
1091
+ CallExpression(p) {
1092
+ const { callee, arguments: args } = p.node;
1093
+ const first = args[0];
1094
+ if (!t.isStringLiteral(first)) return;
1095
+ if (!(t.isIdentifier(callee) && LOADER_IDENTIFIERS.has(callee.name) || t.isImport(callee))) return;
1096
+ rewrite(first);
1022
1097
  }
1098
+ } };
1099
+ };
1100
+ }
1101
+ /** Virtual module aliases; real tsconfig paths go through the babel plugin. */
1102
+ function getVirtualModuleAliases() {
1103
+ const result = {};
1104
+ addSvelteKitVirtualModules(result);
1105
+ addCloudflareVirtualModules(result);
1106
+ return result;
1107
+ }
1108
+ /**
1109
+ * .tsx files are not supported by Jiti.
1110
+ */
1111
+ const jitiOptions = (cwd) => {
1112
+ const plugins = [createRewriteImportPathsPlugin(collectPathsMatchers(cwd))];
1113
+ return {
1114
+ transformOptions: { babel: {
1115
+ presets: [[babelPresetTypeScript, {
1116
+ isTSX: true,
1117
+ allExtensions: true
1118
+ }], [babelPresetReact, { runtime: "automatic" }]],
1119
+ plugins
1120
+ } },
1121
+ extensions: [...SOURCE_EXTENSIONS],
1122
+ alias: getVirtualModuleAliases()
1123
+ };
1124
+ };
1125
+ /**
1126
+ * Picks the auth instance from the loaded module, supporting `export const auth`,
1127
+ * `export default auth`, and `export default { auth }`. Falls back to the raw
1128
+ * module as a defensive default.
1129
+ */
1130
+ const resolveAuthModule = (mod) => {
1131
+ const m = mod;
1132
+ return m?.auth ?? m?.default?.auth ?? m?.default ?? mod;
1133
+ };
1134
+ 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");
1135
+ 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.`;
1136
+ async function getConfig({ cwd, configPath, shouldThrowOnError = false }) {
1137
+ const fail = (message, error) => {
1138
+ if (shouldThrowOnError) throw error instanceof Error ? error : new Error(message);
1139
+ const log = `[#better-auth]: ${message}`;
1140
+ if (error) console.error(log, error);
1141
+ else console.error(log);
1142
+ process.exit(1);
1143
+ };
1144
+ const load = (configFile) => loadConfig({
1145
+ configFile,
1146
+ dotenv: { fileName: [".env", ".env.local"] },
1147
+ jitiOptions: jitiOptions(cwd),
1148
+ resolveModule: resolveAuthModule,
1149
+ cwd
1150
+ });
1151
+ try {
1152
+ if (configPath) {
1153
+ const resolvedPath = existsSync(configPath) ? configPath : path.join(cwd, configPath);
1154
+ const { config } = await load(resolvedPath);
1155
+ const options = config?.options;
1156
+ 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.`);
1157
+ return options;
1023
1158
  }
1024
- }
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);
1159
+ for (const possiblePath of possiblePaths$1) {
1160
+ let config;
1161
+ try {
1162
+ ({config} = await load(possiblePath));
1163
+ } catch (e) {
1164
+ if (isServerOnlyError(e)) return fail(SERVER_ONLY_HINT);
1165
+ return fail("Couldn't read your auth config.", e);
1035
1166
  }
1167
+ if (Object.keys(config).length === 0) continue;
1168
+ 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.");
1169
+ return config.options;
1036
1170
  }
1171
+ return null;
1172
+ } catch (e) {
1173
+ if (isServerOnlyError(e)) return fail(SERVER_ONLY_HINT);
1174
+ return fail("Couldn't read your auth config.", e);
1037
1175
  }
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
- }
1062
- }
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 {
1066
- const useNumberId = options.advanced?.database?.generateId === "serial";
1067
- 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");
1071
- }
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;
1080
- }
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 ? "?" : "[]"}`);
1165
- }
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)`;
1178
- }
1179
- builder.model(modelName).blockAttribute(`index([${indexField}])`);
1180
- }
1181
- const hasAttribute = builder.findByType("attribute", {
1182
- name: "map",
1183
- within: prismaModel?.properties
1184
- });
1185
- const hasChanged = customModelName !== originalTableName;
1186
- if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
1187
- }
1188
- });
1189
- const schemaChanged = schema.trim() !== schemaPrisma.trim();
1190
- return {
1191
- code: schemaChanged ? schema : "",
1192
- fileName: filePath,
1193
- overwrite: schemaPrismaExist && schemaChanged
1194
- };
1195
- };
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
1176
  }
1308
1177
  //#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);
1178
+ //#region src/commands/create-admin.ts
1179
+ function exitWithError(message) {
1180
+ console.error(chalk.red(`Error: ${message}`));
1181
+ process.exit(1);
1323
1182
  }
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;
1350
- }
1351
- break;
1183
+ function parseData(data) {
1184
+ if (!data) return void 0;
1185
+ try {
1186
+ const parsed = JSON.parse(data);
1187
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) exitWithError("--data must be a JSON object.");
1188
+ return parsed;
1189
+ } catch (error) {
1190
+ if (error instanceof SyntaxError) exitWithError("--data must be valid JSON.");
1191
+ throw error;
1352
1192
  }
1353
- aliases["$app/server"] = createDataUriModule(createAppServerModule());
1354
- const customAliases = getSvelteConfigAliases(cwd);
1355
- Object.assign(aliases, customAliases);
1356
- return aliases;
1357
1193
  }
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;
1194
+ async function resolveRequiredInput(options) {
1195
+ let { email, password } = options;
1196
+ if (!email) email = (await prompts({
1197
+ type: "text",
1198
+ name: "email",
1199
+ message: "Admin email"
1200
+ })).email;
1201
+ if (!password) password = (await prompts({
1202
+ type: "password",
1203
+ name: "password",
1204
+ message: "Admin password"
1205
+ })).password;
1206
+ if (!email) exitWithError("Admin email is required.");
1207
+ if (!password) exitWithError("Admin password is required.");
1208
+ return {
1209
+ email,
1210
+ password
1211
+ };
1378
1212
  }
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) {
1213
+ /** @internal */
1214
+ async function createAdminAction(opts) {
1215
+ const options = z.object({
1216
+ cwd: z.string(),
1217
+ config: z.string().optional(),
1218
+ email: z.string().optional(),
1219
+ password: z.string().optional(),
1220
+ name: z.string().default("Admin"),
1221
+ role: z.string().default("admin"),
1222
+ data: z.string().optional(),
1223
+ emailVerified: z.boolean().default(true),
1224
+ force: z.boolean().optional(),
1225
+ y: z.boolean().optional(),
1226
+ yes: z.boolean().optional()
1227
+ }).parse(opts);
1228
+ const cwd = path.resolve(options.cwd);
1229
+ if (!existsSync(cwd)) exitWithError(`The directory "${cwd}" does not exist.`);
1230
+ if (options.y) {
1231
+ console.warn("WARNING: --y is deprecated. Consider -y or --yes");
1232
+ options.yes = true;
1233
+ }
1234
+ const config = await getConfig({
1235
+ cwd,
1236
+ configPath: options.config
1237
+ });
1238
+ 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.");
1239
+ if (!config.database) exitWithError("No database is configured. Add a persistent database before creating an admin user.");
1240
+ const auth = betterAuth(config);
1241
+ const createUser = auth.api.createUser;
1242
+ if (typeof createUser !== "function") exitWithError("The admin plugin is required. Add `admin()` to your Better Auth plugins before running this command.");
1243
+ const { email, password } = await resolveRequiredInput(options);
1244
+ if (!z.email().safeParse(email).success) exitWithError("Invalid email address.");
1245
+ const data = {
1246
+ ...parseData(options.data),
1247
+ emailVerified: options.emailVerified
1248
+ };
1490
1249
  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;
1250
+ const totalUsers = await (await auth.$context).internalAdapter.countTotalUsers();
1251
+ if (totalUsers > 0 && !options.force && !options.yes) {
1252
+ if (!(await prompts({
1253
+ type: "confirm",
1254
+ name: "confirmed",
1255
+ message: `Found ${totalUsers} existing user${totalUsers === 1 ? "" : "s"}. Create an admin user anyway?`,
1256
+ initial: false
1257
+ })).confirmed) {
1258
+ console.log("Create admin cancelled.");
1259
+ process.exit(0);
1260
+ return;
1261
+ }
1262
+ }
1263
+ } catch (error) {
1264
+ 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}` : ""}`);
1265
+ }
1266
+ try {
1267
+ const result = await createUser({ body: {
1268
+ email,
1269
+ password,
1270
+ name: options.name,
1271
+ role: options.role,
1272
+ data
1273
+ } });
1274
+ console.log(chalk.green("Admin user created successfully."));
1275
+ if (result.user?.id) console.log(`User ID: ${result.user.id}`);
1276
+ console.log(`Email: ${result.user?.email ?? email.toLowerCase()}`);
1277
+ console.log(`Role: ${result.user?.role ?? options.role}`);
1278
+ process.exit(0);
1279
+ } catch (error) {
1280
+ if (error instanceof APIError) exitWithError(error.message);
1281
+ if (error instanceof Error) exitWithError(error.message);
1282
+ exitWithError("Failed to create admin user.");
1495
1283
  }
1496
1284
  }
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
1285
+ 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);
1286
+ //#endregion
1287
+ //#region src/generators/drizzle.ts
1288
+ function convertToSnakeCase(str, camelCase) {
1289
+ return camelCase ? str : toSnakeCase(str);
1290
+ }
1291
+ const generateDrizzleSchema = async ({ options, file, adapter }) => {
1292
+ const tables = getAuthTables(options);
1293
+ const filePath = file || "./auth-schema.ts";
1294
+ const databaseType = adapter.options?.provider;
1295
+ 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`);
1296
+ const fileExist = existsSync(filePath);
1297
+ let code = generateImport({
1298
+ databaseType,
1299
+ tables,
1300
+ options
1301
+ });
1302
+ const getModelName = initGetModelName({
1303
+ schema: tables,
1304
+ usePlural: adapter.options?.adapterConfig?.usePlural
1305
+ });
1306
+ const getSingularModelName = initGetModelName({
1307
+ schema: tables,
1308
+ usePlural: false
1309
+ });
1310
+ const getFieldName = initGetFieldName({
1311
+ schema: tables,
1312
+ usePlural: adapter.options?.adapterConfig?.usePlural
1313
+ });
1314
+ const isMigrationDisabled = (model) => Object.entries(tables).some(([tableKey, table]) => {
1315
+ if (table.disableMigrations !== true) return false;
1316
+ const customModelName = table.modelName || tableKey;
1317
+ return model === tableKey || model === customModelName || model === getModelName(tableKey) || model === getModelName(customModelName);
1318
+ });
1319
+ for (const tableKey in tables) {
1320
+ const table = tables[tableKey];
1321
+ if (isMigrationDisabled(tableKey)) continue;
1322
+ const modelName = getModelName(tableKey);
1323
+ const fields = table.fields;
1324
+ function getType(name, field) {
1325
+ 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`);
1326
+ name = convertToSnakeCase(name, adapter.options?.camelCase);
1327
+ if (field.references?.field === "id") {
1328
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1329
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1330
+ if (useNumberId) if (databaseType === "pg") return `integer('${name}')`;
1331
+ else if (databaseType === "mysql") return `int('${name}')`;
1332
+ else return `integer('${name}')`;
1333
+ if (useUUIDs && databaseType === "pg") return `uuid('${name}')`;
1334
+ if (field.references.field) {
1335
+ if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
1336
+ }
1337
+ return `text('${name}')`;
1338
+ }
1339
+ const type = field.type;
1340
+ if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
1341
+ sqlite: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
1342
+ pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
1343
+ mysql: `mysqlEnum('${name}', [${type.map((x) => `'${x}'`).join(", ")}])`
1344
+ }[databaseType];
1345
+ else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
1346
+ const dbTypeMap = {
1347
+ string: {
1348
+ sqlite: `text('${name}')`,
1349
+ pg: `text('${name}')`,
1350
+ 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}')`
1351
+ },
1352
+ boolean: {
1353
+ sqlite: `integer('${name}', { mode: 'boolean' })`,
1354
+ pg: `boolean('${name}')`,
1355
+ mysql: `boolean('${name}')`
1356
+ },
1357
+ number: {
1358
+ sqlite: `integer('${name}')`,
1359
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
1360
+ mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
1361
+ },
1362
+ date: {
1363
+ sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
1364
+ pg: `timestamp('${name}')`,
1365
+ mysql: `timestamp('${name}', { fsp: 3 })`
1366
+ },
1367
+ "number[]": {
1368
+ sqlite: `text('${name}', { mode: "json" })`,
1369
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
1370
+ mysql: `text('${name}', { mode: 'json' })`
1371
+ },
1372
+ "string[]": {
1373
+ sqlite: `text('${name}', { mode: "json" })`,
1374
+ pg: `text('${name}').array()`,
1375
+ mysql: `text('${name}', { mode: "json" })`
1376
+ },
1377
+ json: {
1378
+ sqlite: `text('${name}', { mode: "json" })`,
1379
+ pg: `jsonb('${name}')`,
1380
+ mysql: `json('${name}', { mode: "json" })`
1381
+ }
1382
+ }[type];
1383
+ if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
1384
+ return dbTypeMap[databaseType];
1385
+ }
1386
+ let id = "";
1387
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1388
+ if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
1389
+ else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
1390
+ else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
1391
+ else id = `int("id").autoincrement().primaryKey()`;
1392
+ else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
1393
+ else if (databaseType === "pg") id = `text('id').primaryKey()`;
1394
+ else id = `text('id').primaryKey()`;
1395
+ const indexes = [];
1396
+ const assignIndexes = (indexes) => {
1397
+ if (!indexes.length) return "";
1398
+ const code = [`, (table) => [`];
1399
+ for (const index of indexes) code.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
1400
+ code.push(`]`);
1401
+ return code.join("\n");
1402
+ };
1403
+ const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
1404
+ id: ${id},
1405
+ ${Object.keys(fields).map((field) => {
1406
+ const attr = fields[field];
1407
+ const fieldName = attr.fieldName || field;
1408
+ let type = getType(fieldName, attr);
1409
+ if (attr.index && !attr.unique) indexes.push({
1410
+ type: "index",
1411
+ name: `${modelName}_${fieldName}_idx`,
1412
+ on: fieldName
1413
+ });
1414
+ else if (attr.index && attr.unique) indexes.push({
1415
+ type: "uniqueIndex",
1416
+ name: `${modelName}_${fieldName}_uidx`,
1417
+ on: fieldName
1418
+ });
1419
+ if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
1420
+ if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
1421
+ else type += `.defaultNow()`;
1422
+ } else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
1423
+ else if (Array.isArray(attr.defaultValue)) {
1424
+ const elements = attr.defaultValue.map((value) => JSON.stringify(value)).join(", ");
1425
+ type += `.default([${elements}])`;
1426
+ } else if (typeof attr.defaultValue === "object" && attr.defaultValue !== null) type += `.default(${JSON.stringify(attr.defaultValue)})`;
1427
+ else type += `.default(${attr.defaultValue})`;
1428
+ if (attr.onUpdate && attr.type === "date") {
1429
+ if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
1430
+ }
1431
+ const referencesDisabledModel = attr.references && isMigrationDisabled(attr.references.model);
1432
+ return `${fieldName}: ${type}${attr.required !== false ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references && !referencesDisabledModel ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
1433
+ model: attr.references.model,
1434
+ field: attr.references.field
1435
+ })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
1436
+ }).join(",\n ")}
1437
+ }${assignIndexes(indexes)});`;
1438
+ code += `\n${schema}\n`;
1439
+ }
1440
+ let relationsString = "";
1441
+ for (const tableKey in tables) {
1442
+ const table = tables[tableKey];
1443
+ if (isMigrationDisabled(tableKey)) continue;
1444
+ const modelName = getModelName(tableKey);
1445
+ const oneRelations = [];
1446
+ const manyRelations = [];
1447
+ const manyRelationsSet = /* @__PURE__ */ new Set();
1448
+ const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
1449
+ for (const [fieldName, field] of foreignFields) {
1450
+ const referencedModel = field.references.model;
1451
+ if (isMigrationDisabled(referencedModel)) continue;
1452
+ const relationKey = getSingularModelName(referencedModel);
1453
+ const fieldRef = `${getModelName(tableKey)}.${getFieldName({
1454
+ model: tableKey,
1455
+ field: fieldName
1456
+ })}`;
1457
+ const referenceRef = `${getModelName(referencedModel)}.${getFieldName({
1458
+ model: referencedModel,
1459
+ field: field.references.field || "id"
1460
+ })}`;
1461
+ oneRelations.push({
1462
+ key: relationKey,
1463
+ model: getModelName(referencedModel),
1464
+ type: "one",
1465
+ reference: {
1466
+ field: fieldRef,
1467
+ references: referenceRef,
1468
+ fieldName
1469
+ }
1513
1470
  });
1514
- } catch {
1515
- continue;
1516
1471
  }
1517
- result.push(...collectReferencedTsconfigs(refTsconfigPath, visited));
1472
+ const otherModels = Object.entries(tables).filter(([modelName, otherTable]) => modelName !== tableKey && !otherTable.disableMigrations);
1473
+ const modelRelationsMap = /* @__PURE__ */ new Map();
1474
+ for (const [modelName, otherTable] of otherModels) {
1475
+ const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
1476
+ if (foreignKeysPointingHere.length === 0) continue;
1477
+ const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
1478
+ const hasMany = foreignKeysPointingHere.some(([_, field]) => !field.unique);
1479
+ modelRelationsMap.set(modelName, {
1480
+ modelName,
1481
+ hasUnique,
1482
+ hasMany
1483
+ });
1484
+ }
1485
+ for (const { modelName, hasMany } of modelRelationsMap.values()) {
1486
+ const relationType = hasMany ? "many" : "one";
1487
+ let relationKey = getModelName(modelName);
1488
+ if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
1489
+ if (!manyRelationsSet.has(relationKey)) {
1490
+ manyRelationsSet.add(relationKey);
1491
+ manyRelations.push({
1492
+ key: relationKey,
1493
+ model: getModelName(modelName),
1494
+ type: relationType
1495
+ });
1496
+ }
1497
+ }
1498
+ const relationsByModel = /* @__PURE__ */ new Map();
1499
+ for (const relation of oneRelations) if (relation.reference) {
1500
+ const modelKey = relation.key;
1501
+ if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
1502
+ relationsByModel.get(modelKey).push(relation);
1503
+ }
1504
+ const duplicateRelations = [];
1505
+ const singleRelations = [];
1506
+ for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
1507
+ else singleRelations.push(relations[0]);
1508
+ for (const relation of duplicateRelations) if (relation.reference) {
1509
+ const fieldName = relation.reference.fieldName;
1510
+ const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
1511
+ ${relation.key}: one(${relation.model}, {
1512
+ fields: [${relation.reference.field}],
1513
+ references: [${relation.reference.references}],
1514
+ })
1515
+ }))`;
1516
+ relationsString += `\n${tableRelation}\n`;
1517
+ }
1518
+ const hasOne = singleRelations.length > 0;
1519
+ const hasMany = manyRelations.length > 0;
1520
+ if (hasOne && hasMany) {
1521
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
1522
+ ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
1523
+ fields: [${relation.reference.field}],
1524
+ references: [${relation.reference.references}],
1525
+ })` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
1526
+ ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
1527
+ }))`;
1528
+ relationsString += `\n${tableRelation}\n`;
1529
+ } else if (hasOne) {
1530
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
1531
+ ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
1532
+ fields: [${relation.reference.field}],
1533
+ references: [${relation.reference.references}],
1534
+ })` : "").filter((x) => x !== "").join(",\n ")}
1535
+ }))`;
1536
+ relationsString += `\n${tableRelation}\n`;
1537
+ } else if (hasMany) {
1538
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
1539
+ ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
1540
+ }))`;
1541
+ relationsString += `\n${tableRelation}\n`;
1542
+ }
1518
1543
  }
1519
- return result;
1544
+ code += `\n${relationsString}`;
1545
+ return {
1546
+ code: await prettier.format(code, { parser: "typescript" }),
1547
+ fileName: filePath,
1548
+ overwrite: fileExist
1549
+ };
1550
+ };
1551
+ function generateImport({ databaseType, tables, options }) {
1552
+ const rootImports = ["relations"];
1553
+ const coreImports = [];
1554
+ let hasBigint = false;
1555
+ let hasJson = false;
1556
+ for (const table of Object.values(tables)) {
1557
+ for (const field of Object.values(table.fields)) {
1558
+ if (field.bigint) hasBigint = true;
1559
+ if (field.type === "json") hasJson = true;
1560
+ }
1561
+ if (hasJson && hasBigint) break;
1562
+ }
1563
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1564
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1565
+ coreImports.push(`${databaseType}Table`);
1566
+ coreImports.push(databaseType === "mysql" ? "varchar, text" : databaseType === "pg" ? "text" : "text");
1567
+ coreImports.push(hasBigint ? databaseType !== "sqlite" ? "bigint" : "" : "");
1568
+ coreImports.push(databaseType !== "sqlite" ? "timestamp, boolean" : "");
1569
+ if (databaseType === "mysql") {
1570
+ const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
1571
+ if (useNumberId || hasNonBigintNumber) coreImports.push("int");
1572
+ 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");
1573
+ } else if (databaseType === "pg") {
1574
+ if (useUUIDs) rootImports.push("sql");
1575
+ const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
1576
+ const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
1577
+ if (hasNonBigintNumber || options.advanced?.database?.generateId === "serial" && hasFkToId) coreImports.push("integer");
1578
+ } else coreImports.push("integer");
1579
+ if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
1580
+ if (hasJson) {
1581
+ if (databaseType === "pg") coreImports.push("jsonb");
1582
+ if (databaseType === "mysql") coreImports.push("json");
1583
+ }
1584
+ 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");
1585
+ const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
1586
+ const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
1587
+ if (hasIndexes) coreImports.push("index");
1588
+ if (hasUniqueIndexes) coreImports.push("uniqueIndex");
1589
+ 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
1590
  }
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 = [];
1591
+ //#endregion
1592
+ //#region src/generators/kysely.ts
1593
+ const generateKyselySchema = async ({ options, file }) => {
1594
+ const { compileMigrations } = await getMigrations(options);
1595
+ const migrations = await compileMigrations();
1596
+ return {
1597
+ code: migrations.trim() === ";" ? "" : migrations,
1598
+ fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
1599
+ };
1600
+ };
1601
+ //#endregion
1602
+ //#region src/utils/helper.ts
1603
+ async function tryCatch(promise) {
1530
1604
  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
- }
1605
+ return {
1606
+ data: await promise,
1607
+ error: null
1608
+ };
1537
1609
  } catch (error) {
1538
- console.error(error);
1539
- throw new BetterAuthError("Error parsing tsconfig.json");
1610
+ return {
1611
+ data: null,
1612
+ error
1613
+ };
1540
1614
  }
1541
- return matchers;
1542
1615
  }
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
- }
1616
+ const generateSecretHash = () => {
1617
+ return Crypto.randomBytes(32).toString("hex");
1618
+ };
1619
+ const spawnCommand = (cmd, cwd = process.cwd()) => new Promise((resolve, reject) => {
1620
+ const child = spawn(cmd, {
1621
+ cwd,
1622
+ stdio: "inherit",
1623
+ shell: true
1624
+ });
1625
+ child.on("close", (code, signal) => {
1626
+ if (code !== 0 && code !== null) reject(/* @__PURE__ */ new Error(`Exited with code ${code}`));
1627
+ else if (signal) reject(/* @__PURE__ */ new Error(`Killed with signal ${signal}`));
1628
+ else resolve();
1629
+ });
1630
+ child.on("error", reject);
1631
+ });
1632
+ //#endregion
1633
+ //#region src/utils/get-package-info.ts
1634
+ function getPackageInfo(cwd) {
1635
+ const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
1636
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1572
1637
  }
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;
1638
+ function getPrismaVersion(cwd) {
1639
+ try {
1640
+ const packageInfo = getPackageInfo(cwd);
1641
+ const prismaVersion = packageInfo.dependencies?.prisma || packageInfo.devDependencies?.prisma || packageInfo.dependencies?.["@prisma/client"] || packageInfo.devDependencies?.["@prisma/client"];
1642
+ if (!prismaVersion) return null;
1643
+ const match = prismaVersion.match(/(\d+)/);
1644
+ return match ? parseInt(match[1], 10) : null;
1645
+ } catch {
1646
+ return null;
1577
1647
  }
1578
1648
  }
1579
1649
  /**
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).
1650
+ * Checks if a package has a specific dependency.
1651
+ *
1652
+ * @param packageJson The package.json object
1653
+ * @param dependency The dependency to check for
1654
+ * @returns true if the package has the dependency
1583
1655
  */
1584
- const LOADER_IDENTIFIERS = new Set([
1585
- "require",
1586
- "import",
1587
- "jitiImport"
1588
- ]);
1656
+ function hasDependency(packageJson, dependency) {
1657
+ let hasDependency = false;
1658
+ if (packageJson.dependencies?.[dependency] || packageJson.devDependencies?.[dependency] || packageJson.peerDependencies?.[dependency] || packageJson.optionalDependencies?.[dependency]) hasDependency = true;
1659
+ return hasDependency;
1660
+ }
1589
1661
  /**
1590
- * Rewrites aliased specifiers at AST level. Required because jiti's `alias`
1591
- * option only supports prefix matching and cannot express mid-path wildcards.
1662
+ * Checks if a directory is a monorepo root by looking for common monorepo indicators.
1592
1663
  *
1593
- * Matchers always take precedence over native resolution, mirroring
1594
- * TypeScript's own `paths` → `node_modules` order.
1664
+ * @param dir Directory to check
1665
+ * @returns true if the directory appears to be a monorepo root
1595
1666
  */
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;
1667
+ async function isMonorepoRoot(dir) {
1668
+ const { data: files } = await tryCatch(fs$1.readdir(dir, "utf-8"));
1669
+ if (!files) return false;
1670
+ if (files.includes("pnpm-workspace.yaml")) return true;
1671
+ if (files.includes("package.json")) {
1672
+ const packageJsonPath = path.join(dir, "package.json");
1673
+ const { data } = await tryCatch(fs$1.readFile(packageJsonPath, "utf-8"));
1674
+ if (data) try {
1675
+ const packageJson = JSON.parse(data);
1676
+ if (packageJson.workspaces && (Array.isArray(packageJson.workspaces) || typeof packageJson.workspaces === "object")) return true;
1677
+ } catch {}
1678
+ }
1679
+ return [
1680
+ "lerna.json",
1681
+ "turbo.json",
1682
+ "nx.json",
1683
+ "rush.json"
1684
+ ].some((indicator) => files.includes(indicator));
1632
1685
  }
1633
1686
  /**
1634
- * .tsx files are not supported by Jiti.
1687
+ * Finds the monorepo root by walking up the directory tree.
1688
+ *
1689
+ * @param startDir Starting directory
1690
+ * @returns Path to monorepo root, or null if not found
1635
1691
  */
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);
1692
+ async function findMonorepoRoot(startDir) {
1693
+ let currentDir = path.resolve(startDir);
1694
+ const root = path.parse(currentDir).root;
1695
+ while (currentDir !== root) {
1696
+ if (await isMonorepoRoot(currentDir)) return currentDir;
1697
+ const parentDir = path.dirname(currentDir);
1698
+ if (parentDir === currentDir) break;
1699
+ currentDir = parentDir;
1700
+ }
1701
+ return null;
1702
+ }
1703
+ //#endregion
1704
+ //#region src/generators/prisma.ts
1705
+ const generatePrismaSchema = async ({ adapter, options, file }) => {
1706
+ const provider = adapter.options?.provider || "postgresql";
1707
+ const tables = getAuthTables(options);
1708
+ const filePath = file || "./prisma/schema.prisma";
1709
+ const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
1710
+ const getModelName = initGetModelName({
1711
+ schema: getAuthTables(options),
1712
+ usePlural: adapter.options?.adapterConfig?.usePlural
1713
+ });
1714
+ const getFieldName = initGetFieldName({
1715
+ schema: getAuthTables(options),
1716
+ usePlural: false
1717
+ });
1718
+ const isMigrationDisabled = (model) => Object.entries(tables).some(([tableKey, table]) => {
1719
+ if (table.disableMigrations !== true) return false;
1720
+ const customModelName = table.modelName || tableKey;
1721
+ return model === tableKey || model === customModelName || model === getModelName(tableKey) || model === getModelName(customModelName);
1722
+ });
1723
+ let schemaPrisma = "";
1724
+ if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(path.join(process.cwd(), filePath), "utf-8");
1725
+ else schemaPrisma = getNewPrisma(provider, process.cwd());
1726
+ const prismaVersion = getPrismaVersion(process.cwd());
1727
+ if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
1728
+ const generator = builder.findByType("generator", { name: "client" });
1729
+ if (generator && generator.properties) {
1730
+ const providerProp = generator.properties.find((prop) => prop.type === "assignment" && prop.key === "provider");
1731
+ if (providerProp && providerProp.value === "\"prisma-client-js\"") providerProp.value = "\"prisma-client\"";
1732
+ }
1733
+ const datasource = builder.findByType("datasource", { name: "db" });
1734
+ if (datasource && datasource.properties) {
1735
+ const urlIndex = datasource.properties.findIndex((prop) => prop.type === "assignment" && prop.key === "url");
1736
+ if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
1737
+ }
1738
+ });
1739
+ const manyToManyRelations = /* @__PURE__ */ new Map();
1740
+ for (const table in tables) {
1741
+ if (isMigrationDisabled(table)) continue;
1742
+ const fields = tables[table]?.fields;
1743
+ for (const field in fields) {
1744
+ const attr = fields[field];
1745
+ if (attr.references) {
1746
+ const referencedOriginalModel = attr.references.model;
1747
+ if (isMigrationDisabled(referencedOriginalModel)) continue;
1748
+ const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
1749
+ if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
1750
+ const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
1751
+ manyToManyRelations.get(referencedModelNameCap).add(currentModelNameCap);
1670
1752
  }
1671
- configFile = "auth" in config ? config.auth?.options : config.options;
1672
1753
  }
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);
1754
+ }
1755
+ const indexedFields = /* @__PURE__ */ new Map();
1756
+ for (const table in tables) {
1757
+ const fields = tables[table]?.fields;
1758
+ const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
1759
+ indexedFields.set(modelName, []);
1760
+ for (const field in fields) {
1761
+ const attr = fields[field];
1762
+ if (attr.index && !attr.unique) {
1763
+ const fieldName = attr.fieldName || field;
1764
+ indexedFields.get(modelName).push(fieldName);
1765
+ }
1766
+ }
1767
+ }
1768
+ const schema = produceSchema(schemaPrisma, (builder) => {
1769
+ for (const table in tables) {
1770
+ if (isMigrationDisabled(table)) continue;
1771
+ const originalTableName = table;
1772
+ const customModelName = tables[table]?.modelName || table;
1773
+ const modelName = capitalizeFirstLetter(getModelName(customModelName));
1774
+ const fields = tables[table]?.fields;
1775
+ function getType({ isBigint, isOptional, type }) {
1776
+ if (type === "string") return isOptional ? "String?" : "String";
1777
+ if (type === "number" && isBigint) return isOptional ? "BigInt?" : "BigInt";
1778
+ if (type === "number") return isOptional ? "Int?" : "Int";
1779
+ if (type === "boolean") return isOptional ? "Boolean?" : "Boolean";
1780
+ if (type === "date") return isOptional ? "DateTime?" : "DateTime";
1781
+ if (type === "json") {
1782
+ if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
1783
+ return isOptional ? "Json?" : "Json";
1784
+ }
1785
+ if (type === "string[]") {
1786
+ if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
1787
+ return "String[]";
1788
+ }
1789
+ if (type === "number[]") {
1790
+ if (provider === "sqlite" || provider === "mysql") return "String";
1791
+ return "Int[]";
1688
1792
  }
1689
- break;
1690
1793
  }
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);
1794
+ function getFieldTypeParts(type) {
1795
+ const isArray = type.endsWith("[]");
1796
+ const typeWithoutArray = isArray ? type.slice(0, -2) : type;
1797
+ const isOptional = typeWithoutArray.endsWith("?");
1798
+ return {
1799
+ fieldType: isOptional ? typeWithoutArray.slice(0, -1) : typeWithoutArray,
1800
+ isArray,
1801
+ isOptional
1802
+ };
1696
1803
  }
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);
1804
+ const prismaModel = builder.findByType("model", { name: modelName });
1805
+ if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
1806
+ else {
1807
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1808
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1809
+ if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
1810
+ else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
1811
+ else builder.model(modelName).field("id", "String").attribute("id");
1812
+ }
1813
+ for (const field in fields) {
1814
+ const attr = fields[field];
1815
+ const fieldName = attr.fieldName || field;
1816
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1817
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1818
+ const fieldType = field === "id" && useNumberId ? getType({
1819
+ isBigint: false,
1820
+ isOptional: false,
1821
+ type: "number"
1822
+ }) : getType({
1823
+ isBigint: attr?.bigint || false,
1824
+ isOptional: attr?.required === false,
1825
+ type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
1826
+ });
1827
+ if (prismaModel) {
1828
+ const isAlreadyExist = builder.findByType("field", {
1829
+ name: fieldName,
1830
+ within: prismaModel.properties
1831
+ });
1832
+ if (isAlreadyExist) {
1833
+ if (fieldType && typeof isAlreadyExist.fieldType === "string") {
1834
+ const fieldTypeParts = getFieldTypeParts(fieldType);
1835
+ const existingFieldTypeParts = getFieldTypeParts(isAlreadyExist.fieldType);
1836
+ if ((existingFieldTypeParts.fieldType === "Int" || existingFieldTypeParts.fieldType === "BigInt") && (fieldTypeParts.fieldType === "Int" || fieldTypeParts.fieldType === "BigInt")) {
1837
+ isAlreadyExist.fieldType = fieldTypeParts.fieldType;
1838
+ isAlreadyExist.optional = fieldTypeParts.isOptional || void 0;
1839
+ isAlreadyExist.array = fieldTypeParts.isArray || void 0;
1840
+ }
1841
+ }
1842
+ continue;
1843
+ }
1844
+ }
1845
+ if (!fieldType) throw new Error(`Unsupported Prisma field type for model "${modelName}", field "${fieldName}"${attr.type ? ` (source type: "${attr.type}")` : ""}.`);
1846
+ const fieldBuilder = builder.model(modelName).field(fieldName, fieldType);
1847
+ if (field === "id") {
1848
+ fieldBuilder.attribute("id");
1849
+ if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
1850
+ }
1851
+ if (attr.unique) builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
1852
+ if (attr.defaultValue !== void 0) {
1853
+ if (Array.isArray(attr.defaultValue)) {
1854
+ if (attr.type === "json") {
1855
+ if (Object.prototype.toString.call(attr.defaultValue[0]) === "[object Object]") {
1856
+ fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
1857
+ continue;
1858
+ }
1859
+ const jsonArray = [];
1860
+ for (const value of attr.defaultValue) jsonArray.push(value);
1861
+ fieldBuilder.attribute(`default("${JSON.stringify(jsonArray).replace(/"/g, "\\\"")}")`);
1862
+ continue;
1863
+ }
1864
+ if (attr.defaultValue.length === 0) {
1865
+ fieldBuilder.attribute(`default([])`);
1866
+ continue;
1867
+ } else if (typeof attr.defaultValue[0] === "string" && attr.type === "string[]") {
1868
+ const valueArray = [];
1869
+ for (const value of attr.defaultValue) valueArray.push(JSON.stringify(value));
1870
+ fieldBuilder.attribute(`default([${valueArray}])`);
1871
+ } else if (typeof attr.defaultValue[0] === "number") {
1872
+ const valueArray = [];
1873
+ for (const value of attr.defaultValue) valueArray.push(`${value}`);
1874
+ fieldBuilder.attribute(`default([${valueArray}])`);
1875
+ }
1876
+ } else if (typeof attr.defaultValue === "object" && !Array.isArray(attr.defaultValue) && attr.defaultValue !== null) {
1877
+ if (Object.entries(attr.defaultValue).length === 0) {
1878
+ fieldBuilder.attribute(`default("{}")`);
1879
+ continue;
1880
+ }
1881
+ fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
1882
+ }
1883
+ if (field === "createdAt") fieldBuilder.attribute("default(now())");
1884
+ else if (typeof attr.defaultValue === "string" && provider !== "mysql") fieldBuilder.attribute(`default("${attr.defaultValue}")`);
1885
+ else if (typeof attr.defaultValue === "boolean" || typeof attr.defaultValue === "number") fieldBuilder.attribute(`default(${attr.defaultValue})`);
1886
+ else if (typeof attr.defaultValue === "function") {}
1887
+ }
1888
+ if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
1889
+ else if (attr.onUpdate) {}
1890
+ if (attr.references) {
1891
+ const referencedOriginalModelName = getModelName(attr.references.model);
1892
+ if (isMigrationDisabled(referencedOriginalModelName) || isMigrationDisabled(attr.references.model)) continue;
1893
+ if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
1894
+ const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
1895
+ let action = "Cascade";
1896
+ if (attr.references.onDelete === "no action") action = "NoAction";
1897
+ else if (attr.references.onDelete === "set null") action = "SetNull";
1898
+ else if (attr.references.onDelete === "set default") action = "SetDefault";
1899
+ else if (attr.references.onDelete === "restrict") action = "Restrict";
1900
+ const relationField = `relation(fields: [${getFieldName({
1901
+ model: originalTableName,
1902
+ field: fieldName
1903
+ })}], references: [${getFieldName({
1904
+ model: attr.references.model,
1905
+ field: attr.references.field
1906
+ })}], onDelete: ${action})`;
1907
+ builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${attr.required === false ? "?" : ""}`).attribute(relationField);
1908
+ }
1909
+ if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
1910
+ }
1911
+ if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
1912
+ const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
1913
+ const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
1914
+ const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
1915
+ const isUnique = fkFieldAttr?.unique === true;
1916
+ const fieldName = isUnique || adapter.options?.usePlural === true ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
1917
+ if (!builder.findByType("field", {
1918
+ name: fieldName,
1919
+ within: prismaModel?.properties
1920
+ })) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
1921
+ }
1922
+ const indexedFieldsForModel = indexedFields.get(modelName);
1923
+ if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
1924
+ if (prismaModel) {
1925
+ if (prismaModel.properties.some((v) => v.type === "attribute" && v.name === "index" && JSON.stringify(v.args[0]?.value).includes(fieldName))) continue;
1926
+ }
1927
+ const field = Object.entries(fields).find(([key, attr]) => (attr.fieldName || key) === fieldName)?.[1];
1928
+ let indexField = fieldName;
1929
+ if (provider === "mysql" && field && field.type === "string") {
1930
+ const useNumberId = options.advanced?.database?.generateId === "serial";
1931
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
1932
+ if (field.references?.field === "id" && (useNumberId || useUUIDs)) indexField = `${fieldName}`;
1933
+ else indexField = `${fieldName}(length: 191)`;
1934
+ }
1935
+ builder.model(modelName).blockAttribute(`index([${indexField}])`);
1936
+ }
1937
+ const hasAttribute = builder.findByType("attribute", {
1938
+ name: "map",
1939
+ within: prismaModel?.properties
1940
+ });
1941
+ const hasChanged = customModelName !== originalTableName;
1942
+ if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
1707
1943
  }
1708
- if (shouldThrowOnError) throw e;
1709
- console.error("Couldn't read your auth config.", e);
1710
- process.exit(1);
1711
- }
1712
- }
1944
+ });
1945
+ const schemaChanged = schema.trim() !== schemaPrisma.trim();
1946
+ return {
1947
+ code: schemaChanged ? schema : "",
1948
+ fileName: filePath,
1949
+ overwrite: schemaPrismaExist && schemaChanged
1950
+ };
1951
+ };
1952
+ const getNewPrisma = (provider, cwd) => {
1953
+ const prismaVersion = getPrismaVersion(cwd);
1954
+ const isV7 = prismaVersion && prismaVersion >= 7;
1955
+ const clientProvider = isV7 ? "prisma-client" : "prisma-client-js";
1956
+ if (isV7) return `generator client {
1957
+ provider = "${clientProvider}"
1958
+ }
1959
+
1960
+ datasource db {
1961
+ provider = "${provider}"
1962
+ }`;
1963
+ return `generator client {
1964
+ provider = "${clientProvider}"
1965
+ }
1966
+
1967
+ datasource db {
1968
+ provider = "${provider}"
1969
+ url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
1970
+ }`;
1971
+ };
1972
+ //#endregion
1973
+ //#region src/generators/index.ts
1974
+ const adapters = {
1975
+ prisma: generatePrismaSchema,
1976
+ drizzle: generateDrizzleSchema,
1977
+ kysely: generateKyselySchema
1978
+ };
1979
+ const generateSchema = async (opts) => {
1980
+ const adapter = opts.adapter;
1981
+ if (adapter.createSchema) return adapter.createSchema(opts.options, opts.file).then(({ code, path: fileName, overwrite }) => ({
1982
+ code,
1983
+ fileName,
1984
+ overwrite
1985
+ }));
1986
+ const generator = adapters[adapter.id] ?? null;
1987
+ if (generator) return await generator(opts);
1988
+ throw new Error(`${adapter.id} is not supported. If it is a custom adapter, please request the maintainer to implement the "createSchema" method on the adapter.`);
1989
+ };
1713
1990
  //#endregion
1714
1991
  //#region src/commands/generate.ts
1715
1992
  function createMockAdapter$1(adapterId, dialect) {
@@ -1746,6 +2023,12 @@ function createMockAdapter$1(adapterId, dialect) {
1746
2023
  deleteMany: async () => {
1747
2024
  throw new Error("Mock adapter methods should not be called");
1748
2025
  },
2026
+ consumeOne: async () => {
2027
+ throw new Error("Mock adapter methods should not be called");
2028
+ },
2029
+ incrementOne: async () => {
2030
+ throw new Error("Mock adapter methods should not be called");
2031
+ },
1749
2032
  transaction: async (callback) => {
1750
2033
  throw new Error("Mock adapter methods should not be called");
1751
2034
  },
@@ -1755,6 +2038,19 @@ function createMockAdapter$1(adapterId, dialect) {
1755
2038
  }
1756
2039
  };
1757
2040
  }
2041
+ function getDefaultSchemaOutputFileName(adapterId, now = /* @__PURE__ */ new Date()) {
2042
+ if (adapterId === "prisma") return "schema.prisma";
2043
+ if (adapterId === "kysely") return `${now.toISOString().replace(/:/g, "-")}.sql`;
2044
+ return "auth-schema.ts";
2045
+ }
2046
+ async function resolveSchemaOutputPath({ cwd, output, adapterId, now = /* @__PURE__ */ new Date() }) {
2047
+ if (!output) return output;
2048
+ const resolvedOutput = path.resolve(cwd, output);
2049
+ try {
2050
+ if ((await fs$1.stat(resolvedOutput)).isDirectory()) return path.join(output, getDefaultSchemaOutputFileName(adapterId, now));
2051
+ } catch {}
2052
+ return output;
2053
+ }
1758
2054
  async function generateAction(opts) {
1759
2055
  const options = z.object({
1760
2056
  cwd: z.string(),
@@ -1784,6 +2080,11 @@ async function generateAction(opts) {
1784
2080
  console.error(e.message);
1785
2081
  process.exit(1);
1786
2082
  });
2083
+ options.output = await resolveSchemaOutputPath({
2084
+ cwd,
2085
+ output: options.output,
2086
+ adapterId: adapter.id
2087
+ });
1787
2088
  const spinner = yoctoSpinner({ text: "preparing schema..." }).start();
1788
2089
  const schema = await generateSchema({
1789
2090
  adapter,
@@ -2007,6 +2308,8 @@ function sanitizeBetterAuthConfig(config) {
2007
2308
  const sanitized = JSON.parse(JSON.stringify(config));
2008
2309
  const sensitiveKeys = [
2009
2310
  "secret",
2311
+ "secrets",
2312
+ "secretKey",
2010
2313
  "clientSecret",
2011
2314
  "clientId",
2012
2315
  "authToken",
@@ -2060,6 +2363,7 @@ function sanitizeBetterAuthConfig(config) {
2060
2363
  const lowerSensitiveKey = sensitiveKey.toLowerCase();
2061
2364
  return lowerKey === lowerSensitiveKey || lowerKey.endsWith(lowerSensitiveKey);
2062
2365
  })) if (typeof value === "string" && value.length > 0) result[key] = "[REDACTED]";
2366
+ else if (Array.isArray(value)) result[key] = "[REDACTED]";
2063
2367
  else if (typeof value === "object" && value !== null) result[key] = redactSensitive(value, key);
2064
2368
  else result[key] = value;
2065
2369
  else result[key] = redactSensitive(value, key);
@@ -3528,14 +3832,7 @@ const tempPluginsConfig = {
3528
3832
  isNamedImport: false
3529
3833
  }]
3530
3834
  },
3531
- authClient: {
3532
- function: "genericOAuthClient",
3533
- imports: [{
3534
- path: "better-auth/client/plugins",
3535
- imports: [createImport({ name: "genericOAuthClient" })],
3536
- isNamedImport: false
3537
- }]
3538
- }
3835
+ authClient: null
3539
3836
  },
3540
3837
  anonymous: {
3541
3838
  displayName: "Anonymous",
@@ -3594,25 +3891,6 @@ const tempPluginsConfig = {
3594
3891
  }]
3595
3892
  }
3596
3893
  },
3597
- oidc: {
3598
- displayName: "OIDC",
3599
- auth: {
3600
- function: "oidc",
3601
- imports: [{
3602
- path: "better-auth/plugins",
3603
- imports: [createImport({ name: "oidc" })],
3604
- isNamedImport: false
3605
- }]
3606
- },
3607
- authClient: {
3608
- function: "oidcClient",
3609
- imports: [{
3610
- path: "better-auth/client/plugins",
3611
- imports: [createImport({ name: "oidcClient" })],
3612
- isNamedImport: false
3613
- }]
3614
- }
3615
- },
3616
3894
  admin: {
3617
3895
  displayName: "Admin",
3618
3896
  auth: {
@@ -5284,12 +5562,9 @@ const databasesConfig = [
5284
5562
  imports: [createImport({ name: "createPool" })],
5285
5563
  isNamedImport: false
5286
5564
  }],
5287
- preCode: `const dialect = createPool({ host: "localhost", user: "root", password: "password", database: "database", timezone: "Z" })`,
5565
+ preCode: `const database = createPool({ host: "localhost", user: "root", password: "password", database: "database", timezone: "Z" })`,
5288
5566
  code({ additionalOptions }) {
5289
- return kyselyCode({
5290
- provider: "mysql",
5291
- additionalOptions
5292
- });
5567
+ return `database`;
5293
5568
  },
5294
5569
  dependencies: ["mysql2"]
5295
5570
  },
@@ -5300,12 +5575,9 @@ const databasesConfig = [
5300
5575
  imports: [createImport({ name: "Pool" })],
5301
5576
  isNamedImport: false
5302
5577
  }],
5303
- preCode: `const dialect = new Pool({ connectionString: "postgresql://postgres:password@localhost:5432/database" })`,
5578
+ preCode: `const database = new Pool({ connectionString: "postgresql://postgres:password@localhost:5432/database" })`,
5304
5579
  code({ additionalOptions }) {
5305
- return kyselyCode({
5306
- provider: "postgresql",
5307
- additionalOptions
5308
- });
5580
+ return `database`;
5309
5581
  },
5310
5582
  dependencies: ["pg"],
5311
5583
  devDependencies: ["@types/pg"]
@@ -6671,7 +6943,7 @@ const initActionOptionsSchema = z.object({
6671
6943
  //#region src/commands/login.ts
6672
6944
  async function loginAction() {
6673
6945
  try {
6674
- await spawnCommand("npx @better-auth/cli@latest login");
6946
+ await spawnCommand("npx auth@latest login");
6675
6947
  } catch (error) {
6676
6948
  log.error(error.message || "An unknown error occurred");
6677
6949
  process.exit(1);
@@ -6681,7 +6953,7 @@ async function loginAction() {
6681
6953
  const login = new Command("login").description("Login to Better Auth Infrastructure").action(loginAction);
6682
6954
  async function logoutAction() {
6683
6955
  try {
6684
- await spawnCommand("npx @better-auth/cli@latest logout");
6956
+ await spawnCommand("npx auth@latest logout");
6685
6957
  } catch (error) {
6686
6958
  log.error(error.message || "An unknown error occurred");
6687
6959
  process.exit(1);
@@ -7083,7 +7355,7 @@ async function main() {
7083
7355
  packageInfo = await getPackageInfo();
7084
7356
  cliVersion = packageInfo.version || "1.1.2";
7085
7357
  } catch {}
7086
- 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());
7358
+ 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());
7087
7359
  program.parse();
7088
7360
  }
7089
7361
  main().catch((error) => {