@better-auth/cli 1.3.8-beta.2 → 1.3.8-beta.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.
Files changed (2) hide show
  1. package/dist/index.mjs +231 -20
  2. package/package.json +7 -10
package/dist/index.mjs CHANGED
@@ -4,10 +4,9 @@ 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 } 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
12
  import { exec } from 'child_process';
@@ -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({
@@ -1690,9 +1689,9 @@ function getSvelteKitPathAliases(cwd) {
1690
1689
  const svelteConfigPath = path.join(cwd, "svelte.config.js");
1691
1690
  const svelteConfigTsPath = path.join(cwd, "svelte.config.ts");
1692
1691
  let isSvelteKitProject = false;
1693
- if (fs$2.existsSync(packageJsonPath)) {
1692
+ if (fs.existsSync(packageJsonPath)) {
1694
1693
  try {
1695
- const packageJson = JSON.parse(fs$2.readFileSync(packageJsonPath, "utf-8"));
1694
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1696
1695
  const deps = {
1697
1696
  ...packageJson.dependencies,
1698
1697
  ...packageJson.devDependencies
@@ -1702,19 +1701,19 @@ function getSvelteKitPathAliases(cwd) {
1702
1701
  }
1703
1702
  }
1704
1703
  if (!isSvelteKitProject) {
1705
- isSvelteKitProject = fs$2.existsSync(svelteConfigPath) || fs$2.existsSync(svelteConfigTsPath);
1704
+ isSvelteKitProject = fs.existsSync(svelteConfigPath) || fs.existsSync(svelteConfigTsPath);
1706
1705
  }
1707
1706
  if (!isSvelteKitProject) {
1708
1707
  return aliases;
1709
1708
  }
1710
1709
  const libPaths = [path.join(cwd, "src", "lib"), path.join(cwd, "lib")];
1711
1710
  for (const libPath of libPaths) {
1712
- if (fs$2.existsSync(libPath)) {
1711
+ if (fs.existsSync(libPath)) {
1713
1712
  aliases["$lib"] = libPath;
1714
1713
  const commonSubPaths = ["server", "utils", "components", "stores"];
1715
1714
  for (const subPath of commonSubPaths) {
1716
1715
  const subDir = path.join(libPath, subPath);
1717
- if (fs$2.existsSync(subDir)) {
1716
+ if (fs.existsSync(subDir)) {
1718
1717
  aliases[`$lib/${subPath}`] = subDir;
1719
1718
  }
1720
1719
  }
@@ -1733,9 +1732,9 @@ function getSvelteConfigAliases(cwd) {
1733
1732
  path.join(cwd, "svelte.config.ts")
1734
1733
  ];
1735
1734
  for (const configPath of configPaths) {
1736
- if (fs$2.existsSync(configPath)) {
1735
+ if (fs.existsSync(configPath)) {
1737
1736
  try {
1738
- const content = fs$2.readFileSync(configPath, "utf-8");
1737
+ const content = fs.readFileSync(configPath, "utf-8");
1739
1738
  const aliasMatch = content.match(/alias\s*:\s*\{([^}]+)\}/);
1740
1739
  if (aliasMatch && aliasMatch[1]) {
1741
1740
  const aliasContent = aliasMatch[1];
@@ -1871,9 +1870,9 @@ function resolveReferencePath(configDir, refPath) {
1871
1870
  if (refPath.endsWith(".json")) {
1872
1871
  return resolvedPath;
1873
1872
  }
1874
- if (fs$2.existsSync(resolvedPath)) {
1873
+ if (fs.existsSync(resolvedPath)) {
1875
1874
  try {
1876
- const stats = fs$2.statSync(resolvedPath);
1875
+ const stats = fs.statSync(resolvedPath);
1877
1876
  if (stats.isFile()) {
1878
1877
  return resolvedPath;
1879
1878
  }
@@ -1887,7 +1886,7 @@ function getPathAliasesRecursive(tsconfigPath, visited = /* @__PURE__ */ new Set
1887
1886
  return {};
1888
1887
  }
1889
1888
  visited.add(tsconfigPath);
1890
- if (!fs$2.existsSync(tsconfigPath)) {
1889
+ if (!fs.existsSync(tsconfigPath)) {
1891
1890
  logger.warn(`Referenced tsconfig not found: ${tsconfigPath}`);
1892
1891
  return {};
1893
1892
  }
@@ -1924,7 +1923,7 @@ function getPathAliasesRecursive(tsconfigPath, visited = /* @__PURE__ */ new Set
1924
1923
  }
1925
1924
  function getPathAliases(cwd) {
1926
1925
  const tsConfigPath = path.join(cwd, "tsconfig.json");
1927
- if (!fs$2.existsSync(tsConfigPath)) {
1926
+ if (!fs.existsSync(tsConfigPath)) {
1928
1927
  return null;
1929
1928
  }
1930
1929
  try {
@@ -2338,8 +2337,13 @@ const generateDrizzleSchema = async ({
2338
2337
  type += `.default(${attr.defaultValue})`;
2339
2338
  }
2340
2339
  }
2340
+ if (attr.onUpdate && attr.type === "date") {
2341
+ if (typeof attr.onUpdate === "function") {
2342
+ type += `.$onUpdate(${attr.onUpdate})`;
2343
+ }
2344
+ }
2341
2345
  return `${field}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(
2342
- attr.references.model,
2346
+ tables[attr.references.model]?.modelName || attr.references.model,
2343
2347
  adapter.options
2344
2348
  )}.${attr.references.field}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
2345
2349
  }).join(",\n ")}
@@ -2792,6 +2796,213 @@ const generate = new Command("generate").option(
2792
2796
  "the path to the configuration file. defaults to the first configuration file found."
2793
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);
2794
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
+ console.log(
2903
+ chalk.blue(
2904
+ "\nFor more information, visit: https://better-auth.com/docs/plugins/device-authorization"
2905
+ )
2906
+ );
2907
+ }
2908
+ } catch (err) {
2909
+ spinner.stop();
2910
+ logger.error(
2911
+ `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`
2912
+ );
2913
+ process.exit(1);
2914
+ }
2915
+ }
2916
+ async function pollForToken(authClient, deviceCode, clientId, initialInterval) {
2917
+ let pollingInterval = initialInterval;
2918
+ const spinner = yoctoSpinner({ text: "", color: "cyan" });
2919
+ let dots = 0;
2920
+ return new Promise((resolve, reject) => {
2921
+ const poll = async () => {
2922
+ dots = (dots + 1) % 4;
2923
+ spinner.text = chalk.gray(
2924
+ `Polling for authorization${".".repeat(dots)}${" ".repeat(3 - dots)}`
2925
+ );
2926
+ if (!spinner.isSpinning) spinner.start();
2927
+ try {
2928
+ const { data, error } = await authClient.device.token({
2929
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
2930
+ device_code: deviceCode,
2931
+ client_id: clientId,
2932
+ fetchOptions: {
2933
+ headers: {
2934
+ "user-agent": `Better Auth CLI`
2935
+ }
2936
+ }
2937
+ });
2938
+ if (data?.access_token) {
2939
+ spinner.stop();
2940
+ resolve(data);
2941
+ return;
2942
+ } else if (error) {
2943
+ switch (error.error) {
2944
+ case "authorization_pending":
2945
+ break;
2946
+ case "slow_down":
2947
+ pollingInterval += 5;
2948
+ spinner.text = chalk.yellow(
2949
+ `Slowing down polling to ${pollingInterval}s`
2950
+ );
2951
+ break;
2952
+ case "access_denied":
2953
+ spinner.stop();
2954
+ logger.error("Access was denied by the user");
2955
+ process.exit(1);
2956
+ break;
2957
+ case "expired_token":
2958
+ spinner.stop();
2959
+ logger.error("The device code has expired. Please try again.");
2960
+ process.exit(1);
2961
+ break;
2962
+ default:
2963
+ spinner.stop();
2964
+ logger.error(`Error: ${error.error_description}`);
2965
+ process.exit(1);
2966
+ }
2967
+ }
2968
+ } catch (err) {
2969
+ spinner.stop();
2970
+ logger.error(
2971
+ `Network error: ${err instanceof Error ? err.message : "Unknown error"}`
2972
+ );
2973
+ process.exit(1);
2974
+ }
2975
+ setTimeout(poll, pollingInterval * 1e3);
2976
+ };
2977
+ setTimeout(poll, pollingInterval * 1e3);
2978
+ });
2979
+ }
2980
+ async function storeToken(token) {
2981
+ try {
2982
+ await fs$1.mkdir(CONFIG_DIR, { recursive: true });
2983
+ const tokenData = {
2984
+ access_token: token.access_token,
2985
+ token_type: token.token_type || "Bearer",
2986
+ scope: token.scope,
2987
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2988
+ };
2989
+ await fs$1.writeFile(TOKEN_FILE, JSON.stringify(tokenData, null, 2), "utf-8");
2990
+ } catch (error) {
2991
+ logger.warn("Failed to store authentication token locally");
2992
+ }
2993
+ }
2994
+ async function getStoredToken() {
2995
+ try {
2996
+ const data = await fs$1.readFile(TOKEN_FILE, "utf-8");
2997
+ return JSON.parse(data);
2998
+ } catch {
2999
+ return null;
3000
+ }
3001
+ }
3002
+ const login = new Command("login").description(
3003
+ "Demo: Test device authorization flow with Better Auth demo server"
3004
+ ).option("--server-url <url>", "The Better Auth server URL", DEMO_URL).option("--client-id <id>", "The OAuth client ID", CLIENT_ID).action(loginAction);
3005
+
2795
3006
  process.on("SIGINT", () => process.exit(0));
2796
3007
  process.on("SIGTERM", () => process.exit(0));
2797
3008
  async function main() {
@@ -2801,7 +3012,7 @@ async function main() {
2801
3012
  packageInfo = await getPackageInfo();
2802
3013
  } catch (error) {
2803
3014
  }
2804
- program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).version(packageInfo.version || "1.1.2").description("Better Auth CLI").action(() => program.help());
3015
+ program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(generateSecret).addCommand(login).version(packageInfo.version || "1.1.2").description("Better Auth CLI").action(() => program.help());
2805
3016
  program.parse();
2806
3017
  }
2807
3018
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/cli",
3
- "version": "1.3.8-beta.2",
3
+ "version": "1.3.8-beta.4",
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",
@@ -47,18 +45,16 @@
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.8-beta.2"
59
- },
60
- "peerDependencies": {
61
- "zod": "3.25.0 || ^4.0.0"
56
+ "zod": "^4.0.0",
57
+ "better-auth": "1.3.8-beta.4"
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
  }