auth 1.7.2 ā 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.mts +5 -0
- package/dist/api.mjs +40 -6
- package/dist/index.mjs +278 -66
- package/package.json +6 -6
package/dist/api.d.mts
CHANGED
|
@@ -12,6 +12,11 @@ interface SchemaGeneratorResult {
|
|
|
12
12
|
* without corrupting the rows it already holds.
|
|
13
13
|
*/
|
|
14
14
|
unsafeChanges?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Columns the database requires that Better Auth never writes. No generated
|
|
17
|
+
* migration removes them; each entry names the change that does.
|
|
18
|
+
*/
|
|
19
|
+
schemaProblems?: string[];
|
|
15
20
|
}
|
|
16
21
|
interface SchemaGenerator {
|
|
17
22
|
<Options extends BetterAuthOptions>(opts: {
|
package/dist/api.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { getDatabaseFieldIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes } from "@better-auth/core/db/internal";
|
|
2
|
+
import { diffSchema, formatSchemaFinding, getDatabaseFieldIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes } from "@better-auth/core/db/internal";
|
|
3
3
|
import { capitalizeFirstLetter, toSnakeCase } from "@better-auth/core/utils/string";
|
|
4
4
|
import { initGetFieldName, initGetModelName } from "better-auth/adapters";
|
|
5
5
|
import { getAuthTables } from "better-auth/db";
|
|
@@ -8,7 +8,8 @@ import { getMigrations } from "better-auth/db/migration";
|
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { BetterAuthError } from "@better-auth/core/error";
|
|
11
|
-
import { produceSchema } from "@mrleebo/prisma-ast";
|
|
11
|
+
import { getSchema, produceSchema } from "@mrleebo/prisma-ast";
|
|
12
|
+
import * as z from "zod";
|
|
12
13
|
//#region src/generators/drizzle.ts
|
|
13
14
|
function convertToSnakeCase(str, camelCase) {
|
|
14
15
|
return camelCase ? str : toSnakeCase(str);
|
|
@@ -333,17 +334,20 @@ function commentBanner(unsafeChanges) {
|
|
|
333
334
|
return lines.join("\n");
|
|
334
335
|
}
|
|
335
336
|
const generateKyselySchema = async ({ options, file }) => {
|
|
336
|
-
const { compileMigrations, unsafeChanges } = await getMigrations(options, { throwOnUnsafe: false });
|
|
337
|
+
const { compileMigrations, unsafeChanges, schemaProblems } = await getMigrations(options, { throwOnUnsafe: false });
|
|
337
338
|
const migrations = await compileMigrations();
|
|
338
339
|
const code = migrations.trim() === ";" ? "" : migrations;
|
|
339
340
|
return {
|
|
340
341
|
code: unsafeChanges.length ? `${commentBanner(unsafeChanges)}${code}` : code,
|
|
341
342
|
unsafeChanges,
|
|
343
|
+
schemaProblems,
|
|
342
344
|
fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
|
|
343
345
|
};
|
|
344
346
|
};
|
|
345
|
-
|
|
346
|
-
|
|
347
|
+
z.object({
|
|
348
|
+
name: z.string(),
|
|
349
|
+
version: z.string()
|
|
350
|
+
});
|
|
347
351
|
function getPackageInfo(cwd) {
|
|
348
352
|
const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
|
|
349
353
|
return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
@@ -407,6 +411,27 @@ function getPrismaIndexDefinition(property) {
|
|
|
407
411
|
function prismaIndexMatches(existing, configured) {
|
|
408
412
|
return existing.validFullColumns && existing.unique === (configured.unique ?? false) && existing.columns.length === configured.columns.length && existing.columns.every((column, position) => column === configured.columns[position]);
|
|
409
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* The models an existing schema declares, read the way the comparison wants
|
|
416
|
+
* them: each model by name, each scalar or enum field by name. A relation
|
|
417
|
+
* field is not a column and is skipped.
|
|
418
|
+
*/
|
|
419
|
+
function introspectPrismaSchema(schemaPrisma) {
|
|
420
|
+
const models = getSchema(schemaPrisma).list.filter((block) => block.type === "model");
|
|
421
|
+
const modelNames = new Set(models.map((model) => model.name));
|
|
422
|
+
return models.map((model) => ({
|
|
423
|
+
name: model.name,
|
|
424
|
+
columns: model.properties.flatMap((property) => {
|
|
425
|
+
if (property.type !== "field") return [];
|
|
426
|
+
if (typeof property.fieldType === "string" && modelNames.has(property.fieldType) || property.attributes?.some((attribute) => attribute.name === "relation")) return [];
|
|
427
|
+
return [{
|
|
428
|
+
name: property.name,
|
|
429
|
+
nullable: property.optional === true,
|
|
430
|
+
hasDefault: property.attributes?.some((attribute) => attribute.name === "default" || attribute.name === "updatedAt") ?? false
|
|
431
|
+
}];
|
|
432
|
+
})
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
410
435
|
const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
411
436
|
const provider = adapter.options?.provider || "postgresql";
|
|
412
437
|
const tables = getAuthTables(options);
|
|
@@ -450,6 +475,13 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
450
475
|
if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
|
|
451
476
|
}
|
|
452
477
|
});
|
|
478
|
+
const expectedModels = {};
|
|
479
|
+
for (const table in tables) {
|
|
480
|
+
if (isMigrationDisabled(table)) continue;
|
|
481
|
+
const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
|
|
482
|
+
const entry = expectedModels[modelName] ??= { fields: {} };
|
|
483
|
+
for (const [field, attr] of Object.entries(tables[table]?.fields ?? {})) entry.fields[attr.fieldName || field] = attr;
|
|
484
|
+
}
|
|
453
485
|
const manyToManyRelations = /* @__PURE__ */ new Map();
|
|
454
486
|
for (const table in tables) {
|
|
455
487
|
if (isMigrationDisabled(table)) continue;
|
|
@@ -695,10 +727,12 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
695
727
|
}
|
|
696
728
|
});
|
|
697
729
|
const schemaChanged = schema.trim() !== schemaPrisma.trim();
|
|
730
|
+
const schemaProblems = schemaPrismaExist ? diffSchema(expectedModels, introspectPrismaSchema(schemaPrisma)).filter((finding) => finding.kind === "unexpected-required-column").map((finding) => formatSchemaFinding(finding, "prisma")) : [];
|
|
698
731
|
return {
|
|
699
732
|
code: schemaChanged ? schema : "",
|
|
700
733
|
fileName: filePath,
|
|
701
|
-
overwrite: schemaPrismaExist && schemaChanged
|
|
734
|
+
overwrite: schemaPrismaExist && schemaChanged,
|
|
735
|
+
schemaProblems
|
|
702
736
|
};
|
|
703
737
|
};
|
|
704
738
|
const getNewPrisma = (provider, cwd) => {
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { APIError, BetterAuthError } from "@better-auth/core/error";
|
|
3
4
|
import { Command } from "commander";
|
|
4
5
|
import { exec, execSync, spawn } from "node:child_process";
|
|
@@ -20,13 +21,13 @@ import { createPathsMatcher, getTsconfig, parseTsconfig } from "get-tsconfig";
|
|
|
20
21
|
import fs$1 from "node:fs/promises";
|
|
21
22
|
import { createTelemetry, getTelemetryAuthConfig } from "@better-auth/telemetry";
|
|
22
23
|
import { getAdapter } from "better-auth/db/adapter";
|
|
23
|
-
import { getDatabaseFieldIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes } from "@better-auth/core/db/internal";
|
|
24
|
+
import { diffSchema, formatSchemaFinding, getDatabaseFieldIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes } from "@better-auth/core/db/internal";
|
|
24
25
|
import { capitalizeFirstLetter, toSnakeCase } from "@better-auth/core/utils/string";
|
|
25
26
|
import { initGetFieldName, initGetModelName } from "better-auth/adapters";
|
|
26
27
|
import { getAuthTables } from "better-auth/db";
|
|
27
28
|
import prettier, { format } from "prettier";
|
|
28
29
|
import { UnsafeMigrationError, getMigrations } from "better-auth/db/migration";
|
|
29
|
-
import { produceSchema } from "@mrleebo/prisma-ast";
|
|
30
|
+
import { getSchema, produceSchema } from "@mrleebo/prisma-ast";
|
|
30
31
|
import Crypto from "node:crypto";
|
|
31
32
|
import open from "open";
|
|
32
33
|
import { env } from "@better-auth/core/env";
|
|
@@ -1679,12 +1680,13 @@ function commentBanner(unsafeChanges) {
|
|
|
1679
1680
|
return lines.join("\n");
|
|
1680
1681
|
}
|
|
1681
1682
|
const generateKyselySchema = async ({ options, file }) => {
|
|
1682
|
-
const { compileMigrations, unsafeChanges } = await getMigrations(options, { throwOnUnsafe: false });
|
|
1683
|
+
const { compileMigrations, unsafeChanges, schemaProblems } = await getMigrations(options, { throwOnUnsafe: false });
|
|
1683
1684
|
const migrations = await compileMigrations();
|
|
1684
1685
|
const code = migrations.trim() === ";" ? "" : migrations;
|
|
1685
1686
|
return {
|
|
1686
1687
|
code: unsafeChanges.length ? `${commentBanner(unsafeChanges)}${code}` : code,
|
|
1687
1688
|
unsafeChanges,
|
|
1689
|
+
schemaProblems,
|
|
1688
1690
|
fileName: file || `./better-auth_migrations/${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}.sql`
|
|
1689
1691
|
};
|
|
1690
1692
|
};
|
|
@@ -1721,10 +1723,52 @@ const spawnCommand = (cmd, cwd = process.cwd()) => new Promise((resolve, reject)
|
|
|
1721
1723
|
});
|
|
1722
1724
|
//#endregion
|
|
1723
1725
|
//#region src/utils/get-package-info.ts
|
|
1726
|
+
const packageManifestSchema = z.object({
|
|
1727
|
+
name: z.string(),
|
|
1728
|
+
version: z.string()
|
|
1729
|
+
});
|
|
1724
1730
|
function getPackageInfo(cwd) {
|
|
1725
1731
|
const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
|
|
1726
1732
|
return JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1727
1733
|
}
|
|
1734
|
+
function readPackageVersion(packageJsonPath, expectedPackageName) {
|
|
1735
|
+
if (!existsSync(packageJsonPath)) return null;
|
|
1736
|
+
try {
|
|
1737
|
+
const result = packageManifestSchema.safeParse(JSON.parse(readFileSync(packageJsonPath, "utf-8")));
|
|
1738
|
+
return result.success && result.data.name === expectedPackageName ? result.data.version : null;
|
|
1739
|
+
} catch {
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
function findPackageVersion(resolvedPath, packageName) {
|
|
1744
|
+
let currentDirectory = path.dirname(resolvedPath);
|
|
1745
|
+
const rootDirectory = path.parse(currentDirectory).root;
|
|
1746
|
+
while (true) {
|
|
1747
|
+
const version = readPackageVersion(path.join(currentDirectory, "package.json"), packageName);
|
|
1748
|
+
if (version) return version;
|
|
1749
|
+
if (currentDirectory === rootDirectory) return null;
|
|
1750
|
+
currentDirectory = path.dirname(currentDirectory);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function findPackageVersionInNodeModules(packageName, projectDirectory) {
|
|
1754
|
+
let currentDirectory = path.resolve(projectDirectory);
|
|
1755
|
+
const rootDirectory = path.parse(currentDirectory).root;
|
|
1756
|
+
const packagePath = packageName.split("/");
|
|
1757
|
+
while (true) {
|
|
1758
|
+
const version = readPackageVersion(path.join(currentDirectory, "node_modules", ...packagePath, "package.json"), packageName);
|
|
1759
|
+
if (version) return version;
|
|
1760
|
+
if (currentDirectory === rootDirectory) return null;
|
|
1761
|
+
currentDirectory = path.dirname(currentDirectory);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function getInstalledPackageVersion(packageName, projectDirectory) {
|
|
1765
|
+
const resolve = createRequire(path.join(path.resolve(projectDirectory), "package.json")).resolve;
|
|
1766
|
+
for (const specifier of [`${packageName}/package.json`, packageName]) try {
|
|
1767
|
+
const version = findPackageVersion(resolve(specifier), packageName);
|
|
1768
|
+
if (version) return version;
|
|
1769
|
+
} catch {}
|
|
1770
|
+
return findPackageVersionInNodeModules(packageName, projectDirectory);
|
|
1771
|
+
}
|
|
1728
1772
|
function getPrismaVersion(cwd) {
|
|
1729
1773
|
try {
|
|
1730
1774
|
const packageInfo = getPackageInfo(cwd);
|
|
@@ -1838,6 +1882,27 @@ function getPrismaIndexDefinition(property) {
|
|
|
1838
1882
|
function prismaIndexMatches(existing, configured) {
|
|
1839
1883
|
return existing.validFullColumns && existing.unique === (configured.unique ?? false) && existing.columns.length === configured.columns.length && existing.columns.every((column, position) => column === configured.columns[position]);
|
|
1840
1884
|
}
|
|
1885
|
+
/**
|
|
1886
|
+
* The models an existing schema declares, read the way the comparison wants
|
|
1887
|
+
* them: each model by name, each scalar or enum field by name. A relation
|
|
1888
|
+
* field is not a column and is skipped.
|
|
1889
|
+
*/
|
|
1890
|
+
function introspectPrismaSchema(schemaPrisma) {
|
|
1891
|
+
const models = getSchema(schemaPrisma).list.filter((block) => block.type === "model");
|
|
1892
|
+
const modelNames = new Set(models.map((model) => model.name));
|
|
1893
|
+
return models.map((model) => ({
|
|
1894
|
+
name: model.name,
|
|
1895
|
+
columns: model.properties.flatMap((property) => {
|
|
1896
|
+
if (property.type !== "field") return [];
|
|
1897
|
+
if (typeof property.fieldType === "string" && modelNames.has(property.fieldType) || property.attributes?.some((attribute) => attribute.name === "relation")) return [];
|
|
1898
|
+
return [{
|
|
1899
|
+
name: property.name,
|
|
1900
|
+
nullable: property.optional === true,
|
|
1901
|
+
hasDefault: property.attributes?.some((attribute) => attribute.name === "default" || attribute.name === "updatedAt") ?? false
|
|
1902
|
+
}];
|
|
1903
|
+
})
|
|
1904
|
+
}));
|
|
1905
|
+
}
|
|
1841
1906
|
const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
1842
1907
|
const provider = adapter.options?.provider || "postgresql";
|
|
1843
1908
|
const tables = getAuthTables(options);
|
|
@@ -1881,6 +1946,13 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
1881
1946
|
if (urlIndex !== -1) datasource.properties.splice(urlIndex, 1);
|
|
1882
1947
|
}
|
|
1883
1948
|
});
|
|
1949
|
+
const expectedModels = {};
|
|
1950
|
+
for (const table in tables) {
|
|
1951
|
+
if (isMigrationDisabled(table)) continue;
|
|
1952
|
+
const modelName = capitalizeFirstLetter(getModelName(tables[table]?.modelName || table));
|
|
1953
|
+
const entry = expectedModels[modelName] ??= { fields: {} };
|
|
1954
|
+
for (const [field, attr] of Object.entries(tables[table]?.fields ?? {})) entry.fields[attr.fieldName || field] = attr;
|
|
1955
|
+
}
|
|
1884
1956
|
const manyToManyRelations = /* @__PURE__ */ new Map();
|
|
1885
1957
|
for (const table in tables) {
|
|
1886
1958
|
if (isMigrationDisabled(table)) continue;
|
|
@@ -2126,10 +2198,12 @@ const generatePrismaSchema = async ({ adapter, options, file }) => {
|
|
|
2126
2198
|
}
|
|
2127
2199
|
});
|
|
2128
2200
|
const schemaChanged = schema.trim() !== schemaPrisma.trim();
|
|
2201
|
+
const schemaProblems = schemaPrismaExist ? diffSchema(expectedModels, introspectPrismaSchema(schemaPrisma)).filter((finding) => finding.kind === "unexpected-required-column").map((finding) => formatSchemaFinding(finding, "prisma")) : [];
|
|
2129
2202
|
return {
|
|
2130
2203
|
code: schemaChanged ? schema : "",
|
|
2131
2204
|
fileName: filePath,
|
|
2132
|
-
overwrite: schemaPrismaExist && schemaChanged
|
|
2205
|
+
overwrite: schemaPrismaExist && schemaChanged,
|
|
2206
|
+
schemaProblems
|
|
2133
2207
|
};
|
|
2134
2208
|
};
|
|
2135
2209
|
const getNewPrisma = (provider, cwd) => {
|
|
@@ -2291,6 +2365,10 @@ async function generateAction(opts) {
|
|
|
2291
2365
|
options: config
|
|
2292
2366
|
});
|
|
2293
2367
|
spinner.stop();
|
|
2368
|
+
if (schema.schemaProblems?.length) {
|
|
2369
|
+
console.warn(chalk.red.bold(`ā ${schema.schemaProblems.length} ${schema.schemaProblems.length === 1 ? "column rejects" : "columns reject"} every insert Better Auth makes. No migration fixes this.`));
|
|
2370
|
+
for (const problem of schema.schemaProblems) console.warn(chalk.red(`-> ${problem}`));
|
|
2371
|
+
}
|
|
2294
2372
|
if (schema.unsafeChanges?.length) {
|
|
2295
2373
|
console.warn(chalk.red.bold(`ā ${schema.unsafeChanges.length} ${schema.unsafeChanges.length === 1 ? "change in this schema corrupts" : "changes in this schema corrupt"} a populated database.`));
|
|
2296
2374
|
for (const change of schema.unsafeChanges) console.warn(chalk.red(`-> ${change}`));
|
|
@@ -2393,6 +2471,16 @@ async function generateAction(opts) {
|
|
|
2393
2471
|
const generate = new Command("generate").option("-c, --cwd <cwd>", "the working directory. defaults to the current directory.", process.cwd()).option("--config <config>", "the path to the configuration file. defaults to the first configuration file found.").option("--output <output>", "the file to output to the generated schema").option("--adapter <adapter>", "specify the adapter type (e.g., prisma, drizzle, kysely) without requiring a configured adapter").option("--dialect <dialect>", "specify the database dialect/provider (e.g., postgresql, mysql, sqlite). For drizzle, postgresql maps to 'pg'").option("-y, --yes", "automatically answer yes to all prompts", false).option("--y", "(deprecated) same as --yes", false).action(generateAction);
|
|
2394
2472
|
//#endregion
|
|
2395
2473
|
//#region src/commands/info.ts
|
|
2474
|
+
function resolveReportedDependencies(projectRoot, dependencies, reportedDependencies) {
|
|
2475
|
+
return reportedDependencies.flatMap(({ name, packageName }) => {
|
|
2476
|
+
const declaredVersion = dependencies[packageName];
|
|
2477
|
+
if (!declaredVersion) return [];
|
|
2478
|
+
return [{
|
|
2479
|
+
name,
|
|
2480
|
+
version: getInstalledPackageVersion(packageName, projectRoot) ?? declaredVersion
|
|
2481
|
+
}];
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2396
2484
|
function getSystemInfo() {
|
|
2397
2485
|
const platform = os.platform();
|
|
2398
2486
|
const arch = os.arch();
|
|
@@ -2449,29 +2537,63 @@ function getFrameworkInfo(projectRoot) {
|
|
|
2449
2537
|
if (!existsSync(packageJsonPath)) return null;
|
|
2450
2538
|
try {
|
|
2451
2539
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
2452
|
-
const
|
|
2540
|
+
const installedFrameworks = resolveReportedDependencies(projectRoot, {
|
|
2453
2541
|
...packageJson.dependencies,
|
|
2454
2542
|
...packageJson.devDependencies
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2543
|
+
}, [
|
|
2544
|
+
{
|
|
2545
|
+
name: "next",
|
|
2546
|
+
packageName: "next"
|
|
2547
|
+
},
|
|
2548
|
+
{
|
|
2549
|
+
name: "react",
|
|
2550
|
+
packageName: "react"
|
|
2551
|
+
},
|
|
2552
|
+
{
|
|
2553
|
+
name: "vue",
|
|
2554
|
+
packageName: "vue"
|
|
2555
|
+
},
|
|
2556
|
+
{
|
|
2557
|
+
name: "nuxt",
|
|
2558
|
+
packageName: "nuxt"
|
|
2559
|
+
},
|
|
2560
|
+
{
|
|
2561
|
+
name: "svelte",
|
|
2562
|
+
packageName: "svelte"
|
|
2563
|
+
},
|
|
2564
|
+
{
|
|
2565
|
+
name: "@sveltejs/kit",
|
|
2566
|
+
packageName: "@sveltejs/kit"
|
|
2567
|
+
},
|
|
2568
|
+
{
|
|
2569
|
+
name: "express",
|
|
2570
|
+
packageName: "express"
|
|
2571
|
+
},
|
|
2572
|
+
{
|
|
2573
|
+
name: "fastify",
|
|
2574
|
+
packageName: "fastify"
|
|
2575
|
+
},
|
|
2576
|
+
{
|
|
2577
|
+
name: "hono",
|
|
2578
|
+
packageName: "hono"
|
|
2579
|
+
},
|
|
2580
|
+
{
|
|
2581
|
+
name: "react-router",
|
|
2582
|
+
packageName: "react-router"
|
|
2583
|
+
},
|
|
2584
|
+
{
|
|
2585
|
+
name: "astro",
|
|
2586
|
+
packageName: "astro"
|
|
2587
|
+
},
|
|
2588
|
+
{
|
|
2589
|
+
name: "solid",
|
|
2590
|
+
packageName: "solid-js"
|
|
2591
|
+
},
|
|
2592
|
+
{
|
|
2593
|
+
name: "qwik",
|
|
2594
|
+
packageName: "@builder.io/qwik"
|
|
2595
|
+
}
|
|
2596
|
+
]);
|
|
2475
2597
|
return installedFrameworks.length > 0 ? installedFrameworks : null;
|
|
2476
2598
|
} catch {
|
|
2477
2599
|
return null;
|
|
@@ -2482,29 +2604,63 @@ function getDatabaseInfo(projectRoot) {
|
|
|
2482
2604
|
if (!existsSync(packageJsonPath)) return null;
|
|
2483
2605
|
try {
|
|
2484
2606
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
2485
|
-
const
|
|
2607
|
+
const installedDatabases = resolveReportedDependencies(projectRoot, {
|
|
2486
2608
|
...packageJson.dependencies,
|
|
2487
2609
|
...packageJson.devDependencies
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2610
|
+
}, [
|
|
2611
|
+
{
|
|
2612
|
+
name: "better-sqlite3",
|
|
2613
|
+
packageName: "better-sqlite3"
|
|
2614
|
+
},
|
|
2615
|
+
{
|
|
2616
|
+
name: "@libsql/client",
|
|
2617
|
+
packageName: "@libsql/client"
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
name: "@libsql/kysely-libsql",
|
|
2621
|
+
packageName: "@libsql/kysely-libsql"
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
name: "mysql2",
|
|
2625
|
+
packageName: "mysql2"
|
|
2626
|
+
},
|
|
2627
|
+
{
|
|
2628
|
+
name: "pg",
|
|
2629
|
+
packageName: "pg"
|
|
2630
|
+
},
|
|
2631
|
+
{
|
|
2632
|
+
name: "postgres",
|
|
2633
|
+
packageName: "postgres"
|
|
2634
|
+
},
|
|
2635
|
+
{
|
|
2636
|
+
name: "@prisma/client",
|
|
2637
|
+
packageName: "@prisma/client"
|
|
2638
|
+
},
|
|
2639
|
+
{
|
|
2640
|
+
name: "drizzle",
|
|
2641
|
+
packageName: "drizzle-orm"
|
|
2642
|
+
},
|
|
2643
|
+
{
|
|
2644
|
+
name: "kysely",
|
|
2645
|
+
packageName: "kysely"
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
name: "mongodb",
|
|
2649
|
+
packageName: "mongodb"
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
name: "@neondatabase/serverless",
|
|
2653
|
+
packageName: "@neondatabase/serverless"
|
|
2654
|
+
},
|
|
2655
|
+
{
|
|
2656
|
+
name: "@vercel/postgres",
|
|
2657
|
+
packageName: "@vercel/postgres"
|
|
2658
|
+
},
|
|
2659
|
+
{
|
|
2660
|
+
name: "@planetscale/database",
|
|
2661
|
+
packageName: "@planetscale/database"
|
|
2662
|
+
}
|
|
2663
|
+
]);
|
|
2508
2664
|
return installedDatabases.length > 0 ? installedDatabases : null;
|
|
2509
2665
|
} catch {
|
|
2510
2666
|
return null;
|
|
@@ -2598,6 +2754,12 @@ function sanitizeBetterAuthConfig(config) {
|
|
|
2598
2754
|
return redactSensitive(sanitized);
|
|
2599
2755
|
}
|
|
2600
2756
|
async function getBetterAuthInfo(projectRoot, configPath, suppressLogs = false) {
|
|
2757
|
+
let betterAuthVersion = "Unknown";
|
|
2758
|
+
try {
|
|
2759
|
+
const packageInfo = getPackageInfo(projectRoot);
|
|
2760
|
+
const declaredVersion = packageInfo.dependencies?.["better-auth"] || packageInfo.devDependencies?.["better-auth"] || packageInfo.peerDependencies?.["better-auth"] || packageInfo.optionalDependencies?.["better-auth"];
|
|
2761
|
+
if (declaredVersion) betterAuthVersion = getInstalledPackageVersion("better-auth", projectRoot) ?? declaredVersion;
|
|
2762
|
+
} catch {}
|
|
2601
2763
|
try {
|
|
2602
2764
|
const originalLog = console.log;
|
|
2603
2765
|
const originalWarn = console.warn;
|
|
@@ -2613,9 +2775,8 @@ async function getBetterAuthInfo(projectRoot, configPath, suppressLogs = false)
|
|
|
2613
2775
|
configPath,
|
|
2614
2776
|
shouldThrowOnError: true
|
|
2615
2777
|
});
|
|
2616
|
-
const packageInfo = await getPackageInfo();
|
|
2617
2778
|
return {
|
|
2618
|
-
version:
|
|
2779
|
+
version: betterAuthVersion,
|
|
2619
2780
|
config: sanitizeBetterAuthConfig(config)
|
|
2620
2781
|
};
|
|
2621
2782
|
} finally {
|
|
@@ -2627,7 +2788,7 @@ async function getBetterAuthInfo(projectRoot, configPath, suppressLogs = false)
|
|
|
2627
2788
|
}
|
|
2628
2789
|
} catch (error) {
|
|
2629
2790
|
return {
|
|
2630
|
-
version:
|
|
2791
|
+
version: betterAuthVersion,
|
|
2631
2792
|
config: null,
|
|
2632
2793
|
error: error instanceof Error ? error.message : "Failed to load Better Auth config"
|
|
2633
2794
|
};
|
|
@@ -2704,13 +2865,11 @@ const info = new Command("info").description("Display system and Better Auth con
|
|
|
2704
2865
|
console.log(formatOutput(databases, 2));
|
|
2705
2866
|
}
|
|
2706
2867
|
console.log(chalk.bold.white("\nš Better Auth:"));
|
|
2868
|
+
console.log(` ${chalk.cyan("Version")}: ${betterAuthInfo.version}`);
|
|
2707
2869
|
if (betterAuthInfo.error) console.log(` ${chalk.red("Error:")} ${betterAuthInfo.error}`);
|
|
2708
|
-
else {
|
|
2709
|
-
console.log(` ${chalk.cyan("
|
|
2710
|
-
|
|
2711
|
-
console.log(` ${chalk.cyan("Configuration")}:`);
|
|
2712
|
-
console.log(formatOutput(betterAuthInfo.config, 4));
|
|
2713
|
-
}
|
|
2870
|
+
else if (betterAuthInfo.config) {
|
|
2871
|
+
console.log(` ${chalk.cyan("Configuration")}:`);
|
|
2872
|
+
console.log(formatOutput(betterAuthInfo.config, 4));
|
|
2714
2873
|
}
|
|
2715
2874
|
console.log(chalk.gray("\n" + "=".repeat(50)));
|
|
2716
2875
|
console.log(chalk.gray("\nš” Tip: Use --json flag for JSON output"));
|
|
@@ -2960,11 +3119,14 @@ function installDependencies({ dependencies, packageManager, cwd, type = "prod",
|
|
|
2960
3119
|
const flag = flagMap?.[type];
|
|
2961
3120
|
if (flag) flags.push(flag);
|
|
2962
3121
|
}
|
|
2963
|
-
const
|
|
3122
|
+
const dependencyList = Array.isArray(dependencies) ? dependencies : [dependencies];
|
|
3123
|
+
if (dependencyList.length === 0) return Promise.resolve(true);
|
|
3124
|
+
const command = `${installCommand}${flags.length > 0 ? ` ${flags.join(" ")}` : ""} ${dependencyList.join(" ")}`;
|
|
2964
3125
|
return new Promise((resolve, reject) => {
|
|
2965
3126
|
exec(command, { cwd }, (error, stdout, stderr) => {
|
|
2966
3127
|
if (error) {
|
|
2967
|
-
|
|
3128
|
+
const message = stderr.trim() || stdout.trim() || error.message;
|
|
3129
|
+
reject(new Error(message, { cause: error }));
|
|
2968
3130
|
return;
|
|
2969
3131
|
}
|
|
2970
3132
|
resolve(true);
|
|
@@ -2973,7 +3135,7 @@ function installDependencies({ dependencies, packageManager, cwd, type = "prod",
|
|
|
2973
3135
|
}
|
|
2974
3136
|
//#endregion
|
|
2975
3137
|
//#region src/version.ts
|
|
2976
|
-
const cliVersion = "1.7.
|
|
3138
|
+
const cliVersion = "1.7.4";
|
|
2977
3139
|
//#endregion
|
|
2978
3140
|
//#region src/commands/init/configs/frameworks.config.ts
|
|
2979
3141
|
const FRAMEWORKS = [
|
|
@@ -3183,6 +3345,7 @@ export const Route = createFileRoute('/api/auth/$')({
|
|
|
3183
3345
|
const SOCIAL_PROVIDERS = [
|
|
3184
3346
|
"apple",
|
|
3185
3347
|
"atlassian",
|
|
3348
|
+
"cloudflare",
|
|
3186
3349
|
"cognito",
|
|
3187
3350
|
"discord",
|
|
3188
3351
|
"dropbox",
|
|
@@ -3233,6 +3396,13 @@ const SOCIAL_PROVIDER_CONFIGS = {
|
|
|
3233
3396
|
name: "clientSecret",
|
|
3234
3397
|
envVar: "ATLASSIAN_CLIENT_SECRET"
|
|
3235
3398
|
}] },
|
|
3399
|
+
cloudflare: { options: [{
|
|
3400
|
+
name: "clientId",
|
|
3401
|
+
envVar: "CLOUDFLARE_CLIENT_ID"
|
|
3402
|
+
}, {
|
|
3403
|
+
name: "clientSecret",
|
|
3404
|
+
envVar: "CLOUDFLARE_CLIENT_SECRET"
|
|
3405
|
+
}] },
|
|
3236
3406
|
cognito: { options: [
|
|
3237
3407
|
{
|
|
3238
3408
|
name: "clientId",
|
|
@@ -7156,6 +7326,7 @@ const logout = new Command("logout").description("Logout from Better Auth Infras
|
|
|
7156
7326
|
const REMOTE_MCP_URL = "https://mcp.better-auth.com/mcp";
|
|
7157
7327
|
async function mcpAction(options) {
|
|
7158
7328
|
if (options.cursor) await handleCursorAction();
|
|
7329
|
+
else if (options.codex) handleCodexAction();
|
|
7159
7330
|
else if (options.claudeCode) handleClaudeCodeAction();
|
|
7160
7331
|
else if (options.openCode) handleOpenCodeAction();
|
|
7161
7332
|
else if (options.manual) handleManualAction();
|
|
@@ -7191,6 +7362,20 @@ async function handleCursorAction() {
|
|
|
7191
7362
|
console.log(chalk.gray("⢠You can now use Better Auth features directly in Cursor"));
|
|
7192
7363
|
console.log(chalk.gray("⢠Try: \"Set up Better Auth with Google login\" or \"Help me debug my auth\""));
|
|
7193
7364
|
}
|
|
7365
|
+
function handleCodexAction() {
|
|
7366
|
+
console.log(chalk.bold.blue("š¤ Adding Better Auth MCP to Codex..."));
|
|
7367
|
+
const command = `codex mcp add better-auth --url ${REMOTE_MCP_URL}`;
|
|
7368
|
+
try {
|
|
7369
|
+
execSync(command, { stdio: "inherit" });
|
|
7370
|
+
console.log(chalk.green("\nā Codex MCP configured!"));
|
|
7371
|
+
} catch {
|
|
7372
|
+
console.log(chalk.yellow("\nā Could not automatically add to Codex. Please run this command manually:"));
|
|
7373
|
+
console.log(chalk.cyan(command));
|
|
7374
|
+
}
|
|
7375
|
+
console.log(chalk.bold.white("\n⨠Next Steps:"));
|
|
7376
|
+
console.log(chalk.gray("⢠Once configured, ChatGPT desktop, Codex CLI, and the IDE extension share this MCP server"));
|
|
7377
|
+
console.log(chalk.gray("⢠Run `codex mcp list` to verify the connection"));
|
|
7378
|
+
}
|
|
7194
7379
|
function handleClaudeCodeAction() {
|
|
7195
7380
|
console.log(chalk.bold.blue("š¤ Adding Better Auth MCP to Claude Code..."));
|
|
7196
7381
|
const command = `claude mcp add --transport http better-auth ${REMOTE_MCP_URL}`;
|
|
@@ -7272,6 +7457,7 @@ function showAllOptions() {
|
|
|
7272
7457
|
console.log();
|
|
7273
7458
|
console.log(chalk.bold.white("MCP Clients:"));
|
|
7274
7459
|
console.log(chalk.cyan(" --cursor ") + chalk.gray("Add to Cursor"));
|
|
7460
|
+
console.log(chalk.cyan(" --codex ") + chalk.gray("Add to Codex"));
|
|
7275
7461
|
console.log(chalk.cyan(" --claude-code ") + chalk.gray("Add to Claude Code"));
|
|
7276
7462
|
console.log(chalk.cyan(" --open-code ") + chalk.gray("Add to Open Code"));
|
|
7277
7463
|
console.log(chalk.cyan(" --manual ") + chalk.gray("Manual configuration"));
|
|
@@ -7280,7 +7466,7 @@ function showAllOptions() {
|
|
|
7280
7466
|
console.log(chalk.gray(" ⢠") + chalk.white("better-auth") + chalk.gray(" - Search documentation, code examples, setup assistance"));
|
|
7281
7467
|
console.log();
|
|
7282
7468
|
}
|
|
7283
|
-
const mcp = new Command("mcp").description("Add Better Auth MCP server to MCP Clients").option("--cursor", "Automatically open Cursor with the MCP configuration").option("--claude-code", "Show Claude Code MCP configuration command").option("--open-code", "Show Open Code MCP configuration").option("--manual", "Show manual MCP configuration for mcp.json").action(mcpAction);
|
|
7469
|
+
const mcp = new Command("mcp").description("Add Better Auth MCP server to MCP Clients").option("--cursor", "Automatically open Cursor with the MCP configuration").option("--codex", "Add the MCP server to Codex").option("--claude-code", "Show Claude Code MCP configuration command").option("--open-code", "Show Open Code MCP configuration").option("--manual", "Show manual MCP configuration for mcp.json").action(mcpAction);
|
|
7284
7470
|
//#endregion
|
|
7285
7471
|
//#region src/commands/migrate.ts
|
|
7286
7472
|
/** @internal */
|
|
@@ -7372,6 +7558,21 @@ async function migrateAction(opts) {
|
|
|
7372
7558
|
} catch {}
|
|
7373
7559
|
process.exit(1);
|
|
7374
7560
|
}
|
|
7561
|
+
if (plan.schemaProblems.length) {
|
|
7562
|
+
spinner.stop();
|
|
7563
|
+
console.error(chalk.red.bold(`The database has ${plan.schemaProblems.length} ${plan.schemaProblems.length === 1 ? "column" : "columns"} Better Auth cannot write to. Nothing ran.`));
|
|
7564
|
+
for (const problem of plan.schemaProblems) console.error(chalk.red(`-> ${problem}`));
|
|
7565
|
+
try {
|
|
7566
|
+
await (await createTelemetry(config)).publish({
|
|
7567
|
+
type: "cli_migrate",
|
|
7568
|
+
payload: {
|
|
7569
|
+
outcome: "schema_problem",
|
|
7570
|
+
config: await getTelemetryAuthConfig(config)
|
|
7571
|
+
}
|
|
7572
|
+
});
|
|
7573
|
+
} catch {}
|
|
7574
|
+
process.exit(1);
|
|
7575
|
+
}
|
|
7375
7576
|
const { toBeAdded, toBeAddedIndexes, toBeCreated, runMigrations } = plan;
|
|
7376
7577
|
if (!toBeAdded.length && !toBeAddedIndexes.length && !toBeCreated.length) {
|
|
7377
7578
|
spinner.stop();
|
|
@@ -7439,8 +7640,8 @@ const generateSecret = new Command("secret").action(() => {
|
|
|
7439
7640
|
${chalk.gray("# Auth Secret") + chalk.green(`\nBETTER_AUTH_SECRET=${secret}`)}`);
|
|
7440
7641
|
});
|
|
7441
7642
|
//#endregion
|
|
7442
|
-
//#region
|
|
7443
|
-
|
|
7643
|
+
//#region ../../.changeset/config.json
|
|
7644
|
+
var fixed = [[
|
|
7444
7645
|
"better-auth",
|
|
7445
7646
|
"@better-auth/core",
|
|
7446
7647
|
"auth",
|
|
@@ -7463,7 +7664,11 @@ const fixedReleaseGroup = [[
|
|
|
7463
7664
|
"@better-auth/stripe",
|
|
7464
7665
|
"@better-auth/telemetry",
|
|
7465
7666
|
"@better-auth/test-utils"
|
|
7466
|
-
]]
|
|
7667
|
+
]];
|
|
7668
|
+
//#endregion
|
|
7669
|
+
//#region src/commands/upgrade.ts
|
|
7670
|
+
const dependencyMapSchema = z.record(z.string(), z.string().catch("")).catch({});
|
|
7671
|
+
const fixedReleaseGroup = fixed.find((group) => group.includes("better-auth"));
|
|
7467
7672
|
if (!fixedReleaseGroup) throw new Error("The Better Auth fixed release group is not configured.");
|
|
7468
7673
|
const SYNCHRONIZED_BETTER_AUTH_PACKAGES = new Set(fixedReleaseGroup.filter((name) => name !== "auth"));
|
|
7469
7674
|
function isSynchronizedBetterAuthPackage(name) {
|
|
@@ -7487,8 +7692,8 @@ async function upgradeAction(opts) {
|
|
|
7487
7692
|
console.error(`Could not read package.json in "${cwd}". Make sure you are in a project directory.`);
|
|
7488
7693
|
process.exit(1);
|
|
7489
7694
|
}
|
|
7490
|
-
const deps = packageJson.dependencies
|
|
7491
|
-
const devDeps = packageJson.devDependencies
|
|
7695
|
+
const deps = dependencyMapSchema.parse(packageJson.dependencies);
|
|
7696
|
+
const devDeps = dependencyMapSchema.parse(packageJson.devDependencies);
|
|
7492
7697
|
const candidates = [];
|
|
7493
7698
|
for (const [name, version] of Object.entries(deps)) if (isSynchronizedBetterAuthPackage(name) && !version.startsWith("workspace:")) candidates.push({
|
|
7494
7699
|
name,
|
|
@@ -7506,8 +7711,14 @@ async function upgradeAction(opts) {
|
|
|
7506
7711
|
}
|
|
7507
7712
|
const spinner = yoctoSpinner({ text: "checking for updates..." }).start();
|
|
7508
7713
|
const upgrades = [];
|
|
7714
|
+
const warnings = [];
|
|
7509
7715
|
for (const { name, current, depType } of candidates) {
|
|
7510
|
-
const
|
|
7716
|
+
const currentRange = semver.validRange(current);
|
|
7717
|
+
if (!currentRange) {
|
|
7718
|
+
warnings.push(`Skipped ${name} (${current}). Automatic upgrades require a semver range.`);
|
|
7719
|
+
continue;
|
|
7720
|
+
}
|
|
7721
|
+
const currentVersion = semver.minVersion(currentRange);
|
|
7511
7722
|
if (currentVersion && semver.lt(currentVersion, cliVersion)) upgrades.push({
|
|
7512
7723
|
name,
|
|
7513
7724
|
current,
|
|
@@ -7516,8 +7727,9 @@ async function upgradeAction(opts) {
|
|
|
7516
7727
|
});
|
|
7517
7728
|
}
|
|
7518
7729
|
spinner.stop();
|
|
7730
|
+
for (const warning of warnings) console.warn(chalk.yellow(warning));
|
|
7519
7731
|
if (upgrades.length === 0) {
|
|
7520
|
-
console.log("All better-auth packages are up to date.");
|
|
7732
|
+
console.log(warnings.length > 0 ? "No supported Better Auth package upgrades were found." : "All better-auth packages are up to date.");
|
|
7521
7733
|
return;
|
|
7522
7734
|
}
|
|
7523
7735
|
console.log(`\nThe following packages can be upgraded:\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auth",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.4",
|
|
4
4
|
"description": "The CLI for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
"prompts": "^2.4.2",
|
|
64
64
|
"semver": "^7.8.4",
|
|
65
65
|
"yocto-spinner": "^1.2.0",
|
|
66
|
-
"zod": "^4.
|
|
67
|
-
"@better-auth/core": "1.7.
|
|
68
|
-
"@better-auth/telemetry": "1.7.
|
|
69
|
-
"better-auth": "1.7.
|
|
66
|
+
"zod": "^4.5.4",
|
|
67
|
+
"@better-auth/core": "1.7.4",
|
|
68
|
+
"@better-auth/telemetry": "1.7.4",
|
|
69
|
+
"better-auth": "1.7.4"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"tsx": "^4.21.0",
|
|
79
79
|
"type-fest": "^5.7.0",
|
|
80
80
|
"typescript": "^6.0.3",
|
|
81
|
-
"@better-auth/passkey": "1.7.
|
|
81
|
+
"@better-auth/passkey": "1.7.4"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsdown",
|