@proofkit/better-auth 0.1.0
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 +21 -0
- package/README.md +11 -0
- package/dist/esm/adapter.d.ts +13 -0
- package/dist/esm/adapter.js +178 -0
- package/dist/esm/adapter.js.map +1 -0
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.d.ts +3 -0
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.js +101 -0
- package/dist/esm/better-auth-cli/utils/add-svelte-kit-env-modules.js.map +1 -0
- package/dist/esm/better-auth-cli/utils/get-config.d.ts +8 -0
- package/dist/esm/better-auth-cli/utils/get-config.js +177 -0
- package/dist/esm/better-auth-cli/utils/get-config.js.map +1 -0
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.d.ts +2 -0
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.js +25 -0
- package/dist/esm/better-auth-cli/utils/get-tsconfig-info.js.map +1 -0
- package/dist/esm/cli/index.d.ts +2 -0
- package/dist/esm/cli/index.js +86 -0
- package/dist/esm/cli/index.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/migrate.d.ts +82 -0
- package/dist/esm/migrate.js +181 -0
- package/dist/esm/migrate.js.map +1 -0
- package/dist/esm/odata/index.d.ts +21 -0
- package/dist/esm/odata/index.js +27 -0
- package/dist/esm/odata/index.js.map +1 -0
- package/package.json +70 -0
- package/src/adapter.ts +221 -0
- package/src/better-auth-cli/README.md +1 -0
- package/src/better-auth-cli/utils/add-svelte-kit-env-modules.ts +108 -0
- package/src/better-auth-cli/utils/get-config.ts +220 -0
- package/src/better-auth-cli/utils/get-tsconfig-info.ts +26 -0
- package/src/cli/index.ts +116 -0
- package/src/index.ts +1 -0
- package/src/migrate.ts +236 -0
- package/src/odata/index.ts +45 -0
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CleanedWhere,
|
|
3
|
+
createAdapter,
|
|
4
|
+
type AdapterDebugLogs,
|
|
5
|
+
} from "better-auth/adapters";
|
|
6
|
+
import { FmOdata, type FmOdataConfig } from "./odata";
|
|
7
|
+
import { prettifyError, z } from "zod/v4";
|
|
8
|
+
|
|
9
|
+
interface FileMakerAdapterConfig {
|
|
10
|
+
/**
|
|
11
|
+
* Helps you debug issues with the adapter.
|
|
12
|
+
*/
|
|
13
|
+
debugLogs?: AdapterDebugLogs;
|
|
14
|
+
/**
|
|
15
|
+
* If the table names in the schema are plural.
|
|
16
|
+
*/
|
|
17
|
+
usePlural?: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Connection details for the FileMaker server.
|
|
21
|
+
*/
|
|
22
|
+
odata: FmOdataConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type AdapterOptions = {
|
|
26
|
+
config: FileMakerAdapterConfig;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const configSchema = z.object({
|
|
30
|
+
debugLogs: z.unknown().optional(),
|
|
31
|
+
usePlural: z.boolean().optional(),
|
|
32
|
+
odata: z.object({
|
|
33
|
+
hostname: z.string(),
|
|
34
|
+
auth: z.object({ username: z.string(), password: z.string() }),
|
|
35
|
+
database: z.string().endsWith(".fmp12"),
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const defaultConfig: Required<FileMakerAdapterConfig> = {
|
|
40
|
+
debugLogs: false,
|
|
41
|
+
usePlural: false,
|
|
42
|
+
odata: {
|
|
43
|
+
hostname: "",
|
|
44
|
+
auth: { username: "", password: "" },
|
|
45
|
+
database: "",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse the where clause to an OData filter string.
|
|
51
|
+
* @param where - The where clause to parse.
|
|
52
|
+
* @returns The OData filter string.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function parseWhere(where?: CleanedWhere[]): string {
|
|
56
|
+
if (!where || where.length === 0) return "";
|
|
57
|
+
|
|
58
|
+
// Helper to quote field names with special chars or if field is 'id'
|
|
59
|
+
function quoteField(field: string, value?: any) {
|
|
60
|
+
// Never quote for null or date values (per test expectations)
|
|
61
|
+
if (value === null || value instanceof Date) return field;
|
|
62
|
+
// Always quote if field is 'id' or has space or underscore
|
|
63
|
+
if (field === "id" || /[\s_]/.test(field)) return `"${field}"`;
|
|
64
|
+
return field;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Helper to format values for OData
|
|
68
|
+
function formatValue(value: any): string {
|
|
69
|
+
if (value === null) return "null";
|
|
70
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
71
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
72
|
+
if (value instanceof Date) return `'${value.toISOString()}'`;
|
|
73
|
+
if (Array.isArray(value)) return `(${value.map(formatValue).join(",")})`;
|
|
74
|
+
return value.toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Map our operators to OData
|
|
78
|
+
const opMap: Record<string, string> = {
|
|
79
|
+
eq: "eq",
|
|
80
|
+
ne: "ne",
|
|
81
|
+
lt: "lt",
|
|
82
|
+
lte: "le",
|
|
83
|
+
gt: "gt",
|
|
84
|
+
gte: "ge",
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Build each clause
|
|
88
|
+
const clauses: string[] = [];
|
|
89
|
+
for (let i = 0; i < where.length; i++) {
|
|
90
|
+
const cond = where[i];
|
|
91
|
+
if (!cond) continue;
|
|
92
|
+
const field = quoteField(cond.field, cond.value);
|
|
93
|
+
let clause = "";
|
|
94
|
+
switch (cond.operator) {
|
|
95
|
+
case "eq":
|
|
96
|
+
case "ne":
|
|
97
|
+
case "lt":
|
|
98
|
+
case "lte":
|
|
99
|
+
case "gt":
|
|
100
|
+
case "gte":
|
|
101
|
+
clause = `${field} ${opMap[cond.operator!]} ${formatValue(cond.value)}`;
|
|
102
|
+
break;
|
|
103
|
+
case "in":
|
|
104
|
+
if (Array.isArray(cond.value)) {
|
|
105
|
+
clause = cond.value
|
|
106
|
+
.map((v) => `${field} eq ${formatValue(v)}`)
|
|
107
|
+
.join(" or ");
|
|
108
|
+
clause = `(${clause})`;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "contains":
|
|
112
|
+
clause = `contains(${field}, ${formatValue(cond.value)})`;
|
|
113
|
+
break;
|
|
114
|
+
case "starts_with":
|
|
115
|
+
clause = `startswith(${field}, ${formatValue(cond.value)})`;
|
|
116
|
+
break;
|
|
117
|
+
case "ends_with":
|
|
118
|
+
clause = `endswith(${field}, ${formatValue(cond.value)})`;
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
clause = `${field} eq ${formatValue(cond.value)}`;
|
|
122
|
+
}
|
|
123
|
+
clauses.push(clause);
|
|
124
|
+
// Add connector if not last
|
|
125
|
+
if (i < where.length - 1) {
|
|
126
|
+
clauses.push((cond.connector || "and").toLowerCase());
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return clauses.join(" ");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const FileMakerAdapter = (
|
|
133
|
+
_config: FileMakerAdapterConfig = defaultConfig,
|
|
134
|
+
) => {
|
|
135
|
+
const parsed = configSchema.loose().safeParse(_config);
|
|
136
|
+
|
|
137
|
+
if (!parsed.success) {
|
|
138
|
+
throw new Error(`Invalid configuration: ${prettifyError(parsed.error)}`);
|
|
139
|
+
}
|
|
140
|
+
const config = parsed.data;
|
|
141
|
+
|
|
142
|
+
const odata =
|
|
143
|
+
config.odata instanceof FmOdata ? config.odata : new FmOdata(config.odata);
|
|
144
|
+
const db = odata.database;
|
|
145
|
+
|
|
146
|
+
return createAdapter({
|
|
147
|
+
config: {
|
|
148
|
+
adapterId: "filemaker",
|
|
149
|
+
adapterName: "FileMaker",
|
|
150
|
+
usePlural: config.usePlural ?? false, // Whether the table names in the schema are plural.
|
|
151
|
+
debugLogs: config.debugLogs ?? false, // Whether to enable debug logs.
|
|
152
|
+
supportsJSON: false, // Whether the database supports JSON. (Default: false)
|
|
153
|
+
supportsDates: false, // Whether the database supports dates. (Default: true)
|
|
154
|
+
supportsBooleans: false, // Whether the database supports booleans. (Default: true)
|
|
155
|
+
supportsNumericIds: false, // Whether the database supports auto-incrementing numeric IDs. (Default: true)
|
|
156
|
+
},
|
|
157
|
+
adapter: ({ options }) => {
|
|
158
|
+
return {
|
|
159
|
+
options: { config },
|
|
160
|
+
create: async ({ data, model, select }) => {
|
|
161
|
+
const row = await db.table(model).create(data);
|
|
162
|
+
return row as unknown as typeof data;
|
|
163
|
+
},
|
|
164
|
+
count: async ({ model, where }) => {
|
|
165
|
+
const count = await db.table(model).count(parseWhere(where));
|
|
166
|
+
return count;
|
|
167
|
+
},
|
|
168
|
+
findOne: async ({ model, where }) => {
|
|
169
|
+
const row = await db.table(model).query({
|
|
170
|
+
filter: parseWhere(where),
|
|
171
|
+
top: 1,
|
|
172
|
+
});
|
|
173
|
+
return (row[0] as any) ?? null;
|
|
174
|
+
},
|
|
175
|
+
findMany: async ({ model, where, limit, offset, sortBy }) => {
|
|
176
|
+
const filter = parseWhere(where);
|
|
177
|
+
|
|
178
|
+
const rows = await db.table(model).query({
|
|
179
|
+
filter,
|
|
180
|
+
top: limit,
|
|
181
|
+
skip: offset,
|
|
182
|
+
orderBy: sortBy,
|
|
183
|
+
});
|
|
184
|
+
return rows.map((row) => row as any);
|
|
185
|
+
},
|
|
186
|
+
delete: async ({ model, where }) => {
|
|
187
|
+
const rows = await db.table(model).query({
|
|
188
|
+
filter: parseWhere(where),
|
|
189
|
+
top: 1,
|
|
190
|
+
select: [`"id"`],
|
|
191
|
+
});
|
|
192
|
+
const row = rows[0] as { id: string } | undefined;
|
|
193
|
+
if (!row) return;
|
|
194
|
+
await db.table(model).delete(row.id);
|
|
195
|
+
},
|
|
196
|
+
deleteMany: async ({ model, where }) => {
|
|
197
|
+
const filter = parseWhere(where);
|
|
198
|
+
const count = await db.table(model).count(filter);
|
|
199
|
+
await db.table(model).deleteMany(filter);
|
|
200
|
+
return count;
|
|
201
|
+
},
|
|
202
|
+
update: async ({ model, where, update }) => {
|
|
203
|
+
const rows = await db.table(model).query({
|
|
204
|
+
filter: parseWhere(where),
|
|
205
|
+
top: 1,
|
|
206
|
+
select: [`"id"`],
|
|
207
|
+
});
|
|
208
|
+
const row = rows[0] as { id: string } | undefined;
|
|
209
|
+
if (!row) return null;
|
|
210
|
+
const result = await db.table(model).update(row["id"], update as any);
|
|
211
|
+
return result as any;
|
|
212
|
+
},
|
|
213
|
+
updateMany: async ({ model, where, update }) => {
|
|
214
|
+
const filter = parseWhere(where);
|
|
215
|
+
const rows = await db.table(model).updateMany(filter, update as any);
|
|
216
|
+
return rows.length;
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This utility folder is a copy of code from the better-auth/cli package, since the "getConfig" function is not exported, and it doesn't support custom migrations for non-SQL databases.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export function addSvelteKitEnvModules(aliases: Record<string, string>) {
|
|
2
|
+
aliases["$env/dynamic/private"] = createDataUriModule(
|
|
3
|
+
createDynamicEnvModule(),
|
|
4
|
+
);
|
|
5
|
+
aliases["$env/dynamic/public"] = createDataUriModule(
|
|
6
|
+
createDynamicEnvModule(),
|
|
7
|
+
);
|
|
8
|
+
aliases["$env/static/private"] = createDataUriModule(
|
|
9
|
+
createStaticEnvModule(filterPrivateEnv("PUBLIC_", "")),
|
|
10
|
+
);
|
|
11
|
+
aliases["$env/static/public"] = createDataUriModule(
|
|
12
|
+
createStaticEnvModule(filterPublicEnv("PUBLIC_", "")),
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createDataUriModule(module: string) {
|
|
17
|
+
return `data:text/javascript;charset=utf-8,${encodeURIComponent(module)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createStaticEnvModule(env: Record<string, string>) {
|
|
21
|
+
const declarations = Object.keys(env)
|
|
22
|
+
.filter((k) => validIdentifier.test(k) && !reserved.has(k))
|
|
23
|
+
.map((k) => `export const ${k} = ${JSON.stringify(env[k])};`);
|
|
24
|
+
|
|
25
|
+
return `
|
|
26
|
+
${declarations.join("\n")}
|
|
27
|
+
// jiti dirty hack: .unknown
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createDynamicEnvModule() {
|
|
32
|
+
return `
|
|
33
|
+
export const env = process.env;
|
|
34
|
+
// jiti dirty hack: .unknown
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function filterPrivateEnv(publicPrefix: string, privatePrefix: string) {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
Object.entries(process.env).filter(
|
|
41
|
+
([k]) =>
|
|
42
|
+
k.startsWith(privatePrefix) &&
|
|
43
|
+
(publicPrefix === "" || !k.startsWith(publicPrefix)),
|
|
44
|
+
),
|
|
45
|
+
) as Record<string, string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function filterPublicEnv(publicPrefix: string, privatePrefix: string) {
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
Object.entries(process.env).filter(
|
|
51
|
+
([k]) =>
|
|
52
|
+
k.startsWith(publicPrefix) &&
|
|
53
|
+
(privatePrefix === "" || !k.startsWith(privatePrefix)),
|
|
54
|
+
),
|
|
55
|
+
) as Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
59
|
+
const reserved = new Set([
|
|
60
|
+
"do",
|
|
61
|
+
"if",
|
|
62
|
+
"in",
|
|
63
|
+
"for",
|
|
64
|
+
"let",
|
|
65
|
+
"new",
|
|
66
|
+
"try",
|
|
67
|
+
"var",
|
|
68
|
+
"case",
|
|
69
|
+
"else",
|
|
70
|
+
"enum",
|
|
71
|
+
"eval",
|
|
72
|
+
"null",
|
|
73
|
+
"this",
|
|
74
|
+
"true",
|
|
75
|
+
"void",
|
|
76
|
+
"with",
|
|
77
|
+
"await",
|
|
78
|
+
"break",
|
|
79
|
+
"catch",
|
|
80
|
+
"class",
|
|
81
|
+
"const",
|
|
82
|
+
"false",
|
|
83
|
+
"super",
|
|
84
|
+
"throw",
|
|
85
|
+
"while",
|
|
86
|
+
"yield",
|
|
87
|
+
"delete",
|
|
88
|
+
"export",
|
|
89
|
+
"import",
|
|
90
|
+
"public",
|
|
91
|
+
"return",
|
|
92
|
+
"static",
|
|
93
|
+
"switch",
|
|
94
|
+
"typeof",
|
|
95
|
+
"default",
|
|
96
|
+
"extends",
|
|
97
|
+
"finally",
|
|
98
|
+
"package",
|
|
99
|
+
"private",
|
|
100
|
+
"continue",
|
|
101
|
+
"debugger",
|
|
102
|
+
"function",
|
|
103
|
+
"arguments",
|
|
104
|
+
"interface",
|
|
105
|
+
"protected",
|
|
106
|
+
"implements",
|
|
107
|
+
"instanceof",
|
|
108
|
+
]);
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { loadConfig } from "c12";
|
|
2
|
+
import type { BetterAuthOptions } from "better-auth";
|
|
3
|
+
import { logger } from "better-auth";
|
|
4
|
+
import path from "path";
|
|
5
|
+
// @ts-expect-error not typed
|
|
6
|
+
import babelPresetTypeScript from "@babel/preset-typescript";
|
|
7
|
+
// @ts-expect-error not typed
|
|
8
|
+
import babelPresetReact from "@babel/preset-react";
|
|
9
|
+
import fs, { existsSync } from "fs";
|
|
10
|
+
import { BetterAuthError } from "better-auth";
|
|
11
|
+
import { addSvelteKitEnvModules } from "./add-svelte-kit-env-modules";
|
|
12
|
+
import { getTsconfigInfo } from "./get-tsconfig-info";
|
|
13
|
+
|
|
14
|
+
let possiblePaths = [
|
|
15
|
+
"auth.ts",
|
|
16
|
+
"auth.tsx",
|
|
17
|
+
"auth.js",
|
|
18
|
+
"auth.jsx",
|
|
19
|
+
"auth.server.js",
|
|
20
|
+
"auth.server.ts",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
possiblePaths = [
|
|
24
|
+
...possiblePaths,
|
|
25
|
+
...possiblePaths.map((it) => `lib/server/${it}`),
|
|
26
|
+
...possiblePaths.map((it) => `server/${it}`),
|
|
27
|
+
...possiblePaths.map((it) => `lib/${it}`),
|
|
28
|
+
...possiblePaths.map((it) => `utils/${it}`),
|
|
29
|
+
];
|
|
30
|
+
possiblePaths = [
|
|
31
|
+
...possiblePaths,
|
|
32
|
+
...possiblePaths.map((it) => `src/${it}`),
|
|
33
|
+
...possiblePaths.map((it) => `app/${it}`),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
function getPathAliases(cwd: string): Record<string, string> | null {
|
|
37
|
+
const tsConfigPath = path.join(cwd, "tsconfig.json");
|
|
38
|
+
if (!fs.existsSync(tsConfigPath)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const tsConfig = getTsconfigInfo(cwd);
|
|
43
|
+
const { paths = {}, baseUrl = "." } = tsConfig.compilerOptions || {};
|
|
44
|
+
const result: Record<string, string> = {};
|
|
45
|
+
const obj = Object.entries(paths) as [string, string[]][];
|
|
46
|
+
for (const [alias, aliasPaths] of obj) {
|
|
47
|
+
for (const aliasedPath of aliasPaths) {
|
|
48
|
+
const resolvedBaseUrl = path.join(cwd, baseUrl);
|
|
49
|
+
const finalAlias = alias.slice(-1) === "*" ? alias.slice(0, -1) : alias;
|
|
50
|
+
const finalAliasedPath =
|
|
51
|
+
aliasedPath.slice(-1) === "*"
|
|
52
|
+
? aliasedPath.slice(0, -1)
|
|
53
|
+
: aliasedPath;
|
|
54
|
+
|
|
55
|
+
result[finalAlias || ""] = path.join(resolvedBaseUrl, finalAliasedPath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
addSvelteKitEnvModules(result);
|
|
59
|
+
return result;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(error);
|
|
62
|
+
throw new BetterAuthError("Error parsing tsconfig.json");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* .tsx files are not supported by Jiti.
|
|
67
|
+
*/
|
|
68
|
+
const jitiOptions = (cwd: string) => {
|
|
69
|
+
const alias = getPathAliases(cwd) || {};
|
|
70
|
+
return {
|
|
71
|
+
transformOptions: {
|
|
72
|
+
babel: {
|
|
73
|
+
presets: [
|
|
74
|
+
[
|
|
75
|
+
babelPresetTypeScript,
|
|
76
|
+
{
|
|
77
|
+
isTSX: true,
|
|
78
|
+
allExtensions: true,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
[babelPresetReact, { runtime: "automatic" }],
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
86
|
+
alias,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
export async function getConfig({
|
|
90
|
+
cwd,
|
|
91
|
+
configPath,
|
|
92
|
+
shouldThrowOnError = false,
|
|
93
|
+
}: {
|
|
94
|
+
cwd: string;
|
|
95
|
+
configPath?: string;
|
|
96
|
+
shouldThrowOnError?: boolean;
|
|
97
|
+
}) {
|
|
98
|
+
try {
|
|
99
|
+
let configFile: BetterAuthOptions | null = null;
|
|
100
|
+
if (configPath) {
|
|
101
|
+
let resolvedPath: string = path.join(cwd, configPath);
|
|
102
|
+
if (existsSync(configPath)) resolvedPath = configPath; // If the configPath is a file, use it as is, as it means the path wasn't relative.
|
|
103
|
+
const { config } = await loadConfig<{
|
|
104
|
+
auth: {
|
|
105
|
+
options: BetterAuthOptions;
|
|
106
|
+
};
|
|
107
|
+
default?: {
|
|
108
|
+
options: BetterAuthOptions;
|
|
109
|
+
};
|
|
110
|
+
}>({
|
|
111
|
+
configFile: resolvedPath,
|
|
112
|
+
dotenv: true,
|
|
113
|
+
jitiOptions: jitiOptions(cwd),
|
|
114
|
+
});
|
|
115
|
+
if (!config.auth && !config.default) {
|
|
116
|
+
if (shouldThrowOnError) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
logger.error(
|
|
122
|
+
`[#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.`,
|
|
123
|
+
);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
configFile = config.auth?.options || config.default?.options || null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!configFile) {
|
|
130
|
+
for (const possiblePath of possiblePaths) {
|
|
131
|
+
try {
|
|
132
|
+
const { config } = await loadConfig<{
|
|
133
|
+
auth: {
|
|
134
|
+
options: BetterAuthOptions;
|
|
135
|
+
};
|
|
136
|
+
default?: {
|
|
137
|
+
options: BetterAuthOptions;
|
|
138
|
+
};
|
|
139
|
+
}>({
|
|
140
|
+
configFile: possiblePath,
|
|
141
|
+
jitiOptions: jitiOptions(cwd),
|
|
142
|
+
});
|
|
143
|
+
const hasConfig = Object.keys(config).length > 0;
|
|
144
|
+
if (hasConfig) {
|
|
145
|
+
configFile =
|
|
146
|
+
config.auth?.options || config.default?.options || null;
|
|
147
|
+
if (!configFile) {
|
|
148
|
+
if (shouldThrowOnError) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
"Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth.",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
logger.error("[#better-auth]: Couldn't read your auth config.");
|
|
154
|
+
console.log("");
|
|
155
|
+
logger.info(
|
|
156
|
+
"[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth.",
|
|
157
|
+
);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
if (
|
|
164
|
+
typeof e === "object" &&
|
|
165
|
+
e &&
|
|
166
|
+
"message" in e &&
|
|
167
|
+
typeof e.message === "string" &&
|
|
168
|
+
e.message.includes(
|
|
169
|
+
"This module cannot be imported from a Client Component module",
|
|
170
|
+
)
|
|
171
|
+
) {
|
|
172
|
+
if (shouldThrowOnError) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`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.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
logger.error(
|
|
178
|
+
`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.`,
|
|
179
|
+
);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
if (shouldThrowOnError) {
|
|
183
|
+
throw e;
|
|
184
|
+
}
|
|
185
|
+
logger.error("[#better-auth]: Couldn't read your auth config.", e);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return configFile;
|
|
191
|
+
} catch (e) {
|
|
192
|
+
if (
|
|
193
|
+
typeof e === "object" &&
|
|
194
|
+
e &&
|
|
195
|
+
"message" in e &&
|
|
196
|
+
typeof e.message === "string" &&
|
|
197
|
+
e.message.includes(
|
|
198
|
+
"This module cannot be imported from a Client Component module",
|
|
199
|
+
)
|
|
200
|
+
) {
|
|
201
|
+
if (shouldThrowOnError) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`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.`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
logger.error(
|
|
207
|
+
`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.`,
|
|
208
|
+
);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
if (shouldThrowOnError) {
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
logger.error("Couldn't read your auth config.", e);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export { possiblePaths };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
|
|
4
|
+
export function stripJsonComments(jsonString: string): string {
|
|
5
|
+
return jsonString
|
|
6
|
+
.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) =>
|
|
7
|
+
g ? "" : m,
|
|
8
|
+
)
|
|
9
|
+
.replace(/,(?=\s*[}\]])/g, "");
|
|
10
|
+
}
|
|
11
|
+
export function getTsconfigInfo(cwd?: string, flatPath?: string) {
|
|
12
|
+
let tsConfigPath: string;
|
|
13
|
+
if (flatPath) {
|
|
14
|
+
tsConfigPath = flatPath;
|
|
15
|
+
} else {
|
|
16
|
+
tsConfigPath = cwd
|
|
17
|
+
? path.join(cwd, "tsconfig.json")
|
|
18
|
+
: path.join("tsconfig.json");
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const text = fs.readFileSync(tsConfigPath, "utf-8");
|
|
22
|
+
return JSON.parse(stripJsonComments(text));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node --no-warnings
|
|
2
|
+
import { Command } from "@commander-js/extra-typings";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
executeMigration,
|
|
7
|
+
planMigration,
|
|
8
|
+
prettyPrintMigrationPlan,
|
|
9
|
+
} from "../migrate";
|
|
10
|
+
import { getAdapter, getAuthTables } from "better-auth/db";
|
|
11
|
+
import { getConfig } from "../better-auth-cli/utils/get-config";
|
|
12
|
+
import { logger } from "better-auth";
|
|
13
|
+
import { BasicAuth, Connection, Database } from "fm-odata-client";
|
|
14
|
+
import prompts from "prompts";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { AdapterOptions } from "../adapter";
|
|
17
|
+
import { FmOdata } from "../odata";
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
const program = new Command();
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command("migrate", { isDefault: true })
|
|
24
|
+
.option(
|
|
25
|
+
"--cwd <path>",
|
|
26
|
+
"Path to the current working directory",
|
|
27
|
+
process.cwd(),
|
|
28
|
+
)
|
|
29
|
+
.option("--config <path>", "Path to the config file")
|
|
30
|
+
.option("-u, --username <username>", "Full Access Username")
|
|
31
|
+
.option("-p, --password <password>", "Full Access Password")
|
|
32
|
+
.option("-y, --yes", "Skip confirmation", false)
|
|
33
|
+
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
const cwd = options.cwd;
|
|
36
|
+
if (!fs.existsSync(cwd)) {
|
|
37
|
+
logger.error(`The directory "${cwd}" does not exist.`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const config = await getConfig({
|
|
42
|
+
cwd,
|
|
43
|
+
configPath: options.config,
|
|
44
|
+
});
|
|
45
|
+
if (!config) {
|
|
46
|
+
logger.error(
|
|
47
|
+
"No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.",
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const adapter = await getAdapter(config).catch((e) => {
|
|
53
|
+
logger.error(e.message);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (adapter.id !== "filemaker") {
|
|
58
|
+
logger.error(
|
|
59
|
+
"This generator is only compatible with the FileMaker adapter.",
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const betterAuthSchema = getAuthTables(config);
|
|
65
|
+
|
|
66
|
+
const adapterConfig = (adapter.options as AdapterOptions).config;
|
|
67
|
+
const db = new FmOdata({
|
|
68
|
+
...adapterConfig.odata,
|
|
69
|
+
auth:
|
|
70
|
+
// If the username and password are provided in the CLI, use them to authenticate instead of what's in the config file.
|
|
71
|
+
options.username && options.password
|
|
72
|
+
? {
|
|
73
|
+
username: options.username,
|
|
74
|
+
password: options.password,
|
|
75
|
+
}
|
|
76
|
+
: adapterConfig.odata.auth,
|
|
77
|
+
}).database;
|
|
78
|
+
|
|
79
|
+
const migrationPlan = await planMigration(db, betterAuthSchema);
|
|
80
|
+
|
|
81
|
+
if (migrationPlan.length === 0) {
|
|
82
|
+
logger.info("No changes to apply. Database is up to date.");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!options.yes) {
|
|
87
|
+
prettyPrintMigrationPlan(migrationPlan);
|
|
88
|
+
|
|
89
|
+
if (migrationPlan.length > 0) {
|
|
90
|
+
console.log(
|
|
91
|
+
chalk.gray(
|
|
92
|
+
"💡 Tip: You can use the --yes flag to skip this confirmation.",
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { confirm } = await prompts({
|
|
98
|
+
type: "confirm",
|
|
99
|
+
name: "confirm",
|
|
100
|
+
message: "Apply the above changes to your database?",
|
|
101
|
+
});
|
|
102
|
+
if (!confirm) {
|
|
103
|
+
logger.error("Schema changes not applied.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await executeMigration(db, migrationPlan);
|
|
109
|
+
|
|
110
|
+
logger.info("Migration applied successfully.");
|
|
111
|
+
});
|
|
112
|
+
await program.parseAsync(process.argv);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().catch(console.error);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileMakerAdapter } from "./adapter";
|