create-ailk 0.5.0 → 0.6.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 (299) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +336 -27
  3. package/component-catalog.md +25 -12
  4. package/dist/lib/api-boot-assertion.d.ts +46 -0
  5. package/dist/lib/api-boot-assertion.js +70 -0
  6. package/dist/lib/import-graph.d.ts +29 -0
  7. package/dist/lib/import-graph.js +140 -0
  8. package/dist/module-architecture.d.ts +1 -0
  9. package/dist/module-architecture.js +253 -127
  10. package/dist/surfaces.js +105 -7
  11. package/dist/template-manifest.d.ts +33 -0
  12. package/dist/template-manifest.js +99 -16
  13. package/dist/template-names.d.ts +1 -1
  14. package/dist/template-names.js +1 -0
  15. package/package.json +3 -2
  16. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
  17. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
  18. package/site-templates/marketing-site/ailk-template.json +1 -1
  19. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
  20. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
  21. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
  22. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
  23. package/site-templates/minimalism-site/ailk-template.json +2 -1
  24. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  25. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
  26. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  27. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  28. package/site-templates/simple-service/ailk-template.json +10 -0
  29. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  30. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  31. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  32. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  33. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  34. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  35. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  36. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  37. package/site-templates/simple-service/provenance.json +4 -0
  38. package/templates/.env.example +72 -7
  39. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  40. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  41. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  42. package/templates/apps/api/.env.example +13 -0
  43. package/templates/apps/api/CLAUDE.md +26 -2
  44. package/templates/apps/api/jest.config.cjs +3 -0
  45. package/templates/apps/api/package.json +1 -0
  46. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  47. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
  48. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  49. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  50. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
  51. package/templates/apps/api/src/config/index.ts +62 -0
  52. package/templates/apps/api/src/config/modules.ts +7 -3
  53. package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
  54. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  55. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  56. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  57. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  58. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  59. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  60. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  61. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  62. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  63. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  64. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  65. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  66. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  67. package/templates/apps/api/src/lib/site.ts +208 -9
  68. package/templates/apps/api/src/lib/stripe.ts +66 -0
  69. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
  70. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
  71. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  72. package/templates/apps/api/src/openapi/spec.ts +543 -2
  73. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  74. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  75. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  76. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  77. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  78. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  79. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  80. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  81. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  82. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  83. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  84. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  85. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  86. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  87. package/templates/apps/api/src/routes/content/index.ts +13 -73
  88. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  89. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  90. package/templates/apps/api/src/routes/content/search.ts +129 -0
  91. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  92. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  93. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  94. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  95. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  96. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  97. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  98. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  99. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  100. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  101. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  102. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  103. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  104. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  105. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  106. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  111. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  112. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  113. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  114. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  115. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  116. package/templates/apps/api/src/server.ts +439 -49
  117. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  118. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  119. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  120. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  121. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  122. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  123. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  124. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  125. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  126. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  127. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  128. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  129. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  130. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  131. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  132. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  133. package/templates/apps/api/src/services/audit-log.ts +668 -0
  134. package/templates/apps/api/src/services/consent.ts +18 -0
  135. package/templates/apps/api/src/services/default-tenant.ts +70 -31
  136. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  137. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  138. package/templates/apps/api/src/services/entitlements.ts +80 -0
  139. package/templates/apps/api/src/services/lead-promotion.ts +13 -14
  140. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  141. package/templates/apps/api/src/services/leads-inbox.ts +23 -2
  142. package/templates/apps/api/src/services/listing-config.ts +27 -9
  143. package/templates/apps/api/src/services/project-listings.ts +201 -6
  144. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  145. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  146. package/templates/apps/api/src/services/revalidation.ts +173 -0
  147. package/templates/apps/api/src/services/verified-email.ts +71 -0
  148. package/templates/apps/api/src/vercel-handler.ts +75 -35
  149. package/templates/apps/mcp/.env.example +17 -0
  150. package/templates/apps/mcp/CLAUDE.md +57 -24
  151. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  152. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  153. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  154. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  155. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  156. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  157. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  158. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  159. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  160. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  161. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  162. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  163. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  164. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  165. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  166. package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
  167. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  168. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  169. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  170. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  171. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  172. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  173. package/templates/apps/mcp/jest.config.cjs +13 -0
  174. package/templates/apps/mcp/package.json +2 -0
  175. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  176. package/templates/apps/mcp/src/config/modules.ts +10 -5
  177. package/templates/apps/mcp/src/http-transport.ts +102 -29
  178. package/templates/apps/mcp/src/index.ts +73 -11
  179. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  180. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  181. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  182. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  183. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  184. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  185. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  186. package/templates/apps/mcp/src/server.ts +59 -35
  187. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  188. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  189. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  190. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  191. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  192. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  193. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  194. package/templates/apps/mcp/src/tools/index.ts +6 -4
  195. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  196. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  197. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  198. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  199. package/templates/apps/web/.env.example +71 -5
  200. package/templates/apps/web/CLAUDE.md +44 -0
  201. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  202. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  203. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  204. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  205. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  206. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  207. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  208. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  209. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  210. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  211. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  212. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  213. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  214. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  215. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  216. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  217. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  218. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  219. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  220. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
  221. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
  222. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  223. package/templates/apps/web/app/[locale]/layout.tsx +133 -87
  224. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  225. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  226. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  227. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  228. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  229. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  230. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  231. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  232. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  233. package/templates/apps/web/auth.ts +407 -8
  234. package/templates/apps/web/jest.config.cjs +8 -4
  235. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  236. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  237. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  238. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
  239. package/templates/apps/web/lib/auth-email.ts +318 -0
  240. package/templates/apps/web/lib/auth-env.ts +85 -0
  241. package/templates/apps/web/lib/auth-routes.ts +55 -0
  242. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  243. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  244. package/templates/apps/web/messages/en.json +187 -0
  245. package/templates/apps/web/package.json +2 -1
  246. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  247. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  248. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  249. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  250. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  251. package/templates/database/CHANGELOG.md +142 -0
  252. package/templates/database/README.md +10 -3
  253. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  254. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  255. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  256. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  257. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  258. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  259. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  260. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  261. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  262. package/templates/database/__tests__/tenancy.test.ts +141 -0
  263. package/templates/database/config/schema.prisma +79 -2
  264. package/templates/database/content/schema.prisma +87 -11
  265. package/templates/database/inbox/schema.prisma +452 -9
  266. package/templates/database/jest.config.cjs +5 -3
  267. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  268. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  269. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  270. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  271. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  272. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  273. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  274. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  275. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  276. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  277. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  278. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  279. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  280. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  281. package/templates/database/package.json +1 -1
  282. package/templates/database/tenancy.ts +86 -1
  283. package/templates/database/tsconfig.json +1 -0
  284. package/versions.json +34 -34
  285. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  286. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  287. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  288. package/templates/apps/api/src/routes/content/create.ts +0 -115
  289. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  290. package/templates/apps/api/src/routes/content/update.ts +0 -112
  291. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  292. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  293. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  294. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  295. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  296. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  297. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  298. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  299. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * POST /v1/webhooks/stripe — the request-and-confirm `paidAt` stamp (#5970
3
+ * AC-3).
4
+ *
5
+ * Mock shape is `stripe-flow-checkout.test.ts`'s, deliberately unchanged: the
6
+ * five pre-existing webhook suites are the regression floor for this issue and
7
+ * none of them is edited, so this file mirrors their setup rather than
8
+ * refactoring a shared one.
9
+ */
10
+ import type { FastifyInstance } from "fastify";
11
+ import Fastify from "fastify";
12
+
13
+ const mockTrack = jest.fn().mockResolvedValue(undefined);
14
+
15
+ // Commerce claim (D-64, #5796): every case here is a COMMERCE-CLAIMED
16
+ // deployment, the same posture the sibling webhook suites take.
17
+ jest.mock("@working-theory/license", () => ({
18
+ checkLicense: jest.fn().mockResolvedValue({
19
+ tier: "pro",
20
+ plan: "pro-monthly",
21
+ features: ["commerce"],
22
+ expiresAt: null,
23
+ revoked: false,
24
+ }),
25
+ }));
26
+
27
+ jest.mock("@working-theory/analytics/server", () => ({
28
+ track: (...args: unknown[]) => mockTrack(...args),
29
+ }));
30
+
31
+ // eslint-disable-next-line no-restricted-syntax -- lib/prisma.ts wraps the external DB boundary (PrismaClient requires a live connection); __mocks__/prisma.ts is the repo-standard test double for this boundary.
32
+ jest.mock("../../../lib/prisma.js");
33
+
34
+ const mockVerifyWebhookSignature = jest.fn();
35
+ class MockStripeWebhookSignatureError extends Error {
36
+ constructor(message: string) {
37
+ super(message);
38
+ this.name = "StripeWebhookSignatureError";
39
+ }
40
+ }
41
+ jest.mock("@working-theory/stripe-client", () => ({
42
+ verifyWebhookSignature: (...args: unknown[]) =>
43
+ mockVerifyWebhookSignature(...args),
44
+ StripeWebhookSignatureError: MockStripeWebhookSignatureError,
45
+ assertSandboxKey: jest.fn(),
46
+ createCheckoutSession: jest.fn(),
47
+ getStripeClient: jest.fn(),
48
+ STRIPE_API_VERSION: "2024-06-20",
49
+ StripeConfigError: class StripeConfigError extends Error {},
50
+ UnknownProductCodeError: class UnknownProductCodeError extends Error {},
51
+ WEBHOOK_TOLERANCE_SECONDS: 300,
52
+ }));
53
+
54
+ import * as configModule from "../../../config/index.js";
55
+ import { stripeWebhookHandler } from "../stripe.js";
56
+ import { __resetIdempotencyStore } from "../../../lib/webhook-idempotency.js";
57
+ import { prisma } from "../../../lib/prisma.js";
58
+
59
+ const PAYMENT_LINK_ID = "plink_test_1234567890";
60
+ const SESSION_ID = "cs_test_session_12345678901234567890";
61
+ const INVOICE_ID = "in_test_12345678901234567890";
62
+
63
+ let eventCounter = 0;
64
+ function sessionEvent(object: Record<string, unknown> = {}, eventId?: string) {
65
+ eventCounter += 1;
66
+ return {
67
+ id: eventId ?? `evt_test_${eventCounter}`,
68
+ type: "checkout.session.completed",
69
+ data: {
70
+ object: {
71
+ id: SESSION_ID,
72
+ payment_status: "paid",
73
+ amount_total: 12900,
74
+ currency: "usd",
75
+ payment_link: PAYMENT_LINK_ID,
76
+ ...object,
77
+ },
78
+ },
79
+ };
80
+ }
81
+
82
+ function invoiceEvent(object: Record<string, unknown> = {}) {
83
+ eventCounter += 1;
84
+ return {
85
+ id: `evt_test_${eventCounter}`,
86
+ type: "invoice.paid",
87
+ data: { object: { id: INVOICE_ID, ...object } },
88
+ };
89
+ }
90
+
91
+ async function buildTestServer(): Promise<FastifyInstance> {
92
+ const server = Fastify({ logger: false });
93
+ await server.register(async (webhooksScope) => {
94
+ webhooksScope.addContentTypeParser(
95
+ "application/json",
96
+ { parseAs: "buffer" },
97
+ (_req, body, done) => {
98
+ done(null, body);
99
+ },
100
+ );
101
+ webhooksScope.post("/v1/webhooks/stripe", stripeWebhookHandler);
102
+ });
103
+ await server.ready();
104
+ return server;
105
+ }
106
+
107
+ const VALID_BODY = Buffer.from(JSON.stringify({ type: "test" }));
108
+ const HEADERS = {
109
+ "content-type": "application/json",
110
+ "stripe-signature": "t=1234,v1=abc",
111
+ };
112
+
113
+ let server: FastifyInstance;
114
+
115
+ beforeEach(async () => {
116
+ jest.clearAllMocks();
117
+ __resetIdempotencyStore();
118
+ jest.replaceProperty(configModule, "config", {
119
+ ...configModule.config,
120
+ STRIPE_WEBHOOK_SECRET: "whsec_test_secret",
121
+ });
122
+ (prisma.callback.updateMany as jest.Mock).mockResolvedValue({ count: 1 });
123
+ (prisma.flowCheckout.findUnique as jest.Mock).mockResolvedValue(null);
124
+ (prisma.flowCheckout.updateMany as jest.Mock).mockResolvedValue({ count: 0 });
125
+ (prisma.user.findFirst as jest.Mock).mockResolvedValue(null);
126
+ (prisma.user.findUnique as jest.Mock).mockResolvedValue(null);
127
+ server = await buildTestServer();
128
+ });
129
+
130
+ afterEach(async () => {
131
+ await server.close();
132
+ jest.restoreAllMocks();
133
+ });
134
+
135
+ async function post() {
136
+ return server.inject({
137
+ method: "POST",
138
+ url: "/v1/webhooks/stripe",
139
+ payload: VALID_BODY,
140
+ headers: HEADERS,
141
+ });
142
+ }
143
+
144
+ // ── The match, and the guard that makes it once-only ───────────────────────
145
+
146
+ describe("a paid Payment-Link session stamps the matching row exactly once", () => {
147
+ it("matches on the artifact id and guards on paidAt: null", async () => {
148
+ mockVerifyWebhookSignature.mockReturnValue(sessionEvent());
149
+ const res = await post();
150
+ expect(res.statusCode).toBe(200);
151
+
152
+ expect(prisma.callback.updateMany).toHaveBeenCalledTimes(1);
153
+ const call = (prisma.callback.updateMany as jest.Mock).mock.calls[0][0];
154
+ expect(call.where).toEqual({
155
+ stripePaymentLinkId: PAYMENT_LINK_ID,
156
+ paidAt: null,
157
+ });
158
+ expect(call.data.paidAt).toBeInstanceOf(Date);
159
+ // Written from the same event, for support lookups — never a match key.
160
+ expect(call.data.stripeCheckoutSessionId).toBe(SESSION_ID);
161
+ });
162
+
163
+ it("stamps a zero-due completion too (no_payment_required)", async () => {
164
+ mockVerifyWebhookSignature.mockReturnValue(
165
+ sessionEvent({ payment_status: "no_payment_required" }),
166
+ );
167
+ await post();
168
+ expect(prisma.callback.updateMany).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ it("does NOT stamp a completed-but-unpaid session", async () => {
172
+ mockVerifyWebhookSignature.mockReturnValue(
173
+ sessionEvent({ payment_status: "unpaid" }),
174
+ );
175
+ const res = await post();
176
+ expect(res.statusCode).toBe(200);
177
+ expect(prisma.callback.updateMany).not.toHaveBeenCalled();
178
+ });
179
+ });
180
+
181
+ // ── Replay, both layers ────────────────────────────────────────────────────
182
+
183
+ describe("a replayed event is a no-op", () => {
184
+ it("short-circuits on the same event id with no DB call at all", async () => {
185
+ const event = sessionEvent({}, "evt_test_replay");
186
+ mockVerifyWebhookSignature.mockReturnValue(event);
187
+ await post();
188
+ expect(prisma.callback.updateMany).toHaveBeenCalledTimes(1);
189
+
190
+ (prisma.callback.updateMany as jest.Mock).mockClear();
191
+ mockVerifyWebhookSignature.mockReturnValue(event);
192
+ const second = await post();
193
+ expect(second.statusCode).toBe(200);
194
+ expect(prisma.callback.updateMany).not.toHaveBeenCalled();
195
+ });
196
+
197
+ it("stamps nothing on a DIFFERENT event id for the same session — the dedup-lost-on-restart case", async () => {
198
+ // The in-memory dedup cannot help here; the `paidAt: null` guard is what
199
+ // does, and it reports zero rows because the row is already paid.
200
+ (prisma.callback.updateMany as jest.Mock).mockResolvedValue({ count: 0 });
201
+ mockVerifyWebhookSignature.mockReturnValue(sessionEvent());
202
+ const res = await post();
203
+ expect(res.statusCode).toBe(200);
204
+ const call = (prisma.callback.updateMany as jest.Mock).mock.calls[0][0];
205
+ expect(call.where.paidAt).toBeNull();
206
+ });
207
+ });
208
+
209
+ // ── Unmatched events leave every row untouched ─────────────────────────────
210
+
211
+ describe("an unmatched event touches nothing", () => {
212
+ it("issues NO query at all for an ordinary checkout with no payment_link", async () => {
213
+ mockVerifyWebhookSignature.mockReturnValue(
214
+ sessionEvent({ payment_link: null }),
215
+ );
216
+ const res = await post();
217
+ expect(res.statusCode).toBe(200);
218
+ expect(prisma.callback.updateMany).not.toHaveBeenCalled();
219
+ });
220
+
221
+ it("stamps no row for an unknown payment link, and still answers 200", async () => {
222
+ (prisma.callback.updateMany as jest.Mock).mockResolvedValue({ count: 0 });
223
+ mockVerifyWebhookSignature.mockReturnValue(
224
+ sessionEvent({ payment_link: "plink_never_minted_here" }),
225
+ );
226
+ const res = await post();
227
+ expect(res.statusCode).toBe(200);
228
+ expect(JSON.parse(res.body)).toEqual({ received: true });
229
+ });
230
+
231
+ it("never throws out of the handler when the stamp's own query rejects", async () => {
232
+ (prisma.callback.updateMany as jest.Mock).mockRejectedValue(
233
+ new Error("connection reset"),
234
+ );
235
+ mockVerifyWebhookSignature.mockReturnValue(sessionEvent());
236
+ const res = await post();
237
+ // Always 200 for a verified event — a throw here would make Stripe retry
238
+ // and re-fire the sibling telemetry and billing tasks.
239
+ expect(res.statusCode).toBe(200);
240
+ });
241
+ });
242
+
243
+ // ── The inert invoice rung ─────────────────────────────────────────────────
244
+
245
+ describe("invoice.paid carries the same guarded stamp", () => {
246
+ it("matches on stripeInvoiceId under the same paidAt: null guard", async () => {
247
+ mockVerifyWebhookSignature.mockReturnValue(invoiceEvent());
248
+ const res = await post();
249
+ expect(res.statusCode).toBe(200);
250
+ const call = (prisma.callback.updateMany as jest.Mock).mock.calls[0][0];
251
+ expect(call.where).toEqual({ stripeInvoiceId: INVOICE_ID, paidAt: null });
252
+ expect(call.data.paidAt).toBeInstanceOf(Date);
253
+ });
254
+
255
+ it("is a 200 no-op when the invoice belongs to something else", async () => {
256
+ (prisma.callback.updateMany as jest.Mock).mockResolvedValue({ count: 0 });
257
+ mockVerifyWebhookSignature.mockReturnValue(invoiceEvent());
258
+ expect((await post()).statusCode).toBe(200);
259
+ });
260
+ });
261
+
262
+ // ── The signature still settles first ──────────────────────────────────────
263
+
264
+ describe("the signature is still settled before anything else", () => {
265
+ it("answers 400 on an unsigned body and stamps nothing", async () => {
266
+ mockVerifyWebhookSignature.mockImplementation(() => {
267
+ throw new MockStripeWebhookSignatureError("bad signature");
268
+ });
269
+ const res = await server.inject({
270
+ method: "POST",
271
+ url: "/v1/webhooks/stripe",
272
+ payload: VALID_BODY,
273
+ headers: { "content-type": "application/json" },
274
+ });
275
+ expect(res.statusCode).toBe(400);
276
+ expect(prisma.callback.updateMany).not.toHaveBeenCalled();
277
+ });
278
+ });
279
+
280
+ // ── The existing flow-checkout behaviour is byte-identical ─────────────────
281
+
282
+ describe("the recommender's own stamp is unchanged beside it", () => {
283
+ it("still runs its own guarded update for an ordinary recommender session", async () => {
284
+ (prisma.flowCheckout.updateMany as jest.Mock).mockResolvedValue({ count: 1 });
285
+ (prisma.flowCheckout.findUnique as jest.Mock).mockResolvedValue({
286
+ amountMinor: 12900,
287
+ });
288
+ mockVerifyWebhookSignature.mockReturnValue(
289
+ sessionEvent({
290
+ payment_link: null,
291
+ metadata: { flowCheckoutId: "flchk_1" },
292
+ }),
293
+ );
294
+ const res = await post();
295
+ expect(res.statusCode).toBe(200);
296
+ const call = (prisma.flowCheckout.updateMany as jest.Mock).mock.calls[0][0];
297
+ expect(call.where).toEqual({ id: "flchk_1", status: "created" });
298
+ // And the request stamp stayed out of it entirely.
299
+ expect(prisma.callback.updateMany).not.toHaveBeenCalled();
300
+ });
301
+ });