@better-auth/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +17 -0
- package/dist/index.mjs +610 -0
- package/package.json +39 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
Copyright (c) 2024 - present, Bereket Engida
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
5
|
+
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
6
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
7
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
|
8
|
+
is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
|
11
|
+
substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
14
|
+
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
15
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
16
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
17
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command3 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/migrate.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
import yoctoSpinner from "yocto-spinner";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import prompts from "prompts";
|
|
14
|
+
import { logger as logger2 } from "better-auth";
|
|
15
|
+
import { getAdapter, getMigrations } from "better-auth/db";
|
|
16
|
+
|
|
17
|
+
// src/utils/get-config.ts
|
|
18
|
+
import { loadConfig } from "c12";
|
|
19
|
+
import { logger } from "better-auth";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import babelPresetTypescript from "@babel/preset-typescript";
|
|
22
|
+
import babelPresetReact from "@babel/preset-react";
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import { BetterAuthError } from "better-auth";
|
|
25
|
+
|
|
26
|
+
// src/utils/add-svelte-kit-env-modules.ts
|
|
27
|
+
function addSvelteKitEnvModules(aliases) {
|
|
28
|
+
aliases["$env/dynamic/private"] = createDataUriModule(
|
|
29
|
+
createDynamicEnvModule()
|
|
30
|
+
);
|
|
31
|
+
aliases["$env/dynamic/public"] = createDataUriModule(
|
|
32
|
+
createDynamicEnvModule()
|
|
33
|
+
);
|
|
34
|
+
aliases["$env/static/private"] = createDataUriModule(
|
|
35
|
+
createStaticEnvModule(filterPrivateEnv("PUBLIC_", ""))
|
|
36
|
+
);
|
|
37
|
+
aliases["$env/static/public"] = createDataUriModule(
|
|
38
|
+
createStaticEnvModule(filterPublicEnv("PUBLIC_", ""))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function createDataUriModule(module) {
|
|
42
|
+
return `data:text/javascript;charset=utf-8,${encodeURIComponent(module)}`;
|
|
43
|
+
}
|
|
44
|
+
function createStaticEnvModule(env) {
|
|
45
|
+
const declarations = Object.keys(env).filter((k) => validIdentifier.test(k) && !reserved.has(k)).map((k) => `export const ${k} = ${JSON.stringify(env[k])};`);
|
|
46
|
+
return `
|
|
47
|
+
${declarations.join("\n")}
|
|
48
|
+
// jiti dirty hack: .unknown
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
function createDynamicEnvModule() {
|
|
52
|
+
return `
|
|
53
|
+
export const env = process.env;
|
|
54
|
+
// jiti dirty hack: .unknown
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
function filterPrivateEnv(publicPrefix, privatePrefix) {
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(process.env).filter(
|
|
60
|
+
([k]) => k.startsWith(privatePrefix) && (publicPrefix === "" || !k.startsWith(publicPrefix))
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function filterPublicEnv(publicPrefix, privatePrefix) {
|
|
65
|
+
return Object.fromEntries(
|
|
66
|
+
Object.entries(process.env).filter(
|
|
67
|
+
([k]) => k.startsWith(publicPrefix) && (privatePrefix === "" || !k.startsWith(privatePrefix))
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
72
|
+
var reserved = /* @__PURE__ */ new Set([
|
|
73
|
+
"do",
|
|
74
|
+
"if",
|
|
75
|
+
"in",
|
|
76
|
+
"for",
|
|
77
|
+
"let",
|
|
78
|
+
"new",
|
|
79
|
+
"try",
|
|
80
|
+
"var",
|
|
81
|
+
"case",
|
|
82
|
+
"else",
|
|
83
|
+
"enum",
|
|
84
|
+
"eval",
|
|
85
|
+
"null",
|
|
86
|
+
"this",
|
|
87
|
+
"true",
|
|
88
|
+
"void",
|
|
89
|
+
"with",
|
|
90
|
+
"await",
|
|
91
|
+
"break",
|
|
92
|
+
"catch",
|
|
93
|
+
"class",
|
|
94
|
+
"const",
|
|
95
|
+
"false",
|
|
96
|
+
"super",
|
|
97
|
+
"throw",
|
|
98
|
+
"while",
|
|
99
|
+
"yield",
|
|
100
|
+
"delete",
|
|
101
|
+
"export",
|
|
102
|
+
"import",
|
|
103
|
+
"public",
|
|
104
|
+
"return",
|
|
105
|
+
"static",
|
|
106
|
+
"switch",
|
|
107
|
+
"typeof",
|
|
108
|
+
"default",
|
|
109
|
+
"extends",
|
|
110
|
+
"finally",
|
|
111
|
+
"package",
|
|
112
|
+
"private",
|
|
113
|
+
"continue",
|
|
114
|
+
"debugger",
|
|
115
|
+
"function",
|
|
116
|
+
"arguments",
|
|
117
|
+
"interface",
|
|
118
|
+
"protected",
|
|
119
|
+
"implements",
|
|
120
|
+
"instanceof"
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
// src/utils/get-config.ts
|
|
124
|
+
var possiblePaths = ["auth.ts", "auth.tsx"];
|
|
125
|
+
possiblePaths = [
|
|
126
|
+
...possiblePaths,
|
|
127
|
+
...possiblePaths.map((it) => `lib/server${it}`),
|
|
128
|
+
...possiblePaths.map((it) => `lib/${it}`),
|
|
129
|
+
...possiblePaths.map((it) => `utils/${it}`)
|
|
130
|
+
];
|
|
131
|
+
possiblePaths = [
|
|
132
|
+
...possiblePaths,
|
|
133
|
+
...possiblePaths.map((it) => `src/${it}`),
|
|
134
|
+
...possiblePaths.map((it) => `app/${it}`)
|
|
135
|
+
];
|
|
136
|
+
function stripJsonComments(jsonString) {
|
|
137
|
+
return jsonString.replace(
|
|
138
|
+
/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g,
|
|
139
|
+
(m, g) => g ? "" : m
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function getPathAliases(cwd) {
|
|
143
|
+
const tsConfigPath = path.join(cwd, "tsconfig.json");
|
|
144
|
+
if (!fs.existsSync(tsConfigPath)) {
|
|
145
|
+
logger.warn("[#better-auth]: tsconfig.json not found.");
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const tsConfigContent = fs.readFileSync(tsConfigPath, "utf8");
|
|
150
|
+
const strippedTsConfigContent = stripJsonComments(tsConfigContent);
|
|
151
|
+
const tsConfig = JSON.parse(strippedTsConfigContent);
|
|
152
|
+
const { paths = {} } = tsConfig.compilerOptions || {};
|
|
153
|
+
const result = {};
|
|
154
|
+
const obj = Object.entries(paths);
|
|
155
|
+
for (const [alias, aliasPaths] of obj) {
|
|
156
|
+
for (const _ of aliasPaths) {
|
|
157
|
+
result[alias[0] || ""] = "../";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
addSvelteKitEnvModules(result);
|
|
161
|
+
return result;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(error);
|
|
164
|
+
throw new BetterAuthError("Error parsing tsconfig.json");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
var jitiOptions = (cwd) => {
|
|
168
|
+
const alias = getPathAliases(cwd) || {};
|
|
169
|
+
return {
|
|
170
|
+
transformOptions: {
|
|
171
|
+
babel: {
|
|
172
|
+
presets: [
|
|
173
|
+
[
|
|
174
|
+
babelPresetTypescript,
|
|
175
|
+
{
|
|
176
|
+
isTSX: true,
|
|
177
|
+
allExtensions: true
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
[babelPresetReact, { runtime: "automatic" }]
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
185
|
+
alias
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
async function getConfig({
|
|
189
|
+
cwd,
|
|
190
|
+
configPath
|
|
191
|
+
}) {
|
|
192
|
+
try {
|
|
193
|
+
let configFile = null;
|
|
194
|
+
if (configPath) {
|
|
195
|
+
const { config } = await loadConfig({
|
|
196
|
+
configFile: path.join(cwd, configPath),
|
|
197
|
+
dotenv: true,
|
|
198
|
+
jitiOptions: jitiOptions(cwd)
|
|
199
|
+
});
|
|
200
|
+
if (!config.auth && !config.default) {
|
|
201
|
+
logger.error(
|
|
202
|
+
"[#better-auth]: Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth."
|
|
203
|
+
);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
configFile = config.auth?.options || config.default?.options || null;
|
|
207
|
+
}
|
|
208
|
+
if (!configFile) {
|
|
209
|
+
for (const possiblePath of possiblePaths) {
|
|
210
|
+
try {
|
|
211
|
+
const { config } = await loadConfig({
|
|
212
|
+
configFile: possiblePath,
|
|
213
|
+
jitiOptions: jitiOptions(cwd)
|
|
214
|
+
});
|
|
215
|
+
const hasConfig = Object.keys(config).length > 0;
|
|
216
|
+
if (hasConfig) {
|
|
217
|
+
configFile = config.auth?.options || config.default?.options || null;
|
|
218
|
+
if (!configFile) {
|
|
219
|
+
logger.error("[#better-auth]: Couldn't read your auth config.");
|
|
220
|
+
logger.break();
|
|
221
|
+
logger.info(
|
|
222
|
+
"[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth."
|
|
223
|
+
);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
logger.error("[#better-auth]: Couldn't read your auth config.", e);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return configFile;
|
|
235
|
+
} catch (e) {
|
|
236
|
+
logger.error("Couldn't read your auth config.");
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/commands/migrate.ts
|
|
242
|
+
var migrate = new Command("migrate").option(
|
|
243
|
+
"-c, --cwd <cwd>",
|
|
244
|
+
"the working directory. defaults to the current directory.",
|
|
245
|
+
process.cwd()
|
|
246
|
+
).option(
|
|
247
|
+
"--config <config>",
|
|
248
|
+
"the path to the configuration file. defaults to the first configuration file found."
|
|
249
|
+
).option("--y", "").action(async (opts) => {
|
|
250
|
+
const options = z.object({
|
|
251
|
+
cwd: z.string(),
|
|
252
|
+
config: z.string().optional()
|
|
253
|
+
}).parse(opts);
|
|
254
|
+
const cwd = path2.resolve(options.cwd);
|
|
255
|
+
if (!existsSync(cwd)) {
|
|
256
|
+
logger2.error(`The directory "${cwd}" does not exist.`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
const config = await getConfig({
|
|
260
|
+
cwd,
|
|
261
|
+
configPath: options.config
|
|
262
|
+
});
|
|
263
|
+
if (!config) {
|
|
264
|
+
logger2.error(
|
|
265
|
+
"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag."
|
|
266
|
+
);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const db = await getAdapter(config, true);
|
|
270
|
+
if (!db) {
|
|
271
|
+
logger2.error(
|
|
272
|
+
"Invalid database configuration. Make sure you're not using adapters. Migrate command only works with built-in Kysely adapter."
|
|
273
|
+
);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
if (db.id !== "kysely") {
|
|
277
|
+
logger2.error("Migrate command only works with built-in Kysely adapter.");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
const spinner = yoctoSpinner({ text: "preparing migration..." }).start();
|
|
281
|
+
const { toBeAdded, toBeCreated, runMigrations } = await getMigrations(config);
|
|
282
|
+
if (!toBeAdded.length && !toBeCreated.length) {
|
|
283
|
+
spinner.stop();
|
|
284
|
+
logger2.success("\u{1F680} No migrations needed.");
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
287
|
+
spinner.stop();
|
|
288
|
+
logger2.info(`\u{1F511} The migration will affect the following:`);
|
|
289
|
+
for (const table of [...toBeCreated, ...toBeAdded]) {
|
|
290
|
+
console.log(
|
|
291
|
+
"->",
|
|
292
|
+
chalk.magenta(Object.keys(table.fields).join(", ")),
|
|
293
|
+
chalk.white("fields on"),
|
|
294
|
+
chalk.yellow(`${table.table}`),
|
|
295
|
+
chalk.white("table.")
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const { migrate: migrate2 } = await prompts({
|
|
299
|
+
type: "confirm",
|
|
300
|
+
name: "migrate",
|
|
301
|
+
message: "Are you sure you want to run these migrations?",
|
|
302
|
+
initial: false
|
|
303
|
+
});
|
|
304
|
+
if (!migrate2) {
|
|
305
|
+
logger2.info("Migration cancelled.");
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
308
|
+
spinner?.start("migrating...");
|
|
309
|
+
await runMigrations();
|
|
310
|
+
spinner.stop();
|
|
311
|
+
logger2.success("\u{1F680} migration was completed successfully!");
|
|
312
|
+
process.exit(0);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// src/commands/generate.ts
|
|
316
|
+
import { Command as Command2 } from "commander";
|
|
317
|
+
import { z as z2 } from "zod";
|
|
318
|
+
import { existsSync as existsSync4 } from "fs";
|
|
319
|
+
import path4 from "path";
|
|
320
|
+
import { logger as logger4 } from "better-auth";
|
|
321
|
+
import yoctoSpinner2 from "yocto-spinner";
|
|
322
|
+
import prompts2 from "prompts";
|
|
323
|
+
import fs3 from "fs/promises";
|
|
324
|
+
import chalk2 from "chalk";
|
|
325
|
+
import { getAdapter as getAdapter2 } from "better-auth/db";
|
|
326
|
+
|
|
327
|
+
// src/generators/index.ts
|
|
328
|
+
import { logger as logger3 } from "better-auth";
|
|
329
|
+
|
|
330
|
+
// src/generators/drizzle.ts
|
|
331
|
+
import { getAuthTables } from "better-auth/db";
|
|
332
|
+
import { existsSync as existsSync2 } from "fs";
|
|
333
|
+
var generateDrizzleSchema = async ({
|
|
334
|
+
options,
|
|
335
|
+
file,
|
|
336
|
+
adapter
|
|
337
|
+
}) => {
|
|
338
|
+
const tables = getAuthTables(options);
|
|
339
|
+
const filePath = file || "./auth-schema.ts";
|
|
340
|
+
const databaseType = adapter.options?.provider;
|
|
341
|
+
const timestampAndBoolean = databaseType !== "sqlite" ? "timestamp, boolean" : "";
|
|
342
|
+
const int = databaseType === "mysql" ? "int" : "integer";
|
|
343
|
+
let code = `import { ${databaseType}Table, text, ${int}, ${timestampAndBoolean} } from "drizzle-orm/${databaseType}-core";
|
|
344
|
+
`;
|
|
345
|
+
const fileExist = existsSync2(filePath);
|
|
346
|
+
for (const table in tables) {
|
|
347
|
+
let getType2 = function(name, type) {
|
|
348
|
+
if (type === "string") {
|
|
349
|
+
return `text('${name}')`;
|
|
350
|
+
}
|
|
351
|
+
if (type === "number") {
|
|
352
|
+
return `${int}('${name}')`;
|
|
353
|
+
}
|
|
354
|
+
if (type === "boolean") {
|
|
355
|
+
if (databaseType === "sqlite") {
|
|
356
|
+
return `integer('${name}', {
|
|
357
|
+
mode: "boolean"
|
|
358
|
+
})`;
|
|
359
|
+
}
|
|
360
|
+
return `boolean('${name}')`;
|
|
361
|
+
}
|
|
362
|
+
if (type === "date") {
|
|
363
|
+
if (databaseType === "sqlite") {
|
|
364
|
+
return `integer('${name}', {
|
|
365
|
+
mode: "timestamp"
|
|
366
|
+
})`;
|
|
367
|
+
}
|
|
368
|
+
return `timestamp('${name}')`;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
var getType = getType2;
|
|
372
|
+
const tableName = tables[table].tableName;
|
|
373
|
+
const fields = tables[table].fields;
|
|
374
|
+
const schema = `export const ${table} = ${databaseType}Table("${tableName}", {
|
|
375
|
+
id: text("id").primaryKey(),
|
|
376
|
+
${Object.keys(fields).map((field) => {
|
|
377
|
+
const attr = fields[field];
|
|
378
|
+
return `${field}: ${getType2(field, attr.type)}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${attr.references.model}.${attr.references.field})` : ""}`;
|
|
379
|
+
}).join(",\n ")}
|
|
380
|
+
});`;
|
|
381
|
+
code += `
|
|
382
|
+
${schema}
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
code,
|
|
387
|
+
fileName: filePath,
|
|
388
|
+
overwrite: fileExist
|
|
389
|
+
};
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/generators/prisma.ts
|
|
393
|
+
import { getAuthTables as getAuthTables2 } from "better-auth/db";
|
|
394
|
+
import { produceSchema } from "@mrleebo/prisma-ast";
|
|
395
|
+
import { existsSync as existsSync3 } from "fs";
|
|
396
|
+
import path3 from "path";
|
|
397
|
+
import fs2 from "fs/promises";
|
|
398
|
+
import { capitalizeFirstLetter } from "better-auth";
|
|
399
|
+
var generatePrismaSchema = async ({
|
|
400
|
+
adapter,
|
|
401
|
+
options,
|
|
402
|
+
file
|
|
403
|
+
}) => {
|
|
404
|
+
const provider = adapter.options?.provider || "pg";
|
|
405
|
+
const tables = getAuthTables2(options);
|
|
406
|
+
const filePath = file || "./prisma/schema.prisma";
|
|
407
|
+
const schemaPrismaExist = existsSync3(path3.join(process.cwd(), filePath));
|
|
408
|
+
let schemaPrisma = "";
|
|
409
|
+
if (schemaPrismaExist) {
|
|
410
|
+
schemaPrisma = await fs2.readFile(
|
|
411
|
+
path3.join(process.cwd(), filePath),
|
|
412
|
+
"utf-8"
|
|
413
|
+
);
|
|
414
|
+
} else {
|
|
415
|
+
schemaPrisma = getNewPrisma(provider);
|
|
416
|
+
}
|
|
417
|
+
const schema = produceSchema(schemaPrisma, (builder) => {
|
|
418
|
+
for (const table in tables) {
|
|
419
|
+
let getType2 = function(type, isOptional) {
|
|
420
|
+
if (type === "string") {
|
|
421
|
+
return isOptional ? "String?" : "String";
|
|
422
|
+
}
|
|
423
|
+
if (type === "number") {
|
|
424
|
+
return isOptional ? "Int?" : "Int";
|
|
425
|
+
}
|
|
426
|
+
if (type === "boolean") {
|
|
427
|
+
return isOptional ? "Boolean?" : "Boolean";
|
|
428
|
+
}
|
|
429
|
+
if (type === "date") {
|
|
430
|
+
return isOptional ? "DateTime?" : "DateTime";
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
var getType = getType2;
|
|
434
|
+
const fields = tables[table]?.fields;
|
|
435
|
+
const originalTable = tables[table]?.tableName;
|
|
436
|
+
const tableName = capitalizeFirstLetter(originalTable || "");
|
|
437
|
+
const prismaModel = builder.findByType("model", {
|
|
438
|
+
name: tableName
|
|
439
|
+
});
|
|
440
|
+
!prismaModel && builder.model(tableName).field("id", "String").attribute("id");
|
|
441
|
+
for (const field in fields) {
|
|
442
|
+
const attr = fields[field];
|
|
443
|
+
if (prismaModel) {
|
|
444
|
+
const isAlreadyExist = builder.findByType("field", {
|
|
445
|
+
name: field,
|
|
446
|
+
within: prismaModel.properties
|
|
447
|
+
});
|
|
448
|
+
if (isAlreadyExist) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
builder.model(tableName).field(field, getType2(attr.type, !attr?.required));
|
|
453
|
+
if (attr.unique) {
|
|
454
|
+
builder.model(tableName).blockAttribute(`unique([${field}])`);
|
|
455
|
+
}
|
|
456
|
+
if (attr.references) {
|
|
457
|
+
builder.model(tableName).field(
|
|
458
|
+
`${attr.references.model.toLowerCase()}`,
|
|
459
|
+
capitalizeFirstLetter(attr.references.model)
|
|
460
|
+
).attribute(
|
|
461
|
+
`relation(fields: [${field}], references: [${attr.references.field}], onDelete: Cascade)`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const hasAttribute = builder.findByType("attribute", {
|
|
466
|
+
name: "map",
|
|
467
|
+
within: prismaModel?.properties
|
|
468
|
+
});
|
|
469
|
+
if (originalTable !== tableName && !hasAttribute) {
|
|
470
|
+
builder.model(tableName).blockAttribute("map", originalTable);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
return {
|
|
475
|
+
code: schema.trim() === schemaPrisma.trim() ? "" : schema,
|
|
476
|
+
fileName: filePath
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
var getNewPrisma = (provider) => `generator client {
|
|
480
|
+
provider = "prisma-client-js"
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
datasource db {
|
|
484
|
+
provider = "${provider}"
|
|
485
|
+
url = ${provider === "sqlite" ? `"file:./dev.db"` : `env("DATABASE_URL")`}
|
|
486
|
+
}`;
|
|
487
|
+
|
|
488
|
+
// src/generators/index.ts
|
|
489
|
+
var adapters = {
|
|
490
|
+
prisma: generatePrismaSchema,
|
|
491
|
+
drizzle: generateDrizzleSchema
|
|
492
|
+
};
|
|
493
|
+
var getGenerator = (opts) => {
|
|
494
|
+
const adapter = opts.adapter;
|
|
495
|
+
const generator = adapter.id in adapters ? adapters[adapter.id] : null;
|
|
496
|
+
if (!generator) {
|
|
497
|
+
logger3.error(`${adapter.id} is not supported.`);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
return generator(opts);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/commands/generate.ts
|
|
504
|
+
var generate = new Command2("generate").option(
|
|
505
|
+
"-c, --cwd <cwd>",
|
|
506
|
+
"the working directory. defaults to the current directory.",
|
|
507
|
+
process.cwd()
|
|
508
|
+
).option(
|
|
509
|
+
"--config <config>",
|
|
510
|
+
"the path to the configuration file. defaults to the first configuration file found."
|
|
511
|
+
).option("--output <output>", "the file to output to the generated schema").option("--y", "").action(async (opts) => {
|
|
512
|
+
const options = z2.object({
|
|
513
|
+
cwd: z2.string(),
|
|
514
|
+
config: z2.string().optional(),
|
|
515
|
+
output: z2.string().optional()
|
|
516
|
+
}).parse(opts);
|
|
517
|
+
const cwd = path4.resolve(options.cwd);
|
|
518
|
+
if (!existsSync4(cwd)) {
|
|
519
|
+
logger4.error(`The directory "${cwd}" does not exist.`);
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
const config = await getConfig({
|
|
523
|
+
cwd,
|
|
524
|
+
configPath: options.config
|
|
525
|
+
});
|
|
526
|
+
if (!config) {
|
|
527
|
+
logger4.error(
|
|
528
|
+
"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag."
|
|
529
|
+
);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const spinner = yoctoSpinner2({ text: "preparing schema..." }).start();
|
|
533
|
+
const adapter = await getAdapter2(config, true).catch((e) => {
|
|
534
|
+
logger4.error(e.message);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
});
|
|
537
|
+
const schema = await getGenerator({
|
|
538
|
+
adapter,
|
|
539
|
+
file: options.output,
|
|
540
|
+
options: config
|
|
541
|
+
});
|
|
542
|
+
spinner.stop();
|
|
543
|
+
if (!schema.code) {
|
|
544
|
+
logger4.success("Your schema is already up to date.");
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}
|
|
547
|
+
if (schema.append || schema.overwrite) {
|
|
548
|
+
const { confirm: confirm2 } = await prompts2({
|
|
549
|
+
type: "confirm",
|
|
550
|
+
name: "confirm",
|
|
551
|
+
message: `The file ${schema.fileName} already exists. Do you want to ${chalk2.yellow(
|
|
552
|
+
`${schema.overwrite ? "overwrite" : "append"}`
|
|
553
|
+
)} the schema to the file?`
|
|
554
|
+
});
|
|
555
|
+
if (confirm2) {
|
|
556
|
+
const exist = existsSync4(path4.join(cwd, schema.fileName));
|
|
557
|
+
if (!exist) {
|
|
558
|
+
await fs3.mkdir(path4.dirname(path4.join(cwd, schema.fileName)), {
|
|
559
|
+
recursive: true
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
if (schema.overwrite) {
|
|
563
|
+
await fs3.writeFile(path4.join(cwd, schema.fileName), schema.code);
|
|
564
|
+
} else {
|
|
565
|
+
await fs3.appendFile(path4.join(cwd, schema.fileName), schema.code);
|
|
566
|
+
}
|
|
567
|
+
logger4.success(`\u{1F680} schema was appended successfully!`);
|
|
568
|
+
process.exit(0);
|
|
569
|
+
} else {
|
|
570
|
+
logger4.error("Schema generation aborted.");
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const { confirm } = await prompts2({
|
|
575
|
+
type: "confirm",
|
|
576
|
+
name: "confirm",
|
|
577
|
+
message: `Do you want to generate the schema to ${chalk2.yellow(
|
|
578
|
+
schema.fileName
|
|
579
|
+
)}?`
|
|
580
|
+
});
|
|
581
|
+
if (!confirm) {
|
|
582
|
+
logger4.error("Schema generation aborted.");
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
if (!options.output) {
|
|
586
|
+
const dirExist = existsSync4(
|
|
587
|
+
path4.dirname(path4.join(cwd, schema.fileName))
|
|
588
|
+
);
|
|
589
|
+
if (!dirExist) {
|
|
590
|
+
await fs3.mkdir(path4.dirname(path4.join(cwd, schema.fileName)), {
|
|
591
|
+
recursive: true
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
await fs3.writeFile(
|
|
596
|
+
options.output || path4.join(cwd, schema.fileName),
|
|
597
|
+
schema.code
|
|
598
|
+
);
|
|
599
|
+
logger4.success(`\u{1F680} schema was generated successfully!`);
|
|
600
|
+
process.exit(0);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// src/index.ts
|
|
604
|
+
import "dotenv/config";
|
|
605
|
+
async function main() {
|
|
606
|
+
const program = new Command3();
|
|
607
|
+
program.name("better-auth").addCommand(migrate).addCommand(generate).version("0.0.1").description("Better Auth CLI");
|
|
608
|
+
program.parse();
|
|
609
|
+
}
|
|
610
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-auth/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "The CLI for BetterAuth",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/better-auth/better-auth",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.mjs",
|
|
12
|
+
"bin": "./dist/index.mjs",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"tsup": "^8.2.4",
|
|
15
|
+
"typescript": "^5.6.3"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@babel/preset-react": "^7.24.7",
|
|
19
|
+
"@babel/preset-typescript": "^7.24.7",
|
|
20
|
+
"@mrleebo/prisma-ast": "^0.12.0",
|
|
21
|
+
"@types/prompts": "^2.4.9",
|
|
22
|
+
"c12": "^2.0.1",
|
|
23
|
+
"chalk": "^5.3.0",
|
|
24
|
+
"commander": "^12.1.0",
|
|
25
|
+
"dotenv": "^16.4.5",
|
|
26
|
+
"prompts": "^2.4.2",
|
|
27
|
+
"tinyexec": "^0.3.0",
|
|
28
|
+
"yocto-spinner": "^0.1.1",
|
|
29
|
+
"zod": "^3.23.8",
|
|
30
|
+
"better-auth": "0.4.9-beta.7"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "tsup --watch --sourcemap",
|
|
37
|
+
"build": "tsup --clean"
|
|
38
|
+
}
|
|
39
|
+
}
|