@better-auth/cli 1.3.7 → 1.3.8-beta.10

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.
Files changed (2) hide show
  1. package/dist/index.mjs +666 -42
  2. package/package.json +8 -11
package/dist/index.mjs CHANGED
@@ -4,13 +4,12 @@ import { parse } from 'dotenv';
4
4
  import semver from 'semver';
5
5
  import prettier, { format } from 'prettier';
6
6
  import * as z from 'zod/v4';
7
- import fs$2, { existsSync } from 'fs';
7
+ import fs, { existsSync, readFileSync } from 'fs';
8
8
  import path from 'path';
9
9
  import fs$1 from 'fs/promises';
10
- import fs from 'fs-extra';
11
10
  import chalk from 'chalk';
12
11
  import { intro, log, outro, confirm, isCancel, cancel, spinner, text, select, multiselect } from '@clack/prompts';
13
- import { exec } from 'child_process';
12
+ import { exec, execSync } from 'child_process';
14
13
  import { logger, BetterAuthError, createTelemetry, getTelemetryAuthConfig, capitalizeFirstLetter } from 'better-auth';
15
14
  import Crypto from 'crypto';
16
15
  import yoctoSpinner from 'yocto-spinner';
@@ -20,15 +19,15 @@ import { loadConfig } from 'c12';
20
19
  import babelPresetTypeScript from '@babel/preset-typescript';
21
20
  import babelPresetReact from '@babel/preset-react';
22
21
  import { produceSchema } from '@mrleebo/prisma-ast';
22
+ import { createAuthClient } from 'better-auth/client';
23
+ import { deviceAuthorizationClient } from 'better-auth/client/plugins';
24
+ import open from 'open';
25
+ import os from 'os';
23
26
  import 'dotenv/config';
24
27
 
25
28
  function getPackageInfo(cwd) {
26
29
  const packageJsonPath = cwd ? path.join(cwd, "package.json") : path.join("package.json");
27
- try {
28
- return fs.readJSONSync(packageJsonPath);
29
- } catch (error) {
30
- throw error;
31
- }
30
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
32
31
  }
33
32
 
34
33
  function installDependencies({
@@ -177,22 +176,15 @@ async function generateAuthConfig({
177
176
  insert_content: `${opts.pluginFunctionName}(${opts.pluginContents}),`
178
177
  });
179
178
  } 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;
186
- }
187
- if (char === ")") {
188
- break;
189
- }
190
- }
179
+ const pluginArrayContent = opts.config.slice(start_of_plugins.index, end_of_plugins.index).trim();
180
+ const isPluginArrayEmpty = pluginArrayContent === "";
181
+ const isPluginArrayEndsWithComma = pluginArrayContent.endsWith(",");
182
+ const needsComma = !isPluginArrayEmpty && !isPluginArrayEndsWithComma;
191
183
  new_content = insertContent({
192
184
  line: end_of_plugins.line,
193
185
  character: end_of_plugins.character,
194
186
  content: opts.config,
195
- insert_content: `${!has_found_comma ? "," : ""}${opts.pluginFunctionName}(${opts.pluginContents})`
187
+ insert_content: `${needsComma ? "," : ""}${opts.pluginFunctionName}(${opts.pluginContents})`
196
188
  });
197
189
  }
198
190
  try {
@@ -203,7 +195,7 @@ async function generateAuthConfig({
203
195
  `Failed to generate new auth config during plugin addition phase.`
204
196
  );
205
197
  }
206
- return { code: await new_content, dependencies: [], envs: [] };
198
+ return { code: new_content, dependencies: [], envs: [] };
207
199
  },
208
200
  add_import: async (opts) => {
209
201
  let importString = "";
@@ -1070,7 +1062,7 @@ async function initAction(opts) {
1070
1062
  process.exit(0);
1071
1063
  }
1072
1064
  if (packageManagerPreference === void 0) {
1073
- packageManagerPreference = await getPackageManager();
1065
+ packageManagerPreference = await getPackageManager$1();
1074
1066
  }
1075
1067
  if (shouldInstallBetterAuthDep) {
1076
1068
  s2.start(
@@ -1110,7 +1102,7 @@ async function initAction(opts) {
1110
1102
  }
1111
1103
  if (shouldInstallBetterAuthDep) {
1112
1104
  if (packageManagerPreference === void 0) {
1113
- packageManagerPreference = await getPackageManager();
1105
+ packageManagerPreference = await getPackageManager$1();
1114
1106
  }
1115
1107
  const s2 = spinner({ indicator: "dots" });
1116
1108
  s2.start(
@@ -1273,7 +1265,7 @@ async function initAction(opts) {
1273
1265
  const { dependencies, envs, generatedCode } = await generateAuthConfig({
1274
1266
  current_user_config,
1275
1267
  format: format$1,
1276
- //@ts-ignore
1268
+ //@ts-expect-error
1277
1269
  s,
1278
1270
  plugins: add_plugins,
1279
1271
  database
@@ -1338,7 +1330,7 @@ async function initAction(opts) {
1338
1330
  if (shouldInstallDeps) {
1339
1331
  const s2 = spinner({ indicator: "dots" });
1340
1332
  if (packageManagerPreference === void 0) {
1341
- packageManagerPreference = await getPackageManager();
1333
+ packageManagerPreference = await getPackageManager$1();
1342
1334
  }
1343
1335
  s2.start(
1344
1336
  `Installing dependencies using ${chalk.bold(
@@ -1587,7 +1579,7 @@ async function getLatestNpmVersion(packageName) {
1587
1579
  throw error?.message;
1588
1580
  }
1589
1581
  }
1590
- async function getPackageManager() {
1582
+ async function getPackageManager$1() {
1591
1583
  const { hasBun, hasPnpm } = await checkPackageManagers();
1592
1584
  if (!hasBun && !hasPnpm) return "npm";
1593
1585
  const packageManagerOptions = [];
@@ -1690,9 +1682,9 @@ function getSvelteKitPathAliases(cwd) {
1690
1682
  const svelteConfigPath = path.join(cwd, "svelte.config.js");
1691
1683
  const svelteConfigTsPath = path.join(cwd, "svelte.config.ts");
1692
1684
  let isSvelteKitProject = false;
1693
- if (fs$2.existsSync(packageJsonPath)) {
1685
+ if (fs.existsSync(packageJsonPath)) {
1694
1686
  try {
1695
- const packageJson = JSON.parse(fs$2.readFileSync(packageJsonPath, "utf-8"));
1687
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1696
1688
  const deps = {
1697
1689
  ...packageJson.dependencies,
1698
1690
  ...packageJson.devDependencies
@@ -1702,19 +1694,19 @@ function getSvelteKitPathAliases(cwd) {
1702
1694
  }
1703
1695
  }
1704
1696
  if (!isSvelteKitProject) {
1705
- isSvelteKitProject = fs$2.existsSync(svelteConfigPath) || fs$2.existsSync(svelteConfigTsPath);
1697
+ isSvelteKitProject = fs.existsSync(svelteConfigPath) || fs.existsSync(svelteConfigTsPath);
1706
1698
  }
1707
1699
  if (!isSvelteKitProject) {
1708
1700
  return aliases;
1709
1701
  }
1710
1702
  const libPaths = [path.join(cwd, "src", "lib"), path.join(cwd, "lib")];
1711
1703
  for (const libPath of libPaths) {
1712
- if (fs$2.existsSync(libPath)) {
1704
+ if (fs.existsSync(libPath)) {
1713
1705
  aliases["$lib"] = libPath;
1714
1706
  const commonSubPaths = ["server", "utils", "components", "stores"];
1715
1707
  for (const subPath of commonSubPaths) {
1716
1708
  const subDir = path.join(libPath, subPath);
1717
- if (fs$2.existsSync(subDir)) {
1709
+ if (fs.existsSync(subDir)) {
1718
1710
  aliases[`$lib/${subPath}`] = subDir;
1719
1711
  }
1720
1712
  }
@@ -1733,9 +1725,9 @@ function getSvelteConfigAliases(cwd) {
1733
1725
  path.join(cwd, "svelte.config.ts")
1734
1726
  ];
1735
1727
  for (const configPath of configPaths) {
1736
- if (fs$2.existsSync(configPath)) {
1728
+ if (fs.existsSync(configPath)) {
1737
1729
  try {
1738
- const content = fs$2.readFileSync(configPath, "utf-8");
1730
+ const content = fs.readFileSync(configPath, "utf-8");
1739
1731
  const aliasMatch = content.match(/alias\s*:\s*\{([^}]+)\}/);
1740
1732
  if (aliasMatch && aliasMatch[1]) {
1741
1733
  const aliasContent = aliasMatch[1];
@@ -1871,9 +1863,9 @@ function resolveReferencePath(configDir, refPath) {
1871
1863
  if (refPath.endsWith(".json")) {
1872
1864
  return resolvedPath;
1873
1865
  }
1874
- if (fs$2.existsSync(resolvedPath)) {
1866
+ if (fs.existsSync(resolvedPath)) {
1875
1867
  try {
1876
- const stats = fs$2.statSync(resolvedPath);
1868
+ const stats = fs.statSync(resolvedPath);
1877
1869
  if (stats.isFile()) {
1878
1870
  return resolvedPath;
1879
1871
  }
@@ -1887,7 +1879,7 @@ function getPathAliasesRecursive(tsconfigPath, visited = /* @__PURE__ */ new Set
1887
1879
  return {};
1888
1880
  }
1889
1881
  visited.add(tsconfigPath);
1890
- if (!fs$2.existsSync(tsconfigPath)) {
1882
+ if (!fs.existsSync(tsconfigPath)) {
1891
1883
  logger.warn(`Referenced tsconfig not found: ${tsconfigPath}`);
1892
1884
  return {};
1893
1885
  }
@@ -1924,7 +1916,7 @@ function getPathAliasesRecursive(tsconfigPath, visited = /* @__PURE__ */ new Set
1924
1916
  }
1925
1917
  function getPathAliases(cwd) {
1926
1918
  const tsConfigPath = path.join(cwd, "tsconfig.json");
1927
- if (!fs$2.existsSync(tsConfigPath)) {
1919
+ if (!fs.existsSync(tsConfigPath)) {
1928
1920
  return null;
1929
1921
  }
1930
1922
  try {
@@ -1957,6 +1949,9 @@ const jitiOptions = (cwd) => {
1957
1949
  alias
1958
1950
  };
1959
1951
  };
1952
+ const isDefaultExport = (object) => {
1953
+ return typeof object === "object" && object !== null && !Array.isArray(object) && Object.keys(object).length > 0 && "options" in object;
1954
+ };
1960
1955
  async function getConfig({
1961
1956
  cwd,
1962
1957
  configPath,
@@ -1972,7 +1967,7 @@ async function getConfig({
1972
1967
  dotenv: true,
1973
1968
  jitiOptions: jitiOptions(cwd)
1974
1969
  });
1975
- if (!config.auth && !config.default) {
1970
+ if (!("auth" in config) && !isDefaultExport(config)) {
1976
1971
  if (shouldThrowOnError) {
1977
1972
  throw new Error(
1978
1973
  `Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`
@@ -1983,7 +1978,7 @@ async function getConfig({
1983
1978
  );
1984
1979
  process.exit(1);
1985
1980
  }
1986
- configFile = config.auth?.options || config.default?.options || null;
1981
+ configFile = "auth" in config ? config.auth?.options : config.options;
1987
1982
  }
1988
1983
  if (!configFile) {
1989
1984
  for (const possiblePath of possiblePaths) {
@@ -2326,7 +2321,7 @@ const generateDrizzleSchema = async ({
2326
2321
  ${Object.keys(fields).map((field) => {
2327
2322
  const attr = fields[field];
2328
2323
  let type = getType(field, attr);
2329
- if (attr.defaultValue) {
2324
+ if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") {
2330
2325
  if (typeof attr.defaultValue === "function") {
2331
2326
  type += `.$defaultFn(${attr.defaultValue})`;
2332
2327
  } else if (typeof attr.defaultValue === "string") {
@@ -2335,8 +2330,13 @@ const generateDrizzleSchema = async ({
2335
2330
  type += `.default(${attr.defaultValue})`;
2336
2331
  }
2337
2332
  }
2333
+ if (attr.onUpdate && attr.type === "date") {
2334
+ if (typeof attr.onUpdate === "function") {
2335
+ type += `.$onUpdate(${attr.onUpdate})`;
2336
+ }
2337
+ }
2338
2338
  return `${field}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(
2339
- attr.references.model,
2339
+ tables[attr.references.model]?.modelName || attr.references.model,
2340
2340
  adapter.options
2341
2341
  )}.${attr.references.field}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
2342
2342
  }).join(",\n ")}
@@ -2789,6 +2789,630 @@ const generate = new Command("generate").option(
2789
2789
  "the path to the configuration file. defaults to the first configuration file found."
2790
2790
  ).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);
2791
2791
 
2792
+ const DEMO_URL = "https://demo.better-auth.com";
2793
+ const CLIENT_ID = "better-auth-cli";
2794
+ const CONFIG_DIR = path.join(os.homedir(), ".better-auth");
2795
+ const TOKEN_FILE = path.join(CONFIG_DIR, "token.json");
2796
+ async function loginAction(opts) {
2797
+ const options = z.object({
2798
+ serverUrl: z.string().optional(),
2799
+ clientId: z.string().optional()
2800
+ }).parse(opts);
2801
+ const serverUrl = options.serverUrl || DEMO_URL;
2802
+ const clientId = options.clientId || CLIENT_ID;
2803
+ intro(chalk.bold("\u{1F510} Better Auth CLI Login (Demo)"));
2804
+ console.log(
2805
+ chalk.yellow(
2806
+ "\u26A0\uFE0F This is a demo feature for testing device authorization flow."
2807
+ )
2808
+ );
2809
+ console.log(
2810
+ chalk.gray(
2811
+ " It connects to the Better Auth demo server for testing purposes.\n"
2812
+ )
2813
+ );
2814
+ const existingToken = await getStoredToken();
2815
+ if (existingToken) {
2816
+ const shouldReauth = await confirm({
2817
+ message: "You're already logged in. Do you want to log in again?",
2818
+ initialValue: false
2819
+ });
2820
+ if (isCancel(shouldReauth) || !shouldReauth) {
2821
+ cancel("Login cancelled");
2822
+ process.exit(0);
2823
+ }
2824
+ }
2825
+ const authClient = createAuthClient({
2826
+ baseURL: serverUrl,
2827
+ plugins: [deviceAuthorizationClient()]
2828
+ });
2829
+ const spinner = yoctoSpinner({ text: "Requesting device authorization..." });
2830
+ spinner.start();
2831
+ try {
2832
+ const { data, error } = await authClient.device.code({
2833
+ client_id: clientId,
2834
+ scope: "openid profile email"
2835
+ });
2836
+ spinner.stop();
2837
+ if (error || !data) {
2838
+ logger.error(
2839
+ `Failed to request device authorization: ${error?.error_description || "Unknown error"}`
2840
+ );
2841
+ process.exit(1);
2842
+ }
2843
+ const {
2844
+ device_code,
2845
+ user_code,
2846
+ verification_uri,
2847
+ verification_uri_complete,
2848
+ interval = 5,
2849
+ expires_in
2850
+ } = data;
2851
+ console.log("");
2852
+ console.log(chalk.cyan("\u{1F4F1} Device Authorization Required"));
2853
+ console.log("");
2854
+ console.log(`Please visit: ${chalk.underline.blue(verification_uri)}`);
2855
+ console.log(`Enter code: ${chalk.bold.green(user_code)}`);
2856
+ console.log("");
2857
+ const shouldOpen = await confirm({
2858
+ message: "Open browser automatically?",
2859
+ initialValue: true
2860
+ });
2861
+ if (!isCancel(shouldOpen) && shouldOpen) {
2862
+ const urlToOpen = verification_uri_complete || verification_uri;
2863
+ await open(urlToOpen);
2864
+ }
2865
+ console.log(
2866
+ chalk.gray(
2867
+ `Waiting for authorization (expires in ${Math.floor(expires_in / 60)} minutes)...`
2868
+ )
2869
+ );
2870
+ const token = await pollForToken(
2871
+ authClient,
2872
+ device_code,
2873
+ clientId,
2874
+ interval
2875
+ );
2876
+ if (token) {
2877
+ await storeToken(token);
2878
+ const { data: session } = await authClient.getSession({
2879
+ fetchOptions: {
2880
+ headers: {
2881
+ Authorization: `Bearer ${token.access_token}`
2882
+ }
2883
+ }
2884
+ });
2885
+ outro(
2886
+ chalk.green(
2887
+ `\u2705 Demo login successful! Logged in as ${session?.user?.name || session?.user?.email || "User"}`
2888
+ )
2889
+ );
2890
+ console.log(
2891
+ chalk.gray(
2892
+ "\n\u{1F4DD} Note: This was a demo authentication for testing purposes."
2893
+ )
2894
+ );
2895
+ console.log(
2896
+ chalk.blue(
2897
+ "\nFor more information, visit: https://better-auth.com/docs/plugins/device-authorization"
2898
+ )
2899
+ );
2900
+ }
2901
+ } catch (err) {
2902
+ spinner.stop();
2903
+ logger.error(
2904
+ `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`
2905
+ );
2906
+ process.exit(1);
2907
+ }
2908
+ }
2909
+ async function pollForToken(authClient, deviceCode, clientId, initialInterval) {
2910
+ let pollingInterval = initialInterval;
2911
+ const spinner = yoctoSpinner({ text: "", color: "cyan" });
2912
+ let dots = 0;
2913
+ return new Promise((resolve, reject) => {
2914
+ const poll = async () => {
2915
+ dots = (dots + 1) % 4;
2916
+ spinner.text = chalk.gray(
2917
+ `Polling for authorization${".".repeat(dots)}${" ".repeat(3 - dots)}`
2918
+ );
2919
+ if (!spinner.isSpinning) spinner.start();
2920
+ try {
2921
+ const { data, error } = await authClient.device.token({
2922
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
2923
+ device_code: deviceCode,
2924
+ client_id: clientId,
2925
+ fetchOptions: {
2926
+ headers: {
2927
+ "user-agent": `Better Auth CLI`
2928
+ }
2929
+ }
2930
+ });
2931
+ if (data?.access_token) {
2932
+ spinner.stop();
2933
+ resolve(data);
2934
+ return;
2935
+ } else if (error) {
2936
+ switch (error.error) {
2937
+ case "authorization_pending":
2938
+ break;
2939
+ case "slow_down":
2940
+ pollingInterval += 5;
2941
+ spinner.text = chalk.yellow(
2942
+ `Slowing down polling to ${pollingInterval}s`
2943
+ );
2944
+ break;
2945
+ case "access_denied":
2946
+ spinner.stop();
2947
+ logger.error("Access was denied by the user");
2948
+ process.exit(1);
2949
+ break;
2950
+ case "expired_token":
2951
+ spinner.stop();
2952
+ logger.error("The device code has expired. Please try again.");
2953
+ process.exit(1);
2954
+ break;
2955
+ default:
2956
+ spinner.stop();
2957
+ logger.error(`Error: ${error.error_description}`);
2958
+ process.exit(1);
2959
+ }
2960
+ }
2961
+ } catch (err) {
2962
+ spinner.stop();
2963
+ logger.error(
2964
+ `Network error: ${err instanceof Error ? err.message : "Unknown error"}`
2965
+ );
2966
+ process.exit(1);
2967
+ }
2968
+ setTimeout(poll, pollingInterval * 1e3);
2969
+ };
2970
+ setTimeout(poll, pollingInterval * 1e3);
2971
+ });
2972
+ }
2973
+ async function storeToken(token) {
2974
+ try {
2975
+ await fs$1.mkdir(CONFIG_DIR, { recursive: true });
2976
+ const tokenData = {
2977
+ access_token: token.access_token,
2978
+ token_type: token.token_type || "Bearer",
2979
+ scope: token.scope,
2980
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2981
+ };
2982
+ await fs$1.writeFile(TOKEN_FILE, JSON.stringify(tokenData, null, 2), "utf-8");
2983
+ } catch (error) {
2984
+ logger.warn("Failed to store authentication token locally");
2985
+ }
2986
+ }
2987
+ async function getStoredToken() {
2988
+ try {
2989
+ const data = await fs$1.readFile(TOKEN_FILE, "utf-8");
2990
+ return JSON.parse(data);
2991
+ } catch {
2992
+ return null;
2993
+ }
2994
+ }
2995
+ const login = new Command("login").description(
2996
+ "Demo: Test device authorization flow with Better Auth demo server"
2997
+ ).option("--server-url <url>", "The Better Auth server URL", DEMO_URL).option("--client-id <id>", "The OAuth client ID", CLIENT_ID).action(loginAction);
2998
+
2999
+ function getSystemInfo() {
3000
+ const platform = os.platform();
3001
+ const arch = os.arch();
3002
+ const version = os.version();
3003
+ const release = os.release();
3004
+ const cpus = os.cpus();
3005
+ const memory = os.totalmem();
3006
+ const freeMemory = os.freemem();
3007
+ return {
3008
+ platform,
3009
+ arch,
3010
+ version,
3011
+ release,
3012
+ cpuCount: cpus.length,
3013
+ cpuModel: cpus[0]?.model || "Unknown",
3014
+ totalMemory: `${(memory / 1024 / 1024 / 1024).toFixed(2)} GB`,
3015
+ freeMemory: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`
3016
+ };
3017
+ }
3018
+ function getNodeInfo() {
3019
+ return {
3020
+ version: process.version,
3021
+ env: process.env.NODE_ENV || "development"
3022
+ };
3023
+ }
3024
+ function getPackageManager() {
3025
+ const userAgent = process.env.npm_config_user_agent || "";
3026
+ if (userAgent.includes("yarn")) {
3027
+ return { name: "yarn", version: getVersion("yarn") };
3028
+ }
3029
+ if (userAgent.includes("pnpm")) {
3030
+ return { name: "pnpm", version: getVersion("pnpm") };
3031
+ }
3032
+ if (userAgent.includes("bun")) {
3033
+ return { name: "bun", version: getVersion("bun") };
3034
+ }
3035
+ return { name: "npm", version: getVersion("npm") };
3036
+ }
3037
+ function getVersion(command) {
3038
+ try {
3039
+ const output = execSync(`${command} --version`, { encoding: "utf8" });
3040
+ return output.trim();
3041
+ } catch {
3042
+ return "Not installed";
3043
+ }
3044
+ }
3045
+ function getFrameworkInfo(projectRoot) {
3046
+ const packageJsonPath = path.join(projectRoot, "package.json");
3047
+ if (!existsSync(packageJsonPath)) {
3048
+ return null;
3049
+ }
3050
+ try {
3051
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
3052
+ const deps = {
3053
+ ...packageJson.dependencies,
3054
+ ...packageJson.devDependencies
3055
+ };
3056
+ const frameworks = {
3057
+ next: deps["next"],
3058
+ react: deps["react"],
3059
+ vue: deps["vue"],
3060
+ nuxt: deps["nuxt"],
3061
+ svelte: deps["svelte"],
3062
+ "@sveltejs/kit": deps["@sveltejs/kit"],
3063
+ express: deps["express"],
3064
+ fastify: deps["fastify"],
3065
+ hono: deps["hono"],
3066
+ remix: deps["@remix-run/react"],
3067
+ astro: deps["astro"],
3068
+ solid: deps["solid-js"],
3069
+ qwik: deps["@builder.io/qwik"]
3070
+ };
3071
+ const installedFrameworks = Object.entries(frameworks).filter(([_, version]) => version).map(([name, version]) => ({ name, version }));
3072
+ return installedFrameworks.length > 0 ? installedFrameworks : null;
3073
+ } catch {
3074
+ return null;
3075
+ }
3076
+ }
3077
+ function getDatabaseInfo(projectRoot) {
3078
+ const packageJsonPath = path.join(projectRoot, "package.json");
3079
+ if (!existsSync(packageJsonPath)) {
3080
+ return null;
3081
+ }
3082
+ try {
3083
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
3084
+ const deps = {
3085
+ ...packageJson.dependencies,
3086
+ ...packageJson.devDependencies
3087
+ };
3088
+ const databases = {
3089
+ "better-sqlite3": deps["better-sqlite3"],
3090
+ "@libsql/client": deps["@libsql/client"],
3091
+ "@libsql/kysely-libsql": deps["@libsql/kysely-libsql"],
3092
+ mysql2: deps["mysql2"],
3093
+ pg: deps["pg"],
3094
+ postgres: deps["postgres"],
3095
+ "@prisma/client": deps["@prisma/client"],
3096
+ drizzle: deps["drizzle-orm"],
3097
+ kysely: deps["kysely"],
3098
+ mongodb: deps["mongodb"],
3099
+ "@neondatabase/serverless": deps["@neondatabase/serverless"],
3100
+ "@vercel/postgres": deps["@vercel/postgres"],
3101
+ "@planetscale/database": deps["@planetscale/database"]
3102
+ };
3103
+ const installedDatabases = Object.entries(databases).filter(([_, version]) => version).map(([name, version]) => ({ name, version }));
3104
+ return installedDatabases.length > 0 ? installedDatabases : null;
3105
+ } catch {
3106
+ return null;
3107
+ }
3108
+ }
3109
+ function sanitizeBetterAuthConfig(config) {
3110
+ if (!config) return null;
3111
+ const sanitized = JSON.parse(JSON.stringify(config));
3112
+ const sensitiveKeys = [
3113
+ "secret",
3114
+ "clientSecret",
3115
+ "clientId",
3116
+ "authToken",
3117
+ "apiKey",
3118
+ "apiSecret",
3119
+ "privateKey",
3120
+ "publicKey",
3121
+ "password",
3122
+ "token",
3123
+ "webhook",
3124
+ "connectionString",
3125
+ "databaseUrl",
3126
+ "databaseURL",
3127
+ "TURSO_AUTH_TOKEN",
3128
+ "TURSO_DATABASE_URL",
3129
+ "MYSQL_DATABASE_URL",
3130
+ "DATABASE_URL",
3131
+ "POSTGRES_URL",
3132
+ "MONGODB_URI",
3133
+ "stripeKey",
3134
+ "stripeWebhookSecret"
3135
+ ];
3136
+ const allowedKeys = [
3137
+ "baseURL",
3138
+ "callbackURL",
3139
+ "redirectURL",
3140
+ "trustedOrigins",
3141
+ "appName"
3142
+ ];
3143
+ function redactSensitive(obj, parentKey) {
3144
+ if (typeof obj !== "object" || obj === null) {
3145
+ if (parentKey && typeof obj === "string" && obj.length > 0) {
3146
+ if (allowedKeys.some(
3147
+ (allowed) => parentKey.toLowerCase() === allowed.toLowerCase()
3148
+ )) {
3149
+ return obj;
3150
+ }
3151
+ const lowerKey = parentKey.toLowerCase();
3152
+ if (sensitiveKeys.some((key) => {
3153
+ const lowerSensitiveKey = key.toLowerCase();
3154
+ return lowerKey === lowerSensitiveKey || lowerKey.endsWith(lowerSensitiveKey);
3155
+ })) {
3156
+ return "[REDACTED]";
3157
+ }
3158
+ }
3159
+ return obj;
3160
+ }
3161
+ if (Array.isArray(obj)) {
3162
+ return obj.map((item) => redactSensitive(item, parentKey));
3163
+ }
3164
+ const result = {};
3165
+ for (const [key, value] of Object.entries(obj)) {
3166
+ if (allowedKeys.some(
3167
+ (allowed) => key.toLowerCase() === allowed.toLowerCase()
3168
+ )) {
3169
+ result[key] = value;
3170
+ continue;
3171
+ }
3172
+ const lowerKey = key.toLowerCase();
3173
+ if (sensitiveKeys.some((sensitiveKey) => {
3174
+ const lowerSensitiveKey = sensitiveKey.toLowerCase();
3175
+ return lowerKey === lowerSensitiveKey || lowerKey.endsWith(lowerSensitiveKey);
3176
+ })) {
3177
+ if (typeof value === "string" && value.length > 0) {
3178
+ result[key] = "[REDACTED]";
3179
+ } else if (typeof value === "object" && value !== null) {
3180
+ result[key] = redactSensitive(value, key);
3181
+ } else {
3182
+ result[key] = value;
3183
+ }
3184
+ } else {
3185
+ result[key] = redactSensitive(value, key);
3186
+ }
3187
+ }
3188
+ return result;
3189
+ }
3190
+ if (sanitized.database) {
3191
+ if (typeof sanitized.database === "string") {
3192
+ sanitized.database = "[REDACTED]";
3193
+ } else if (sanitized.database.url) {
3194
+ sanitized.database.url = "[REDACTED]";
3195
+ }
3196
+ if (sanitized.database.authToken) {
3197
+ sanitized.database.authToken = "[REDACTED]";
3198
+ }
3199
+ }
3200
+ if (sanitized.socialProviders) {
3201
+ for (const provider in sanitized.socialProviders) {
3202
+ if (sanitized.socialProviders[provider]) {
3203
+ sanitized.socialProviders[provider] = redactSensitive(
3204
+ sanitized.socialProviders[provider],
3205
+ provider
3206
+ );
3207
+ }
3208
+ }
3209
+ }
3210
+ if (sanitized.emailAndPassword?.sendResetPassword) {
3211
+ sanitized.emailAndPassword.sendResetPassword = "[Function]";
3212
+ }
3213
+ if (sanitized.emailVerification?.sendVerificationEmail) {
3214
+ sanitized.emailVerification.sendVerificationEmail = "[Function]";
3215
+ }
3216
+ if (sanitized.plugins && Array.isArray(sanitized.plugins)) {
3217
+ sanitized.plugins = sanitized.plugins.map((plugin) => {
3218
+ if (typeof plugin === "function") {
3219
+ return "[Plugin Function]";
3220
+ }
3221
+ if (plugin && typeof plugin === "object") {
3222
+ const pluginName = plugin.id || plugin.name || "unknown";
3223
+ return {
3224
+ name: pluginName,
3225
+ config: redactSensitive(plugin.config || plugin)
3226
+ };
3227
+ }
3228
+ return plugin;
3229
+ });
3230
+ }
3231
+ return redactSensitive(sanitized);
3232
+ }
3233
+ async function getBetterAuthInfo(projectRoot, configPath, suppressLogs = false) {
3234
+ try {
3235
+ const originalLog = console.log;
3236
+ const originalWarn = console.warn;
3237
+ const originalError = console.error;
3238
+ if (suppressLogs) {
3239
+ console.log = () => {
3240
+ };
3241
+ console.warn = () => {
3242
+ };
3243
+ console.error = () => {
3244
+ };
3245
+ }
3246
+ try {
3247
+ const config = await getConfig({
3248
+ cwd: projectRoot,
3249
+ configPath,
3250
+ shouldThrowOnError: false
3251
+ });
3252
+ const packageInfo = await getPackageInfo();
3253
+ return {
3254
+ version: packageInfo.version || "Unknown",
3255
+ config: sanitizeBetterAuthConfig(config)
3256
+ };
3257
+ } finally {
3258
+ if (suppressLogs) {
3259
+ console.log = originalLog;
3260
+ console.warn = originalWarn;
3261
+ console.error = originalError;
3262
+ }
3263
+ }
3264
+ } catch (error) {
3265
+ return {
3266
+ version: "Unknown",
3267
+ config: null,
3268
+ error: error instanceof Error ? error.message : "Failed to load Better Auth config"
3269
+ };
3270
+ }
3271
+ }
3272
+ function formatOutput(data, indent = 0) {
3273
+ const spaces = " ".repeat(indent);
3274
+ if (data === null || data === void 0) {
3275
+ return `${spaces}${chalk.gray("N/A")}`;
3276
+ }
3277
+ if (typeof data === "string" || typeof data === "number" || typeof data === "boolean") {
3278
+ return `${spaces}${data}`;
3279
+ }
3280
+ if (Array.isArray(data)) {
3281
+ if (data.length === 0) {
3282
+ return `${spaces}${chalk.gray("[]")}`;
3283
+ }
3284
+ return data.map((item) => formatOutput(item, indent)).join("\n");
3285
+ }
3286
+ if (typeof data === "object") {
3287
+ const entries = Object.entries(data);
3288
+ if (entries.length === 0) {
3289
+ return `${spaces}${chalk.gray("{}")}`;
3290
+ }
3291
+ return entries.map(([key, value]) => {
3292
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
3293
+ return `${spaces}${chalk.cyan(key)}:
3294
+ ${formatOutput(value, indent + 2)}`;
3295
+ }
3296
+ return `${spaces}${chalk.cyan(key)}: ${formatOutput(value, 0)}`;
3297
+ }).join("\n");
3298
+ }
3299
+ return `${spaces}${JSON.stringify(data)}`;
3300
+ }
3301
+ const info = new Command("info").description("Display system and Better Auth configuration information").option("--cwd <cwd>", "The working directory", process.cwd()).option("--config <config>", "Path to the Better Auth configuration file").option("-j, --json", "Output as JSON").option("-c, --copy", "Copy output to clipboard (requires pbcopy/xclip)").action(async (options) => {
3302
+ const projectRoot = path.resolve(options.cwd || process.cwd());
3303
+ const systemInfo = getSystemInfo();
3304
+ const nodeInfo = getNodeInfo();
3305
+ const packageManager = getPackageManager();
3306
+ const frameworks = getFrameworkInfo(projectRoot);
3307
+ const databases = getDatabaseInfo(projectRoot);
3308
+ const betterAuthInfo = await getBetterAuthInfo(
3309
+ projectRoot,
3310
+ options.config,
3311
+ options.json
3312
+ );
3313
+ const fullInfo = {
3314
+ system: systemInfo,
3315
+ node: nodeInfo,
3316
+ packageManager,
3317
+ frameworks,
3318
+ databases,
3319
+ betterAuth: betterAuthInfo
3320
+ };
3321
+ if (options.json) {
3322
+ const jsonOutput = JSON.stringify(fullInfo, null, 2);
3323
+ console.log(jsonOutput);
3324
+ if (options.copy) {
3325
+ try {
3326
+ const platform = os.platform();
3327
+ if (platform === "darwin") {
3328
+ execSync("pbcopy", { input: jsonOutput });
3329
+ console.log(chalk.green("\n\u2713 Copied to clipboard"));
3330
+ } else if (platform === "linux") {
3331
+ execSync("xclip -selection clipboard", { input: jsonOutput });
3332
+ console.log(chalk.green("\n\u2713 Copied to clipboard"));
3333
+ } else if (platform === "win32") {
3334
+ execSync("clip", { input: jsonOutput });
3335
+ console.log(chalk.green("\n\u2713 Copied to clipboard"));
3336
+ }
3337
+ } catch {
3338
+ console.log(chalk.yellow("\n\u26A0 Could not copy to clipboard"));
3339
+ }
3340
+ }
3341
+ return;
3342
+ }
3343
+ console.log(chalk.bold("\n\u{1F4CA} Better Auth System Information\n"));
3344
+ console.log(chalk.gray("=".repeat(50)));
3345
+ console.log(chalk.bold.white("\n\u{1F5A5}\uFE0F System Information:"));
3346
+ console.log(formatOutput(systemInfo, 2));
3347
+ console.log(chalk.bold.white("\n\u{1F4E6} Node.js:"));
3348
+ console.log(formatOutput(nodeInfo, 2));
3349
+ console.log(chalk.bold.white("\n\u{1F4E6} Package Manager:"));
3350
+ console.log(formatOutput(packageManager, 2));
3351
+ if (frameworks) {
3352
+ console.log(chalk.bold.white("\n\u{1F680} Frameworks:"));
3353
+ console.log(formatOutput(frameworks, 2));
3354
+ }
3355
+ if (databases) {
3356
+ console.log(chalk.bold.white("\n\u{1F4BE} Database Clients:"));
3357
+ console.log(formatOutput(databases, 2));
3358
+ }
3359
+ console.log(chalk.bold.white("\n\u{1F510} Better Auth:"));
3360
+ if (betterAuthInfo.error) {
3361
+ console.log(` ${chalk.red("Error:")} ${betterAuthInfo.error}`);
3362
+ } else {
3363
+ console.log(` ${chalk.cyan("Version")}: ${betterAuthInfo.version}`);
3364
+ if (betterAuthInfo.config) {
3365
+ console.log(` ${chalk.cyan("Configuration")}:`);
3366
+ console.log(formatOutput(betterAuthInfo.config, 4));
3367
+ }
3368
+ }
3369
+ console.log(chalk.gray("\n" + "=".repeat(50)));
3370
+ console.log(chalk.gray("\n\u{1F4A1} Tip: Use --json flag for JSON output"));
3371
+ console.log(chalk.gray("\u{1F4A1} Use --copy flag to copy output to clipboard"));
3372
+ console.log(
3373
+ chalk.gray("\u{1F4A1} When reporting issues, include this information\n")
3374
+ );
3375
+ if (options.copy) {
3376
+ const textOutput = `
3377
+ Better Auth System Information
3378
+ ==============================
3379
+
3380
+ System Information:
3381
+ ${JSON.stringify(systemInfo, null, 2)}
3382
+
3383
+ Node.js:
3384
+ ${JSON.stringify(nodeInfo, null, 2)}
3385
+
3386
+ Package Manager:
3387
+ ${JSON.stringify(packageManager, null, 2)}
3388
+
3389
+ Frameworks:
3390
+ ${JSON.stringify(frameworks, null, 2)}
3391
+
3392
+ Database Clients:
3393
+ ${JSON.stringify(databases, null, 2)}
3394
+
3395
+ Better Auth:
3396
+ ${JSON.stringify(betterAuthInfo, null, 2)}
3397
+ `;
3398
+ try {
3399
+ const platform = os.platform();
3400
+ if (platform === "darwin") {
3401
+ execSync("pbcopy", { input: textOutput });
3402
+ console.log(chalk.green("\u2713 Copied to clipboard"));
3403
+ } else if (platform === "linux") {
3404
+ execSync("xclip -selection clipboard", { input: textOutput });
3405
+ console.log(chalk.green("\u2713 Copied to clipboard"));
3406
+ } else if (platform === "win32") {
3407
+ execSync("clip", { input: textOutput });
3408
+ console.log(chalk.green("\u2713 Copied to clipboard"));
3409
+ }
3410
+ } catch {
3411
+ console.log(chalk.yellow("\u26A0 Could not copy to clipboard"));
3412
+ }
3413
+ }
3414
+ });
3415
+
2792
3416
  process.on("SIGINT", () => process.exit(0));
2793
3417
  process.on("SIGTERM", () => process.exit(0));
2794
3418
  async function main() {
@@ -2798,7 +3422,7 @@ async function main() {
2798
3422
  packageInfo = await getPackageInfo();
2799
3423
  } catch (error) {
2800
3424
  }
2801
- program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).version(packageInfo.version || "1.1.2").description("Better Auth CLI").action(() => program.help());
3425
+ program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(info).addCommand(login).version(packageInfo.version || "1.1.2").description("Better Auth CLI").action(() => program.help());
2802
3426
  program.parse();
2803
3427
  }
2804
3428
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/cli",
3
- "version": "1.3.7",
3
+ "version": "1.3.8-beta.10",
4
4
  "description": "The CLI for Better Auth",
5
5
  "module": "dist/index.mjs",
6
6
  "repository": {
@@ -26,11 +26,9 @@
26
26
  "exports": "./dist/index.mjs",
27
27
  "bin": "./dist/index.mjs",
28
28
  "devDependencies": {
29
- "@types/diff": "^7.0.1",
30
- "@types/fs-extra": "^11.0.4",
29
+ "tsx": "^4.20.4",
31
30
  "typescript": "^5.9.2",
32
- "unbuild": "^3.5.0",
33
- "zod": "^4.0.0"
31
+ "unbuild": "^3.5.0"
34
32
  },
35
33
  "dependencies": {
36
34
  "@babel/core": "^7.0.0",
@@ -42,23 +40,21 @@
42
40
  "@types/better-sqlite3": "^7.6.12",
43
41
  "@types/prompts": "^2.4.9",
44
42
  "better-sqlite3": "^11.6.0",
45
- "c12": "^2.0.1",
43
+ "c12": "^3.2.0",
46
44
  "chalk": "^5.3.0",
47
45
  "commander": "^12.1.0",
48
46
  "dotenv": "^16.4.7",
49
47
  "drizzle-orm": "^0.33.0",
50
- "fs-extra": "^11.3.0",
51
48
  "get-tsconfig": "^4.8.1",
49
+ "open": "^10.1.0",
52
50
  "prettier": "^3.4.2",
53
51
  "prisma": "^5.22.0",
54
52
  "prompts": "^2.4.2",
55
53
  "semver": "^7.7.1",
56
54
  "tinyexec": "^0.3.1",
57
55
  "yocto-spinner": "^0.1.1",
58
- "better-auth": "1.3.7"
59
- },
60
- "peerDependencies": {
61
- "zod": "3.25.0 || ^4.0.0"
56
+ "zod": "^4.0.0",
57
+ "better-auth": "1.3.8-beta.10"
62
58
  },
63
59
  "files": [
64
60
  "dist"
@@ -67,6 +63,7 @@
67
63
  "build": "unbuild",
68
64
  "stub": "unbuild --stub",
69
65
  "start": "node ./dist/index.mjs",
66
+ "dev": "tsx ./src/index.ts",
70
67
  "test": "vitest"
71
68
  }
72
69
  }