betterstart-cli 0.0.28 → 0.0.30

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 (82) hide show
  1. package/dist/assets/adapters/next/integrations/mailchimp/actions/mailchimp.ts +0 -1
  2. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -1
  3. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +1 -1
  4. package/dist/assets/adapters/next/templates/init/admin-globals.css +1 -1
  5. package/dist/assets/adapters/next/templates/init/components/layouts/admin-nav-link.tsx +12 -2
  6. package/dist/assets/adapters/next/templates/init/components/layouts/admin-settings-sidebar.tsx +75 -0
  7. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar-nav-link.tsx +12 -2
  8. package/dist/assets/adapters/next/templates/init/components/layouts/admin-sidebar.tsx +2 -8
  9. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid.tsx +14 -5
  10. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-table.tsx +111 -112
  11. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +4 -0
  12. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  13. package/dist/assets/adapters/next/templates/init/components/shared/entity-versions/entity-versions-drawer.tsx +1 -1
  14. package/dist/assets/adapters/next/templates/init/components/shared/media/media-url-importer.tsx +45 -47
  15. package/dist/assets/adapters/next/templates/init/components/shared/page-header.tsx +1 -1
  16. package/dist/assets/adapters/next/templates/init/data/webhook-events.ts +5 -0
  17. package/dist/assets/adapters/next/templates/init/hooks/use-table-utils.ts +27 -7
  18. package/dist/assets/adapters/next/templates/init/hooks/use-webhooks.ts +211 -0
  19. package/dist/assets/adapters/next/templates/init/lib/actions/audit/types.ts +2 -0
  20. package/dist/assets/adapters/next/templates/init/lib/actions/forms/index.ts +0 -1
  21. package/dist/assets/adapters/next/templates/init/lib/actions/forms/types.ts +0 -4
  22. package/dist/assets/adapters/next/templates/init/lib/actions/forms/upsert-form-settings.ts +0 -2
  23. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/create-webhook.ts +55 -0
  24. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/delete-webhook.ts +26 -0
  25. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/dispatch.ts +135 -0
  26. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-deliveries.ts +124 -0
  27. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-secret.ts +28 -0
  28. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhook-subscriptions.ts +32 -0
  29. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/get-webhooks.ts +154 -0
  30. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/index.ts +30 -0
  31. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/internal-get-active-endpoints.ts +20 -0
  32. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/regenerate-webhook-secret.ts +35 -0
  33. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/register.ts +70 -0
  34. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +81 -0
  35. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/types.ts +104 -0
  36. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/update-webhook.ts +72 -0
  37. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +71 -2
  38. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/register.ts +2 -0
  39. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +3 -1
  40. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/types.ts +6 -1
  41. package/dist/assets/adapters/next/templates/init/pages/auth-gate-rsc.tsx +1 -1
  42. package/dist/assets/adapters/next/templates/init/pages/media/media-page-content.tsx +1 -1
  43. package/dist/assets/adapters/next/templates/init/pages/media/media-page-skeleton.tsx +1 -1
  44. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-columns.tsx +150 -0
  45. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-page-content.tsx +137 -0
  46. package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page-skeleton.tsx +1 -1
  47. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +295 -0
  48. package/dist/assets/adapters/next/templates/init/pages/settings/forms/edit-form-notifications-dialog.tsx +128 -0
  49. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-content.tsx +111 -0
  50. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page-skeleton.tsx +7 -0
  51. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-page.tsx +14 -0
  52. package/dist/assets/adapters/next/templates/init/pages/settings/settings-layout.tsx +10 -0
  53. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/delete-webhook-dialog.tsx +82 -0
  54. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-actions.tsx +53 -0
  55. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-enabled-switch.tsx +18 -0
  56. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhook-endpoint-dialog.tsx +370 -0
  57. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-columns.tsx +258 -0
  58. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-columns.tsx +200 -0
  59. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page-content.tsx +117 -0
  60. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-page.tsx +14 -0
  61. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-logs-table.tsx +253 -0
  62. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-content.tsx +161 -0
  63. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page-skeleton.tsx +7 -0
  64. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-page.tsx +14 -0
  65. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +264 -0
  66. package/dist/assets/adapters/next/templates/init/pages/users/users-page-skeleton.tsx +1 -1
  67. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +103 -109
  68. package/dist/assets/adapters/next/templates/init/types/webhooks.ts +7 -0
  69. package/dist/assets/adapters/next/templates/init/utils/webhook/signature.ts +13 -0
  70. package/dist/assets/shared-assets/react-admin/ui/card.tsx +1 -1
  71. package/dist/assets/shared-assets/react-admin/ui/drawer.tsx +2 -2
  72. package/dist/assets/shared-assets/react-admin/ui/input-group.tsx +3 -3
  73. package/dist/assets/shared-assets/react-admin/ui/sidebar.tsx +1 -1
  74. package/dist/cli.js +947 -620
  75. package/dist/cli.js.map +1 -1
  76. package/package.json +1 -1
  77. package/dist/assets/adapters/next/templates/init/lib/actions/forms/test-form-webhook.ts +0 -40
  78. package/dist/assets/adapters/next/templates/init/pages/audit-log/audit-log-page-content.tsx +0 -550
  79. package/dist/assets/adapters/next/templates/init/utils/webhook/webhook.ts +0 -28
  80. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-drawer.tsx +0 -0
  81. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-detail-row.tsx +0 -0
  82. /package/dist/assets/adapters/next/templates/init/pages/{audit-log → settings/audit-log}/audit-log-page.tsx +0 -0
package/dist/cli.js CHANGED
@@ -1410,16 +1410,29 @@ import path9 from "path";
1410
1410
  var PG_SSL_WARNING_START = "Warning: SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'.";
1411
1411
  var PG_SSL_WARNING_END = "See https://www.postgresql.org/docs/current/libpq-ssl.html for libpq SSL mode definitions.";
1412
1412
  var NODE_TRACE_WARNING_HINT = "(Use `node --trace-warnings";
1413
- var DRIZZLE_CONFIG_BANNER_PATTERNS = [
1413
+ var DRIZZLE_NOISE_PATTERNS = [
1414
1414
  /^No config path provided, using default 'drizzle\.config\.ts'$/,
1415
1415
  /^Reading config file '.+drizzle\.config\.ts'$/,
1416
- /^Using 'pg' driver for database querying$/
1416
+ /^Using 'pg' driver for database querying$/,
1417
+ /^\[.\] Pulling schema from database\.\.\.$/u,
1418
+ /^\[.\] Changes applied$/u
1417
1419
  ];
1418
- function resolveDrizzlePushStdio(inheritStdio) {
1419
- return inheritStdio ? "inherit" : "pipe";
1420
+ var SUPPRESSIBLE_LINE_STARTS = [
1421
+ "No config path provided",
1422
+ "Reading config file '",
1423
+ "Using 'pg' driver",
1424
+ "(node:",
1425
+ "["
1426
+ ];
1427
+ var ANSI_ESCAPE_PATTERN = /\u001B\[[0-9;]*[A-Za-z]/g;
1428
+ function resolveDrizzlePushStdio(interactive) {
1429
+ return interactive ? ["inherit", "pipe", "pipe"] : "pipe";
1430
+ }
1431
+ function stripAnsi(text7) {
1432
+ return text7.replace(ANSI_ESCAPE_PATTERN, "").replaceAll("\r", "");
1420
1433
  }
1421
- function shouldSuppressWarningLine(line, state) {
1422
- const text7 = line.trimEnd();
1434
+ function shouldSuppressOutputLine(line, state) {
1435
+ const text7 = stripAnsi(line).trim();
1423
1436
  if (state.suppressNextTraceHint) {
1424
1437
  state.suppressNextTraceHint = false;
1425
1438
  if (text7.startsWith(NODE_TRACE_WARNING_HINT)) {
@@ -1437,20 +1450,20 @@ function shouldSuppressWarningLine(line, state) {
1437
1450
  }
1438
1451
  return true;
1439
1452
  }
1440
- return false;
1441
- }
1442
- function shouldSuppressDrizzleConfigBannerLine(line) {
1443
- const text7 = line.trim();
1444
- return DRIZZLE_CONFIG_BANNER_PATTERNS.some((pattern) => pattern.test(text7));
1445
- }
1446
- function shouldSuppressOutputLine(line, state) {
1447
- return shouldSuppressWarningLine(line, state) || shouldSuppressDrizzleConfigBannerLine(line);
1453
+ return DRIZZLE_NOISE_PATTERNS.some((pattern) => pattern.test(text7));
1448
1454
  }
1449
- function filterWarningLines(output, state) {
1455
+ function filterNoiseLines(output, state) {
1450
1456
  const lines = output.match(/[^\n]*\n|[^\n]+/g) ?? [];
1451
1457
  return lines.filter((line) => !shouldSuppressOutputLine(line, state)).join("");
1452
1458
  }
1453
- function createPgConnectionStringSslWarningFilter(write) {
1459
+ function couldBecomeSuppressible(partial, state) {
1460
+ if (state.suppressingPgSslWarning || state.suppressNextTraceHint) {
1461
+ return true;
1462
+ }
1463
+ const text7 = stripAnsi(partial);
1464
+ return SUPPRESSIBLE_LINE_STARTS.some((start) => text7.startsWith(start) || start.startsWith(text7));
1465
+ }
1466
+ function createDrizzleNoiseFilter(forward) {
1454
1467
  const state = {
1455
1468
  suppressingPgSslWarning: false,
1456
1469
  suppressNextTraceHint: false
@@ -1460,24 +1473,27 @@ function createPgConnectionStringSslWarningFilter(write) {
1460
1473
  write(chunk) {
1461
1474
  buffer += chunk;
1462
1475
  const lastNewlineIndex = buffer.lastIndexOf("\n");
1463
- if (lastNewlineIndex === -1) {
1464
- return;
1476
+ if (lastNewlineIndex !== -1) {
1477
+ const complete = buffer.slice(0, lastNewlineIndex + 1);
1478
+ buffer = buffer.slice(lastNewlineIndex + 1);
1479
+ const filtered = filterNoiseLines(complete, state);
1480
+ if (filtered) {
1481
+ forward(filtered);
1482
+ }
1465
1483
  }
1466
- const complete = buffer.slice(0, lastNewlineIndex + 1);
1467
- buffer = buffer.slice(lastNewlineIndex + 1);
1468
- const filtered = filterWarningLines(complete, state);
1469
- if (filtered) {
1470
- write(filtered);
1484
+ if (buffer && !couldBecomeSuppressible(buffer, state)) {
1485
+ forward(buffer);
1486
+ buffer = "";
1471
1487
  }
1472
1488
  },
1473
1489
  end() {
1474
1490
  if (!buffer) {
1475
1491
  return;
1476
1492
  }
1477
- const filtered = filterWarningLines(buffer, state);
1493
+ const filtered = filterNoiseLines(buffer, state);
1478
1494
  buffer = "";
1479
1495
  if (filtered) {
1480
- write(filtered);
1496
+ forward(filtered);
1481
1497
  }
1482
1498
  }
1483
1499
  };
@@ -1487,20 +1503,20 @@ function runDrizzlePush(cwd, options = {}) {
1487
1503
  const drizzleBin = path9.join(cwd, "node_modules", ".bin", "drizzle-kit");
1488
1504
  const child = spawn2(drizzleBin, ["push", "--force"], {
1489
1505
  cwd,
1490
- stdio: resolveDrizzlePushStdio(options.inheritStdio),
1506
+ stdio: resolveDrizzlePushStdio(options.interactive),
1491
1507
  env: { ...process.env }
1492
1508
  });
1493
1509
  let stdout = "";
1494
1510
  let stderr = "";
1495
- const stderrFilter = createPgConnectionStringSslWarningFilter((chunk) => {
1496
- if (options.inheritStdio) {
1511
+ const stderrFilter = createDrizzleNoiseFilter((chunk) => {
1512
+ if (options.interactive) {
1497
1513
  process.stderr.write(chunk);
1498
1514
  return;
1499
1515
  }
1500
1516
  stderr += chunk;
1501
1517
  });
1502
- const stdoutFilter = createPgConnectionStringSslWarningFilter((chunk) => {
1503
- if (options.inheritStdio) {
1518
+ const stdoutFilter = createDrizzleNoiseFilter((chunk) => {
1519
+ if (options.interactive) {
1504
1520
  process.stdout.write(chunk);
1505
1521
  return;
1506
1522
  }
@@ -1515,7 +1531,7 @@ function runDrizzlePush(cwd, options = {}) {
1515
1531
  child.on("close", (code, signal) => {
1516
1532
  stdoutFilter.end();
1517
1533
  stderrFilter.end();
1518
- if (options.inheritStdio) {
1534
+ if (options.interactive) {
1519
1535
  if (code === 0) {
1520
1536
  resolve({ success: true, error: null });
1521
1537
  return;
@@ -1733,7 +1749,7 @@ async function runPostGenerate(cwd, schemaName, options = {}) {
1733
1749
  } else {
1734
1750
  console.log("\n Running drizzle-kit push...");
1735
1751
  try {
1736
- const pushResult = await runDrizzlePush(cwd, { inheritStdio: true });
1752
+ const pushResult = await runDrizzlePush(cwd, { interactive: true });
1737
1753
  if (!pushResult.success) {
1738
1754
  throw new Error(pushResult.error ?? "drizzle-kit push failed");
1739
1755
  }
@@ -2856,6 +2872,8 @@ function resolveProjectPaths(config) {
2856
2872
  const adminTypesDir = path14.posix.join(adminDir, "types");
2857
2873
  const adminNavigationFile = path14.posix.join(adminDir, "data", "navigation.ts");
2858
2874
  const adminNavigationDir = path14.posix.join(adminDir, "data", "navigation");
2875
+ const adminWebhookEventsFile = path14.posix.join(adminDir, "data", "webhook-events.ts");
2876
+ const adminWebhookEventsDir = path14.posix.join(adminDir, "data", "webhook-events");
2859
2877
  const adminActionsDir = path14.posix.join(adminDir, "lib", "actions");
2860
2878
  return {
2861
2879
  adminDir,
@@ -2872,6 +2890,8 @@ function resolveProjectPaths(config) {
2872
2890
  adminTypesNavigationFile: path14.posix.join(adminTypesDir, "navigation.ts"),
2873
2891
  adminNavigationFile,
2874
2892
  adminNavigationDir,
2893
+ adminWebhookEventsFile,
2894
+ adminWebhookEventsDir,
2875
2895
  adminActionsDir,
2876
2896
  adminEmailTemplatesDir: path14.posix.join(adminDir, "components", "email-templates"),
2877
2897
  adminHooksDir: path14.posix.join(adminDir, "hooks")
@@ -6322,6 +6342,22 @@ function lifecycleHookContextExample(event, schemaName, input, data, schemaType
6322
6342
  if (schemaType === "form") {
6323
6343
  context.schemaType = "form";
6324
6344
  }
6345
+ if (event === "beforeRestore" || event === "afterRestore") {
6346
+ context.entityId = SAMPLE_UUID;
6347
+ context.changedFields = [];
6348
+ context.metadata = { restoredFromVersion: 2 };
6349
+ if (phase === "after") {
6350
+ context.data = data;
6351
+ }
6352
+ return createExample("Context", context);
6353
+ }
6354
+ if (event === "beforeReorder" || event === "afterReorder") {
6355
+ context.changedFields = ["sortOrder"];
6356
+ if (phase === "after") {
6357
+ context.data = { order: [{ id: SAMPLE_UUID, sortOrder: 0 }] };
6358
+ }
6359
+ return createExample("Context", context);
6360
+ }
6325
6361
  if (event !== "beforeCreate" && event !== "afterCreate") {
6326
6362
  context.entityId = SAMPLE_UUID;
6327
6363
  }
@@ -6348,7 +6384,11 @@ function buildEntityDevModeLifecycleHooks(schema, adminImportAlias) {
6348
6384
  "beforeUpdate",
6349
6385
  "afterUpdate",
6350
6386
  "beforeDelete",
6351
- "afterDelete"
6387
+ "afterDelete",
6388
+ "beforeRestore",
6389
+ "afterRestore",
6390
+ "beforeReorder",
6391
+ "afterReorder"
6352
6392
  ];
6353
6393
  if (schema.actions?.draft === true) {
6354
6394
  events.push("beforePublish", "afterPublish", "beforeUnpublish", "afterUnpublish");
@@ -6896,7 +6936,7 @@ function generatePageSkeleton(pascal, label) {
6896
6936
  return `export function ${pascal}SubmissionsPageSkeleton() {
6897
6937
  return (
6898
6938
  <div className="flex items-center justify-center h-48">
6899
- <div className="text-muted-foreground">Loading ${label}...</div>
6939
+ <div className="text-muted-foreground">Loading ${label}\u2026</div>
6900
6940
  </div>
6901
6941
  )
6902
6942
  }
@@ -7059,7 +7099,7 @@ ${deleteTransition}${bulkDeleteHandler}
7059
7099
  <div className="flex items-center gap-1">
7060
7100
  <${devModeIntegrateButtonExportName} initialSettings={initialSettings} />${exportButton}
7061
7101
  <Button variant="outline" asChild>
7062
- <Link href="${adminRoutePath}/forms/${kebab}/settings">
7102
+ <Link href="${adminRoutePath}/settings/forms">
7063
7103
  <Settings className="size-3.5" />
7064
7104
  Settings
7065
7105
  </Link>
@@ -7105,199 +7145,6 @@ ${bulkDeleteActions}
7105
7145
  `;
7106
7146
  }
7107
7147
 
7108
- // adapters/next/generators/form-admin/admin-settings-page.ts
7109
- function generateSettingsPage(pascal, kebab) {
7110
- return `import { getFormSettings } from '@admin/actions/forms'
7111
- import { ${pascal}SettingsPageContent } from './settings-page-content'
7112
-
7113
- export default async function ${pascal}SettingsPage() {
7114
- const settings = await getFormSettings('${kebab}')
7115
-
7116
- return <${pascal}SettingsPageContent initialSettings={settings} />
7117
- }
7118
- `;
7119
- }
7120
- function generateSettingsPageContent(pascal, kebab, label) {
7121
- return `'use client'
7122
-
7123
- import {
7124
- testFormWebhook,
7125
- type FormSettingsData,
7126
- upsertFormSettings,
7127
- } from '@admin/actions/forms'
7128
- import { PageHeader } from '@admin/components/shared/page-header'
7129
- import { Button } from '@admin/components/ui/button'
7130
- import {
7131
- Form,
7132
- FormControl,
7133
- FormDescription,
7134
- FormField,
7135
- FormItem,
7136
- FormLabel,
7137
- } from '@admin/components/ui/form'
7138
- import { Input } from '@admin/components/ui/input'
7139
- import { Switch } from '@admin/components/ui/switch'
7140
- import { Textarea } from '@admin/components/ui/textarea'
7141
- import { LoaderCircle } from 'lucide-react'
7142
- import * as React from 'react'
7143
- import { useForm } from 'react-hook-form'
7144
- import { toast } from 'sonner'
7145
-
7146
- interface ${pascal}SettingsPageContentProps {
7147
- initialSettings: FormSettingsData | null
7148
- }
7149
-
7150
- interface ${pascal}SettingsFormValues {
7151
- notificationEmails: string
7152
- webhookUrl: string
7153
- webhookEnabled: boolean
7154
- }
7155
-
7156
- export function ${pascal}SettingsPageContent({
7157
- initialSettings,
7158
- }: ${pascal}SettingsPageContentProps) {
7159
- const form = useForm<${pascal}SettingsFormValues>({
7160
- defaultValues: {
7161
- notificationEmails: initialSettings?.notificationEmails ?? '',
7162
- webhookUrl: initialSettings?.webhookUrl ?? '',
7163
- webhookEnabled: initialSettings?.webhookEnabled ?? false,
7164
- },
7165
- })
7166
- const [isSaving, startSaveTransition] = React.useTransition()
7167
- const [isTesting, startTestTransition] = React.useTransition()
7168
- const webhookUrl = form.watch('webhookUrl')
7169
- const webhookEnabled = form.watch('webhookEnabled')
7170
-
7171
- const handleSave = (values: ${pascal}SettingsFormValues) => {
7172
- startSaveTransition(async () => {
7173
- const result = await upsertFormSettings('${kebab}', {
7174
- notificationEmails: values.notificationEmails || null,
7175
- webhookUrl: values.webhookUrl || null,
7176
- webhookEnabled: values.webhookEnabled,
7177
- })
7178
- if (result.success) {
7179
- toast.success('Settings saved successfully')
7180
- } else {
7181
- toast.error(result.error || 'Failed to save settings')
7182
- }
7183
- })
7184
- }
7185
-
7186
- const handleTestWebhook = () => {
7187
- startTestTransition(async () => {
7188
- const result = await testFormWebhook('${kebab}')
7189
- if (result.success) {
7190
- toast.success('Test webhook sent successfully')
7191
- } else {
7192
- toast.error(result.error || 'Failed to send test webhook')
7193
- }
7194
- })
7195
- }
7196
-
7197
- return (
7198
- <>
7199
- <PageHeader title="${label} Settings" />
7200
- <Form {...form}>
7201
- <form
7202
- onSubmit={form.handleSubmit(handleSave)}
7203
- className="space-y-6 rounded-sm border p-6 mx-6 mt-6"
7204
- >
7205
- <FormField
7206
- control={form.control}
7207
- name="notificationEmails"
7208
- render={({ field }) => (
7209
- <FormItem>
7210
- <FormLabel>Notification Emails</FormLabel>
7211
- <FormControl>
7212
- <Textarea
7213
- placeholder="email@example.com, another@example.com"
7214
- rows={3}
7215
- {...field}
7216
- />
7217
- </FormControl>
7218
- <FormDescription>
7219
- Comma-separated list of email addresses to notify on new submissions.
7220
- </FormDescription>
7221
- </FormItem>
7222
- )}
7223
- />
7224
-
7225
- <div className="space-y-4 border-t pt-6">
7226
- <FormField
7227
- control={form.control}
7228
- name="webhookUrl"
7229
- render={({ field }) => (
7230
- <FormItem>
7231
- <FormLabel>Webhook URL</FormLabel>
7232
- <FormControl>
7233
- <Input
7234
- type="url"
7235
- placeholder="https://hooks.example.com/endpoint"
7236
- {...field}
7237
- />
7238
- </FormControl>
7239
- <FormDescription>
7240
- Receive a POST request with form data on each submission.
7241
- </FormDescription>
7242
- </FormItem>
7243
- )}
7244
- />
7245
-
7246
- <FormField
7247
- control={form.control}
7248
- name="webhookEnabled"
7249
- render={({ field }) => (
7250
- <FormItem className="flex flex-row items-center space-x-2 space-y-0">
7251
- <FormControl>
7252
- <Switch
7253
- checked={field.value}
7254
- onCheckedChange={field.onChange}
7255
- />
7256
- </FormControl>
7257
- <FormLabel>Enable Webhook</FormLabel>
7258
- </FormItem>
7259
- )}
7260
- />
7261
-
7262
- {webhookUrl && webhookEnabled && (
7263
- <Button
7264
- type="button"
7265
- variant="outline"
7266
- onClick={handleTestWebhook}
7267
- disabled={isTesting}
7268
- >
7269
- {isTesting ? (
7270
- <>
7271
- <LoaderCircle className="animate-spin" />
7272
- Sending...
7273
- </>
7274
- ) : (
7275
- 'Test Webhook'
7276
- )}
7277
- </Button>
7278
- )}
7279
- </div>
7280
-
7281
- <div className="border-t pt-6">
7282
- <Button type="submit" disabled={isSaving}>
7283
- {isSaving ? (
7284
- <>
7285
- <LoaderCircle className="animate-spin" />
7286
- Saving...
7287
- </>
7288
- ) : (
7289
- 'Save Settings'
7290
- )}
7291
- </Button>
7292
- </div>
7293
- </form>
7294
- </Form>
7295
- </>
7296
- )
7297
- }
7298
- `;
7299
- }
7300
-
7301
7148
  // adapters/next/generators/form-admin/admin-submission-drawer.ts
7302
7149
  function generateSubmissionDrawer(pascal, kebab, fields, label, includeDynamic, _actionImportPath) {
7303
7150
  const fieldItems = fields.filter((f) => f.name).map((f) => {
@@ -7428,7 +7275,7 @@ export function ${pascal}SubmissionDrawer({
7428
7275
  </DrawerHeader>
7429
7276
  <ScrollArea className="min-h-0 flex-1">
7430
7277
  {isPending ? (
7431
- <div className="p-4 text-sm text-muted-foreground">Loading submission...</div>
7278
+ <div className="p-4 text-sm text-muted-foreground">Loading submission\u2026</div>
7432
7279
  ) : error ? (
7433
7280
  <div className="p-4 text-sm text-destructive">
7434
7281
  Error loading submission: {error.message}
@@ -7468,7 +7315,6 @@ import {
7468
7315
  DataGridRow
7469
7316
  } from '@admin/components/shared/data-table/data-grid'
7470
7317
  import { DataTablePagination } from '@admin/components/shared/data-table/data-table-pagination'
7471
- import { Card, CardContent } from '@admin/components/ui/card'
7472
7318
  import { use${pascal}Submissions } from '@admin/hooks/use-${kebab}-form'
7473
7319
  import { useTableUtils } from '@admin/hooks/use-table-utils'
7474
7320
  import {
@@ -7602,108 +7448,104 @@ export function ${pascal}SubmissionsTable<TValue>({
7602
7448
 
7603
7449
  return (
7604
7450
  <div className={cn("flex flex-col gap-6 pt-px pb-2 h-full", className)}>
7605
- <Card className="min-h-0 flex-1 p-0">
7606
- <CardContent ref={tableContainerRef} className="flex min-h-0 flex-1 flex-col p-0">
7607
- <DataGrid containerClassName="min-h-0 flex-1" gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
7608
- <DataGridHeader>
7609
- {table.getHeaderGroups().map((headerGroup) => (
7610
- <DataGridRow key={headerGroup.id}>
7611
- {headerGroup.headers.map((header, headerIndex) => {
7612
- const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
7613
- const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
7614
- const nextHeader = headerGroup.headers[headerIndex + 1]
7615
- const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
7616
- return (
7617
- <DataGridHead
7618
- key={header.id}
7619
- className={cn('group/resize-header relative overflow-hidden text-foreground', {
7620
- 'text-center': meta?.align === 'center',
7621
- 'text-right': meta?.align === 'right'
7622
- })}
7623
- data-column-id={header.column.id}
7624
- onMouseEnter={() => setHoveredColumnId(header.column.id)}
7625
- onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
7626
- >
7627
- {header.isPlaceholder
7628
- ? null
7629
- : flexRender(header.column.columnDef.header, header.getContext())}
7630
- {canResizeColumn ? (
7631
- <button
7632
- type="button"
7633
- aria-label={\`Resize \${resizeLabel} column\`}
7634
- className="group/resize-handle absolute right-0 top-0 z-10 flex h-full w-5 cursor-col-resize touch-none select-none items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:text-foreground focus-visible:opacity-100 focus-visible:outline-none group-focus-within/resize-header:opacity-100 data-[column-hovered=true]:opacity-100 data-[resizing=true]:opacity-100"
7635
- data-resize-handle
7636
- data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
7637
- data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
7638
- onDoubleClick={resetColumnSizingToContainer}
7639
- onMouseDown={header.getResizeHandler()}
7640
- onTouchStart={header.getResizeHandler()}
7641
- >
7642
- <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
7643
- </button>
7644
- ) : null}
7645
- </DataGridHead>
7646
- )
7647
- })}
7648
- </DataGridRow>
7649
- ))}
7650
- </DataGridHeader>
7651
- <DataGridBody>
7652
- {isPending ? (
7653
- <DataGridRow>
7654
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7655
- <div className="text-muted-foreground">Loading ${label} submissions...</div>
7656
- </DataGridCell>
7657
- </DataGridRow>
7658
- ) : error ? (
7659
- <DataGridRow>
7660
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7661
- <div className="text-destructive">Error loading ${label} submissions: {error.message}</div>
7662
- </DataGridCell>
7663
- </DataGridRow>
7664
- ) : table.getRowModel().rows?.length ? (
7665
- table.getRowModel().rows.map((row) => (
7666
- <DataGridRow
7667
- key={row.id}
7668
- data-state={row.getIsSelected() ? 'selected' : undefined}
7669
- className="cursor-pointer hover:bg-muted/50"
7670
- onClick={(e) => {
7671
- const target = e.target as HTMLElement
7672
- if (!e.currentTarget.contains(target)) return
7673
- if (target.closest('button, a, input, [role="checkbox"]')) return
7674
- onViewSubmission(row.original.id)
7675
- }}
7676
- >
7677
- {row.getVisibleCells().map((cell) => {
7678
- const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
7679
- return (
7680
- <DataGridCell
7681
- key={cell.id}
7682
- className={cn({
7683
- 'text-center': meta?.align === 'center',
7684
- 'text-right': meta?.align === 'right'
7685
- })}
7686
- data-column-id={cell.column.id}
7687
- onMouseEnter={() => setHoveredColumnId(cell.column.id)}
7688
- onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
7689
- >
7690
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
7691
- </DataGridCell>
7692
- )
7451
+ <DataGrid containerClassName="min-h-0 flex-1" containerRef={tableContainerRef} gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
7452
+ <DataGridHeader>
7453
+ {table.getHeaderGroups().map((headerGroup) => (
7454
+ <DataGridRow key={headerGroup.id}>
7455
+ {headerGroup.headers.map((header, headerIndex) => {
7456
+ const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
7457
+ const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
7458
+ const nextHeader = headerGroup.headers[headerIndex + 1]
7459
+ const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
7460
+ return (
7461
+ <DataGridHead
7462
+ key={header.id}
7463
+ className={cn('group/resize-header relative overflow-hidden', {
7464
+ 'text-center': meta?.align === 'center',
7465
+ 'text-right': meta?.align === 'right'
7693
7466
  })}
7694
- </DataGridRow>
7695
- ))
7696
- ) : (
7697
- <DataGridRow>
7698
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7699
- No ${label} submissions found.
7700
- </DataGridCell>
7701
- </DataGridRow>
7702
- )}
7703
- </DataGridBody>
7704
- </DataGrid>
7705
- </CardContent>
7706
- </Card>
7467
+ data-column-id={header.column.id}
7468
+ onMouseEnter={() => setHoveredColumnId(header.column.id)}
7469
+ onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
7470
+ >
7471
+ {header.isPlaceholder
7472
+ ? null
7473
+ : flexRender(header.column.columnDef.header, header.getContext())}
7474
+ {canResizeColumn ? (
7475
+ <button
7476
+ type="button"
7477
+ aria-label={\`Resize \${resizeLabel} column\`}
7478
+ className="group/resize-handle absolute right-0 top-0 z-10 flex h-full w-5 cursor-col-resize touch-none select-none items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:text-foreground focus-visible:opacity-100 focus-visible:outline-none group-focus-within/resize-header:opacity-100 data-[column-hovered=true]:opacity-100 data-[resizing=true]:opacity-100"
7479
+ data-resize-handle
7480
+ data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
7481
+ data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
7482
+ onDoubleClick={resetColumnSizingToContainer}
7483
+ onMouseDown={header.getResizeHandler()}
7484
+ onTouchStart={header.getResizeHandler()}
7485
+ >
7486
+ <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
7487
+ </button>
7488
+ ) : null}
7489
+ </DataGridHead>
7490
+ )
7491
+ })}
7492
+ </DataGridRow>
7493
+ ))}
7494
+ </DataGridHeader>
7495
+ <DataGridBody>
7496
+ {isPending ? (
7497
+ <DataGridRow>
7498
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7499
+ <div className="text-muted-foreground">Loading ${label} submissions\u2026</div>
7500
+ </DataGridCell>
7501
+ </DataGridRow>
7502
+ ) : error ? (
7503
+ <DataGridRow>
7504
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7505
+ <div className="text-destructive">Error loading ${label} submissions: {error.message}</div>
7506
+ </DataGridCell>
7507
+ </DataGridRow>
7508
+ ) : table.getRowModel().rows?.length ? (
7509
+ table.getRowModel().rows.map((row) => (
7510
+ <DataGridRow
7511
+ key={row.id}
7512
+ data-state={row.getIsSelected() ? 'selected' : undefined}
7513
+ className="cursor-pointer hover:bg-muted/50"
7514
+ onClick={(e) => {
7515
+ const target = e.target as HTMLElement
7516
+ if (!e.currentTarget.contains(target)) return
7517
+ if (target.closest('button, a, input, [role="checkbox"]')) return
7518
+ onViewSubmission(row.original.id)
7519
+ }}
7520
+ >
7521
+ {row.getVisibleCells().map((cell) => {
7522
+ const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
7523
+ return (
7524
+ <DataGridCell
7525
+ key={cell.id}
7526
+ className={cn({
7527
+ 'text-center': meta?.align === 'center',
7528
+ 'text-right': meta?.align === 'right'
7529
+ })}
7530
+ data-column-id={cell.column.id}
7531
+ onMouseEnter={() => setHoveredColumnId(cell.column.id)}
7532
+ onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
7533
+ >
7534
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
7535
+ </DataGridCell>
7536
+ )
7537
+ })}
7538
+ </DataGridRow>
7539
+ ))
7540
+ ) : (
7541
+ <DataGridRow>
7542
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
7543
+ No ${label} submissions found.
7544
+ </DataGridCell>
7545
+ </DataGridRow>
7546
+ )}
7547
+ </DataGridBody>
7548
+ </DataGrid>
7707
7549
 
7708
7550
  {shouldShowPagination ? (
7709
7551
  <DataTablePagination
@@ -7812,14 +7654,6 @@ function generateFormAdminPages(schema, pagesDir, options) {
7812
7654
  hasDynamicFields(schema),
7813
7655
  actionImportPath
7814
7656
  )
7815
- ),
7816
- createGeneratedFile(
7817
- `${pagesDir}/forms/${kebab}/settings/page.tsx`,
7818
- generateSettingsPage(pascal, kebab)
7819
- ),
7820
- createGeneratedFile(
7821
- `${pagesDir}/forms/${kebab}/settings/settings-page-content.tsx`,
7822
- generateSettingsPageContent(pascal, kebab, schema.label)
7823
7657
  )
7824
7658
  ]
7825
7659
  };
@@ -8227,7 +8061,6 @@ import { ${tableName} } from '@admin/db/schema'
8227
8061
  import { getFormSettings } from '@admin/actions/forms'
8228
8062
  import { sendFormEmails } from '@admin/actions/email/form-delivery'
8229
8063
  import { getTouchedFieldNames } from '@admin/utils/audit/audit'
8230
- import { sendWebhook } from '@admin/utils/webhook/webhook'
8231
8064
  import { ${cacheInvalidationFn} } from 'next/cache'
8232
8065
  ${mailchimpImport}
8233
8066
  import { CACHE_TAG, CACHE_TAG_BY_ID } from './types'
@@ -8268,16 +8101,7 @@ export async function create${pascal}Submission(
8268
8101
  submitterEmail: ${submitterEmailField ? `data.${submitterEmailField}` : "null"},
8269
8102
  submissionData: { ...data },
8270
8103
  submittedAt: created${pascal}Submission.submittedAt,
8271
- })
8272
-
8273
- if (settings?.webhookEnabled && settings?.webhookUrl) {
8274
- sendWebhook(settings.webhookUrl, {
8275
- form_name: '${formName}',
8276
- submission_id: created${pascal}Submission.id,
8277
- ...data,
8278
- submitted_at: created${pascal}Submission.submittedAt,
8279
- })
8280
- }${mailchimpCall}
8104
+ })${mailchimpCall}
8281
8105
 
8282
8106
  ${cacheInvalidationFn}(CACHE_TAG)
8283
8107
  ${cacheInvalidationFn}(CACHE_TAG_BY_ID(created${pascal}Submission.id))
@@ -8792,6 +8616,58 @@ function namespaceGeneratedFiles(files, namespace) {
8792
8616
  }));
8793
8617
  }
8794
8618
 
8619
+ // adapters/next/generators/webhook-events.ts
8620
+ var NO_WEBHOOK_SCHEMAS = /* @__PURE__ */ new Set(["settings"]);
8621
+ function renderExtract(schemaName, kind, label, events, settingsKey) {
8622
+ const lines = [
8623
+ ` schema: '${schemaName}'`,
8624
+ ` kind: '${kind}'`,
8625
+ ` label: ${JSON.stringify(label)}`,
8626
+ ` events: [${events.map((event) => `'${event}'`).join(", ")}]`
8627
+ ];
8628
+ if (settingsKey) {
8629
+ lines.push(` settingsKey: '${settingsKey}'`);
8630
+ }
8631
+ return `import type { WebhookEventSource } from '@admin/types/webhooks'
8632
+
8633
+ export const ${toCamelCase(schemaName)}WebhookEvents: WebhookEventSource = {
8634
+ ${lines.join(",\n")}
8635
+ }
8636
+ `;
8637
+ }
8638
+ function updateWebhookEvents(schema, webhookEventsDir, kind) {
8639
+ if (NO_WEBHOOK_SCHEMAS.has(schema.name)) {
8640
+ return { files: [] };
8641
+ }
8642
+ const events = kind === "single" ? [`${schema.name}.updated`] : [
8643
+ `${schema.name}.created`,
8644
+ `${schema.name}.updated`,
8645
+ `${schema.name}.deleted`,
8646
+ ...schema.actions?.draft === true ? [`${schema.name}.published`, `${schema.name}.unpublished`] : [],
8647
+ `${schema.name}.restored`,
8648
+ `${schema.name}.reordered`
8649
+ ];
8650
+ return {
8651
+ files: [
8652
+ createGeneratedFile(
8653
+ `${webhookEventsDir}/${schema.name}.ts`,
8654
+ renderExtract(schema.name, kind, schema.label, events)
8655
+ )
8656
+ ]
8657
+ };
8658
+ }
8659
+ function updateFormWebhookEvents(schema, webhookEventsDir) {
8660
+ const kebabName = toKebabCase(schema.name);
8661
+ return {
8662
+ files: [
8663
+ createGeneratedFile(
8664
+ `${webhookEventsDir}/${schema.name}.ts`,
8665
+ renderExtract(schema.name, "form", schema.label, [`form.${kebabName}.submitted`], kebabName)
8666
+ )
8667
+ ]
8668
+ };
8669
+ }
8670
+
8795
8671
  // adapters/next/generators/form-pipeline/pipeline.ts
8796
8672
  function runFormPipeline(schema, cwd, config, options = {}) {
8797
8673
  const paths = resolveProjectPaths(config);
@@ -8831,6 +8707,10 @@ function runFormPipeline(schema, cwd, config, options = {}) {
8831
8707
  {
8832
8708
  name: "Navigation",
8833
8709
  run: () => updateFormNavigation(schema, paths.adminNavigationDir, namespacedOptions).files
8710
+ },
8711
+ {
8712
+ name: "Webhook events",
8713
+ run: () => updateFormWebhookEvents(schema, paths.adminWebhookEventsDir).files
8834
8714
  }
8835
8715
  ];
8836
8716
  for (let i = 0; i < steps.length; i++) {
@@ -9839,6 +9719,13 @@ function buildRestoreManyToManyStatements(ctx) {
9839
9719
  }
9840
9720
  function genRestoreContent(ctx) {
9841
9721
  const kebabSingular = toKebabCase(ctx.singular);
9722
+ const restoreContext = lifecycleContext(
9723
+ ctx.plural,
9724
+ `,
9725
+ entityId: versionRow.entityId,
9726
+ changedFields: [],
9727
+ metadata: { restoredFromVersion: versionRow.version }`
9728
+ );
9842
9729
  const snapshotImport = ctx.hasM2M ? `build${ctx.Singular}VersionSnapshotWithRelationships` : `build${ctx.Singular}VersionSnapshot`;
9843
9730
  const currentSnapshot = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(currentRow as unknown as ${ctx.Singular}, tx)` : `build${ctx.Singular}VersionSnapshot(currentRow as unknown as ${ctx.Singular})`;
9844
9731
  const dbImports = [
@@ -9864,6 +9751,7 @@ import {
9864
9751
  build${ctx.Singular}RestoreData,
9865
9752
  ${snapshotImport}
9866
9753
  } from './helpers'
9754
+ ${lifecycleHookImport()}
9867
9755
  import type { ${ctx.Singular}UpdateResult, ${ctx.Singular} } from './types'
9868
9756
 
9869
9757
  export async function restore${ctx.Singular}Version(
@@ -9885,6 +9773,9 @@ export async function restore${ctx.Singular}Version(
9885
9773
 
9886
9774
  const versionData = versionRow.data as Record<string, unknown>
9887
9775
  const restoreData = build${ctx.Singular}RestoreData(versionData)
9776
+ const auditRequestMetadata = await getAuditRequestMetadata()
9777
+ const lifecycleContext = ${restoreContext}
9778
+ ${beforeHook("beforeRestore", "lifecycleContext")}
9888
9779
 
9889
9780
  const result = await db.transaction(async (tx) => {
9890
9781
  const [currentRow] = await tx
@@ -9916,6 +9807,13 @@ ${restoreM2M}
9916
9807
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.byId(versionRow.entityId))
9917
9808
  ${ctx.cacheInvalidationFn}(entityVersionsCacheTags.for('${ctx.plural}', versionRow.entityId))
9918
9809
  const restored${ctx.Singular} = await get${ctx.Singular}ById(versionRow.entityId)
9810
+ ${afterHook(
9811
+ "afterRestore",
9812
+ `{
9813
+ ...lifecycleContext,
9814
+ data: (restored${ctx.Singular} ?? result[0]) as unknown as Record<string, unknown>
9815
+ }`
9816
+ )}
9919
9817
 
9920
9818
  return {
9921
9819
  success: true,
@@ -10128,14 +10026,20 @@ export async function delete${ctx.Singular}(id: string): Promise<${ctx.Singular}
10128
10026
  ${beforeHook("beforeDelete", "lifecycleContext")}
10129
10027
 
10130
10028
  try {
10131
- await db.transaction(async (tx) => {
10029
+ const [deletedRow] = await db.transaction(async (tx) => {
10132
10030
  await deleteEntityVersions(tx, '${ctx.plural}', [id])
10133
- await tx.delete(${ctx.tableVar}).where(eq(${ctx.tableVar}.id, id))
10031
+ return tx.delete(${ctx.tableVar}).where(eq(${ctx.tableVar}.id, id)).returning()
10134
10032
  })
10135
10033
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.all)
10136
10034
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.byId(id))
10137
10035
  ${ctx.cacheInvalidationFn}(entityVersionsCacheTags.for('${ctx.plural}', id))
10138
- ${afterHook("afterDelete", "lifecycleContext")}
10036
+ ${afterHook(
10037
+ "afterDelete",
10038
+ `{
10039
+ ...lifecycleContext,
10040
+ data: deletedRow as unknown as Record<string, unknown> | undefined
10041
+ }`
10042
+ )}
10139
10043
  return { success: true }
10140
10044
  } catch (error) {
10141
10045
  console.error('Error deleting ${ctx.singular}:', error)
@@ -10179,17 +10083,26 @@ export async function deleteBulk${ctx.Plural}(ids: string[]): Promise<${ctx.Sing
10179
10083
  }
10180
10084
 
10181
10085
  try {
10182
- await db.transaction(async (tx) => {
10086
+ const deletedRows = await db.transaction(async (tx) => {
10183
10087
  await deleteEntityVersions(tx, '${ctx.plural}', ids)
10184
- await tx.delete(${ctx.tableVar}).where(inArray(${ctx.tableVar}.id, ids))
10088
+ return tx.delete(${ctx.tableVar}).where(inArray(${ctx.tableVar}.id, ids)).returning()
10185
10089
  })
10090
+ const deletedRowsById = new Map(
10091
+ deletedRows.map((row) => [row.id, row as unknown as Record<string, unknown>])
10092
+ )
10186
10093
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.all)
10187
10094
  for (const id of ids) {
10188
10095
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.byId(id))
10189
10096
  ${ctx.cacheInvalidationFn}(entityVersionsCacheTags.for('${ctx.plural}', id))
10190
10097
  }
10191
10098
  for (const lifecycleContext of lifecycleContexts) {
10192
- ${afterHook("afterDelete", "lifecycleContext")}
10099
+ ${afterHook(
10100
+ "afterDelete",
10101
+ `{
10102
+ ...lifecycleContext,
10103
+ data: deletedRowsById.get(lifecycleContext.entityId)
10104
+ }`
10105
+ )}
10193
10106
  }
10194
10107
  return { success: true }
10195
10108
  } catch (error) {
@@ -10200,6 +10113,8 @@ export async function deleteBulk${ctx.Plural}(ids: string[]): Promise<${ctx.Sing
10200
10113
  `;
10201
10114
  }
10202
10115
  function genBulkSortOrderContent(ctx) {
10116
+ const reorderContext = lifecycleContext(ctx.plural, `,
10117
+ changedFields: ['sortOrder']`);
10203
10118
  return `'use server'
10204
10119
 
10205
10120
  import db from '@admin/db'
@@ -10208,15 +10123,20 @@ import { ${ctx.tableVar} } from '@admin/db/schema'
10208
10123
  import { asc, eq, notInArray } from 'drizzle-orm'
10209
10124
  import { ${ctx.cacheInvalidationFn} } from 'next/cache'
10210
10125
  import { ${ctx.camelPlural}CacheTags } from './types'
10126
+ ${lifecycleHookImport()}
10211
10127
 
10212
10128
  export async function bulkUpdate${ctx.Plural}SortOrder(
10213
10129
  updates: Array<{ id: string; sortOrder: number }>
10214
10130
  ): Promise<{ success: boolean; error?: string }> {
10215
- await requireRole([UserRole.ADMIN, UserRole.EDITOR])
10131
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
10132
+ if (updates.length === 0) return { success: true }
10216
10133
 
10217
- try {
10218
- if (updates.length === 0) return { success: true }
10134
+ const now = new Date().toISOString()
10135
+ const auditRequestMetadata = await getAuditRequestMetadata()
10136
+ const lifecycleContext = ${reorderContext}
10137
+ ${beforeHook("beforeReorder", "lifecycleContext")}
10219
10138
 
10139
+ try {
10220
10140
  // Sort the provided items by their new position
10221
10141
  const sorted = [...updates].sort((a, b) => a.sortOrder - b.sortOrder)
10222
10142
  const updatedIds = sorted.map((u) => u.id)
@@ -10260,13 +10180,18 @@ export async function bulkUpdate${ctx.Plural}SortOrder(
10260
10180
  }
10261
10181
 
10262
10182
  // Renumber all items gap-free
10183
+ const appliedOrder = allIds
10184
+ .filter(Boolean)
10185
+ .map((id, index) => ({ id, sortOrder: index }))
10186
+
10263
10187
  await Promise.all(
10264
- allIds.filter(Boolean).map((id, index) =>
10265
- db.update(${ctx.tableVar}).set({ sortOrder: index }).where(eq(${ctx.tableVar}.id, id))
10188
+ appliedOrder.map(({ id, sortOrder }) =>
10189
+ db.update(${ctx.tableVar}).set({ sortOrder }).where(eq(${ctx.tableVar}.id, id))
10266
10190
  )
10267
10191
  )
10268
10192
 
10269
10193
  ${ctx.cacheInvalidationFn}(${ctx.camelPlural}CacheTags.all)
10194
+ ${afterHook("afterReorder", "{ ...lifecycleContext, data: { order: appliedOrder } }")}
10270
10195
  return { success: true }
10271
10196
  } catch (error) {
10272
10197
  console.error('Error bulk updating sort order:', error)
@@ -11628,7 +11553,7 @@ import { getAuditRequestMetadata } from '@admin/actions/audit/request-metadata'
11628
11553
  import db from '@admin/db'
11629
11554
  import { ${upsertDbImports.join(", ")} } from '@admin/db/schema'
11630
11555
  import { eq } from 'drizzle-orm'
11631
- import { runAdminEventHooks } from '@admin/lib/lifecycle-hooks'
11556
+ import { runAfterHooks, runBeforeHooks } from '@admin/lib/lifecycle-hooks'
11632
11557
  import { getChangedFieldNames, getTouchedFieldNames } from '@admin/utils/audit/audit'
11633
11558
  import { ${cacheInvalidationFn} } from 'next/cache'
11634
11559
  import { get${Singular} } from './get-${kebabSingular}'
@@ -11680,6 +11605,22 @@ ${htmlUpsertBlock}
11680
11605
  ? getChangedFieldNames(previousRow as unknown as Record<string, unknown>, processedData)
11681
11606
  : getTouchedFieldNames(processedData)
11682
11607
 
11608
+ const lifecycleContext = {
11609
+ user: actor,
11610
+ entityType: '${schema.name}',
11611
+ schemaType: 'single' as const,
11612
+ auditCategory: '${auditCategory}' as const,
11613
+ timestamp: now,
11614
+ sourceType: 'admin_ui' as const,
11615
+ ipAddress: auditRequestMetadata.ipAddress,
11616
+ userAgent: auditRequestMetadata.userAgent,
11617
+ entityId: SINGLETON_ID,
11618
+ input: processedData,
11619
+ changedFields,
11620
+ targetLabel: ${JSON.stringify(schema.label)}
11621
+ }
11622
+ await runBeforeHooks(previousRow ? 'beforeUpdate' : 'beforeCreate', lifecycleContext)
11623
+
11683
11624
  const result = await db.transaction(async (tx) => {
11684
11625
  const rows = await tx
11685
11626
  .insert(${tableVar})
@@ -11704,18 +11645,9 @@ ${m2mWriteStatements}
11704
11645
 
11705
11646
  ${cacheInvalidationFn}(${cacheTagsVar}.all)
11706
11647
  const saved${Singular} = await get${Singular}()
11707
- runAdminEventHooks({
11708
- category: '${auditCategory}',
11709
- action: previousRow ? 'update' : 'create',
11710
- targetType: '${schema.name}',
11711
- targetId: SINGLETON_ID,
11712
- targetLabel: ${JSON.stringify(schema.label)},
11713
- user: actor,
11714
- timestamp: now,
11715
- sourceType: 'admin_ui' as const,
11716
- changedFields,
11717
- metadata: { schemaType: 'single' },
11718
- ...auditRequestMetadata
11648
+ runAfterHooks(previousRow ? 'afterUpdate' : 'afterCreate', {
11649
+ ...lifecycleContext,
11650
+ data: (saved${Singular} ?? result[0]) as unknown as Record<string, unknown>
11719
11651
  })
11720
11652
 
11721
11653
  return {
@@ -16574,7 +16506,7 @@ export default async function ${Plural}Page() {
16574
16506
  const skeletonContent = `export function ${Plural}PageSkeleton() {
16575
16507
  return (
16576
16508
  <div className="flex items-center justify-center h-48">
16577
- <div className="text-muted-foreground">Loading ${schema.label}...</div>
16509
+ <div className="text-muted-foreground">Loading ${schema.label}\u2026</div>
16578
16510
  </div>
16579
16511
  )
16580
16512
  }
@@ -16906,7 +16838,6 @@ function generateSinglePage(schema, pagesDir, options = {}) {
16906
16838
  const Singular = toPascalCase(singular);
16907
16839
  const PageName = toPascalCase(schema.name);
16908
16840
  const actionImportPath = resolveSchemaActionImportPath(schema.name, options.outputNamespace);
16909
- const adminRoutePath = options.adminRoutePath ?? "/admin";
16910
16841
  const devModeIntegration = buildSingleDevModeIntegration(
16911
16842
  schema,
16912
16843
  options.adminDir ?? "admin",
@@ -16914,7 +16845,6 @@ function generateSinglePage(schema, pagesDir, options = {}) {
16914
16845
  options.adminImportAlias
16915
16846
  );
16916
16847
  const settingsImport = schema.name === "settings" ? "" : "import { getSetting } from '@admin/actions/settings'\n";
16917
- const settingsAuditImports = schema.name === "settings" ? "import { Button } from '@admin/components/ui/button'\nimport { ScrollText } from 'lucide-react'\nimport Link from 'next/link'\n" : "";
16918
16848
  const dataLoad = schema.name === "settings" ? ` const data = await get${Singular}()
16919
16849
  ` : ` const [data, initialSettings] = await Promise.all([
16920
16850
  get${Singular}(),
@@ -16922,16 +16852,7 @@ function generateSinglePage(schema, pagesDir, options = {}) {
16922
16852
  ])
16923
16853
  `;
16924
16854
  const initialSettingsProp = schema.name === "settings" ? "initialSettings={data}" : "initialSettings={initialSettings}";
16925
- const actionsContent = schema.name === "settings" ? `<div className="flex items-center gap-2">
16926
- <Button asChild variant="outline" size="sm">
16927
- <Link href="${adminRoutePath}/system/audit-log">
16928
- <ScrollText />
16929
- Audit Log
16930
- </Link>
16931
- </Button>
16932
- <${devModeIntegration.buttonExportName} ${initialSettingsProp} />
16933
- </div>` : `<${devModeIntegration.buttonExportName} ${initialSettingsProp} />`;
16934
- const content = `${settingsImport}${settingsAuditImports}import { get${Singular} } from '@admin/actions/${actionImportPath}'
16855
+ const content = `${settingsImport}import { get${Singular} } from '@admin/actions/${actionImportPath}'
16935
16856
  import { PageHeader } from '@admin/components/shared/page-header'
16936
16857
  import { ${devModeIntegration.buttonExportName} } from '${devModeIntegration.buttonImportPath}'
16937
16858
  import { ${Singular}Form } from './${schema.name}-form'
@@ -16944,9 +16865,9 @@ ${dataLoad}
16944
16865
  <React.Fragment>
16945
16866
  <PageHeader
16946
16867
  title="${schema.label}"
16947
- actions={${actionsContent}}
16868
+ actions={<${devModeIntegration.buttonExportName} ${initialSettingsProp} />}
16948
16869
  />
16949
- <main className="container mx-auto max-w-5xl w-full p-4">
16870
+ <main className="mx-auto max-w-5xl w-full p-4">
16950
16871
  <${Singular}Form initialData={data} />
16951
16872
  </main>
16952
16873
  </React.Fragment>
@@ -17007,7 +16928,6 @@ import { GripVertical } from 'lucide-react'
17007
16928
  import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
17008
16929
  ${hasEdit ? "import { useRouter } from 'next/navigation'\n" : ""}import { use${Plural} } from '@admin/hooks/use-${schema.name}'
17009
16930
  import type { ${Singular} } from '@admin/actions/${actionImportPath}'
17010
- import { Card, CardContent } from '@admin/components/ui/card'
17011
16931
  import { useTableUtils } from '@admin/hooks/use-table-utils'
17012
16932
  import { cn } from '@admin/utils/shared/cn'
17013
16933
 
@@ -17113,110 +17033,106 @@ ${hasEdit ? " const router = useRouter()\n" : ""} const [{
17113
17033
  <div
17114
17034
  className={cn("flex flex-col gap-6 pt-px pb-2 h-full", className)}
17115
17035
  >
17116
- <Card className="min-h-0 flex-1 p-0">
17117
- <CardContent ref={tableContainerRef} className="flex min-h-0 flex-1 flex-col p-0">
17118
- <DataGrid containerClassName="min-h-0 flex-1" gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
17119
- <DataGridHeader>
17120
- {table.getHeaderGroups().map((headerGroup) => (
17121
- <DataGridRow key={headerGroup.id}>
17122
- {headerGroup.headers.map((header, headerIndex) => {
17123
- const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
17124
- const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
17125
- const nextHeader = headerGroup.headers[headerIndex + 1]
17126
- const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
17127
- return (
17128
- <DataGridHead
17129
- key={header.id}
17130
- className={cn('group/resize-header relative overflow-hidden text-foreground', {
17131
- 'text-center': meta?.align === 'center',
17132
- 'text-right': meta?.align === 'right'
17133
- })}
17134
- data-column-id={header.column.id}
17135
- onMouseEnter={() => setHoveredColumnId(header.column.id)}
17136
- onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
17137
- >
17138
- {header.isPlaceholder
17139
- ? null
17140
- : flexRender(header.column.columnDef.header, header.getContext())}
17141
- {canResizeColumn ? (
17142
- <button
17143
- type="button"
17144
- aria-label={\`Resize \${resizeLabel} column\`}
17145
- className="group/resize-handle absolute right-0 top-0 z-10 flex h-full w-5 cursor-col-resize touch-none select-none items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:text-foreground focus-visible:opacity-100 focus-visible:outline-none group-focus-within/resize-header:opacity-100 data-[column-hovered=true]:opacity-100 data-[resizing=true]:opacity-100"
17146
- data-resize-handle
17147
- data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
17148
- data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
17149
- onDoubleClick={resetColumnSizingToContainer}
17150
- onMouseDown={header.getResizeHandler()}
17151
- onTouchStart={header.getResizeHandler()}
17152
- >
17153
- <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
17154
- </button>
17155
- ) : null}
17156
- </DataGridHead>
17157
- )
17158
- })}
17159
- </DataGridRow>
17160
- ))}
17161
- </DataGridHeader>
17162
- <DataGridBody>
17163
- {isPending ? (
17164
- <DataGridRow>
17165
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17166
- <div className="text-muted-foreground">Loading ${schema.label}...</div>
17167
- </DataGridCell>
17168
- </DataGridRow>
17169
- ) : error ? (
17170
- <DataGridRow>
17171
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17172
- <div className="text-destructive">Error loading ${schema.label}: {error.message}</div>
17173
- </DataGridCell>
17174
- </DataGridRow>
17175
- ) : table.getRowModel().rows?.length ? (
17176
- table.getRowModel().rows.map((row) => (
17177
- <DataGridRow
17178
- key={row.id}
17179
- className={cn('hover:bg-muted/50', {
17180
- 'cursor-pointer': ${hasEdit ? "true" : "false"}
17036
+ <DataGrid containerClassName="min-h-0 flex-1" containerRef={tableContainerRef} gridTemplateColumns={gridTemplateColumns} style={{ width: table.getTotalSize() }}>
17037
+ <DataGridHeader>
17038
+ {table.getHeaderGroups().map((headerGroup) => (
17039
+ <DataGridRow key={headerGroup.id}>
17040
+ {headerGroup.headers.map((header, headerIndex) => {
17041
+ const meta = header.column.columnDef.meta as Record<string, unknown> | undefined
17042
+ const resizeLabel = typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : header.column.id
17043
+ const nextHeader = headerGroup.headers[headerIndex + 1]
17044
+ const canResizeColumn = header.column.getCanResize() && nextHeader?.column.getCanResize() === true
17045
+ return (
17046
+ <DataGridHead
17047
+ key={header.id}
17048
+ className={cn('group/resize-header relative overflow-hidden', {
17049
+ 'text-center': meta?.align === 'center',
17050
+ 'text-right': meta?.align === 'right'
17181
17051
  })}
17182
- data-state={row.getIsSelected() ? 'selected' : undefined}${hasEdit ? `
17183
- onClick={(e) => {
17184
- const target = e.target as HTMLElement
17185
- if (!e.currentTarget.contains(target)) return
17186
- if (target.closest('button, a, input, [role="checkbox"]')) return
17187
- router.push(\`${adminRoutePath}/${schema.name}/\${row.original.id}/edit\`)
17188
- }}` : ""}
17052
+ data-column-id={header.column.id}
17053
+ onMouseEnter={() => setHoveredColumnId(header.column.id)}
17054
+ onMouseLeave={() => setHoveredColumnId((current) => (current === header.column.id ? null : current))}
17189
17055
  >
17190
- {row.getVisibleCells().map((cell) => {
17191
- const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
17192
- return (
17193
- <DataGridCell
17194
- key={cell.id}
17195
- className={cn({
17196
- 'text-center': meta?.align === 'center',
17197
- 'text-right': meta?.align === 'right'
17198
- })}
17199
- data-column-id={cell.column.id}
17200
- onMouseEnter={() => setHoveredColumnId(cell.column.id)}
17201
- onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
17202
- >
17203
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
17204
- </DataGridCell>
17205
- )
17206
- })}
17207
- </DataGridRow>
17208
- ))
17209
- ) : (
17210
- <DataGridRow>
17211
- <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17212
- No ${schema.label} found.
17213
- </DataGridCell>
17214
- </DataGridRow>
17215
- )}
17216
- </DataGridBody>
17217
- </DataGrid>
17218
- </CardContent>
17219
- </Card>
17056
+ {header.isPlaceholder
17057
+ ? null
17058
+ : flexRender(header.column.columnDef.header, header.getContext())}
17059
+ {canResizeColumn ? (
17060
+ <button
17061
+ type="button"
17062
+ aria-label={\`Resize \${resizeLabel} column\`}
17063
+ className="group/resize-handle absolute right-0 top-0 z-10 flex h-full w-5 cursor-col-resize touch-none select-none items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:text-foreground focus-visible:opacity-100 focus-visible:outline-none group-focus-within/resize-header:opacity-100 data-[column-hovered=true]:opacity-100 data-[resizing=true]:opacity-100"
17064
+ data-resize-handle
17065
+ data-column-hovered={hoveredColumnId === header.column.id ? 'true' : 'false'}
17066
+ data-resizing={header.column.getIsResizing() ? 'true' : 'false'}
17067
+ onDoubleClick={resetColumnSizingToContainer}
17068
+ onMouseDown={header.getResizeHandler()}
17069
+ onTouchStart={header.getResizeHandler()}
17070
+ >
17071
+ <GripVertical aria-hidden="true" className={cn('size-3.5', { 'text-ring': header.column.getIsResizing() })} />
17072
+ </button>
17073
+ ) : null}
17074
+ </DataGridHead>
17075
+ )
17076
+ })}
17077
+ </DataGridRow>
17078
+ ))}
17079
+ </DataGridHeader>
17080
+ <DataGridBody>
17081
+ {isPending ? (
17082
+ <DataGridRow>
17083
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17084
+ <div className="text-muted-foreground">Loading ${schema.label}\u2026</div>
17085
+ </DataGridCell>
17086
+ </DataGridRow>
17087
+ ) : error ? (
17088
+ <DataGridRow>
17089
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17090
+ <div className="text-destructive">Error loading ${schema.label}: {error.message}</div>
17091
+ </DataGridCell>
17092
+ </DataGridRow>
17093
+ ) : table.getRowModel().rows?.length ? (
17094
+ table.getRowModel().rows.map((row) => (
17095
+ <DataGridRow
17096
+ key={row.id}
17097
+ className={cn('hover:bg-muted/50', {
17098
+ 'cursor-pointer': ${hasEdit ? "true" : "false"}
17099
+ })}
17100
+ data-state={row.getIsSelected() ? 'selected' : undefined}${hasEdit ? `
17101
+ onClick={(e) => {
17102
+ const target = e.target as HTMLElement
17103
+ if (!e.currentTarget.contains(target)) return
17104
+ if (target.closest('button, a, input, [role="checkbox"]')) return
17105
+ router.push(\`${adminRoutePath}/${schema.name}/\${row.original.id}/edit\`)
17106
+ }}` : ""}
17107
+ >
17108
+ {row.getVisibleCells().map((cell) => {
17109
+ const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
17110
+ return (
17111
+ <DataGridCell
17112
+ key={cell.id}
17113
+ className={cn({
17114
+ 'text-center': meta?.align === 'center',
17115
+ 'text-right': meta?.align === 'right'
17116
+ })}
17117
+ data-column-id={cell.column.id}
17118
+ onMouseEnter={() => setHoveredColumnId(cell.column.id)}
17119
+ onMouseLeave={() => setHoveredColumnId((current) => (current === cell.column.id ? null : current))}
17120
+ >
17121
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
17122
+ </DataGridCell>
17123
+ )
17124
+ })}
17125
+ </DataGridRow>
17126
+ ))
17127
+ ) : (
17128
+ <DataGridRow>
17129
+ <DataGridCell className="flex h-24 items-center justify-center text-center" style={{ gridColumn: '1 / -1' }}>
17130
+ No ${schema.label} found.
17131
+ </DataGridCell>
17132
+ </DataGridRow>
17133
+ )}
17134
+ </DataGridBody>
17135
+ </DataGrid>
17220
17136
 
17221
17137
  {shouldShowPagination ? (
17222
17138
  <DataTablePagination
@@ -17331,6 +17247,10 @@ function runEntityPipeline(schema, cwd, config, options = {}) {
17331
17247
  name: "Navigation",
17332
17248
  run: () => updateNavigation(normalizedSchema, paths.adminNavigationDir, namespacedOptions).files
17333
17249
  });
17250
+ steps.push({
17251
+ name: "Webhook events",
17252
+ run: () => updateWebhookEvents(normalizedSchema, paths.adminWebhookEventsDir, "entity").files
17253
+ });
17334
17254
  for (let i = 0; i < steps.length; i++) {
17335
17255
  const step = steps[i];
17336
17256
  const stepNum = i + 1;
@@ -17393,6 +17313,10 @@ function runSinglePipeline(schema, cwd, config, options = {}) {
17393
17313
  {
17394
17314
  name: "Navigation",
17395
17315
  run: () => updateNavigation(normalizedSchema, paths.adminNavigationDir, namespacedOptions).files
17316
+ },
17317
+ {
17318
+ name: "Webhook events",
17319
+ run: () => updateWebhookEvents(normalizedSchema, paths.adminWebhookEventsDir, "single").files
17396
17320
  }
17397
17321
  ];
17398
17322
  for (let i = 0; i < steps.length; i++) {
@@ -17968,18 +17892,41 @@ others.sort((a, b) => {
17968
17892
  export const ${namespace.camel}Navigation = [...(dashboard ? [dashboard] : []), ...others]
17969
17893
  `;
17970
17894
  }
17895
+ function renderWebhookEventsBarrel(extractNames, namespaceValue) {
17896
+ const namespace = resolveAdminNamespace(namespaceValue);
17897
+ const importLines = extractNames.map(
17898
+ (name) => `import { ${toCamelCase(name)}WebhookEvents } from './webhook-events/${name}'`
17899
+ );
17900
+ const entryLines = extractNames.map((name) => ` ${toCamelCase(name)}WebhookEvents`);
17901
+ const extractImports = importLines.length > 0 ? `
17902
+ ${importLines.join("\n")}` : "";
17903
+ const entries = entryLines.length > 0 ? `[
17904
+ ${entryLines.join(",\n")}
17905
+ ]` : "[]";
17906
+ return `import type { WebhookEventSource } from '${namespace.alias}/types/webhooks'${extractImports}
17907
+
17908
+ export type { WebhookEventSource } from '${namespace.alias}/types/webhooks'
17909
+
17910
+ export const webhookEventSources: WebhookEventSource[] = ${entries}
17911
+ `;
17912
+ }
17971
17913
  function renderBarrelFiles(cwd, config) {
17972
17914
  const paths = resolveProjectPaths(config);
17973
17915
  const dbExtracts = listNamespacedExtracts(cwd, paths.adminDbDir).filter(
17974
17916
  (extract) => !(extract.namespace === "core" && extract.name === "schema")
17975
17917
  );
17976
17918
  const navExtractNames = listExtractNames(cwd, paths.adminNavigationDir, /* @__PURE__ */ new Set(["core"]));
17919
+ const webhookEventExtractNames = listExtractNames(cwd, paths.adminWebhookEventsDir);
17977
17920
  return namespaceGeneratedFiles(
17978
17921
  [
17979
17922
  createGeneratedFile(paths.adminDbSchemaFile, renderDbBarrel(dbExtracts)),
17980
17923
  createGeneratedFile(
17981
17924
  paths.adminNavigationFile,
17982
17925
  renderNavigationBarrel(navExtractNames, config.frameworkConfig.next.namespace)
17926
+ ),
17927
+ createGeneratedFile(
17928
+ paths.adminWebhookEventsFile,
17929
+ renderWebhookEventsBarrel(webhookEventExtractNames, config.frameworkConfig.next.namespace)
17983
17930
  )
17984
17931
  ],
17985
17932
  config.frameworkConfig.next.namespace
@@ -21905,6 +21852,16 @@ function openBrowserVercelNeon() {
21905
21852
  `Opening Vercel... Create a Neon Postgres database, then copy the ${pc.cyan("DATABASE_URL")} from the dashboard.`
21906
21853
  );
21907
21854
  }
21855
+ function openBrowserVercelNeonResource(url) {
21856
+ if (!url) {
21857
+ openBrowserVercelNeon();
21858
+ return;
21859
+ }
21860
+ openBrowser(url);
21861
+ p10.log.info(
21862
+ `Opening Vercel... Copy the Neon ${pc.cyan("DATABASE_URL")} from the database dashboard, then paste it below.`
21863
+ );
21864
+ }
21908
21865
  async function promptConnectionString() {
21909
21866
  const input = await p10.text({
21910
21867
  message: "Paste your PostgreSQL connection string",
@@ -22337,6 +22294,10 @@ function scaffoldComponents({ cwd, config }) {
22337
22294
  "components/layouts/admin-sidebar-nav-link-skeleton.tsx",
22338
22295
  readTemplate("components/layouts/admin-sidebar-nav-link-skeleton.tsx")
22339
22296
  );
22297
+ write(
22298
+ "components/layouts/admin-settings-sidebar.tsx",
22299
+ readTemplate("components/layouts/admin-settings-sidebar.tsx")
22300
+ );
22340
22301
  write(
22341
22302
  "components/layouts/admin-sidebar-user-menu.tsx",
22342
22303
  readTemplate("components/layouts/admin-sidebar-user-menu.tsx")
@@ -22501,6 +22462,7 @@ function scaffoldComponents({ cwd, config }) {
22501
22462
  write("types/auth.ts", readTemplate("types/auth.ts"));
22502
22463
  write("types/navigation.ts", readTemplate("types/navigation.ts"));
22503
22464
  write("types/table-meta.ts", readTemplate("types/table-meta.ts"));
22465
+ write("types/webhooks.ts", readTemplate("types/webhooks.ts"));
22504
22466
  for (const relPath of [
22505
22467
  "utils/auth/roles.ts",
22506
22468
  "utils/audit/audit.ts",
@@ -22536,7 +22498,7 @@ function scaffoldComponents({ cwd, config }) {
22536
22498
  "utils/upload/upload.ts",
22537
22499
  "utils/user/user.ts",
22538
22500
  "utils/validation/validation.ts",
22539
- "utils/webhook/webhook.ts"
22501
+ "utils/webhook/signature.ts"
22540
22502
  ]) {
22541
22503
  write(relPath, readTemplate(relPath));
22542
22504
  }
@@ -22618,6 +22580,7 @@ function scaffoldComponents({ cwd, config }) {
22618
22580
  write("hooks/use-admin-theme.tsx", readTemplate("hooks/use-admin-theme.tsx"));
22619
22581
  write("hooks/use-audit-log.ts", readTemplate("hooks/use-audit-log.ts"));
22620
22582
  write("hooks/use-users.ts", readTemplate("hooks/use-users.ts"));
22583
+ write("hooks/use-webhooks.ts", readTemplate("hooks/use-webhooks.ts"));
22621
22584
  write("hooks/use-mobile.ts", readTemplate("hooks/use-mobile.ts"));
22622
22585
  write("hooks/use-failed-image-url.ts", readTemplate("hooks/use-failed-image-url.ts"));
22623
22586
  write("hooks/use-media.ts", readTemplate("hooks/use-media.ts"));
@@ -22632,14 +22595,28 @@ function scaffoldComponents({ cwd, config }) {
22632
22595
  const adminNamespace = resolveAdminNamespace(namespace);
22633
22596
  const projectName = detectProjectName(cwd);
22634
22597
  write("data/navigation.ts", readTemplate("data/navigation.ts"));
22598
+ write("data/webhook-events.ts", readTemplate("data/webhook-events.ts"));
22635
22599
  write(`utils/app/${adminNamespace.camel}.ts`, adminDataTemplate(projectName, namespace));
22636
22600
  for (const relPath of [
22637
22601
  "lib/actions/forms/types.ts",
22638
22602
  "lib/actions/forms/get-form-settings.ts",
22639
22603
  "lib/actions/forms/upsert-form-settings.ts",
22640
22604
  "lib/actions/forms/get-all-form-settings.ts",
22641
- "lib/actions/forms/test-form-webhook.ts",
22642
22605
  "lib/actions/forms/index.ts",
22606
+ "lib/actions/webhooks/types.ts",
22607
+ "lib/actions/webhooks/internal-get-active-endpoints.ts",
22608
+ "lib/actions/webhooks/dispatch.ts",
22609
+ "lib/actions/webhooks/get-webhooks.ts",
22610
+ "lib/actions/webhooks/create-webhook.ts",
22611
+ "lib/actions/webhooks/update-webhook.ts",
22612
+ "lib/actions/webhooks/delete-webhook.ts",
22613
+ "lib/actions/webhooks/get-webhook-secret.ts",
22614
+ "lib/actions/webhooks/regenerate-webhook-secret.ts",
22615
+ "lib/actions/webhooks/get-webhook-subscriptions.ts",
22616
+ "lib/actions/webhooks/send-test-webhook.ts",
22617
+ "lib/actions/webhooks/get-webhook-deliveries.ts",
22618
+ "lib/actions/webhooks/register.ts",
22619
+ "lib/actions/webhooks/index.ts",
22643
22620
  "lib/actions/entity-versions/types.ts",
22644
22621
  "lib/actions/entity-versions/get-entity-versions.ts",
22645
22622
  "lib/actions/entity-versions/internal-create-entity-version.ts",
@@ -22824,41 +22801,27 @@ function defaultSettingsSchema() {
22824
22801
  type: "section",
22825
22802
  name: "siteSettings",
22826
22803
  label: "Dashboard Settings",
22827
- description: "General settings for the dashboard",
22828
22804
  fields: [
22829
22805
  {
22830
22806
  name: "title",
22831
22807
  type: "string",
22832
- label: "Name",
22808
+ label: "Application Name",
22833
22809
  hint: "Displayed in the sidebar and throughout the dashboard",
22834
22810
  default: "BetterStart"
22835
22811
  },
22836
- {
22837
- name: "dashboardDescription",
22838
- type: "text",
22839
- label: "Description",
22840
- hint: "A brief description of the application"
22841
- }
22812
+ { name: "dashboardLogo", type: "image", label: "Logo" }
22842
22813
  ]
22843
22814
  },
22844
- {
22845
- type: "section",
22846
- name: "branding",
22847
- label: "Branding",
22848
- description: "Logo and visual identity",
22849
- fields: [{ name: "dashboardLogo", type: "image", label: "Logo" }]
22850
- },
22851
22815
  {
22852
22816
  type: "section",
22853
22817
  name: "developer",
22854
- label: "Developer",
22855
- description: "Developer tools for the dashboard",
22818
+ label: "Developers",
22856
22819
  fields: [
22857
22820
  {
22858
22821
  name: "devMode",
22859
22822
  type: "boolean",
22860
22823
  label: "Dev Mode",
22861
- hint: "Show generated integration snippets and schema details in the dashboard",
22824
+ hint: "Show generated integration snippets and schema details for developers",
22862
22825
  default: true
22863
22826
  }
22864
22827
  ]
@@ -22867,7 +22830,6 @@ function defaultSettingsSchema() {
22867
22830
  type: "section",
22868
22831
  name: "auditLog",
22869
22832
  label: "Audit Log",
22870
- description: "Audit retention controls",
22871
22833
  fields: [
22872
22834
  {
22873
22835
  name: "auditRetentionEnabled",
@@ -23188,6 +23150,78 @@ function scaffoldLayout({ cwd, config }) {
23188
23150
  path41.join(usersDir, "change-password-dialog.tsx"),
23189
23151
  readTemplate("pages/users/change-password-dialog.tsx")
23190
23152
  );
23153
+ const settingsDir = path41.join(config.paths.pages, "settings");
23154
+ write(path41.join(settingsDir, "layout.tsx"), readTemplate("pages/settings/settings-layout.tsx"));
23155
+ const settingsFormsDir = path41.join(settingsDir, "forms");
23156
+ write(
23157
+ path41.join(settingsFormsDir, "page.tsx"),
23158
+ readTemplate("pages/settings/forms/forms-settings-page.tsx")
23159
+ );
23160
+ write(
23161
+ path41.join(settingsFormsDir, "forms-settings-page-skeleton.tsx"),
23162
+ readTemplate("pages/settings/forms/forms-settings-page-skeleton.tsx")
23163
+ );
23164
+ write(
23165
+ path41.join(settingsFormsDir, "forms-settings-page-content.tsx"),
23166
+ readTemplate("pages/settings/forms/forms-settings-page-content.tsx")
23167
+ );
23168
+ write(
23169
+ path41.join(settingsFormsDir, "edit-form-notifications-dialog.tsx"),
23170
+ readTemplate("pages/settings/forms/edit-form-notifications-dialog.tsx")
23171
+ );
23172
+ const settingsWebhooksDir = path41.join(settingsDir, "webhooks");
23173
+ write(
23174
+ path41.join(settingsWebhooksDir, "page.tsx"),
23175
+ readTemplate("pages/settings/webhooks/webhooks-page.tsx")
23176
+ );
23177
+ write(
23178
+ path41.join(settingsWebhooksDir, "webhooks-page-skeleton.tsx"),
23179
+ readTemplate("pages/settings/webhooks/webhooks-page-skeleton.tsx")
23180
+ );
23181
+ write(
23182
+ path41.join(settingsWebhooksDir, "webhooks-page-content.tsx"),
23183
+ readTemplate("pages/settings/webhooks/webhooks-page-content.tsx")
23184
+ );
23185
+ write(
23186
+ path41.join(settingsWebhooksDir, "webhooks-table.tsx"),
23187
+ readTemplate("pages/settings/webhooks/webhooks-table.tsx")
23188
+ );
23189
+ write(
23190
+ path41.join(settingsWebhooksDir, "webhooks-columns.tsx"),
23191
+ readTemplate("pages/settings/webhooks/webhooks-columns.tsx")
23192
+ );
23193
+ write(
23194
+ path41.join(settingsWebhooksDir, "webhook-actions.tsx"),
23195
+ readTemplate("pages/settings/webhooks/webhook-actions.tsx")
23196
+ );
23197
+ write(
23198
+ path41.join(settingsWebhooksDir, "webhook-enabled-switch.tsx"),
23199
+ readTemplate("pages/settings/webhooks/webhook-enabled-switch.tsx")
23200
+ );
23201
+ write(
23202
+ path41.join(settingsWebhooksDir, "webhook-endpoint-dialog.tsx"),
23203
+ readTemplate("pages/settings/webhooks/webhook-endpoint-dialog.tsx")
23204
+ );
23205
+ write(
23206
+ path41.join(settingsWebhooksDir, "delete-webhook-dialog.tsx"),
23207
+ readTemplate("pages/settings/webhooks/delete-webhook-dialog.tsx")
23208
+ );
23209
+ write(
23210
+ path41.join(settingsWebhooksDir, "webhooks-logs-page-content.tsx"),
23211
+ readTemplate("pages/settings/webhooks/webhooks-logs-page-content.tsx")
23212
+ );
23213
+ write(
23214
+ path41.join(settingsWebhooksDir, "webhooks-logs-table.tsx"),
23215
+ readTemplate("pages/settings/webhooks/webhooks-logs-table.tsx")
23216
+ );
23217
+ write(
23218
+ path41.join(settingsWebhooksDir, "webhooks-logs-columns.tsx"),
23219
+ readTemplate("pages/settings/webhooks/webhooks-logs-columns.tsx")
23220
+ );
23221
+ write(
23222
+ path41.join(settingsWebhooksDir, "logs", "page.tsx"),
23223
+ readTemplate("pages/settings/webhooks/webhooks-logs-page.tsx")
23224
+ );
23191
23225
  const mediaDir = path41.join(config.paths.pages, "media");
23192
23226
  write(path41.join(mediaDir, "page.tsx"), readTemplate("pages/media/media-page.tsx"));
23193
23227
  write(
@@ -23198,23 +23232,34 @@ function scaffoldLayout({ cwd, config }) {
23198
23232
  path41.join(mediaDir, "media-page-content.tsx"),
23199
23233
  readTemplate("pages/media/media-page-content.tsx")
23200
23234
  );
23201
- const auditLogDir = path41.join(config.paths.pages, "system", "audit-log");
23202
- write(path41.join(auditLogDir, "page.tsx"), readTemplate("pages/audit-log/audit-log-page.tsx"));
23235
+ const auditLogDir = path41.join(config.paths.pages, "settings", "audit-log");
23236
+ write(
23237
+ path41.join(auditLogDir, "page.tsx"),
23238
+ readTemplate("pages/settings/audit-log/audit-log-page.tsx")
23239
+ );
23203
23240
  write(
23204
23241
  path41.join(auditLogDir, "audit-log-page-skeleton.tsx"),
23205
- readTemplate("pages/audit-log/audit-log-page-skeleton.tsx")
23242
+ readTemplate("pages/settings/audit-log/audit-log-page-skeleton.tsx")
23206
23243
  );
23207
23244
  write(
23208
23245
  path41.join(auditLogDir, "audit-log-page-content.tsx"),
23209
- readTemplate("pages/audit-log/audit-log-page-content.tsx")
23246
+ readTemplate("pages/settings/audit-log/audit-log-page-content.tsx")
23247
+ );
23248
+ write(
23249
+ path41.join(auditLogDir, "audit-log-table.tsx"),
23250
+ readTemplate("pages/settings/audit-log/audit-log-table.tsx")
23251
+ );
23252
+ write(
23253
+ path41.join(auditLogDir, "audit-log-columns.tsx"),
23254
+ readTemplate("pages/settings/audit-log/audit-log-columns.tsx")
23210
23255
  );
23211
23256
  write(
23212
23257
  path41.join(auditLogDir, "audit-log-detail-drawer.tsx"),
23213
- readTemplate("pages/audit-log/audit-log-detail-drawer.tsx")
23258
+ readTemplate("pages/settings/audit-log/audit-log-detail-drawer.tsx")
23214
23259
  );
23215
23260
  write(
23216
23261
  path41.join(auditLogDir, "audit-log-detail-row.tsx"),
23217
- readTemplate("pages/audit-log/audit-log-detail-row.tsx")
23262
+ readTemplate("pages/settings/audit-log/audit-log-detail-row.tsx")
23218
23263
  );
23219
23264
  const accountDir = path41.join(adminDir, "(account)");
23220
23265
  write(path41.join(accountDir, "layout.tsx"), readTemplate("pages/account-layout.tsx"));
@@ -24620,25 +24665,17 @@ import pc5 from "picocolors";
24620
24665
  var PROVISION_TIMEOUT_MS2 = 18e4;
24621
24666
  var INTERACTIVE_PROVISION_TIMEOUT_MS2 = 6e5;
24622
24667
  async function provisionNeonInteractive(runner, cwd, options) {
24623
- const addArgs = ["integration", "add", "neon", "--no-env-pull"];
24624
- if (options.plan) addArgs.push("--plan", options.plan);
24625
24668
  const quietSpinner = spinner2();
24626
24669
  quietSpinner.start("Creating your Neon database");
24627
- const quiet = await runVercel(runner, addArgs, {
24670
+ const quiet = await runVercel(runner, neonAddArgs(options), {
24628
24671
  cwd,
24629
24672
  mode: "capture",
24630
24673
  timeoutMs: PROVISION_TIMEOUT_MS2,
24631
24674
  env: options.env
24632
24675
  });
24633
24676
  if (quiet.success) {
24634
- quietSpinner.message("Retrieving the database connection string");
24635
- const databaseUrl2 = await pullDatabaseUrl(runner, cwd, options.env);
24636
- if (!databaseUrl2) {
24637
- quietSpinner.stop("No DATABASE_URL came back from Vercel");
24638
- return { failure: "env-pull-empty" };
24639
- }
24640
24677
  quietSpinner.clear();
24641
- return { databaseUrl: databaseUrl2 };
24678
+ return readNeonProvisionInfo(quiet.stdout);
24642
24679
  }
24643
24680
  quietSpinner.clear();
24644
24681
  p15.log.info(
@@ -24646,7 +24683,7 @@ async function provisionNeonInteractive(runner, cwd, options) {
24646
24683
  "(the Free plan is recommended)."
24647
24684
  )}`
24648
24685
  );
24649
- const add = await runVercel(runner, addArgs, {
24686
+ const add = await runVercel(runner, connectedNeonAddArgs(options), {
24650
24687
  cwd,
24651
24688
  mode: "inherit",
24652
24689
  timeoutMs: INTERACTIVE_PROVISION_TIMEOUT_MS2,
@@ -24656,21 +24693,19 @@ async function provisionNeonInteractive(runner, cwd, options) {
24656
24693
  return { failure: add.timedOut ? "timeout" : "provision-failed" };
24657
24694
  }
24658
24695
  const pullSpinner = spinner2();
24659
- pullSpinner.start("Retrieving the database connection string");
24660
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24696
+ pullSpinner.start("Retrieving DATABASE_URL from Vercel");
24697
+ const databaseUrl = await pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, options.env);
24661
24698
  if (!databaseUrl) {
24662
- pullSpinner.stop("No DATABASE_URL came back from Vercel");
24663
- return { failure: "env-pull-empty" };
24699
+ pullSpinner.stop(`${pc5.yellow("\u25B2")} No DATABASE_URL came back from Vercel`);
24700
+ return {};
24664
24701
  }
24665
24702
  pullSpinner.clear();
24666
24703
  return { databaseUrl };
24667
24704
  }
24668
24705
  async function provisionNeon(runner, cwd, options) {
24669
- const addArgs = ["integration", "add", "neon", "--no-env-pull", "--format", "json"];
24670
- if (options.plan) addArgs.push("--plan", options.plan);
24671
24706
  const provisionSpinner = spinner2();
24672
24707
  provisionSpinner.start("Creating your Neon database");
24673
- const add = await runVercel(runner, addArgs, {
24708
+ const add = await runVercel(runner, neonAddArgs(options), {
24674
24709
  cwd,
24675
24710
  mode: "capture",
24676
24711
  timeoutMs: PROVISION_TIMEOUT_MS2,
@@ -24685,33 +24720,49 @@ ${add.stderr}`.trim();
24685
24720
  if (/claim/i.test(combined)) return { failure: "claim-required", detail };
24686
24721
  return { failure: add.timedOut ? "timeout" : "provision-failed", detail };
24687
24722
  }
24688
- const meta = parseVercelJson(add.stdout);
24689
- const resourceName = meta?.resourceName ?? meta?.name;
24690
- provisionSpinner.message("Retrieving the database connection string");
24691
- const databaseUrl = await pullDatabaseUrl(runner, cwd, options.env);
24692
- if (!databaseUrl) {
24693
- provisionSpinner.stop("No DATABASE_URL came back from Vercel");
24694
- return { resourceName, failure: "env-pull-empty" };
24695
- }
24696
24723
  provisionSpinner.clear();
24697
- return { databaseUrl, resourceName };
24724
+ return readNeonProvisionInfo(add.stdout);
24698
24725
  }
24699
- function pullDatabaseUrl(runner, cwd, env) {
24700
- return pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24726
+ async function connectNeonResourceToProject(runner, cwd, resourceName, env) {
24727
+ const connect = await runVercel(
24728
+ runner,
24729
+ ["integration", "resource", "connect", resourceName, "--yes", "--format", "json"],
24730
+ { cwd, mode: "capture", timeoutMs: PROVISION_TIMEOUT_MS2, env }
24731
+ );
24732
+ if (connect.success) return { ok: true };
24733
+ const detail = `${connect.stdout}
24734
+ ${connect.stderr}`.trim();
24735
+ if (/already connected/i.test(detail)) {
24736
+ return { ok: true, alreadyConnected: true, detail: detail || void 0 };
24737
+ }
24738
+ return { ok: false, detail: detail || void 0 };
24701
24739
  }
24702
24740
  function readDbUrlFromDotenv(filePath) {
24703
24741
  const vars = parseDotenvFile(filePath);
24704
24742
  const isPg = (v) => !!v && (v.startsWith("postgres://") || v.startsWith("postgresql://"));
24705
24743
  if (isPg(vars.get("DATABASE_URL"))) return vars.get("DATABASE_URL");
24706
24744
  if (isPg(vars.get("POSTGRES_URL"))) return vars.get("POSTGRES_URL");
24707
- for (const [key, value] of vars) {
24708
- if (/DATABASE_URL$/.test(key) && isPg(value)) return value;
24709
- }
24710
- for (const value of vars.values()) {
24711
- if (isPg(value)) return value;
24712
- }
24713
24745
  return void 0;
24714
24746
  }
24747
+ function readNeonProvisionInfo(stdout) {
24748
+ const meta = parseVercelJson(stdout);
24749
+ if (!meta) return {};
24750
+ return {
24751
+ resourceName: meta.resource?.name,
24752
+ dashboardUrl: meta.dashboardUrl,
24753
+ resourceUrl: meta.ssoUrl?.resource
24754
+ };
24755
+ }
24756
+ function neonAddArgs(options) {
24757
+ const addArgs = ["integration", "add", "neon", "--no-connect", "--format", "json"];
24758
+ if (options.plan) addArgs.push("--plan", options.plan);
24759
+ return addArgs;
24760
+ }
24761
+ function connectedNeonAddArgs(options) {
24762
+ const addArgs = ["integration", "add", "neon"];
24763
+ if (options.plan) addArgs.push("--plan", options.plan);
24764
+ return addArgs;
24765
+ }
24715
24766
 
24716
24767
  // adapters/next/init/vercel/flow.ts
24717
24768
  async function runVercelNeonFlow(options) {
@@ -24727,23 +24778,29 @@ async function runVercelNeonFlow(options) {
24727
24778
  p16.log.warn(authFailureMessage(auth.reason));
24728
24779
  return { ok: false };
24729
24780
  }
24730
- const projectSpinner = spinner2();
24731
- projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24732
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24733
- projectSpinner.stop(
24734
- project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24735
- );
24781
+ await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24736
24782
  const neon = await provisionNeonForMode(runner, options);
24737
- if (neon.failure || !neon.databaseUrl) {
24783
+ if (neon.failure) {
24738
24784
  p16.log.warn(neonFailureMessage(neon.failure));
24739
24785
  if (neon.detail) p16.log.message(pc6.dim(neon.detail));
24740
24786
  return { ok: false };
24741
24787
  }
24788
+ let databaseUrl = neon.databaseUrl;
24789
+ if (neon.resourceName) {
24790
+ const pulledDatabaseUrl = await connectNeonAndPullDatabaseUrl(
24791
+ runner,
24792
+ options.cwd,
24793
+ neon.resourceName,
24794
+ env
24795
+ );
24796
+ databaseUrl = pulledDatabaseUrl ?? databaseUrl;
24797
+ }
24742
24798
  return {
24743
24799
  ok: true,
24744
- databaseUrl: neon.databaseUrl,
24745
- vercelProjectId: project2.projectId,
24746
- neonResourceName: neon.resourceName
24800
+ databaseUrl,
24801
+ neonResourceName: neon.resourceName,
24802
+ dashboardUrl: neon.dashboardUrl,
24803
+ resourceUrl: neon.resourceUrl
24747
24804
  };
24748
24805
  } catch (error) {
24749
24806
  p16.log.warn(
@@ -24767,16 +24824,7 @@ async function runVercelBlobFlow(options) {
24767
24824
  p16.log.warn(authFailureMessage(auth.reason));
24768
24825
  return { ok: false };
24769
24826
  }
24770
- let projectId = readLinkedProjectId(options.cwd);
24771
- if (!projectId) {
24772
- const projectSpinner = spinner2();
24773
- projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24774
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24775
- projectSpinner.stop(
24776
- project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24777
- );
24778
- projectId = project2.projectId;
24779
- }
24827
+ await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24780
24828
  const blob = await provisionBlobForMode(runner, options);
24781
24829
  if (blob.failure || !blob.token) {
24782
24830
  p16.log.warn(blobFailureMessage(blob.failure));
@@ -24786,7 +24834,6 @@ async function runVercelBlobFlow(options) {
24786
24834
  return {
24787
24835
  ok: true,
24788
24836
  token: blob.token,
24789
- vercelProjectId: projectId,
24790
24837
  blobStoreName: blob.storeName
24791
24838
  };
24792
24839
  } catch (error) {
@@ -24812,13 +24859,24 @@ async function runVercelDeployFlow(options) {
24812
24859
  printManualDeployHint();
24813
24860
  return { ok: false };
24814
24861
  }
24815
- if (!readLinkedProjectId(options.cwd)) {
24816
- const projectSpinner = spinner2();
24817
- projectSpinner.start(`Creating a Vercel project ${pc6.cyan(options.projectName)}`);
24818
- const project2 = await createVercelProject(runner, options.cwd, options.projectName, env);
24819
- projectSpinner.stop(
24820
- project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24862
+ await ensureLinkedProject(runner, options.cwd, options.projectName, env);
24863
+ if (options.neonResourceName) {
24864
+ const neonSpinner = spinner2();
24865
+ neonSpinner.start("Connecting Neon database to Vercel project");
24866
+ const connect = await connectNeonResourceToProject(
24867
+ runner,
24868
+ options.cwd,
24869
+ options.neonResourceName,
24870
+ env
24821
24871
  );
24872
+ if (connect.ok) {
24873
+ neonSpinner.stop(
24874
+ connect.alreadyConnected ? "Neon database already connected to Vercel project" : "Connected Neon database to Vercel project"
24875
+ );
24876
+ } else {
24877
+ neonSpinner.stop(`${pc6.yellow("\u25B2")} Could not connect Neon database to Vercel project`);
24878
+ if (connect.detail) p16.log.message(pc6.dim(connect.detail));
24879
+ }
24822
24880
  }
24823
24881
  const envSpinner = spinner2();
24824
24882
  envSpinner.start("Syncing environment variables to Vercel");
@@ -24873,6 +24931,33 @@ async function runVercelDeployFlow(options) {
24873
24931
  function printManualDeployHint() {
24874
24932
  p16.log.info(`You can deploy manually: ${pc6.cyan("vercel deploy --prod")}`);
24875
24933
  }
24934
+ async function ensureLinkedProject(runner, cwd, projectName, env) {
24935
+ if (readLinkedProjectId(cwd)) return;
24936
+ const projectSpinner = spinner2();
24937
+ projectSpinner.start(`Creating a Vercel project ${pc6.cyan(projectName)}`);
24938
+ const project2 = await createVercelProject(runner, cwd, projectName, env);
24939
+ projectSpinner.stop(
24940
+ project2.linked ? `Linked Vercel project ${pc6.cyan(project2.name)}` : `Using Vercel project ${pc6.cyan(project2.name)}`
24941
+ );
24942
+ }
24943
+ async function connectNeonAndPullDatabaseUrl(runner, cwd, resourceName, env) {
24944
+ const neonSpinner = spinner2();
24945
+ neonSpinner.start("Connecting Neon database to Vercel project");
24946
+ const connect = await connectNeonResourceToProject(runner, cwd, resourceName, env);
24947
+ if (!connect.ok) {
24948
+ neonSpinner.stop(`${pc6.yellow("\u25B2")} Could not connect Neon database to Vercel project`);
24949
+ if (connect.detail) p16.log.message(pc6.dim(connect.detail));
24950
+ return void 0;
24951
+ }
24952
+ neonSpinner.message("Retrieving DATABASE_URL from Vercel");
24953
+ const databaseUrl = await pullVercelEnvValue(runner, cwd, readDbUrlFromDotenv, env);
24954
+ if (!databaseUrl) {
24955
+ neonSpinner.stop(`${pc6.yellow("\u25B2")} No DATABASE_URL came back from Vercel`);
24956
+ return void 0;
24957
+ }
24958
+ neonSpinner.clear();
24959
+ return databaseUrl;
24960
+ }
24876
24961
  function provisionBlobForMode(runner, options) {
24877
24962
  const opts = { name: blobStoreName(options.projectName), env: options.env };
24878
24963
  return options.interactive ? provisionBlobStoreInteractive(runner, options.cwd, opts) : provisionBlobStore(runner, options.cwd, opts);
@@ -24919,8 +25004,6 @@ function neonFailureMessage(reason) {
24919
25004
  return "Provisioning needs the Neon marketplace terms accepted \u2014 falling back to manual entry.";
24920
25005
  case "claim-required":
24921
25006
  return "The Neon resource needs to be claimed \u2014 falling back to manual entry.";
24922
- case "env-pull-empty":
24923
- return "Provisioned Neon, but no DATABASE_URL came back \u2014 falling back to manual entry.";
24924
25007
  case "timeout":
24925
25008
  return "Neon provisioning timed out \u2014 falling back to manual entry.";
24926
25009
  default:
@@ -25038,10 +25121,15 @@ async function main() {
25038
25121
  process.exit(0)
25039
25122
  }
25040
25123
 
25041
- const EMAIL = process.env.SEED_EMAIL!
25042
- const PASSWORD = process.env.SEED_PASSWORD!
25124
+ const EMAIL = process.env.SEED_EMAIL
25125
+ const PASSWORD = process.env.SEED_PASSWORD
25043
25126
  const NAME = process.env.SEED_NAME || 'Admin'
25044
25127
 
25128
+ if (!EMAIL || !PASSWORD) {
25129
+ console.error(' SEED_EMAIL and SEED_PASSWORD are required.')
25130
+ process.exit(1)
25131
+ }
25132
+
25045
25133
  // Check if a user already exists with the requested email
25046
25134
  const existingUser = await db
25047
25135
  .select({ id: schema.user.id, name: schema.user.name })
@@ -25221,7 +25309,7 @@ async function runSeedCommand(options) {
25221
25309
  }
25222
25310
 
25223
25311
  // adapters/next/commands/init.ts
25224
- var ANSI_ESCAPE_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/g;
25312
+ var ANSI_ESCAPE_PATTERN2 = /\u001B\[[0-?]*[ -/]*[@-~]/g;
25225
25313
  var REMOVABLE_NAMESPACE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
25226
25314
  function normalizeNamespaceForRemoval(value) {
25227
25315
  if (typeof value !== "string") {
@@ -25493,6 +25581,7 @@ async function runInitCommand(name, options) {
25493
25581
  isFreshProject = true;
25494
25582
  }
25495
25583
  let databaseUrl;
25584
+ let neonResourceName;
25496
25585
  const existingDbUrl = readExistingDbUrl(cwd);
25497
25586
  if (options.yes) {
25498
25587
  if (options.databaseUrl) {
@@ -25514,9 +25603,23 @@ async function runInitCommand(name, options) {
25514
25603
  env: process.env
25515
25604
  });
25516
25605
  if (flow.ok && flow.databaseUrl) {
25606
+ neonResourceName = flow.neonResourceName;
25517
25607
  databaseUrl = flow.databaseUrl;
25518
25608
  persistDatabaseUrl(cwd, databaseUrl);
25519
25609
  p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25610
+ } else if (flow.ok) {
25611
+ neonResourceName = flow.neonResourceName;
25612
+ p17.log.warn("Created a Neon database, but DATABASE_URL could not be retrieved from Vercel.");
25613
+ const resourceUrl = flow.resourceUrl ?? flow.dashboardUrl;
25614
+ if (resourceUrl) {
25615
+ p17.log.info(
25616
+ `Open ${pc7.cyan(resourceUrl)} to copy DATABASE_URL, then rerun ${pc7.cyan("betterstart init --database-url <url> --yes")}.`
25617
+ );
25618
+ } else {
25619
+ p17.log.info(
25620
+ `Rerun ${pc7.cyan("betterstart init --database-url <url> --yes")} with a Postgres connection string.`
25621
+ );
25622
+ }
25520
25623
  } else {
25521
25624
  p17.log.warn("Vercel provisioning did not complete; continuing without a database URL.");
25522
25625
  }
@@ -25536,9 +25639,16 @@ async function runInitCommand(name, options) {
25536
25639
  env: process.env
25537
25640
  });
25538
25641
  if (flow.ok && flow.databaseUrl) {
25642
+ neonResourceName = flow.neonResourceName;
25539
25643
  databaseUrl = flow.databaseUrl;
25540
25644
  persistDatabaseUrl(cwd, databaseUrl);
25541
25645
  p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25646
+ } else if (flow.ok) {
25647
+ neonResourceName = flow.neonResourceName;
25648
+ openBrowserVercelNeonResource(flow.resourceUrl ?? flow.dashboardUrl);
25649
+ databaseUrl = await promptConnectionString();
25650
+ persistDatabaseUrl(cwd, databaseUrl);
25651
+ p17.log.success(`Saved DATABASE_URL to ${pc7.cyan(".env.local")}`);
25542
25652
  } else {
25543
25653
  p17.log.info("Falling back to a manual database connection string.");
25544
25654
  openBrowserVercelNeon();
@@ -25868,7 +25978,7 @@ async function runInitCommand(name, options) {
25868
25978
  } else {
25869
25979
  s.start("Pushing database schema (drizzle-kit push)");
25870
25980
  }
25871
- const pushResult = await runDrizzlePush(cwd, { inheritStdio: useTerminalForDbPush });
25981
+ const pushResult = await runDrizzlePush(cwd, { interactive: useTerminalForDbPush });
25872
25982
  if (pushResult.success) {
25873
25983
  if (useTerminalForDbPush) {
25874
25984
  const verification = await verifyDatabaseReachable(cwd);
@@ -26062,10 +26172,15 @@ Run manually: ${pc7.cyan(betterstartExecCommand(pm, "seed"))}`,
26062
26172
  initialValue: linkedVercelProject
26063
26173
  });
26064
26174
  if (!p17.isCancel(deployNow) && deployNow) {
26065
- await runVercelDeployFlow({ cwd, projectName, env: process.env });
26175
+ await runVercelDeployFlow({ cwd, projectName, neonResourceName, env: process.env });
26066
26176
  }
26067
26177
  } else if (linkedVercelProject && process.env.VERCEL_TOKEN) {
26068
- const deployFlow = await runVercelDeployFlow({ cwd, projectName, env: process.env });
26178
+ const deployFlow = await runVercelDeployFlow({
26179
+ cwd,
26180
+ projectName,
26181
+ neonResourceName,
26182
+ env: process.env
26183
+ });
26069
26184
  if (!deployFlow.ok) {
26070
26185
  p17.log.warn("Vercel deploy did not complete; continuing.");
26071
26186
  }
@@ -26463,8 +26578,8 @@ function runQuietCommand(bin, args, options) {
26463
26578
  });
26464
26579
  });
26465
26580
  }
26466
- function stripAnsi(value) {
26467
- return value.replace(ANSI_ESCAPE_PATTERN, "");
26581
+ function stripAnsi2(value) {
26582
+ return value.replace(ANSI_ESCAPE_PATTERN2, "");
26468
26583
  }
26469
26584
  function printAdminReadyNote(state) {
26470
26585
  const lines = [`Admin: ${pc7.cyan(state.adminLoginUrl)}`];
@@ -26475,7 +26590,7 @@ function printAdminReadyNote(state) {
26475
26590
  p17.note(lines.join("\n"), "Admin ready");
26476
26591
  }
26477
26592
  function shouldSuppressDevServerStartupLine(line) {
26478
- const plain = stripAnsi(line).trim();
26593
+ const plain = stripAnsi2(line).trim();
26479
26594
  if (plain.length === 0) {
26480
26595
  return { suppress: true, isReadyLine: false };
26481
26596
  }
@@ -26718,6 +26833,7 @@ function cleanupEmptyDirs3(cwd, deletedPaths, configPaths) {
26718
26833
  const stopRoots = /* @__PURE__ */ new Set([
26719
26834
  path54.join(cwd, ...configPaths.adminDir.split("/")),
26720
26835
  path54.join(cwd, ...configPaths.adminNavigationDir.split("/")),
26836
+ path54.join(cwd, ...configPaths.adminWebhookEventsDir.split("/")),
26721
26837
  path54.join(cwd, ...configPaths.pagesDir.split("/"))
26722
26838
  ]);
26723
26839
  for (const deletedPath of deletedPaths) {
@@ -27504,6 +27620,11 @@ var TEMPLATE_REGISTRY = {
27504
27620
  relPath: "components/layouts/admin-sidebar-nav-link-skeleton.tsx",
27505
27621
  content: () => readTemplate("components/layouts/admin-sidebar-nav-link-skeleton.tsx")
27506
27622
  },
27623
+ "admin-settings-sidebar": {
27624
+ relPath: "components/layouts/admin-settings-sidebar.tsx",
27625
+ content: () => readTemplate("components/layouts/admin-settings-sidebar.tsx"),
27626
+ dependencies: ["admin-sidebar-nav-link"]
27627
+ },
27507
27628
  "admin-sidebar-user-menu": {
27508
27629
  relPath: "components/layouts/admin-sidebar-user-menu.tsx",
27509
27630
  content: () => readTemplate("components/layouts/admin-sidebar-user-menu.tsx"),
@@ -27898,6 +28019,11 @@ var TEMPLATE_REGISTRY = {
27898
28019
  dependencies: ["audit-action"]
27899
28020
  },
27900
28021
  "use-users": { relPath: "hooks/use-users.ts", content: () => readTemplate("hooks/use-users.ts") },
28022
+ "use-webhooks": {
28023
+ relPath: "hooks/use-webhooks.ts",
28024
+ content: () => readTemplate("hooks/use-webhooks.ts"),
28025
+ dependencies: ["webhooks-action", "form-settings-action"]
28026
+ },
27901
28027
  "use-mobile": {
27902
28028
  relPath: "hooks/use-mobile.ts",
27903
28029
  content: () => readTemplate("hooks/use-mobile.ts")
@@ -27946,6 +28072,10 @@ var TEMPLATE_REGISTRY = {
27946
28072
  relPath: "types/table-meta.ts",
27947
28073
  content: () => readTemplate("types/table-meta.ts")
27948
28074
  },
28075
+ "webhooks-types": {
28076
+ relPath: "types/webhooks.ts",
28077
+ content: () => readTemplate("types/webhooks.ts")
28078
+ },
27949
28079
  // Utils
27950
28080
  "app-admin": {
27951
28081
  relPath: ({ config }) => `utils/app/${resolveAdminNamespace(config.frameworkConfig.next.namespace).camel}.ts`,
@@ -28078,9 +28208,9 @@ var TEMPLATE_REGISTRY = {
28078
28208
  relPath: "utils/validation/validation.ts",
28079
28209
  content: () => readTemplate("utils/validation/validation.ts")
28080
28210
  },
28081
- webhook: {
28082
- relPath: "utils/webhook/webhook.ts",
28083
- content: () => readTemplate("utils/webhook/webhook.ts")
28211
+ "webhook-signature": {
28212
+ relPath: "utils/webhook/signature.ts",
28213
+ content: () => readTemplate("utils/webhook/signature.ts")
28084
28214
  },
28085
28215
  "dev-mode-code-block-height-utils": {
28086
28216
  relPath: "utils/dev-mode/code-block-height.ts",
@@ -28113,7 +28243,8 @@ var TEMPLATE_REGISTRY = {
28113
28243
  },
28114
28244
  "lifecycle-hooks-register": {
28115
28245
  relPath: "lib/lifecycle-hooks/register.ts",
28116
- content: () => readTemplate("lib/lifecycle-hooks/register.ts")
28246
+ content: () => readTemplate("lib/lifecycle-hooks/register.ts"),
28247
+ dependencies: ["webhooks-register-action"]
28117
28248
  },
28118
28249
  "lifecycle-hooks-register-local": {
28119
28250
  relPath: "lib/lifecycle-hooks/register.local.ts",
@@ -28298,6 +28429,119 @@ var TEMPLATE_REGISTRY = {
28298
28429
  base: "cwd",
28299
28430
  dependencies: ["profile-action", "users-action"]
28300
28431
  },
28432
+ "settings-layout": {
28433
+ relPath: "app/(admin)/admin/(authenticated)/settings/layout.tsx",
28434
+ content: () => readTemplate("pages/settings/settings-layout.tsx"),
28435
+ base: "cwd",
28436
+ dependencies: ["admin-settings-sidebar"]
28437
+ },
28438
+ "forms-settings-page": {
28439
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/page.tsx",
28440
+ content: () => readTemplate("pages/settings/forms/forms-settings-page.tsx"),
28441
+ base: "cwd",
28442
+ dependencies: ["forms-settings-page-skeleton", "forms-settings-page-content"]
28443
+ },
28444
+ "forms-settings-page-skeleton": {
28445
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-page-skeleton.tsx",
28446
+ content: () => readTemplate("pages/settings/forms/forms-settings-page-skeleton.tsx"),
28447
+ base: "cwd"
28448
+ },
28449
+ "forms-settings-page-content": {
28450
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/forms-settings-page-content.tsx",
28451
+ content: () => readTemplate("pages/settings/forms/forms-settings-page-content.tsx"),
28452
+ base: "cwd",
28453
+ dependencies: ["edit-form-notifications-dialog", "use-webhooks"]
28454
+ },
28455
+ "edit-form-notifications-dialog": {
28456
+ relPath: "app/(admin)/admin/(authenticated)/settings/forms/edit-form-notifications-dialog.tsx",
28457
+ content: () => readTemplate("pages/settings/forms/edit-form-notifications-dialog.tsx"),
28458
+ base: "cwd",
28459
+ dependencies: ["form-settings-action"]
28460
+ },
28461
+ "webhooks-page": {
28462
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/page.tsx",
28463
+ content: () => readTemplate("pages/settings/webhooks/webhooks-page.tsx"),
28464
+ base: "cwd",
28465
+ dependencies: ["webhooks-page-skeleton", "webhooks-page-content"]
28466
+ },
28467
+ "webhooks-page-skeleton": {
28468
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-page-skeleton.tsx",
28469
+ content: () => readTemplate("pages/settings/webhooks/webhooks-page-skeleton.tsx"),
28470
+ base: "cwd"
28471
+ },
28472
+ "webhooks-page-content": {
28473
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-page-content.tsx",
28474
+ content: () => readTemplate("pages/settings/webhooks/webhooks-page-content.tsx"),
28475
+ base: "cwd",
28476
+ dependencies: [
28477
+ "webhooks-table",
28478
+ "webhooks-columns",
28479
+ "webhook-endpoint-dialog",
28480
+ "delete-webhook-dialog",
28481
+ "use-webhooks",
28482
+ "webhooks-action"
28483
+ ]
28484
+ },
28485
+ "webhooks-table": {
28486
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-table.tsx",
28487
+ content: () => readTemplate("pages/settings/webhooks/webhooks-table.tsx"),
28488
+ base: "cwd",
28489
+ dependencies: ["use-webhooks"]
28490
+ },
28491
+ "webhooks-columns": {
28492
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-columns.tsx",
28493
+ content: () => readTemplate("pages/settings/webhooks/webhooks-columns.tsx"),
28494
+ base: "cwd",
28495
+ dependencies: ["webhook-actions", "webhook-enabled-switch"]
28496
+ },
28497
+ "webhook-actions": {
28498
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhook-actions.tsx",
28499
+ content: () => readTemplate("pages/settings/webhooks/webhook-actions.tsx"),
28500
+ base: "cwd",
28501
+ dependencies: ["use-webhooks"]
28502
+ },
28503
+ "webhook-enabled-switch": {
28504
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhook-enabled-switch.tsx",
28505
+ content: () => readTemplate("pages/settings/webhooks/webhook-enabled-switch.tsx"),
28506
+ base: "cwd",
28507
+ dependencies: ["use-webhooks"]
28508
+ },
28509
+ "webhook-endpoint-dialog": {
28510
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhook-endpoint-dialog.tsx",
28511
+ content: () => readTemplate("pages/settings/webhooks/webhook-endpoint-dialog.tsx"),
28512
+ base: "cwd",
28513
+ dependencies: ["use-webhooks", "webhooks-action"]
28514
+ },
28515
+ "delete-webhook-dialog": {
28516
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/delete-webhook-dialog.tsx",
28517
+ content: () => readTemplate("pages/settings/webhooks/delete-webhook-dialog.tsx"),
28518
+ base: "cwd",
28519
+ dependencies: ["webhooks-action"]
28520
+ },
28521
+ "webhooks-logs-page": {
28522
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/logs/page.tsx",
28523
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-page.tsx"),
28524
+ base: "cwd",
28525
+ dependencies: ["webhooks-logs-page-content", "webhooks-page-skeleton"]
28526
+ },
28527
+ "webhooks-logs-page-content": {
28528
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-logs-page-content.tsx",
28529
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-page-content.tsx"),
28530
+ base: "cwd",
28531
+ dependencies: ["webhooks-logs-table", "webhooks-logs-columns", "use-webhooks"]
28532
+ },
28533
+ "webhooks-logs-table": {
28534
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-logs-table.tsx",
28535
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-table.tsx"),
28536
+ base: "cwd",
28537
+ dependencies: ["use-webhooks"]
28538
+ },
28539
+ "webhooks-logs-columns": {
28540
+ relPath: "app/(admin)/admin/(authenticated)/settings/webhooks/webhooks-logs-columns.tsx",
28541
+ content: () => readTemplate("pages/settings/webhooks/webhooks-logs-columns.tsx"),
28542
+ base: "cwd",
28543
+ dependencies: ["webhooks-action"]
28544
+ },
28301
28545
  // Lib
28302
28546
  "email-index": {
28303
28547
  relPath: "lib/actions/email/index.ts",
@@ -28375,8 +28619,7 @@ var TEMPLATE_REGISTRY = {
28375
28619
  "form-settings-types",
28376
28620
  "get-form-settings-action",
28377
28621
  "upsert-form-settings-action",
28378
- "get-all-form-settings-action",
28379
- "test-form-webhook-action"
28622
+ "get-all-form-settings-action"
28380
28623
  ]
28381
28624
  },
28382
28625
  "form-settings-types": {
@@ -28398,9 +28641,87 @@ var TEMPLATE_REGISTRY = {
28398
28641
  content: () => readTemplate("lib/actions/forms/get-all-form-settings.ts"),
28399
28642
  dependencies: ["form-settings-types"]
28400
28643
  },
28401
- "test-form-webhook-action": {
28402
- relPath: "lib/actions/forms/test-form-webhook.ts",
28403
- content: () => readTemplate("lib/actions/forms/test-form-webhook.ts")
28644
+ "webhooks-action": {
28645
+ relPath: "lib/actions/webhooks/index.ts",
28646
+ content: () => readTemplate("lib/actions/webhooks/index.ts"),
28647
+ dependencies: [
28648
+ "webhooks-action-types",
28649
+ "internal-get-active-endpoints-action",
28650
+ "webhooks-dispatch",
28651
+ "get-webhooks-action",
28652
+ "create-webhook-action",
28653
+ "update-webhook-action",
28654
+ "delete-webhook-action",
28655
+ "get-webhook-secret-action",
28656
+ "regenerate-webhook-secret-action",
28657
+ "get-webhook-subscriptions-action",
28658
+ "send-test-webhook-action",
28659
+ "get-webhook-deliveries-action",
28660
+ "webhooks-register-action"
28661
+ ]
28662
+ },
28663
+ "webhooks-action-types": {
28664
+ relPath: "lib/actions/webhooks/types.ts",
28665
+ content: () => readTemplate("lib/actions/webhooks/types.ts")
28666
+ },
28667
+ "internal-get-active-endpoints-action": {
28668
+ relPath: "lib/actions/webhooks/internal-get-active-endpoints.ts",
28669
+ content: () => readTemplate("lib/actions/webhooks/internal-get-active-endpoints.ts")
28670
+ },
28671
+ "webhooks-dispatch": {
28672
+ relPath: "lib/actions/webhooks/dispatch.ts",
28673
+ content: () => readTemplate("lib/actions/webhooks/dispatch.ts"),
28674
+ dependencies: ["webhook-signature", "internal-get-active-endpoints-action"]
28675
+ },
28676
+ "get-webhooks-action": {
28677
+ relPath: "lib/actions/webhooks/get-webhooks.ts",
28678
+ content: () => readTemplate("lib/actions/webhooks/get-webhooks.ts"),
28679
+ dependencies: ["webhooks-action-types"]
28680
+ },
28681
+ "create-webhook-action": {
28682
+ relPath: "lib/actions/webhooks/create-webhook.ts",
28683
+ content: () => readTemplate("lib/actions/webhooks/create-webhook.ts"),
28684
+ dependencies: ["webhooks-action-types", "webhook-signature"]
28685
+ },
28686
+ "update-webhook-action": {
28687
+ relPath: "lib/actions/webhooks/update-webhook.ts",
28688
+ content: () => readTemplate("lib/actions/webhooks/update-webhook.ts"),
28689
+ dependencies: ["webhooks-action-types"]
28690
+ },
28691
+ "delete-webhook-action": {
28692
+ relPath: "lib/actions/webhooks/delete-webhook.ts",
28693
+ content: () => readTemplate("lib/actions/webhooks/delete-webhook.ts"),
28694
+ dependencies: ["webhooks-action-types"]
28695
+ },
28696
+ "get-webhook-secret-action": {
28697
+ relPath: "lib/actions/webhooks/get-webhook-secret.ts",
28698
+ content: () => readTemplate("lib/actions/webhooks/get-webhook-secret.ts"),
28699
+ dependencies: ["webhooks-action-types"]
28700
+ },
28701
+ "regenerate-webhook-secret-action": {
28702
+ relPath: "lib/actions/webhooks/regenerate-webhook-secret.ts",
28703
+ content: () => readTemplate("lib/actions/webhooks/regenerate-webhook-secret.ts"),
28704
+ dependencies: ["webhooks-action-types", "webhook-signature"]
28705
+ },
28706
+ "get-webhook-subscriptions-action": {
28707
+ relPath: "lib/actions/webhooks/get-webhook-subscriptions.ts",
28708
+ content: () => readTemplate("lib/actions/webhooks/get-webhook-subscriptions.ts"),
28709
+ dependencies: ["webhooks-action-types"]
28710
+ },
28711
+ "send-test-webhook-action": {
28712
+ relPath: "lib/actions/webhooks/send-test-webhook.ts",
28713
+ content: () => readTemplate("lib/actions/webhooks/send-test-webhook.ts"),
28714
+ dependencies: ["webhooks-action-types", "webhook-signature", "webhooks-dispatch"]
28715
+ },
28716
+ "get-webhook-deliveries-action": {
28717
+ relPath: "lib/actions/webhooks/get-webhook-deliveries.ts",
28718
+ content: () => readTemplate("lib/actions/webhooks/get-webhook-deliveries.ts"),
28719
+ dependencies: ["webhooks-action-types"]
28720
+ },
28721
+ "webhooks-register-action": {
28722
+ relPath: "lib/actions/webhooks/register.ts",
28723
+ content: () => readTemplate("lib/actions/webhooks/register.ts"),
28724
+ dependencies: ["webhooks-dispatch", "webhooks-types"]
28404
28725
  },
28405
28726
  "entity-versions-action": {
28406
28727
  relPath: "lib/actions/entity-versions/index.ts",
@@ -28688,44 +29009,50 @@ var TEMPLATE_REGISTRY = {
28688
29009
  dependencies: ["use-media", "media-grid-pagination"]
28689
29010
  },
28690
29011
  "audit-log-page": {
28691
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/page.tsx",
28692
- content: () => readTemplate("pages/audit-log/audit-log-page.tsx"),
29012
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/page.tsx",
29013
+ content: () => readTemplate("pages/settings/audit-log/audit-log-page.tsx"),
28693
29014
  base: "cwd",
28694
29015
  dependencies: ["audit-log-page-content", "audit-log-page-skeleton"]
28695
29016
  },
28696
29017
  "audit-log-page-skeleton": {
28697
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-page-skeleton.tsx",
28698
- content: () => readTemplate("pages/audit-log/audit-log-page-skeleton.tsx"),
29018
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-page-skeleton.tsx",
29019
+ content: () => readTemplate("pages/settings/audit-log/audit-log-page-skeleton.tsx"),
28699
29020
  base: "cwd",
28700
29021
  dependencies: ["page-header", "skeleton"]
28701
29022
  },
28702
29023
  "audit-log-page-content": {
28703
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-page-content.tsx",
28704
- content: () => readTemplate("pages/audit-log/audit-log-page-content.tsx"),
29024
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-page-content.tsx",
29025
+ content: () => readTemplate("pages/settings/audit-log/audit-log-page-content.tsx"),
28705
29026
  base: "cwd",
28706
29027
  dependencies: [
29028
+ "audit-log-columns",
28707
29029
  "audit-log-detail-drawer",
28708
- "badge",
28709
- "button",
28710
- "card",
28711
- "data-grid",
28712
- "data-table-pagination",
29030
+ "audit-log-table",
28713
29031
  "entity-filters-bar",
28714
- "page-header",
28715
- "sort-indicator",
28716
- "use-table-utils",
28717
- "use-audit-log"
29032
+ "page-header"
28718
29033
  ]
28719
29034
  },
29035
+ "audit-log-table": {
29036
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-table.tsx",
29037
+ content: () => readTemplate("pages/settings/audit-log/audit-log-table.tsx"),
29038
+ base: "cwd",
29039
+ dependencies: ["data-grid", "data-table-pagination", "use-audit-log", "use-table-utils"]
29040
+ },
29041
+ "audit-log-columns": {
29042
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-columns.tsx",
29043
+ content: () => readTemplate("pages/settings/audit-log/audit-log-columns.tsx"),
29044
+ base: "cwd",
29045
+ dependencies: ["badge", "button", "sort-indicator"]
29046
+ },
28720
29047
  "audit-log-detail-drawer": {
28721
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-detail-drawer.tsx",
28722
- content: () => readTemplate("pages/audit-log/audit-log-detail-drawer.tsx"),
29048
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-detail-drawer.tsx",
29049
+ content: () => readTemplate("pages/settings/audit-log/audit-log-detail-drawer.tsx"),
28723
29050
  base: "cwd",
28724
29051
  dependencies: ["audit-log-detail-row", "badge", "drawer", "scroll-area"]
28725
29052
  },
28726
29053
  "audit-log-detail-row": {
28727
- relPath: "app/(admin)/admin/(authenticated)/system/audit-log/audit-log-detail-row.tsx",
28728
- content: () => readTemplate("pages/audit-log/audit-log-detail-row.tsx"),
29054
+ relPath: "app/(admin)/admin/(authenticated)/settings/audit-log/audit-log-detail-row.tsx",
29055
+ content: () => readTemplate("pages/settings/audit-log/audit-log-detail-row.tsx"),
28729
29056
  base: "cwd"
28730
29057
  }
28731
29058
  };