create-ailk 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/component-catalog.json +186 -8
  2. package/component-catalog.md +9 -7
  3. package/dist/add-module.js +20 -14
  4. package/dist/add-route.d.ts +55 -1
  5. package/dist/add-route.js +294 -12
  6. package/dist/cli.js +27 -10
  7. package/dist/derive-architecture.d.ts +12 -3
  8. package/dist/derive-architecture.js +20 -8
  9. package/dist/design-package.js +7 -2
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +5 -5
  12. package/dist/lib/npm-auth.d.ts +9 -0
  13. package/dist/lib/npm-auth.js +48 -0
  14. package/dist/lib/scaffold-render-sweep.d.ts +3 -2
  15. package/dist/lib/scaffold-render-sweep.js +5 -8
  16. package/dist/module-architecture.js +99 -9
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/programmatic.js +19 -5
  20. package/dist/seed-flows.d.ts +4 -1
  21. package/dist/seed-flows.js +50 -4
  22. package/dist/site-chrome.d.ts +8 -0
  23. package/dist/site-chrome.js +17 -1
  24. package/dist/spoke-checkout.d.ts +65 -41
  25. package/dist/spoke-checkout.js +208 -430
  26. package/dist/starter-grid.d.ts +0 -3
  27. package/dist/starter-grid.js +24 -14
  28. package/dist/surfaces.js +52 -26
  29. package/dist/sync-routes.d.ts +38 -1
  30. package/dist/sync-routes.js +187 -13
  31. package/dist/template-record.d.ts +4 -3
  32. package/dist/template-record.js +8 -4
  33. package/dist/topology.js +25 -1
  34. package/dist/update.js +15 -0
  35. package/package.json +3 -3
  36. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  37. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  38. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  39. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  40. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  41. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  42. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  43. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  44. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  45. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  46. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  47. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  48. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  49. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  50. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  51. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  52. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  53. package/templates/.env.example +9 -7
  54. package/templates/.github/workflows/ci.yml +13 -0
  55. package/templates/README.md +1 -0
  56. package/templates/_gitignore +4 -0
  57. package/templates/apps/api/.env.example +27 -0
  58. package/templates/apps/api/CLAUDE.md +87 -17
  59. package/templates/apps/api/package.json +1 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  61. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  62. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  63. package/templates/apps/api/src/config/index.ts +109 -0
  64. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  65. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  66. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  67. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  68. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  69. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  70. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  71. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  72. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  73. package/templates/apps/api/src/lib/stripe.ts +74 -4
  74. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  75. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  77. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  78. package/templates/apps/api/src/middleware/auth.ts +13 -6
  79. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  80. package/templates/apps/api/src/openapi/spec.ts +33 -3
  81. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  82. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  83. package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
  84. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  85. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
  86. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  87. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  88. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  89. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  90. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  91. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  92. package/templates/apps/api/src/routes/consent/account.ts +268 -0
  93. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  94. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  95. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  96. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  97. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  98. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  99. package/templates/apps/api/src/routes/content/index.ts +16 -2
  100. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  101. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  102. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  103. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  104. package/templates/apps/api/src/routes/leads/index.ts +164 -7
  105. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  106. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  111. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  112. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  113. package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
  114. package/templates/apps/api/src/server.ts +163 -46
  115. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  116. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
  117. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  118. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
  119. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  120. package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
  121. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
  122. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  123. package/templates/apps/api/src/services/consent.ts +95 -0
  124. package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
  125. package/templates/apps/api/src/services/entitlements.ts +157 -1
  126. package/templates/apps/api/src/services/lead-routing.ts +431 -47
  127. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  128. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
  129. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  130. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
  131. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  132. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
  133. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
  134. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  135. package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
  136. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
  137. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  138. package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
  139. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  140. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  141. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  142. package/templates/apps/api/src/services/person.ts +164 -17
  143. package/templates/apps/api/src/services/plan-offerings.ts +125 -0
  144. package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
  145. package/templates/apps/mcp/CLAUDE.md +22 -14
  146. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  147. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  148. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  149. package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
  150. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  151. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  152. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  153. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  154. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  155. package/templates/apps/mcp/src/server.ts +5 -0
  156. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  157. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  158. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  159. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  160. package/templates/apps/web/.env.example +24 -31
  161. package/templates/apps/web/CLAUDE.md +393 -9
  162. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  163. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  164. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  165. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  166. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  167. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  168. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  169. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  170. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  171. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  172. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  173. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  174. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  175. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  176. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  177. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  178. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  179. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  183. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  184. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  185. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  186. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  187. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  188. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  189. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  190. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  191. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  192. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  193. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  194. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  195. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  196. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
  197. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
  198. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
  199. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
  200. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  201. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  202. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
  203. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
  204. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
  205. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  206. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  207. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  208. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  209. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  210. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  211. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  212. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
  213. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  214. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
  215. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
  216. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
  217. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
  218. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  219. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
  220. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  221. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  222. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
  223. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  224. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  225. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  226. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  227. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  228. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  229. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  230. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  231. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
  232. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  233. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  234. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  235. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  236. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  237. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  238. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  239. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  240. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  241. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  242. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  243. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  244. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  245. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  246. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  247. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  248. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  249. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  250. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  251. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  252. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  253. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  254. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  255. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  256. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  257. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  258. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  259. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  260. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  261. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  262. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  263. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  264. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  265. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  266. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  267. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  268. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  269. package/templates/apps/web/auth.ts +28 -2
  270. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  271. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  272. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  273. package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
  274. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  275. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
  276. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  277. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  278. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
  279. package/templates/apps/web/lib/account-invite.ts +37 -0
  280. package/templates/apps/web/lib/auth-routes.ts +36 -2
  281. package/templates/apps/web/lib/checkout-config.ts +16 -0
  282. package/templates/apps/web/lib/consent-policies.ts +22 -0
  283. package/templates/apps/web/lib/deck-page.ts +7 -0
  284. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  285. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  286. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  287. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  288. package/templates/apps/web/lib/magnet-routes.ts +42 -0
  289. package/templates/apps/web/lib/modules.generated.ts +9 -0
  290. package/templates/apps/web/lib/newsletter.actions.ts +202 -0
  291. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  292. package/templates/apps/web/lib/posting-title.ts +17 -0
  293. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  294. package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
  295. package/templates/apps/web/lib/site-chrome.ts +2 -0
  296. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  297. package/templates/apps/web/messages/en.json +71 -20
  298. package/templates/apps/web/middleware.ts +12 -2
  299. package/templates/apps/web/package.json +1 -0
  300. package/templates/apps/web/security-headers.config.ts +17 -0
  301. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  302. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  303. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  304. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  305. package/templates/content/en/BlogList/blog.mdx +4 -71
  306. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  307. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  308. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  309. package/templates/content/en/BookCall/start-project.mdx +1 -17
  310. package/templates/content/en/ContactPage/contact.mdx +6 -63
  311. package/templates/content/en/HomePage/home.mdx +3 -21
  312. package/templates/database/CHANGELOG.md +69 -0
  313. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  314. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  315. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  316. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  317. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  318. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  319. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  320. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  321. package/templates/database/__tests__/seed.test.ts +64 -1
  322. package/templates/database/auth/schema.prisma +4 -0
  323. package/templates/database/config/schema.prisma +91 -0
  324. package/templates/database/inbox/schema.prisma +17 -2
  325. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  326. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  327. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  328. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  329. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  330. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  331. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  332. package/templates/database/package.json +1 -1
  333. package/templates/database/seed.ts +62 -3
  334. package/versions.json +35 -35
  335. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  336. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
  337. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  338. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  339. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  340. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  341. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  342. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -2,14 +2,19 @@
2
2
  * POST /api/webhooks/stripe — org-scoped billing path (#3601, spec AC2/AC4)
3
3
  *
4
4
  * Covers the Organization billing-link branch keyed on
5
- * metadata.organizationId (spec D2): a checkout with organizationId links the
6
- * Organization (real org AND individual-as-default-org), idempotently; a
7
- * checkout WITHOUT organizationId takes the User path and never touches the
5
+ * metadata.organizationId (spec D2), proof-based since #9067 D13 (AC-7): a
6
+ * checkout with organizationId links the Organization (real org AND
7
+ * individual-as-default-org) only when the row recorded that session and has
8
+ * no customer, idempotently; an unrecorded session, a bound customer and any
9
+ * customer.created write nothing and log `org_bind_unproven`. A checkout
10
+ * WITHOUT organizationId takes the User path and never touches the
8
11
  * Organization (single-tenant, degrade-clean regression floor).
9
12
  *
10
13
  * Companion to stripe.test.ts (the User-path tests, which stay unmodified —
11
14
  * AC4 regression floor).
12
15
  */
16
+ import { Writable } from "node:stream";
17
+
13
18
  import type { FastifyInstance } from "fastify";
14
19
  import Fastify from "fastify";
15
20
 
@@ -70,8 +75,17 @@ const REAL_ORG_ID = "org_00000000-0000-7000-8000-0000000000ab";
70
75
  const VALID_BODY = Buffer.from(JSON.stringify({ type: "test" }));
71
76
  const VALID_SIGNATURE = "t=1234,v1=abc";
72
77
 
78
+ let logLines: string[] = [];
79
+
73
80
  async function buildTestServer(): Promise<FastifyInstance> {
74
- const server = Fastify({ logger: false });
81
+ logLines = [];
82
+ const stream = new Writable({
83
+ write(chunk, _encoding, done) {
84
+ logLines.push(String(chunk));
85
+ done();
86
+ },
87
+ });
88
+ const server = Fastify({ logger: { level: "info", stream } });
75
89
  await server.register(async (scope) => {
76
90
  scope.addContentTypeParser(
77
91
  "application/json",
@@ -88,13 +102,14 @@ function checkoutEvent(
88
102
  id: string,
89
103
  metadata: Record<string, string> | null,
90
104
  customer = "cus_test_org_123",
105
+ sessionId = "cs_test_org",
91
106
  ) {
92
107
  return {
93
108
  id,
94
109
  type: "checkout.session.completed",
95
110
  data: {
96
111
  object: {
97
- id: "cs_test_org",
112
+ id: sessionId,
98
113
  amount_total: 9900,
99
114
  currency: "usd",
100
115
  customer,
@@ -117,7 +132,7 @@ async function fire(server: FastifyInstance) {
117
132
  });
118
133
  }
119
134
 
120
- describe("POST /api/webhooks/stripe — org-scoped billing (#3601)", () => {
135
+ describe("POST /api/webhooks/stripe — org-scoped billing (#3601, proof-based since #9067 D13)", () => {
121
136
  let server: FastifyInstance;
122
137
 
123
138
  beforeEach(async () => {
@@ -127,6 +142,8 @@ describe("POST /api/webhooks/stripe — org-scoped billing (#3601)", () => {
127
142
  ...configModule.config,
128
143
  STRIPE_WEBHOOK_SECRET: "whsec_test_secret",
129
144
  });
145
+ (prisma.organization.findFirst as jest.Mock).mockResolvedValue(null);
146
+ (prisma.organization.updateMany as jest.Mock).mockResolvedValue({ count: 1 });
130
147
  server = await buildTestServer();
131
148
  });
132
149
 
@@ -135,85 +152,85 @@ describe("POST /api/webhooks/stripe — org-scoped billing (#3601)", () => {
135
152
  jest.restoreAllMocks();
136
153
  });
137
154
 
138
- // AC2 — a real org checkout links the Organization by id; replay is a no-op.
139
- it("links the Organization on checkout with metadata.organizationId, idempotent on replay", async () => {
140
- const customerId = "cus_test_org_123";
155
+ /** The arm's `org_bind_unproven` lines, each with no customer or Organization id. */
156
+ function unprovenLines(...ids: string[]): string[] {
157
+ const lines = logLines.filter((line) => line.includes("org_bind_unproven"));
158
+ for (const line of lines) for (const id of ids) expect(line).not.toContain(id);
159
+ return lines;
160
+ }
141
161
 
142
- // First fire: no prior composite link → update the target org.
143
- (prisma.organization.findFirst as jest.Mock).mockResolvedValueOnce(null);
144
- (prisma.organization.update as jest.Mock).mockResolvedValueOnce({
145
- id: REAL_ORG_ID,
146
- billingProvider: "stripe",
147
- billingCustomerId: customerId,
148
- });
162
+ function boundWrite(organizationId: string, sessionId: string, customerId: string) {
163
+ return {
164
+ where: { id: organizationId, planCheckoutSessionId: sessionId, billingCustomerId: null },
165
+ data: { billingProvider: "stripe", billingCustomerId: customerId, planCheckoutSessionId: null },
166
+ };
167
+ }
149
168
 
169
+ // AC-7 — the recorded session binds into an empty column, clearing the record.
170
+ it("binds the Organization only to the plan session it recorded, idempotent on replay", async () => {
150
171
  mockVerifyWebhookSignature.mockReturnValue(
151
- checkoutEvent("evt_org_001", { organizationId: REAL_ORG_ID }, customerId),
172
+ checkoutEvent("evt_org_001", { organizationId: "org_1" }, "cus_1", "cs_1"),
152
173
  );
153
174
  const r1 = await fire(server);
154
175
  expect(r1.statusCode).toBe(200);
155
- expect(prisma.organization.update).toHaveBeenCalledTimes(1);
156
- expect(prisma.organization.update).toHaveBeenCalledWith(
157
- expect.objectContaining({
158
- where: { id: REAL_ORG_ID },
159
- data: expect.objectContaining({
160
- billingProvider: "stripe",
161
- billingCustomerId: customerId,
162
- }),
163
- }),
164
- );
176
+ expect(prisma.organization.updateMany).toHaveBeenCalledTimes(1);
177
+ expect(prisma.organization.updateMany).toHaveBeenCalledWith(boundWrite("org_1", "cs_1", "cus_1"));
178
+ expect(prisma.organization.update).not.toHaveBeenCalled();
165
179
  // The User path is never taken for an org checkout.
166
180
  expect(prisma.user.update).not.toHaveBeenCalled();
181
+ expect(unprovenLines()).toHaveLength(0);
167
182
 
168
- // Replay the SAME event id → idempotency store short-circuits, no 2nd write.
169
- mockVerifyWebhookSignature.mockReturnValue(
170
- checkoutEvent("evt_org_001", { organizationId: REAL_ORG_ID }, customerId),
171
- );
183
+ // Replay the SAME event id → the duplicate check stops it before the arm.
172
184
  const r2 = await fire(server);
173
185
  expect(r2.statusCode).toBe(200);
174
- expect(prisma.organization.update).toHaveBeenCalledTimes(1);
186
+ expect(prisma.organization.updateMany).toHaveBeenCalledTimes(1);
175
187
  });
176
188
 
177
- // AC2 — typed-composite match already pointing at this org → no-op (a fresh
178
- // event id, but the customer is already linked to the target org).
189
+ // AC-7 — a second delivery of the same session under a new event id, after
190
+ // binding, is the idempotent no-op (typed-composite match).
179
191
  it("is a no-op when the org is already linked to this customer (typed-composite match)", async () => {
180
- const customerId = "cus_already_linked";
181
192
  (prisma.organization.findFirst as jest.Mock).mockResolvedValueOnce({
182
- id: REAL_ORG_ID,
193
+ id: "org_1",
183
194
  billingProvider: "stripe",
184
- billingCustomerId: customerId,
195
+ billingCustomerId: "cus_1",
185
196
  });
197
+ mockVerifyWebhookSignature.mockReturnValue(
198
+ checkoutEvent("evt_org_002", { organizationId: "org_1" }, "cus_1", "cs_1"),
199
+ );
200
+ const r = await fire(server);
201
+ expect(r.statusCode).toBe(200);
202
+ expect(prisma.organization.updateMany).not.toHaveBeenCalled();
203
+ expect(prisma.organization.update).not.toHaveBeenCalled();
204
+ expect(logLines.some((line) => line.includes("idempotent no-op"))).toBe(true);
205
+ });
186
206
 
207
+ // AC-7 — the row holds another session id or none, or already holds a
208
+ // customer: the guarded write moves no row, so nothing binds.
209
+ it.each([
210
+ ["holds another session id or none", { organizationId: "org_1" }],
211
+ ["already holds cus_other", { organizationId: "org_1" }],
212
+ ])("writes nothing and logs org_bind_unproven when the row %s", async (_label, metadata) => {
213
+ (prisma.organization.updateMany as jest.Mock).mockResolvedValueOnce({ count: 0 });
187
214
  mockVerifyWebhookSignature.mockReturnValue(
188
- checkoutEvent("evt_org_002", { organizationId: REAL_ORG_ID }, customerId),
215
+ checkoutEvent("evt_org_003", metadata, "cus_1", "cs_1"),
189
216
  );
190
217
  const r = await fire(server);
191
218
  expect(r.statusCode).toBe(200);
192
219
  expect(prisma.organization.update).not.toHaveBeenCalled();
220
+ expect(prisma.user.update).not.toHaveBeenCalled();
221
+ expect(unprovenLines("cus_1", "org_1")).toHaveLength(1);
193
222
  });
194
223
 
195
224
  // AC2 — an individual is billed on their sentinel default Organization
196
- // (orgs-of-one): the SAME org path, targeting DEFAULT_ORG_ID.
225
+ // (orgs-of-one): the SAME org path, under the same proof.
197
226
  it("bills an individual on their sentinel default Organization (orgs-of-one)", async () => {
198
- const customerId = "cus_individual_xyz";
199
- (prisma.organization.findFirst as jest.Mock).mockResolvedValueOnce(null);
200
- (prisma.organization.update as jest.Mock).mockResolvedValueOnce({
201
- id: DEFAULT_ORG_ID,
202
- billingProvider: "stripe",
203
- billingCustomerId: customerId,
204
- });
205
-
206
227
  mockVerifyWebhookSignature.mockReturnValue(
207
- checkoutEvent(
208
- "evt_org_003",
209
- { organizationId: DEFAULT_ORG_ID },
210
- customerId,
211
- ),
228
+ checkoutEvent("evt_org_004", { organizationId: DEFAULT_ORG_ID }, "cus_individual_xyz", "cs_indiv"),
212
229
  );
213
230
  const r = await fire(server);
214
231
  expect(r.statusCode).toBe(200);
215
- expect(prisma.organization.update).toHaveBeenCalledWith(
216
- expect.objectContaining({ where: { id: DEFAULT_ORG_ID } }),
232
+ expect(prisma.organization.updateMany).toHaveBeenCalledWith(
233
+ boundWrite(DEFAULT_ORG_ID, "cs_indiv", "cus_individual_xyz"),
217
234
  );
218
235
  expect(prisma.user.update).not.toHaveBeenCalled();
219
236
  });
@@ -243,22 +260,17 @@ describe("POST /api/webhooks/stripe — org-scoped billing (#3601)", () => {
243
260
  expect(prisma.user.update).toHaveBeenCalledTimes(1);
244
261
  expect(prisma.organization.findFirst).not.toHaveBeenCalled();
245
262
  expect(prisma.organization.update).not.toHaveBeenCalled();
263
+ expect(prisma.organization.updateMany).not.toHaveBeenCalled();
246
264
  });
247
265
 
248
- // customer.created also routes on metadata.organizationId.
249
- it("links the Organization on customer.created with metadata.organizationId", async () => {
250
- const customerId = "cus_created_org";
251
- (prisma.organization.findFirst as jest.Mock).mockResolvedValueOnce(null);
252
- (prisma.organization.update as jest.Mock).mockResolvedValueOnce({
253
- id: REAL_ORG_ID,
254
- });
255
-
266
+ // AC-7 — a customer's own metadata proves nothing: customer.created binds no Organization.
267
+ it("writes nothing and logs org_bind_unproven on customer.created with metadata.organizationId", async () => {
256
268
  mockVerifyWebhookSignature.mockReturnValue({
257
269
  id: "evt_cust_005",
258
270
  type: "customer.created",
259
271
  data: {
260
272
  object: {
261
- id: customerId,
273
+ id: "cus_created_org",
262
274
  email: "org-admin@example.com",
263
275
  metadata: { organizationId: REAL_ORG_ID },
264
276
  },
@@ -266,16 +278,16 @@ describe("POST /api/webhooks/stripe — org-scoped billing (#3601)", () => {
266
278
  });
267
279
  const r = await fire(server);
268
280
  expect(r.statusCode).toBe(200);
269
- expect(prisma.organization.update).toHaveBeenCalledWith(
270
- expect.objectContaining({ where: { id: REAL_ORG_ID } }),
271
- );
281
+ expect(prisma.organization.findFirst).not.toHaveBeenCalled();
282
+ expect(prisma.organization.update).not.toHaveBeenCalled();
283
+ expect(prisma.organization.updateMany).not.toHaveBeenCalled();
272
284
  expect(prisma.user.update).not.toHaveBeenCalled();
285
+ expect(unprovenLines("cus_created_org", REAL_ORG_ID)).toHaveLength(1);
273
286
  });
274
287
 
275
288
  // A DB failure on the org link never throws (200 still returned).
276
289
  it("returns 200 even when the org billing update throws", async () => {
277
- (prisma.organization.findFirst as jest.Mock).mockResolvedValueOnce(null);
278
- (prisma.organization.update as jest.Mock).mockRejectedValueOnce(
290
+ (prisma.organization.updateMany as jest.Mock).mockRejectedValueOnce(
279
291
  new Error("db unreachable"),
280
292
  );
281
293
 
@@ -624,17 +624,17 @@ describe.each(HUB_STAMPS)("case 13 — a billing event with %s", (_label, stamp)
624
624
  });
625
625
  });
626
626
 
627
- it("customer.created with organizationId binds the Organization's billing customer", async () => {
627
+ // #9067 D13 — a customer's own metadata proves no plan checkout: the
628
+ // event passes the site filter and still binds no Organization.
629
+ it("customer.created with organizationId passes the filter and writes no Organization", async () => {
628
630
  mockVerifyWebhookSignature.mockReturnValue(
629
631
  customerEvent("customer.created", { ...stamp, organizationId: ORG_ID }),
630
632
  );
631
633
  const res = await post();
632
634
 
633
635
  expect(res.statusCode).toBe(200);
634
- expect(prisma.organization.update).toHaveBeenCalledWith({
635
- where: { id: ORG_ID },
636
- data: { billingProvider: "stripe", billingCustomerId: CUSTOMER_ID },
637
- });
636
+ expect(prisma.organization.update).not.toHaveBeenCalled();
637
+ expect(prisma.organization.updateMany).not.toHaveBeenCalled();
638
638
  });
639
639
 
640
640
  it("customer.subscription.updated writes the subscriber's dunning record", async () => {
@@ -1129,7 +1129,10 @@ describe("POST /api/webhooks/stripe", () => {
1129
1129
 
1130
1130
  // T9 (F8 — organizationId keeps precedence over the user signal)
1131
1131
  it("T9: organizationId takes the Organization path even with a user signal", async () => {
1132
+ // #9067 D13 — under the proof rule: the arm binds only the session the
1133
+ // Organization recorded, and the User path is still never reached.
1132
1134
  (prisma.organization.findFirst as jest.Mock).mockResolvedValueOnce(null);
1135
+ (prisma.organization.updateMany as jest.Mock).mockResolvedValueOnce({ count: 1 });
1133
1136
 
1134
1137
  const { statusCode } = await withLogCapture(
1135
1138
  checkoutCompleted("cus_org", {
@@ -1139,9 +1142,12 @@ describe("POST /api/webhooks/stripe", () => {
1139
1142
  );
1140
1143
 
1141
1144
  expect(statusCode).toBe(200);
1142
- expect(prisma.organization.update).toHaveBeenCalledWith(
1143
- expect.objectContaining({ where: { id: "org_1" } }),
1145
+ expect(prisma.organization.updateMany).toHaveBeenCalledWith(
1146
+ expect.objectContaining({
1147
+ where: { id: "org_1", planCheckoutSessionId: "cs_test_6852", billingCustomerId: null },
1148
+ }),
1144
1149
  );
1150
+ expect(prisma.organization.update).not.toHaveBeenCalled();
1145
1151
  expect(prisma.user.findUnique).not.toHaveBeenCalled();
1146
1152
  expect(prisma.user.update).not.toHaveBeenCalled();
1147
1153
  });
@@ -4,6 +4,14 @@
4
4
  * Routes:
5
5
  * POST /v1/webhooks/stripe — verify Stripe signature, dispatch event,
6
6
  * fire telemetry, respond 200.
7
+ * POST /v1/webhooks/stripe/:connectionToken — the per-Organization
8
+ * receiver (#8932 D1): the token names ONE
9
+ * Organization's StripeConnection, whose own
10
+ * secret verifies the delivery and whose
11
+ * recorded Offerings sessions it fulfils.
12
+ * Base tree like the legacy receiver — the
13
+ * commerce claim is read at call time — with
14
+ * its own rate-limit bucket.
7
15
  * POST /v1/webhooks/calcom — verify the scheduling provider's signature,
8
16
  * normalize a booking event into an
9
17
  * Appointment row (#6085, brief H7).
@@ -32,9 +40,11 @@ import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
32
40
  import { isModuleEnabled } from "../../config/modules.js";
33
41
  import { loadSlice } from "../../lib/slice-load.js";
34
42
 
43
+ import { stripeOfferingsWebhookHandler } from "./stripe-offerings.js";
35
44
  import { stripeWebhookHandler } from "./stripe.js";
36
45
 
37
46
  export const STRIPE_WEBHOOK_RATE_LIMIT_PER_MIN = 100;
47
+ export const STRIPE_OFFERINGS_WEBHOOK_RATE_LIMIT_PER_MIN = 100;
38
48
  export const CALCOM_WEBHOOK_RATE_LIMIT_PER_MIN = 100;
39
49
 
40
50
  // Kept off the `import()` call site so `tsc` never resolves it and the OSS
@@ -70,6 +80,19 @@ export const webhooksRoutes: FastifyPluginAsync = async (fastify) => {
70
80
  stripeWebhookHandler,
71
81
  );
72
82
 
83
+ fastify.post(
84
+ "/v1/webhooks/stripe/:connectionToken",
85
+ {
86
+ config: {
87
+ rateLimit: {
88
+ max: STRIPE_OFFERINGS_WEBHOOK_RATE_LIMIT_PER_MIN,
89
+ timeWindow: "1 minute",
90
+ },
91
+ },
92
+ },
93
+ stripeOfferingsWebhookHandler,
94
+ );
95
+
73
96
  if (isModuleEnabled("scheduling")) {
74
97
  const mod = (await loadSlice(CALCOM_HANDLER, () => import(CALCOM_HANDLER))) as {
75
98
  calcomWebhookHandler?: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;