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,34 @@
1
+ /**
2
+ * Checkout route group.
3
+ *
4
+ * Routes:
5
+ * POST /v1/checkout/sessions — create a Stripe hosted-page checkout session
6
+ * GET /v1/checkout/sessions/:id — read a session's payment status + line items (#6084)
7
+ *
8
+ * The GET registers inside a NESTED scope carrying its own rate limiter
9
+ * (D16) — the same composition idiom the sibling `requests` scope uses
10
+ * (`server.ts`'s `requestsScope`). Nesting keeps the POST above
11
+ * byte-identical: the limiter applies only to routes registered on
12
+ * `readScope`. Both routes inherit `requireCommerceClaim` from the OUTER
13
+ * `checkoutScope` in `server.ts` — no gate code is written in either file.
14
+ */
15
+ import type { FastifyPluginAsync } from "fastify";
16
+
17
+ import {
18
+ DEFAULT_PER_IP_PER_MINUTE_STRICT,
19
+ rateLimitMiddleware,
20
+ } from "../../middleware/rate-limit.js";
21
+
22
+ import { checkoutSessionReadHandler } from "./session.js";
23
+ import { checkoutSessionsHandler } from "./sessions.js";
24
+
25
+ export const checkoutRoutes: FastifyPluginAsync = async (fastify) => {
26
+ fastify.post("/v1/checkout/sessions", checkoutSessionsHandler);
27
+
28
+ await fastify.register(async (readScope) => {
29
+ await readScope.register(rateLimitMiddleware, {
30
+ perIpPerMinute: DEFAULT_PER_IP_PER_MINUTE_STRICT,
31
+ });
32
+ readScope.get("/v1/checkout/sessions/:id", checkoutSessionReadHandler);
33
+ });
34
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * GET /v1/checkout/sessions/:id
3
+ *
4
+ * Reads a Checkout Session's payment status + paid line items — a READ,
5
+ * never a write (the webhook already granted; #6084 spec §1.2). Registered
6
+ * inside `checkoutRoutes` (`index.ts`), which inherits the SAME
7
+ * `requireCommerceClaim` scope preHandler `sessions.ts`'s POST does — no
8
+ * gate code is written here (spec §1.4, D8 layer 3).
9
+ *
10
+ * Security invariants (spec D8):
11
+ * - `:id` shape-gated BEFORE any Stripe call — a non-matching id answers
12
+ * 400 with zero SDK invocations, so this route is not a free Stripe-API
13
+ * proxy.
14
+ * - PII-free response: an explicit six-key allow-list (D3) — never a
15
+ * spread of the retrieve's projection.
16
+ * - The id is never echoed in an error body, and only the first 8
17
+ * characters ever reach a log line — mirroring `sessions.ts`'s own
18
+ * stated invariant.
19
+ * - `Cache-Control: no-store`, `X-Robots-Tag: noindex, nofollow`,
20
+ * `Referrer-Policy: no-referrer` on the 200 — the same three headers
21
+ * `routes/requests/confirm.ts` sets on its own unauthenticated,
22
+ * token-bearing response (D3, AC-9).
23
+ * - Deliberately NOT gated by `X-Internal-Auth` (D8 layer 4 — withdrawn;
24
+ * no `apps/web` file holds `OSS_API_INTERNAL_SECRET`).
25
+ *
26
+ * Error mapping mirrors `sessions.ts`'s own vocabulary (D3):
27
+ * 400 validation-error — `:id` fails the shape check
28
+ * 404 unknown-session — Stripe raised `resource_missing`
29
+ * 500 stripe-not-configured — STRIPE_SECRET_KEY is missing/empty
30
+ * 500 live-mode-forbidden — STRIPE_SECRET_KEY is a live key
31
+ * 500 config-error — STRIPE_SECRET_KEY invalid configuration
32
+ * 502 checkout-unavailable — Stripe SDK threw an unexpected error
33
+ */
34
+ import type { FastifyReply, FastifyRequest } from "fastify";
35
+ import { z } from "zod";
36
+
37
+ import {
38
+ assertSandboxKey,
39
+ retrieveCheckoutSession,
40
+ StripeConfigError,
41
+ UnknownSessionError,
42
+ } from "../../lib/stripe.js";
43
+
44
+ // A Stripe Checkout Session id is an unguessable bearer capability, not an
45
+ // authorization-checked resource (D8). The shape gate is layer 2 of D8's
46
+ // defense: it runs BEFORE any Stripe call, so a non-matching id can never
47
+ // spend a Stripe API call. Exported so the test suite can assert the upper
48
+ // bound directly — an id over Fastify's OWN `maxParamLength` (default 100,
49
+ // unconfigured here) never reaches this schema at all, and the test suite
50
+ // covers that layer separately.
51
+ export const SESSION_ID_PATTERN = /^cs_[A-Za-z0-9_]{1,250}$/;
52
+
53
+ const CheckoutSessionParamsSchema = z.object({
54
+ id: z
55
+ .string()
56
+ .regex(SESSION_ID_PATTERN, "id must match /^cs_[A-Za-z0-9_]{1,250}$/"),
57
+ });
58
+
59
+ /** D3's response headers — the unauthenticated-bearer-credential precedent (`confirm.ts:41-43`). */
60
+ function setPrivateResponseHeaders(reply: FastifyReply): void {
61
+ reply.header("Cache-Control", "no-store");
62
+ reply.header("X-Robots-Tag", "noindex, nofollow");
63
+ reply.header("Referrer-Policy", "no-referrer");
64
+ }
65
+
66
+ export async function checkoutSessionReadHandler(
67
+ request: FastifyRequest,
68
+ reply: FastifyReply,
69
+ ): Promise<void> {
70
+ const parsed = CheckoutSessionParamsSchema.safeParse(request.params);
71
+ if (!parsed.success) {
72
+ return reply.status(400).send({
73
+ error: "validation-error",
74
+ details: parsed.error.issues,
75
+ });
76
+ }
77
+
78
+ const stripeSecretKey = process.env["STRIPE_SECRET_KEY"];
79
+ if (!stripeSecretKey) {
80
+ return reply.status(500).send({ error: "stripe-not-configured" });
81
+ }
82
+ try {
83
+ assertSandboxKey(stripeSecretKey);
84
+ } catch {
85
+ return reply.status(500).send({ error: "live-mode-forbidden" });
86
+ }
87
+
88
+ const { id } = parsed.data;
89
+
90
+ try {
91
+ const session = await retrieveCheckoutSession({ sessionId: id });
92
+
93
+ request.log.info(
94
+ {
95
+ method: request.method,
96
+ sessionIdPrefix: id.substring(0, 8),
97
+ status: 200,
98
+ },
99
+ "[checkout] session read",
100
+ );
101
+
102
+ setPrivateResponseHeaders(reply);
103
+ // The D3 allow-list, named field-for-field — never a spread. Nothing
104
+ // else may join this object (AC-3's set-equality test pins exactly
105
+ // these six keys).
106
+ return reply.status(200).send({
107
+ id: session.id,
108
+ status: session.status,
109
+ paymentStatus: session.paymentStatus,
110
+ currency: session.currency,
111
+ amountTotal: session.amountTotal,
112
+ lineItems: session.lineItems,
113
+ });
114
+ } catch (err) {
115
+ if (err instanceof UnknownSessionError) {
116
+ // 404 — never echo the id in the response body.
117
+ return reply.status(404).send({ error: "unknown-session" });
118
+ }
119
+ if (err instanceof StripeConfigError) {
120
+ request.log.error(
121
+ {
122
+ method: request.method,
123
+ errName: err.name,
124
+ message: err.message,
125
+ },
126
+ "[checkout] config error",
127
+ );
128
+ return reply.status(500).send({ error: "config-error" });
129
+ }
130
+ request.log.error(
131
+ {
132
+ method: request.method,
133
+ errName: err instanceof Error ? err.name : "unknown",
134
+ },
135
+ "[checkout] stripe error; checkout-unavailable",
136
+ );
137
+ return reply.status(502).send({ error: "checkout-unavailable" });
138
+ }
139
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * POST /api/checkout/sessions
3
+ *
4
+ * Generic Stripe checkout primitive. Resolves a productCode to a Stripe
5
+ * hosted-page checkout session and returns the session ID + URL. The session
6
+ * mode is derived from the resolved price by the stripe-client (a recurring
7
+ * price opens a subscription session), so this route serves both (#4398).
8
+ *
9
+ * Security invariants:
10
+ * - Sandbox-only: live Stripe keys are rejected at runtime before any Stripe call.
11
+ * - No PII in logs: successUrl/cancelUrl and full session IDs are never logged.
12
+ * Only the first 8 chars of the session ID appear in log lines.
13
+ * - Error responses never echo user input (productCode, URLs).
14
+ * - No DB write: this unit creates a Stripe session only; webhook-driven
15
+ * persistence is a follow-up issue.
16
+ * - X-Internal-Auth gate: when OSS_API_INTERNAL_SECRET env var is set,
17
+ * the X-Internal-Auth header must match; requests without a matching header
18
+ * are rejected with 401. When the env var is unset, no gate is applied
19
+ * (backward-compat for OSS standalone deployments). See #935.
20
+ *
21
+ * Error mapping:
22
+ * 401 internal-auth-required — X-Internal-Auth missing/mismatch (only when env var set)
23
+ * 400 validation-error — Zod validation failed (missing/invalid fields)
24
+ * 404 unknown-product-code — no active Stripe price for the given productCode
25
+ * 500 stripe-not-configured — STRIPE_SECRET_KEY is missing/empty (no Stripe call)
26
+ * 500 live-mode-forbidden — STRIPE_SECRET_KEY is a live key
27
+ * 500 config-error — STRIPE_SECRET_KEY invalid configuration (from the SDK wrapper)
28
+ * 502 checkout-unavailable — Stripe SDK threw an unexpected error
29
+ */
30
+ import { timingSafeEqual } from "node:crypto";
31
+
32
+ import type { FastifyReply, FastifyRequest } from "fastify";
33
+ import { z } from "zod";
34
+
35
+ import {
36
+ assertSandboxKey,
37
+ createCheckoutSession,
38
+ StripeConfigError,
39
+ UnknownProductCodeError,
40
+ } from "../../lib/stripe.js";
41
+
42
+ // ── Input validation schema ───────────────────────────────────────────────────
43
+
44
+ /**
45
+ * Validates a redirect URL: must parse and must use http: or https: scheme.
46
+ * All other schemes (javascript:, ftp:, data:, file:, etc.) are rejected.
47
+ * Accepts http: for dev flexibility; Stripe enforces https: on production
48
+ * hosted-page redirects at their end.
49
+ */
50
+ function isValidRedirectUrl(raw: string): boolean {
51
+ let parsed: URL;
52
+ try {
53
+ parsed = new URL(raw);
54
+ } catch {
55
+ return false;
56
+ }
57
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
58
+ }
59
+
60
+ const PRODUCT_CODE_REGEX = /^[A-Za-z0-9._-]{1,200}$/;
61
+
62
+ const CheckoutSessionRequestSchema = z.object({
63
+ productCode: z
64
+ .string()
65
+ .regex(
66
+ PRODUCT_CODE_REGEX,
67
+ "productCode must match /^[A-Za-z0-9._-]{1,200}$/",
68
+ ),
69
+ successUrl: z.string().refine(isValidRedirectUrl, {
70
+ message:
71
+ "successUrl must be a valid URL with a non-empty host and not use javascript: scheme",
72
+ }),
73
+ cancelUrl: z.string().refine(isValidRedirectUrl, {
74
+ message:
75
+ "cancelUrl must be a valid URL with a non-empty host and not use javascript: scheme",
76
+ }),
77
+ quantity: z.number().int().min(1).max(999).optional(),
78
+ allowPromotionCodes: z.boolean().optional(),
79
+ });
80
+
81
+ // ── Internal-auth gate ────────────────────────────────────────────────────────
82
+
83
+ /**
84
+ * Enforce the X-Internal-Auth gate when OSS_API_INTERNAL_SECRET is configured.
85
+ *
86
+ * Gate is env-var-conditional:
87
+ * - env var SET → header must be present and must match exactly
88
+ * - env var UNSET → no gate applied (OSS standalone backward-compat)
89
+ *
90
+ * Returns true if the request should be allowed through; false if it must be
91
+ * rejected with 401.
92
+ *
93
+ * Uses a constant-time string comparison to prevent timing attacks on the
94
+ * secret value. Node's `crypto.timingSafeEqual` requires equal-length Buffers,
95
+ * so we compare lengths first (short-circuit is safe — length mismatch reveals
96
+ * no information about the secret's content beyond its length, which is
97
+ * acceptable for a shared internal secret).
98
+ */
99
+ function passesInternalAuthGate(request: FastifyRequest): boolean {
100
+ const expectedSecret = process.env["OSS_API_INTERNAL_SECRET"];
101
+ // Gate is disabled when env var is not set — OSS standalone backward-compat.
102
+ if (!expectedSecret) return true;
103
+
104
+ const provided = request.headers["x-internal-auth"];
105
+ if (typeof provided !== "string" || !provided) return false;
106
+
107
+ // Constant-time comparison.
108
+ const expectedBuf = Buffer.from(expectedSecret, "utf8");
109
+ const providedBuf = Buffer.from(provided, "utf8");
110
+ if (expectedBuf.length !== providedBuf.length) return false;
111
+
112
+ return timingSafeEqual(expectedBuf, providedBuf);
113
+ }
114
+
115
+ // ── Handler ───────────────────────────────────────────────────────────────────
116
+
117
+ export async function checkoutSessionsHandler(
118
+ request: FastifyRequest,
119
+ reply: FastifyReply,
120
+ ): Promise<void> {
121
+ // ── X-Internal-Auth gate (env-var-conditional) ────────────────────────────
122
+ if (!passesInternalAuthGate(request)) {
123
+ return reply.status(401).send({ error: "internal-auth-required" });
124
+ }
125
+
126
+ const stripeSecretKey = process.env["STRIPE_SECRET_KEY"];
127
+ if (!stripeSecretKey) {
128
+ return reply.status(500).send({ error: "stripe-not-configured" });
129
+ }
130
+ try {
131
+ assertSandboxKey(stripeSecretKey);
132
+ } catch {
133
+ return reply.status(500).send({ error: "live-mode-forbidden" });
134
+ }
135
+
136
+ const parsed = CheckoutSessionRequestSchema.safeParse(request.body);
137
+ if (!parsed.success) {
138
+ return reply.status(400).send({
139
+ error: "validation-error",
140
+ details: parsed.error.issues,
141
+ });
142
+ }
143
+
144
+ const { productCode, successUrl, cancelUrl, quantity, allowPromotionCodes } =
145
+ parsed.data;
146
+
147
+ try {
148
+ const { sessionId, url } = await createCheckoutSession({
149
+ productCode,
150
+ successUrl,
151
+ cancelUrl,
152
+ quantity: quantity ?? 1,
153
+ // Omitted when the caller says nothing — Stripe's default (no promo-code
154
+ // field) is unchanged for every existing caller.
155
+ ...(allowPromotionCodes !== undefined && { allowPromotionCodes }),
156
+ });
157
+
158
+ request.log.info(
159
+ {
160
+ method: request.method,
161
+ path: request.url,
162
+ sessionIdPrefix: sessionId.substring(0, 8),
163
+ status: 201,
164
+ },
165
+ "[checkout] session created",
166
+ );
167
+
168
+ return reply.status(201).send({ sessionId, url });
169
+ } catch (err) {
170
+ if (err instanceof UnknownProductCodeError) {
171
+ // 404 — do NOT echo productCode in the response body
172
+ return reply.status(404).send({ error: "unknown-product-code" });
173
+ }
174
+ if (err instanceof StripeConfigError) {
175
+ // 500 — misconfiguration; log message (no PII) but never echo in response
176
+ request.log.error(
177
+ {
178
+ method: request.method,
179
+ path: request.url,
180
+ errName: err.name,
181
+ message: err.message,
182
+ },
183
+ "[checkout] config error",
184
+ );
185
+ return reply.status(500).send({ error: "config-error" });
186
+ }
187
+ request.log.error(
188
+ {
189
+ method: request.method,
190
+ path: request.url,
191
+ errName: err instanceof Error ? err.name : "unknown",
192
+ },
193
+ "[checkout] stripe error; checkout-unavailable",
194
+ );
195
+ return reply.status(502).send({ error: "checkout-unavailable" });
196
+ }
197
+ }