auth 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as getPackageInfo, c as spawnCommand, d as generateDrizzleSchema, i as findMonorepoRoot, l as tryCatch, n as generateSchema, o as hasDependency, r as generatePrismaSchema, s as generateSecretHash$1 } from "./generators-YiCQyoNc.mjs";
3
2
  import { Command } from "commander";
4
3
  import * as fs$2 from "node:fs";
5
4
  import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
@@ -12,9 +11,13 @@ import chalk from "chalk";
12
11
  import prompts from "prompts";
13
12
  import yoctoSpinner from "yocto-spinner";
14
13
  import * as z$1 from "zod/v4";
15
- import { format } from "prettier";
14
+ import { initGetFieldName, initGetModelName } from "better-auth/adapters";
15
+ import { getAuthTables } from "better-auth/db";
16
+ import prettier, { format } from "prettier";
16
17
  import { getMigrations } from "better-auth/db/migration";
17
- import { exec, execSync } from "node:child_process";
18
+ import { capitalizeFirstLetter } from "@better-auth/core/utils/string";
19
+ import { produceSchema } from "@mrleebo/prisma-ast";
20
+ import { exec, execSync, spawn } from "node:child_process";
18
21
  import Crypto from "node:crypto";
19
22
  import babelPresetReact from "@babel/preset-react";
20
23
  import babelPresetTypeScript from "@babel/preset-typescript";
@@ -30,6 +33,667 @@ import { base64 } from "@better-auth/utils/base64";
30
33
  import * as semver from "semver";
31
34
  import "dotenv/config";
32
35
 
36
+ //#region src/generators/drizzle.ts
37
+ function convertToSnakeCase(str, camelCase) {
38
+ if (camelCase) return str;
39
+ return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
40
+ }
41
+ const generateDrizzleSchema = async ({ options, file, adapter }) => {
42
+ const tables = getAuthTables(options);
43
+ const filePath = file || "./auth-schema.ts";
44
+ const databaseType = adapter.options?.provider;
45
+ 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`);
46
+ const fileExist = existsSync(filePath);
47
+ let code = generateImport({
48
+ databaseType,
49
+ tables,
50
+ options
51
+ });
52
+ const getModelName = initGetModelName({
53
+ schema: tables,
54
+ usePlural: adapter.options?.adapterConfig?.usePlural
55
+ });
56
+ const getFieldName = initGetFieldName({
57
+ schema: tables,
58
+ usePlural: adapter.options?.adapterConfig?.usePlural
59
+ });
60
+ for (const tableKey in tables) {
61
+ const table = tables[tableKey];
62
+ const modelName = getModelName(tableKey);
63
+ const fields = table.fields;
64
+ function getType(name, field) {
65
+ 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`);
66
+ name = convertToSnakeCase(name, adapter.options?.camelCase);
67
+ if (field.references?.field === "id") {
68
+ const useNumberId = options.advanced?.database?.generateId === "serial";
69
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
70
+ if (useNumberId) if (databaseType === "pg") return `integer('${name}')`;
71
+ else if (databaseType === "mysql") return `int('${name}')`;
72
+ else return `integer('${name}')`;
73
+ if (useUUIDs && databaseType === "pg") return `uuid('${name}')`;
74
+ if (field.references.field) {
75
+ if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
76
+ }
77
+ return `text('${name}')`;
78
+ }
79
+ const type = field.type;
80
+ if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
81
+ sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
82
+ pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
83
+ mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
84
+ }[databaseType];
85
+ else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
86
+ const dbTypeMap = {
87
+ string: {
88
+ sqlite: `text('${name}')`,
89
+ pg: `text('${name}')`,
90
+ 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}')`
91
+ },
92
+ boolean: {
93
+ sqlite: `integer('${name}', { mode: 'boolean' })`,
94
+ pg: `boolean('${name}')`,
95
+ mysql: `boolean('${name}')`
96
+ },
97
+ number: {
98
+ sqlite: `integer('${name}')`,
99
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
100
+ mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
101
+ },
102
+ date: {
103
+ sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
104
+ pg: `timestamp('${name}')`,
105
+ mysql: `timestamp('${name}', { fsp: 3 })`
106
+ },
107
+ "number[]": {
108
+ sqlite: `text('${name}', { mode: "json" })`,
109
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
110
+ mysql: `text('${name}', { mode: 'json' })`
111
+ },
112
+ "string[]": {
113
+ sqlite: `text('${name}', { mode: "json" })`,
114
+ pg: `text('${name}').array()`,
115
+ mysql: `text('${name}', { mode: "json" })`
116
+ },
117
+ json: {
118
+ sqlite: `text('${name}', { mode: "json" })`,
119
+ pg: `jsonb('${name}')`,
120
+ mysql: `json('${name}', { mode: "json" })`
121
+ }
122
+ }[type];
123
+ if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
124
+ return dbTypeMap[databaseType];
125
+ }
126
+ let id = "";
127
+ const useNumberId = options.advanced?.database?.generateId === "serial";
128
+ if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
129
+ else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
130
+ else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
131
+ else id = `int("id").autoincrement().primaryKey()`;
132
+ else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
133
+ else if (databaseType === "pg") id = `text('id').primaryKey()`;
134
+ else id = `text('id').primaryKey()`;
135
+ const indexes = [];
136
+ const assignIndexes = (indexes) => {
137
+ if (!indexes.length) return "";
138
+ const code = [`, (table) => [`];
139
+ for (const index of indexes) code.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
140
+ code.push(`]`);
141
+ return code.join("\n");
142
+ };
143
+ const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
144
+ id: ${id},
145
+ ${Object.keys(fields).map((field) => {
146
+ const attr = fields[field];
147
+ const fieldName = attr.fieldName || field;
148
+ let type = getType(fieldName, attr);
149
+ if (attr.index && !attr.unique) indexes.push({
150
+ type: "index",
151
+ name: `${modelName}_${fieldName}_idx`,
152
+ on: fieldName
153
+ });
154
+ else if (attr.index && attr.unique) indexes.push({
155
+ type: "uniqueIndex",
156
+ name: `${modelName}_${fieldName}_uidx`,
157
+ on: fieldName
158
+ });
159
+ if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
160
+ if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
161
+ else type += `.defaultNow()`;
162
+ } else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
163
+ else type += `.default(${attr.defaultValue})`;
164
+ if (attr.onUpdate && attr.type === "date") {
165
+ if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
166
+ }
167
+ return `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
168
+ model: attr.references.model,
169
+ field: attr.references.field
170
+ })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
171
+ }).join(",\n ")}
172
+ }${assignIndexes(indexes)});`;
173
+ code += `\n${schema}\n`;
174
+ }
175
+ let relationsString = "";
176
+ for (const tableKey in tables) {
177
+ const table = tables[tableKey];
178
+ const modelName = getModelName(tableKey);
179
+ const oneRelations = [];
180
+ const manyRelations = [];
181
+ const manyRelationsSet = /* @__PURE__ */ new Set();
182
+ const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
183
+ for (const [fieldName, field] of foreignFields) {
184
+ const referencedModel = field.references.model;
185
+ const relationKey = getModelName(referencedModel);
186
+ const fieldRef = `${getModelName(tableKey)}.${getFieldName({
187
+ model: tableKey,
188
+ field: fieldName
189
+ })}`;
190
+ const referenceRef = `${getModelName(referencedModel)}.${getFieldName({
191
+ model: referencedModel,
192
+ field: field.references.field || "id"
193
+ })}`;
194
+ oneRelations.push({
195
+ key: relationKey,
196
+ model: getModelName(referencedModel),
197
+ type: "one",
198
+ reference: {
199
+ field: fieldRef,
200
+ references: referenceRef,
201
+ fieldName
202
+ }
203
+ });
204
+ }
205
+ const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
206
+ const modelRelationsMap = /* @__PURE__ */ new Map();
207
+ for (const [modelName, otherTable] of otherModels) {
208
+ const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
209
+ if (foreignKeysPointingHere.length === 0) continue;
210
+ const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
211
+ const hasMany = foreignKeysPointingHere.some(([_, field]) => !field.unique);
212
+ modelRelationsMap.set(modelName, {
213
+ modelName,
214
+ hasUnique,
215
+ hasMany
216
+ });
217
+ }
218
+ for (const { modelName, hasMany } of modelRelationsMap.values()) {
219
+ const relationType = hasMany ? "many" : "one";
220
+ let relationKey = getModelName(modelName);
221
+ if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
222
+ if (!manyRelationsSet.has(relationKey)) {
223
+ manyRelationsSet.add(relationKey);
224
+ manyRelations.push({
225
+ key: relationKey,
226
+ model: getModelName(modelName),
227
+ type: relationType
228
+ });
229
+ }
230
+ }
231
+ const relationsByModel = /* @__PURE__ */ new Map();
232
+ for (const relation of oneRelations) if (relation.reference) {
233
+ const modelKey = relation.key;
234
+ if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
235
+ relationsByModel.get(modelKey).push(relation);
236
+ }
237
+ const duplicateRelations = [];
238
+ const singleRelations = [];
239
+ for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
240
+ else singleRelations.push(relations[0]);
241
+ for (const relation of duplicateRelations) if (relation.reference) {
242
+ const fieldName = relation.reference.fieldName;
243
+ const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
244
+ ${relation.key}: one(${relation.model}, {
245
+ fields: [${relation.reference.field}],
246
+ references: [${relation.reference.references}],
247
+ })
248
+ }))`;
249
+ relationsString += `\n${tableRelation}\n`;
250
+ }
251
+ const hasOne = singleRelations.length > 0;
252
+ const hasMany = manyRelations.length > 0;
253
+ if (hasOne && hasMany) {
254
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
255
+ ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
256
+ fields: [${relation.reference.field}],
257
+ references: [${relation.reference.references}],
258
+ })` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
259
+ ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
260
+ }))`;
261
+ relationsString += `\n${tableRelation}\n`;
262
+ } else if (hasOne) {
263
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
264
+ ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
265
+ fields: [${relation.reference.field}],
266
+ references: [${relation.reference.references}],
267
+ })` : "").filter((x) => x !== "").join(",\n ")}
268
+ }))`;
269
+ relationsString += `\n${tableRelation}\n`;
270
+ } else if (hasMany) {
271
+ const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
272
+ ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
273
+ }))`;
274
+ relationsString += `\n${tableRelation}\n`;
275
+ }
276
+ }
277
+ code += `\n${relationsString}`;
278
+ return {
279
+ code: await prettier.format(code, { parser: "typescript" }),
280
+ fileName: filePath,
281
+ overwrite: fileExist
282
+ };
283
+ };
284
+ function generateImport({ databaseType, tables, options }) {
285
+ const rootImports = ["relations"];
286
+ const coreImports = [];
287
+ let hasBigint = false;
288
+ let hasJson = false;
289
+ for (const table of Object.values(tables)) {
290
+ for (const field of Object.values(table.fields)) {
291
+ if (field.bigint) hasBigint = true;
292
+ if (field.type === "json") hasJson = true;
293
+ }
294
+ if (hasJson && hasBigint) break;
295
+ }
296
+ const useNumberId = options.advanced?.database?.generateId === "serial";
297
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
298
+ coreImports.push(`${databaseType}Table`);
299
+ coreImports.push(databaseType === "mysql" ? "varchar, text" : databaseType === "pg" ? "text" : "text");
300
+ coreImports.push(hasBigint ? databaseType !== "sqlite" ? "bigint" : "" : "");
301
+ coreImports.push(databaseType !== "sqlite" ? "timestamp, boolean" : "");
302
+ if (databaseType === "mysql") {
303
+ const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
304
+ if (useNumberId || hasNonBigintNumber) coreImports.push("int");
305
+ 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");
306
+ } else if (databaseType === "pg") {
307
+ if (useUUIDs) rootImports.push("sql");
308
+ const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
309
+ const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
310
+ if (hasNonBigintNumber || options.advanced?.database?.generateId === "serial" && hasFkToId) coreImports.push("integer");
311
+ } else coreImports.push("integer");
312
+ if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
313
+ if (hasJson) {
314
+ if (databaseType === "pg") coreImports.push("jsonb");
315
+ if (databaseType === "mysql") coreImports.push("json");
316
+ }
317
+ 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");
318
+ const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
319
+ const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
320
+ if (hasIndexes) coreImports.push("index");
321
+ if (hasUniqueIndexes) coreImports.push("uniqueIndex");
322
+ 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`;
323
+ }
324
+
325
+ //#endregion
326
+ //#region src/generators/kysely.ts
327
+ const generateKyselySchema = async ({ options, file }) => {
328
+ const { compileMigrations } = await getMigrations(options);
329
+ const migrations = await compileMigrations();
330
+ return {
331
+ code: migrations.trim() === ";" ? "" : migrations,
332
+ fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
333
+ };
334
+ };
335
+
336
+ //#endregion
337
+ //#region src/utils/helper.ts
338
+ async function tryCatch(promise) {
339
+ try {
340
+ return {
341
+ data: await promise,
342
+ error: null
343
+ };
344
+ } catch (error) {
345
+ return {
346
+ data: null,
347
+ error
348
+ };
349
+ }
350
+ }
351
+ const generateSecretHash$1 = () => {
352
+ return Crypto.randomBytes(16).toString("hex");
353
+ };
354
+ const spawnCommand = (cmd, cwd = process.cwd()) => new Promise((resolve, reject) => {
355
+ const child = spawn(cmd, {
356
+ cwd,
357
+ stdio: "inherit",
358
+ shell: true
359
+ });
360
+ child.on("close", (code, signal) => {
361
+ if (code !== 0 && code !== null) reject(/* @__PURE__ */ new Error(`Exited with code ${code}`));
362
+ else if (signal) reject(/* @__PURE__ */ new Error(`Killed with signal ${signal}`));
363
+ else resolve();
364
+ });
365
+ child.on("error", reject);
366
+ });
367
+
368
+ //#endregion
369
+ //#region src/utils/get-package-info.ts
370
+ function getPackageInfo(cwd) {
371
+ const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
372
+ return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
373
+ }
374
+ function getPrismaVersion(cwd) {
375
+ try {
376
+ const packageInfo = getPackageInfo(cwd);
377
+ const prismaVersion = packageInfo.dependencies?.prisma || packageInfo.devDependencies?.prisma || packageInfo.dependencies?.["@prisma/client"] || packageInfo.devDependencies?.["@prisma/client"];
378
+ if (!prismaVersion) return null;
379
+ const match = prismaVersion.match(/(\d+)/);
380
+ return match ? parseInt(match[1], 10) : null;
381
+ } catch {
382
+ return null;
383
+ }
384
+ }
385
+ /**
386
+ * Checks if a package has a specific dependency.
387
+ *
388
+ * @param packageJson The package.json object
389
+ * @param dependency The dependency to check for
390
+ * @returns true if the package has the dependency
391
+ */
392
+ function hasDependency(packageJson, dependency) {
393
+ let hasDependency = false;
394
+ if (packageJson.dependencies?.[dependency] || packageJson.devDependencies?.[dependency] || packageJson.peerDependencies?.[dependency] || packageJson.optionalDependencies?.[dependency]) hasDependency = true;
395
+ return hasDependency;
396
+ }
397
+ /**
398
+ * Checks if a directory is a monorepo root by looking for common monorepo indicators.
399
+ *
400
+ * @param dir Directory to check
401
+ * @returns true if the directory appears to be a monorepo root
402
+ */
403
+ async function isMonorepoRoot(dir) {
404
+ const { data: files } = await tryCatch(fs$1.readdir(dir, "utf-8"));
405
+ if (!files) return false;
406
+ if (files.includes("pnpm-workspace.yaml")) return true;
407
+ if (files.includes("package.json")) {
408
+ const packageJsonPath = path.join(dir, "package.json");
409
+ const { data } = await tryCatch(fs$1.readFile(packageJsonPath, "utf-8"));
410
+ if (data) try {
411
+ const packageJson = JSON.parse(data);
412
+ if (packageJson.workspaces && (Array.isArray(packageJson.workspaces) || typeof packageJson.workspaces === "object")) return true;
413
+ } catch {}
414
+ }
415
+ return [
416
+ "lerna.json",
417
+ "turbo.json",
418
+ "nx.json",
419
+ "rush.json"
420
+ ].some((indicator) => files.includes(indicator));
421
+ }
422
+ /**
423
+ * Finds the monorepo root by walking up the directory tree.
424
+ *
425
+ * @param startDir Starting directory
426
+ * @returns Path to monorepo root, or null if not found
427
+ */
428
+ async function findMonorepoRoot(startDir) {
429
+ let currentDir = path.resolve(startDir);
430
+ const root = path.parse(currentDir).root;
431
+ while (currentDir !== root) {
432
+ if (await isMonorepoRoot(currentDir)) return currentDir;
433
+ const parentDir = path.dirname(currentDir);
434
+ if (parentDir === currentDir) break;
435
+ currentDir = parentDir;
436
+ }
437
+ return null;
438
+ }
439
+
440
+ //#endregion
441
+ //#region src/generators/prisma.ts
442
+ const generatePrismaSchema = async ({ adapter, options, file }) => {
443
+ const provider = adapter.options?.provider || "postgresql";
444
+ const tables = getAuthTables(options);
445
+ const filePath = file || "./prisma/schema.prisma";
446
+ const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
447
+ const getModelName = initGetModelName({
448
+ schema: getAuthTables(options),
449
+ usePlural: adapter.options?.adapterConfig?.usePlural
450
+ });
451
+ const getFieldName = initGetFieldName({
452
+ schema: getAuthTables(options),
453
+ usePlural: false
454
+ });
455
+ let schemaPrisma = "";
456
+ if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(path.join(process.cwd(), filePath), "utf-8");
457
+ else schemaPrisma = getNewPrisma(provider, process.cwd());
458
+ const prismaVersion = getPrismaVersion(process.cwd());
459
+ if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
460
+ const generator = builder.findByType("generator", { name: "client" });
461
+ if (generator && generator.properties) {
462
+ const providerProp = generator.properties.find((prop) => prop.type === "assignment" && prop.key === "provider");
463
+ if (providerProp && providerProp.value === "\"prisma-client-js\"") providerProp.value = "\"prisma-client\"";
464
+ }
465
+ const datasource = builder.findByType("datasource", { name: "db" });
466
+ if (datasource && datasource.properties) {
467
+ const urlIndex = datasource.properties.findIndex((prop) => prop.type === "assignment" && prop.key === "url");
468
+ if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
469
+ }
470
+ });
471
+ const manyToManyRelations = /* @__PURE__ */ new Map();
472
+ for (const table in tables) {
473
+ const fields = tables[table]?.fields;
474
+ for (const field in fields) {
475
+ const attr = fields[field];
476
+ if (attr.references) {
477
+ const referencedOriginalModel = attr.references.model;
478
+ const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
479
+ if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
480
+ const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
481
+ manyToManyRelations.get(referencedModelNameCap).add(currentModelNameCap);
482
+ }
483
+ }
484
+ }
485
+ const indexedFields = /* @__PURE__ */ new Map();
486
+ for (const table in tables) {
487
+ const fields = tables[table]?.fields;
488
+ const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
489
+ indexedFields.set(modelName, []);
490
+ for (const field in fields) {
491
+ const attr = fields[field];
492
+ if (attr.index && !attr.unique) {
493
+ const fieldName = attr.fieldName || field;
494
+ indexedFields.get(modelName).push(fieldName);
495
+ }
496
+ }
497
+ }
498
+ const schema = produceSchema(schemaPrisma, (builder) => {
499
+ for (const table in tables) {
500
+ const originalTableName = table;
501
+ const customModelName = tables[table]?.modelName || table;
502
+ const modelName = capitalizeFirstLetter(getModelName(customModelName));
503
+ const fields = tables[table]?.fields;
504
+ function getType({ isBigint, isOptional, type }) {
505
+ if (type === "string") return isOptional ? "String?" : "String";
506
+ if (type === "number" && isBigint) return isOptional ? "BigInt?" : "BigInt";
507
+ if (type === "number") return isOptional ? "Int?" : "Int";
508
+ if (type === "boolean") return isOptional ? "Boolean?" : "Boolean";
509
+ if (type === "date") return isOptional ? "DateTime?" : "DateTime";
510
+ if (type === "json") {
511
+ if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
512
+ return isOptional ? "Json?" : "Json";
513
+ }
514
+ if (type === "string[]") {
515
+ if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
516
+ return "String[]";
517
+ }
518
+ if (type === "number[]") {
519
+ if (provider === "sqlite" || provider === "mysql") return "String";
520
+ return "Int[]";
521
+ }
522
+ }
523
+ const prismaModel = builder.findByType("model", { name: modelName });
524
+ if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
525
+ else {
526
+ const useNumberId = options.advanced?.database?.generateId === "serial";
527
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
528
+ if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
529
+ else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
530
+ else builder.model(modelName).field("id", "String").attribute("id");
531
+ }
532
+ for (const field in fields) {
533
+ const attr = fields[field];
534
+ const fieldName = attr.fieldName || field;
535
+ if (prismaModel) {
536
+ if (builder.findByType("field", {
537
+ name: fieldName,
538
+ within: prismaModel.properties
539
+ })) continue;
540
+ }
541
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
542
+ const useNumberId = options.advanced?.database?.generateId === "serial";
543
+ const fieldBuilder = builder.model(modelName).field(fieldName, field === "id" && useNumberId ? getType({
544
+ isBigint: false,
545
+ isOptional: false,
546
+ type: "number"
547
+ }) : getType({
548
+ isBigint: attr?.bigint || false,
549
+ isOptional: !attr?.required,
550
+ type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
551
+ }));
552
+ if (field === "id") {
553
+ fieldBuilder.attribute("id");
554
+ if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
555
+ }
556
+ if (attr.unique) builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
557
+ if (attr.defaultValue !== void 0) {
558
+ if (Array.isArray(attr.defaultValue)) {
559
+ if (attr.type === "json") {
560
+ if (Object.prototype.toString.call(attr.defaultValue[0]) === "[object Object]") {
561
+ fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
562
+ continue;
563
+ }
564
+ const jsonArray = [];
565
+ for (const value of attr.defaultValue) jsonArray.push(value);
566
+ fieldBuilder.attribute(`default("${JSON.stringify(jsonArray).replace(/"/g, "\\\"")}")`);
567
+ continue;
568
+ }
569
+ if (attr.defaultValue.length === 0) {
570
+ fieldBuilder.attribute(`default([])`);
571
+ continue;
572
+ } else if (typeof attr.defaultValue[0] === "string" && attr.type === "string[]") {
573
+ const valueArray = [];
574
+ for (const value of attr.defaultValue) valueArray.push(JSON.stringify(value));
575
+ fieldBuilder.attribute(`default([${valueArray}])`);
576
+ } else if (typeof attr.defaultValue[0] === "number") {
577
+ const valueArray = [];
578
+ for (const value of attr.defaultValue) valueArray.push(`${value}`);
579
+ fieldBuilder.attribute(`default([${valueArray}])`);
580
+ }
581
+ } else if (typeof attr.defaultValue === "object" && !Array.isArray(attr.defaultValue) && attr.defaultValue !== null) {
582
+ if (Object.entries(attr.defaultValue).length === 0) {
583
+ fieldBuilder.attribute(`default("{}")`);
584
+ continue;
585
+ }
586
+ fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
587
+ }
588
+ if (field === "createdAt") fieldBuilder.attribute("default(now())");
589
+ else if (typeof attr.defaultValue === "string" && provider !== "mysql") fieldBuilder.attribute(`default("${attr.defaultValue}")`);
590
+ else if (typeof attr.defaultValue === "boolean" || typeof attr.defaultValue === "number") fieldBuilder.attribute(`default(${attr.defaultValue})`);
591
+ else if (typeof attr.defaultValue === "function") {}
592
+ }
593
+ if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
594
+ else if (attr.onUpdate) {}
595
+ if (attr.references) {
596
+ if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
597
+ const referencedOriginalModelName = getModelName(attr.references.model);
598
+ const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
599
+ let action = "Cascade";
600
+ if (attr.references.onDelete === "no action") action = "NoAction";
601
+ else if (attr.references.onDelete === "set null") action = "SetNull";
602
+ else if (attr.references.onDelete === "set default") action = "SetDefault";
603
+ else if (attr.references.onDelete === "restrict") action = "Restrict";
604
+ const relationField = `relation(fields: [${getFieldName({
605
+ model: originalTableName,
606
+ field: fieldName
607
+ })}], references: [${getFieldName({
608
+ model: attr.references.model,
609
+ field: attr.references.field
610
+ })}], onDelete: ${action})`;
611
+ builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${!attr.required ? "?" : ""}`).attribute(relationField);
612
+ }
613
+ if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
614
+ }
615
+ if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
616
+ const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
617
+ const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
618
+ const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
619
+ const isUnique = fkFieldAttr?.unique === true;
620
+ const fieldName = isUnique || adapter.options?.usePlural === true ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
621
+ if (!builder.findByType("field", {
622
+ name: fieldName,
623
+ within: prismaModel?.properties
624
+ })) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
625
+ }
626
+ const indexedFieldsForModel = indexedFields.get(modelName);
627
+ if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
628
+ if (prismaModel) {
629
+ if (prismaModel.properties.some((v) => v.type === "attribute" && v.name === "index" && JSON.stringify(v.args[0]?.value).includes(fieldName))) continue;
630
+ }
631
+ const field = Object.entries(fields).find(([key, attr]) => (attr.fieldName || key) === fieldName)?.[1];
632
+ let indexField = fieldName;
633
+ if (provider === "mysql" && field && field.type === "string") {
634
+ const useNumberId = options.advanced?.database?.generateId === "serial";
635
+ const useUUIDs = options.advanced?.database?.generateId === "uuid";
636
+ if (field.references?.field === "id" && (useNumberId || useUUIDs)) indexField = `${fieldName}`;
637
+ else indexField = `${fieldName}(length: 191)`;
638
+ }
639
+ builder.model(modelName).blockAttribute(`index([${indexField}])`);
640
+ }
641
+ const hasAttribute = builder.findByType("attribute", {
642
+ name: "map",
643
+ within: prismaModel?.properties
644
+ });
645
+ const hasChanged = customModelName !== originalTableName;
646
+ if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
647
+ }
648
+ });
649
+ const schemaChanged = schema.trim() !== schemaPrisma.trim();
650
+ return {
651
+ code: schemaChanged ? schema : "",
652
+ fileName: filePath,
653
+ overwrite: schemaPrismaExist && schemaChanged
654
+ };
655
+ };
656
+ const getNewPrisma = (provider, cwd) => {
657
+ const prismaVersion = getPrismaVersion(cwd);
658
+ const isV7 = prismaVersion && prismaVersion >= 7;
659
+ const clientProvider = isV7 ? "prisma-client" : "prisma-client-js";
660
+ if (isV7) return `generator client {
661
+ provider = "${clientProvider}"
662
+ }
663
+
664
+ datasource db {
665
+ provider = "${provider}"
666
+ }`;
667
+ return `generator client {
668
+ provider = "${clientProvider}"
669
+ }
670
+
671
+ datasource db {
672
+ provider = "${provider}"
673
+ url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
674
+ }`;
675
+ };
676
+
677
+ //#endregion
678
+ //#region src/generators/index.ts
679
+ const adapters = {
680
+ prisma: generatePrismaSchema,
681
+ drizzle: generateDrizzleSchema,
682
+ kysely: generateKyselySchema
683
+ };
684
+ const generateSchema = (opts) => {
685
+ const adapter = opts.adapter;
686
+ const generator = adapter.id in adapters ? adapters[adapter.id] : null;
687
+ if (generator) return generator(opts);
688
+ if (adapter.createSchema) return adapter.createSchema(opts.options, opts.file).then(({ code, path: fileName, overwrite }) => ({
689
+ code,
690
+ fileName,
691
+ overwrite
692
+ }));
693
+ throw new Error(`${adapter.id} is not supported. If it is a custom adapter, please request the maintainer to implement createSchema`);
694
+ };
695
+
696
+ //#endregion
33
697
  //#region src/utils/add-cloudflare-modules.ts
34
698
  const createModule = () => {
35
699
  return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
@@ -5345,7 +6009,7 @@ export const auth = betterAuth({
5345
6009
  return;
5346
6010
  }
5347
6011
  if (connectResponse.connect) {
5348
- await open("https://beta.better-auth.com/onboarding");
6012
+ await open("https://better-auth.com/onboarding");
5349
6013
  console.log(chalk.cyan("\n→ ") + "Opening Better Auth onboarding in your browser...\n");
5350
6014
  }
5351
6015
  console.log(chalk.green(`\n✔ `) + chalk.bold("Success! ") + "Project setup complete.\n");