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,370 @@
1
+ /**
2
+ * Applies a module bundle's declarative `patches` (spec 5418 § 3 D1) onto an
3
+ * already-scaffolded target — the only mechanism that edits a file the
4
+ * buyer's scaffold already has (`installMap`/`postInstall`, spec 5373 § D7,
5
+ * can only add a file or print a message). Computes every patched file in
6
+ * memory first; only after every target's in-memory pass succeeds does a
7
+ * second pass write the changed files back to disk, once (D4) — so a
8
+ * conflict, a missing target, or an unparseable target anywhere in the
9
+ * manifest's patch set leaves the scaffold exactly as `copyTree`/`installMap`
10
+ * left it, nothing partial.
11
+ *
12
+ * `kind` decides how a target is read and re-serialized:
13
+ * - "json" — `package.json` (object-shaped fields, `merge-keys`) and
14
+ * `tsconfig.json` (array-shaped, `append-unique`). Parsed with
15
+ * `JSON.parse`, mutated in place, re-serialized with
16
+ * `JSON.stringify(obj, null, 2) + "\n"` (D2). A `package.json` target
17
+ * then runs through `substituteWorkspaceVersions`, mirroring
18
+ * `copy.ts`'s `isPackageJson` branch for a freshly-copied file.
19
+ * - "next-config" / "jest-config" — treated as text, never parsed or
20
+ * evaluated (NFR1 — no AST dependency). Each op is one of five named,
21
+ * idempotent, anchored transforms (D3): checked "already applied" →
22
+ * "anchor found, apply" → "not found, unparseable" — never a
23
+ * best-effort splice.
24
+ *
25
+ * Every failure mode here is one of D4's classes 1-3 (conflict, missing
26
+ * target, unparseable target). Class 4 (unknown kind/op) is caught earlier,
27
+ * at manifest validation (D6, `extract-module-bundle.ts`), before this
28
+ * function is ever called — every op here is assumed to be a recognized
29
+ * vocabulary member for its patch's `kind`.
30
+ */
31
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
32
+ import { basename, resolve } from "node:path";
33
+ import { substituteWorkspaceVersions, } from "../substitute.js";
34
+ /** D4 class 1 — a merge-keys key, or a JS-config anchor's already-applied value, disagrees. */
35
+ function conflict(target, locator, existing, wanted) {
36
+ return new Error(`patch conflict in ${target}: "${locator}" is ${JSON.stringify(existing)}, module wants ${JSON.stringify(wanted)}`);
37
+ }
38
+ /** D4 class 2 — `resolve(targetDir, target)` does not exist on disk. */
39
+ function missingTarget(target) {
40
+ return new Error(`patch target missing: ${target} does not exist in this scaffold`);
41
+ }
42
+ /** D4 class 3 — a json target fails JSON.parse. */
43
+ function unparseableJson(target) {
44
+ return new Error(`patch target unparseable: ${target} is not valid JSON`);
45
+ }
46
+ /** D4 class 3 — a next-config/jest-config anchor (or a JSON `path`) cannot be located at all. */
47
+ function unparseableAnchor(target, anchor) {
48
+ return new Error(`patch target unparseable: ${target} — could not locate "${anchor}"`);
49
+ }
50
+ /**
51
+ * Navigate `path` into `root`, requiring every intermediate segment to
52
+ * already resolve to a plain object. Returns the immediate parent + final
53
+ * key, or `null` if any intermediate segment is missing / not an object —
54
+ * the caller decides what "the final key's own container is missing or the
55
+ * wrong shape" means for its own op (D2: never create structure the buyer
56
+ * never had; a missing/wrong-shape container is `unparseableAnchor`, the
57
+ * same disposition the "if under-specified" resolution gives `merge-keys`
58
+ * and, by symmetry, `append-unique`).
59
+ */
60
+ function resolvePathParent(root, path) {
61
+ let cur = root;
62
+ for (let i = 0; i < path.length - 1; i++) {
63
+ const seg = path[i];
64
+ if (typeof cur !== "object" ||
65
+ cur === null ||
66
+ Array.isArray(cur) ||
67
+ !Object.prototype.hasOwnProperty.call(cur, seg)) {
68
+ return null;
69
+ }
70
+ cur = cur[seg];
71
+ }
72
+ if (typeof cur !== "object" || cur === null || Array.isArray(cur))
73
+ return null;
74
+ return { parent: cur, key: path[path.length - 1] };
75
+ }
76
+ function deepEqual(a, b) {
77
+ return JSON.stringify(a) === JSON.stringify(b);
78
+ }
79
+ /**
80
+ * Resolve `workspace:*` to the concrete published semver BEFORE the
81
+ * merge-keys comparison/write, not only at final text-level serialization
82
+ * (D2 mirrors `copy.ts`'s `isPackageJson` branch, but that branch runs on a
83
+ * FRESHLY-COPIED file that never gets re-read; a patch target does — a
84
+ * second `applyModulePatches` run reads back the ALREADY-substituted `^x.y.z`
85
+ * value, so comparing it against a still-literal `"workspace:*"` `entries`
86
+ * value would misreport idempotent re-application as a conflict). Reuses
87
+ * `substituteWorkspaceVersions` itself (same regex, same throw-on-missing
88
+ * behavior) rather than re-deriving the caret-prefix rule.
89
+ */
90
+ function resolveEntryValue(key, value, versions) {
91
+ if (value !== "workspace:*" || !key.startsWith("@working-theory/"))
92
+ return value;
93
+ const substituted = substituteWorkspaceVersions(`"${key}": "workspace:*"`, versions);
94
+ const match = /:\s*"([^"]+)"$/.exec(substituted);
95
+ return match ? match[1] : value;
96
+ }
97
+ function applyJsonMergeKeys(target, root, op, versions, isPackageJson) {
98
+ const resolved = resolvePathParent(root, op.path);
99
+ const container = resolved ? resolved.parent[resolved.key] : undefined;
100
+ if (!resolved || typeof container !== "object" || container === null || Array.isArray(container)) {
101
+ throw unparseableAnchor(target, op.path.join("."));
102
+ }
103
+ const obj = container;
104
+ for (const [key, rawValue] of Object.entries(op.entries)) {
105
+ const value = isPackageJson ? resolveEntryValue(key, rawValue, versions) : rawValue;
106
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
107
+ obj[key] = value;
108
+ }
109
+ else if (!deepEqual(obj[key], value)) {
110
+ throw conflict(target, [...op.path, key].join("."), obj[key], value);
111
+ }
112
+ // Present with the identical value — no-op (idempotent).
113
+ }
114
+ }
115
+ function applyJsonAppendUnique(target, root, op) {
116
+ const resolved = resolvePathParent(root, op.path);
117
+ const container = resolved ? resolved.parent[resolved.key] : undefined;
118
+ if (!resolved || !Array.isArray(container)) {
119
+ throw unparseableAnchor(target, op.path.join("."));
120
+ }
121
+ for (const item of op.items) {
122
+ if (!container.some((existing) => deepEqual(existing, item))) {
123
+ container.push(item);
124
+ }
125
+ }
126
+ }
127
+ function applyJsonOps(target, text, ops, versions, isPackageJson) {
128
+ let root;
129
+ try {
130
+ root = JSON.parse(text);
131
+ }
132
+ catch {
133
+ throw unparseableJson(target);
134
+ }
135
+ for (const op of ops) {
136
+ if (op.op === "merge-keys")
137
+ applyJsonMergeKeys(target, root, op, versions, isPackageJson);
138
+ else if (op.op === "append-unique")
139
+ applyJsonAppendUnique(target, root, op);
140
+ else {
141
+ throw new Error(`invalid module manifest: unknown patch op "${op.op}" for target ${target}`);
142
+ }
143
+ }
144
+ return `${JSON.stringify(root, null, 2)}\n`;
145
+ }
146
+ /** The `[...]` span of the array literal opened right after `anchorLiteral`, or `null` if the anchor/array can't be located. */
147
+ function findArraySpan(text, anchorLiteral) {
148
+ const anchorIdx = text.indexOf(anchorLiteral);
149
+ if (anchorIdx === -1)
150
+ return null;
151
+ const openIdx = text.indexOf("[", anchorIdx);
152
+ if (openIdx === -1)
153
+ return null;
154
+ let depth = 0;
155
+ let closeIdx = -1;
156
+ for (let i = openIdx; i < text.length; i++) {
157
+ if (text[i] === "[")
158
+ depth++;
159
+ else if (text[i] === "]") {
160
+ depth--;
161
+ if (depth === 0) {
162
+ closeIdx = i;
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ if (closeIdx === -1)
168
+ return null;
169
+ return { openIdx, closeIdx };
170
+ }
171
+ /** Whether `elementLine` already occurs WITHIN the array literal opened after `anchorLiteral` (not merely somewhere in the file). */
172
+ function arrayContainsElement(text, anchorLiteral, elementLine) {
173
+ const span = findArraySpan(text, anchorLiteral);
174
+ if (span === null)
175
+ return false;
176
+ return text.slice(span.openIdx, span.closeIdx).includes(elementLine);
177
+ }
178
+ /** Insert `elementLine` (already including its own trailing "\n") as the last element of the array literal opened right after `anchorLiteral`. */
179
+ function insertAsLastArrayElement(text, anchorLiteral, elementLine) {
180
+ const span = findArraySpan(text, anchorLiteral);
181
+ if (span === null)
182
+ return null;
183
+ const { openIdx, closeIdx } = span;
184
+ const body = text.slice(openIdx + 1, closeIdx);
185
+ const trailingMatch = /\n[ \t]*$/.exec(body);
186
+ const trailingWs = trailingMatch ? trailingMatch[0] : "\n";
187
+ const bodyCore = trailingMatch ? body.slice(0, -trailingWs.length) : body;
188
+ const newBody = `${bodyCore}\n${elementLine.replace(/\n$/, "")}${trailingWs}`;
189
+ return text.slice(0, openIdx + 1) + newBody + text.slice(closeIdx);
190
+ }
191
+ /** Insert `entryText` (including its own trailing "\n") immediately after the line containing `anchorLiteral`. */
192
+ function insertAfterAnchorLine(text, anchorLiteral, entryText) {
193
+ const anchorIdx = text.indexOf(anchorLiteral);
194
+ if (anchorIdx === -1)
195
+ return null;
196
+ const lineEnd = text.indexOf("\n", anchorIdx);
197
+ if (lineEnd === -1)
198
+ return null;
199
+ return text.slice(0, lineEnd + 1) + entryText + text.slice(lineEnd + 1);
200
+ }
201
+ function applyAddTranspilePackage(target, text, op) {
202
+ const elementLine = ` "${op.package}",\n`;
203
+ if (arrayContainsElement(text, "transpilePackages: [", elementLine))
204
+ return text; // already applied — idempotent no-op.
205
+ const next = insertAsLastArrayElement(text, "transpilePackages: [", elementLine);
206
+ if (next === null)
207
+ throw unparseableAnchor(target, "transpilePackages: [");
208
+ return next;
209
+ }
210
+ function applyWrapExportWith(target, text, op) {
211
+ let next = text;
212
+ if (!next.includes(op.importLine)) {
213
+ const re = /^import\b.*\n/gm;
214
+ let lastEnd = -1;
215
+ let m;
216
+ while ((m = re.exec(next)))
217
+ lastEnd = m.index + m[0].length;
218
+ if (lastEnd === -1)
219
+ throw unparseableAnchor(target, op.importLine.trimEnd());
220
+ next = next.slice(0, lastEnd) + op.importLine + next.slice(lastEnd);
221
+ }
222
+ if (next.includes(op.replaceWith))
223
+ return next; // already applied — idempotent no-op.
224
+ if (!next.includes(op.find))
225
+ throw unparseableAnchor(target, op.find);
226
+ return next.replace(op.find, op.replaceWith);
227
+ }
228
+ function applyAddTransformIgnorePattern(target, text, op) {
229
+ if (text.includes(op.replaceWith))
230
+ return text; // already applied — idempotent no-op.
231
+ if (!text.includes(op.find))
232
+ throw unparseableAnchor(target, op.find);
233
+ return text.replace(op.find, op.replaceWith);
234
+ }
235
+ /**
236
+ * Shared by add-module-name-mapper / add-transform: a keyed entry inside an
237
+ * object literal. `renderedValue` is the exact FILE-TEXT form of the wanted
238
+ * value (quoted for a mapper's string value; verbatim for a transform's raw
239
+ * source text) — used only for the idempotence text comparison and the
240
+ * insertion. `plainWantedValue` is the LOGICAL value — used for the D4
241
+ * class-1 message, which JSON.stringifies it itself (D4's `<newValueJSON>`
242
+ * shape); passing the already-quoted `renderedValue` there would double-quote it.
243
+ * `unwrapQuotes` strips one layer of literal `"…"` off the captured
244
+ * existing-file text before it goes into the message, for the same reason —
245
+ * on for add-module-name-mapper (always a quoted string), off for
246
+ * add-transform (verbatim source, not necessarily quoted at all).
247
+ */
248
+ function applyKeyedEntry(target, text, anchorLiteral, locatorPrefix, pattern, renderedValue, plainWantedValue, unwrapQuotes, entryText) {
249
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
250
+ const keyRe = new RegExp(`"${escaped}":\\s*(.+?),`, "s");
251
+ const match = keyRe.exec(text);
252
+ if (match) {
253
+ const existingRaw = match[1].trim();
254
+ if (existingRaw === renderedValue.trim())
255
+ return text; // already applied — idempotent no-op.
256
+ const plainExisting = unwrapQuotes
257
+ ? existingRaw.replace(/^"([\s\S]*)"$/, "$1")
258
+ : existingRaw;
259
+ throw conflict(target, `${locatorPrefix}["${pattern}"]`, plainExisting, plainWantedValue);
260
+ }
261
+ const next = insertAfterAnchorLine(text, anchorLiteral, entryText);
262
+ if (next === null)
263
+ throw unparseableAnchor(target, anchorLiteral);
264
+ return next;
265
+ }
266
+ function applyAddModuleNameMapper(target, text, op) {
267
+ const renderedValue = `"${op.value}"`;
268
+ const entryText = ` "${op.pattern}":\n ${renderedValue},\n`;
269
+ return applyKeyedEntry(target, text, "moduleNameMapper: {", "moduleNameMapper", op.pattern, renderedValue, op.value, true, entryText);
270
+ }
271
+ function applyAddTransform(target, text, op) {
272
+ const entryText = ` "${op.pattern}": ${op.value},\n`;
273
+ return applyKeyedEntry(target, text, "transform: {", "transform", op.pattern, op.value, op.value, false, entryText);
274
+ }
275
+ function applyTextOps(target, text, ops) {
276
+ let next = text;
277
+ for (const op of ops) {
278
+ if (op.op === "add-transpile-package")
279
+ next = applyAddTranspilePackage(target, next, op);
280
+ else if (op.op === "wrap-export-with")
281
+ next = applyWrapExportWith(target, next, op);
282
+ else if (op.op === "add-transform-ignore-pattern")
283
+ next = applyAddTransformIgnorePattern(target, next, op);
284
+ else if (op.op === "add-module-name-mapper")
285
+ next = applyAddModuleNameMapper(target, next, op);
286
+ else if (op.op === "add-transform")
287
+ next = applyAddTransform(target, next, op);
288
+ else {
289
+ throw new Error(`invalid module manifest: unknown patch op "${op.op}" for target ${target}`);
290
+ }
291
+ }
292
+ return next;
293
+ }
294
+ /**
295
+ * Compute every patched target's new content in memory — throws on the
296
+ * first D4 class 1-3 failure encountered (deterministic order — targets in
297
+ * `patches` array order) and writes nothing anywhere; returns only the
298
+ * targets whose content actually changed. Pure with respect to the
299
+ * filesystem: reads go through `options.readTarget`, nothing is written.
300
+ * Pair with `writeModulePatches` for the second pass, deferred until every
301
+ * other part of the caller's own operation (e.g. `copyTree`/`installMap`)
302
+ * has ALSO succeeded, so a patch failure can never leave partial output
303
+ * from an earlier step in place either.
304
+ */
305
+ export function computeModulePatches(patches, options) {
306
+ if (patches.length === 0)
307
+ return [];
308
+ // Group by target — a manifest MAY list the same target across multiple
309
+ // ModulePatch entries; every op targeting a file applies, in array order,
310
+ // to that one file's in-memory buffer.
311
+ const byTarget = new Map();
312
+ for (const patch of patches) {
313
+ const existing = byTarget.get(patch.target);
314
+ if (existing)
315
+ existing.ops.push(...patch.ops);
316
+ else
317
+ byTarget.set(patch.target, { kind: patch.kind, ops: [...patch.ops] });
318
+ }
319
+ const computed = [];
320
+ for (const [target, { kind, ops }] of byTarget) {
321
+ const original = options.readTarget(target);
322
+ if (original === undefined)
323
+ throw missingTarget(target);
324
+ let content;
325
+ if (kind === "json") {
326
+ const isPackageJson = basename(target) === "package.json";
327
+ content = applyJsonOps(target, original, ops, options.versions, isPackageJson);
328
+ if (isPackageJson) {
329
+ content = substituteWorkspaceVersions(content, options.versions);
330
+ }
331
+ }
332
+ else if (kind === "next-config" || kind === "jest-config") {
333
+ content = applyTextOps(target, original, ops);
334
+ }
335
+ else {
336
+ throw new Error(`invalid module manifest: unknown patch kind "${kind}" for target ${target}`);
337
+ }
338
+ if (content !== original)
339
+ computed.push({ target, content });
340
+ }
341
+ return computed;
342
+ }
343
+ /** Write every `computeModulePatches` result to disk, resolved against `baseDir`, once. */
344
+ export function writeModulePatches(computed, baseDir) {
345
+ const baseAbs = resolve(baseDir);
346
+ for (const { target, content } of computed) {
347
+ writeFileSync(resolve(baseAbs, target), content);
348
+ }
349
+ }
350
+ /**
351
+ * Apply every patch in `patches` onto the scaffold at `targetDir` — reads
352
+ * every target directly from `targetDir` (no staging tree involved) and
353
+ * writes once everything computes cleanly. The convenience form for a
354
+ * patch set whose targets are ALL pre-existing scaffold files; a caller
355
+ * whose bundle also delivers some of those targets itself (via plain
356
+ * content or `installMap`) should use `computeModulePatches`/
357
+ * `writeModulePatches` directly so a patch failure can roll back the
358
+ * whole merge, not just the patch step.
359
+ */
360
+ export function applyModulePatches(patches, targetDir, options) {
361
+ const targetAbs = resolve(targetDir);
362
+ const readTarget = (target) => {
363
+ const absPath = resolve(targetAbs, target);
364
+ if (!existsSync(absPath))
365
+ return undefined;
366
+ return readFileSync(absPath, "utf8");
367
+ };
368
+ const computed = computeModulePatches(patches, { ...options, readTarget });
369
+ writeModulePatches(computed, targetAbs);
370
+ }
@@ -1,3 +1,4 @@
1
+ import type { WorkspaceVersionMap } from "../substitute.js";
1
2
  export interface ExtractModuleBundleOptions {
2
3
  /** The fetched bundle bytes (a gzipped tar archive). */
3
4
  bytes: Uint8Array;
@@ -5,12 +6,59 @@ export interface ExtractModuleBundleOptions {
5
6
  targetDir: string;
6
7
  /** The module name — used only for error messages and staging dir naming. */
7
8
  moduleName: string;
9
+ /**
10
+ * Canonical `@working-theory/*` package versions (spec 5418 § 3 D2), for
11
+ * `package.json` patch targets' `workspace:*` substitution — the same map
12
+ * `copyTemplate` threads to `substituteWorkspaceVersions`. Defaults to `{}`
13
+ * (a bundle with no `patches`, or none using `workspace:*`, needs nothing).
14
+ */
15
+ versions?: WorkspaceVersionMap;
16
+ /**
17
+ * Present ⟹ `moduleName` names a SITE TEMPLATE, not a module, and the
18
+ * archive's root manifest is `ailk-template.json` cross-checked against
19
+ * this list (record D10, DD4). See `InstallBundleTreeOptions`.
20
+ */
21
+ templateRequires?: readonly string[];
8
22
  }
9
23
  /**
10
24
  * Extract a fetched module bundle into `targetDir`, merging its files onto
11
25
  * the tree `copyTemplate` already wrote. Throws on any unsafe entry,
12
- * decompress error, or limit overrun — `targetDir` is left untouched on
13
- * failure (validation completes in a throwaway staging dir before any file
14
- * is copied into place).
26
+ * decompress error, limit overrun, invalid `installMap`, or `patches`
27
+ * failure (D4) — `targetDir` is left untouched on failure in every one of
28
+ * those cases: validation AND patch computation both complete before any
29
+ * file is copied or written into place.
15
30
  */
16
31
  export declare function extractModuleBundle(options: ExtractModuleBundleOptions): Promise<void>;
32
+ export interface InstallBundleTreeOptions {
33
+ /**
34
+ * The validated tree whose files land in the scaffold. Two source roots,
35
+ * one install step (spec 5481 § 3 DD7): the staging dir
36
+ * `extractModuleBundle` above produced, or `site-templates/<name>/` read
37
+ * straight off disk on the keyless Free path.
38
+ */
39
+ sourceRoot: string;
40
+ /** The already-scaffolded target directory the files land in. */
41
+ targetDir: string;
42
+ /** The module or template name — error messages and nothing else. */
43
+ name: string;
44
+ /**
45
+ * Present ⟹ this tree is a SITE TEMPLATE's: the root manifest is
46
+ * `ailk-template.json`, it MUST be present and parseable, and its
47
+ * `requires` must agree with this list — the client registry's own
48
+ * (record D10, DD4). Absent ⟹ a module bundle, read exactly as before.
49
+ */
50
+ templateRequires?: readonly string[];
51
+ /** Canonical `@working-theory/*` versions for `workspace:*` substitution in patch targets. */
52
+ versions?: WorkspaceVersionMap;
53
+ }
54
+ /**
55
+ * Apply one resolved manifest's `installMap`, plain content and `patches`
56
+ * from `sourceRoot` into `targetDir`.
57
+ *
58
+ * Extracted from `extractModuleBundle` so the Free template path — which has
59
+ * no archive to unpack, only a local tree — runs the SAME step (DD7). The
60
+ * atomicity guarantee is this function's, not its callers': validation AND
61
+ * patch computation both complete before the first byte is written, so a
62
+ * rejected tree leaves `targetDir` exactly as it was.
63
+ */
64
+ export declare function installBundleTree(options: InstallBundleTreeOptions): void;