better-auth-studio 1.0.58-beta.3 → 1.0.58

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.
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAyLhG;AAwBD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAgmKR"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAOA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAyLhG;AAwBD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAk8KR"}
package/dist/routes.js CHANGED
@@ -4331,6 +4331,317 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4331
4331
  res.status(500).json({ success: false, error: 'Failed to generate token' });
4332
4332
  }
4333
4333
  });
4334
+ router.post('/api/tools/plugin-generator', async (req, res) => {
4335
+ try {
4336
+ const { pluginName, description, clientFramework = 'react', tables = [], hooks = [], middleware = [], endpoints = [], rateLimit, } = req.body || {};
4337
+ if (!pluginName || typeof pluginName !== 'string' || pluginName.trim().length === 0) {
4338
+ return res.status(400).json({ success: false, error: 'Plugin name is required' });
4339
+ }
4340
+ const validNameRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
4341
+ if (!validNameRegex.test(pluginName.trim())) {
4342
+ return res.status(400).json({
4343
+ success: false,
4344
+ error: 'Plugin name must be a valid JavaScript identifier (letters, numbers, _, $)',
4345
+ });
4346
+ }
4347
+ const sanitizedName = pluginName.trim();
4348
+ const camelCaseName = sanitizedName.charAt(0).toLowerCase() + sanitizedName.slice(1);
4349
+ const pascalCaseName = sanitizedName.charAt(0).toUpperCase() + sanitizedName.slice(1);
4350
+ // Generate schema
4351
+ const schemaCode = tables.length > 0
4352
+ ? tables
4353
+ .map((table) => {
4354
+ const fields = table.fields
4355
+ ?.filter((f) => f.name.trim())
4356
+ .map((field) => {
4357
+ let attrStr = `type: "${field.type}"`;
4358
+ if (field.required)
4359
+ attrStr += ',\n required: true';
4360
+ if (field.unique)
4361
+ attrStr += ',\n unique: true';
4362
+ return ` ${field.name}: {\n ${attrStr}\n }`;
4363
+ })
4364
+ .join(',\n') || '';
4365
+ return ` ${table.name}: {
4366
+ fields: {
4367
+ ${fields}
4368
+ },
4369
+ }`;
4370
+ })
4371
+ .join(',\n')
4372
+ : '';
4373
+ // Generate hooks
4374
+ const beforeHooks = hooks
4375
+ .filter((h) => h.timing === 'before')
4376
+ .map((hook) => {
4377
+ let matcher = '';
4378
+ if (hook.action === 'sign-up') {
4379
+ matcher = `(ctx) => ctx.path.startsWith("/sign-up")`;
4380
+ }
4381
+ else if (hook.action === 'sign-in') {
4382
+ matcher = `(ctx) => ctx.path.startsWith("/sign-in")`;
4383
+ }
4384
+ else if (hook.action === 'custom' && hook.customPath) {
4385
+ matcher = `(ctx) => ctx.path === "${hook.customPath}"`;
4386
+ if (hook.customMatcher) {
4387
+ matcher = `(ctx) => ctx.path === "${hook.customPath}" && (${hook.customMatcher})`;
4388
+ }
4389
+ }
4390
+ else {
4391
+ matcher = `(ctx) => true`;
4392
+ }
4393
+ return ` {
4394
+ matcher: ${matcher},
4395
+ handler: createAuthMiddleware(async (ctx) => {
4396
+ // ${hook.name || `${hook.timing} ${hook.action} hook`}
4397
+ ${hook.hookLogic || '// Hook logic here'}
4398
+ }),
4399
+ }`;
4400
+ });
4401
+ const afterHooks = hooks
4402
+ .filter((h) => h.timing === 'after')
4403
+ .map((hook) => {
4404
+ let matcher = '';
4405
+ if (hook.action === 'sign-up') {
4406
+ matcher = `(ctx) => ctx.path.startsWith("/sign-up")`;
4407
+ }
4408
+ else if (hook.action === 'sign-in') {
4409
+ matcher = `(ctx) => ctx.path.startsWith("/sign-in")`;
4410
+ }
4411
+ else if (hook.action === 'custom' && hook.customPath) {
4412
+ matcher = `(ctx) => ctx.path === "${hook.customPath}"`;
4413
+ if (hook.customMatcher) {
4414
+ matcher = `(ctx) => ctx.path === "${hook.customPath}" && (${hook.customMatcher})`;
4415
+ }
4416
+ }
4417
+ else {
4418
+ matcher = `(ctx) => true`;
4419
+ }
4420
+ return ` {
4421
+ matcher: ${matcher},
4422
+ handler: createAuthMiddleware(async (ctx) => {
4423
+ // ${hook.name || `${hook.timing} ${hook.action} hook`}
4424
+ ${hook.hookLogic || '// Hook logic here'}
4425
+ }),
4426
+ }`;
4427
+ });
4428
+ // Generate middleware
4429
+ const middlewareCode = middleware
4430
+ .map((mw) => {
4431
+ return ` {
4432
+ path: "${mw.path}",
4433
+ middleware: createAuthMiddleware(async (ctx) => {
4434
+ // ${mw.name || 'Middleware'}
4435
+ ${mw.middlewareLogic || '// Middleware logic here'}
4436
+ }),
4437
+ }`;
4438
+ })
4439
+ .join(',\n');
4440
+ // Generate endpoints
4441
+ const endpointsCode = endpoints.length > 0
4442
+ ? endpoints
4443
+ .map((endpoint) => {
4444
+ const endpointName = endpoint.name?.trim() || `endpoint${endpoints.indexOf(endpoint) + 1}`;
4445
+ const sanitizedName = endpointName.replace(/[^a-zA-Z0-9]/g, '');
4446
+ const endpointPath = endpoint.path?.trim() || `/${camelCaseName}/${sanitizedName}`;
4447
+ const handlerLogic = endpoint.handlerLogic ||
4448
+ '// Endpoint handler logic here\n return ctx.json({ success: true });';
4449
+ const formattedHandlerLogic = handlerLogic
4450
+ .split('\n')
4451
+ .map((line) => {
4452
+ const trimmed = line.trim();
4453
+ if (!trimmed)
4454
+ return '';
4455
+ if (!line.startsWith(' ')) {
4456
+ return ' ' + trimmed;
4457
+ }
4458
+ return line;
4459
+ })
4460
+ .filter(Boolean)
4461
+ .join('\n');
4462
+ return ` ${sanitizedName}: createAuthEndpoint(
4463
+ "${endpointPath}",
4464
+ {
4465
+ method: "${endpoint.method || 'POST'}" as const,
4466
+ },
4467
+ async (ctx) => {
4468
+ // ${endpoint.name || sanitizedName}
4469
+ ${formattedHandlerLogic}
4470
+ },
4471
+ ),`;
4472
+ })
4473
+ .join('\n')
4474
+ : '';
4475
+ const rateLimitCode = rateLimit
4476
+ ? (() => {
4477
+ const rl = rateLimit;
4478
+ let pathMatcher = '';
4479
+ if (rl.pathType === 'exact') {
4480
+ pathMatcher = `(path: string) => path === "${rl.path}"`;
4481
+ }
4482
+ else if (rl.pathType === 'prefix') {
4483
+ pathMatcher = `(path: string) => path.startsWith("${rl.path}")`;
4484
+ }
4485
+ else if (rl.pathType === 'regex') {
4486
+ pathMatcher = `(path: string) => new RegExp("${rl.path.replace(/"/g, '\\"')}").test(path)`;
4487
+ }
4488
+ else {
4489
+ pathMatcher = `(path: string) => true`;
4490
+ }
4491
+ const windowValue = rl.window && rl.window > 0 ? rl.window : 15 * 60 * 1000;
4492
+ const maxValue = rl.max && rl.max > 0 ? rl.max : 100;
4493
+ return ` window: ${windowValue},
4494
+ max: ${maxValue},
4495
+ pathMatcher: ${pathMatcher}`;
4496
+ })()
4497
+ : '';
4498
+ const cleanCode = (code) => {
4499
+ return code
4500
+ .split('\n')
4501
+ .map((line) => line.trimEnd())
4502
+ .filter((line, index, arr) => {
4503
+ if (line === '' && arr[index + 1] === '')
4504
+ return false;
4505
+ return true;
4506
+ })
4507
+ .join('\n')
4508
+ .replace(/\n{3,}/g, '\n\n')
4509
+ .trim();
4510
+ };
4511
+ const pluginParts = [];
4512
+ if (schemaCode) {
4513
+ pluginParts.push(` schema: {\n${schemaCode}\n }`);
4514
+ }
4515
+ if (beforeHooks.length > 0 || afterHooks.length > 0) {
4516
+ const hooksParts = [];
4517
+ if (beforeHooks.length > 0) {
4518
+ hooksParts.push(` before: [\n${beforeHooks.join(',\n')}\n ]`);
4519
+ }
4520
+ if (afterHooks.length > 0) {
4521
+ hooksParts.push(` after: [\n${afterHooks.join(',\n')}\n ]`);
4522
+ }
4523
+ pluginParts.push(` hooks: {\n${hooksParts.join(',\n')}\n }`);
4524
+ }
4525
+ if (middlewareCode) {
4526
+ pluginParts.push(` middlewares: [\n${middlewareCode}\n ]`);
4527
+ }
4528
+ if (endpointsCode) {
4529
+ pluginParts.push(` endpoints: {\n${endpointsCode}\n }`);
4530
+ }
4531
+ if (rateLimitCode) {
4532
+ pluginParts.push(` rateLimit: {\n${rateLimitCode}\n }`);
4533
+ }
4534
+ // Generate server plugin code
4535
+ const imports = ['import type { BetterAuthPlugin } from "@better-auth/core"'];
4536
+ if (hooks.length > 0 || middleware.length > 0 || endpoints.length > 0) {
4537
+ imports.push('import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api"');
4538
+ }
4539
+ const serverPluginBody = pluginParts.length > 0
4540
+ ? ` id: "${camelCaseName}" as const,\n${pluginParts.join(',\n')}`
4541
+ : ` id: "${camelCaseName}" as const`;
4542
+ const serverPluginCode = cleanCode(`import type { BetterAuthPlugin } from "@better-auth/core";
4543
+ ${imports.join('\n')}
4544
+
4545
+ ${description ? `/**\n * ${description.replace(/\n/g, '\n * ')}\n */` : ''}
4546
+ export const ${camelCaseName} = (options?: Record<string, any>) => {
4547
+ return {
4548
+ ${serverPluginBody}
4549
+ } satisfies BetterAuthPlugin;
4550
+ };
4551
+ `);
4552
+ const pathMethods = endpoints.length > 0
4553
+ ? endpoints
4554
+ .map((endpoint) => {
4555
+ const endpointPath = endpoint.path?.trim() || '';
4556
+ const method = endpoint.method || 'POST';
4557
+ return ` "${endpointPath}": "${method}"`;
4558
+ })
4559
+ .join(',\n')
4560
+ : '';
4561
+ const sessionAffectingPaths = endpoints
4562
+ .filter((endpoint) => {
4563
+ const path = endpoint.path?.trim() || '';
4564
+ return path.includes('/sign-in') || path.includes('/sign-up');
4565
+ })
4566
+ .map((endpoint) => {
4567
+ const endpointPath = endpoint.path?.trim() || '';
4568
+ return ` {
4569
+ matcher: (path) => path === "${endpointPath}",
4570
+ signal: "$sessionSignal",
4571
+ }`;
4572
+ });
4573
+ const atomListenersCode = sessionAffectingPaths.length > 0
4574
+ ? `\n atomListeners: [\n${sessionAffectingPaths.join(',\n')}\n ],`
4575
+ : '';
4576
+ const clientPluginCode = cleanCode(`import type { BetterAuthClientPlugin } from "@better-auth/core";
4577
+ import type { ${camelCaseName} } from "..";
4578
+
4579
+ export const ${camelCaseName}Client = () => {
4580
+ return {
4581
+ id: "${camelCaseName}" as const,
4582
+ $InferServerPlugin: {} as ReturnType<typeof ${camelCaseName}>,${pathMethods ? `\n pathMethods: {\n${pathMethods}\n },` : ''}${atomListenersCode}
4583
+ } satisfies BetterAuthClientPlugin;
4584
+ };
4585
+ `);
4586
+ // Generate server setup code
4587
+ const serverSetupCode = cleanCode(`import { betterAuth } from "@better-auth/core";
4588
+ import { ${camelCaseName} } from "./plugin/${camelCaseName}";
4589
+
4590
+ export const auth = betterAuth({
4591
+ // ... your existing config
4592
+ plugins: [
4593
+ ${camelCaseName}(),
4594
+ ],
4595
+ });
4596
+ `);
4597
+ const frameworkImportMap = {
4598
+ react: 'better-auth/react',
4599
+ svelte: 'better-auth/svelte',
4600
+ solid: 'better-auth/solid',
4601
+ vue: 'better-auth/vue',
4602
+ };
4603
+ const frameworkImport = frameworkImportMap[clientFramework] || 'better-auth/react';
4604
+ // Get baseURL based on framework
4605
+ const baseURLMap = {
4606
+ react: 'process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"',
4607
+ svelte: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
4608
+ solid: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
4609
+ vue: 'import.meta.env.PUBLIC_BETTER_AUTH_URL || "http://localhost:5173"',
4610
+ };
4611
+ const baseURL = baseURLMap[clientFramework] ||
4612
+ 'process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"';
4613
+ const clientSetupCode = cleanCode(`import { createAuthClient } from "${frameworkImport}";
4614
+ import { ${camelCaseName}Client } from "./plugin/${camelCaseName}/client";
4615
+
4616
+ export const authClient = createAuthClient({
4617
+ baseURL: ${baseURL},
4618
+ plugins: [
4619
+ ${camelCaseName}Client(),
4620
+ ],
4621
+ });
4622
+ `);
4623
+ return res.json({
4624
+ success: true,
4625
+ plugin: {
4626
+ name: sanitizedName,
4627
+ server: serverPluginCode,
4628
+ client: clientPluginCode,
4629
+ serverSetup: serverSetupCode,
4630
+ clientSetup: clientSetupCode,
4631
+ filePaths: {
4632
+ server: `plugin/${camelCaseName}/index.ts`,
4633
+ client: `plugin/${camelCaseName}/client/index.ts`,
4634
+ serverSetup: 'auth.ts',
4635
+ clientSetup: 'auth-client.ts',
4636
+ },
4637
+ },
4638
+ });
4639
+ }
4640
+ catch (error) {
4641
+ const message = error instanceof Error ? error.message : 'Failed to generate plugin';
4642
+ res.status(500).json({ success: false, error: message });
4643
+ }
4644
+ });
4334
4645
  router.post('/api/tools/export', async (req, res) => {
4335
4646
  try {
4336
4647
  const { tables, format, limit } = req.body;
@@ -4344,7 +4655,6 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4344
4655
  return res.status(500).json({ success: false, error: 'Auth adapter not available' });
4345
4656
  }
4346
4657
  const exportData = {};
4347
- // Fetch data from each table
4348
4658
  for (const tableName of tables) {
4349
4659
  try {
4350
4660
  const data = await adapter.findMany({
@@ -4354,7 +4664,6 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4354
4664
  exportData[tableName] = data || [];
4355
4665
  }
4356
4666
  catch (error) {
4357
- // If table doesn't exist or can't be accessed, skip it
4358
4667
  exportData[tableName] = [];
4359
4668
  }
4360
4669
  }
@@ -4707,7 +5016,8 @@ export function createRoutes(authConfig, configPath, geoDbPath) {
4707
5016
  let value = match[2].trim();
4708
5017
  // Remove surrounding quotes if present (handles both single and double quotes)
4709
5018
  if (value.length >= 2) {
4710
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
5019
+ if ((value.startsWith('"') && value.endsWith('"')) ||
5020
+ (value.startsWith("'") && value.endsWith("'"))) {
4711
5021
  value = value.slice(1, -1).trim();
4712
5022
  }
4713
5023
  }