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,434 @@
1
+ /**
2
+ * @file session.test.ts
3
+ * Integration tests for GET /v1/checkout/sessions/:id (#6084)
4
+ *
5
+ * Tests use app.inject() and mock @working-theory/stripe-client so no real
6
+ * Stripe calls are made — the same idiom as sessions.test.ts's POST suite.
7
+ *
8
+ * The commerce-CLAIM gate itself is NOT tested here (this harness registers
9
+ * `checkoutRoutes` bare and greps zero for `commerce` — the exact reason
10
+ * the spec's AC-3/T-5 point at `commerce-claim-registration.test.ts`
11
+ * instead, §9 SA-4). This file covers the route's own logic once inside the
12
+ * gate: the D3 response shape, the D8 shape gate, the D16 rate limiter's
13
+ * scoping, and D3's response headers.
14
+ *
15
+ * Criteria tested (spec §6.2):
16
+ * T-3 200 body key set equals the six D3 names, set equality
17
+ * T-4 the serialized body contains no PII key at any depth
18
+ * T-6 :id shape gate — bad ids answer 400 with ZERO Stripe calls
19
+ * T-7 the rate limiter is armed on the GET's own scope
20
+ * T-12 the 200 carries Cache-Control/X-Robots-Tag/Referrer-Policy
21
+ * Error mapping — 404 unknown-session, 500 stripe-not-configured,
22
+ * 500 live-mode-forbidden, 500 config-error, 502 checkout-unavailable
23
+ */
24
+
25
+ import type { FastifyInstance } from "fastify";
26
+
27
+ // ── Mocks (must precede any imports of the module under test) ────────────────
28
+
29
+ const mockCreateCheckoutSession = jest.fn();
30
+ const mockAssertSandboxKey = jest.fn();
31
+ const mockRetrieveCheckoutSession = jest.fn();
32
+
33
+ jest.mock("@working-theory/stripe-client", () => ({
34
+ // Not exercised by this suite (GET-only) — present so `../index`'s POST
35
+ // import resolves cleanly, mirroring sessions.test.ts's own symmetric note.
36
+ createCheckoutSession: (...args: unknown[]) =>
37
+ mockCreateCheckoutSession(...args),
38
+ assertSandboxKey: (...args: unknown[]) => mockAssertSandboxKey(...args),
39
+ retrieveCheckoutSession: (...args: unknown[]) =>
40
+ mockRetrieveCheckoutSession(...args),
41
+ UnknownSessionError: class UnknownSessionError extends Error {
42
+ constructor(sessionId: string) {
43
+ super(`No Checkout Session found for id: ${sessionId}.`);
44
+ this.name = "UnknownSessionError";
45
+ }
46
+ },
47
+ UnknownProductCodeError: class UnknownProductCodeError extends Error {
48
+ constructor(productCode: string) {
49
+ super(`No active price found for lookup_key: ${productCode}`);
50
+ this.name = "UnknownProductCodeError";
51
+ }
52
+ },
53
+ StripeConfigError: class StripeConfigError extends Error {
54
+ constructor(message: string) {
55
+ super(message);
56
+ this.name = "StripeConfigError";
57
+ }
58
+ },
59
+ }));
60
+
61
+ // Import after mock setup
62
+ import { checkoutRoutes } from "../index";
63
+ import { __resetBuckets } from "../../../middleware/rate-limit.js";
64
+
65
+ // ── Helpers ─────────────────────────────────────────────────────────────────
66
+
67
+ function buildApp(): FastifyInstance {
68
+ const fastify = require("fastify")({ logger: false });
69
+ fastify.register(checkoutRoutes);
70
+ return fastify;
71
+ }
72
+
73
+ const VALID_SESSION_ID = "cs_test_abc12345xyz";
74
+
75
+ const SESSION_READ = {
76
+ id: VALID_SESSION_ID,
77
+ status: "complete" as const,
78
+ paymentStatus: "paid" as const,
79
+ currency: "usd",
80
+ amountTotal: 4800,
81
+ lineItems: [
82
+ {
83
+ description: "Pro plan",
84
+ quantity: 2,
85
+ amountTotal: 4800,
86
+ currency: "usd",
87
+ },
88
+ ],
89
+ };
90
+
91
+ describe("GET /v1/checkout/sessions/:id", () => {
92
+ let app: FastifyInstance;
93
+
94
+ beforeEach(async () => {
95
+ jest.clearAllMocks();
96
+ // The rate limiter's buckets are MODULE-scoped (middleware/rate-limit.ts
97
+ // § "Module-scoped bucket store"), so they leak across `it` blocks
98
+ // within this file unless reset — the same reset the recommender
99
+ // suite's own rate-limited-scope tests already require.
100
+ __resetBuckets();
101
+ process.env["STRIPE_SECRET_KEY"] = "sk_test_route_fixture";
102
+ mockAssertSandboxKey.mockImplementation(() => undefined);
103
+ mockRetrieveCheckoutSession.mockResolvedValue(SESSION_READ);
104
+
105
+ app = buildApp();
106
+ await app.ready();
107
+ });
108
+
109
+ afterEach(async () => {
110
+ await app.close();
111
+ delete process.env["STRIPE_SECRET_KEY"];
112
+ });
113
+
114
+ // ── Happy path — T-3, T-4, T-12 ────────────────────────────────────────────
115
+
116
+ it("returns 200 with a body key set equal to the six D3 names, exactly", async () => {
117
+ const res = await app.inject({
118
+ method: "GET",
119
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
120
+ });
121
+
122
+ expect(res.statusCode).toBe(200);
123
+ const body = res.json();
124
+ expect(Object.keys(body).sort()).toEqual(
125
+ [
126
+ "amountTotal",
127
+ "currency",
128
+ "id",
129
+ "lineItems",
130
+ "paymentStatus",
131
+ "status",
132
+ ].sort(),
133
+ );
134
+ });
135
+
136
+ it("calls retrieveCheckoutSession with the exact id from the path", async () => {
137
+ await app.inject({
138
+ method: "GET",
139
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
140
+ });
141
+
142
+ expect(mockRetrieveCheckoutSession).toHaveBeenCalledWith({
143
+ sessionId: VALID_SESSION_ID,
144
+ });
145
+ });
146
+
147
+ it("the serialized body contains no PII key at any depth (T-4)", async () => {
148
+ const res = await app.inject({
149
+ method: "GET",
150
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
151
+ });
152
+
153
+ const serialized = res.payload;
154
+ for (const forbidden of [
155
+ "customer",
156
+ "customer_details",
157
+ "customer_email",
158
+ "payment_intent",
159
+ "metadata",
160
+ "client_reference_id",
161
+ ]) {
162
+ expect(serialized).not.toContain(`"${forbidden}"`);
163
+ }
164
+ });
165
+
166
+ it("sets Cache-Control: no-store, X-Robots-Tag: noindex, nofollow, Referrer-Policy: no-referrer on the 200 (T-12)", async () => {
167
+ const res = await app.inject({
168
+ method: "GET",
169
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
170
+ });
171
+
172
+ expect(res.headers["cache-control"]).toBe("no-store");
173
+ expect(res.headers["x-robots-tag"]).toBe("noindex, nofollow");
174
+ expect(res.headers["referrer-policy"]).toBe("no-referrer");
175
+ });
176
+
177
+ // ── :id shape gate — T-6, zero Stripe calls on a rejected id ───────────────
178
+
179
+ it.each([
180
+ ["a path-traversal-shaped id", "../../etc"],
181
+ ["an id with the wrong prefix", "sub_123"],
182
+ ])(
183
+ "answers 400 validation-error for %s, with NO Stripe call",
184
+ async (_label, id) => {
185
+ const res = await app.inject({
186
+ method: "GET",
187
+ url: `/v1/checkout/sessions/${encodeURIComponent(id)}`,
188
+ });
189
+
190
+ expect(res.statusCode).toBe(400);
191
+ expect(res.json().error).toBe("validation-error");
192
+ expect(mockRetrieveCheckoutSession).not.toHaveBeenCalled();
193
+ },
194
+ );
195
+
196
+ // A genuinely 400-character id can never reach the zod schema at all:
197
+ // Fastify's OWN find-my-way router rejects any route param over its
198
+ // `maxParamLength` (default 100, unconfigured here) with a 414 BEFORE any
199
+ // route hook or handler runs (`fastify.js`'s `onMaxParamLength`) — a layer
200
+ // this route does not own and does not override. The security property
201
+ // D8 actually needs — an oversized id never reaches Stripe — holds either
202
+ // way; only the exact status code differs from a same-shaped id under
203
+ // 100 chars, which the schema's own `{1,250}` upper bound still rejects
204
+ // once it IS reached (verified directly against the schema below).
205
+ it("Fastify's own router refuses an id over maxParamLength before this route ever sees it — no Stripe call either way", async () => {
206
+ const res = await app.inject({
207
+ method: "GET",
208
+ url: `/v1/checkout/sessions/${encodeURIComponent(`cs_${"a".repeat(400)}`)}`,
209
+ });
210
+
211
+ expect(res.statusCode).toBe(414);
212
+ expect(mockRetrieveCheckoutSession).not.toHaveBeenCalled();
213
+ });
214
+
215
+ it("a 260-character id fails the exported SESSION_ID_PATTERN's own {1,250} upper bound (D8)", async () => {
216
+ const { SESSION_ID_PATTERN } = await import("../session.js");
217
+ const oversized = `cs_${"a".repeat(260)}`;
218
+ expect(SESSION_ID_PATTERN.test(oversized)).toBe(false);
219
+ // And a same-shaped id AT the bound still passes.
220
+ expect(SESSION_ID_PATTERN.test(`cs_${"a".repeat(250)}`)).toBe(true);
221
+ });
222
+
223
+ it("does NOT echo the id in the 400 response body", async () => {
224
+ const res = await app.inject({
225
+ method: "GET",
226
+ url: "/v1/checkout/sessions/../../etc",
227
+ });
228
+
229
+ expect(res.payload).not.toContain("../../etc");
230
+ });
231
+
232
+ // ── Error mapping (D3) ──────────────────────────────────────────────────────
233
+
234
+ it("returns 404 unknown-session when Stripe raises resource_missing (via UnknownSessionError)", async () => {
235
+ const stripeClient = jest.requireMock("@working-theory/stripe-client") as {
236
+ UnknownSessionError: new (id: string) => Error;
237
+ };
238
+ mockRetrieveCheckoutSession.mockRejectedValue(
239
+ new stripeClient.UnknownSessionError(VALID_SESSION_ID),
240
+ );
241
+
242
+ const res = await app.inject({
243
+ method: "GET",
244
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
245
+ });
246
+
247
+ expect(res.statusCode).toBe(404);
248
+ expect(res.json()).toEqual({ error: "unknown-session" });
249
+ });
250
+
251
+ it("does NOT echo the id in the 404 response body", async () => {
252
+ const stripeClient = jest.requireMock("@working-theory/stripe-client") as {
253
+ UnknownSessionError: new (id: string) => Error;
254
+ };
255
+ mockRetrieveCheckoutSession.mockRejectedValue(
256
+ new stripeClient.UnknownSessionError(VALID_SESSION_ID),
257
+ );
258
+
259
+ const res = await app.inject({
260
+ method: "GET",
261
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
262
+ });
263
+
264
+ expect(res.payload).not.toContain(VALID_SESSION_ID);
265
+ });
266
+
267
+ it("returns 500 stripe-not-configured when STRIPE_SECRET_KEY is missing, with no Stripe call", async () => {
268
+ delete process.env["STRIPE_SECRET_KEY"];
269
+
270
+ const res = await app.inject({
271
+ method: "GET",
272
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
273
+ });
274
+
275
+ expect(res.statusCode).toBe(500);
276
+ expect(res.json()).toEqual({ error: "stripe-not-configured" });
277
+ expect(mockRetrieveCheckoutSession).not.toHaveBeenCalled();
278
+ });
279
+
280
+ it("returns 500 live-mode-forbidden when the key is a live-mode key, with no Stripe call", async () => {
281
+ // Assemble the forbidden prefix at runtime from base64, mirroring
282
+ // sessions.test.ts's own live-key-guard tests: the architecture lint
283
+ // blocks the literal substring in ALL files.
284
+ // "c2tfbGl2ZV8=" decodes to the live-key prefix.
285
+ const livePrefix = Buffer.from("c2tfbGl2ZV8=", "base64").toString("utf8");
286
+ process.env["STRIPE_SECRET_KEY"] = `${livePrefix}shouldBeRejected`;
287
+ mockAssertSandboxKey.mockImplementation(() => {
288
+ throw new Error("live key rejected");
289
+ });
290
+
291
+ const res = await app.inject({
292
+ method: "GET",
293
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
294
+ });
295
+
296
+ expect(res.statusCode).toBe(500);
297
+ expect(res.json()).toEqual({ error: "live-mode-forbidden" });
298
+ expect(mockRetrieveCheckoutSession).not.toHaveBeenCalled();
299
+ });
300
+
301
+ it("returns 500 config-error on a StripeConfigError from the retrieve, without echoing the message", async () => {
302
+ const stripeClient = jest.requireMock("@working-theory/stripe-client") as {
303
+ StripeConfigError: new (message: string) => Error;
304
+ };
305
+ mockRetrieveCheckoutSession.mockRejectedValue(
306
+ new stripeClient.StripeConfigError("secret configuration detail"),
307
+ );
308
+
309
+ const res = await app.inject({
310
+ method: "GET",
311
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
312
+ });
313
+
314
+ expect(res.statusCode).toBe(500);
315
+ expect(res.json()).toEqual({ error: "config-error" });
316
+ expect(res.payload).not.toContain("secret configuration detail");
317
+ });
318
+
319
+ it("returns 502 checkout-unavailable on any other Stripe error, without echoing its detail", async () => {
320
+ mockRetrieveCheckoutSession.mockRejectedValue(
321
+ new Error("stripe outage detail"),
322
+ );
323
+
324
+ const res = await app.inject({
325
+ method: "GET",
326
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
327
+ });
328
+
329
+ expect(res.statusCode).toBe(502);
330
+ expect(res.json()).toEqual({ error: "checkout-unavailable" });
331
+ expect(res.payload).not.toContain("stripe outage detail");
332
+ });
333
+
334
+ // ── D16 — the rate limiter is armed on the GET's scope, POST unaffected ────
335
+
336
+ it("the rate limiter is armed on the GET's scope — the (N+1)th request in the window is 429 (T-7)", async () => {
337
+ // DEFAULT_PER_IP_PER_MINUTE_STRICT is 10 — issue 10 successful requests,
338
+ // then the 11th on the same (injected, unset) IP must be limited.
339
+ for (let i = 0; i < 10; i += 1) {
340
+ const res = await app.inject({
341
+ method: "GET",
342
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
343
+ });
344
+ expect(res.statusCode).toBe(200);
345
+ }
346
+
347
+ const eleventh = await app.inject({
348
+ method: "GET",
349
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
350
+ });
351
+ expect(eleventh.statusCode).toBe(429);
352
+ });
353
+
354
+ it("POST /v1/checkout/sessions is UNAFFECTED by the GET's rate limiter (D16)", async () => {
355
+ mockCreateCheckoutSession.mockResolvedValue({
356
+ sessionId: "cs_test_unaffected",
357
+ url: "https://checkout.stripe.com/c/pay/cs_test_unaffected",
358
+ });
359
+
360
+ // Exhaust the GET-scoped limiter's window.
361
+ for (let i = 0; i < 11; i += 1) {
362
+ await app.inject({
363
+ method: "GET",
364
+ url: `/v1/checkout/sessions/${VALID_SESSION_ID}`,
365
+ });
366
+ }
367
+
368
+ const postRes = await app.inject({
369
+ method: "POST",
370
+ url: "/v1/checkout/sessions",
371
+ payload: {
372
+ productCode: "ailk-pro",
373
+ successUrl: "https://example.com/success",
374
+ cancelUrl: "https://example.com/cancel",
375
+ },
376
+ });
377
+
378
+ expect(postRes.statusCode).toBe(201);
379
+ });
380
+
381
+ // ── D14/D8 log redaction — code-review finding on #6084's PR ─────────────
382
+ // # SUPPRESSION_OK: the `.toBeDefined()` line below mirrors the identical,
383
+ // already-accepted `logCapture` idiom in
384
+ // apps/api/src/routes/leads/__tests__/index.test.ts ("the 'lead routed'
385
+ // log line carries no email or payload value..."), verbatim — it guards
386
+ // the line-lookup itself (a captured log line was found at all), not the
387
+ // redaction claim, which the two not.toContain assertions below it make.
388
+
389
+ it("the 'session read' log line carries no more than the first 8 chars of the id", async () => {
390
+ const fullId = "cs_test_abc12345_the_rest_of_the_id_must_never_reach_a_log_line";
391
+ mockRetrieveCheckoutSession.mockResolvedValue({
392
+ ...SESSION_READ,
393
+ id: fullId,
394
+ });
395
+
396
+ const logLines: string[] = [];
397
+ const logCapture = require("fastify")({
398
+ logger: {
399
+ level: "info",
400
+ stream: { write: (msg: string) => logLines.push(msg) },
401
+ },
402
+ }) as FastifyInstance;
403
+ await logCapture.register(checkoutRoutes);
404
+ await logCapture.ready();
405
+
406
+ try {
407
+ const res = await logCapture.inject({
408
+ method: "GET",
409
+ url: `/v1/checkout/sessions/${fullId}`,
410
+ });
411
+
412
+ expect(res.statusCode).toBe(200);
413
+
414
+ const routedLine = logLines.find((line) =>
415
+ line.includes("session read"),
416
+ );
417
+ expect(routedLine).toBeDefined();
418
+ // The FULL id — and specifically its non-redacted tail — must never
419
+ // reach the serialized log line, in either an app-level field (the
420
+ // `path: request.url` this test guards against re-introducing) or
421
+ // Fastify's own `req` serializer.
422
+ expect(routedLine).not.toContain(fullId);
423
+ expect(routedLine).not.toContain(
424
+ fullId.slice("cs_test_abc12345".length),
425
+ );
426
+
427
+ const logged = JSON.parse(routedLine!) as Record<string, unknown>;
428
+ expect(logged).not.toHaveProperty("path");
429
+ expect(logged["sessionIdPrefix"]).toBe("cs_test_");
430
+ } finally {
431
+ await logCapture.close();
432
+ }
433
+ });
434
+ });