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
@@ -0,0 +1,195 @@
1
+ /**
2
+ * The comparison instrument for #5314 — diffs the routes the server ACTUALLY
3
+ * registers against each surface that claims to describe them.
4
+ *
5
+ * Two surfaces describe this API's endpoints: the root index `endpoints`
6
+ * object (`server.ts`) and the OpenAPI `paths` object (`spec.ts`). Before
7
+ * #5314 both were hand-maintained literals, and both had drifted — 11 and 7
8
+ * entries against 68 registered routes, with `/v1/project-listings/public`
9
+ * missing from both and `/v1/billing/usage` missing from the root index.
10
+ *
11
+ * This module only COMPARES. The derivation that keeps the surfaces honest
12
+ * lives in `spec.ts` beside the shapes it produces; keeping the comparison
13
+ * separate is what lets the gate assert against a surface it did not build.
14
+ *
15
+ * Deliberately free of any `fastify` or `spec.ts` import: it takes plain data,
16
+ * so the gate can feed it a SERVED document (fetched over `inject`) rather
17
+ * than the object the code under test just returned.
18
+ */
19
+
20
+ /** One route as Fastify registered it. `url` is Fastify-shaped (`:param`). */
21
+ export interface RegisteredRoute {
22
+ /** Uppercase HTTP method. HEAD is excluded — Fastify derives it from GET. */
23
+ method: string;
24
+ /** Fastify path, e.g. `/v1/content/:type/:slug`. */
25
+ url: string;
26
+ }
27
+
28
+ /** One surface's disagreement with the registered route table. */
29
+ export interface SurfaceDiff {
30
+ /** Which surface this diff describes. */
31
+ surface: "openapi-paths" | "root-index-endpoints";
32
+ /** `METHOD /path` registered by the server but absent from the surface. */
33
+ missingFromSurface: string[];
34
+ /** `METHOD /path` named by the surface but not registered by the server. */
35
+ orphanedInSurface: string[];
36
+ }
37
+
38
+ /**
39
+ * Fastify path → OpenAPI path template: `/v1/content/:type` becomes
40
+ * `/v1/content/{type}`. OpenAPI 3.1 has no `:param` form, so a document that
41
+ * published the Fastify string would be describing a path no client can build.
42
+ */
43
+ export function toOpenApiPath(fastifyPath: string): string {
44
+ return fastifyPath
45
+ .split("/")
46
+ .map((segment) =>
47
+ segment.startsWith(":") ? `{${segment.slice(1)}}` : segment,
48
+ )
49
+ .join("/");
50
+ }
51
+
52
+ /** The comparison key both sides normalise to: `GET /v1/content/types`. */
53
+ export function routeKey(method: string, path: string): string {
54
+ return `${method.toUpperCase()} ${path}`;
55
+ }
56
+
57
+ /** Registered routes as OpenAPI-shaped comparison keys. */
58
+ function registeredOpenApiKeys(routes: RegisteredRoute[]): Set<string> {
59
+ return new Set(routes.map((r) => routeKey(r.method, toOpenApiPath(r.url))));
60
+ }
61
+
62
+ /** Registered routes as Fastify-shaped comparison keys. */
63
+ function registeredFastifyKeys(routes: RegisteredRoute[]): Set<string> {
64
+ return new Set(routes.map((r) => routeKey(r.method, r.url)));
65
+ }
66
+
67
+ /**
68
+ * The OpenAPI methods a path item declares. Non-method keys an OpenAPI path
69
+ * item may legally carry (`parameters`, `summary`, `description`, `servers`,
70
+ * `$ref`) are not operations and must not be compared as if they were.
71
+ */
72
+ const OPENAPI_METHODS = new Set([
73
+ "get",
74
+ "put",
75
+ "post",
76
+ "delete",
77
+ "options",
78
+ "head",
79
+ "patch",
80
+ "trace",
81
+ ]);
82
+
83
+ /**
84
+ * Comparison keys a served OpenAPI `paths` object claims.
85
+ *
86
+ * Takes the served document's `paths` as plain data so the gate can compare
87
+ * what an agent would actually fetch, not what the builder returned.
88
+ */
89
+ export function openApiSurfaceKeys(
90
+ paths: Record<string, Record<string, unknown>>,
91
+ ): Set<string> {
92
+ const keys = new Set<string>();
93
+ for (const [path, item] of Object.entries(paths ?? {})) {
94
+ for (const method of Object.keys(item ?? {})) {
95
+ if (!OPENAPI_METHODS.has(method.toLowerCase())) continue;
96
+ if (method.toLowerCase() === "head") continue;
97
+ keys.add(routeKey(method, path));
98
+ }
99
+ }
100
+ return keys;
101
+ }
102
+
103
+ /**
104
+ * Comparison keys a root-index `endpoints` object claims.
105
+ *
106
+ * Each value is `"METHOD[/METHOD...] /path"` — the shape the hand-authored
107
+ * object already used (`"GET/PATCH /v1/user/profile"`), preserved so no
108
+ * published entry changes shape. A value that does not parse is reported as
109
+ * an orphan rather than skipped: an unparseable entry is precisely the kind
110
+ * of hand-edit this instrument exists to catch.
111
+ */
112
+ export function rootIndexSurfaceKeys(
113
+ endpoints: Record<string, string>,
114
+ ): Set<string> {
115
+ const keys = new Set<string>();
116
+ for (const value of Object.values(endpoints ?? {})) {
117
+ const match = /^([A-Za-z/]+)\s+(\S+)$/.exec(value ?? "");
118
+ if (!match?.[1] || !match[2]) {
119
+ keys.add(`UNPARSEABLE ${value}`);
120
+ continue;
121
+ }
122
+ for (const method of match[1].split("/")) {
123
+ if (!method) continue;
124
+ if (method.toUpperCase() === "HEAD") continue;
125
+ keys.add(routeKey(method, match[2]));
126
+ }
127
+ }
128
+ return keys;
129
+ }
130
+
131
+ function diff(registered: Set<string>, surface: Set<string>) {
132
+ return {
133
+ missingFromSurface: [...registered].filter((k) => !surface.has(k)).sort(),
134
+ orphanedInSurface: [...surface].filter((k) => !registered.has(k)).sort(),
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Diff the registered route table against both surfaces.
140
+ *
141
+ * `paths` is the served OpenAPI `paths` object (OpenAPI-templated); `endpoints`
142
+ * is the served root-index `endpoints` object (Fastify-shaped, matching the
143
+ * strings that surface has always published). Each is compared in its own
144
+ * vocabulary so neither surface is judged against a shape it never used.
145
+ */
146
+ export function compareSurfaces(
147
+ registered: RegisteredRoute[],
148
+ paths: Record<string, Record<string, unknown>>,
149
+ endpoints: Record<string, string>,
150
+ ): SurfaceDiff[] {
151
+ return [
152
+ {
153
+ surface: "openapi-paths",
154
+ ...diff(registeredOpenApiKeys(registered), openApiSurfaceKeys(paths)),
155
+ },
156
+ {
157
+ surface: "root-index-endpoints",
158
+ ...diff(
159
+ registeredFastifyKeys(registered),
160
+ rootIndexSurfaceKeys(endpoints),
161
+ ),
162
+ },
163
+ ];
164
+ }
165
+
166
+ /** Whether any surface disagrees with the registered route table. */
167
+ export function hasDrift(diffs: SurfaceDiff[]): boolean {
168
+ return diffs.some(
169
+ (d) => d.missingFromSurface.length > 0 || d.orphanedInSurface.length > 0,
170
+ );
171
+ }
172
+
173
+ /**
174
+ * Render the drift for a failing assertion, or `""` when there is none.
175
+ *
176
+ * A surface that agrees contributes nothing, so the empty string means "no
177
+ * drift" and can be asserted against directly. Counts come first so a reader
178
+ * sees the scale before the list, then EVERY entry — a truncated drift report
179
+ * is how a gate teaches people to ignore it.
180
+ */
181
+ export function formatSurfaceDrift(diffs: SurfaceDiff[]): string {
182
+ const lines: string[] = [];
183
+ for (const d of diffs) {
184
+ if (d.missingFromSurface.length === 0 && d.orphanedInSurface.length === 0) {
185
+ continue;
186
+ }
187
+ lines.push(
188
+ `${d.surface}: ${d.missingFromSurface.length} registered route(s) missing from the surface, ` +
189
+ `${d.orphanedInSurface.length} surface entr(ies) with no registered route`,
190
+ );
191
+ for (const k of d.missingFromSurface) lines.push(` missing: ${k}`);
192
+ for (const k of d.orphanedInSurface) lines.push(` orphaned: ${k}`);
193
+ }
194
+ return lines.join("\n");
195
+ }
@@ -61,14 +61,28 @@ const validBody = {
61
61
 
62
62
  describe("POST /api/content/:type", () => {
63
63
  let app: FastifyInstance;
64
+ // The write handlers only register when content-write is enabled
65
+ // (routes/content/index.ts's isModuleEnabled('content-write') gate); an
66
+ // unset AILK_ENABLED_MODULES no longer defaults to every module on
67
+ // (D-44, #5368), so this suite enables it explicitly.
68
+ const priorEnabledModules = process.env.AILK_ENABLED_MODULES;
64
69
 
65
70
  beforeEach(async () => {
71
+ process.env.AILK_ENABLED_MODULES = "content-write";
66
72
  app = require("fastify")();
67
73
  await app.register(contentRoutes);
68
74
  jest.clearAllMocks();
69
75
  mockCheckLicense.mockReset();
70
76
  });
71
77
 
78
+ afterAll(() => {
79
+ if (priorEnabledModules === undefined) {
80
+ delete process.env.AILK_ENABLED_MODULES;
81
+ } else {
82
+ process.env.AILK_ENABLED_MODULES = priorEnabledModules;
83
+ }
84
+ });
85
+
72
86
  afterEach(async () => {
73
87
  await app.close();
74
88
  });
@@ -49,14 +49,28 @@ function freeLicense() {
49
49
 
50
50
  describe("DELETE /api/content/:type/:slug", () => {
51
51
  let app: FastifyInstance;
52
+ // The write handlers only register when content-write is enabled
53
+ // (routes/content/index.ts's isModuleEnabled('content-write') gate); an
54
+ // unset AILK_ENABLED_MODULES no longer defaults to every module on
55
+ // (D-44, #5368), so this suite enables it explicitly.
56
+ const priorEnabledModules = process.env.AILK_ENABLED_MODULES;
52
57
 
53
58
  beforeEach(async () => {
59
+ process.env.AILK_ENABLED_MODULES = "content-write";
54
60
  app = require("fastify")();
55
61
  await app.register(contentRoutes);
56
62
  jest.clearAllMocks();
57
63
  mockCheckLicense.mockReset();
58
64
  });
59
65
 
66
+ afterAll(() => {
67
+ if (priorEnabledModules === undefined) {
68
+ delete process.env.AILK_ENABLED_MODULES;
69
+ } else {
70
+ process.env.AILK_ENABLED_MODULES = priorEnabledModules;
71
+ }
72
+ });
73
+
60
74
  afterEach(async () => {
61
75
  await app.close();
62
76
  });
@@ -61,14 +61,28 @@ const validBody = {
61
61
 
62
62
  describe("PATCH /api/content/:type/:slug", () => {
63
63
  let app: FastifyInstance;
64
+ // The write handlers only register when content-write is enabled
65
+ // (routes/content/index.ts's isModuleEnabled('content-write') gate); an
66
+ // unset AILK_ENABLED_MODULES no longer defaults to every module on
67
+ // (D-44, #5368), so this suite enables it explicitly.
68
+ const priorEnabledModules = process.env.AILK_ENABLED_MODULES;
64
69
 
65
70
  beforeEach(async () => {
71
+ process.env.AILK_ENABLED_MODULES = "content-write";
66
72
  app = require("fastify")();
67
73
  await app.register(contentRoutes);
68
74
  jest.clearAllMocks();
69
75
  mockCheckLicense.mockReset();
70
76
  });
71
77
 
78
+ afterAll(() => {
79
+ if (priorEnabledModules === undefined) {
80
+ delete process.env.AILK_ENABLED_MODULES;
81
+ } else {
82
+ process.env.AILK_ENABLED_MODULES = priorEnabledModules;
83
+ }
84
+ });
85
+
72
86
  afterEach(async () => {
73
87
  await app.close();
74
88
  });
@@ -24,25 +24,52 @@
24
24
  import type { FastifyPluginAsync } from "fastify";
25
25
 
26
26
  import { isModuleEnabled } from "../../config/modules.js";
27
+ import { loadSlice } from "../../lib/slice-load.js";
27
28
 
28
29
  import { getPageRoute } from "./get-page.js";
29
30
  import { listPagesRoute } from "./list-pages.js";
30
31
  import { listTypesRoute } from "./list-types.js";
31
32
 
32
33
  /**
33
- * Content-write slice entry points, as { specifier, exportName }.
34
+ * Content-write slice entry points, as { specifier, exportName, load }.
34
35
  *
35
36
  * What defeats `tsc`'s resolution is the INDIRECTION, not the type: TypeScript
36
37
  * resolves `import("...")` only when the argument is a string-literal
37
- * EXPRESSION written at the call site. Reading the specifier from a variable
38
- * makes it unresolvable regardless of whether that variable's type is `string`
39
- * or a literal (verified both ways). `string` is used here for readability, not
40
- * because a literal type would leak.
38
+ * EXPRESSION written at the call site.
39
+ *
40
+ * WHICH indirection matters, and #5304 is the correction. This table used to
41
+ * hold the specifier as a string and the loop read it out per iteration — a
42
+ * shape `@vercel/nft` cannot evaluate either, so the deploy-time tracer did not
43
+ * emit these files and a deployed site shipped without them. A module-level
44
+ * constant dereferenced into `import()` is unresolvable to `tsc` and traceable
45
+ * to `nft`, so the strip and the deployment are both satisfied. `specifier` is
46
+ * carried for the drift message below and reads the same constant as `load`, so
47
+ * the two cannot disagree.
41
48
  */
42
- const WRITE_ROUTES: ReadonlyArray<{ specifier: string; exportName: string }> = [
43
- { specifier: "./create.js", exportName: "createPageRoute" },
44
- { specifier: "./update.js", exportName: "updatePageRoute" },
45
- { specifier: "./delete.js", exportName: "deletePageRoute" },
49
+ const CREATE_ROUTE = "./create.js";
50
+ const UPDATE_ROUTE = "./update.js";
51
+ const DELETE_ROUTE = "./delete.js";
52
+
53
+ const WRITE_ROUTES: ReadonlyArray<{
54
+ specifier: string;
55
+ exportName: string;
56
+ load: () => Promise<unknown>;
57
+ }> = [
58
+ {
59
+ specifier: CREATE_ROUTE,
60
+ exportName: "createPageRoute",
61
+ load: () => import(CREATE_ROUTE),
62
+ },
63
+ {
64
+ specifier: UPDATE_ROUTE,
65
+ exportName: "updatePageRoute",
66
+ load: () => import(UPDATE_ROUTE),
67
+ },
68
+ {
69
+ specifier: DELETE_ROUTE,
70
+ exportName: "deletePageRoute",
71
+ load: () => import(DELETE_ROUTE),
72
+ },
46
73
  ];
47
74
 
48
75
  export const contentRoutes: FastifyPluginAsync = async (fastify) => {
@@ -51,8 +78,8 @@ export const contentRoutes: FastifyPluginAsync = async (fastify) => {
51
78
  await fastify.register(getPageRoute);
52
79
 
53
80
  if (isModuleEnabled("content-write")) {
54
- for (const { specifier, exportName } of WRITE_ROUTES) {
55
- const mod = (await import(specifier)) as Record<
81
+ for (const { specifier, exportName, load } of WRITE_ROUTES) {
82
+ const mod = (await loadSlice(specifier, load)) as Record<
56
83
  string,
57
84
  FastifyPluginAsync | undefined
58
85
  >;
@@ -1,3 +1,6 @@
1
+ // Not marked oss: false (D-48/#5366 escalation) — tests the
2
+ // always-shipping project-listings anonymous surface; see
3
+ // routes/project-listings/me.ts's header comment.
1
4
  /**
2
5
  * @file configured-application.test.ts
3
6
  *
@@ -43,6 +46,13 @@ jest.mock("../../../lib/prisma.js");
43
46
 
44
47
  jest.mock("@working-theory/email", () => ({
45
48
  sendLeadNotification: jest.fn().mockResolvedValue({ ok: true, messageId: "msg-test" }),
49
+ // #5406 — `start` now reaches the resume email. Declared here for the reason
50
+ // docs/lessons/project-listings/04 records: a mock factory that omits an
51
+ // export the code under test calls leaves it `undefined` at the callsite.
52
+ // These suites configure no `resumeEmail`, so the send short-circuits before
53
+ // this is reached; it is declared so a future fixture that DOES configure
54
+ // one fails on its assertion rather than on a missing mock.
55
+ sendListingResumeEmail: jest.fn().mockResolvedValue({ ok: true, messageId: "msg-resume" }),
46
56
  }));
47
57
 
48
58
  jest.mock("@working-theory/license", () => ({
@@ -217,6 +227,13 @@ function installStore(): void {
217
227
  anythingElse: null,
218
228
  address: null,
219
229
  questions: null,
230
+ // #5407 — a row written before the columns existed, and a freshly
231
+ // started draft on a site that asks for one name line: parts null,
232
+ // no phone, no verification stamp.
233
+ founderFirstName: null,
234
+ founderLastName: null,
235
+ founderPhone: null,
236
+ emailVerifiedAt: null,
220
237
  founderCompany: null,
221
238
  leadId: null,
222
239
  headline: null,
@@ -1,3 +1,6 @@
1
+ // Not marked oss: false (D-48/#5366 escalation) — tests the
2
+ // always-shipping project-listings anonymous surface; see
3
+ // routes/project-listings/me.ts's header comment.
1
4
  /**
2
5
  * @file drafts.test.ts
3
6
  *
@@ -40,6 +43,13 @@ jest.mock("../../../lib/prisma.js");
40
43
 
41
44
  jest.mock("@working-theory/email", () => ({
42
45
  sendLeadNotification: jest.fn().mockResolvedValue({ ok: true, messageId: "msg-test" }),
46
+ // #5406 — `start` now reaches the resume email. Declared here for the reason
47
+ // docs/lessons/project-listings/04 records: a mock factory that omits an
48
+ // export the code under test calls leaves it `undefined` at the callsite.
49
+ // These suites configure no `resumeEmail`, so the send short-circuits before
50
+ // this is reached; it is declared so a future fixture that DOES configure
51
+ // one fails on its assertion rather than on a missing mock.
52
+ sendListingResumeEmail: jest.fn().mockResolvedValue({ ok: true, messageId: "msg-resume" }),
43
53
  }));
44
54
 
45
55
  jest.mock("@working-theory/license", () => ({
@@ -193,6 +203,13 @@ function installStore(): void {
193
203
  anythingElse: null,
194
204
  address: null,
195
205
  questions: null,
206
+ // #5407 — a row written before the columns existed, and a freshly
207
+ // started draft on a site that asks for one name line: parts null,
208
+ // no phone, no verification stamp.
209
+ founderFirstName: null,
210
+ founderLastName: null,
211
+ founderPhone: null,
212
+ emailVerifiedAt: null,
196
213
  founderCompany: null,
197
214
  leadId: null,
198
215
  headline: null,