create-ailk 0.2.0 → 0.4.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 (292) hide show
  1. package/component-catalog.md +50 -53
  2. package/dist/cli.js +19 -9
  3. package/dist/lib/apply-module-patches.d.ts +47 -0
  4. package/dist/lib/apply-module-patches.js +370 -0
  5. package/dist/lib/extract-module-bundle.d.ts +51 -3
  6. package/dist/lib/extract-module-bundle.js +327 -28
  7. package/dist/lib/fetch-module.d.ts +3 -38
  8. package/dist/lib/fetch-module.js +26 -8
  9. package/dist/lib/fetch-template.d.ts +20 -0
  10. package/dist/lib/fetch-template.js +129 -0
  11. package/dist/lib/module-license-gate.d.ts +10 -0
  12. package/dist/lib/module-license-gate.js +13 -4
  13. package/dist/lib/paid-paths.d.ts +16 -0
  14. package/dist/lib/paid-paths.js +90 -0
  15. package/dist/module-architecture.d.ts +197 -23
  16. package/dist/module-architecture.js +820 -25
  17. package/dist/parse-args.d.ts +9 -2
  18. package/dist/parse-args.js +46 -1
  19. package/dist/programmatic.d.ts +28 -2
  20. package/dist/programmatic.js +115 -8
  21. package/dist/surfaces.d.ts +12 -36
  22. package/dist/surfaces.js +77 -5
  23. package/dist/sync-routes.js +31 -1
  24. package/dist/template-manifest.d.ts +92 -0
  25. package/dist/template-manifest.js +168 -0
  26. package/dist/template-names.d.ts +41 -0
  27. package/dist/template-names.js +44 -0
  28. package/package.json +3 -2
  29. package/site-templates/marketing-site-free/ailk-template.json +8 -0
  30. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +103 -0
  31. package/site-templates/marketing-site-free/content/en/PrivacyPage/privacy.mdx +79 -0
  32. package/site-templates/marketing-site-free/content/en/TermsPage/terms.mdx +80 -0
  33. package/site-templates/marketing-site-free/provenance.json +4 -0
  34. package/templates/.claude/agents/web.md +2 -3
  35. package/templates/.claude/rules/architecture.md +5 -5
  36. package/templates/.claude/skills/README.md +2 -1
  37. package/templates/.env.example +21 -9
  38. package/templates/CONVENTIONS.md +6 -7
  39. package/templates/apps/api/.env.example +9 -0
  40. package/templates/apps/api/CLAUDE.md +33 -5
  41. package/templates/apps/api/package.json +0 -1
  42. package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
  43. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
  44. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
  45. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +166 -0
  46. package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
  47. package/templates/apps/api/src/config/index.ts +19 -0
  48. package/templates/apps/api/src/config/modules.ts +12 -14
  49. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  50. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
  51. package/templates/apps/api/src/lib/slice-load.ts +90 -0
  52. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
  53. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
  54. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
  55. package/templates/apps/api/src/openapi/index.ts +9 -3
  56. package/templates/apps/api/src/openapi/spec.ts +505 -65
  57. package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
  58. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
  59. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
  60. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
  61. package/templates/apps/api/src/routes/content/index.ts +38 -11
  62. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
  63. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
  64. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
  65. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
  66. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
  67. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
  68. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
  69. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
  70. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
  71. package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
  72. package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
  73. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
  74. package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
  75. package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
  76. package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
  77. package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
  78. package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
  79. package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
  80. package/templates/apps/api/src/server.ts +504 -137
  81. package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
  82. package/templates/apps/api/src/services/listing-config.ts +58 -0
  83. package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
  84. package/templates/apps/api/src/services/project-listings.ts +229 -4
  85. package/templates/apps/api/src/vercel-handler.ts +60 -26
  86. package/templates/apps/mcp/.env.example +8 -0
  87. package/templates/apps/mcp/CLAUDE.md +2 -2
  88. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
  89. package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
  90. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
  91. package/templates/apps/mcp/src/config/modules.ts +14 -13
  92. package/templates/apps/mcp/src/server.ts +8 -4
  93. package/templates/apps/mcp/src/tools/index.ts +7 -22
  94. package/templates/apps/web/.env.example +15 -0
  95. package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
  96. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
  97. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
  98. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
  99. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
  100. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
  101. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
  102. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
  103. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
  104. package/templates/apps/web/app/[locale]/pricing/page.tsx +40 -0
  105. package/templates/apps/web/app/[locale]/privacy/page.tsx +40 -0
  106. package/templates/apps/web/app/[locale]/terms/page.tsx +40 -0
  107. package/templates/apps/web/app/llms.txt/route.ts +12 -9
  108. package/templates/apps/web/jest.config.cjs +19 -13
  109. package/templates/apps/web/lib/__tests__/server-api.test.ts +61 -0
  110. package/templates/apps/web/lib/server-api.ts +28 -12
  111. package/templates/apps/web/messages/en.json +58 -0
  112. package/templates/apps/web/next.config.mjs +1 -3
  113. package/templates/apps/web/package.json +0 -6
  114. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  115. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  116. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  117. package/templates/apps/web/public/favicon-16x16.png +0 -0
  118. package/templates/apps/web/public/favicon-32x32.png +0 -0
  119. package/templates/apps/web/public/favicon.ico +0 -0
  120. package/templates/apps/web/public/favicon.svg +6 -3
  121. package/templates/apps/web/public/lockup-horizontal.svg +4 -0
  122. package/templates/apps/web/public/logomark.svg +4 -0
  123. package/templates/apps/web/public/site.webmanifest +2 -2
  124. package/templates/apps/web/public/wordmark.svg +4 -0
  125. package/templates/database/CHANGELOG.md +47 -0
  126. package/templates/database/inbox/schema.prisma +67 -0
  127. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
  128. package/templates/database/package.json +1 -1
  129. package/templates/package.json +1 -1
  130. package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
  131. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
  132. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
  133. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
  134. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
  135. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
  136. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
  137. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
  138. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
  139. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
  140. package/templates/apps/api/src/__tests__/server.test.ts +0 -253
  141. package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
  142. package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
  143. package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
  144. package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
  145. package/templates/apps/api/src/bin/seed-presets.ts +0 -58
  146. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
  147. package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
  148. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
  149. package/templates/apps/api/src/lib/stripe.ts +0 -52
  150. package/templates/apps/api/src/lib/tenant-db.ts +0 -225
  151. package/templates/apps/api/src/lib/ws-token.ts +0 -91
  152. package/templates/apps/api/src/middleware/tenant.ts +0 -99
  153. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
  154. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
  155. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
  156. package/templates/apps/api/src/routes/billing/index.ts +0 -36
  157. package/templates/apps/api/src/routes/billing/portal.ts +0 -182
  158. package/templates/apps/api/src/routes/billing/read.ts +0 -75
  159. package/templates/apps/api/src/routes/billing/usage.ts +0 -153
  160. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
  161. package/templates/apps/api/src/routes/checkout/index.ts +0 -13
  162. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
  163. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
  164. package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
  165. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
  166. package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
  167. package/templates/apps/api/src/routes/flows/README.md +0 -147
  168. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
  169. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
  170. package/templates/apps/api/src/routes/flows/index.ts +0 -202
  171. package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
  172. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
  173. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
  174. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
  175. package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
  176. package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
  177. package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
  178. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
  179. package/templates/apps/api/src/routes/schedule/index.ts +0 -249
  180. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
  181. package/templates/apps/api/src/routes/slack/actions.ts +0 -177
  182. package/templates/apps/api/src/routes/slack/index.ts +0 -38
  183. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
  184. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
  185. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
  186. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
  187. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
  188. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
  189. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
  190. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
  191. package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
  192. package/templates/apps/api/src/routes/webhooks/README.md +0 -80
  193. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
  194. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
  195. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
  196. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
  197. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
  198. package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
  199. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
  200. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
  201. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
  202. package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
  203. package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
  204. package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
  205. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
  206. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
  207. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
  208. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
  209. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
  210. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
  211. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
  212. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
  213. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
  214. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
  215. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
  216. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
  217. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
  218. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
  219. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
  220. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
  221. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
  222. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
  223. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
  224. package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
  225. package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
  226. package/templates/apps/api/src/services/flow-engine.ts +0 -1296
  227. package/templates/apps/api/src/services/lead-promotion.ts +0 -176
  228. package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
  229. package/templates/apps/api/src/services/listing-promotion.ts +0 -342
  230. package/templates/apps/api/src/services/playbook-compile.ts +0 -398
  231. package/templates/apps/api/src/services/playbook-render.ts +0 -263
  232. package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
  233. package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
  234. package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
  235. package/templates/apps/api/src/services/recommender-capture.ts +0 -835
  236. package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
  237. package/templates/apps/api/src/services/scheduling/index.ts +0 -63
  238. package/templates/apps/api/src/services/scheduling/types.ts +0 -88
  239. package/templates/apps/api/src/services/tenant-context.ts +0 -451
  240. package/templates/apps/api/src/services/usage-metering.ts +0 -699
  241. package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
  242. package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
  243. package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
  244. package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
  245. package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
  246. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
  247. package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
  248. package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
  249. package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
  250. package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
  252. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
  253. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
  254. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
  255. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
  256. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
  257. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
  258. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
  259. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
  260. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
  261. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
  262. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
  263. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
  264. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
  265. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
  266. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
  267. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
  268. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
  269. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
  270. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
  271. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
  272. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
  273. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
  274. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
  275. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
  276. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
  277. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
  278. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
  279. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
  280. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
  281. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
  282. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
  283. package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
  284. package/templates/apps/web/content/en/blog/README.txt +0 -25
  285. package/templates/apps/web/content/en/docs/index.mdx +0 -28
  286. package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
  287. package/templates/apps/web/lib/blog-list-data.ts +0 -95
  288. package/templates/apps/web/lib/blog-post-data.ts +0 -79
  289. package/templates/apps/web/lib/source.ts +0 -34
  290. package/templates/apps/web/public/logomark-dark.svg +0 -4
  291. package/templates/apps/web/public/logomark-light.svg +0 -4
  292. package/templates/apps/web/source.config.ts +0 -78
@@ -1,254 +0,0 @@
1
- "use server";
2
-
3
- /**
4
- * checkout.actions.ts — the recommender's two email-moment Server Actions
5
- * (#4842 D7), siblings of `flow.actions.ts` (ADR 0013 `*.actions.ts`):
6
- *
7
- * - `startCheckoutAction` — Pay: validates with the shared
8
- * `flowCheckoutInputSchema` (#4841 — a body with NO money field; the
9
- * server recomputes `price()` from the register it served, PRD L104),
10
- * calls `flows.checkout`, and on `ok` hands the visitor to Stripe's
11
- * hosted page via `redirect(url)` (D1 — hosted Checkout, Sam 2026-09-05).
12
- * - `saveRecommendationAction` — "Email me this plan": validates with
13
- * `flowSaveInputSchema`, calls `flows.save`, returns the lead id.
14
- *
15
- * Both mirror `submitFlowStepAction`'s idiom exactly: shared-schema
16
- * validation, the visitor-IP forwarding helper (duplicated here — the
17
- * `flow.actions.ts` original is module-private and that file is this unit's
18
- * regression floor), a status → kind map, and NEVER a throw to the client.
19
- * Logging is PII-safe: slug + kind only — the email is never logged.
20
- *
21
- * `redirect()` throws `NEXT_REDIRECT` by design, so it is called OUTSIDE the
22
- * try/catch that maps a thrown api-client error to `network` — inside it,
23
- * the catch would swallow the very navigation the action exists to perform.
24
- *
25
- * The Stripe return URLs are BUILT HERE, never accepted from the client
26
- * (security review, #4875): a direct caller could otherwise mint a session
27
- * that redirects post-payment to an arbitrary host, since the shared
28
- * `flowRedirectUrlSchema` pins only `http(s)` + a host. The client hands over
29
- * the permalink `payload` it already has; the action decodes it, requires it
30
- * to name THIS flow, and composes `<site origin>/<safe locale>/flows/<slug>/
31
- * results/<payload>?checkout=success|cancelled` from the app's own configured
32
- * origin (`resolveBaseUrl()` — the same source `layout.tsx` / `sitemap.xml`
33
- * use for every absolute URL).
34
- */
35
-
36
- import { flows } from "@working-theory/api-client";
37
- import type { FlowCheckoutInputRaw, FlowSaveInput } from "@working-theory/validation";
38
- import {
39
- decodePermalink,
40
- flowCheckoutInputSchema,
41
- flowSaveInputSchema,
42
- flowSlugSchema,
43
- } from "@working-theory/validation";
44
- import { headers } from "next/headers";
45
- import { redirect } from "next/navigation";
46
-
47
- import { resolveBaseUrl } from "../../../../src/lib/routes";
48
-
49
- export type EmailMomentFailureKind =
50
- | "invalid_input"
51
- | "not_configured"
52
- | "rate_limited"
53
- | "unavailable"
54
- | "network";
55
-
56
- export interface EmailMomentFailure {
57
- ok: false;
58
- kind: EmailMomentFailureKind;
59
- retryAfterSeconds?: number;
60
- }
61
-
62
- /** `startCheckoutAction` never returns on success — it `redirect()`s. */
63
- export type CheckoutActionResult = EmailMomentFailure;
64
-
65
- /**
66
- * The client-supplied checkout input: the wire body MINUS the two return
67
- * URLs (server-built, above) PLUS the permalink `payload` they are built from.
68
- */
69
- export type StartCheckoutInput = Omit<FlowCheckoutInputRaw, "successUrl" | "cancelUrl"> & {
70
- payload: string;
71
- };
72
-
73
- // Mirrors `FlowStepperClient.tsx:80–88` (#4707) — the locale segment of a
74
- // built URL is validated against the same build-time-baked allowlist, never
75
- // interpolated raw.
76
- const DEFAULT_LOCALE = "en";
77
- const ENABLED_LOCALES = (process.env.NEXT_PUBLIC_ENABLED_LOCALES ?? DEFAULT_LOCALE).split(",");
78
-
79
- /** Validate a candidate locale against the configured set; unknown → default. */
80
- function resolveSafeLocale(candidate: string): string {
81
- return ENABLED_LOCALES.includes(candidate) ? candidate : DEFAULT_LOCALE;
82
- }
83
-
84
- /**
85
- * The permalink's absolute URL on THIS site, or `undefined` when the payload
86
- * is not a valid permalink of `slug` (never a URL to somewhere else).
87
- */
88
- function permalinkUrlFor(slug: string, locale: string, payload: string): string | undefined {
89
- const decoded = decodePermalink(payload);
90
- if (!decoded.ok || decoded.value.flowSlug !== slug) return undefined;
91
- const origin = resolveBaseUrl().replace(/\/+$/, "");
92
- return `${origin}/${resolveSafeLocale(locale)}/flows/${slug}/results/${payload}`;
93
- }
94
-
95
- export type SaveActionResult = { ok: true; leadId: string } | EmailMomentFailure;
96
-
97
- /**
98
- * Reads the visitor's real IP from THIS action's own incoming request —
99
- * never from the browser-supplied payload — preferring `x-forwarded-for`
100
- * and falling back to `x-real-ip`. Returns `undefined` rather than throwing
101
- * on any failure (a local copy of `flow.actions.ts`'s helper, #4730).
102
- */
103
- async function resolveVisitorIpInit(): Promise<
104
- { headers: Record<string, string> } | undefined
105
- > {
106
- try {
107
- const incoming = await headers();
108
- const visitorIp =
109
- incoming.get("x-forwarded-for") ?? incoming.get("x-real-ip");
110
- return visitorIp
111
- ? { headers: { "x-forwarded-for": visitorIp } }
112
- : undefined;
113
- } catch {
114
- return undefined;
115
- }
116
- }
117
-
118
- /** PII-safe error identity for logs — the `recommender.ts` idiom (`err.name` only). */
119
- function errorName(err: unknown): string {
120
- return err instanceof Error ? err.name : "unknown";
121
- }
122
-
123
- /** The shared `status → kind` map (D7). Never throws. */
124
- function failureFor(res: { status: number; body: unknown }): EmailMomentFailure {
125
- if (res.status === 400) return { ok: false, kind: "invalid_input" };
126
- if (res.status === 409) return { ok: false, kind: "not_configured" };
127
- if (res.status === 429) {
128
- const body = res.body as { retryAfterSeconds?: number } | null;
129
- return {
130
- ok: false,
131
- kind: "rate_limited",
132
- ...(typeof body?.retryAfterSeconds === "number"
133
- ? { retryAfterSeconds: body.retryAfterSeconds }
134
- : {}),
135
- };
136
- }
137
- return { ok: false, kind: "unavailable" };
138
- }
139
-
140
- /**
141
- * exactOptionalPropertyTypes-safe rebuild of the parsed body: Zod's inferred
142
- * optional fields type as `T | undefined`, which does not structurally match
143
- * the api-client's `sessionId?: string` / `locale?: string` — so an absent
144
- * optional key is OMITTED rather than sent as an explicit `undefined`.
145
- */
146
- function emailMomentFields(parsed: FlowSaveInput): FlowSaveInput {
147
- const { answers, email, locale, sessionId } = parsed;
148
- return {
149
- answers,
150
- email,
151
- ...(locale !== undefined ? { locale } : {}),
152
- ...(sessionId !== undefined ? { sessionId } : {}),
153
- };
154
- }
155
-
156
- export async function startCheckoutAction(
157
- slug: string,
158
- input: StartCheckoutInput,
159
- ): Promise<CheckoutActionResult> {
160
- const parsedSlug = flowSlugSchema.safeParse(slug);
161
- if (!parsedSlug.success) return { ok: false, kind: "invalid_input" };
162
-
163
- // Only the named fields cross into the wire body — a caller-supplied
164
- // `successUrl` / `cancelUrl` (or any other stray key) is dropped here.
165
- const { answers, email, locale, sessionId, offerKey, seats, period, creditStop, payload } =
166
- input;
167
- if (typeof payload !== "string" || payload.length === 0 || payload.length > 4096) {
168
- return { ok: false, kind: "invalid_input" };
169
- }
170
- const permalinkUrl = permalinkUrlFor(parsedSlug.data, locale ?? DEFAULT_LOCALE, payload);
171
- if (permalinkUrl === undefined) return { ok: false, kind: "invalid_input" };
172
-
173
- const parsed = flowCheckoutInputSchema.safeParse({
174
- answers,
175
- email,
176
- ...(locale !== undefined ? { locale } : {}),
177
- ...(sessionId !== undefined ? { sessionId } : {}),
178
- offerKey,
179
- seats,
180
- period,
181
- ...(creditStop !== undefined ? { creditStop } : {}),
182
- successUrl: `${permalinkUrl}?checkout=success`,
183
- cancelUrl: `${permalinkUrl}?checkout=cancelled`,
184
- });
185
- if (!parsed.success) return { ok: false, kind: "invalid_input" };
186
-
187
- const wireBody: FlowCheckoutInputRaw = {
188
- ...emailMomentFields(parsed.data),
189
- offerKey: parsed.data.offerKey,
190
- seats: parsed.data.seats,
191
- period: parsed.data.period,
192
- creditStop: parsed.data.creditStop,
193
- successUrl: parsed.data.successUrl,
194
- cancelUrl: parsed.data.cancelUrl,
195
- };
196
-
197
- let url: string;
198
- try {
199
- const visitorIpInit = await resolveVisitorIpInit();
200
- const res = visitorIpInit
201
- ? await flows.checkout(parsedSlug.data, wireBody, visitorIpInit)
202
- : await flows.checkout(parsedSlug.data, wireBody);
203
- if (!res.ok) {
204
- const failure = failureFor(res);
205
- console.error(
206
- `[startCheckoutAction] slug=${parsedSlug.data} kind=${failure.kind} status=${res.status}`,
207
- );
208
- return failure;
209
- }
210
- url = res.body.url;
211
- } catch (err) {
212
- // Name only — never the error object (it can carry the request payload).
213
- console.error(
214
- `[startCheckoutAction] slug=${parsedSlug.data} kind=network errName=${errorName(err)}`,
215
- );
216
- return { ok: false, kind: "network" };
217
- }
218
-
219
- // Outside the try/catch on purpose — see the file header.
220
- redirect(url);
221
- }
222
-
223
- export async function saveRecommendationAction(
224
- slug: string,
225
- body: FlowSaveInput,
226
- ): Promise<SaveActionResult> {
227
- const parsedSlug = flowSlugSchema.safeParse(slug);
228
- if (!parsedSlug.success) return { ok: false, kind: "invalid_input" };
229
-
230
- const parsed = flowSaveInputSchema.safeParse(body);
231
- if (!parsed.success) return { ok: false, kind: "invalid_input" };
232
-
233
- const payload = emailMomentFields(parsed.data);
234
-
235
- try {
236
- const visitorIpInit = await resolveVisitorIpInit();
237
- const res = visitorIpInit
238
- ? await flows.save(parsedSlug.data, payload, visitorIpInit)
239
- : await flows.save(parsedSlug.data, payload);
240
- if (!res.ok) {
241
- const failure = failureFor(res);
242
- console.error(
243
- `[saveRecommendationAction] slug=${parsedSlug.data} kind=${failure.kind} status=${res.status}`,
244
- );
245
- return failure;
246
- }
247
- return { ok: true, leadId: res.body.leadId };
248
- } catch (err) {
249
- console.error(
250
- `[saveRecommendationAction] slug=${parsedSlug.data} kind=network errName=${errorName(err)}`,
251
- );
252
- return { ok: false, kind: "network" };
253
- }
254
- }