@better-auth/cli 1.4.12 → 1.4.14
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.d.mts +44 -0
- package/dist/api.mjs +3 -0
- package/dist/generators-Ht8QYIi_.mjs +571 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +109 -631
- package/package.json +19 -6
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { i as getPackageInfo, n as generateSchema } from "./generators-Ht8QYIi_.mjs";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
import * as fs$2 from "node:fs";
|
|
4
5
|
import fs, { existsSync, readFileSync } from "node:fs";
|
|
@@ -6,15 +7,12 @@ import fs$1 from "node:fs/promises";
|
|
|
6
7
|
import * as path$1 from "node:path";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import { createTelemetry, getTelemetryAuthConfig } from "@better-auth/telemetry";
|
|
9
|
-
import { getAdapter,
|
|
10
|
+
import { getAdapter, getMigrations } from "better-auth/db";
|
|
10
11
|
import chalk from "chalk";
|
|
11
12
|
import prompts from "prompts";
|
|
12
13
|
import yoctoSpinner from "yocto-spinner";
|
|
13
14
|
import * as z from "zod/v4";
|
|
14
|
-
import {
|
|
15
|
-
import prettier, { format } from "prettier";
|
|
16
|
-
import { capitalizeFirstLetter } from "@better-auth/core/utils";
|
|
17
|
-
import { produceSchema } from "@mrleebo/prisma-ast";
|
|
15
|
+
import { format } from "prettier";
|
|
18
16
|
import babelPresetReact from "@babel/preset-react";
|
|
19
17
|
import babelPresetTypeScript from "@babel/preset-typescript";
|
|
20
18
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
@@ -32,568 +30,6 @@ import open from "open";
|
|
|
32
30
|
import { base64 } from "@better-auth/utils/base64";
|
|
33
31
|
import "dotenv/config";
|
|
34
32
|
|
|
35
|
-
//#region src/generators/drizzle.ts
|
|
36
|
-
function convertToSnakeCase(str, camelCase) {
|
|
37
|
-
if (camelCase) return str;
|
|
38
|
-
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
|
|
39
|
-
}
|
|
40
|
-
const generateDrizzleSchema = async ({ options, file, adapter }) => {
|
|
41
|
-
const tables = getAuthTables(options);
|
|
42
|
-
const filePath = file || "./auth-schema.ts";
|
|
43
|
-
const databaseType = adapter.options?.provider;
|
|
44
|
-
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`);
|
|
45
|
-
const fileExist = existsSync(filePath);
|
|
46
|
-
let code = generateImport({
|
|
47
|
-
databaseType,
|
|
48
|
-
tables,
|
|
49
|
-
options
|
|
50
|
-
});
|
|
51
|
-
const getModelName = initGetModelName({
|
|
52
|
-
schema: tables,
|
|
53
|
-
usePlural: adapter.options?.adapterConfig?.usePlural
|
|
54
|
-
});
|
|
55
|
-
const getFieldName = initGetFieldName({
|
|
56
|
-
schema: tables,
|
|
57
|
-
usePlural: adapter.options?.adapterConfig?.usePlural
|
|
58
|
-
});
|
|
59
|
-
for (const tableKey in tables) {
|
|
60
|
-
const table = tables[tableKey];
|
|
61
|
-
const modelName = getModelName(tableKey);
|
|
62
|
-
const fields = table.fields;
|
|
63
|
-
function getType(name, field) {
|
|
64
|
-
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`);
|
|
65
|
-
name = convertToSnakeCase(name, adapter.options?.camelCase);
|
|
66
|
-
if (field.references?.field === "id") {
|
|
67
|
-
const useNumberId$1 = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
|
|
68
|
-
const useUUIDs = options.advanced?.database?.generateId === "uuid";
|
|
69
|
-
if (useNumberId$1) if (databaseType === "pg") return `integer('${name}')`;
|
|
70
|
-
else if (databaseType === "mysql") return `int('${name}')`;
|
|
71
|
-
else return `integer('${name}')`;
|
|
72
|
-
if (useUUIDs && databaseType === "pg") return `uuid('${name}')`;
|
|
73
|
-
if (field.references.field) {
|
|
74
|
-
if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
|
|
75
|
-
}
|
|
76
|
-
return `text('${name}')`;
|
|
77
|
-
}
|
|
78
|
-
const type = field.type;
|
|
79
|
-
if (typeof type !== "string") if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
|
|
80
|
-
sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
|
|
81
|
-
pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
|
|
82
|
-
mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
|
|
83
|
-
}[databaseType];
|
|
84
|
-
else throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
|
|
85
|
-
const dbTypeMap = {
|
|
86
|
-
string: {
|
|
87
|
-
sqlite: `text('${name}')`,
|
|
88
|
-
pg: `text('${name}')`,
|
|
89
|
-
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}')`
|
|
90
|
-
},
|
|
91
|
-
boolean: {
|
|
92
|
-
sqlite: `integer('${name}', { mode: 'boolean' })`,
|
|
93
|
-
pg: `boolean('${name}')`,
|
|
94
|
-
mysql: `boolean('${name}')`
|
|
95
|
-
},
|
|
96
|
-
number: {
|
|
97
|
-
sqlite: `integer('${name}')`,
|
|
98
|
-
pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
|
|
99
|
-
mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
|
|
100
|
-
},
|
|
101
|
-
date: {
|
|
102
|
-
sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
|
|
103
|
-
pg: `timestamp('${name}')`,
|
|
104
|
-
mysql: `timestamp('${name}', { fsp: 3 })`
|
|
105
|
-
},
|
|
106
|
-
"number[]": {
|
|
107
|
-
sqlite: `text('${name}', { mode: "json" })`,
|
|
108
|
-
pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
|
|
109
|
-
mysql: `text('${name}', { mode: 'json' })`
|
|
110
|
-
},
|
|
111
|
-
"string[]": {
|
|
112
|
-
sqlite: `text('${name}', { mode: "json" })`,
|
|
113
|
-
pg: `text('${name}').array()`,
|
|
114
|
-
mysql: `text('${name}', { mode: "json" })`
|
|
115
|
-
},
|
|
116
|
-
json: {
|
|
117
|
-
sqlite: `text('${name}', { mode: "json" })`,
|
|
118
|
-
pg: `jsonb('${name}')`,
|
|
119
|
-
mysql: `json('${name}', { mode: "json" })`
|
|
120
|
-
}
|
|
121
|
-
}[type];
|
|
122
|
-
if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
|
|
123
|
-
return dbTypeMap[databaseType];
|
|
124
|
-
}
|
|
125
|
-
let id = "";
|
|
126
|
-
const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
|
|
127
|
-
if (options.advanced?.database?.generateId === "uuid" && databaseType === "pg") id = `uuid("id").default(sql\`pg_catalog.gen_random_uuid()\`).primaryKey()`;
|
|
128
|
-
else if (useNumberId) if (databaseType === "pg") id = `integer("id").generatedByDefaultAsIdentity().primaryKey()`;
|
|
129
|
-
else if (databaseType === "sqlite") id = `integer("id", { mode: "number" }).primaryKey({ autoIncrement: true })`;
|
|
130
|
-
else id = `int("id").autoincrement().primaryKey()`;
|
|
131
|
-
else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
|
|
132
|
-
else if (databaseType === "pg") id = `text('id').primaryKey()`;
|
|
133
|
-
else id = `text('id').primaryKey()`;
|
|
134
|
-
let indexes = [];
|
|
135
|
-
const assignIndexes = (indexes$1) => {
|
|
136
|
-
if (!indexes$1.length) return "";
|
|
137
|
-
let code$1 = [`, (table) => [`];
|
|
138
|
-
for (const index of indexes$1) code$1.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
|
|
139
|
-
code$1.push(`]`);
|
|
140
|
-
return code$1.join("\n");
|
|
141
|
-
};
|
|
142
|
-
const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
|
|
143
|
-
id: ${id},
|
|
144
|
-
${Object.keys(fields).map((field) => {
|
|
145
|
-
const attr = fields[field];
|
|
146
|
-
const fieldName = attr.fieldName || field;
|
|
147
|
-
let type = getType(fieldName, attr);
|
|
148
|
-
if (attr.index && !attr.unique) indexes.push({
|
|
149
|
-
type: "index",
|
|
150
|
-
name: `${modelName}_${fieldName}_idx`,
|
|
151
|
-
on: fieldName
|
|
152
|
-
});
|
|
153
|
-
else if (attr.index && attr.unique) indexes.push({
|
|
154
|
-
type: "uniqueIndex",
|
|
155
|
-
name: `${modelName}_${fieldName}_uidx`,
|
|
156
|
-
on: fieldName
|
|
157
|
-
});
|
|
158
|
-
if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
|
|
159
|
-
if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
|
|
160
|
-
else type += `.defaultNow()`;
|
|
161
|
-
} else if (typeof attr.defaultValue === "string") type += `.default("${attr.defaultValue}")`;
|
|
162
|
-
else type += `.default(${attr.defaultValue})`;
|
|
163
|
-
if (attr.onUpdate && attr.type === "date") {
|
|
164
|
-
if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
|
|
165
|
-
}
|
|
166
|
-
return `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
|
|
167
|
-
model: attr.references.model,
|
|
168
|
-
field: attr.references.field
|
|
169
|
-
})}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
|
|
170
|
-
}).join(",\n ")}
|
|
171
|
-
}${assignIndexes(indexes)});`;
|
|
172
|
-
code += `\n${schema}\n`;
|
|
173
|
-
}
|
|
174
|
-
let relationsString = "";
|
|
175
|
-
for (const tableKey in tables) {
|
|
176
|
-
const table = tables[tableKey];
|
|
177
|
-
const modelName = getModelName(tableKey);
|
|
178
|
-
const oneRelations = [];
|
|
179
|
-
const manyRelations = [];
|
|
180
|
-
const manyRelationsSet = /* @__PURE__ */ new Set();
|
|
181
|
-
const foreignFields = Object.entries(table.fields).filter(([_, field]) => field.references);
|
|
182
|
-
for (const [fieldName, field] of foreignFields) {
|
|
183
|
-
const referencedModel = field.references.model;
|
|
184
|
-
const relationKey = getModelName(referencedModel);
|
|
185
|
-
const fieldRef = `${getModelName(tableKey)}.${getFieldName({
|
|
186
|
-
model: tableKey,
|
|
187
|
-
field: fieldName
|
|
188
|
-
})}`;
|
|
189
|
-
const referenceRef = `${getModelName(referencedModel)}.${getFieldName({
|
|
190
|
-
model: referencedModel,
|
|
191
|
-
field: field.references.field || "id"
|
|
192
|
-
})}`;
|
|
193
|
-
oneRelations.push({
|
|
194
|
-
key: relationKey,
|
|
195
|
-
model: getModelName(referencedModel),
|
|
196
|
-
type: "one",
|
|
197
|
-
reference: {
|
|
198
|
-
field: fieldRef,
|
|
199
|
-
references: referenceRef,
|
|
200
|
-
fieldName
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
const otherModels = Object.entries(tables).filter(([modelName$1]) => modelName$1 !== tableKey);
|
|
205
|
-
const modelRelationsMap = /* @__PURE__ */ new Map();
|
|
206
|
-
for (const [modelName$1, otherTable] of otherModels) {
|
|
207
|
-
const foreignKeysPointingHere = Object.entries(otherTable.fields).filter(([_, field]) => field.references?.model === tableKey || field.references?.model === getModelName(tableKey));
|
|
208
|
-
if (foreignKeysPointingHere.length === 0) continue;
|
|
209
|
-
const hasUnique = foreignKeysPointingHere.some(([_, field]) => !!field.unique);
|
|
210
|
-
const hasMany$1 = foreignKeysPointingHere.some(([_, field]) => !field.unique);
|
|
211
|
-
modelRelationsMap.set(modelName$1, {
|
|
212
|
-
modelName: modelName$1,
|
|
213
|
-
hasUnique,
|
|
214
|
-
hasMany: hasMany$1
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
for (const { modelName: modelName$1, hasMany: hasMany$1 } of modelRelationsMap.values()) {
|
|
218
|
-
const relationType = hasMany$1 ? "many" : "one";
|
|
219
|
-
let relationKey = getModelName(modelName$1);
|
|
220
|
-
if (!adapter.options?.adapterConfig?.usePlural && relationType === "many") relationKey = `${relationKey}s`;
|
|
221
|
-
if (!manyRelationsSet.has(relationKey)) {
|
|
222
|
-
manyRelationsSet.add(relationKey);
|
|
223
|
-
manyRelations.push({
|
|
224
|
-
key: relationKey,
|
|
225
|
-
model: getModelName(modelName$1),
|
|
226
|
-
type: relationType
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const relationsByModel = /* @__PURE__ */ new Map();
|
|
231
|
-
for (const relation of oneRelations) if (relation.reference) {
|
|
232
|
-
const modelKey = relation.key;
|
|
233
|
-
if (!relationsByModel.has(modelKey)) relationsByModel.set(modelKey, []);
|
|
234
|
-
relationsByModel.get(modelKey).push(relation);
|
|
235
|
-
}
|
|
236
|
-
const duplicateRelations = [];
|
|
237
|
-
const singleRelations = [];
|
|
238
|
-
for (const [_modelKey, relations] of relationsByModel.entries()) if (relations.length > 1) duplicateRelations.push(...relations);
|
|
239
|
-
else singleRelations.push(relations[0]);
|
|
240
|
-
for (const relation of duplicateRelations) if (relation.reference) {
|
|
241
|
-
const fieldName = relation.reference.fieldName;
|
|
242
|
-
const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
|
|
243
|
-
${relation.key}: one(${relation.model}, {
|
|
244
|
-
fields: [${relation.reference.field}],
|
|
245
|
-
references: [${relation.reference.references}],
|
|
246
|
-
})
|
|
247
|
-
}))`;
|
|
248
|
-
relationsString += `\n${tableRelation}\n`;
|
|
249
|
-
}
|
|
250
|
-
const hasOne = singleRelations.length > 0;
|
|
251
|
-
const hasMany = manyRelations.length > 0;
|
|
252
|
-
if (hasOne && hasMany) {
|
|
253
|
-
const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
|
|
254
|
-
${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
|
|
255
|
-
fields: [${relation.reference.field}],
|
|
256
|
-
references: [${relation.reference.references}],
|
|
257
|
-
})` : "").filter((x) => x !== "").join(",\n ")}${singleRelations.length > 0 && manyRelations.length > 0 ? "," : ""}
|
|
258
|
-
${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
|
|
259
|
-
}))`;
|
|
260
|
-
relationsString += `\n${tableRelation}\n`;
|
|
261
|
-
} else if (hasOne) {
|
|
262
|
-
const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
|
|
263
|
-
${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
|
|
264
|
-
fields: [${relation.reference.field}],
|
|
265
|
-
references: [${relation.reference.references}],
|
|
266
|
-
})` : "").filter((x) => x !== "").join(",\n ")}
|
|
267
|
-
}))`;
|
|
268
|
-
relationsString += `\n${tableRelation}\n`;
|
|
269
|
-
} else if (hasMany) {
|
|
270
|
-
const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
|
|
271
|
-
${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
|
|
272
|
-
}))`;
|
|
273
|
-
relationsString += `\n${tableRelation}\n`;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
code += `\n${relationsString}`;
|
|
277
|
-
return {
|
|
278
|
-
code: await prettier.format(code, { parser: "typescript" }),
|
|
279
|
-
fileName: filePath,
|
|
280
|
-
overwrite: fileExist
|
|
281
|
-
};
|
|
282
|
-
};
|
|
283
|
-
function generateImport({ databaseType, tables, options }) {
|
|
284
|
-
const rootImports = ["relations"];
|
|
285
|
-
const coreImports = [];
|
|
286
|
-
let hasBigint = false;
|
|
287
|
-
let hasJson = false;
|
|
288
|
-
for (const table of Object.values(tables)) {
|
|
289
|
-
for (const field of Object.values(table.fields)) {
|
|
290
|
-
if (field.bigint) hasBigint = true;
|
|
291
|
-
if (field.type === "json") hasJson = true;
|
|
292
|
-
}
|
|
293
|
-
if (hasJson && hasBigint) break;
|
|
294
|
-
}
|
|
295
|
-
const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
|
|
296
|
-
const useUUIDs = options.advanced?.database?.generateId === "uuid";
|
|
297
|
-
coreImports.push(`${databaseType}Table`);
|
|
298
|
-
coreImports.push(databaseType === "mysql" ? "varchar, text" : databaseType === "pg" ? "text" : "text");
|
|
299
|
-
coreImports.push(hasBigint ? databaseType !== "sqlite" ? "bigint" : "" : "");
|
|
300
|
-
coreImports.push(databaseType !== "sqlite" ? "timestamp, boolean" : "");
|
|
301
|
-
if (databaseType === "mysql") {
|
|
302
|
-
const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
|
|
303
|
-
if (!!useNumberId || hasNonBigintNumber) coreImports.push("int");
|
|
304
|
-
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");
|
|
305
|
-
} else if (databaseType === "pg") {
|
|
306
|
-
if (useUUIDs) rootImports.push("sql");
|
|
307
|
-
const hasNonBigintNumber = Object.values(tables).some((table) => Object.values(table.fields).some((field) => (field.type === "number" || field.type === "number[]") && !field.bigint));
|
|
308
|
-
const hasFkToId = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.references?.field === "id"));
|
|
309
|
-
if (hasNonBigintNumber || (options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial") && hasFkToId) coreImports.push("integer");
|
|
310
|
-
} else coreImports.push("integer");
|
|
311
|
-
if (databaseType === "pg" && useUUIDs) coreImports.push("uuid");
|
|
312
|
-
if (hasJson) {
|
|
313
|
-
if (databaseType === "pg") coreImports.push("jsonb");
|
|
314
|
-
if (databaseType === "mysql") coreImports.push("json");
|
|
315
|
-
}
|
|
316
|
-
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");
|
|
317
|
-
const hasIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.index && !field.unique));
|
|
318
|
-
const hasUniqueIndexes = Object.values(tables).some((table) => Object.values(table.fields).some((field) => field.unique && field.index));
|
|
319
|
-
if (hasIndexes) coreImports.push("index");
|
|
320
|
-
if (hasUniqueIndexes) coreImports.push("uniqueIndex");
|
|
321
|
-
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`;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
//#endregion
|
|
325
|
-
//#region src/generators/kysely.ts
|
|
326
|
-
const generateMigrations = async ({ options, file }) => {
|
|
327
|
-
const { compileMigrations } = await getMigrations(options);
|
|
328
|
-
const migrations = await compileMigrations();
|
|
329
|
-
return {
|
|
330
|
-
code: migrations.trim() === ";" ? "" : migrations,
|
|
331
|
-
fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
|
|
332
|
-
};
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
//#endregion
|
|
336
|
-
//#region src/utils/get-package-info.ts
|
|
337
|
-
function getPackageInfo(cwd) {
|
|
338
|
-
const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
|
|
339
|
-
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
340
|
-
}
|
|
341
|
-
function getPrismaVersion(cwd) {
|
|
342
|
-
try {
|
|
343
|
-
const packageInfo = getPackageInfo(cwd);
|
|
344
|
-
const prismaVersion = packageInfo.dependencies?.prisma || packageInfo.devDependencies?.prisma || packageInfo.dependencies?.["@prisma/client"] || packageInfo.devDependencies?.["@prisma/client"];
|
|
345
|
-
if (!prismaVersion) return null;
|
|
346
|
-
const match = prismaVersion.match(/(\d+)/);
|
|
347
|
-
return match ? parseInt(match[1], 10) : null;
|
|
348
|
-
} catch {
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
//#endregion
|
|
354
|
-
//#region src/generators/prisma.ts
|
|
355
|
-
const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
356
|
-
const provider = adapter.options?.provider || "postgresql";
|
|
357
|
-
const tables = getAuthTables(options);
|
|
358
|
-
const filePath = file || "./prisma/schema.prisma";
|
|
359
|
-
const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
|
|
360
|
-
const getModelName = initGetModelName({
|
|
361
|
-
schema: getAuthTables(options),
|
|
362
|
-
usePlural: adapter.options?.adapterConfig?.usePlural
|
|
363
|
-
});
|
|
364
|
-
const getFieldName = initGetFieldName({
|
|
365
|
-
schema: getAuthTables(options),
|
|
366
|
-
usePlural: false
|
|
367
|
-
});
|
|
368
|
-
let schemaPrisma = "";
|
|
369
|
-
if (schemaPrismaExist) schemaPrisma = await fs$1.readFile(path.join(process.cwd(), filePath), "utf-8");
|
|
370
|
-
else schemaPrisma = getNewPrisma(provider, process.cwd());
|
|
371
|
-
const prismaVersion = getPrismaVersion(process.cwd());
|
|
372
|
-
if (prismaVersion && prismaVersion >= 7 && schemaPrismaExist) schemaPrisma = produceSchema(schemaPrisma, (builder) => {
|
|
373
|
-
const generator = builder.findByType("generator", { name: "client" });
|
|
374
|
-
if (generator && generator.properties) {
|
|
375
|
-
const providerProp = generator.properties.find((prop) => prop.type === "assignment" && prop.key === "provider");
|
|
376
|
-
if (providerProp && providerProp.value === "\"prisma-client-js\"") providerProp.value = "\"prisma-client\"";
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
const manyToManyRelations = /* @__PURE__ */ new Map();
|
|
380
|
-
for (const table in tables) {
|
|
381
|
-
const fields = tables[table]?.fields;
|
|
382
|
-
for (const field in fields) {
|
|
383
|
-
const attr = fields[field];
|
|
384
|
-
if (attr.references) {
|
|
385
|
-
const referencedOriginalModel = attr.references.model;
|
|
386
|
-
const referencedModelNameCap = capitalizeFirstLetter(getModelName(tables[referencedOriginalModel]?.modelName || referencedOriginalModel));
|
|
387
|
-
if (!manyToManyRelations.has(referencedModelNameCap)) manyToManyRelations.set(referencedModelNameCap, /* @__PURE__ */ new Set());
|
|
388
|
-
const currentModelNameCap = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
|
|
389
|
-
manyToManyRelations.get(referencedModelNameCap).add(currentModelNameCap);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
const indexedFields = /* @__PURE__ */ new Map();
|
|
394
|
-
for (const table in tables) {
|
|
395
|
-
const fields = tables[table]?.fields;
|
|
396
|
-
const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
|
|
397
|
-
indexedFields.set(modelName, []);
|
|
398
|
-
for (const field in fields) {
|
|
399
|
-
const attr = fields[field];
|
|
400
|
-
if (attr.index && !attr.unique) {
|
|
401
|
-
const fieldName = attr.fieldName || field;
|
|
402
|
-
indexedFields.get(modelName).push(fieldName);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const schema = produceSchema(schemaPrisma, (builder) => {
|
|
407
|
-
for (const table in tables) {
|
|
408
|
-
const originalTableName = table;
|
|
409
|
-
const customModelName = tables[table]?.modelName || table;
|
|
410
|
-
const modelName = capitalizeFirstLetter(getModelName(customModelName));
|
|
411
|
-
const fields = tables[table]?.fields;
|
|
412
|
-
function getType({ isBigint, isOptional, type }) {
|
|
413
|
-
if (type === "string") return isOptional ? "String?" : "String";
|
|
414
|
-
if (type === "number" && isBigint) return isOptional ? "BigInt?" : "BigInt";
|
|
415
|
-
if (type === "number") return isOptional ? "Int?" : "Int";
|
|
416
|
-
if (type === "boolean") return isOptional ? "Boolean?" : "Boolean";
|
|
417
|
-
if (type === "date") return isOptional ? "DateTime?" : "DateTime";
|
|
418
|
-
if (type === "json") {
|
|
419
|
-
if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
|
|
420
|
-
return isOptional ? "Json?" : "Json";
|
|
421
|
-
}
|
|
422
|
-
if (type === "string[]") {
|
|
423
|
-
if (provider === "sqlite" || provider === "mysql") return isOptional ? "String?" : "String";
|
|
424
|
-
return "String[]";
|
|
425
|
-
}
|
|
426
|
-
if (type === "number[]") {
|
|
427
|
-
if (provider === "sqlite" || provider === "mysql") return "String";
|
|
428
|
-
return "Int[]";
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
const prismaModel = builder.findByType("model", { name: modelName });
|
|
432
|
-
if (!prismaModel) if (provider === "mongodb") builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
|
|
433
|
-
else {
|
|
434
|
-
const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
|
|
435
|
-
const useUUIDs = options.advanced?.database?.generateId === "uuid";
|
|
436
|
-
if (useNumberId) builder.model(modelName).field("id", "Int").attribute("id").attribute("default(autoincrement())");
|
|
437
|
-
else if (useUUIDs && provider === "postgresql") builder.model(modelName).field("id", "String").attribute("id").attribute("default(dbgenerated(\"pg_catalog.gen_random_uuid()\"))").attribute("db.Uuid");
|
|
438
|
-
else builder.model(modelName).field("id", "String").attribute("id");
|
|
439
|
-
}
|
|
440
|
-
for (const field in fields) {
|
|
441
|
-
const attr = fields[field];
|
|
442
|
-
const fieldName = attr.fieldName || field;
|
|
443
|
-
if (prismaModel) {
|
|
444
|
-
if (builder.findByType("field", {
|
|
445
|
-
name: fieldName,
|
|
446
|
-
within: prismaModel.properties
|
|
447
|
-
})) continue;
|
|
448
|
-
}
|
|
449
|
-
const useUUIDs = options.advanced?.database?.generateId === "uuid";
|
|
450
|
-
const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
|
|
451
|
-
const fieldBuilder = builder.model(modelName).field(fieldName, field === "id" && useNumberId ? getType({
|
|
452
|
-
isBigint: false,
|
|
453
|
-
isOptional: false,
|
|
454
|
-
type: "number"
|
|
455
|
-
}) : getType({
|
|
456
|
-
isBigint: attr?.bigint || false,
|
|
457
|
-
isOptional: !attr?.required,
|
|
458
|
-
type: attr.references?.field === "id" ? useNumberId ? "number" : "string" : attr.type
|
|
459
|
-
}));
|
|
460
|
-
if (field === "id") {
|
|
461
|
-
fieldBuilder.attribute("id");
|
|
462
|
-
if (provider === "mongodb") fieldBuilder.attribute(`map("_id")`);
|
|
463
|
-
}
|
|
464
|
-
if (attr.unique) builder.model(modelName).blockAttribute(`unique([${fieldName}])`);
|
|
465
|
-
if (attr.defaultValue !== void 0) {
|
|
466
|
-
if (Array.isArray(attr.defaultValue)) {
|
|
467
|
-
if (attr.type === "json") {
|
|
468
|
-
if (Object.prototype.toString.call(attr.defaultValue[0]) === "[object Object]") {
|
|
469
|
-
fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
let jsonArray = [];
|
|
473
|
-
for (const value of attr.defaultValue) jsonArray.push(value);
|
|
474
|
-
fieldBuilder.attribute(`default("${JSON.stringify(jsonArray).replace(/"/g, "\\\"")}")`);
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (attr.defaultValue.length === 0) {
|
|
478
|
-
fieldBuilder.attribute(`default([])`);
|
|
479
|
-
continue;
|
|
480
|
-
} else if (typeof attr.defaultValue[0] === "string" && attr.type === "string[]") {
|
|
481
|
-
let valueArray = [];
|
|
482
|
-
for (const value of attr.defaultValue) valueArray.push(JSON.stringify(value));
|
|
483
|
-
fieldBuilder.attribute(`default([${valueArray}])`);
|
|
484
|
-
} else if (typeof attr.defaultValue[0] === "number") {
|
|
485
|
-
let valueArray = [];
|
|
486
|
-
for (const value of attr.defaultValue) valueArray.push(`${value}`);
|
|
487
|
-
fieldBuilder.attribute(`default([${valueArray}])`);
|
|
488
|
-
}
|
|
489
|
-
} else if (typeof attr.defaultValue === "object" && !Array.isArray(attr.defaultValue) && attr.defaultValue !== null) {
|
|
490
|
-
if (Object.entries(attr.defaultValue).length === 0) {
|
|
491
|
-
fieldBuilder.attribute(`default("{}")`);
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
fieldBuilder.attribute(`default("${JSON.stringify(attr.defaultValue).replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}")`);
|
|
495
|
-
}
|
|
496
|
-
if (field === "createdAt") fieldBuilder.attribute("default(now())");
|
|
497
|
-
else if (typeof attr.defaultValue === "string" && provider !== "mysql") fieldBuilder.attribute(`default("${attr.defaultValue}")`);
|
|
498
|
-
else if (typeof attr.defaultValue === "boolean" || typeof attr.defaultValue === "number") fieldBuilder.attribute(`default(${attr.defaultValue})`);
|
|
499
|
-
else if (typeof attr.defaultValue === "function") {}
|
|
500
|
-
}
|
|
501
|
-
if (field === "updatedAt" && attr.onUpdate) fieldBuilder.attribute("updatedAt");
|
|
502
|
-
else if (attr.onUpdate) {}
|
|
503
|
-
if (attr.references) {
|
|
504
|
-
if (useUUIDs && provider === "postgresql" && attr.references?.field === "id") builder.model(modelName).field(fieldName).attribute(`db.Uuid`);
|
|
505
|
-
const referencedOriginalModelName = getModelName(attr.references.model);
|
|
506
|
-
const referencedCustomModelName = tables[referencedOriginalModelName]?.modelName || referencedOriginalModelName;
|
|
507
|
-
let action = "Cascade";
|
|
508
|
-
if (attr.references.onDelete === "no action") action = "NoAction";
|
|
509
|
-
else if (attr.references.onDelete === "set null") action = "SetNull";
|
|
510
|
-
else if (attr.references.onDelete === "set default") action = "SetDefault";
|
|
511
|
-
else if (attr.references.onDelete === "restrict") action = "Restrict";
|
|
512
|
-
const relationField = `relation(fields: [${getFieldName({
|
|
513
|
-
model: originalTableName,
|
|
514
|
-
field: fieldName
|
|
515
|
-
})}], references: [${getFieldName({
|
|
516
|
-
model: attr.references.model,
|
|
517
|
-
field: attr.references.field
|
|
518
|
-
})}], onDelete: ${action})`;
|
|
519
|
-
builder.model(modelName).field(referencedCustomModelName.toLowerCase(), `${capitalizeFirstLetter(referencedCustomModelName)}${!attr.required ? "?" : ""}`).attribute(relationField);
|
|
520
|
-
}
|
|
521
|
-
if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") builder.model(modelName).field(fieldName).attribute("db.Text");
|
|
522
|
-
}
|
|
523
|
-
if (manyToManyRelations.has(modelName)) for (const relatedModel of manyToManyRelations.get(modelName)) {
|
|
524
|
-
const relatedTableName = Object.keys(tables).find((key) => capitalizeFirstLetter(tables[key]?.modelName || key) === relatedModel);
|
|
525
|
-
const relatedFields = relatedTableName ? tables[relatedTableName]?.fields : {};
|
|
526
|
-
const [_fieldKey, fkFieldAttr] = Object.entries(relatedFields || {}).find(([_fieldName, fieldAttr]) => fieldAttr.references && getModelName(fieldAttr.references.model) === getModelName(originalTableName)) || [];
|
|
527
|
-
const isUnique = fkFieldAttr?.unique === true;
|
|
528
|
-
const fieldName = isUnique || adapter.options?.usePlural === true ? `${relatedModel.toLowerCase()}` : `${relatedModel.toLowerCase()}s`;
|
|
529
|
-
if (!builder.findByType("field", {
|
|
530
|
-
name: fieldName,
|
|
531
|
-
within: prismaModel?.properties
|
|
532
|
-
})) builder.model(modelName).field(fieldName, `${relatedModel}${isUnique ? "?" : "[]"}`);
|
|
533
|
-
}
|
|
534
|
-
const indexedFieldsForModel = indexedFields.get(modelName);
|
|
535
|
-
if (indexedFieldsForModel && indexedFieldsForModel.length > 0) for (const fieldName of indexedFieldsForModel) {
|
|
536
|
-
if (prismaModel) {
|
|
537
|
-
if (prismaModel.properties.some((v) => v.type === "attribute" && v.name === "index" && JSON.stringify(v.args[0]?.value).includes(fieldName))) continue;
|
|
538
|
-
}
|
|
539
|
-
const field = Object.entries(fields).find(([key, attr]) => (attr.fieldName || key) === fieldName)?.[1];
|
|
540
|
-
let indexField = fieldName;
|
|
541
|
-
if (provider === "mysql" && field && field.type === "string") {
|
|
542
|
-
const useNumberId = options.advanced?.database?.useNumberId || options.advanced?.database?.generateId === "serial";
|
|
543
|
-
const useUUIDs = options.advanced?.database?.generateId === "uuid";
|
|
544
|
-
if (field.references?.field === "id" && (useNumberId || useUUIDs)) indexField = `${fieldName}`;
|
|
545
|
-
else indexField = `${fieldName}(length: 191)`;
|
|
546
|
-
}
|
|
547
|
-
builder.model(modelName).blockAttribute(`index([${indexField}])`);
|
|
548
|
-
}
|
|
549
|
-
const hasAttribute = builder.findByType("attribute", {
|
|
550
|
-
name: "map",
|
|
551
|
-
within: prismaModel?.properties
|
|
552
|
-
});
|
|
553
|
-
const hasChanged = customModelName !== originalTableName;
|
|
554
|
-
if (!hasAttribute) builder.model(modelName).blockAttribute("map", `${getModelName(hasChanged ? customModelName : originalTableName)}`);
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
const schemaChanged = schema.trim() !== schemaPrisma.trim();
|
|
558
|
-
return {
|
|
559
|
-
code: schemaChanged ? schema : "",
|
|
560
|
-
fileName: filePath,
|
|
561
|
-
overwrite: schemaPrismaExist && schemaChanged
|
|
562
|
-
};
|
|
563
|
-
};
|
|
564
|
-
const getNewPrisma = (provider, cwd) => {
|
|
565
|
-
const prismaVersion = getPrismaVersion(cwd);
|
|
566
|
-
return `generator client {
|
|
567
|
-
provider = "${prismaVersion && prismaVersion >= 7 ? "prisma-client" : "prisma-client-js"}"
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
datasource db {
|
|
571
|
-
provider = "${provider}"
|
|
572
|
-
url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
|
|
573
|
-
}`;
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
//#endregion
|
|
577
|
-
//#region src/generators/index.ts
|
|
578
|
-
const adapters = {
|
|
579
|
-
prisma: generatePrismaSchema,
|
|
580
|
-
drizzle: generateDrizzleSchema,
|
|
581
|
-
kysely: generateMigrations
|
|
582
|
-
};
|
|
583
|
-
const generateSchema = (opts) => {
|
|
584
|
-
const adapter = opts.adapter;
|
|
585
|
-
const generator = adapter.id in adapters ? adapters[adapter.id] : null;
|
|
586
|
-
if (generator) return generator(opts);
|
|
587
|
-
if (adapter.createSchema) return adapter.createSchema(opts.options, opts.file).then(({ code, path: fileName, overwrite }) => ({
|
|
588
|
-
code,
|
|
589
|
-
fileName,
|
|
590
|
-
overwrite
|
|
591
|
-
}));
|
|
592
|
-
console.error(`${adapter.id} is not supported. If it is a custom adapter, please request the maintainer to implement createSchema`);
|
|
593
|
-
process.exit(1);
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
//#endregion
|
|
597
33
|
//#region src/utils/add-cloudflare-modules.ts
|
|
598
34
|
const createModule = () => {
|
|
599
35
|
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
|
@@ -1514,9 +950,9 @@ async function generateAuthConfig({ format: format$1, current_user_config, spinn
|
|
|
1514
950
|
};
|
|
1515
951
|
const config_generation = {
|
|
1516
952
|
add_plugin: async (opts) => {
|
|
1517
|
-
|
|
953
|
+
const start_of_plugins = getGroupInfo(opts.config, common_indexes.START_OF_PLUGINS, {});
|
|
1518
954
|
if (!start_of_plugins) throw new Error("Couldn't find start of your plugins array in your auth config file.");
|
|
1519
|
-
|
|
955
|
+
const end_of_plugins = getGroupInfo(opts.config, common_indexes.END_OF_PLUGINS, { start_of_plugins: start_of_plugins.index });
|
|
1520
956
|
if (!end_of_plugins) throw new Error("Couldn't find end of your plugins array in your auth config file.");
|
|
1521
957
|
let new_content;
|
|
1522
958
|
if (opts.direction_in_plugins_array === "prepend") new_content = insertContent({
|
|
@@ -1570,7 +1006,7 @@ async function generateAuthConfig({ format: format$1, current_user_config, spinn
|
|
|
1570
1006
|
let database_code_str = "";
|
|
1571
1007
|
async function add_db({ db_code, dependencies, envs, imports, code_before_betterAuth }) {
|
|
1572
1008
|
if (code_before_betterAuth) {
|
|
1573
|
-
|
|
1009
|
+
const start_of_betterauth$1 = getGroupInfo(opts.config, common_indexes.START_OF_BETTERAUTH, {});
|
|
1574
1010
|
if (!start_of_betterauth$1) throw new Error("Couldn't find start of betterAuth() function.");
|
|
1575
1011
|
opts.config = insertContent({
|
|
1576
1012
|
line: start_of_betterauth$1.line - 1,
|
|
@@ -1718,7 +1154,7 @@ async function generateAuthConfig({ format: format$1, current_user_config, spinn
|
|
|
1718
1154
|
variables: [{ name: "MongoClient" }]
|
|
1719
1155
|
}]
|
|
1720
1156
|
});
|
|
1721
|
-
|
|
1157
|
+
const start_of_betterauth = getGroupInfo(opts.config, common_indexes.START_OF_BETTERAUTH, {});
|
|
1722
1158
|
if (!start_of_betterauth) throw new Error("Couldn't find start of betterAuth() function.");
|
|
1723
1159
|
let new_content;
|
|
1724
1160
|
new_content = insertContent({
|
|
@@ -1741,8 +1177,8 @@ async function generateAuthConfig({ format: format$1, current_user_config, spinn
|
|
|
1741
1177
|
}
|
|
1742
1178
|
};
|
|
1743
1179
|
let new_user_config = await format$1(current_user_config);
|
|
1744
|
-
|
|
1745
|
-
|
|
1180
|
+
const total_dependencies = [];
|
|
1181
|
+
const total_envs = [];
|
|
1746
1182
|
if (plugins.length !== 0) {
|
|
1747
1183
|
const imports = [];
|
|
1748
1184
|
for await (const plugin of plugins) {
|
|
@@ -2186,7 +1622,7 @@ const getDefaultAuthClientConfig = async ({ auth_config_path, framework, clientP
|
|
|
2186
1622
|
}
|
|
2187
1623
|
return result;
|
|
2188
1624
|
}
|
|
2189
|
-
|
|
1625
|
+
const imports = groupImportVariables();
|
|
2190
1626
|
let importString = "";
|
|
2191
1627
|
for (const import_ of imports) if (Array.isArray(import_.variables)) importString += `import { ${import_.variables.map((x) => `${x.asType ? "type " : ""}${x.name}${x.as ? ` as ${x.as}` : ""}`).join(", ")} } from "${import_.path}";\n`;
|
|
2192
1628
|
else importString += `import ${import_.variables.asType ? "type " : ""}${import_.variables.name}${import_.variables.as ? ` as ${import_.variables.as}` : ""} from "${import_.path}";\n`;
|
|
@@ -2584,7 +2020,7 @@ async function initAction(opts) {
|
|
|
2584
2020
|
authClientConfigPath = path.join(cwd, "auth-client.ts");
|
|
2585
2021
|
log.info(`Creating auth client config file: ${authClientConfigPath}`);
|
|
2586
2022
|
try {
|
|
2587
|
-
|
|
2023
|
+
const contents = await getDefaultAuthClientConfig({
|
|
2588
2024
|
auth_config_path: ("./" + path.join(config_path.replace(cwd, ""))).replace(".//", "./"),
|
|
2589
2025
|
clientPlugins: add_plugins.filter((x) => x.clientName).map((plugin) => {
|
|
2590
2026
|
let contents$1 = "";
|
|
@@ -2643,7 +2079,7 @@ async function initAction(opts) {
|
|
|
2643
2079
|
cancel(`✋ Operation cancelled.`);
|
|
2644
2080
|
process.exit(0);
|
|
2645
2081
|
}
|
|
2646
|
-
|
|
2082
|
+
const envs = [];
|
|
2647
2083
|
if (isMissingSecret) envs.push("BETTER_AUTH_SECRET");
|
|
2648
2084
|
if (isMissingUrl) envs.push("BETTER_AUTH_URL");
|
|
2649
2085
|
if (shouldAdd === "yes") {
|
|
@@ -2727,7 +2163,7 @@ async function getPackageManager() {
|
|
|
2727
2163
|
value: "npm",
|
|
2728
2164
|
hint: "not recommended"
|
|
2729
2165
|
});
|
|
2730
|
-
|
|
2166
|
+
const packageManager = await select({
|
|
2731
2167
|
message: "Choose a package manager",
|
|
2732
2168
|
options: packageManagerOptions
|
|
2733
2169
|
});
|
|
@@ -2913,69 +2349,100 @@ const login = new Command("login").description("Demo: Test device authorization
|
|
|
2913
2349
|
|
|
2914
2350
|
//#endregion
|
|
2915
2351
|
//#region src/commands/mcp.ts
|
|
2352
|
+
const REMOTE_MCP_URL = "https://mcp.chonkie.ai/better-auth/better-auth-builder/mcp";
|
|
2353
|
+
const LOCAL_MCP_COMMAND = "npx @better-auth/mcp";
|
|
2916
2354
|
async function mcpAction(options) {
|
|
2917
|
-
const
|
|
2918
|
-
const
|
|
2919
|
-
if (options.cursor) await handleCursorAction(
|
|
2920
|
-
else if (options.claudeCode) handleClaudeCodeAction(
|
|
2921
|
-
else if (options.openCode) handleOpenCodeAction(
|
|
2922
|
-
else if (options.manual) handleManualAction(
|
|
2923
|
-
else showAllOptions(
|
|
2355
|
+
const installLocal = !options.remoteOnly;
|
|
2356
|
+
const installRemote = !options.localOnly;
|
|
2357
|
+
if (options.cursor) await handleCursorAction(installLocal, installRemote);
|
|
2358
|
+
else if (options.claudeCode) handleClaudeCodeAction(installLocal, installRemote);
|
|
2359
|
+
else if (options.openCode) handleOpenCodeAction(installLocal, installRemote);
|
|
2360
|
+
else if (options.manual) handleManualAction(installLocal, installRemote);
|
|
2361
|
+
else showAllOptions();
|
|
2924
2362
|
}
|
|
2925
|
-
async function handleCursorAction(
|
|
2926
|
-
const mcpConfig = { url: mcpUrl };
|
|
2927
|
-
const encodedConfig = base64.encode(new TextEncoder().encode(JSON.stringify(mcpConfig)));
|
|
2928
|
-
const deeplinkUrl = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(mcpName)}&config=${encodedConfig}`;
|
|
2363
|
+
async function handleCursorAction(installLocal, installRemote) {
|
|
2929
2364
|
console.log(chalk.bold.blue("🚀 Adding Better Auth MCP to Cursor..."));
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2365
|
+
const platform = os$1.platform();
|
|
2366
|
+
let openCommand;
|
|
2367
|
+
switch (platform) {
|
|
2368
|
+
case "darwin":
|
|
2369
|
+
openCommand = "open";
|
|
2370
|
+
break;
|
|
2371
|
+
case "win32":
|
|
2372
|
+
openCommand = "start";
|
|
2373
|
+
break;
|
|
2374
|
+
case "linux":
|
|
2375
|
+
openCommand = "xdg-open";
|
|
2376
|
+
break;
|
|
2377
|
+
default: throw new Error(`Unsupported platform: ${platform}`);
|
|
2378
|
+
}
|
|
2379
|
+
const installed = [];
|
|
2380
|
+
if (installRemote) {
|
|
2381
|
+
const remoteConfig = { url: REMOTE_MCP_URL };
|
|
2382
|
+
const encodedRemote = base64.encode(new TextEncoder().encode(JSON.stringify(remoteConfig)));
|
|
2383
|
+
const remoteDeeplink = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("better-auth-docs")}&config=${encodedRemote}`;
|
|
2384
|
+
try {
|
|
2385
|
+
execSync(platform === "win32" ? `start "" "${remoteDeeplink}"` : `${openCommand} "${remoteDeeplink}"`, { stdio: "inherit" });
|
|
2386
|
+
installed.push("better-auth-docs (remote - documentation & search)");
|
|
2387
|
+
} catch {
|
|
2388
|
+
console.log(chalk.yellow("\n⚠ Could not automatically open Cursor for remote MCP."));
|
|
2944
2389
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2390
|
+
}
|
|
2391
|
+
if (installLocal) {
|
|
2392
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2393
|
+
const localConfig = { command: LOCAL_MCP_COMMAND };
|
|
2394
|
+
const encodedLocal = base64.encode(new TextEncoder().encode(JSON.stringify(localConfig)));
|
|
2395
|
+
const localDeeplink = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("better-auth")}&config=${encodedLocal}`;
|
|
2396
|
+
try {
|
|
2397
|
+
execSync(platform === "win32" ? `start "" "${localDeeplink}"` : `${openCommand} "${localDeeplink}"`, { stdio: "inherit" });
|
|
2398
|
+
installed.push("better-auth (local - setup & diagnostics)");
|
|
2399
|
+
} catch {
|
|
2400
|
+
console.log(chalk.yellow("\n⚠ Could not automatically open Cursor for local MCP."));
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (installed.length > 0) {
|
|
2404
|
+
console.log(chalk.green("\n✓ Cursor MCP servers installed:"));
|
|
2405
|
+
for (const name of installed) console.log(chalk.green(` • ${name}`));
|
|
2951
2406
|
}
|
|
2952
2407
|
console.log(chalk.bold.white("\n✨ Next Steps:"));
|
|
2953
|
-
console.log(chalk.gray("• The MCP
|
|
2408
|
+
console.log(chalk.gray("• The MCP servers will be added to your Cursor configuration"));
|
|
2954
2409
|
console.log(chalk.gray("• You can now use Better Auth features directly in Cursor"));
|
|
2410
|
+
console.log(chalk.gray("• Try: \"Set up Better Auth with Google login\" or \"Help me debug my auth\""));
|
|
2955
2411
|
}
|
|
2956
|
-
function handleClaudeCodeAction(
|
|
2412
|
+
function handleClaudeCodeAction(installLocal, installRemote) {
|
|
2957
2413
|
console.log(chalk.bold.blue("🤖 Adding Better Auth MCP to Claude Code..."));
|
|
2958
|
-
const
|
|
2959
|
-
|
|
2414
|
+
const commands = [];
|
|
2415
|
+
if (installRemote) commands.push(`claude mcp add --transport http better-auth-docs ${REMOTE_MCP_URL}`);
|
|
2416
|
+
if (installLocal) commands.push(`claude mcp add better-auth -- ${LOCAL_MCP_COMMAND}`);
|
|
2417
|
+
let anySucceeded = false;
|
|
2418
|
+
for (const command of commands) try {
|
|
2960
2419
|
execSync(command, { stdio: "inherit" });
|
|
2961
|
-
|
|
2420
|
+
anySucceeded = true;
|
|
2962
2421
|
} catch {
|
|
2963
2422
|
console.log(chalk.yellow("\n⚠ Could not automatically add to Claude Code. Please run this command manually:"));
|
|
2964
2423
|
console.log(chalk.cyan(command));
|
|
2965
2424
|
}
|
|
2425
|
+
if (anySucceeded) console.log(chalk.green("\n✓ Claude Code MCP configured!"));
|
|
2966
2426
|
console.log(chalk.bold.white("\n✨ Next Steps:"));
|
|
2967
|
-
console.log(chalk.gray("• The MCP
|
|
2427
|
+
console.log(chalk.gray("• The MCP servers will be added to your Claude Code configuration"));
|
|
2968
2428
|
console.log(chalk.gray("• You can now use Better Auth features directly in Claude Code"));
|
|
2969
2429
|
}
|
|
2970
|
-
function handleOpenCodeAction(
|
|
2430
|
+
function handleOpenCodeAction(installLocal, installRemote) {
|
|
2971
2431
|
console.log(chalk.bold.blue("🔧 Adding Better Auth MCP to Open Code..."));
|
|
2432
|
+
const mcpConfig = {};
|
|
2433
|
+
if (installRemote) mcpConfig["better-auth-docs"] = {
|
|
2434
|
+
type: "remote",
|
|
2435
|
+
url: REMOTE_MCP_URL,
|
|
2436
|
+
enabled: true
|
|
2437
|
+
};
|
|
2438
|
+
if (installLocal) mcpConfig["better-auth"] = {
|
|
2439
|
+
type: "stdio",
|
|
2440
|
+
command: LOCAL_MCP_COMMAND,
|
|
2441
|
+
enabled: true
|
|
2442
|
+
};
|
|
2972
2443
|
const openCodeConfig = {
|
|
2973
2444
|
$schema: "https://opencode.ai/config.json",
|
|
2974
|
-
mcp:
|
|
2975
|
-
type: "remote",
|
|
2976
|
-
url: mcpUrl,
|
|
2977
|
-
enabled: true
|
|
2978
|
-
} }
|
|
2445
|
+
mcp: mcpConfig
|
|
2979
2446
|
};
|
|
2980
2447
|
const configPath = path$1.join(process.cwd(), "opencode.json");
|
|
2981
2448
|
try {
|
|
@@ -2994,18 +2461,20 @@ function handleOpenCodeAction(mcpUrl) {
|
|
|
2994
2461
|
};
|
|
2995
2462
|
fs$2.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
|
|
2996
2463
|
console.log(chalk.green(`\n✓ Open Code configuration written to ${configPath}`));
|
|
2997
|
-
console.log(chalk.green("✓ Better Auth MCP added successfully!"));
|
|
2464
|
+
console.log(chalk.green("✓ Better Auth MCP servers added successfully!"));
|
|
2998
2465
|
} catch {
|
|
2999
2466
|
console.log(chalk.yellow("\n⚠ Could not automatically write opencode.json. Please add this configuration manually:"));
|
|
3000
2467
|
console.log(chalk.cyan(JSON.stringify(openCodeConfig, null, 2)));
|
|
3001
2468
|
}
|
|
3002
2469
|
console.log(chalk.bold.white("\n✨ Next Steps:"));
|
|
3003
|
-
console.log(chalk.gray("• Restart Open Code to load the new MCP
|
|
2470
|
+
console.log(chalk.gray("• Restart Open Code to load the new MCP servers"));
|
|
3004
2471
|
console.log(chalk.gray("• You can now use Better Auth features directly in Open Code"));
|
|
3005
2472
|
}
|
|
3006
|
-
function handleManualAction(
|
|
3007
|
-
console.log(chalk.bold.blue("📝
|
|
3008
|
-
const manualConfig = {
|
|
2473
|
+
function handleManualAction(installLocal, installRemote) {
|
|
2474
|
+
console.log(chalk.bold.blue("📝 Better Auth MCP Configuration..."));
|
|
2475
|
+
const manualConfig = {};
|
|
2476
|
+
if (installRemote) manualConfig["better-auth-docs"] = { url: REMOTE_MCP_URL };
|
|
2477
|
+
if (installLocal) manualConfig["better-auth"] = { command: LOCAL_MCP_COMMAND };
|
|
3009
2478
|
const configPath = path$1.join(process.cwd(), "mcp.json");
|
|
3010
2479
|
try {
|
|
3011
2480
|
let existingConfig = {};
|
|
@@ -3019,27 +2488,36 @@ function handleManualAction(mcpUrl, mcpName) {
|
|
|
3019
2488
|
};
|
|
3020
2489
|
fs$2.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
|
|
3021
2490
|
console.log(chalk.green(`\n✓ MCP configuration written to ${configPath}`));
|
|
3022
|
-
console.log(chalk.green("✓ Better Auth MCP added successfully!"));
|
|
2491
|
+
console.log(chalk.green("✓ Better Auth MCP servers added successfully!"));
|
|
3023
2492
|
} catch {
|
|
3024
2493
|
console.log(chalk.yellow("\n⚠ Could not automatically write mcp.json. Please add this configuration manually:"));
|
|
3025
2494
|
console.log(chalk.cyan(JSON.stringify(manualConfig, null, 2)));
|
|
3026
2495
|
}
|
|
3027
2496
|
console.log(chalk.bold.white("\n✨ Next Steps:"));
|
|
3028
|
-
console.log(chalk.gray("• Restart your MCP client to load the new
|
|
2497
|
+
console.log(chalk.gray("• Restart your MCP client to load the new servers"));
|
|
3029
2498
|
console.log(chalk.gray("• You can now use Better Auth features directly in your MCP client"));
|
|
3030
2499
|
}
|
|
3031
|
-
function showAllOptions(
|
|
3032
|
-
console.log(chalk.bold.blue("🔌 Better Auth MCP
|
|
2500
|
+
function showAllOptions() {
|
|
2501
|
+
console.log(chalk.bold.blue("🔌 Better Auth MCP Servers"));
|
|
3033
2502
|
console.log(chalk.gray("Choose your MCP client to get started:"));
|
|
3034
2503
|
console.log();
|
|
3035
|
-
console.log(chalk.bold.white("
|
|
2504
|
+
console.log(chalk.bold.white("MCP Clients:"));
|
|
3036
2505
|
console.log(chalk.cyan(" --cursor ") + chalk.gray("Add to Cursor"));
|
|
3037
2506
|
console.log(chalk.cyan(" --claude-code ") + chalk.gray("Add to Claude Code"));
|
|
3038
2507
|
console.log(chalk.cyan(" --open-code ") + chalk.gray("Add to Open Code"));
|
|
3039
2508
|
console.log(chalk.cyan(" --manual ") + chalk.gray("Manual configuration"));
|
|
3040
2509
|
console.log();
|
|
2510
|
+
console.log(chalk.bold.white("Server Selection:"));
|
|
2511
|
+
console.log(chalk.cyan(" --local-only ") + chalk.gray("Install only local MCP (setup & diagnostics)"));
|
|
2512
|
+
console.log(chalk.cyan(" --remote-only ") + chalk.gray("Install only remote MCP (documentation & search)"));
|
|
2513
|
+
console.log(chalk.gray(" (default: install both servers)"));
|
|
2514
|
+
console.log();
|
|
2515
|
+
console.log(chalk.bold.white("Servers:"));
|
|
2516
|
+
console.log(chalk.gray(" • ") + chalk.white("better-auth") + chalk.gray(" (local) - Setup auth, diagnose issues, validate config"));
|
|
2517
|
+
console.log(chalk.gray(" • ") + chalk.white("better-auth-docs") + chalk.gray(" (remote) - Search documentation, code examples"));
|
|
2518
|
+
console.log();
|
|
3041
2519
|
}
|
|
3042
|
-
const mcp = new Command("mcp").description("Add Better Auth MCP
|
|
2520
|
+
const mcp = new Command("mcp").description("Add Better Auth MCP servers to MCP Clients").option("--cursor", "Automatically open Cursor with the MCP configuration").option("--claude-code", "Show Claude Code MCP configuration command").option("--open-code", "Show Open Code MCP configuration").option("--manual", "Show manual MCP configuration for mcp.json").option("--local-only", "Install only local MCP server (setup & diagnostics)").option("--remote-only", "Install only remote MCP server (documentation & search)").action(mcpAction);
|
|
3043
2521
|
|
|
3044
2522
|
//#endregion
|
|
3045
2523
|
//#region src/commands/migrate.ts
|