@better-auth/cli 1.2.0-beta.9 → 1.2.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/index.mjs CHANGED
@@ -1,27 +1,2376 @@
1
1
  #!/usr/bin/env node
2
- import{Command as De}from"commander";import{Command as de}from"commander";import{z as M}from"zod";import{existsSync as ue}from"fs";import ge from"path";import he from"yocto-spinner";import E from"chalk";import ye from"prompts";import{logger as w}from"better-auth";import{getAdapter as be,getMigrations as we}from"better-auth/db";import{loadConfig as R}from"c12";import{logger as A}from"better-auth";import q from"path";import ce from"@babel/preset-typescript";import me from"@babel/preset-react";import L from"fs";import{BetterAuthError as pe}from"better-auth";function D(e){e["$env/dynamic/private"]=B(F()),e["$env/dynamic/public"]=B(F()),e["$env/static/private"]=B(z(ie("PUBLIC_",""))),e["$env/static/public"]=B(z(ne("PUBLIC_","")))}function B(e){return`data:text/javascript;charset=utf-8,${encodeURIComponent(e)}`}function z(e){return`
3
- ${Object.keys(e).filter(t=>se.test(t)&&!ae.has(t)).map(t=>`export const ${t} = ${JSON.stringify(e[t])};`).join(`
4
- `)}
2
+ import { Command } from 'commander';
3
+ import { z } from 'zod';
4
+ import fs$1, { existsSync } from 'fs';
5
+ import path from 'path';
6
+ import yoctoSpinner from 'yocto-spinner';
7
+ import chalk from 'chalk';
8
+ import prompts from 'prompts';
9
+ import { logger, BetterAuthError, capitalizeFirstLetter } from 'better-auth';
10
+ import { getAdapter, getMigrations, getAuthTables } from 'better-auth/db';
11
+ import { loadConfig } from 'c12';
12
+ import babelPresetTypescript from '@babel/preset-typescript';
13
+ import babelPresetReact from '@babel/preset-react';
14
+ import fs from 'fs-extra';
15
+ import fs$2 from 'fs/promises';
16
+ import { produceSchema } from '@mrleebo/prisma-ast';
17
+ import 'dotenv/config';
18
+ import Crypto from 'crypto';
19
+ import { parse } from 'dotenv';
20
+ import semver from 'semver';
21
+ import { format } from 'prettier';
22
+ import { intro, log, outro, confirm, isCancel, cancel, spinner, text, select, multiselect } from '@clack/prompts';
23
+ import { exec } from 'child_process';
24
+
25
+ function addSvelteKitEnvModules(aliases) {
26
+ aliases["$env/dynamic/private"] = createDataUriModule(
27
+ createDynamicEnvModule()
28
+ );
29
+ aliases["$env/dynamic/public"] = createDataUriModule(
30
+ createDynamicEnvModule()
31
+ );
32
+ aliases["$env/static/private"] = createDataUriModule(
33
+ createStaticEnvModule(filterPrivateEnv("PUBLIC_", ""))
34
+ );
35
+ aliases["$env/static/public"] = createDataUriModule(
36
+ createStaticEnvModule(filterPublicEnv("PUBLIC_", ""))
37
+ );
38
+ }
39
+ function createDataUriModule(module) {
40
+ return `data:text/javascript;charset=utf-8,${encodeURIComponent(module)}`;
41
+ }
42
+ function createStaticEnvModule(env) {
43
+ const declarations = Object.keys(env).filter((k) => validIdentifier.test(k) && !reserved.has(k)).map((k) => `export const ${k} = ${JSON.stringify(env[k])};`);
44
+ return `
45
+ ${declarations.join("\n")}
5
46
  // jiti dirty hack: .unknown
6
- `}function F(){return`
47
+ `;
48
+ }
49
+ function createDynamicEnvModule() {
50
+ return `
7
51
  export const env = process.env;
8
52
  // jiti dirty hack: .unknown
9
- `}function ie(e,o){return Object.fromEntries(Object.entries(process.env).filter(([t])=>t.startsWith(o)&&(e===""||!t.startsWith(e))))}function ne(e,o){return Object.fromEntries(Object.entries(process.env).filter(([t])=>t.startsWith(e)&&(o===""||!t.startsWith(o))))}var se=/^[a-zA-Z_$][a-zA-Z0-9_$]*$/,ae=new Set(["do","if","in","for","let","new","try","var","case","else","enum","eval","null","this","true","void","with","await","break","catch","class","const","false","super","throw","while","yield","delete","export","import","public","return","static","switch","typeof","default","extends","finally","package","private","continue","debugger","function","arguments","interface","protected","implements","instanceof"]);var h=["auth.ts","auth.tsx","auth.js","auth.jsx"];h=[...h,...h.map(e=>`lib/server/${e}`),...h.map(e=>`server/${e}`),...h.map(e=>`lib/${e}`),...h.map(e=>`utils/${e}`)];h=[...h,...h.map(e=>`src/${e}`),...h.map(e=>`app/${e}`)];function le(e){return e.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g,(o,t)=>t?"":o).replace(/,(?=\s*[}\]])/g,"")}function fe(e){let o=q.join(e,"tsconfig.json");if(!L.existsSync(o))return null;try{let t=L.readFileSync(o,"utf8"),s=le(t),r=JSON.parse(s),{paths:c={},baseUrl:i="."}=r.compilerOptions||{},d={},p=Object.entries(c);for(let[u,m]of p)for(let v of m){let x=q.join(e,i),$=u.slice(-1)==="*"?u.slice(0,-1):u,b=v.slice(-1)==="*"?v.slice(0,-1):v;d[$||""]=q.join(x,b)}return D(d),d}catch(t){throw console.error(t),new pe("Error parsing tsconfig.json")}}var G=e=>{let o=fe(e)||{};return{transformOptions:{babel:{presets:[[ce,{isTSX:!0,allExtensions:!0}],[me,{runtime:"automatic"}]]}},extensions:[".ts",".tsx",".js",".jsx"],alias:o}};async function N({cwd:e,configPath:o}){try{let t=null;if(o){let s=q.join(e,o),{config:r}=await R({configFile:s,dotenv:!0,jitiOptions:G(e)});!r.auth&&!r.default&&(A.error(`[#better-auth]: Couldn't read your auth config in ${s}. Make sure to default export your auth instance or to export as a variable named auth.`),process.exit(1)),t=r.auth?.options||r.default?.options||null}if(!t)for(let s of h)try{let{config:r}=await R({configFile:s,jitiOptions:G(e)});if(Object.keys(r).length>0){t=r.auth?.options||r.default?.options||null,t||(A.error("[#better-auth]: Couldn't read your auth config."),console.log(""),A.info("[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth."),process.exit(1));break}}catch(r){typeof r=="object"&&r&&"message"in r&&typeof r.message=="string"&&r.message.includes("This module cannot be imported from a Client Component module")&&(A.error("Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI."),process.exit(1)),A.error("[#better-auth]: Couldn't read your auth config.",r),process.exit(1)}return t}catch(t){typeof t=="object"&&t&&"message"in t&&typeof t.message=="string"&&t.message.includes("This module cannot be imported from a Client Component module")&&(A.error("Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI."),process.exit(1)),A.error("Couldn't read your auth config.",t),process.exit(1)}}async function xe(e){let o=M.object({cwd:M.string(),config:M.string().optional(),y:M.boolean().optional()}).parse(e),t=ge.resolve(o.cwd);ue(t)||(w.error(`The directory "${t}" does not exist.`),process.exit(1));let s=await N({cwd:t,configPath:o.config});if(!s){w.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.");return}let r=await be(s);r||(w.error("Invalid database configuration. Make sure you're not using adapters. Migrate command only works with built-in Kysely adapter."),process.exit(1)),r.id!=="kysely"&&(r.id==="prisma"&&(w.error("The migrate command only works with the built-in Kysely adapter. For Prisma, run `npx @better-auth/cli generate` to create the schema, then use Prisma\u2019s migrate or push to apply it."),process.exit(0)),r.id==="drizzle"&&(w.error("The migrate command only works with the built-in Kysely adapter. For Drizzle, run `npx @better-auth/cli generate` to create the schema, then use Drizzle\u2019s migrate or push to apply it."),process.exit(0)),w.error("Migrate command isn't supported for this adapter."),process.exit(1));let c=he({text:"preparing migration..."}).start(),{toBeAdded:i,toBeCreated:d,runMigrations:p}=await we(s);!i.length&&!d.length&&(c.stop(),w.info("\u{1F680} No migrations needed."),process.exit(0)),c.stop(),w.info("\u{1F511} The migration will affect the following:");for(let m of[...d,...i])console.log("->",E.magenta(Object.keys(m.fields).join(", ")),E.white("fields on"),E.yellow(`${m.table}`),E.white("table."));let u=o.y;u||(u=(await ye({type:"confirm",name:"migrate",message:"Are you sure you want to run these migrations?",initial:!1})).migrate),u||(w.info("Migration cancelled."),process.exit(0)),c?.start("migrating..."),await p(),c.stop(),w.info("\u{1F680} migration was completed successfully!"),process.exit(0)}var _=new de("migrate").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("-y, --y","automatically accept and run migrations without prompting",!1).action(xe);import{Command as Be}from"commander";import{z as T}from"zod";import{existsSync as O}from"fs";import y from"path";import{logger as j}from"better-auth";import qe from"yocto-spinner";import X from"prompts";import k from"fs/promises";import Q from"chalk";import{getAdapter as Ne}from"better-auth/db";import{logger as Pe}from"better-auth";import{getAuthTables as $e}from"better-auth/db";import{existsSync as ve}from"fs";function U(e){return e.replace(/[A-Z]/g,o=>`_${o.toLowerCase()}`)}var K=async({options:e,file:o,adapter:t})=>{let s=$e(e),r=o||"./auth-schema.ts",c=t.options?.provider,i=t.options?.usePlural,d=c!=="sqlite"?"timestamp, boolean":"",p=c==="mysql"?"int":"integer",u=Object.values(s).some(f=>Object.values(f.fields).some(C=>C.bigint)),x=`import { ${c}Table, ${c==="mysql"?"varchar, text":"text"}, ${p}${u?`, ${c!=="sqlite"?"bigint":""}`:""}, ${d} } from "drizzle-orm/${c}-core";
10
- `,$=ve(r);for(let f in s){let P=function(n,g){n=U(n);let re=g.type;return{string:{sqlite:`text('${n}')`,pg:`text('${n}')`,mysql:g.unique?`varchar('${n}', { length: 255 })`:g.references?`varchar('${n}', { length: 36 })`:`text('${n}')`},boolean:{sqlite:`integer('${n}', { mode: 'boolean' })`,pg:`boolean('${n}')`,mysql:`boolean('${n}')`},number:{sqlite:`integer('${n}')`,pg:g.bigint?`bigint('${n}', { mode: 'number' })`:`integer('${n}')`,mysql:g.bigint?`bigint('${n}', { mode: 'number' })`:`int('${n}')`},date:{sqlite:`integer('${n}', { mode: 'timestamp' })`,pg:`timestamp('${n}')`,mysql:`timestamp('${n}')`}}[re][c||"sqlite"]};var b=P;let C=i?`${s[f].modelName}s`:s[f].modelName,S=s[f].fields,l=c==="mysql"?'varchar("id", { length: 36 }).primaryKey()':'text("id").primaryKey()',a=`export const ${C} = ${c}Table("${U(C)}", {
11
- id: ${l},
12
- ${Object.keys(S).map(n=>{let g=S[n];return`${n}: ${P(n,g)}${g.required?".notNull()":""}${g.unique?".unique()":""}${g.references?`.references(()=> ${i?`${g.references.model}s`:g.references.model}.${g.references.field}, { onDelete: 'cascade' })`:""}`}).join(`,
13
- `)}
14
- });`;x+=`
15
- ${a}
16
- `}return{code:x,fileName:r,overwrite:$}};import{getAuthTables as Se}from"better-auth/db";import{produceSchema as je}from"@mrleebo/prisma-ast";import{existsSync as Ce}from"fs";import J from"path";import Ae from"fs/promises";import{capitalizeFirstLetter as I}from"better-auth";var W=async({adapter:e,options:o,file:t})=>{let s=e.options?.provider||"postgresql",r=Se(o),c=t||"./prisma/schema.prisma",i=Ce(J.join(process.cwd(),c)),d="";i?d=await Ae.readFile(J.join(process.cwd(),c),"utf-8"):d=Te(s);let p=new Map;for(let m in r){let v=r[m]?.fields;for(let x in v){let $=v[x];if($.references){let b=I($.references.model);p.has(b)||p.set(b,new Set),p.get(b).add(I(m))}}}let u=je(d,m=>{for(let x in r){let C=function(l,a,n){if(l==="string")return a?"String?":"String";if(l==="number"&&n)return a?"BigInt?":"BigInt";if(l==="number")return a?"Int?":"Int";if(l==="boolean")return a?"Boolean?":"Boolean";if(l==="date")return a?"DateTime?":"DateTime";if(l==="string[]")return"String[]";if(l==="number[]")return"Int[]"};var v=C;let $=r[x]?.fields,b=r[x]?.modelName,f=I(b||""),S=m.findByType("model",{name:f});S||(s==="mongodb"?m.model(f).field("id","String").attribute("id").attribute('map("_id")'):m.model(f).field("id","String").attribute("id"));for(let l in $){let a=$[l];S&&m.findByType("field",{name:l,within:S.properties})||(m.model(f).field(l,C(a.type,!a?.required,a?.bigint||!1)),a.unique&&m.model(f).blockAttribute(`unique([${l}])`),a.references&&m.model(f).field(`${a.references.model.toLowerCase()}`,I(a.references.model)).attribute(`relation(fields: [${l}], references: [${a.references.field}], onDelete: Cascade)`),!a.unique&&!a.references&&s==="mysql"&&a.type==="string"&&m.model(f).field(l).attribute("db.Text"))}if(p.has(f))for(let l of p.get(f)){let a=`${l.toLowerCase()}s`;m.findByType("field",{name:a,within:S?.properties})||m.model(f).field(a,`${l}[]`)}let P=m.findByType("attribute",{name:"map",within:S?.properties});b!==f&&!P&&m.model(f).blockAttribute("map",b)}});return{code:u.trim()===d.trim()?"":u,fileName:c}},Te=e=>`generator client {
53
+ `;
54
+ }
55
+ function filterPrivateEnv(publicPrefix, privatePrefix) {
56
+ return Object.fromEntries(
57
+ Object.entries(process.env).filter(
58
+ ([k]) => k.startsWith(privatePrefix) && (!k.startsWith(publicPrefix))
59
+ )
60
+ );
61
+ }
62
+ function filterPublicEnv(publicPrefix, privatePrefix) {
63
+ return Object.fromEntries(
64
+ Object.entries(process.env).filter(
65
+ ([k]) => k.startsWith(publicPrefix) && (privatePrefix === "")
66
+ )
67
+ );
68
+ }
69
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
70
+ const reserved = /* @__PURE__ */ new Set([
71
+ "do",
72
+ "if",
73
+ "in",
74
+ "for",
75
+ "let",
76
+ "new",
77
+ "try",
78
+ "var",
79
+ "case",
80
+ "else",
81
+ "enum",
82
+ "eval",
83
+ "null",
84
+ "this",
85
+ "true",
86
+ "void",
87
+ "with",
88
+ "await",
89
+ "break",
90
+ "catch",
91
+ "class",
92
+ "const",
93
+ "false",
94
+ "super",
95
+ "throw",
96
+ "while",
97
+ "yield",
98
+ "delete",
99
+ "export",
100
+ "import",
101
+ "public",
102
+ "return",
103
+ "static",
104
+ "switch",
105
+ "typeof",
106
+ "default",
107
+ "extends",
108
+ "finally",
109
+ "package",
110
+ "private",
111
+ "continue",
112
+ "debugger",
113
+ "function",
114
+ "arguments",
115
+ "interface",
116
+ "protected",
117
+ "implements",
118
+ "instanceof"
119
+ ]);
120
+
121
+ function stripJsonComments(jsonString) {
122
+ return jsonString.replace(
123
+ /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g,
124
+ (m, g) => g ? "" : m
125
+ ).replace(/,(?=\s*[}\]])/g, "");
126
+ }
127
+ function getTsconfigInfo(cwd) {
128
+ const packageJsonPath = cwd ? path.join(cwd, "tsconfig.json") : path.join("tsconfig.json");
129
+ try {
130
+ const text = fs.readFileSync(packageJsonPath, "utf-8");
131
+ return JSON.parse(stripJsonComments(text));
132
+ } catch (error) {
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ let possiblePaths = [
138
+ "auth.ts",
139
+ "auth.tsx",
140
+ "auth.js",
141
+ "auth.jsx",
142
+ "auth.server.js",
143
+ "auth.server.ts"
144
+ ];
145
+ possiblePaths = [
146
+ ...possiblePaths,
147
+ ...possiblePaths.map((it) => `lib/server/${it}`),
148
+ ...possiblePaths.map((it) => `server/${it}`),
149
+ ...possiblePaths.map((it) => `lib/${it}`),
150
+ ...possiblePaths.map((it) => `utils/${it}`)
151
+ ];
152
+ possiblePaths = [
153
+ ...possiblePaths,
154
+ ...possiblePaths.map((it) => `src/${it}`),
155
+ ...possiblePaths.map((it) => `app/${it}`)
156
+ ];
157
+ function getPathAliases(cwd) {
158
+ const tsConfigPath = path.join(cwd, "tsconfig.json");
159
+ if (!fs$1.existsSync(tsConfigPath)) {
160
+ return null;
161
+ }
162
+ try {
163
+ const tsConfig = getTsconfigInfo(cwd);
164
+ const { paths = {}, baseUrl = "." } = tsConfig.compilerOptions || {};
165
+ const result = {};
166
+ const obj = Object.entries(paths);
167
+ for (const [alias, aliasPaths] of obj) {
168
+ for (const aliasedPath of aliasPaths) {
169
+ const resolvedBaseUrl = path.join(cwd, baseUrl);
170
+ const finalAlias = alias.slice(-1) === "*" ? alias.slice(0, -1) : alias;
171
+ const finalAliasedPath = aliasedPath.slice(-1) === "*" ? aliasedPath.slice(0, -1) : aliasedPath;
172
+ result[finalAlias || ""] = path.join(resolvedBaseUrl, finalAliasedPath);
173
+ }
174
+ }
175
+ addSvelteKitEnvModules(result);
176
+ return result;
177
+ } catch (error) {
178
+ console.error(error);
179
+ throw new BetterAuthError("Error parsing tsconfig.json");
180
+ }
181
+ }
182
+ const jitiOptions = (cwd) => {
183
+ const alias = getPathAliases(cwd) || {};
184
+ return {
185
+ transformOptions: {
186
+ babel: {
187
+ presets: [
188
+ [
189
+ babelPresetTypescript,
190
+ {
191
+ isTSX: true,
192
+ allExtensions: true
193
+ }
194
+ ],
195
+ [babelPresetReact, { runtime: "automatic" }]
196
+ ]
197
+ }
198
+ },
199
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
200
+ alias
201
+ };
202
+ };
203
+ async function getConfig({
204
+ cwd,
205
+ configPath,
206
+ shouldThrowOnError = false
207
+ }) {
208
+ try {
209
+ let configFile = null;
210
+ if (configPath) {
211
+ let resolvedPath = path.join(cwd, configPath);
212
+ if (existsSync(configPath)) resolvedPath = configPath;
213
+ const { config } = await loadConfig({
214
+ configFile: resolvedPath,
215
+ dotenv: true,
216
+ jitiOptions: jitiOptions(cwd)
217
+ });
218
+ if (!config.auth && !config.default) {
219
+ if (shouldThrowOnError) {
220
+ throw new Error(
221
+ `Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`
222
+ );
223
+ }
224
+ logger.error(
225
+ `[#better-auth]: Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`
226
+ );
227
+ process.exit(1);
228
+ }
229
+ configFile = config.auth?.options || config.default?.options || null;
230
+ }
231
+ if (!configFile) {
232
+ for (const possiblePath of possiblePaths) {
233
+ try {
234
+ const { config } = await loadConfig({
235
+ configFile: possiblePath,
236
+ jitiOptions: jitiOptions(cwd)
237
+ });
238
+ const hasConfig = Object.keys(config).length > 0;
239
+ if (hasConfig) {
240
+ configFile = config.auth?.options || config.default?.options || null;
241
+ if (!configFile) {
242
+ if (shouldThrowOnError) {
243
+ throw new Error(
244
+ "Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth."
245
+ );
246
+ }
247
+ logger.error("[#better-auth]: Couldn't read your auth config.");
248
+ console.log("");
249
+ logger.info(
250
+ "[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth."
251
+ );
252
+ process.exit(1);
253
+ }
254
+ break;
255
+ }
256
+ } catch (e) {
257
+ if (typeof e === "object" && e && "message" in e && typeof e.message === "string" && e.message.includes(
258
+ "This module cannot be imported from a Client Component module"
259
+ )) {
260
+ if (shouldThrowOnError) {
261
+ throw new Error(
262
+ `Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`
263
+ );
264
+ }
265
+ logger.error(
266
+ `Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`
267
+ );
268
+ process.exit(1);
269
+ }
270
+ if (shouldThrowOnError) {
271
+ throw e;
272
+ }
273
+ logger.error("[#better-auth]: Couldn't read your auth config.", e);
274
+ process.exit(1);
275
+ }
276
+ }
277
+ }
278
+ return configFile;
279
+ } catch (e) {
280
+ if (typeof e === "object" && e && "message" in e && typeof e.message === "string" && e.message.includes(
281
+ "This module cannot be imported from a Client Component module"
282
+ )) {
283
+ if (shouldThrowOnError) {
284
+ throw new Error(
285
+ `Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`
286
+ );
287
+ }
288
+ logger.error(
289
+ `Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`
290
+ );
291
+ process.exit(1);
292
+ }
293
+ if (shouldThrowOnError) {
294
+ throw e;
295
+ }
296
+ logger.error("Couldn't read your auth config.", e);
297
+ process.exit(1);
298
+ }
299
+ }
300
+
301
+ async function migrateAction(opts) {
302
+ const options = z.object({
303
+ cwd: z.string(),
304
+ config: z.string().optional(),
305
+ y: z.boolean().optional()
306
+ }).parse(opts);
307
+ const cwd = path.resolve(options.cwd);
308
+ if (!existsSync(cwd)) {
309
+ logger.error(`The directory "${cwd}" does not exist.`);
310
+ process.exit(1);
311
+ }
312
+ const config = await getConfig({
313
+ cwd,
314
+ configPath: options.config
315
+ });
316
+ if (!config) {
317
+ logger.error(
318
+ "No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag."
319
+ );
320
+ return;
321
+ }
322
+ const db = await getAdapter(config);
323
+ if (!db) {
324
+ logger.error(
325
+ "Invalid database configuration. Make sure you're not using adapters. Migrate command only works with built-in Kysely adapter."
326
+ );
327
+ process.exit(1);
328
+ }
329
+ if (db.id !== "kysely") {
330
+ if (db.id === "prisma") {
331
+ logger.error(
332
+ "The migrate command only works with the built-in Kysely adapter. For Prisma, run `npx @better-auth/cli generate` to create the schema, then use Prisma\u2019s migrate or push to apply it."
333
+ );
334
+ process.exit(0);
335
+ }
336
+ if (db.id === "drizzle") {
337
+ logger.error(
338
+ "The migrate command only works with the built-in Kysely adapter. For Drizzle, run `npx @better-auth/cli generate` to create the schema, then use Drizzle\u2019s migrate or push to apply it."
339
+ );
340
+ process.exit(0);
341
+ }
342
+ logger.error("Migrate command isn't supported for this adapter.");
343
+ process.exit(1);
344
+ }
345
+ const spinner = yoctoSpinner({ text: "preparing migration..." }).start();
346
+ const { toBeAdded, toBeCreated, runMigrations } = await getMigrations(config);
347
+ if (!toBeAdded.length && !toBeCreated.length) {
348
+ spinner.stop();
349
+ logger.info("\u{1F680} No migrations needed.");
350
+ process.exit(0);
351
+ }
352
+ spinner.stop();
353
+ logger.info(`\u{1F511} The migration will affect the following:`);
354
+ for (const table of [...toBeCreated, ...toBeAdded]) {
355
+ console.log(
356
+ "->",
357
+ chalk.magenta(Object.keys(table.fields).join(", ")),
358
+ chalk.white("fields on"),
359
+ chalk.yellow(`${table.table}`),
360
+ chalk.white("table.")
361
+ );
362
+ }
363
+ let migrate2 = options.y;
364
+ if (!migrate2) {
365
+ const response = await prompts({
366
+ type: "confirm",
367
+ name: "migrate",
368
+ message: "Are you sure you want to run these migrations?",
369
+ initial: false
370
+ });
371
+ migrate2 = response.migrate;
372
+ }
373
+ if (!migrate2) {
374
+ logger.info("Migration cancelled.");
375
+ process.exit(0);
376
+ }
377
+ spinner?.start("migrating...");
378
+ await runMigrations();
379
+ spinner.stop();
380
+ logger.info("\u{1F680} migration was completed successfully!");
381
+ process.exit(0);
382
+ }
383
+ const migrate = new Command("migrate").option(
384
+ "-c, --cwd <cwd>",
385
+ "the working directory. defaults to the current directory.",
386
+ process.cwd()
387
+ ).option(
388
+ "--config <config>",
389
+ "the path to the configuration file. defaults to the first configuration file found."
390
+ ).option(
391
+ "-y, --y",
392
+ "automatically accept and run migrations without prompting",
393
+ false
394
+ ).action(migrateAction);
395
+
396
+ function convertToSnakeCase(str) {
397
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
398
+ }
399
+ const generateDrizzleSchema = async ({
400
+ options,
401
+ file,
402
+ adapter
403
+ }) => {
404
+ const tables = getAuthTables(options);
405
+ const filePath = file || "./auth-schema.ts";
406
+ const databaseType = adapter.options?.provider;
407
+ const usePlural = adapter.options?.usePlural;
408
+ const timestampAndBoolean = databaseType !== "sqlite" ? "timestamp, boolean" : "";
409
+ const int = databaseType === "mysql" ? "int" : "integer";
410
+ const hasBigint = Object.values(tables).some(
411
+ (table) => Object.values(table.fields).some((field) => field.bigint)
412
+ );
413
+ const bigint = databaseType !== "sqlite" ? "bigint" : "";
414
+ const text = databaseType === "mysql" ? "varchar, text" : "text";
415
+ let code = `import { ${databaseType}Table, ${text}, ${int}${hasBigint ? `, ${bigint}` : ""}, ${timestampAndBoolean} } from "drizzle-orm/${databaseType}-core";
416
+ `;
417
+ const fileExist = existsSync(filePath);
418
+ for (const table in tables) {
419
+ let getType = function(name, field) {
420
+ name = convertToSnakeCase(name);
421
+ const type = field.type;
422
+ const typeMap = {
423
+ string: {
424
+ sqlite: `text('${name}')`,
425
+ pg: `text('${name}')`,
426
+ mysql: field.unique ? `varchar('${name}', { length: 255 })` : field.references ? `varchar('${name}', { length: 36 })` : `text('${name}')`
427
+ },
428
+ boolean: {
429
+ sqlite: `integer('${name}', { mode: 'boolean' })`,
430
+ pg: `boolean('${name}')`,
431
+ mysql: `boolean('${name}')`
432
+ },
433
+ number: {
434
+ sqlite: `integer('${name}')`,
435
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
436
+ mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
437
+ },
438
+ date: {
439
+ sqlite: `integer('${name}', { mode: 'timestamp' })`,
440
+ pg: `timestamp('${name}')`,
441
+ mysql: `timestamp('${name}')`
442
+ }
443
+ };
444
+ return typeMap[type][databaseType || "sqlite"];
445
+ };
446
+ const modelName = usePlural ? `${tables[table].modelName}s` : tables[table].modelName;
447
+ const fields = tables[table].fields;
448
+ const id = databaseType === "mysql" ? `varchar("id", { length: 36 }).primaryKey()` : `text("id").primaryKey()`;
449
+ const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(
450
+ modelName
451
+ )}", {
452
+ id: ${id},
453
+ ${Object.keys(fields).map((field) => {
454
+ const attr = fields[field];
455
+ return `${field}: ${getType(field, attr)}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${usePlural ? `${attr.references.model}s` : attr.references.model}.${attr.references.field}, { onDelete: 'cascade' })` : ""}`;
456
+ }).join(",\n ")}
457
+ });`;
458
+ code += `
459
+ ${schema}
460
+ `;
461
+ }
462
+ return {
463
+ code,
464
+ fileName: filePath,
465
+ overwrite: fileExist
466
+ };
467
+ };
468
+
469
+ const generatePrismaSchema = async ({
470
+ adapter,
471
+ options,
472
+ file
473
+ }) => {
474
+ const provider = adapter.options?.provider || "postgresql";
475
+ const tables = getAuthTables(options);
476
+ const filePath = file || "./prisma/schema.prisma";
477
+ const schemaPrismaExist = existsSync(path.join(process.cwd(), filePath));
478
+ let schemaPrisma = "";
479
+ if (schemaPrismaExist) {
480
+ schemaPrisma = await fs$2.readFile(
481
+ path.join(process.cwd(), filePath),
482
+ "utf-8"
483
+ );
484
+ } else {
485
+ schemaPrisma = getNewPrisma(provider);
486
+ }
487
+ const manyToManyRelations = /* @__PURE__ */ new Map();
488
+ for (const table in tables) {
489
+ const fields = tables[table]?.fields;
490
+ for (const field in fields) {
491
+ const attr = fields[field];
492
+ if (attr.references) {
493
+ const referencedModel = capitalizeFirstLetter(attr.references.model);
494
+ if (!manyToManyRelations.has(referencedModel)) {
495
+ manyToManyRelations.set(referencedModel, /* @__PURE__ */ new Set());
496
+ }
497
+ manyToManyRelations.get(referencedModel).add(capitalizeFirstLetter(table));
498
+ }
499
+ }
500
+ }
501
+ const schema = produceSchema(schemaPrisma, (builder) => {
502
+ for (const table in tables) {
503
+ let getType = function(type, isOptional, isBigint) {
504
+ if (type === "string") {
505
+ return isOptional ? "String?" : "String";
506
+ }
507
+ if (type === "number" && isBigint) {
508
+ return isOptional ? "BigInt?" : "BigInt";
509
+ }
510
+ if (type === "number") {
511
+ return isOptional ? "Int?" : "Int";
512
+ }
513
+ if (type === "boolean") {
514
+ return isOptional ? "Boolean?" : "Boolean";
515
+ }
516
+ if (type === "date") {
517
+ return isOptional ? "DateTime?" : "DateTime";
518
+ }
519
+ if (type === "string[]") {
520
+ return isOptional ? "String[]" : "String[]";
521
+ }
522
+ if (type === "number[]") {
523
+ return isOptional ? "Int[]" : "Int[]";
524
+ }
525
+ };
526
+ const fields = tables[table]?.fields;
527
+ const originalTable = tables[table]?.modelName;
528
+ const modelName = capitalizeFirstLetter(originalTable || "");
529
+ const prismaModel = builder.findByType("model", {
530
+ name: modelName
531
+ });
532
+ if (!prismaModel) {
533
+ if (provider === "mongodb") {
534
+ builder.model(modelName).field("id", "String").attribute("id").attribute(`map("_id")`);
535
+ } else {
536
+ builder.model(modelName).field("id", "String").attribute("id");
537
+ }
538
+ }
539
+ for (const field in fields) {
540
+ const attr = fields[field];
541
+ if (prismaModel) {
542
+ const isAlreadyExist = builder.findByType("field", {
543
+ name: field,
544
+ within: prismaModel.properties
545
+ });
546
+ if (isAlreadyExist) {
547
+ continue;
548
+ }
549
+ }
550
+ builder.model(modelName).field(
551
+ field,
552
+ getType(attr.type, !attr?.required, attr?.bigint || false)
553
+ );
554
+ if (attr.unique) {
555
+ builder.model(modelName).blockAttribute(`unique([${field}])`);
556
+ }
557
+ if (attr.references) {
558
+ builder.model(modelName).field(
559
+ `${attr.references.model.toLowerCase()}`,
560
+ capitalizeFirstLetter(attr.references.model)
561
+ ).attribute(
562
+ `relation(fields: [${field}], references: [${attr.references.field}], onDelete: Cascade)`
563
+ );
564
+ }
565
+ if (!attr.unique && !attr.references && provider === "mysql" && attr.type === "string") {
566
+ builder.model(modelName).field(field).attribute("db.Text");
567
+ }
568
+ }
569
+ if (manyToManyRelations.has(modelName)) {
570
+ for (const relatedModel of manyToManyRelations.get(modelName)) {
571
+ const fieldName = `${relatedModel.toLowerCase()}s`;
572
+ const existingField = builder.findByType("field", {
573
+ name: fieldName,
574
+ within: prismaModel?.properties
575
+ });
576
+ if (!existingField) {
577
+ builder.model(modelName).field(fieldName, `${relatedModel}[]`);
578
+ }
579
+ }
580
+ }
581
+ const hasAttribute = builder.findByType("attribute", {
582
+ name: "map",
583
+ within: prismaModel?.properties
584
+ });
585
+ if (originalTable !== modelName && !hasAttribute) {
586
+ builder.model(modelName).blockAttribute("map", originalTable);
587
+ }
588
+ }
589
+ });
590
+ return {
591
+ code: schema.trim() === schemaPrisma.trim() ? "" : schema,
592
+ fileName: filePath
593
+ };
594
+ };
595
+ const getNewPrisma = (provider) => `generator client {
17
596
  provider = "prisma-client-js"
18
597
  }
19
598
 
20
599
  datasource db {
21
- provider = "${e}"
22
- url = ${e==="sqlite"?'"file:./dev.db"':'env("DATABASE_URL")'}
23
- }`;import{getMigrations as ke}from"better-auth/db";var Y=async({options:e,file:o})=>{let{compileMigrations:t}=await ke(e);return{code:await t(),fileName:o||`./better-auth_migrations/${new Date().toISOString().replace(/:/g,"-")}.sql`}};var Z={prisma:W,drizzle:K,kysely:Y},H=e=>{let o=e.adapter,t=o.id in Z?Z[o.id]:null;return t||(Pe.error(`${o.id} is not supported.`),process.exit(1)),t(e)};async function Me(e){let o=T.object({cwd:T.string(),config:T.string().optional(),output:T.string().optional(),y:T.boolean().optional()}).parse(e),t=y.resolve(o.cwd);O(t)||(j.error(`The directory "${t}" does not exist.`),process.exit(1));let s=await N({cwd:t,configPath:o.config});if(!s){j.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.");return}let r=await Ne(s).catch(p=>{j.error(p.message),process.exit(1)}),c=qe({text:"preparing schema..."}).start(),i=await H({adapter:r,file:o.output,options:s});if(c.stop(),i.code||(j.info("Your schema is already up to date."),process.exit(0)),i.append||i.overwrite){let p=o.y;p||(p=(await X({type:"confirm",name:"confirm",message:`The file ${i.fileName} already exists. Do you want to ${Q.yellow(`${i.overwrite?"overwrite":"append"}`)} the schema to the file?`})).confirm),p?(O(y.join(t,i.fileName))||await k.mkdir(y.dirname(y.join(t,i.fileName)),{recursive:!0}),i.overwrite?await k.writeFile(y.join(t,i.fileName),i.code):await k.appendFile(y.join(t,i.fileName),i.code),j.success(`\u{1F680} Schema was ${i.overwrite?"overwritten":"appended"} successfully!`),process.exit(0)):(j.error("Schema generation aborted."),process.exit(1))}let d=o.y;d||(d=(await X({type:"confirm",name:"confirm",message:`Do you want to generate the schema to ${Q.yellow(i.fileName)}?`})).confirm),d||(j.error("Schema generation aborted."),process.exit(1)),o.output||O(y.dirname(y.join(t,i.fileName)))||await k.mkdir(y.dirname(y.join(t,i.fileName)),{recursive:!0}),await k.writeFile(o.output||y.join(t,i.fileName),i.code),j.success("\u{1F680} Schema was generated successfully!"),process.exit(0)}var V=new Be("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, --y","automatically answer yes to all prompts",!1).action(Me);import"dotenv/config";import{logger as Ee}from"better-auth";import ee from"chalk";import{Command as Ie}from"commander";import Oe from"crypto";var te=new Ie("secret").action(()=>{let e=Oe.randomBytes(32).toString("hex");Ee.info(`
600
+ provider = "${provider}"
601
+ url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
602
+ }`;
603
+
604
+ const generateMigrations = async ({
605
+ options,
606
+ file
607
+ }) => {
608
+ const { compileMigrations } = await getMigrations(options);
609
+ const migrations = await compileMigrations();
610
+ return {
611
+ code: migrations,
612
+ fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
613
+ };
614
+ };
615
+
616
+ const adapters = {
617
+ prisma: generatePrismaSchema,
618
+ drizzle: generateDrizzleSchema,
619
+ kysely: generateMigrations
620
+ };
621
+ const getGenerator = (opts) => {
622
+ const adapter = opts.adapter;
623
+ const generator = adapter.id in adapters ? adapters[adapter.id] : null;
624
+ if (!generator) {
625
+ logger.error(`${adapter.id} is not supported.`);
626
+ process.exit(1);
627
+ }
628
+ return generator(opts);
629
+ };
630
+
631
+ async function generateAction(opts) {
632
+ const options = z.object({
633
+ cwd: z.string(),
634
+ config: z.string().optional(),
635
+ output: z.string().optional(),
636
+ y: z.boolean().optional()
637
+ }).parse(opts);
638
+ const cwd = path.resolve(options.cwd);
639
+ if (!existsSync(cwd)) {
640
+ logger.error(`The directory "${cwd}" does not exist.`);
641
+ process.exit(1);
642
+ }
643
+ const config = await getConfig({
644
+ cwd,
645
+ configPath: options.config
646
+ });
647
+ if (!config) {
648
+ logger.error(
649
+ "No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag."
650
+ );
651
+ return;
652
+ }
653
+ const adapter = await getAdapter(config).catch((e) => {
654
+ logger.error(e.message);
655
+ process.exit(1);
656
+ });
657
+ const spinner = yoctoSpinner({ text: "preparing schema..." }).start();
658
+ const schema = await getGenerator({
659
+ adapter,
660
+ file: options.output,
661
+ options: config
662
+ });
663
+ spinner.stop();
664
+ if (!schema.code) {
665
+ logger.info("Your schema is already up to date.");
666
+ process.exit(0);
667
+ }
668
+ if (schema.append || schema.overwrite) {
669
+ let confirm2 = options.y;
670
+ if (!confirm2) {
671
+ const response = await prompts({
672
+ type: "confirm",
673
+ name: "confirm",
674
+ message: `The file ${schema.fileName} already exists. Do you want to ${chalk.yellow(
675
+ `${schema.overwrite ? "overwrite" : "append"}`
676
+ )} the schema to the file?`
677
+ });
678
+ confirm2 = response.confirm;
679
+ }
680
+ if (confirm2) {
681
+ const exist = existsSync(path.join(cwd, schema.fileName));
682
+ if (!exist) {
683
+ await fs$2.mkdir(path.dirname(path.join(cwd, schema.fileName)), {
684
+ recursive: true
685
+ });
686
+ }
687
+ if (schema.overwrite) {
688
+ await fs$2.writeFile(path.join(cwd, schema.fileName), schema.code);
689
+ } else {
690
+ await fs$2.appendFile(path.join(cwd, schema.fileName), schema.code);
691
+ }
692
+ logger.success(
693
+ `\u{1F680} Schema was ${schema.overwrite ? "overwritten" : "appended"} successfully!`
694
+ );
695
+ process.exit(0);
696
+ } else {
697
+ logger.error("Schema generation aborted.");
698
+ process.exit(1);
699
+ }
700
+ }
701
+ let confirm = options.y;
702
+ if (!confirm) {
703
+ const response = await prompts({
704
+ type: "confirm",
705
+ name: "confirm",
706
+ message: `Do you want to generate the schema to ${chalk.yellow(
707
+ schema.fileName
708
+ )}?`
709
+ });
710
+ confirm = response.confirm;
711
+ }
712
+ if (!confirm) {
713
+ logger.error("Schema generation aborted.");
714
+ process.exit(1);
715
+ }
716
+ if (!options.output) {
717
+ const dirExist = existsSync(path.dirname(path.join(cwd, schema.fileName)));
718
+ if (!dirExist) {
719
+ await fs$2.mkdir(path.dirname(path.join(cwd, schema.fileName)), {
720
+ recursive: true
721
+ });
722
+ }
723
+ }
724
+ await fs$2.writeFile(
725
+ options.output || path.join(cwd, schema.fileName),
726
+ schema.code
727
+ );
728
+ logger.success(`\u{1F680} Schema was generated successfully!`);
729
+ process.exit(0);
730
+ }
731
+ const generate = new Command("generate").option(
732
+ "-c, --cwd <cwd>",
733
+ "the working directory. defaults to the current directory.",
734
+ process.cwd()
735
+ ).option(
736
+ "--config <config>",
737
+ "the path to the configuration file. defaults to the first configuration file found."
738
+ ).option("--output <output>", "the file to output to the generated schema").option("-y, --y", "automatically answer yes to all prompts", false).action(generateAction);
739
+
740
+ const generateSecret = new Command("secret").action(() => {
741
+ const secret = generateSecretHash();
742
+ logger.info(`
24
743
  Add the following to your .env file:
25
- ${ee.gray("# Auth Secret")+ee.green(`
26
- BETTER_AUTH_SECRET=${e}`)}`)});import ze from"path";import Fe from"fs-extra";function oe(){let e=ze.join("package.json");return Fe.readJSONSync(e)}process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));async function Re(){let e=new De("better-auth"),o=await oe();e.addCommand(_).addCommand(V).addCommand(te).version(o.version||"1.1.2").description("Better Auth CLI"),e.parse()}Re();
27
- //# sourceMappingURL=index.mjs.map
744
+ ${chalk.gray("# Auth Secret") + chalk.green(`
745
+ BETTER_AUTH_SECRET=${secret}`)}`);
746
+ });
747
+ const generateSecretHash = () => {
748
+ return Crypto.randomBytes(32).toString("hex");
749
+ };
750
+
751
+ function getPackageInfo(cwd) {
752
+ const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
753
+ try {
754
+ return fs.readJSONSync(packageJsonPath);
755
+ } catch (error) {
756
+ throw error;
757
+ }
758
+ }
759
+
760
+ function installDependencies({
761
+ dependencies,
762
+ packageManager,
763
+ cwd
764
+ }) {
765
+ let installCommand;
766
+ switch (packageManager) {
767
+ case "npm":
768
+ installCommand = "npm install --force";
769
+ break;
770
+ case "pnpm":
771
+ installCommand = "pnpm install";
772
+ break;
773
+ case "bun":
774
+ installCommand = "bun install";
775
+ break;
776
+ case "yarn":
777
+ installCommand = "yarn install";
778
+ break;
779
+ default:
780
+ throw new Error("Invalid package manager");
781
+ }
782
+ const command = `${installCommand} ${dependencies.join(" ")}`;
783
+ return new Promise((resolve, reject) => {
784
+ exec(command, { cwd }, (error, stdout, stderr) => {
785
+ if (error) {
786
+ reject(new Error(stderr));
787
+ return;
788
+ }
789
+ resolve(true);
790
+ });
791
+ });
792
+ }
793
+
794
+ function checkCommand(command) {
795
+ return new Promise((resolve) => {
796
+ exec(`${command} --version`, (error) => {
797
+ if (error) {
798
+ resolve(false);
799
+ } else {
800
+ resolve(true);
801
+ }
802
+ });
803
+ });
804
+ }
805
+ async function checkPackageManagers() {
806
+ const hasPnpm = await checkCommand("pnpm");
807
+ const hasBun = await checkCommand("bun");
808
+ return {
809
+ hasPnpm,
810
+ hasBun
811
+ };
812
+ }
813
+
814
+ function formatMilliseconds(ms) {
815
+ if (ms < 0) {
816
+ throw new Error("Milliseconds cannot be negative");
817
+ }
818
+ if (ms < 1e3) {
819
+ return `${ms}ms`;
820
+ }
821
+ const seconds = Math.floor(ms / 1e3);
822
+ const milliseconds = ms % 1e3;
823
+ return `${seconds}s ${milliseconds}ms`;
824
+ }
825
+
826
+ async function generateAuthConfig({
827
+ format,
828
+ current_user_config,
829
+ spinner,
830
+ plugins,
831
+ database
832
+ }) {
833
+ let _start_of_plugins_common_index = {
834
+ START_OF_PLUGINS: {
835
+ type: "regex",
836
+ regex: /betterAuth\([\w\W]*plugins:[\W]*\[()/m,
837
+ getIndex: ({ matchIndex, match }) => {
838
+ return matchIndex + match[0].length;
839
+ }
840
+ }
841
+ };
842
+ const common_indexs = {
843
+ START_OF_PLUGINS: _start_of_plugins_common_index.START_OF_PLUGINS,
844
+ END_OF_PLUGINS: {
845
+ type: "manual",
846
+ getIndex: ({ content, additionalFields }) => {
847
+ const clsoingBracketIndex = findClosingBracket(
848
+ content,
849
+ additionalFields.start_of_plugins,
850
+ "[",
851
+ "]"
852
+ );
853
+ return clsoingBracketIndex;
854
+ }
855
+ },
856
+ START_OF_BETTERAUTH: {
857
+ type: "regex",
858
+ regex: /betterAuth\({()/m,
859
+ getIndex: ({ matchIndex }) => {
860
+ return matchIndex + "betterAuth({".length;
861
+ }
862
+ }
863
+ };
864
+ const config_generation = {
865
+ add_plugin: async (opts) => {
866
+ let start_of_plugins = getGroupInfo(
867
+ opts.config,
868
+ common_indexs.START_OF_PLUGINS,
869
+ {}
870
+ );
871
+ if (!start_of_plugins) {
872
+ throw new Error(
873
+ "Couldn't find start of your plugins array in your auth config file."
874
+ );
875
+ }
876
+ let end_of_plugins = getGroupInfo(
877
+ opts.config,
878
+ common_indexs.END_OF_PLUGINS,
879
+ { start_of_plugins: start_of_plugins.index }
880
+ );
881
+ if (!end_of_plugins) {
882
+ throw new Error(
883
+ "Couldn't find end of your plugins array in your auth config file."
884
+ );
885
+ }
886
+ let new_content;
887
+ if (opts.direction_in_plugins_array === "prepend") {
888
+ new_content = insertContent({
889
+ line: start_of_plugins.line,
890
+ character: start_of_plugins.character,
891
+ content: opts.config,
892
+ insert_content: `${opts.pluginFunctionName}(${opts.pluginContents}),`
893
+ });
894
+ } else {
895
+ let has_found_comma = false;
896
+ const str = opts.config.slice(start_of_plugins.index, end_of_plugins.index).split("").reverse();
897
+ for (let index = 0; index < str.length; index++) {
898
+ const char = str[index];
899
+ if (char === ",") {
900
+ has_found_comma = true;
901
+ }
902
+ if (char === ")") {
903
+ break;
904
+ }
905
+ }
906
+ new_content = insertContent({
907
+ line: end_of_plugins.line,
908
+ character: end_of_plugins.character,
909
+ content: opts.config,
910
+ insert_content: `${!has_found_comma ? "," : ""}${opts.pluginFunctionName}(${opts.pluginContents})`
911
+ });
912
+ }
913
+ try {
914
+ new_content = await format(new_content);
915
+ } catch (error) {
916
+ console.error(error);
917
+ throw new Error(
918
+ `Failed to generate new auth config during plugin addition phase.`
919
+ );
920
+ }
921
+ return { code: await new_content, dependencies: [], envs: [] };
922
+ },
923
+ add_import: async (opts) => {
924
+ let importString = "";
925
+ for (const import_ of opts.imports) {
926
+ if (Array.isArray(import_.variables)) {
927
+ importString += `import { ${import_.variables.map(
928
+ (x) => `${x.asType ? "type " : ""}${x.name}${x.as ? ` as ${x.as}` : ""}`
929
+ ).join(", ")} } from "${import_.path}";
930
+ `;
931
+ } else {
932
+ importString += `import ${import_.variables.asType ? "type " : ""}${import_.variables.name}${import_.variables.as ? ` as ${import_.variables.as}` : ""} from "${import_.path}";
933
+ `;
934
+ }
935
+ }
936
+ try {
937
+ let new_content = format(importString + opts.config);
938
+ return { code: await new_content, dependencies: [], envs: [] };
939
+ } catch (error) {
940
+ console.error(error);
941
+ throw new Error(
942
+ `Failed to generate new auth config during import addition phase.`
943
+ );
944
+ }
945
+ },
946
+ add_database: async (opts) => {
947
+ const required_envs = [];
948
+ const required_deps = [];
949
+ let database_code_str = "";
950
+ async function add_db({
951
+ db_code,
952
+ dependencies,
953
+ envs,
954
+ imports,
955
+ code_before_betterAuth
956
+ }) {
957
+ if (code_before_betterAuth) {
958
+ let start_of_betterauth2 = getGroupInfo(
959
+ opts.config,
960
+ common_indexs.START_OF_BETTERAUTH,
961
+ {}
962
+ );
963
+ if (!start_of_betterauth2) {
964
+ throw new Error("Couldn't find start of betterAuth() function.");
965
+ }
966
+ opts.config = insertContent({
967
+ line: start_of_betterauth2.line - 1,
968
+ character: 0,
969
+ content: opts.config,
970
+ insert_content: `
971
+ ${code_before_betterAuth}
972
+ `
973
+ });
974
+ }
975
+ const code_gen = await config_generation.add_import({
976
+ config: opts.config,
977
+ imports
978
+ });
979
+ opts.config = code_gen.code;
980
+ database_code_str = db_code;
981
+ required_envs.push(...envs, ...code_gen.envs);
982
+ required_deps.push(...dependencies, ...code_gen.dependencies);
983
+ }
984
+ if (opts.database === "sqlite") {
985
+ await add_db({
986
+ db_code: `new Database(process.env.DATABASE_URL || "database.sqlite")`,
987
+ dependencies: ["better-sqlite3"],
988
+ envs: ["DATABASE_URL"],
989
+ imports: [
990
+ {
991
+ path: "better-sqlite3",
992
+ variables: {
993
+ asType: false,
994
+ name: "Database"
995
+ }
996
+ }
997
+ ]
998
+ });
999
+ } else if (opts.database === "postgres") {
1000
+ await add_db({
1001
+ db_code: `new Pool({
1002
+ connectionString: process.env.DATABASE_URL || "postgresql://postgres:password@localhost:5432/database"
1003
+ })`,
1004
+ dependencies: ["pg"],
1005
+ envs: ["DATABASE_URL"],
1006
+ imports: [
1007
+ {
1008
+ path: "pg",
1009
+ variables: [
1010
+ {
1011
+ asType: false,
1012
+ name: "Pool"
1013
+ }
1014
+ ]
1015
+ }
1016
+ ]
1017
+ });
1018
+ } else if (opts.database === "mysql") {
1019
+ await add_db({
1020
+ db_code: `createPool(process.env.DATABASE_URL!)`,
1021
+ dependencies: ["mysql2"],
1022
+ envs: ["DATABASE_URL"],
1023
+ imports: [
1024
+ {
1025
+ path: "mysql2/promise",
1026
+ variables: [
1027
+ {
1028
+ asType: false,
1029
+ name: "createPool"
1030
+ }
1031
+ ]
1032
+ }
1033
+ ]
1034
+ });
1035
+ } else if (opts.database === "mssql") {
1036
+ const dialectCode = `new MssqlDialect({
1037
+ tarn: {
1038
+ ...Tarn,
1039
+ options: {
1040
+ min: 0,
1041
+ max: 10,
1042
+ },
1043
+ },
1044
+ tedious: {
1045
+ ...Tedious,
1046
+ connectionFactory: () => new Tedious.Connection({
1047
+ authentication: {
1048
+ options: {
1049
+ password: 'password',
1050
+ userName: 'username',
1051
+ },
1052
+ type: 'default',
1053
+ },
1054
+ options: {
1055
+ database: 'some_db',
1056
+ port: 1433,
1057
+ trustServerCertificate: true,
1058
+ },
1059
+ server: 'localhost',
1060
+ }),
1061
+ },
1062
+ })`;
1063
+ await add_db({
1064
+ code_before_betterAuth: dialectCode,
1065
+ db_code: `dialect`,
1066
+ dependencies: ["tedious", "tarn", "kysely"],
1067
+ envs: ["DATABASE_URL"],
1068
+ imports: [
1069
+ {
1070
+ path: "tedious",
1071
+ variables: {
1072
+ name: "*",
1073
+ as: "Tedious"
1074
+ }
1075
+ },
1076
+ {
1077
+ path: "tarn",
1078
+ variables: {
1079
+ name: "*",
1080
+ as: "Tarn"
1081
+ }
1082
+ },
1083
+ {
1084
+ path: "kysely",
1085
+ variables: [
1086
+ {
1087
+ name: "MssqlDialect"
1088
+ }
1089
+ ]
1090
+ }
1091
+ ]
1092
+ });
1093
+ } else if (opts.database === "drizzle:mysql" || opts.database === "drizzle:sqlite" || opts.database === "drizzle:pg") {
1094
+ await add_db({
1095
+ db_code: `new DrizzleAdapter(db, {
1096
+ provider: "${opts.database.replace(
1097
+ "drizzle:",
1098
+ ""
1099
+ )}",
1100
+ })`,
1101
+ dependencies: [""],
1102
+ envs: [],
1103
+ imports: [
1104
+ {
1105
+ path: "better-auth/adapters/drizzle",
1106
+ variables: [
1107
+ {
1108
+ name: "DrizzleAdapter"
1109
+ }
1110
+ ]
1111
+ },
1112
+ {
1113
+ path: "./database.ts",
1114
+ variables: [
1115
+ {
1116
+ name: "db"
1117
+ }
1118
+ ]
1119
+ }
1120
+ ]
1121
+ });
1122
+ } else if (opts.database === "prisma:mysql" || opts.database === "prisma:sqlite" || opts.database === "prisma:pg") {
1123
+ await add_db({
1124
+ db_code: `new PrismaAdapter(client, {
1125
+ provider: "${opts.database.replace(
1126
+ "prisma:",
1127
+ ""
1128
+ )}",
1129
+ })`,
1130
+ dependencies: [`@prisma/client`],
1131
+ envs: [],
1132
+ code_before_betterAuth: "const client = new PrismaClient();",
1133
+ imports: [
1134
+ {
1135
+ path: "better-auth/adapters/prisma",
1136
+ variables: [
1137
+ {
1138
+ name: "PrismaAdapter"
1139
+ }
1140
+ ]
1141
+ },
1142
+ {
1143
+ path: "@prisma/client",
1144
+ variables: [
1145
+ {
1146
+ name: "PrismaClient"
1147
+ }
1148
+ ]
1149
+ }
1150
+ ]
1151
+ });
1152
+ } else if (opts.database === "mongodb") {
1153
+ await add_db({
1154
+ db_code: `mongodbAdapter(db)`,
1155
+ dependencies: ["mongodb"],
1156
+ envs: [`DATABASE_URL`],
1157
+ code_before_betterAuth: [
1158
+ `const client = new MongoClient(process.env.DATABASE_URL || "mongodb://localhost:27017/database");`,
1159
+ `const db = client.db();`
1160
+ ].join("\n"),
1161
+ imports: [
1162
+ {
1163
+ path: "better-auth/adapters/mongo",
1164
+ variables: [
1165
+ {
1166
+ name: "mongodbAdapter"
1167
+ }
1168
+ ]
1169
+ },
1170
+ {
1171
+ path: "mongodb",
1172
+ variables: [
1173
+ {
1174
+ name: "MongoClient"
1175
+ }
1176
+ ]
1177
+ }
1178
+ ]
1179
+ });
1180
+ }
1181
+ let start_of_betterauth = getGroupInfo(
1182
+ opts.config,
1183
+ common_indexs.START_OF_BETTERAUTH,
1184
+ {}
1185
+ );
1186
+ if (!start_of_betterauth) {
1187
+ throw new Error("Couldn't find start of betterAuth() function.");
1188
+ }
1189
+ let new_content;
1190
+ new_content = insertContent({
1191
+ line: start_of_betterauth.line,
1192
+ character: start_of_betterauth.character,
1193
+ content: opts.config,
1194
+ insert_content: `database: ${database_code_str},`
1195
+ });
1196
+ try {
1197
+ new_content = await format(new_content);
1198
+ return {
1199
+ code: new_content,
1200
+ dependencies: required_deps,
1201
+ envs: required_envs
1202
+ };
1203
+ } catch (error) {
1204
+ console.error(error);
1205
+ throw new Error(
1206
+ `Failed to generate new auth config during database addition phase.`
1207
+ );
1208
+ }
1209
+ }
1210
+ };
1211
+ let new_user_config = await format(current_user_config);
1212
+ let total_dependencies = [];
1213
+ let total_envs = [];
1214
+ if (plugins.length !== 0) {
1215
+ const imports = [];
1216
+ for await (const plugin of plugins) {
1217
+ const existingIndex = imports.findIndex((x) => x.path === plugin.path);
1218
+ if (existingIndex !== -1) {
1219
+ imports[existingIndex].variables.push({
1220
+ name: plugin.name,
1221
+ asType: false
1222
+ });
1223
+ } else {
1224
+ imports.push({
1225
+ path: plugin.path,
1226
+ variables: [
1227
+ {
1228
+ name: plugin.name,
1229
+ asType: false
1230
+ }
1231
+ ]
1232
+ });
1233
+ }
1234
+ }
1235
+ if (imports.length !== 0) {
1236
+ const { code, envs, dependencies } = await config_generation.add_import({
1237
+ config: new_user_config,
1238
+ imports
1239
+ });
1240
+ total_dependencies.push(...dependencies);
1241
+ total_envs.push(...envs);
1242
+ new_user_config = code;
1243
+ }
1244
+ }
1245
+ for await (const plugin of plugins) {
1246
+ try {
1247
+ let pluginContents = "";
1248
+ if (plugin.id === "magic-link") {
1249
+ pluginContents = `{
1250
+ sendMagicLink({ email, token, url }, request) {
1251
+ // Send email with magic link
1252
+ },
1253
+ }`;
1254
+ } else if (plugin.id === "email-otp") {
1255
+ pluginContents = `{
1256
+ async sendVerificationOTP({ email, otp, type }, request) {
1257
+ // Send email with OTP
1258
+ },
1259
+ }`;
1260
+ } else if (plugin.id === "generic-oauth") {
1261
+ pluginContents = `{
1262
+ config: [],
1263
+ }`;
1264
+ } else if (plugin.id === "oidc") {
1265
+ pluginContents = `{
1266
+ loginPage: "/sign-in",
1267
+ }`;
1268
+ }
1269
+ const { code, dependencies, envs } = await config_generation.add_plugin({
1270
+ config: new_user_config,
1271
+ direction_in_plugins_array: plugin.id === "next-cookies" ? "append" : "prepend",
1272
+ pluginFunctionName: plugin.name,
1273
+ pluginContents
1274
+ });
1275
+ new_user_config = code;
1276
+ total_envs.push(...envs);
1277
+ total_dependencies.push(...dependencies);
1278
+ } catch (error) {
1279
+ spinner.stop(
1280
+ `Something went wrong while generating/updating your new auth config file.`,
1281
+ 1
1282
+ );
1283
+ logger.error(error.message);
1284
+ process.exit(1);
1285
+ }
1286
+ }
1287
+ if (database) {
1288
+ try {
1289
+ const { code, dependencies, envs } = await config_generation.add_database(
1290
+ {
1291
+ config: new_user_config,
1292
+ database
1293
+ }
1294
+ );
1295
+ new_user_config = code;
1296
+ total_dependencies.push(...dependencies);
1297
+ total_envs.push(...envs);
1298
+ } catch (error) {
1299
+ spinner.stop(
1300
+ `Something went wrong while generating/updating your new auth config file.`,
1301
+ 1
1302
+ );
1303
+ logger.error(error.message);
1304
+ process.exit(1);
1305
+ }
1306
+ }
1307
+ return {
1308
+ generatedCode: new_user_config,
1309
+ dependencies: total_dependencies,
1310
+ envs: total_envs
1311
+ };
1312
+ }
1313
+ function findClosingBracket(content, startIndex, openingBracket, closingBracket) {
1314
+ let stack = 0;
1315
+ let inString = false;
1316
+ let quoteChar = null;
1317
+ for (let i = startIndex; i < content.length; i++) {
1318
+ const char = content[i];
1319
+ if (char === '"' || char === "'" || char === "`") {
1320
+ if (!inString) {
1321
+ inString = true;
1322
+ quoteChar = char;
1323
+ } else if (char === quoteChar) {
1324
+ inString = false;
1325
+ quoteChar = null;
1326
+ }
1327
+ continue;
1328
+ }
1329
+ if (!inString) {
1330
+ if (char === openingBracket) {
1331
+ stack++;
1332
+ } else if (char === closingBracket) {
1333
+ if (stack === 0) {
1334
+ return i;
1335
+ }
1336
+ stack--;
1337
+ }
1338
+ }
1339
+ }
1340
+ return null;
1341
+ }
1342
+ function insertContent(params) {
1343
+ const { line, character, content, insert_content } = params;
1344
+ const lines = content.split("\n");
1345
+ if (line < 1 || line > lines.length) {
1346
+ throw new Error("Invalid line number");
1347
+ }
1348
+ const targetLineIndex = line - 1;
1349
+ if (character < 0 || character > lines[targetLineIndex].length) {
1350
+ throw new Error("Invalid character index");
1351
+ }
1352
+ const targetLine = lines[targetLineIndex];
1353
+ const updatedLine = targetLine.slice(0, character) + insert_content + targetLine.slice(character);
1354
+ lines[targetLineIndex] = updatedLine;
1355
+ return lines.join("\n");
1356
+ }
1357
+ function getGroupInfo(content, commonIndexConfig, additionalFields) {
1358
+ if (commonIndexConfig.type === "regex") {
1359
+ const { regex, getIndex } = commonIndexConfig;
1360
+ const match = regex.exec(content);
1361
+ if (match) {
1362
+ const matchIndex = match.index;
1363
+ const groupIndex = getIndex({ matchIndex, match, additionalFields });
1364
+ if (groupIndex === null) return null;
1365
+ const position = getPosition(content, groupIndex);
1366
+ return {
1367
+ line: position.line,
1368
+ character: position.character,
1369
+ index: groupIndex
1370
+ };
1371
+ }
1372
+ return null;
1373
+ } else {
1374
+ const { getIndex } = commonIndexConfig;
1375
+ const index = getIndex({ content, additionalFields });
1376
+ if (index === null) return null;
1377
+ const { line, character } = getPosition(content, index);
1378
+ return {
1379
+ line,
1380
+ character,
1381
+ index
1382
+ };
1383
+ }
1384
+ }
1385
+ const getPosition = (str, index) => {
1386
+ const lines = str.slice(0, index).split("\n");
1387
+ return {
1388
+ line: lines.length,
1389
+ character: lines[lines.length - 1].length
1390
+ };
1391
+ };
1392
+
1393
+ const supportedDatabases = [
1394
+ // Built-in kysely
1395
+ "sqlite",
1396
+ "mysql",
1397
+ "mssql",
1398
+ "postgres",
1399
+ // Drizzle
1400
+ "drizzle:pg",
1401
+ "drizzle:mysql",
1402
+ "drizzle:sqlite",
1403
+ // Prisma
1404
+ "prisma:pg",
1405
+ "prisma:mysql",
1406
+ "prisma:sqlite",
1407
+ // Mongo
1408
+ "mongodb"
1409
+ ];
1410
+ const supportedPlugins = [
1411
+ {
1412
+ id: "two-factor",
1413
+ name: "twoFactor",
1414
+ path: `better-auth/plugins`,
1415
+ clientName: "twoFactorClient",
1416
+ clientPath: "better-auth/client/plugins"
1417
+ },
1418
+ {
1419
+ id: "username",
1420
+ name: "username",
1421
+ clientName: "usernameClient",
1422
+ path: `better-auth/plugins`,
1423
+ clientPath: "better-auth/client/plugins"
1424
+ },
1425
+ {
1426
+ id: "anonymous",
1427
+ name: "anonymous",
1428
+ clientName: "anonymousClient",
1429
+ path: `better-auth/plugins`,
1430
+ clientPath: "better-auth/client/plugins"
1431
+ },
1432
+ {
1433
+ id: "phone-number",
1434
+ name: "phoneNumber",
1435
+ clientName: "phoneNumberClient",
1436
+ path: `better-auth/plugins`,
1437
+ clientPath: "better-auth/client/plugins"
1438
+ },
1439
+ {
1440
+ id: "magic-link",
1441
+ name: "magicLink",
1442
+ clientName: "magicLinkClient",
1443
+ clientPath: "better-auth/client/plugins",
1444
+ path: `better-auth/plugins`
1445
+ },
1446
+ {
1447
+ id: "email-otp",
1448
+ name: "emailOTP",
1449
+ clientName: "emailOTPClient",
1450
+ path: `better-auth/plugins`,
1451
+ clientPath: "better-auth/client/plugins"
1452
+ },
1453
+ {
1454
+ id: "passkey",
1455
+ name: "passkey",
1456
+ clientName: "passkeyClient",
1457
+ path: `better-auth/plugins/passkey`,
1458
+ clientPath: "better-auth/client/plugins"
1459
+ },
1460
+ {
1461
+ id: "generic-oauth",
1462
+ name: "genericOAuth",
1463
+ clientName: "genericOAuthClient",
1464
+ path: `better-auth/plugins`,
1465
+ clientPath: "better-auth/client/plugins"
1466
+ },
1467
+ {
1468
+ id: "one-tap",
1469
+ name: "oneTap",
1470
+ clientName: "oneTapClient",
1471
+ path: `better-auth/plugins`,
1472
+ clientPath: "better-auth/client/plugins"
1473
+ },
1474
+ {
1475
+ id: "api-key",
1476
+ name: "apiKey",
1477
+ clientName: "apiKeyClient",
1478
+ path: `better-auth/plugins`,
1479
+ clientPath: "better-auth/client/plugins"
1480
+ },
1481
+ {
1482
+ id: "admin",
1483
+ name: "admin",
1484
+ clientName: "adminClient",
1485
+ path: `better-auth/plugins`,
1486
+ clientPath: "better-auth/client/plugins"
1487
+ },
1488
+ {
1489
+ id: "organization",
1490
+ name: "organization",
1491
+ clientName: "organizationClient",
1492
+ path: `better-auth/plugins`,
1493
+ clientPath: "better-auth/client/plugins"
1494
+ },
1495
+ {
1496
+ id: "oidc",
1497
+ name: "oidcProvider",
1498
+ clientName: "oidcClient",
1499
+ path: `better-auth/plugins`,
1500
+ clientPath: "better-auth/client/plugins"
1501
+ },
1502
+ {
1503
+ id: "sso",
1504
+ name: "sso",
1505
+ clientName: "ssoClient",
1506
+ path: `better-auth/plugins/sso`,
1507
+ clientPath: "better-auth/client/plugins"
1508
+ },
1509
+ {
1510
+ id: "bearer",
1511
+ name: "bearer",
1512
+ clientName: void 0,
1513
+ path: `better-auth/plugins`,
1514
+ clientPath: void 0
1515
+ },
1516
+ {
1517
+ id: "multi-session",
1518
+ name: "multiSession",
1519
+ clientName: "multiSessionClient",
1520
+ path: `better-auth/plugins`,
1521
+ clientPath: "better-auth/client/plugins"
1522
+ },
1523
+ {
1524
+ id: "oauth-proxy",
1525
+ name: "oAuthProxy",
1526
+ clientName: void 0,
1527
+ path: `better-auth/plugins`,
1528
+ clientPath: void 0
1529
+ },
1530
+ {
1531
+ id: "open-api",
1532
+ name: "openAPI",
1533
+ clientName: void 0,
1534
+ path: `better-auth/plugins`,
1535
+ clientPath: void 0
1536
+ },
1537
+ {
1538
+ id: "jwt",
1539
+ name: "jwt",
1540
+ clientName: void 0,
1541
+ clientPath: void 0,
1542
+ path: `better-auth/plugins`
1543
+ },
1544
+ {
1545
+ id: "next-cookies",
1546
+ name: "nextCookies",
1547
+ clientPath: void 0,
1548
+ clientName: void 0,
1549
+ path: `better-auth/next-js`
1550
+ }
1551
+ ];
1552
+ const defaultFormatOptions = {
1553
+ trailingComma: "all",
1554
+ useTabs: false,
1555
+ tabWidth: 4
1556
+ };
1557
+ const getDefaultAuthConfig = async ({
1558
+ appName
1559
+ }) => await format(
1560
+ [
1561
+ "import { betterAuth } from 'better-auth';",
1562
+ "",
1563
+ "export const auth = betterAuth({",
1564
+ appName ? `appName: "${appName}",` : "",
1565
+ "plugins: [],",
1566
+ "});"
1567
+ ].join("\n"),
1568
+ {
1569
+ filepath: "auth.ts",
1570
+ ...defaultFormatOptions
1571
+ }
1572
+ );
1573
+ const getDefaultAuthClientConfig = async ({
1574
+ auth_config_path,
1575
+ framework,
1576
+ clientPlugins
1577
+ }) => {
1578
+ function groupImportVariables() {
1579
+ const result = [
1580
+ {
1581
+ path: "better-auth/client/plugins",
1582
+ variables: [{ name: "inferAdditionalFields" }]
1583
+ }
1584
+ ];
1585
+ for (const plugin of clientPlugins) {
1586
+ for (const import_ of plugin.imports) {
1587
+ if (Array.isArray(import_.variables)) {
1588
+ for (const variable of import_.variables) {
1589
+ const existingIndex = result.findIndex(
1590
+ (x) => x.path === import_.path
1591
+ );
1592
+ if (existingIndex !== -1) {
1593
+ const vars = result[existingIndex].variables;
1594
+ if (Array.isArray(vars)) {
1595
+ vars.push(variable);
1596
+ } else {
1597
+ result[existingIndex].variables = [vars, variable];
1598
+ }
1599
+ } else {
1600
+ result.push({
1601
+ path: import_.path,
1602
+ variables: [variable]
1603
+ });
1604
+ }
1605
+ }
1606
+ } else {
1607
+ const existingIndex = result.findIndex(
1608
+ (x) => x.path === import_.path
1609
+ );
1610
+ if (existingIndex !== -1) {
1611
+ const vars = result[existingIndex].variables;
1612
+ if (Array.isArray(vars)) {
1613
+ vars.push(import_.variables);
1614
+ } else {
1615
+ result[existingIndex].variables = [vars, import_.variables];
1616
+ }
1617
+ } else {
1618
+ result.push({
1619
+ path: import_.path,
1620
+ variables: [import_.variables]
1621
+ });
1622
+ }
1623
+ }
1624
+ }
1625
+ }
1626
+ return result;
1627
+ }
1628
+ let imports = groupImportVariables();
1629
+ let importString = "";
1630
+ for (const import_ of imports) {
1631
+ if (Array.isArray(import_.variables)) {
1632
+ importString += `import { ${import_.variables.map(
1633
+ (x) => `${x.asType ? "type " : ""}${x.name}${x.as ? ` as ${x.as}` : ""}`
1634
+ ).join(", ")} } from "${import_.path}";
1635
+ `;
1636
+ } else {
1637
+ importString += `import ${import_.variables.asType ? "type " : ""}${import_.variables.name}${import_.variables.as ? ` as ${import_.variables.as}` : ""} from "${import_.path}";
1638
+ `;
1639
+ }
1640
+ }
1641
+ return await format(
1642
+ [
1643
+ `import { createAuthClient } from "better-auth/${framework === "nextjs" ? "react" : framework === "vanilla" ? "client" : framework}";`,
1644
+ `import type { auth } from "${auth_config_path}";`,
1645
+ importString,
1646
+ ``,
1647
+ `export const authClient = createAuthClient({`,
1648
+ `baseURL: "http://localhost:3000",`,
1649
+ `plugins: [inferAdditionalFields<typeof auth>(),${clientPlugins.map((x) => `${x.name}(${x.contents})`).join(", ")}],`,
1650
+ `});`
1651
+ ].join("\n"),
1652
+ {
1653
+ filepath: "auth-client.ts",
1654
+ ...defaultFormatOptions
1655
+ }
1656
+ );
1657
+ };
1658
+ const optionsSchema = z.object({
1659
+ cwd: z.string(),
1660
+ config: z.string().optional(),
1661
+ database: z.enum(supportedDatabases).optional(),
1662
+ "skip-db": z.boolean().optional(),
1663
+ "skip-plugins": z.boolean().optional(),
1664
+ "package-manager": z.string().optional()
1665
+ });
1666
+ const outroText = `\u{1F973} All Done, Happy Hacking!`;
1667
+ async function initAction(opts) {
1668
+ console.log();
1669
+ intro("\u{1F44B} Initializing Better Auth");
1670
+ const options = optionsSchema.parse(opts);
1671
+ const cwd = path.resolve(options.cwd);
1672
+ let packageManagerPreference = void 0;
1673
+ let config_path = "";
1674
+ let framework = "vanilla";
1675
+ const format$1 = async (code) => await format(code, {
1676
+ filepath: config_path,
1677
+ ...defaultFormatOptions
1678
+ });
1679
+ let packageInfo;
1680
+ try {
1681
+ packageInfo = getPackageInfo(cwd);
1682
+ } catch (error) {
1683
+ log.error(`\u274C Couldn't read your package.json file. (dir: ${cwd})`);
1684
+ log.error(JSON.stringify(error, null, 2));
1685
+ process.exit(1);
1686
+ }
1687
+ const envFiles = await getEnvFiles(cwd);
1688
+ if (!envFiles.length) {
1689
+ outro("\u274C No .env files found. Please create an env file first.");
1690
+ process.exit(0);
1691
+ }
1692
+ let targetEnvFile;
1693
+ if (envFiles.includes(".env")) targetEnvFile = ".env";
1694
+ else if (envFiles.includes(".env.local")) targetEnvFile = ".env.local";
1695
+ else if (envFiles.includes(".env.development"))
1696
+ targetEnvFile = ".env.development";
1697
+ else if (envFiles.length === 1) targetEnvFile = envFiles[0];
1698
+ else targetEnvFile = "none";
1699
+ let tsconfigInfo;
1700
+ try {
1701
+ tsconfigInfo = await getTsconfigInfo(cwd);
1702
+ } catch (error) {
1703
+ log.error(`\u274C Couldn't read your tsconfig.json file. (dir: ${cwd})`);
1704
+ console.error(error);
1705
+ process.exit(1);
1706
+ }
1707
+ if (!("compilerOptions" in tsconfigInfo && "strict" in tsconfigInfo.compilerOptions && tsconfigInfo.compilerOptions.strict === true)) {
1708
+ log.warn(
1709
+ `Better Auth requires your tsconfig.json to have "compilerOptions.strict" set to true.`
1710
+ );
1711
+ const shouldAdd = await confirm({
1712
+ message: `Would you like us to set ${chalk.bold(
1713
+ `strict`
1714
+ )} to ${chalk.bold(`true`)}?`
1715
+ });
1716
+ if (isCancel(shouldAdd)) {
1717
+ cancel(`\u270B Operation cancelled.`);
1718
+ process.exit(0);
1719
+ }
1720
+ if (shouldAdd) {
1721
+ try {
1722
+ await fs$2.writeFile(
1723
+ path.join(cwd, "tsconfig.json"),
1724
+ await format(
1725
+ JSON.stringify(
1726
+ Object.assign(tsconfigInfo, {
1727
+ compilerOptions: {
1728
+ strict: true
1729
+ }
1730
+ })
1731
+ ),
1732
+ { filepath: "tsconfig.json", ...defaultFormatOptions }
1733
+ ),
1734
+ "utf-8"
1735
+ );
1736
+ log.success(`\u{1F680} tsconfig.json successfully updated!`);
1737
+ } catch (error) {
1738
+ log.error(
1739
+ `Failed to add "compilerOptions.strict" to your tsconfig.json file.`
1740
+ );
1741
+ console.error(error);
1742
+ process.exit(1);
1743
+ }
1744
+ }
1745
+ }
1746
+ const s = spinner({ indicator: "dots" });
1747
+ s.start(`Checking better-auth installation`);
1748
+ let latest_betterauth_version;
1749
+ try {
1750
+ latest_betterauth_version = await getLatestNpmVersion("better-auth");
1751
+ } catch (error) {
1752
+ log.error(`\u274C Couldn't get latest version of better-auth.`);
1753
+ console.error(error);
1754
+ process.exit(1);
1755
+ }
1756
+ if (!packageInfo.dependencies || !Object.keys(packageInfo.dependencies).includes("better-auth")) {
1757
+ s.stop("Finished fetching latest version of better-auth.");
1758
+ const s2 = spinner({ indicator: "dots" });
1759
+ const shouldInstallBetterAuthDep = await confirm({
1760
+ message: `Would you like to install Better Auth?`
1761
+ });
1762
+ if (isCancel(shouldInstallBetterAuthDep)) {
1763
+ cancel(`\u270B Operation cancelled.`);
1764
+ process.exit(0);
1765
+ }
1766
+ if (packageManagerPreference === void 0) {
1767
+ packageManagerPreference = await getPackageManager();
1768
+ }
1769
+ if (shouldInstallBetterAuthDep) {
1770
+ s2.start(
1771
+ `Installing Better Auth using ${chalk.bold(packageManagerPreference)}`
1772
+ );
1773
+ try {
1774
+ const start = Date.now();
1775
+ await installDependencies({
1776
+ dependencies: ["better-auth@latest"],
1777
+ packageManager: packageManagerPreference,
1778
+ cwd
1779
+ });
1780
+ s2.stop(
1781
+ `Better Auth installed ${chalk.greenBright(
1782
+ `successfully`
1783
+ )}! ${chalk.gray(`(${formatMilliseconds(Date.now() - start)})`)}`
1784
+ );
1785
+ } catch (error) {
1786
+ s2.stop(`Failed to install Better Auth:`);
1787
+ console.error(error);
1788
+ process.exit(1);
1789
+ }
1790
+ }
1791
+ } else if (packageInfo.dependencies["better-auth"] !== "workspace:*" && semver.lt(
1792
+ semver.coerce(packageInfo.dependencies["better-auth"])?.toString(),
1793
+ semver.clean(latest_betterauth_version)
1794
+ )) {
1795
+ s.stop("Finished fetching latest version of better-auth.");
1796
+ const shouldInstallBetterAuthDep = await confirm({
1797
+ message: `Your current Better Auth dependency is out-of-date. Would you like to update it? (${chalk.bold(
1798
+ packageInfo.dependencies["better-auth"]
1799
+ )} \u2192 ${chalk.bold(`v${latest_betterauth_version}`)})`
1800
+ });
1801
+ if (isCancel(shouldInstallBetterAuthDep)) {
1802
+ cancel(`\u270B Operation cancelled.`);
1803
+ process.exit(0);
1804
+ }
1805
+ if (shouldInstallBetterAuthDep) {
1806
+ if (packageManagerPreference === void 0) {
1807
+ packageManagerPreference = await getPackageManager();
1808
+ }
1809
+ const s2 = spinner({ indicator: "dots" });
1810
+ s2.start(
1811
+ `Updating Better Auth using ${chalk.bold(packageManagerPreference)}`
1812
+ );
1813
+ try {
1814
+ const start = Date.now();
1815
+ await installDependencies({
1816
+ dependencies: ["better-auth@latest"],
1817
+ packageManager: packageManagerPreference,
1818
+ cwd
1819
+ });
1820
+ s2.stop(
1821
+ `Better Auth updated ${chalk.greenBright(
1822
+ `successfully`
1823
+ )}! ${chalk.gray(`(${formatMilliseconds(Date.now() - start)})`)}`
1824
+ );
1825
+ } catch (error) {
1826
+ s2.stop(`Failed to update Better Auth:`);
1827
+ log.error(error.message);
1828
+ process.exit(1);
1829
+ }
1830
+ }
1831
+ } else {
1832
+ s.stop(`Better Auth dependencies are ${chalk.greenBright(`up-to-date`)}!`);
1833
+ }
1834
+ const packageJson = getPackageInfo(cwd);
1835
+ let appName;
1836
+ if (!packageJson.name) {
1837
+ const newAppName = await text({
1838
+ message: "What is the name of your application?"
1839
+ });
1840
+ if (isCancel(newAppName)) {
1841
+ cancel("\u270B Operation cancelled.");
1842
+ process.exit(0);
1843
+ }
1844
+ appName = newAppName;
1845
+ } else {
1846
+ appName = packageJson.name;
1847
+ }
1848
+ let possiblePaths = ["auth.ts", "auth.tsx", "auth.js", "auth.jsx"];
1849
+ possiblePaths = [
1850
+ ...possiblePaths,
1851
+ ...possiblePaths.map((it) => `lib/server/${it}`),
1852
+ ...possiblePaths.map((it) => `server/${it}`),
1853
+ ...possiblePaths.map((it) => `lib/${it}`),
1854
+ ...possiblePaths.map((it) => `utils/${it}`)
1855
+ ];
1856
+ possiblePaths = [
1857
+ ...possiblePaths,
1858
+ ...possiblePaths.map((it) => `src/${it}`),
1859
+ ...possiblePaths.map((it) => `app/${it}`)
1860
+ ];
1861
+ if (options.config) {
1862
+ config_path = path.join(cwd, options.config);
1863
+ } else {
1864
+ for (const possiblePath of possiblePaths) {
1865
+ const doesExist = existsSync(path.join(cwd, possiblePath));
1866
+ if (doesExist) {
1867
+ config_path = path.join(cwd, possiblePath);
1868
+ break;
1869
+ }
1870
+ }
1871
+ }
1872
+ let current_user_config = "";
1873
+ let database = null;
1874
+ let add_plugins = [];
1875
+ if (!config_path) {
1876
+ const shouldCreateAuthConfig = await select({
1877
+ message: `Would you like to create an auth config file?`,
1878
+ options: [
1879
+ { label: "Yes", value: "yes" },
1880
+ { label: "No", value: "no" }
1881
+ ]
1882
+ });
1883
+ if (isCancel(shouldCreateAuthConfig)) {
1884
+ cancel(`\u270B Operation cancelled.`);
1885
+ process.exit(0);
1886
+ }
1887
+ if (shouldCreateAuthConfig === "yes") {
1888
+ const shouldSetupDb = await confirm({
1889
+ message: `Would you like to set up your ${chalk.bold(`database`)}?`,
1890
+ initialValue: true
1891
+ });
1892
+ if (isCancel(shouldSetupDb)) {
1893
+ cancel(`\u270B Operating cancelled.`);
1894
+ process.exit(0);
1895
+ }
1896
+ if (shouldSetupDb) {
1897
+ const prompted_database = await select({
1898
+ message: "Choose a Database Dialect",
1899
+ options: supportedDatabases.map((it) => ({ value: it, label: it }))
1900
+ });
1901
+ if (isCancel(prompted_database)) {
1902
+ cancel(`\u270B Operating cancelled.`);
1903
+ process.exit(0);
1904
+ }
1905
+ database = prompted_database;
1906
+ }
1907
+ if (options["skip-plugins"] !== false) {
1908
+ const shouldSetupPlugins = await confirm({
1909
+ message: `Would you like to set up ${chalk.bold(`plugins`)}?`
1910
+ });
1911
+ if (isCancel(shouldSetupPlugins)) {
1912
+ cancel(`\u270B Operating cancelled.`);
1913
+ process.exit(0);
1914
+ }
1915
+ if (shouldSetupPlugins) {
1916
+ const prompted_plugins = await multiselect({
1917
+ message: "Select your new plugins",
1918
+ options: supportedPlugins.filter((x) => x.id !== "next-cookies").map((x) => ({ value: x.id, label: x.id })),
1919
+ required: false
1920
+ });
1921
+ if (isCancel(prompted_plugins)) {
1922
+ cancel(`\u270B Operating cancelled.`);
1923
+ process.exit(0);
1924
+ }
1925
+ add_plugins = prompted_plugins.map(
1926
+ (x) => supportedPlugins.find((y) => y.id === x)
1927
+ );
1928
+ const possible_next_config_paths = [
1929
+ "next.config.js",
1930
+ "next.config.ts",
1931
+ "next.config.mjs",
1932
+ ".next/server/next.config.js",
1933
+ ".next/server/next.config.ts",
1934
+ ".next/server/next.config.mjs"
1935
+ ];
1936
+ for (const possible_next_config_path of possible_next_config_paths) {
1937
+ if (existsSync(path.join(cwd, possible_next_config_path))) {
1938
+ framework = "nextjs";
1939
+ break;
1940
+ }
1941
+ }
1942
+ if (framework === "nextjs") {
1943
+ const result = await confirm({
1944
+ message: `It looks like you're using NextJS. Do you want to add the next-cookies plugin? ${chalk.bold(
1945
+ `(Recommended)`
1946
+ )}`
1947
+ });
1948
+ if (isCancel(result)) {
1949
+ cancel(`\u270B Operating cancelled.`);
1950
+ process.exit(0);
1951
+ }
1952
+ if (result) {
1953
+ add_plugins.push(
1954
+ supportedPlugins.find((x) => x.id === "next-cookies")
1955
+ );
1956
+ }
1957
+ }
1958
+ }
1959
+ }
1960
+ const filePath = path.join(cwd, "auth.ts");
1961
+ config_path = filePath;
1962
+ log.info(`Creating auth config file: ${filePath}`);
1963
+ try {
1964
+ current_user_config = await getDefaultAuthConfig({
1965
+ appName
1966
+ });
1967
+ const { dependencies, envs, generatedCode } = await generateAuthConfig({
1968
+ current_user_config,
1969
+ format: format$1,
1970
+ //@ts-ignore
1971
+ s,
1972
+ plugins: add_plugins,
1973
+ database
1974
+ });
1975
+ current_user_config = generatedCode;
1976
+ await fs$2.writeFile(filePath, current_user_config);
1977
+ config_path = filePath;
1978
+ log.success(`\u{1F680} Auth config file successfully created!`);
1979
+ if (envs.length !== 0) {
1980
+ log.info(
1981
+ `There are ${envs.length} environment variables for your database of choice.`
1982
+ );
1983
+ const shouldUpdateEnvs = await confirm({
1984
+ message: `Would you like us to update your ENV files?`
1985
+ });
1986
+ if (isCancel(shouldUpdateEnvs)) {
1987
+ cancel("\u270B Operation cancelled.");
1988
+ process.exit(0);
1989
+ }
1990
+ if (shouldUpdateEnvs) {
1991
+ const filesToUpdate = await multiselect({
1992
+ message: "Select the .env files you want to update",
1993
+ options: envFiles.map((x) => ({
1994
+ value: path.join(cwd, x),
1995
+ label: x
1996
+ })),
1997
+ required: false
1998
+ });
1999
+ if (isCancel(filesToUpdate)) {
2000
+ cancel("\u270B Operation cancelled.");
2001
+ process.exit(0);
2002
+ }
2003
+ if (filesToUpdate.length === 0) {
2004
+ log.info("No .env files to update. Skipping...");
2005
+ } else {
2006
+ try {
2007
+ await updateEnvs({
2008
+ files: filesToUpdate,
2009
+ envs,
2010
+ isCommented: true
2011
+ });
2012
+ } catch (error) {
2013
+ log.error(`Failed to update .env files:`);
2014
+ log.error(JSON.stringify(error, null, 2));
2015
+ process.exit(1);
2016
+ }
2017
+ log.success(`\u{1F680} ENV files successfully updated!`);
2018
+ }
2019
+ }
2020
+ }
2021
+ if (dependencies.length !== 0) {
2022
+ log.info(
2023
+ `There are ${dependencies.length} dependencies to install. (${dependencies.map((x) => chalk.green(x)).join(", ")})`
2024
+ );
2025
+ const shouldInstallDeps = await confirm({
2026
+ message: `Would you like us to install dependencies?`
2027
+ });
2028
+ if (isCancel(shouldInstallDeps)) {
2029
+ cancel("\u270B Operation cancelled.");
2030
+ process.exit(0);
2031
+ }
2032
+ if (shouldInstallDeps) {
2033
+ const s2 = spinner({ indicator: "dots" });
2034
+ if (packageManagerPreference === void 0) {
2035
+ packageManagerPreference = await getPackageManager();
2036
+ }
2037
+ s2.start(
2038
+ `Installing dependencies using ${chalk.bold(
2039
+ packageManagerPreference
2040
+ )}...`
2041
+ );
2042
+ try {
2043
+ const start = Date.now();
2044
+ await installDependencies({
2045
+ dependencies,
2046
+ packageManager: packageManagerPreference,
2047
+ cwd
2048
+ });
2049
+ s2.stop(
2050
+ `Dependencies installed ${chalk.greenBright(
2051
+ `successfully`
2052
+ )} ${chalk.gray(
2053
+ `(${formatMilliseconds(Date.now() - start)})`
2054
+ )}`
2055
+ );
2056
+ } catch (error) {
2057
+ s2.stop(
2058
+ `Failed to install dependencies using ${packageManagerPreference}:`
2059
+ );
2060
+ log.error(error.message);
2061
+ process.exit(1);
2062
+ }
2063
+ }
2064
+ }
2065
+ } catch (error) {
2066
+ log.error(`Failed to create auth config file: ${filePath}`);
2067
+ console.error(error);
2068
+ process.exit(1);
2069
+ }
2070
+ } else if (shouldCreateAuthConfig === "no") {
2071
+ log.info(`Skipping auth config file creation.`);
2072
+ }
2073
+ } else {
2074
+ log.message();
2075
+ log.success(`Found auth config file. ${chalk.gray(`(${config_path})`)}`);
2076
+ log.message();
2077
+ }
2078
+ let possibleClientPaths = [
2079
+ "auth-client.ts",
2080
+ "auth-client.tsx",
2081
+ "auth-client.js",
2082
+ "auth-client.jsx",
2083
+ "client.ts",
2084
+ "client.tsx",
2085
+ "client.js",
2086
+ "client.jsx"
2087
+ ];
2088
+ possibleClientPaths = [
2089
+ ...possibleClientPaths,
2090
+ ...possibleClientPaths.map((it) => `lib/server/${it}`),
2091
+ ...possibleClientPaths.map((it) => `server/${it}`),
2092
+ ...possibleClientPaths.map((it) => `lib/${it}`),
2093
+ ...possibleClientPaths.map((it) => `utils/${it}`)
2094
+ ];
2095
+ possibleClientPaths = [
2096
+ ...possibleClientPaths,
2097
+ ...possibleClientPaths.map((it) => `src/${it}`),
2098
+ ...possibleClientPaths.map((it) => `app/${it}`)
2099
+ ];
2100
+ let authClientConfigPath = null;
2101
+ for (const possiblePath of possibleClientPaths) {
2102
+ const doesExist = existsSync(path.join(cwd, possiblePath));
2103
+ if (doesExist) {
2104
+ authClientConfigPath = path.join(cwd, possiblePath);
2105
+ break;
2106
+ }
2107
+ }
2108
+ if (!authClientConfigPath) {
2109
+ const choice = await select({
2110
+ message: `Would you like to create an auth client config file?`,
2111
+ options: [
2112
+ { label: "Yes", value: "yes" },
2113
+ { label: "No", value: "no" }
2114
+ ]
2115
+ });
2116
+ if (isCancel(choice)) {
2117
+ cancel(`\u270B Operation cancelled.`);
2118
+ process.exit(0);
2119
+ }
2120
+ if (choice === "yes") {
2121
+ authClientConfigPath = path.join(cwd, "auth-client.ts");
2122
+ log.info(`Creating auth client config file: ${authClientConfigPath}`);
2123
+ try {
2124
+ let contents = await getDefaultAuthClientConfig({
2125
+ auth_config_path: ("./" + path.join(config_path.replace(cwd, ""))).replace(".//", "./"),
2126
+ clientPlugins: add_plugins.filter((x) => x.clientName).map((plugin) => {
2127
+ let contents2 = "";
2128
+ if (plugin.id === "one-tap") {
2129
+ contents2 = `{ clientId: "MY_CLIENT_ID" }`;
2130
+ }
2131
+ return {
2132
+ contents: contents2,
2133
+ id: plugin.id,
2134
+ name: plugin.clientName,
2135
+ imports: [
2136
+ {
2137
+ path: "better-auth/client/plugins",
2138
+ variables: [{ name: plugin.clientName }]
2139
+ }
2140
+ ]
2141
+ };
2142
+ }),
2143
+ framework
2144
+ });
2145
+ await fs$2.writeFile(authClientConfigPath, contents);
2146
+ log.success(`\u{1F680} Auth client config file successfully created!`);
2147
+ } catch (error) {
2148
+ log.error(
2149
+ `Failed to create auth client config file: ${authClientConfigPath}`
2150
+ );
2151
+ log.error(JSON.stringify(error, null, 2));
2152
+ process.exit(1);
2153
+ }
2154
+ } else if (choice === "no") {
2155
+ log.info(`Skipping auth client config file creation.`);
2156
+ }
2157
+ } else {
2158
+ log.success(
2159
+ `Found auth client config file. ${chalk.gray(
2160
+ `(${authClientConfigPath})`
2161
+ )}`
2162
+ );
2163
+ }
2164
+ if (targetEnvFile !== "none") {
2165
+ try {
2166
+ const fileContents = await fs$2.readFile(
2167
+ path.join(cwd, targetEnvFile),
2168
+ "utf8"
2169
+ );
2170
+ const parsed = parse(fileContents);
2171
+ let isMissingSecret = false;
2172
+ let isMissingUrl = false;
2173
+ if (parsed.BETTER_AUTH_SECRET === void 0) isMissingSecret = true;
2174
+ if (parsed.BETTER_AUTH_URL === void 0) isMissingUrl = true;
2175
+ if (isMissingSecret || isMissingUrl) {
2176
+ let txt = "";
2177
+ if (isMissingSecret && !isMissingUrl)
2178
+ txt = chalk.bold(`BETTER_AUTH_SECRET`);
2179
+ else if (!isMissingSecret && isMissingUrl)
2180
+ txt = chalk.bold(`BETTER_AUTH_URL`);
2181
+ else
2182
+ txt = chalk.bold.underline(`BETTER_AUTH_SECRET`) + ` and ` + chalk.bold.underline(`BETTER_AUTH_URL`);
2183
+ log.warn(`Missing ${txt} in ${targetEnvFile}`);
2184
+ const shouldAdd = await select({
2185
+ message: `Do you want to add ${txt} to ${targetEnvFile}?`,
2186
+ options: [
2187
+ { label: "Yes", value: "yes" },
2188
+ { label: "No", value: "no" },
2189
+ { label: "Choose other file(s)", value: "other" }
2190
+ ]
2191
+ });
2192
+ if (isCancel(shouldAdd)) {
2193
+ cancel(`\u270B Operation cancelled.`);
2194
+ process.exit(0);
2195
+ }
2196
+ let envs = [];
2197
+ if (isMissingSecret) {
2198
+ envs.push("BETTER_AUTH_SECRET");
2199
+ }
2200
+ if (isMissingUrl) {
2201
+ envs.push("BETTER_AUTH_URL");
2202
+ }
2203
+ if (shouldAdd === "yes") {
2204
+ try {
2205
+ await updateEnvs({
2206
+ files: [path.join(cwd, targetEnvFile)],
2207
+ envs,
2208
+ isCommented: false
2209
+ });
2210
+ } catch (error) {
2211
+ log.error(`Failed to add ENV variables to ${targetEnvFile}`);
2212
+ log.error(JSON.stringify(error, null, 2));
2213
+ process.exit(1);
2214
+ }
2215
+ log.success(`\u{1F680} ENV variables successfully added!`);
2216
+ if (isMissingUrl) {
2217
+ log.info(
2218
+ `Be sure to update your BETTER_AUTH_URL according to your app's needs.`
2219
+ );
2220
+ }
2221
+ } else if (shouldAdd === "no") {
2222
+ log.info(`Skipping ENV step.`);
2223
+ } else if (shouldAdd === "other") {
2224
+ if (!envFiles.length) {
2225
+ cancel("No env files found. Please create an env file first.");
2226
+ process.exit(0);
2227
+ }
2228
+ const envFilesToUpdate = await multiselect({
2229
+ message: "Select the .env files you want to update",
2230
+ options: envFiles.map((x) => ({
2231
+ value: path.join(cwd, x),
2232
+ label: x
2233
+ })),
2234
+ required: false
2235
+ });
2236
+ if (isCancel(envFilesToUpdate)) {
2237
+ cancel("\u270B Operation cancelled.");
2238
+ process.exit(0);
2239
+ }
2240
+ if (envFilesToUpdate.length === 0) {
2241
+ log.info("No .env files to update. Skipping...");
2242
+ } else {
2243
+ try {
2244
+ await updateEnvs({
2245
+ files: envFilesToUpdate,
2246
+ envs,
2247
+ isCommented: false
2248
+ });
2249
+ } catch (error) {
2250
+ log.error(`Failed to update .env files:`);
2251
+ log.error(JSON.stringify(error, null, 2));
2252
+ process.exit(1);
2253
+ }
2254
+ log.success(`\u{1F680} ENV files successfully updated!`);
2255
+ }
2256
+ }
2257
+ }
2258
+ } catch (error) {
2259
+ }
2260
+ }
2261
+ outro(outroText);
2262
+ console.log();
2263
+ process.exit(0);
2264
+ }
2265
+ const init = new Command("init").option("-c, --cwd <cwd>", "The working directory.", process.cwd()).option(
2266
+ "--config <config>",
2267
+ "The path to the auth configuration file. defaults to the first `auth.ts` file found."
2268
+ ).option("--skip-db", "Skip the database setup.").option("--skip-plugins", "Skip the plugins setup.").option(
2269
+ "--package-manager <package-manager>",
2270
+ "The package manager you want to use."
2271
+ ).action(initAction);
2272
+ async function getLatestNpmVersion(packageName) {
2273
+ try {
2274
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
2275
+ if (!response.ok) {
2276
+ throw new Error(`Package not found: ${response.statusText}`);
2277
+ }
2278
+ const data = await response.json();
2279
+ return data["dist-tags"].latest;
2280
+ } catch (error) {
2281
+ throw error?.message;
2282
+ }
2283
+ }
2284
+ async function getPackageManager() {
2285
+ const { hasBun, hasPnpm } = await checkPackageManagers();
2286
+ if (!hasBun && !hasPnpm) return "npm";
2287
+ const packageManagerOptions = [];
2288
+ if (hasPnpm) {
2289
+ packageManagerOptions.push({
2290
+ value: "pnpm",
2291
+ label: "pnpm",
2292
+ hint: "recommended"
2293
+ });
2294
+ }
2295
+ if (hasBun) {
2296
+ packageManagerOptions.push({
2297
+ value: "bun",
2298
+ label: "bun"
2299
+ });
2300
+ }
2301
+ packageManagerOptions.push({
2302
+ value: "npm",
2303
+ hint: "not recommended"
2304
+ });
2305
+ let packageManager = await select({
2306
+ message: "Choose a package manager",
2307
+ options: packageManagerOptions
2308
+ });
2309
+ if (isCancel(packageManager)) {
2310
+ cancel(`Operation cancelled.`);
2311
+ process.exit(0);
2312
+ }
2313
+ return packageManager;
2314
+ }
2315
+ async function getEnvFiles(cwd) {
2316
+ const files = await fs$2.readdir(cwd);
2317
+ return files.filter((x) => x.startsWith(".env"));
2318
+ }
2319
+ async function updateEnvs({
2320
+ envs,
2321
+ files,
2322
+ isCommented
2323
+ }) {
2324
+ let previouslyGeneratedSecret = null;
2325
+ for (const file of files) {
2326
+ const content = await fs$2.readFile(file, "utf8");
2327
+ const lines = content.split("\n");
2328
+ const newLines = envs.map(
2329
+ (x) => `${isCommented ? "# " : ""}${x}=${getEnvDescription(x) ?? `"some_value"`}`
2330
+ );
2331
+ newLines.push("");
2332
+ newLines.push(...lines);
2333
+ await fs$2.writeFile(file, newLines.join("\n"), "utf8");
2334
+ }
2335
+ function getEnvDescription(env) {
2336
+ if (env === "DATABASE_HOST") {
2337
+ return `"The host of your database"`;
2338
+ }
2339
+ if (env === "DATABASE_PORT") {
2340
+ return `"The port of your database"`;
2341
+ }
2342
+ if (env === "DATABASE_USER") {
2343
+ return `"The username of your database"`;
2344
+ }
2345
+ if (env === "DATABASE_PASSWORD") {
2346
+ return `"The password of your database"`;
2347
+ }
2348
+ if (env === "DATABASE_NAME") {
2349
+ return `"The name of your database"`;
2350
+ }
2351
+ if (env === "DATABASE_URL") {
2352
+ return `"The URL of your database"`;
2353
+ }
2354
+ if (env === "BETTER_AUTH_SECRET") {
2355
+ previouslyGeneratedSecret = previouslyGeneratedSecret ?? generateSecretHash();
2356
+ return `"${previouslyGeneratedSecret}"`;
2357
+ }
2358
+ if (env === "BETTER_AUTH_URL") {
2359
+ return `"http://localhost:3000" # Your APP URL`;
2360
+ }
2361
+ }
2362
+ }
2363
+
2364
+ process.on("SIGINT", () => process.exit(0));
2365
+ process.on("SIGTERM", () => process.exit(0));
2366
+ async function main() {
2367
+ const program = new Command("better-auth");
2368
+ let packageInfo = {};
2369
+ try {
2370
+ packageInfo = await getPackageInfo();
2371
+ } catch (error) {
2372
+ }
2373
+ program.addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(init).version(packageInfo.version || "1.1.2").description("Better Auth CLI");
2374
+ program.parse();
2375
+ }
2376
+ main();