@better-auth/cli 1.3.8-beta.1 → 1.3.8-beta.3

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 +213 -4
  2. package/package.json +6 -3
package/dist/index.mjs CHANGED
@@ -20,6 +20,10 @@ import { loadConfig } from 'c12';
20
20
  import babelPresetTypeScript from '@babel/preset-typescript';
21
21
  import babelPresetReact from '@babel/preset-react';
22
22
  import { produceSchema } from '@mrleebo/prisma-ast';
23
+ import { createAuthClient } from 'better-auth/client';
24
+ import { deviceAuthorizationClient } from 'better-auth/client/plugins';
25
+ import open from 'open';
26
+ import os from 'os';
23
27
  import 'dotenv/config';
24
28
 
25
29
  function getPackageInfo(cwd) {
@@ -1957,6 +1961,9 @@ const jitiOptions = (cwd) => {
1957
1961
  alias
1958
1962
  };
1959
1963
  };
1964
+ const isDefaultExport = (object) => {
1965
+ return typeof object === "object" && object !== null && !Array.isArray(object) && Object.keys(object).length > 0 && "options" in object;
1966
+ };
1960
1967
  async function getConfig({
1961
1968
  cwd,
1962
1969
  configPath,
@@ -1972,7 +1979,7 @@ async function getConfig({
1972
1979
  dotenv: true,
1973
1980
  jitiOptions: jitiOptions(cwd)
1974
1981
  });
1975
- if (!config.auth && !config.default) {
1982
+ if (!("auth" in config) && !isDefaultExport(config)) {
1976
1983
  if (shouldThrowOnError) {
1977
1984
  throw new Error(
1978
1985
  `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 +1990,7 @@ async function getConfig({
1983
1990
  );
1984
1991
  process.exit(1);
1985
1992
  }
1986
- configFile = config.auth?.options || config.default?.options || null;
1993
+ configFile = "auth" in config ? config.auth?.options : config.options;
1987
1994
  }
1988
1995
  if (!configFile) {
1989
1996
  for (const possiblePath of possiblePaths) {
@@ -2326,7 +2333,7 @@ const generateDrizzleSchema = async ({
2326
2333
  ${Object.keys(fields).map((field) => {
2327
2334
  const attr = fields[field];
2328
2335
  let type = getType(field, attr);
2329
- if (attr.defaultValue) {
2336
+ if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") {
2330
2337
  if (typeof attr.defaultValue === "function") {
2331
2338
  type += `.$defaultFn(${attr.defaultValue})`;
2332
2339
  } else if (typeof attr.defaultValue === "string") {
@@ -2789,6 +2796,208 @@ const generate = new Command("generate").option(
2789
2796
  "the path to the configuration file. defaults to the first configuration file found."
2790
2797
  ).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
2798
 
2799
+ const DEMO_URL = "https://demo.better-auth.com";
2800
+ const CLIENT_ID = "better-auth-cli";
2801
+ const CONFIG_DIR = path.join(os.homedir(), ".better-auth");
2802
+ const TOKEN_FILE = path.join(CONFIG_DIR, "token.json");
2803
+ async function loginAction(opts) {
2804
+ const options = z.object({
2805
+ serverUrl: z.string().optional(),
2806
+ clientId: z.string().optional()
2807
+ }).parse(opts);
2808
+ const serverUrl = options.serverUrl || DEMO_URL;
2809
+ const clientId = options.clientId || CLIENT_ID;
2810
+ intro(chalk.bold("\u{1F510} Better Auth CLI Login (Demo)"));
2811
+ console.log(
2812
+ chalk.yellow(
2813
+ "\u26A0\uFE0F This is a demo feature for testing device authorization flow."
2814
+ )
2815
+ );
2816
+ console.log(
2817
+ chalk.gray(
2818
+ " It connects to the Better Auth demo server for testing purposes.\n"
2819
+ )
2820
+ );
2821
+ const existingToken = await getStoredToken();
2822
+ if (existingToken) {
2823
+ const shouldReauth = await confirm({
2824
+ message: "You're already logged in. Do you want to log in again?",
2825
+ initialValue: false
2826
+ });
2827
+ if (isCancel(shouldReauth) || !shouldReauth) {
2828
+ cancel("Login cancelled");
2829
+ process.exit(0);
2830
+ }
2831
+ }
2832
+ const authClient = createAuthClient({
2833
+ baseURL: serverUrl,
2834
+ plugins: [deviceAuthorizationClient()]
2835
+ });
2836
+ const spinner = yoctoSpinner({ text: "Requesting device authorization..." });
2837
+ spinner.start();
2838
+ try {
2839
+ const { data, error } = await authClient.device.code({
2840
+ client_id: clientId,
2841
+ scope: "openid profile email"
2842
+ });
2843
+ spinner.stop();
2844
+ if (error || !data) {
2845
+ logger.error(
2846
+ `Failed to request device authorization: ${error?.error_description || "Unknown error"}`
2847
+ );
2848
+ process.exit(1);
2849
+ }
2850
+ const {
2851
+ device_code,
2852
+ user_code,
2853
+ verification_uri,
2854
+ verification_uri_complete,
2855
+ interval = 5,
2856
+ expires_in
2857
+ } = data;
2858
+ console.log("");
2859
+ console.log(chalk.cyan("\u{1F4F1} Device Authorization Required"));
2860
+ console.log("");
2861
+ console.log(`Please visit: ${chalk.underline.blue(verification_uri)}`);
2862
+ console.log(`Enter code: ${chalk.bold.green(user_code)}`);
2863
+ console.log("");
2864
+ const shouldOpen = await confirm({
2865
+ message: "Open browser automatically?",
2866
+ initialValue: true
2867
+ });
2868
+ if (!isCancel(shouldOpen) && shouldOpen) {
2869
+ const urlToOpen = verification_uri_complete || verification_uri;
2870
+ await open(urlToOpen);
2871
+ }
2872
+ console.log(
2873
+ chalk.gray(
2874
+ `Waiting for authorization (expires in ${Math.floor(expires_in / 60)} minutes)...`
2875
+ )
2876
+ );
2877
+ const token = await pollForToken(
2878
+ authClient,
2879
+ device_code,
2880
+ clientId,
2881
+ interval
2882
+ );
2883
+ if (token) {
2884
+ await storeToken(token);
2885
+ const { data: session } = await authClient.getSession({
2886
+ fetchOptions: {
2887
+ headers: {
2888
+ Authorization: `Bearer ${token.access_token}`
2889
+ }
2890
+ }
2891
+ });
2892
+ outro(
2893
+ chalk.green(
2894
+ `\u2705 Demo login successful! Logged in as ${session?.user?.name || session?.user?.email || "User"}`
2895
+ )
2896
+ );
2897
+ console.log(
2898
+ chalk.gray(
2899
+ "\n\u{1F4DD} Note: This was a demo authentication for testing purposes."
2900
+ )
2901
+ );
2902
+ }
2903
+ } catch (err) {
2904
+ spinner.stop();
2905
+ logger.error(
2906
+ `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`
2907
+ );
2908
+ process.exit(1);
2909
+ }
2910
+ }
2911
+ async function pollForToken(authClient, deviceCode, clientId, initialInterval) {
2912
+ let pollingInterval = initialInterval;
2913
+ const spinner = yoctoSpinner({ text: "", color: "cyan" });
2914
+ let dots = 0;
2915
+ return new Promise((resolve, reject) => {
2916
+ const poll = async () => {
2917
+ dots = (dots + 1) % 4;
2918
+ spinner.text = chalk.gray(
2919
+ `Polling for authorization${".".repeat(dots)}${" ".repeat(3 - dots)}`
2920
+ );
2921
+ if (!spinner.isSpinning) spinner.start();
2922
+ try {
2923
+ const { data, error } = await authClient.device.token({
2924
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
2925
+ device_code: deviceCode,
2926
+ client_id: clientId,
2927
+ fetchOptions: {
2928
+ headers: {
2929
+ "user-agent": `Better Auth CLI`
2930
+ }
2931
+ }
2932
+ });
2933
+ if (data?.access_token) {
2934
+ spinner.stop();
2935
+ resolve(data);
2936
+ return;
2937
+ } else if (error) {
2938
+ switch (error.error) {
2939
+ case "authorization_pending":
2940
+ break;
2941
+ case "slow_down":
2942
+ pollingInterval += 5;
2943
+ spinner.text = chalk.yellow(
2944
+ `Slowing down polling to ${pollingInterval}s`
2945
+ );
2946
+ break;
2947
+ case "access_denied":
2948
+ spinner.stop();
2949
+ logger.error("Access was denied by the user");
2950
+ process.exit(1);
2951
+ break;
2952
+ case "expired_token":
2953
+ spinner.stop();
2954
+ logger.error("The device code has expired. Please try again.");
2955
+ process.exit(1);
2956
+ break;
2957
+ default:
2958
+ spinner.stop();
2959
+ logger.error(`Error: ${error.error_description}`);
2960
+ process.exit(1);
2961
+ }
2962
+ }
2963
+ } catch (err) {
2964
+ spinner.stop();
2965
+ logger.error(
2966
+ `Network error: ${err instanceof Error ? err.message : "Unknown error"}`
2967
+ );
2968
+ process.exit(1);
2969
+ }
2970
+ setTimeout(poll, pollingInterval * 1e3);
2971
+ };
2972
+ setTimeout(poll, pollingInterval * 1e3);
2973
+ });
2974
+ }
2975
+ async function storeToken(token) {
2976
+ try {
2977
+ await fs$1.mkdir(CONFIG_DIR, { recursive: true });
2978
+ const tokenData = {
2979
+ access_token: token.access_token,
2980
+ token_type: token.token_type || "Bearer",
2981
+ scope: token.scope,
2982
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2983
+ };
2984
+ await fs$1.writeFile(TOKEN_FILE, JSON.stringify(tokenData, null, 2), "utf-8");
2985
+ } catch (error) {
2986
+ logger.warn("Failed to store authentication token locally");
2987
+ }
2988
+ }
2989
+ async function getStoredToken() {
2990
+ try {
2991
+ const data = await fs$1.readFile(TOKEN_FILE, "utf-8");
2992
+ return JSON.parse(data);
2993
+ } catch {
2994
+ return null;
2995
+ }
2996
+ }
2997
+ const login = new Command("login").description(
2998
+ "Demo: Test device authorization flow with Better Auth demo server"
2999
+ ).option("--server-url <url>", "The Better Auth server URL", DEMO_URL).option("--client-id <id>", "The OAuth client ID", CLIENT_ID).action(loginAction);
3000
+
2792
3001
  process.on("SIGINT", () => process.exit(0));
2793
3002
  process.on("SIGTERM", () => process.exit(0));
2794
3003
  async function main() {
@@ -2798,7 +3007,7 @@ async function main() {
2798
3007
  packageInfo = await getPackageInfo();
2799
3008
  } catch (error) {
2800
3009
  }
2801
- program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).version(packageInfo.version || "1.1.2").description("Better Auth CLI").action(() => program.help());
3010
+ program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(login).version(packageInfo.version || "1.1.2").description("Better Auth CLI").action(() => program.help());
2802
3011
  program.parse();
2803
3012
  }
2804
3013
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/cli",
3
- "version": "1.3.8-beta.1",
3
+ "version": "1.3.8-beta.3",
4
4
  "description": "The CLI for Better Auth",
5
5
  "module": "dist/index.mjs",
6
6
  "repository": {
@@ -28,6 +28,7 @@
28
28
  "devDependencies": {
29
29
  "@types/diff": "^7.0.1",
30
30
  "@types/fs-extra": "^11.0.4",
31
+ "tsx": "^4.20.4",
31
32
  "typescript": "^5.9.2",
32
33
  "unbuild": "^3.5.0",
33
34
  "zod": "^4.0.0"
@@ -42,20 +43,21 @@
42
43
  "@types/better-sqlite3": "^7.6.12",
43
44
  "@types/prompts": "^2.4.9",
44
45
  "better-sqlite3": "^11.6.0",
45
- "c12": "^2.0.1",
46
+ "c12": "^3.2.0",
46
47
  "chalk": "^5.3.0",
47
48
  "commander": "^12.1.0",
48
49
  "dotenv": "^16.4.7",
49
50
  "drizzle-orm": "^0.33.0",
50
51
  "fs-extra": "^11.3.0",
51
52
  "get-tsconfig": "^4.8.1",
53
+ "open": "^10.1.0",
52
54
  "prettier": "^3.4.2",
53
55
  "prisma": "^5.22.0",
54
56
  "prompts": "^2.4.2",
55
57
  "semver": "^7.7.1",
56
58
  "tinyexec": "^0.3.1",
57
59
  "yocto-spinner": "^0.1.1",
58
- "better-auth": "1.3.8-beta.1"
60
+ "better-auth": "1.3.8-beta.3"
59
61
  },
60
62
  "peerDependencies": {
61
63
  "zod": "3.25.0 || ^4.0.0"
@@ -67,6 +69,7 @@
67
69
  "build": "unbuild",
68
70
  "stub": "unbuild --stub",
69
71
  "start": "node ./dist/index.mjs",
72
+ "dev": "tsx ./src/index.ts",
70
73
  "test": "vitest"
71
74
  }
72
75
  }