auth 1.5.0-beta.20 → 1.5.1-beta.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/api.mjs +586 -2
- package/dist/api.mjs.map +1 -0
- package/dist/index.mjs +837 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -10
- package/dist/generators-YiCQyoNc.mjs +0 -675
- package/dist/generators-YiCQyoNc.mjs.map +0 -1
- package/dist/index.d.mts +0 -7
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 {
|
|
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 {
|
|
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";
|
|
@@ -27,8 +30,670 @@ import z from "zod";
|
|
|
27
30
|
import { env } from "@better-auth/core/env";
|
|
28
31
|
import { log } from "@clack/prompts";
|
|
29
32
|
import { base64 } from "@better-auth/utils/base64";
|
|
33
|
+
import * as semver from "semver";
|
|
30
34
|
import "dotenv/config";
|
|
31
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
|
|
32
697
|
//#region src/utils/add-cloudflare-modules.ts
|
|
33
698
|
const createModule = () => {
|
|
34
699
|
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
|
@@ -436,11 +1101,56 @@ async function getConfig({ cwd, configPath, shouldThrowOnError = false }) {
|
|
|
436
1101
|
|
|
437
1102
|
//#endregion
|
|
438
1103
|
//#region src/commands/generate.ts
|
|
1104
|
+
function createMockAdapter$1(adapterId, dialect) {
|
|
1105
|
+
let provider;
|
|
1106
|
+
if (dialect) {
|
|
1107
|
+
if (adapterId === "drizzle") if (dialect === "postgresql") provider = "pg";
|
|
1108
|
+
else if (dialect === "mysql" || dialect === "sqlite") provider = dialect;
|
|
1109
|
+
else provider = dialect;
|
|
1110
|
+
else if (adapterId === "prisma") provider = dialect;
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
id: adapterId,
|
|
1114
|
+
create: async () => {
|
|
1115
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1116
|
+
},
|
|
1117
|
+
findOne: async () => {
|
|
1118
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1119
|
+
},
|
|
1120
|
+
findMany: async () => {
|
|
1121
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1122
|
+
},
|
|
1123
|
+
count: async () => {
|
|
1124
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1125
|
+
},
|
|
1126
|
+
update: async () => {
|
|
1127
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1128
|
+
},
|
|
1129
|
+
updateMany: async () => {
|
|
1130
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1131
|
+
},
|
|
1132
|
+
delete: async () => {
|
|
1133
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1134
|
+
},
|
|
1135
|
+
deleteMany: async () => {
|
|
1136
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1137
|
+
},
|
|
1138
|
+
transaction: async (callback) => {
|
|
1139
|
+
throw new Error("Mock adapter methods should not be called");
|
|
1140
|
+
},
|
|
1141
|
+
options: {
|
|
1142
|
+
adapterConfig: { adapterId },
|
|
1143
|
+
...provider && { provider }
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
439
1147
|
async function generateAction(opts) {
|
|
440
1148
|
const options = z$1.object({
|
|
441
1149
|
cwd: z$1.string(),
|
|
442
1150
|
config: z$1.string().optional(),
|
|
443
1151
|
output: z$1.string().optional(),
|
|
1152
|
+
adapter: z$1.string().optional(),
|
|
1153
|
+
dialect: z$1.string().optional(),
|
|
444
1154
|
y: z$1.boolean().optional(),
|
|
445
1155
|
yes: z$1.boolean().optional()
|
|
446
1156
|
}).parse(opts);
|
|
@@ -457,7 +1167,9 @@ async function generateAction(opts) {
|
|
|
457
1167
|
console.error("No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");
|
|
458
1168
|
return;
|
|
459
1169
|
}
|
|
460
|
-
|
|
1170
|
+
let adapter;
|
|
1171
|
+
if (options.adapter) adapter = createMockAdapter$1(options.adapter, options.dialect);
|
|
1172
|
+
else adapter = await getAdapter(config).catch((e) => {
|
|
461
1173
|
console.error(e.message);
|
|
462
1174
|
process.exit(1);
|
|
463
1175
|
});
|
|
@@ -559,7 +1271,7 @@ async function generateAction(opts) {
|
|
|
559
1271
|
} catch {}
|
|
560
1272
|
process.exit(0);
|
|
561
1273
|
}
|
|
562
|
-
const generate = new Command("generate").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("--output <output>", "the file to output to the generated schema").option("-y, --yes", "automatically answer yes to all prompts", false).option("--y", "(deprecated) same as --yes", false).action(generateAction);
|
|
1274
|
+
const generate = new Command("generate").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("--output <output>", "the file to output to the generated schema").option("--adapter <adapter>", "specify the adapter type (e.g., prisma, drizzle, kysely) without requiring a configured adapter").option("--dialect <dialect>", "specify the database dialect/provider (e.g., postgresql, mysql, sqlite). For drizzle, postgresql maps to 'pg'").option("-y, --yes", "automatically answer yes to all prompts", false).option("--y", "(deprecated) same as --yes", false).action(generateAction);
|
|
563
1275
|
|
|
564
1276
|
//#endregion
|
|
565
1277
|
//#region src/commands/info.ts
|
|
@@ -5297,7 +6009,7 @@ export const auth = betterAuth({
|
|
|
5297
6009
|
return;
|
|
5298
6010
|
}
|
|
5299
6011
|
if (connectResponse.connect) {
|
|
5300
|
-
await open("https://
|
|
6012
|
+
await open("https://better-auth.com/onboarding");
|
|
5301
6013
|
console.log(chalk.cyan("\n→ ") + "Opening Better Auth onboarding in your browser...\n");
|
|
5302
6014
|
}
|
|
5303
6015
|
console.log(chalk.green(`\n✔ `) + chalk.bold("Success! ") + "Project setup complete.\n");
|
|
@@ -5672,6 +6384,124 @@ const generateSecretHash = () => {
|
|
|
5672
6384
|
return Crypto.randomBytes(32).toString("hex");
|
|
5673
6385
|
};
|
|
5674
6386
|
|
|
6387
|
+
//#endregion
|
|
6388
|
+
//#region src/utils/fetch-latest-version.ts
|
|
6389
|
+
async function fetchLatestVersion(packageName) {
|
|
6390
|
+
const encoded = packageName.startsWith("@") ? `@${encodeURIComponent(packageName.slice(1))}` : encodeURIComponent(packageName);
|
|
6391
|
+
try {
|
|
6392
|
+
const response = await fetch(`https://registry.npmjs.org/${encoded}/latest`);
|
|
6393
|
+
if (!response.ok) return null;
|
|
6394
|
+
return (await response.json()).version ?? null;
|
|
6395
|
+
} catch {
|
|
6396
|
+
return null;
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
|
|
6400
|
+
//#endregion
|
|
6401
|
+
//#region src/commands/upgrade.ts
|
|
6402
|
+
function isBetterAuthPackage(name) {
|
|
6403
|
+
return name === "better-auth" || name.startsWith("@better-auth/");
|
|
6404
|
+
}
|
|
6405
|
+
async function upgradeAction(opts) {
|
|
6406
|
+
const options = z$1.object({
|
|
6407
|
+
cwd: z$1.string(),
|
|
6408
|
+
yes: z$1.boolean().optional()
|
|
6409
|
+
}).parse(opts);
|
|
6410
|
+
const cwd = path.resolve(options.cwd);
|
|
6411
|
+
if (!existsSync(cwd)) {
|
|
6412
|
+
console.error(`The directory "${cwd}" does not exist.`);
|
|
6413
|
+
process.exit(1);
|
|
6414
|
+
}
|
|
6415
|
+
let packageJson;
|
|
6416
|
+
try {
|
|
6417
|
+
packageJson = getPackageInfo(cwd);
|
|
6418
|
+
} catch {
|
|
6419
|
+
console.error(`Could not read package.json in "${cwd}". Make sure you are in a project directory.`);
|
|
6420
|
+
process.exit(1);
|
|
6421
|
+
}
|
|
6422
|
+
const deps = packageJson.dependencies ?? {};
|
|
6423
|
+
const devDeps = packageJson.devDependencies ?? {};
|
|
6424
|
+
const candidates = [];
|
|
6425
|
+
for (const [name, version] of Object.entries(deps)) if (isBetterAuthPackage(name) && !version.startsWith("workspace:")) candidates.push({
|
|
6426
|
+
name,
|
|
6427
|
+
current: version,
|
|
6428
|
+
depType: "prod"
|
|
6429
|
+
});
|
|
6430
|
+
for (const [name, version] of Object.entries(devDeps)) if (isBetterAuthPackage(name) && !version.startsWith("workspace:")) candidates.push({
|
|
6431
|
+
name,
|
|
6432
|
+
current: version,
|
|
6433
|
+
depType: "dev"
|
|
6434
|
+
});
|
|
6435
|
+
if (candidates.length === 0) {
|
|
6436
|
+
console.log("No better-auth packages found in this project.");
|
|
6437
|
+
return;
|
|
6438
|
+
}
|
|
6439
|
+
const spinner = yoctoSpinner({ text: "checking for updates..." }).start();
|
|
6440
|
+
const results = await Promise.allSettled(candidates.map(async (c) => {
|
|
6441
|
+
const latest = await fetchLatestVersion(c.name);
|
|
6442
|
+
return {
|
|
6443
|
+
...c,
|
|
6444
|
+
latest
|
|
6445
|
+
};
|
|
6446
|
+
}));
|
|
6447
|
+
const upgrades = [];
|
|
6448
|
+
for (const result of results) {
|
|
6449
|
+
if (result.status !== "fulfilled" || !result.value.latest) continue;
|
|
6450
|
+
const { name, current, latest, depType } = result.value;
|
|
6451
|
+
const coerced = semver.coerce(current);
|
|
6452
|
+
if (coerced && semver.lt(coerced, latest)) upgrades.push({
|
|
6453
|
+
name,
|
|
6454
|
+
current,
|
|
6455
|
+
latest,
|
|
6456
|
+
depType
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
spinner.stop();
|
|
6460
|
+
if (upgrades.length === 0) {
|
|
6461
|
+
console.log("All better-auth packages are up to date.");
|
|
6462
|
+
return;
|
|
6463
|
+
}
|
|
6464
|
+
console.log(`\nThe following packages can be upgraded:\n`);
|
|
6465
|
+
for (const u of upgrades) console.log(` ${chalk.cyan(u.name)} ${chalk.gray(u.current)} ${chalk.white("→")} ${chalk.green(u.latest)}`);
|
|
6466
|
+
console.log();
|
|
6467
|
+
let confirmed = options.yes;
|
|
6468
|
+
if (!confirmed) confirmed = (await prompts({
|
|
6469
|
+
type: "confirm",
|
|
6470
|
+
name: "confirmed",
|
|
6471
|
+
message: "Do you want to upgrade these packages?",
|
|
6472
|
+
initial: true
|
|
6473
|
+
})).confirmed;
|
|
6474
|
+
if (!confirmed) {
|
|
6475
|
+
console.log("Upgrade cancelled.");
|
|
6476
|
+
return;
|
|
6477
|
+
}
|
|
6478
|
+
const { packageManager } = await detectPackageManager(cwd, packageJson);
|
|
6479
|
+
const prodUpgrades = upgrades.filter((u) => u.depType === "prod").map((u) => `${u.name}@${u.latest}`);
|
|
6480
|
+
const devUpgrades = upgrades.filter((u) => u.depType === "dev").map((u) => `${u.name}@${u.latest}`);
|
|
6481
|
+
const installSpinner = yoctoSpinner({ text: "installing updates..." }).start();
|
|
6482
|
+
try {
|
|
6483
|
+
if (prodUpgrades.length > 0) await installDependencies({
|
|
6484
|
+
dependencies: prodUpgrades,
|
|
6485
|
+
packageManager,
|
|
6486
|
+
cwd,
|
|
6487
|
+
type: "prod"
|
|
6488
|
+
});
|
|
6489
|
+
if (devUpgrades.length > 0) await installDependencies({
|
|
6490
|
+
dependencies: devUpgrades,
|
|
6491
|
+
packageManager,
|
|
6492
|
+
cwd,
|
|
6493
|
+
type: "dev"
|
|
6494
|
+
});
|
|
6495
|
+
installSpinner.stop();
|
|
6496
|
+
console.log(chalk.green("Successfully upgraded better-auth packages."));
|
|
6497
|
+
} catch (error) {
|
|
6498
|
+
installSpinner.stop();
|
|
6499
|
+
console.error("Failed to install updates:", error);
|
|
6500
|
+
process.exit(1);
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
const upgrade = new Command("upgrade").description("Upgrade better-auth packages to their latest versions").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("-y, --yes", "automatically accept and upgrade without prompting", false).action(upgradeAction);
|
|
6504
|
+
|
|
5675
6505
|
//#endregion
|
|
5676
6506
|
//#region src/index.ts
|
|
5677
6507
|
process.on("SIGINT", () => process.exit(0));
|
|
@@ -5684,7 +6514,7 @@ async function main() {
|
|
|
5684
6514
|
packageInfo = await getPackageInfo();
|
|
5685
6515
|
cliVersion = packageInfo.version || "1.1.2";
|
|
5686
6516
|
} catch {}
|
|
5687
|
-
program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(info).addCommand(login).addCommand(logout).addCommand(mcp).version(cliVersion).description("Better Auth CLI").action(() => program.help());
|
|
6517
|
+
program.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());
|
|
5688
6518
|
program.parse();
|
|
5689
6519
|
}
|
|
5690
6520
|
main().catch((error) => {
|