pecunia-cli 0.0.0

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 ADDED
@@ -0,0 +1,770 @@
1
+ #!/usr/bin/env node
2
+ import { i as getPackageInfo, n as generateSchema } from "./generators-xnsg6Fwk.mjs";
3
+ import { Command } from "commander";
4
+ import fs, { existsSync, readFileSync } from "node:fs";
5
+ import fs$1 from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { getAdapter, getMigrations } from "pecunia";
8
+ import chalk from "chalk";
9
+ import prompts from "prompts";
10
+ import yoctoSpinner from "yocto-spinner";
11
+ import * as z from "zod/v4";
12
+ import { PecuniaError } from "@pecunia/core";
13
+ import "prettier";
14
+ import babelPresetReact from "@babel/preset-react";
15
+ import babelPresetTypeScript from "@babel/preset-typescript";
16
+ import { loadConfig } from "c12";
17
+ import { execSync } from "node:child_process";
18
+ import os from "node:os";
19
+ import "@clack/prompts";
20
+ import "dotenv";
21
+ import "semver";
22
+ import "dotenv/config";
23
+
24
+ //#region src/utils/get-tsconfig-info.ts
25
+ function stripJsonComments(jsonString) {
26
+ return jsonString.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(?=\s*[}\]])/g, "");
27
+ }
28
+ function getTsconfigInfo(cwd, flatPath) {
29
+ let tsConfigPath;
30
+ if (flatPath) tsConfigPath = flatPath;
31
+ else tsConfigPath = cwd ? path.join(cwd, "tsconfig.json") : path.join("tsconfig.json");
32
+ try {
33
+ const text = fs.readFileSync(tsConfigPath, "utf-8");
34
+ return JSON.parse(stripJsonComments(text));
35
+ } catch (error) {
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ //#endregion
41
+ //#region src/utils/get-config.ts
42
+ let possiblePaths = [
43
+ "payment.ts",
44
+ "payment.tsx",
45
+ "payment.js",
46
+ "payment.jsx",
47
+ "payment.server.js",
48
+ "payment.server.ts",
49
+ "payment/index.ts",
50
+ "payment/index.tsx",
51
+ "payment/index.js",
52
+ "payment/index.jsx",
53
+ "payment/index.server.js",
54
+ "payment/index.server.ts"
55
+ ];
56
+ possiblePaths = [
57
+ ...possiblePaths,
58
+ ...possiblePaths.map((it) => `lib/server/${it}`),
59
+ ...possiblePaths.map((it) => `server/payment/${it}`),
60
+ ...possiblePaths.map((it) => `server/${it}`),
61
+ ...possiblePaths.map((it) => `payment/${it}`),
62
+ ...possiblePaths.map((it) => `lib/${it}`),
63
+ ...possiblePaths.map((it) => `utils/${it}`)
64
+ ];
65
+ possiblePaths = [
66
+ ...possiblePaths,
67
+ ...possiblePaths.map((it) => `src/${it}`),
68
+ ...possiblePaths.map((it) => `app/${it}`)
69
+ ];
70
+ function resolveReferencePath(configDir, refPath) {
71
+ const resolvedPath = path.resolve(configDir, refPath);
72
+ if (refPath.endsWith(".json")) return resolvedPath;
73
+ if (fs.existsSync(resolvedPath)) try {
74
+ if (fs.statSync(resolvedPath).isFile()) return resolvedPath;
75
+ } catch {}
76
+ return path.resolve(configDir, refPath, "tsconfig.json");
77
+ }
78
+ function getPathAliasesRecursive(tsconfigPath, visited = /* @__PURE__ */ new Set()) {
79
+ if (visited.has(tsconfigPath)) return {};
80
+ visited.add(tsconfigPath);
81
+ if (!fs.existsSync(tsconfigPath)) {
82
+ console.warn(`Referenced tsconfig not found: ${tsconfigPath}`);
83
+ return {};
84
+ }
85
+ try {
86
+ const tsConfig = getTsconfigInfo(void 0, tsconfigPath);
87
+ const { paths = {}, baseUrl = "." } = tsConfig.compilerOptions || {};
88
+ const result = {};
89
+ const configDir = path.dirname(tsconfigPath);
90
+ const obj = Object.entries(paths);
91
+ for (const [alias, aliasPaths] of obj) for (const aliasedPath of aliasPaths) {
92
+ const resolvedBaseUrl = path.resolve(configDir, baseUrl);
93
+ const finalAlias = alias.slice(-1) === "*" ? alias.slice(0, -1) : alias;
94
+ const finalAliasedPath = aliasedPath.slice(-1) === "*" ? aliasedPath.slice(0, -1) : aliasedPath;
95
+ result[finalAlias || ""] = path.join(resolvedBaseUrl, finalAliasedPath);
96
+ }
97
+ if (tsConfig.references) for (const ref of tsConfig.references) {
98
+ const refAliases = getPathAliasesRecursive(resolveReferencePath(configDir, ref.path), visited);
99
+ for (const [alias, aliasPath] of Object.entries(refAliases)) if (!(alias in result)) result[alias] = aliasPath;
100
+ }
101
+ return result;
102
+ } catch (error) {
103
+ console.warn(`Error parsing tsconfig at ${tsconfigPath}: ${error}`);
104
+ return {};
105
+ }
106
+ }
107
+ function getPathAliases(cwd) {
108
+ const tsConfigPath = path.join(cwd, "tsconfig.json");
109
+ if (!fs.existsSync(tsConfigPath)) return null;
110
+ try {
111
+ const result = getPathAliasesRecursive(tsConfigPath);
112
+ addSvelteKitEnvModules(result);
113
+ addCloudflareModules(result);
114
+ return result;
115
+ } catch (error) {
116
+ console.error(error);
117
+ throw new PecuniaError("Error parsing tsconfig.json");
118
+ }
119
+ }
120
+ /**
121
+ * .tsx files are not supported by Jiti.
122
+ */
123
+ const jitiOptions = (cwd) => {
124
+ const alias = getPathAliases(cwd) || {};
125
+ return {
126
+ transformOptions: { babel: { presets: [[babelPresetTypeScript, {
127
+ isTSX: true,
128
+ allExtensions: true
129
+ }], [babelPresetReact, { runtime: "automatic" }]] } },
130
+ extensions: [
131
+ ".ts",
132
+ ".tsx",
133
+ ".js",
134
+ ".jsx"
135
+ ],
136
+ alias
137
+ };
138
+ };
139
+ const isDefaultExport = (object) => {
140
+ return typeof object === "object" && object !== null && !Array.isArray(object) && Object.keys(object).length > 0 && "options" in object;
141
+ };
142
+ async function getConfig({ cwd, configPath, shouldThrowOnError = false }) {
143
+ try {
144
+ let configFile = null;
145
+ if (configPath) {
146
+ let resolvedPath = path.join(cwd, configPath);
147
+ if (existsSync(configPath)) resolvedPath = configPath;
148
+ const { config } = await loadConfig({
149
+ configFile: resolvedPath,
150
+ dotenv: true,
151
+ jitiOptions: jitiOptions(cwd),
152
+ cwd
153
+ });
154
+ if (!("auth" in config) && !isDefaultExport(config)) {
155
+ if (shouldThrowOnError) throw new Error(`Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
156
+ console.error(`[#better-auth]: Couldn't read your auth config in ${resolvedPath}. Make sure to default export your auth instance or to export as a variable named auth.`);
157
+ process.exit(1);
158
+ }
159
+ configFile = "auth" in config ? config.auth?.options : config.options;
160
+ }
161
+ if (!configFile) for (const possiblePath of possiblePaths) try {
162
+ const { config } = await loadConfig({
163
+ configFile: possiblePath,
164
+ jitiOptions: jitiOptions(cwd),
165
+ cwd
166
+ });
167
+ if (Object.keys(config).length > 0) {
168
+ configFile = config.auth?.options || config.default?.options || null;
169
+ if (!configFile) {
170
+ if (shouldThrowOnError) throw new Error("Couldn't read your auth config. Make sure to default export your auth instance or to export as a variable named auth.");
171
+ console.error("[#better-auth]: Couldn't read your auth config.");
172
+ console.log("");
173
+ console.log("[#better-auth]: Make sure to default export your auth instance or to export as a variable named auth.");
174
+ process.exit(1);
175
+ }
176
+ break;
177
+ }
178
+ } catch (e) {
179
+ if (typeof e === "object" && e && "message" in e && typeof e.message === "string" && e.message.includes("This module cannot be imported from a Client Component module")) {
180
+ if (shouldThrowOnError) throw new Error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
181
+ console.error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
182
+ process.exit(1);
183
+ }
184
+ if (shouldThrowOnError) throw e;
185
+ console.error("[#better-auth]: Couldn't read your auth config.", e);
186
+ process.exit(1);
187
+ }
188
+ return configFile;
189
+ } catch (e) {
190
+ if (typeof e === "object" && e && "message" in e && typeof e.message === "string" && e.message.includes("This module cannot be imported from a Client Component module")) {
191
+ if (shouldThrowOnError) throw new Error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
192
+ console.error(`Please remove import 'server-only' from your auth config file temporarily. The CLI cannot resolve the configuration with it included. You can re-add it after running the CLI.`);
193
+ process.exit(1);
194
+ }
195
+ if (shouldThrowOnError) throw e;
196
+ console.error("Couldn't read your auth config.", e);
197
+ process.exit(1);
198
+ }
199
+ }
200
+
201
+ //#endregion
202
+ //#region src/commands/generate.ts
203
+ async function generateAction(opts) {
204
+ const options = z.object({
205
+ cwd: z.string(),
206
+ config: z.string().optional(),
207
+ output: z.string().optional(),
208
+ y: z.boolean().optional(),
209
+ yes: z.boolean().optional()
210
+ }).parse(opts);
211
+ const cwd = path.resolve(options.cwd);
212
+ if (!existsSync(cwd)) {
213
+ console.error(`The directory "${cwd}" does not exist.`);
214
+ process.exit(1);
215
+ }
216
+ const config = await getConfig({
217
+ cwd,
218
+ configPath: options.config
219
+ });
220
+ if (!config) {
221
+ console.error("No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");
222
+ return;
223
+ }
224
+ const adapter = await getAdapter(config).catch((e) => {
225
+ console.error(e.message);
226
+ process.exit(1);
227
+ });
228
+ const spinner = yoctoSpinner({ text: "preparing schema..." }).start();
229
+ const schema = await generateSchema({
230
+ adapter,
231
+ file: options.output,
232
+ options: config
233
+ });
234
+ spinner.stop();
235
+ if (!schema.code) {
236
+ console.log("Your schema is already up to date.");
237
+ try {
238
+ await (await createTelemetry(config)).publish({
239
+ type: "cli_generate",
240
+ payload: {
241
+ outcome: "no_changes",
242
+ config: getTelemetryAuthConfig(config, {
243
+ adapter: adapter.id,
244
+ database: typeof config.database === "function" ? "adapter" : "kysely"
245
+ })
246
+ }
247
+ });
248
+ } catch {}
249
+ process.exit(0);
250
+ }
251
+ if (schema.overwrite) {
252
+ let confirm$1 = options.y || options.yes;
253
+ if (!confirm$1) confirm$1 = (await prompts({
254
+ type: "confirm",
255
+ name: "confirm",
256
+ message: `The file ${schema.fileName} already exists. Do you want to ${chalk.yellow(`${schema.overwrite ? "overwrite" : "append"}`)} the schema to the file?`
257
+ })).confirm;
258
+ if (confirm$1) {
259
+ if (!existsSync(path.join(cwd, schema.fileName))) await fs$1.mkdir(path.dirname(path.join(cwd, schema.fileName)), { recursive: true });
260
+ if (schema.overwrite) await fs$1.writeFile(path.join(cwd, schema.fileName), schema.code);
261
+ else await fs$1.appendFile(path.join(cwd, schema.fileName), schema.code);
262
+ console.log(`šŸš€ Schema was ${schema.overwrite ? "overwritten" : "appended"} successfully!`);
263
+ try {
264
+ await (await createTelemetry(config)).publish({
265
+ type: "cli_generate",
266
+ payload: {
267
+ outcome: schema.overwrite ? "overwritten" : "appended",
268
+ config: getTelemetryAuthConfig(config)
269
+ }
270
+ });
271
+ } catch {}
272
+ process.exit(0);
273
+ } else {
274
+ console.error("Schema generation aborted.");
275
+ try {
276
+ await (await createTelemetry(config)).publish({
277
+ type: "cli_generate",
278
+ payload: {
279
+ outcome: "aborted",
280
+ config: getTelemetryAuthConfig(config)
281
+ }
282
+ });
283
+ } catch {}
284
+ process.exit(1);
285
+ }
286
+ }
287
+ if (options.y) {
288
+ console.warn("WARNING: --y is deprecated. Consider -y or --yes");
289
+ options.yes = true;
290
+ }
291
+ let confirm = options.yes;
292
+ if (!confirm) confirm = (await prompts({
293
+ type: "confirm",
294
+ name: "confirm",
295
+ message: `Do you want to generate the schema to ${chalk.yellow(schema.fileName)}?`
296
+ })).confirm;
297
+ if (!confirm) {
298
+ console.error("Schema generation aborted.");
299
+ try {
300
+ await (await createTelemetry(config)).publish({
301
+ type: "cli_generate",
302
+ payload: {
303
+ outcome: "aborted",
304
+ config: getTelemetryAuthConfig(config)
305
+ }
306
+ });
307
+ } catch {}
308
+ process.exit(1);
309
+ }
310
+ if (!options.output) {
311
+ if (!existsSync(path.dirname(path.join(cwd, schema.fileName)))) await fs$1.mkdir(path.dirname(path.join(cwd, schema.fileName)), { recursive: true });
312
+ }
313
+ await fs$1.writeFile(options.output || path.join(cwd, schema.fileName), schema.code);
314
+ console.log(`šŸš€ Schema was generated successfully!`);
315
+ try {
316
+ await (await createTelemetry(config)).publish({
317
+ type: "cli_generate",
318
+ payload: {
319
+ outcome: "generated",
320
+ config: getTelemetryAuthConfig(config)
321
+ }
322
+ });
323
+ } catch {}
324
+ process.exit(0);
325
+ }
326
+ 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("-y, --yes", "automatically answer yes to all prompts", false).option("--y", "(deprecated) same as --yes", false).action(generateAction);
327
+
328
+ //#endregion
329
+ //#region src/commands/info.ts
330
+ function getSystemInfo() {
331
+ const platform = os.platform();
332
+ const arch = os.arch();
333
+ const version = os.version();
334
+ const release = os.release();
335
+ const cpus = os.cpus();
336
+ const memory = os.totalmem();
337
+ const freeMemory = os.freemem();
338
+ return {
339
+ platform,
340
+ arch,
341
+ version,
342
+ release,
343
+ cpuCount: cpus.length,
344
+ cpuModel: cpus[0]?.model || "Unknown",
345
+ totalMemory: `${(memory / 1024 / 1024 / 1024).toFixed(2)} GB`,
346
+ freeMemory: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`
347
+ };
348
+ }
349
+ function getNodeInfo() {
350
+ return {
351
+ version: process.version,
352
+ env: process.env.NODE_ENV || "development"
353
+ };
354
+ }
355
+ function getPackageManager() {
356
+ const userAgent = process.env.npm_config_user_agent || "";
357
+ if (userAgent.includes("yarn")) return {
358
+ name: "yarn",
359
+ version: getVersion("yarn")
360
+ };
361
+ if (userAgent.includes("pnpm")) return {
362
+ name: "pnpm",
363
+ version: getVersion("pnpm")
364
+ };
365
+ if (userAgent.includes("bun")) return {
366
+ name: "bun",
367
+ version: getVersion("bun")
368
+ };
369
+ return {
370
+ name: "npm",
371
+ version: getVersion("npm")
372
+ };
373
+ }
374
+ function getVersion(command) {
375
+ try {
376
+ return execSync(`${command} --version`, { encoding: "utf8" }).trim();
377
+ } catch {
378
+ return "Not installed";
379
+ }
380
+ }
381
+ function getFrameworkInfo(projectRoot) {
382
+ const packageJsonPath = path.join(projectRoot, "package.json");
383
+ if (!existsSync(packageJsonPath)) return null;
384
+ try {
385
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
386
+ const deps = {
387
+ ...packageJson.dependencies,
388
+ ...packageJson.devDependencies
389
+ };
390
+ const frameworks = {
391
+ next: deps["next"],
392
+ react: deps["react"],
393
+ vue: deps["vue"],
394
+ nuxt: deps["nuxt"],
395
+ svelte: deps["svelte"],
396
+ "@sveltejs/kit": deps["@sveltejs/kit"],
397
+ express: deps["express"],
398
+ fastify: deps["fastify"],
399
+ hono: deps["hono"],
400
+ remix: deps["@remix-run/react"],
401
+ astro: deps["astro"],
402
+ solid: deps["solid-js"],
403
+ qwik: deps["@builder.io/qwik"]
404
+ };
405
+ const installedFrameworks = Object.entries(frameworks).filter(([_, version]) => version).map(([name, version]) => ({
406
+ name,
407
+ version
408
+ }));
409
+ return installedFrameworks.length > 0 ? installedFrameworks : null;
410
+ } catch {
411
+ return null;
412
+ }
413
+ }
414
+ function getDatabaseInfo(projectRoot) {
415
+ const packageJsonPath = path.join(projectRoot, "package.json");
416
+ if (!existsSync(packageJsonPath)) return null;
417
+ try {
418
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
419
+ const deps = {
420
+ ...packageJson.dependencies,
421
+ ...packageJson.devDependencies
422
+ };
423
+ const databases = {
424
+ "better-sqlite3": deps["better-sqlite3"],
425
+ "@libsql/client": deps["@libsql/client"],
426
+ "@libsql/kysely-libsql": deps["@libsql/kysely-libsql"],
427
+ mysql2: deps["mysql2"],
428
+ pg: deps["pg"],
429
+ postgres: deps["postgres"],
430
+ "@prisma/client": deps["@prisma/client"],
431
+ drizzle: deps["drizzle-orm"],
432
+ kysely: deps["kysely"],
433
+ mongodb: deps["mongodb"],
434
+ "@neondatabase/serverless": deps["@neondatabase/serverless"],
435
+ "@vercel/postgres": deps["@vercel/postgres"],
436
+ "@planetscale/database": deps["@planetscale/database"]
437
+ };
438
+ const installedDatabases = Object.entries(databases).filter(([_, version]) => version).map(([name, version]) => ({
439
+ name,
440
+ version
441
+ }));
442
+ return installedDatabases.length > 0 ? installedDatabases : null;
443
+ } catch {
444
+ return null;
445
+ }
446
+ }
447
+ function sanitizeBetterAuthConfig(config) {
448
+ if (!config) return null;
449
+ const sanitized = JSON.parse(JSON.stringify(config));
450
+ const sensitiveKeys = [
451
+ "secret",
452
+ "clientSecret",
453
+ "clientId",
454
+ "authToken",
455
+ "apiKey",
456
+ "apiSecret",
457
+ "privateKey",
458
+ "publicKey",
459
+ "password",
460
+ "token",
461
+ "webhook",
462
+ "connectionString",
463
+ "databaseUrl",
464
+ "databaseURL",
465
+ "TURSO_AUTH_TOKEN",
466
+ "TURSO_DATABASE_URL",
467
+ "MYSQL_DATABASE_URL",
468
+ "DATABASE_URL",
469
+ "POSTGRES_URL",
470
+ "MONGODB_URI",
471
+ "stripeKey",
472
+ "stripeWebhookSecret"
473
+ ];
474
+ const allowedKeys = [
475
+ "baseURL",
476
+ "callbackURL",
477
+ "redirectURL",
478
+ "trustedOrigins",
479
+ "appName"
480
+ ];
481
+ function redactSensitive(obj, parentKey) {
482
+ if (typeof obj !== "object" || obj === null) {
483
+ if (parentKey && typeof obj === "string" && obj.length > 0) {
484
+ if (allowedKeys.some((allowed) => parentKey.toLowerCase() === allowed.toLowerCase())) return obj;
485
+ const lowerKey = parentKey.toLowerCase();
486
+ if (sensitiveKeys.some((key) => {
487
+ const lowerSensitiveKey = key.toLowerCase();
488
+ return lowerKey === lowerSensitiveKey || lowerKey.endsWith(lowerSensitiveKey);
489
+ })) return "[REDACTED]";
490
+ }
491
+ return obj;
492
+ }
493
+ if (Array.isArray(obj)) return obj.map((item) => redactSensitive(item, parentKey));
494
+ const result = {};
495
+ for (const [key, value] of Object.entries(obj)) {
496
+ if (allowedKeys.some((allowed) => key.toLowerCase() === allowed.toLowerCase())) {
497
+ result[key] = value;
498
+ continue;
499
+ }
500
+ const lowerKey = key.toLowerCase();
501
+ if (sensitiveKeys.some((sensitiveKey) => {
502
+ const lowerSensitiveKey = sensitiveKey.toLowerCase();
503
+ return lowerKey === lowerSensitiveKey || lowerKey.endsWith(lowerSensitiveKey);
504
+ })) if (typeof value === "string" && value.length > 0) result[key] = "[REDACTED]";
505
+ else if (typeof value === "object" && value !== null) result[key] = redactSensitive(value, key);
506
+ else result[key] = value;
507
+ else result[key] = redactSensitive(value, key);
508
+ }
509
+ return result;
510
+ }
511
+ if (sanitized.database) {
512
+ if (typeof sanitized.database === "string") sanitized.database = "[REDACTED]";
513
+ else if (sanitized.database.url) sanitized.database.url = "[REDACTED]";
514
+ if (sanitized.database.authToken) sanitized.database.authToken = "[REDACTED]";
515
+ }
516
+ if (sanitized.socialProviders) {
517
+ for (const provider in sanitized.socialProviders) if (sanitized.socialProviders[provider]) sanitized.socialProviders[provider] = redactSensitive(sanitized.socialProviders[provider], provider);
518
+ }
519
+ if (sanitized.emailAndPassword?.sendResetPassword) sanitized.emailAndPassword.sendResetPassword = "[Function]";
520
+ if (sanitized.emailVerification?.sendVerificationEmail) sanitized.emailVerification.sendVerificationEmail = "[Function]";
521
+ if (sanitized.plugins && Array.isArray(sanitized.plugins)) sanitized.plugins = sanitized.plugins.map((plugin) => {
522
+ if (typeof plugin === "function") return "[Plugin Function]";
523
+ if (plugin && typeof plugin === "object") return {
524
+ name: plugin.id || plugin.name || "unknown",
525
+ config: redactSensitive(plugin.config || plugin)
526
+ };
527
+ return plugin;
528
+ });
529
+ return redactSensitive(sanitized);
530
+ }
531
+ async function getBetterAuthInfo(projectRoot, configPath, suppressLogs = false) {
532
+ try {
533
+ const originalLog = console.log;
534
+ const originalWarn = console.warn;
535
+ const originalError = console.error;
536
+ if (suppressLogs) {
537
+ console.log = () => {};
538
+ console.warn = () => {};
539
+ console.error = () => {};
540
+ }
541
+ try {
542
+ const config = await getConfig({
543
+ cwd: projectRoot,
544
+ configPath,
545
+ shouldThrowOnError: true
546
+ });
547
+ const packageInfo = await getPackageInfo();
548
+ return {
549
+ version: packageInfo.dependencies?.["better-auth"] || packageInfo.devDependencies?.["better-auth"] || packageInfo.peerDependencies?.["better-auth"] || packageInfo.optionalDependencies?.["better-auth"] || "Unknown",
550
+ config: sanitizeBetterAuthConfig(config)
551
+ };
552
+ } finally {
553
+ if (suppressLogs) {
554
+ console.log = originalLog;
555
+ console.warn = originalWarn;
556
+ console.error = originalError;
557
+ }
558
+ }
559
+ } catch (error) {
560
+ return {
561
+ version: "Unknown",
562
+ config: null,
563
+ error: error instanceof Error ? error.message : "Failed to load Better Auth config"
564
+ };
565
+ }
566
+ }
567
+ function formatOutput(data, indent = 0) {
568
+ const spaces = " ".repeat(indent);
569
+ if (data === null || data === void 0) return `${spaces}${chalk.gray("N/A")}`;
570
+ if (typeof data === "string" || typeof data === "number" || typeof data === "boolean") return `${spaces}${data}`;
571
+ if (Array.isArray(data)) {
572
+ if (data.length === 0) return `${spaces}${chalk.gray("[]")}`;
573
+ return data.map((item) => formatOutput(item, indent)).join("\n");
574
+ }
575
+ if (typeof data === "object") {
576
+ const entries = Object.entries(data);
577
+ if (entries.length === 0) return `${spaces}${chalk.gray("{}")}`;
578
+ return entries.map(([key, value]) => {
579
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return `${spaces}${chalk.cyan(key)}:\n${formatOutput(value, indent + 2)}`;
580
+ return `${spaces}${chalk.cyan(key)}: ${formatOutput(value, 0)}`;
581
+ }).join("\n");
582
+ }
583
+ return `${spaces}${JSON.stringify(data)}`;
584
+ }
585
+ 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) => {
586
+ const projectRoot = path.resolve(options.cwd || process.cwd());
587
+ const systemInfo = getSystemInfo();
588
+ const nodeInfo = getNodeInfo();
589
+ const packageManager = getPackageManager();
590
+ const frameworks = getFrameworkInfo(projectRoot);
591
+ const databases = getDatabaseInfo(projectRoot);
592
+ const betterAuthInfo = await getBetterAuthInfo(projectRoot, options.config, options.json);
593
+ const fullInfo = {
594
+ system: systemInfo,
595
+ node: nodeInfo,
596
+ packageManager,
597
+ frameworks,
598
+ databases,
599
+ betterAuth: betterAuthInfo
600
+ };
601
+ if (options.json) {
602
+ const jsonOutput = JSON.stringify(fullInfo, null, 2);
603
+ console.log(jsonOutput);
604
+ if (options.copy) try {
605
+ const platform = os.platform();
606
+ if (platform === "darwin") {
607
+ execSync("pbcopy", { input: jsonOutput });
608
+ console.log(chalk.green("\nāœ“ Copied to clipboard"));
609
+ } else if (platform === "linux") {
610
+ execSync("xclip -selection clipboard", { input: jsonOutput });
611
+ console.log(chalk.green("\nāœ“ Copied to clipboard"));
612
+ } else if (platform === "win32") {
613
+ execSync("clip", { input: jsonOutput });
614
+ console.log(chalk.green("\nāœ“ Copied to clipboard"));
615
+ }
616
+ } catch {
617
+ console.log(chalk.yellow("\n⚠ Could not copy to clipboard"));
618
+ }
619
+ return;
620
+ }
621
+ console.log(chalk.bold("\nšŸ“Š Better Auth System Information\n"));
622
+ console.log(chalk.gray("=".repeat(50)));
623
+ console.log(chalk.bold.white("\nšŸ–„ļø System Information:"));
624
+ console.log(formatOutput(systemInfo, 2));
625
+ console.log(chalk.bold.white("\nšŸ“¦ Node.js:"));
626
+ console.log(formatOutput(nodeInfo, 2));
627
+ console.log(chalk.bold.white("\nšŸ“¦ Package Manager:"));
628
+ console.log(formatOutput(packageManager, 2));
629
+ if (frameworks) {
630
+ console.log(chalk.bold.white("\nšŸš€ Frameworks:"));
631
+ console.log(formatOutput(frameworks, 2));
632
+ }
633
+ if (databases) {
634
+ console.log(chalk.bold.white("\nšŸ’¾ Database Clients:"));
635
+ console.log(formatOutput(databases, 2));
636
+ }
637
+ console.log(chalk.bold.white("\nšŸ” Better Auth:"));
638
+ if (betterAuthInfo.error) console.log(` ${chalk.red("Error:")} ${betterAuthInfo.error}`);
639
+ else {
640
+ console.log(` ${chalk.cyan("Version")}: ${betterAuthInfo.version}`);
641
+ if (betterAuthInfo.config) {
642
+ console.log(` ${chalk.cyan("Configuration")}:`);
643
+ console.log(formatOutput(betterAuthInfo.config, 4));
644
+ }
645
+ }
646
+ console.log(chalk.gray("\n" + "=".repeat(50)));
647
+ console.log(chalk.gray("\nšŸ’” Tip: Use --json flag for JSON output"));
648
+ console.log(chalk.gray("šŸ’” Use --copy flag to copy output to clipboard"));
649
+ console.log(chalk.gray("šŸ’” When reporting issues, include this information\n"));
650
+ if (options.copy) {
651
+ const textOutput = `
652
+ Better Auth System Information
653
+ ==============================
654
+
655
+ System Information:
656
+ ${JSON.stringify(systemInfo, null, 2)}
657
+
658
+ Node.js:
659
+ ${JSON.stringify(nodeInfo, null, 2)}
660
+
661
+ Package Manager:
662
+ ${JSON.stringify(packageManager, null, 2)}
663
+
664
+ Frameworks:
665
+ ${JSON.stringify(frameworks, null, 2)}
666
+
667
+ Database Clients:
668
+ ${JSON.stringify(databases, null, 2)}
669
+
670
+ Better Auth:
671
+ ${JSON.stringify(betterAuthInfo, null, 2)}
672
+ `;
673
+ try {
674
+ const platform = os.platform();
675
+ if (platform === "darwin") {
676
+ execSync("pbcopy", { input: textOutput });
677
+ console.log(chalk.green("āœ“ Copied to clipboard"));
678
+ } else if (platform === "linux") {
679
+ execSync("xclip -selection clipboard", { input: textOutput });
680
+ console.log(chalk.green("āœ“ Copied to clipboard"));
681
+ } else if (platform === "win32") {
682
+ execSync("clip", { input: textOutput });
683
+ console.log(chalk.green("āœ“ Copied to clipboard"));
684
+ }
685
+ } catch {
686
+ console.log(chalk.yellow("⚠ Could not copy to clipboard"));
687
+ }
688
+ }
689
+ });
690
+
691
+ //#endregion
692
+ //#region src/commands/init.ts
693
+ const init = new Command("init").option("-c, --cwd <cwd>", "The working directory.", process.cwd()).option("--config <config>", "The path to the auth configuration file. defaults to the first `auth.ts` file found.").option("--tsconfig <tsconfig>", "The path to the tsconfig file.").option("--skip-db", "Skip the database setup.").option("--skip-plugins", "Skip the plugins setup.").option("--package-manager <package-manager>", "The package manager you want to use.").action();
694
+
695
+ //#endregion
696
+ //#region src/commands/migrate.ts
697
+ /** @internal */
698
+ async function migrateAction(opts) {
699
+ const options = z.object({
700
+ cwd: z.string(),
701
+ config: z.string().optional(),
702
+ y: z.boolean().optional(),
703
+ yes: z.boolean().optional()
704
+ }).parse(opts);
705
+ const cwd = path.resolve(options.cwd);
706
+ if (!existsSync(cwd)) {
707
+ console.error(`The directory "${cwd}" does not exist.`);
708
+ process.exit(1);
709
+ }
710
+ const config = await getConfig({
711
+ cwd,
712
+ configPath: options.config
713
+ });
714
+ if (!config) {
715
+ console.error("No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.");
716
+ return;
717
+ }
718
+ const db = await getAdapter(config);
719
+ if (!db) {
720
+ console.error("Invalid database configuration. Make sure you're not using adapters. Migrate command only works with built-in Kysely adapter.");
721
+ process.exit(1);
722
+ }
723
+ if (db.id !== "kysely") {
724
+ if (db.id === "prisma") console.error("The migrate command only works with the built-in Kysely adapter. For Prisma, run `npx @better-auth/cli generate` to create the schema, then use Prisma's migrate or push to apply it.");
725
+ if (db.id === "drizzle") console.error("The migrate command only works with the built-in Kysely adapter. For Drizzle, run `npx @better-auth/cli generate` to create the schema, then use Drizzle's migrate or push to apply it.");
726
+ console.error("Migrate command isn't supported for this adapter.");
727
+ }
728
+ const spinner = yoctoSpinner({ text: "preparing migration..." }).start();
729
+ const { toBeAdded, toBeCreated, runMigrations } = await getMigrations(config);
730
+ spinner.stop();
731
+ console.log(`šŸ”‘ The migration will affect the following:`);
732
+ for (const table of [...toBeCreated, ...toBeAdded]) console.log("->", chalk.magenta(Object.keys(table.fields).join(", ")), chalk.white("fields on"), chalk.yellow(`${table.table}`), chalk.white("table."));
733
+ if (options.y) {
734
+ console.warn("WARNING: --y is deprecated. Consider -y or --yes");
735
+ options.yes = true;
736
+ }
737
+ let migrate$1 = options.yes;
738
+ if (!migrate$1) migrate$1 = (await prompts({
739
+ type: "confirm",
740
+ name: "migrate",
741
+ message: "Are you sure you want to run these migrations?",
742
+ initial: false
743
+ })).migrate;
744
+ spinner?.start("migrating...");
745
+ await runMigrations();
746
+ spinner.stop();
747
+ console.log("šŸš€ migration was completed successfully!");
748
+ }
749
+ const migrate = new Command("migrate").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("-y, --yes", "automatically accept and run migrations without prompting", false).option("--y", "(deprecated) same as --yes", false).action(migrateAction);
750
+
751
+ //#endregion
752
+ //#region src/index.ts
753
+ process.on("SIGINT", () => process.exit(0));
754
+ process.on("SIGTERM", () => process.exit(0));
755
+ async function main() {
756
+ const program = new Command("better-auth");
757
+ let packageInfo = {};
758
+ try {
759
+ packageInfo = await getPackageInfo();
760
+ } catch {}
761
+ program.addCommand(init).addCommand(migrate).addCommand(generate).addCommand(info).version(packageInfo.version || "0.0.0").description("Pecunia CLI").action(() => program.help());
762
+ program.parse();
763
+ }
764
+ main().catch((error) => {
765
+ console.error("Error running Pecunia CLI:", error);
766
+ process.exit(1);
767
+ });
768
+
769
+ //#endregion
770
+ export { };