@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.
- package/dist/index.mjs +231 -20
- 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
|
|
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
|
-
|
|
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
|
|
1692
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1694
1693
|
try {
|
|
1695
|
-
const packageJson = JSON.parse(fs
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1735
|
+
if (fs.existsSync(configPath)) {
|
|
1737
1736
|
try {
|
|
1738
|
-
const content = fs
|
|
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
|
|
1873
|
+
if (fs.existsSync(resolvedPath)) {
|
|
1875
1874
|
try {
|
|
1876
|
-
const stats = fs
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
}
|