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/README.md +12 -1
- package/dist/api.d.mts +1 -1
- package/dist/api.mjs +70 -21
- package/dist/index.mjs +1395 -1123
- package/package.json +20 -17
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
|
|
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/
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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
|
-
|
|
894
|
-
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
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
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
*
|
|
940
|
-
*
|
|
941
|
-
* @
|
|
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
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
967
|
-
*
|
|
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
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
return
|
|
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
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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/
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
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
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
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
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
if (refMatcher) matchers.push(refMatcher);
|
|
1536
|
-
}
|
|
1605
|
+
return {
|
|
1606
|
+
data: await promise,
|
|
1607
|
+
error: null
|
|
1608
|
+
};
|
|
1537
1609
|
} catch (error) {
|
|
1538
|
-
|
|
1539
|
-
|
|
1610
|
+
return {
|
|
1611
|
+
data: null,
|
|
1612
|
+
error
|
|
1613
|
+
};
|
|
1540
1614
|
}
|
|
1541
|
-
return matchers;
|
|
1542
1615
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
const
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
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
|
|
1574
|
-
|
|
1575
|
-
const
|
|
1576
|
-
|
|
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
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
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
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1594
|
-
*
|
|
1664
|
+
* @param dir Directory to check
|
|
1665
|
+
* @returns true if the directory appears to be a monorepo root
|
|
1595
1666
|
*/
|
|
1596
|
-
function
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
};
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1637
|
-
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
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
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
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
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
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
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
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
|
|
5565
|
+
preCode: `const database = createPool({ host: "localhost", user: "root", password: "password", database: "database", timezone: "Z" })`,
|
|
5288
5566
|
code({ additionalOptions }) {
|
|
5289
|
-
return
|
|
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
|
|
5578
|
+
preCode: `const database = new Pool({ connectionString: "postgresql://postgres:password@localhost:5432/database" })`,
|
|
5304
5579
|
code({ additionalOptions }) {
|
|
5305
|
-
return
|
|
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
|
|
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
|
|
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) => {
|