better-auth-studio 1.0.59-beta.9 → 1.0.60-beta.1

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/README.md CHANGED
@@ -253,10 +253,10 @@ cd better-auth-studio
253
253
  pnpm install
254
254
 
255
255
  # Build the project
256
- pnpm run build
256
+ pnpm build
257
257
 
258
258
  # Start development server
259
- pnpm run dev
259
+ pnpm dev
260
260
  ```
261
261
 
262
262
  ### Contributing
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAeA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAqLhG;AAeD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA4mLR"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAeA,OAAO,EAA+B,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+D9C,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAqLhG;AAeD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,UAAU,EACtB,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAuzLR"}
package/dist/routes.js CHANGED
@@ -5122,14 +5122,11 @@ export const authClient = createAuthClient({
5122
5122
  }
5123
5123
  const envPath = join(process.cwd(), '.env');
5124
5124
  const envLocalPath = join(process.cwd(), '.env.local');
5125
- // Try .env.local first, then .env
5126
5125
  const targetPath = existsSync(envLocalPath) ? envLocalPath : envPath;
5127
5126
  const envContent = existsSync(targetPath) ? readFileSync(targetPath, 'utf-8') : '';
5128
- // Generate environment variable names
5129
5127
  const providerUpper = provider.toUpperCase();
5130
5128
  const clientIdKey = `${providerUpper}_CLIENT_ID`;
5131
5129
  const clientSecretKey = `${providerUpper}_CLIENT_SECRET`;
5132
- // Parse existing .env file
5133
5130
  const envLines = envContent.split('\n');
5134
5131
  const existingCredentials = {};
5135
5132
  envLines.forEach((line) => {
@@ -5291,6 +5288,198 @@ export const authClient = createAuthClient({
5291
5288
  });
5292
5289
  }
5293
5290
  });
5291
+ router.post('/api/tools/apply-email-template', async (req, res) => {
5292
+ try {
5293
+ const { subject, html, templateId } = req.body || {};
5294
+ if (!subject || !html || !templateId) {
5295
+ return res
5296
+ .status(400)
5297
+ .json({ success: false, message: 'templateId, subject and html are required' });
5298
+ }
5299
+ const authPathFromConfig = configPath ? join(process.cwd(), configPath) : null;
5300
+ const authPath = authPathFromConfig || (await findAuthConfigPath());
5301
+ if (!authPath || !existsSync(authPath)) {
5302
+ return res.status(404).json({ success: false, message: 'auth.ts not found' });
5303
+ }
5304
+ let fileContent = readFileSync(authPath, 'utf-8');
5305
+ // Escape backticks and ${ for template literals
5306
+ // First escape backslashes, then escape backticks and ${ to avoid double-escaping
5307
+ const escapedSubject = subject.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
5308
+ const escapedHtml = html.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '\\${');
5309
+ if (!fileContent.includes("from 'resend'")) {
5310
+ fileContent = `import { Resend } from 'resend';\n` + fileContent;
5311
+ }
5312
+ if (!fileContent.includes('const resend = new Resend(')) {
5313
+ const importBlockEnd = fileContent.indexOf('\n', fileContent.lastIndexOf('import'));
5314
+ if (importBlockEnd > -1) {
5315
+ fileContent =
5316
+ fileContent.slice(0, importBlockEnd + 1) +
5317
+ `const resend = new Resend(process.env.RESEND_API_KEY || '');\n` +
5318
+ fileContent.slice(importBlockEnd + 1);
5319
+ }
5320
+ else {
5321
+ fileContent =
5322
+ `const resend = new Resend(process.env.RESEND_API_KEY || '');\n` + fileContent;
5323
+ }
5324
+ }
5325
+ const handlers = {
5326
+ 'password-reset': {
5327
+ regex: /sendResetPassword\s*:\s*async\s*\([^)]*\)\s*=>\s*\{[\s\S]*?\},?/,
5328
+ sectionRegex: /emailAndPassword\s*:\s*\{\s*/,
5329
+ fn: `sendResetPassword: async ({ user, url, token }, request) => {
5330
+ const subject = \`${escapedSubject}\`
5331
+ .replace(/{{user.name}}/g, user?.name || '')
5332
+ .replace(/{{user.email}}/g, user?.email || '');
5333
+
5334
+ const html = \`${escapedHtml}\`
5335
+ .replace(/{{user.name}}/g, user?.name || '')
5336
+ .replace(/{{user.email}}/g, user?.email || '')
5337
+ .replace(/{{url}}/g, url || '')
5338
+ .replace(/{{token}}/g, token || '');
5339
+
5340
+ void sendEmail({
5341
+ to: user.email,
5342
+ subject,
5343
+ html,
5344
+ });
5345
+ }`,
5346
+ },
5347
+ 'email-verification': {
5348
+ regex: /sendVerificationEmail\s*:\s*async\s*\([^)]*\)\s*=>\s*\{[\s\S]*?\},?/,
5349
+ sectionRegex: /emailVerification\s*:\s*\{\s*/,
5350
+ fn: `sendVerificationEmail: async ({ user, url, token }, request) => {
5351
+ const subject = \`${escapedSubject}\`
5352
+ .replace(/{{user.name}}/g, user?.name || '')
5353
+ .replace(/{{user.email}}/g, user?.email || '');
5354
+
5355
+ const html = \`${escapedHtml}\`
5356
+ .replace(/{{user.name}}/g, user?.name || '')
5357
+ .replace(/{{user.email}}/g, user?.email || '')
5358
+ .replace(/{{url}}/g, url || '')
5359
+ .replace(/{{token}}/g, token || '');
5360
+
5361
+ void sendEmail({
5362
+ to: user.email,
5363
+ subject,
5364
+ html,
5365
+ });
5366
+ }`,
5367
+ },
5368
+ 'magic-link': {
5369
+ regex: /sendMagicLink\s*:\s*async\s*\([^)]*\)\s*=>\s*\{[\s\S]*?\},?/,
5370
+ sectionRegex: /magicLink\s*:\s*\{\s*/,
5371
+ fn: `sendMagicLink: async ({ email, token, url }, ctx) => {
5372
+ const subject = \`${escapedSubject}\`
5373
+ .replace(/{{user.email}}/g, email || '');
5374
+
5375
+ const html = \`${escapedHtml}\`
5376
+ .replace(/{{user.email}}/g, email || '')
5377
+ .replace(/{{url}}/g, url || '')
5378
+ .replace(/{{token}}/g, token || '');
5379
+
5380
+ void sendEmail({
5381
+ to: email,
5382
+ subject,
5383
+ html,
5384
+ });
5385
+ }`,
5386
+ },
5387
+ 'org-invitation': {
5388
+ regex: /sendInvitationEmail\s*:\s*async\s*\([^)]*\)\s*=>\s*\{[\s\S]*?\},?/,
5389
+ fn: `sendInvitationEmail: async ({ data, request }: {
5390
+ data: {
5391
+ invitation: {
5392
+ id: string;
5393
+ organizationId: string;
5394
+ email: string;
5395
+ role: string;
5396
+ status: "pending" | "accepted" | "rejected" | "canceled";
5397
+ inviterId: string;
5398
+ expiresAt: Date;
5399
+ createdAt: Date;
5400
+ teamId?: string | null | undefined;
5401
+ };
5402
+ organization: { name?: string; slug?: string };
5403
+ inviter: { user: { name?: string; email?: string } };
5404
+ };
5405
+ request?: Request;
5406
+ }) => {
5407
+ const { invitation, organization, inviter } = data;
5408
+ const baseUrl = process.env.BETTER_AUTH_URL || 'http://localhost:3000';
5409
+ const url = \`\${baseUrl}/accept-invitation?id=\${invitation.id}\`;
5410
+
5411
+ const subject = \`${escapedSubject}\`
5412
+ .replace(/{{organization.name}}/g, organization?.name || '')
5413
+ .replace(/{{invitation.role}}/g, invitation.role || '')
5414
+ .replace(/{{inviter.user.name}}/g, inviter?.user?.name || '')
5415
+ .replace(/{{inviter.user.email}}/g, inviter?.user?.email || '')
5416
+ .replace(/{{invitation.email}}/g, invitation.email || '');
5417
+
5418
+ const html = \`${escapedHtml}\`
5419
+ .replace(/{{invitation.url}}/g, url)
5420
+ .replace(/{{invitation.role}}/g, invitation.role || '')
5421
+ .replace(/{{organization.name}}/g, organization?.name || '')
5422
+ .replace(/{{organization.slug}}/g, organization?.slug || '')
5423
+ .replace(/{{inviter.user.name}}/g, inviter?.user?.name || '')
5424
+ .replace(/{{inviter.user.email}}/g, inviter?.user?.email || '')
5425
+ .replace(/{{invitation.email}}/g, invitation.email || '')
5426
+ .replace(/{{invitation.expiresAt}}/g, invitation.expiresAt?.toLocaleString() || '')
5427
+ .replace(/{{expiresIn}}/g, invitation.expiresAt ? \`\${Math.ceil((invitation.expiresAt.getTime() - Date.now()) / (1000 * 60 * 60 * 24))} days\` : '');
5428
+
5429
+ void sendEmail({
5430
+ to: invitation.email,
5431
+ subject,
5432
+ html,
5433
+ });
5434
+ }`,
5435
+ },
5436
+ };
5437
+ const handler = handlers[templateId];
5438
+ if (!handler) {
5439
+ return res
5440
+ .status(400)
5441
+ .json({ success: false, message: 'Unsupported templateId for apply' });
5442
+ }
5443
+ if (!fileContent.includes('const sendEmail = async')) {
5444
+ const insertPoint = fileContent.indexOf('export const auth');
5445
+ fileContent =
5446
+ fileContent.slice(0, insertPoint) +
5447
+ `const sendEmail = async ({ to, subject, text, html }: { to: string; subject: string; text?: string; html?: string }) => {\n console.log(\`Sending email to \${to} | \${subject}\`);\n if (text) console.log('Text content:', text);\n if (html) console.log('HTML content:', html);\n};\n\n` +
5448
+ fileContent.slice(insertPoint);
5449
+ }
5450
+ if (handler.regex.test(fileContent)) {
5451
+ fileContent = fileContent.replace(handler.regex, `${handler.fn},`);
5452
+ }
5453
+ else if (templateId === 'org-invitation') {
5454
+ const orgPluginRegex = /organization\(\s*\{\s*/;
5455
+ if (!orgPluginRegex.test(fileContent)) {
5456
+ return res.status(400).json({
5457
+ success: false,
5458
+ message: 'organization plugin config not found in auth.ts',
5459
+ });
5460
+ }
5461
+ fileContent = fileContent.replace(orgPluginRegex, (m) => `${m}${handler.fn},\n `);
5462
+ }
5463
+ else {
5464
+ const sectionRegex = handler.sectionRegex || /emailAndPassword\s*:\s*\{\s*/;
5465
+ if (!sectionRegex.test(fileContent)) {
5466
+ return res.status(400).json({
5467
+ success: false,
5468
+ message: 'target email config section not found in auth.ts',
5469
+ });
5470
+ }
5471
+ fileContent = fileContent.replace(sectionRegex, (m) => `${m}${handler.fn},\n `);
5472
+ }
5473
+ writeFileSync(authPath, fileContent, 'utf-8');
5474
+ res.json({ success: true, path: authPath });
5475
+ }
5476
+ catch (error) {
5477
+ res.status(500).json({
5478
+ success: false,
5479
+ message: error?.message || 'Failed to apply invitation template',
5480
+ });
5481
+ }
5482
+ });
5294
5483
  return router;
5295
5484
  }
5296
5485
  //# sourceMappingURL=routes.js.map