auth 1.7.0-beta.0 → 1.7.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/api.d.mts +1 -1
- package/dist/api.mjs +70 -21
- package/dist/index.mjs +1395 -1123
- package/package.json +20 -17
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Better Auth CLI
|
|
2
2
|
|
|
3
3
|
Better Auth comes with a built-in CLI to help you manage the database schema
|
|
4
|
-
needed for both core functionality and plugins
|
|
4
|
+
needed for both core functionality and plugins, and to create an initial admin user
|
|
5
|
+
for projects using the Admin plugin.
|
|
5
6
|
|
|
6
7
|
### **Init**
|
|
7
8
|
|
|
@@ -34,6 +35,16 @@ tool.
|
|
|
34
35
|
npx auth@latest migrate
|
|
35
36
|
```
|
|
36
37
|
|
|
38
|
+
### **Create Admin**
|
|
39
|
+
|
|
40
|
+
Create the first admin user for an app using the Admin plugin. The command
|
|
41
|
+
uses your Better Auth config and prompts before creating an admin when users
|
|
42
|
+
already exist. The created admin email is marked as verified by default.
|
|
43
|
+
|
|
44
|
+
```bash title="terminal"
|
|
45
|
+
npx auth@latest create-admin --email admin@example.com --name "Admin" --role admin
|
|
46
|
+
```
|
|
47
|
+
|
|
37
48
|
### **Secret**
|
|
38
49
|
|
|
39
50
|
The CLI also provides a way to generate a secret key for your Better Auth
|
package/dist/api.d.mts
CHANGED
|
@@ -26,7 +26,7 @@ declare const generateSchema: (opts: {
|
|
|
26
26
|
adapter: DBAdapter$1;
|
|
27
27
|
file?: string;
|
|
28
28
|
options: BetterAuthOptions;
|
|
29
|
-
}) => Promise<SchemaGeneratorResult
|
|
29
|
+
}) => Promise<SchemaGeneratorResult | {
|
|
30
30
|
code: string;
|
|
31
31
|
fileName: string;
|
|
32
32
|
overwrite: boolean | undefined;
|
package/dist/api.mjs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { capitalizeFirstLetter, toSnakeCase } from "@better-auth/core/utils/string";
|
|
2
3
|
import { initGetFieldName, initGetModelName } from "better-auth/adapters";
|
|
3
4
|
import { getAuthTables } from "better-auth/db";
|
|
4
5
|
import prettier from "prettier";
|
|
5
6
|
import { getMigrations } from "better-auth/db/migration";
|
|
6
7
|
import fs from "node:fs/promises";
|
|
7
8
|
import path from "node:path";
|
|
8
|
-
import { capitalizeFirstLetter } from "@better-auth/core/utils/string";
|
|
9
9
|
import { produceSchema } from "@mrleebo/prisma-ast";
|
|
10
10
|
//#region src/generators/drizzle.ts
|
|
11
11
|
function convertToSnakeCase(str, camelCase) {
|
|
12
|
-
|
|
13
|
-
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
|
|
12
|
+
return camelCase ? str : toSnakeCase(str);
|
|
14
13
|
}
|
|
15
14
|
const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
16
15
|
const tables = getAuthTables(options);
|
|
@@ -27,12 +26,22 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
|
27
26
|
schema: tables,
|
|
28
27
|
usePlural: adapter.options?.adapterConfig?.usePlural
|
|
29
28
|
});
|
|
29
|
+
const getSingularModelName = initGetModelName({
|
|
30
|
+
schema: tables,
|
|
31
|
+
usePlural: false
|
|
32
|
+
});
|
|
30
33
|
const getFieldName = initGetFieldName({
|
|
31
34
|
schema: tables,
|
|
32
35
|
usePlural: adapter.options?.adapterConfig?.usePlural
|
|
33
36
|
});
|
|
37
|
+
const isMigrationDisabled = (model) => Object.entries(tables).some(([tableKey, table]) => {
|
|
38
|
+
if (table.disableMigrations !== true) return false;
|
|
39
|
+
const customModelName = table.modelName || tableKey;
|
|
40
|
+
return model === tableKey || model === customModelName || model === getModelName(tableKey) || model === getModelName(customModelName);
|
|
41
|
+
});
|
|
34
42
|
for (const tableKey in tables) {
|
|
35
43
|
const table = tables[tableKey];
|
|
44
|
+
if (isMigrationDisabled(tableKey)) continue;
|
|
36
45
|
const modelName = getModelName(tableKey);
|
|
37
46
|
const fields = table.fields;
|
|
38
47
|
function getType(name, field) {
|
|
@@ -52,9 +61,9 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
|
52
61
|
}
|
|
53
62
|
const type = field.type;
|
|
54
63
|
if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
|
|
55
|
-
sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
|
|
64
|
+
sqlite: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
|
|
56
65
|
pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
|
|
57
|
-
mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
|
|
66
|
+
mysql: `mysqlEnum('${name}', [${type.map((x) => `'${x}'`).join(", ")}])`
|
|
58
67
|
}[databaseType];
|
|
59
68
|
else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
|
|
60
69
|
const dbTypeMap = {
|
|
@@ -134,11 +143,16 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
|
134
143
|
if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
|
|
135
144
|
else type += `.defaultNow()`;
|
|
136
145
|
} else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
|
|
146
|
+
else if (Array.isArray(attr.defaultValue)) {
|
|
147
|
+
const elements = attr.defaultValue.map((value) => JSON.stringify(value)).join(", ");
|
|
148
|
+
type += `.default([${elements}])`;
|
|
149
|
+
} else if (typeof attr.defaultValue === "object" && attr.defaultValue !== null) type += `.default(${JSON.stringify(attr.defaultValue)})`;
|
|
137
150
|
else type += `.default(${attr.defaultValue})`;
|
|
138
151
|
if (attr.onUpdate && attr.type === "date") {
|
|
139
152
|
if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
|
|
140
153
|
}
|
|
141
|
-
|
|
154
|
+
const referencesDisabledModel = attr.references && isMigrationDisabled(attr.references.model);
|
|
155
|
+
return `${fieldName}: ${type}${attr.required !== false ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references && !referencesDisabledModel ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
|
|
142
156
|
model: attr.references.model,
|
|
143
157
|
field: attr.references.field
|
|
144
158
|
})}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
|
|
@@ -149,6 +163,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
|
149
163
|
let relationsString = "";
|
|
150
164
|
for (const tableKey in tables) {
|
|
151
165
|
const table = tables[tableKey];
|
|
166
|
+
if (isMigrationDisabled(tableKey)) continue;
|
|
152
167
|
const modelName = getModelName(tableKey);
|
|
153
168
|
const oneRelations = [];
|
|
154
169
|
const manyRelations = [];
|
|
@@ -156,7 +171,8 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
|
156
171
|
const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
|
|
157
172
|
for (const [fieldName, field] of foreignFields) {
|
|
158
173
|
const referencedModel = field.references.model;
|
|
159
|
-
|
|
174
|
+
if (isMigrationDisabled(referencedModel)) continue;
|
|
175
|
+
const relationKey = getSingularModelName(referencedModel);
|
|
160
176
|
const fieldRef = `${getModelName(tableKey)}.${getFieldName({
|
|
161
177
|
model: tableKey,
|
|
162
178
|
field: fieldName
|
|
@@ -176,7 +192,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
|
176
192
|
}
|
|
177
193
|
});
|
|
178
194
|
}
|
|
179
|
-
const otherModels = Object.entries(tables).filter(([modelName]) => modelName !== tableKey);
|
|
195
|
+
const otherModels = Object.entries(tables).filter(([modelName, otherTable]) => modelName !== tableKey && !otherTable.disableMigrations);
|
|
180
196
|
const modelRelationsMap = /* @__PURE__ */ new Map();
|
|
181
197
|
for (const [modelName, otherTable] of otherModels) {
|
|
182
198
|
const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
|
|
@@ -337,6 +353,11 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
337
353
|
schema: getAuthTables(options),
|
|
338
354
|
usePlural: false
|
|
339
355
|
});
|
|
356
|
+
const isMigrationDisabled = (model) => Object.entries(tables).some(([tableKey, table]) => {
|
|
357
|
+
if (table.disableMigrations !== true) return false;
|
|
358
|
+
const customModelName = table.modelName || tableKey;
|
|
359
|
+
return model === tableKey || model === customModelName || model === getModelName(tableKey) || model === getModelName(customModelName);
|
|
360
|
+
});
|
|
340
361
|
let schemaPrisma = "";
|
|
341
362
|
if (schemaPrismaExist) schemaPrisma = await fs.readFile(path.join(process.cwd(), filePath), "utf-8");
|
|
342
363
|
else schemaPrisma = getNewPrisma(provider, process.cwd());
|
|
@@ -355,11 +376,13 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
355
376
|
});
|
|
356
377
|
const manyToManyRelations = /* @__PURE__ */ new Map();
|
|
357
378
|
for (const table in tables) {
|
|
379
|
+
if (isMigrationDisabled(table)) continue;
|
|
358
380
|
const fields = tables[table]?.fields;
|
|
359
381
|
for (const field in fields) {
|
|
360
382
|
const attr = fields[field];
|
|
361
383
|
if (attr.references) {
|
|
362
384
|
const referencedOriginalModel = attr.references.model;
|
|
385
|
+
if (isMigrationDisabled(referencedOriginalModel)) continue;
|
|
363
386
|
const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
|
|
364
387
|
if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
|
|
365
388
|
const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
|
|
@@ -382,6 +405,7 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
382
405
|
}
|
|
383
406
|
const schema = produceSchema(schemaPrisma, (builder) => {
|
|
384
407
|
for (const table in tables) {
|
|
408
|
+
if (isMigrationDisabled(table)) continue;
|
|
385
409
|
const originalTableName = table;
|
|
386
410
|
const customModelName = tables[table]?.modelName || table;
|
|
387
411
|
const modelName = capitalizeFirstLetter(getModelName(customModelName));
|
|
@@ -405,6 +429,16 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
405
429
|
return "Int[]";
|
|
406
430
|
}
|
|
407
431
|
}
|
|
432
|
+
function getFieldTypeParts(type) {
|
|
433
|
+
const isArray = type.endsWith("[]");
|
|
434
|
+
const typeWithoutArray = isArray ? type.slice(0, -2) : type;
|
|
435
|
+
const isOptional = typeWithoutArray.endsWith("?");
|
|
436
|
+
return {
|
|
437
|
+
fieldType: isOptional ? typeWithoutArray.slice(0, -1) : typeWithoutArray,
|
|
438
|
+
isArray,
|
|
439
|
+
isOptional
|
|
440
|
+
};
|
|
441
|
+
}
|
|
408
442
|
const prismaModel = builder.findByType("model", { name: modelName });
|
|
409
443
|
if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
|
|
410
444
|
else {
|
|
@@ -417,15 +451,9 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
417
451
|
for (const field in fields) {
|
|
418
452
|
const attr = fields[field];
|
|
419
453
|
const fieldName = attr.fieldName || field;
|
|
420
|
-
if (prismaModel) {
|
|
421
|
-
if (builder.findByType("field", {
|
|
422
|
-
name: fieldName,
|
|
423
|
-
within: prismaModel.properties
|
|
424
|
-
})) continue;
|
|
425
|
-
}
|
|
426
454
|
const useUUIDs = options.advanced?.database?.generateId === "uuid";
|
|
427
455
|
const useNumberId = options.advanced?.database?.generateId === "serial";
|
|
428
|
-
const
|
|
456
|
+
const fieldType = field === "id" && useNumberId ? getType({
|
|
429
457
|
isBigint: false,
|
|
430
458
|
isOptional: false,
|
|
431
459
|
type: "number"
|
|
@@ -433,7 +461,27 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
433
461
|
isBigint: attr?.bigint || false,
|
|
434
462
|
isOptional: attr?.required === false,
|
|
435
463
|
type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
|
|
436
|
-
})
|
|
464
|
+
});
|
|
465
|
+
if (prismaModel) {
|
|
466
|
+
const isAlreadyExist = builder.findByType("field", {
|
|
467
|
+
name: fieldName,
|
|
468
|
+
within: prismaModel.properties
|
|
469
|
+
});
|
|
470
|
+
if (isAlreadyExist) {
|
|
471
|
+
if (fieldType && typeof isAlreadyExist.fieldType === "string") {
|
|
472
|
+
const fieldTypeParts = getFieldTypeParts(fieldType);
|
|
473
|
+
const existingFieldTypeParts = getFieldTypeParts(isAlreadyExist.fieldType);
|
|
474
|
+
if ((existingFieldTypeParts.fieldType === "Int" || existingFieldTypeParts.fieldType === "BigInt") && (fieldTypeParts.fieldType === "Int" || fieldTypeParts.fieldType === "BigInt")) {
|
|
475
|
+
isAlreadyExist.fieldType = fieldTypeParts.fieldType;
|
|
476
|
+
isAlreadyExist.optional = fieldTypeParts.isOptional || void 0;
|
|
477
|
+
isAlreadyExist.array = fieldTypeParts.isArray || void 0;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!fieldType) throw new Error(`Unsupported Prisma field type for model "${modelName}", field "${fieldName}"${attr.type ? ` (source type: "${attr.type}")` : ""}.`);
|
|
484
|
+
const fieldBuilder = builder.model(modelName).field(fieldName, fieldType);
|
|
437
485
|
if (field === "id") {
|
|
438
486
|
fieldBuilder.attribute("id");
|
|
439
487
|
if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
|
|
@@ -478,8 +526,9 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
478
526
|
if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
|
|
479
527
|
else if (attr.onUpdate) {}
|
|
480
528
|
if (attr.references) {
|
|
481
|
-
if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
|
|
482
529
|
const referencedOriginalModelName = getModelName(attr.references.model);
|
|
530
|
+
if (isMigrationDisabled(referencedOriginalModelName) || isMigrationDisabled(attr.references.model)) continue;
|
|
531
|
+
if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
|
|
483
532
|
const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
|
|
484
533
|
let action = "Cascade";
|
|
485
534
|
if (attr.references.onDelete === "no action") action = "NoAction";
|
|
@@ -565,16 +614,16 @@ const adapters = {
|
|
|
565
614
|
drizzle: generateDrizzleSchema,
|
|
566
615
|
kysely: generateKyselySchema
|
|
567
616
|
};
|
|
568
|
-
const generateSchema = (opts) => {
|
|
617
|
+
const generateSchema = async (opts) => {
|
|
569
618
|
const adapter = opts.adapter;
|
|
570
|
-
const generator = adapter.id in adapters ? adapters[adapter.id] : null;
|
|
571
|
-
if (generator) return generator(opts);
|
|
572
619
|
if (adapter.createSchema) return adapter.createSchema(opts.options, opts.file).then(({ code, path: fileName, overwrite }) => ({
|
|
573
620
|
code,
|
|
574
621
|
fileName,
|
|
575
622
|
overwrite
|
|
576
623
|
}));
|
|
577
|
-
|
|
624
|
+
const generator = adapters[adapter.id] ?? null;
|
|
625
|
+
if (generator) return await generator(opts);
|
|
626
|
+
throw new Error(`${adapter.id} is not supported. If it is a custom adapter, please request the maintainer to implement the "createSchema" method on the adapter.`);
|
|
578
627
|
};
|
|
579
628
|
//#endregion
|
|
580
629
|
export { adapters, generateDrizzleSchema, generateKyselySchema, generatePrismaSchema, generateSchema };
|