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