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